351 lines
12 KiB
JavaScript
351 lines
12 KiB
JavaScript
/*
|
|
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
|
|
Available via Academic Free License >= 2.1 OR the modified BSD license.
|
|
see: http://dojotoolkit.org/license for details
|
|
*/
|
|
|
|
|
|
if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
dojo._hasResource["dojo.html"] = true;
|
|
dojo.provide("dojo.html");
|
|
dojo.require("dojo.parser");
|
|
|
|
dojo.getObject("html", true, dojo);
|
|
|
|
// the parser might be needed..
|
|
(function(){ // private scope, sort of a namespace
|
|
|
|
// idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes
|
|
var idCounter = 0,
|
|
d = dojo;
|
|
|
|
dojo.html._secureForInnerHtml = function(/*String*/ cont){
|
|
// summary:
|
|
// removes !DOCTYPE and title elements from the html string.
|
|
//
|
|
// khtml is picky about dom faults, you can't attach a style or <title> node as child of body
|
|
// must go into head, so we need to cut out those tags
|
|
// cont:
|
|
// An html string for insertion into the dom
|
|
//
|
|
return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String
|
|
};
|
|
|
|
/*====
|
|
dojo.html._emptyNode = function(node){
|
|
// summary:
|
|
// removes all child nodes from the given node
|
|
// node: DOMNode
|
|
// the parent element
|
|
};
|
|
=====*/
|
|
dojo.html._emptyNode = dojo.empty;
|
|
|
|
dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){
|
|
// summary:
|
|
// inserts the given content into the given node
|
|
// node:
|
|
// the parent element
|
|
// content:
|
|
// the content to be set on the parent element.
|
|
// This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
|
|
|
|
// always empty
|
|
d.empty(node);
|
|
|
|
if(cont) {
|
|
if(typeof cont == "string") {
|
|
cont = d._toDom(cont, node.ownerDocument);
|
|
}
|
|
if(!cont.nodeType && d.isArrayLike(cont)) {
|
|
// handle as enumerable, but it may shrink as we enumerate it
|
|
for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) {
|
|
d.place( cont[i], node, "last");
|
|
}
|
|
} else {
|
|
// pass nodes, documentFragments and unknowns through to dojo.place
|
|
d.place(cont, node, "last");
|
|
}
|
|
}
|
|
|
|
// return DomNode
|
|
return node;
|
|
};
|
|
|
|
// we wrap up the content-setting operation in a object
|
|
dojo.declare("dojo.html._ContentSetter", null,
|
|
{
|
|
// node: DomNode|String
|
|
// An node which will be the parent element that we set content into
|
|
node: "",
|
|
|
|
// content: String|DomNode|DomNode[]
|
|
// The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes
|
|
content: "",
|
|
|
|
// id: String?
|
|
// Usually only used internally, and auto-generated with each instance
|
|
id: "",
|
|
|
|
// cleanContent: Boolean
|
|
// Should the content be treated as a full html document,
|
|
// and the real content stripped of <html>, <body> wrapper before injection
|
|
cleanContent: false,
|
|
|
|
// extractContent: Boolean
|
|
// Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection
|
|
extractContent: false,
|
|
|
|
// parseContent: Boolean
|
|
// Should the node by passed to the parser after the new content is set
|
|
parseContent: false,
|
|
|
|
// parserScope: String
|
|
// Flag passed to parser. Root for attribute names to search for. If scopeName is dojo,
|
|
// will search for data-dojo-type (or dojoType). For backwards compatibility
|
|
// reasons defaults to dojo._scopeName (which is "dojo" except when
|
|
// multi-version support is used, when it will be something like dojo16, dojo20, etc.)
|
|
parserScope: dojo._scopeName,
|
|
|
|
// startup: Boolean
|
|
// Start the child widgets after parsing them. Only obeyed if parseContent is true.
|
|
startup: true,
|
|
|
|
// lifecyle methods
|
|
constructor: function(/* Object */params, /* String|DomNode */node){
|
|
// summary:
|
|
// Provides a configurable, extensible object to wrap the setting on content on a node
|
|
// call the set() method to actually set the content..
|
|
|
|
// the original params are mixed directly into the instance "this"
|
|
dojo.mixin(this, params || {});
|
|
|
|
// give precedence to params.node vs. the node argument
|
|
// and ensure its a node, not an id string
|
|
node = this.node = dojo.byId( this.node || node );
|
|
|
|
if(!this.id){
|
|
this.id = [
|
|
"Setter",
|
|
(node) ? node.id || node.tagName : "",
|
|
idCounter++
|
|
].join("_");
|
|
}
|
|
},
|
|
set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){
|
|
// summary:
|
|
// front-end to the set-content sequence
|
|
// cont:
|
|
// An html string, node or enumerable list of nodes for insertion into the dom
|
|
// If not provided, the object's content property will be used
|
|
if(undefined !== cont){
|
|
this.content = cont;
|
|
}
|
|
// in the re-use scenario, set needs to be able to mixin new configuration
|
|
if(params){
|
|
this._mixin(params);
|
|
}
|
|
|
|
this.onBegin();
|
|
this.setContent();
|
|
this.onEnd();
|
|
|
|
return this.node;
|
|
},
|
|
setContent: function(){
|
|
// summary:
|
|
// sets the content on the node
|
|
|
|
var node = this.node;
|
|
if(!node) {
|
|
// can't proceed
|
|
throw new Error(this.declaredClass + ": setContent given no node");
|
|
}
|
|
try{
|
|
node = dojo.html._setNodeContent(node, this.content);
|
|
}catch(e){
|
|
// check if a domfault occurs when we are appending this.errorMessage
|
|
// like for instance if domNode is a UL and we try append a DIV
|
|
|
|
// FIXME: need to allow the user to provide a content error message string
|
|
var errMess = this.onContentError(e);
|
|
try{
|
|
node.innerHTML = errMess;
|
|
}catch(e){
|
|
console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e);
|
|
}
|
|
}
|
|
// always put back the node for the next method
|
|
this.node = node; // DomNode
|
|
},
|
|
|
|
empty: function() {
|
|
// summary
|
|
// cleanly empty out existing content
|
|
|
|
// destroy any widgets from a previous run
|
|
// NOTE: if you dont want this you'll need to empty
|
|
// the parseResults array property yourself to avoid bad things happenning
|
|
if(this.parseResults && this.parseResults.length) {
|
|
dojo.forEach(this.parseResults, function(w) {
|
|
if(w.destroy){
|
|
w.destroy();
|
|
}
|
|
});
|
|
delete this.parseResults;
|
|
}
|
|
// this is fast, but if you know its already empty or safe, you could
|
|
// override empty to skip this step
|
|
dojo.html._emptyNode(this.node);
|
|
},
|
|
|
|
onBegin: function(){
|
|
// summary
|
|
// Called after instantiation, but before set();
|
|
// It allows modification of any of the object properties
|
|
// - including the node and content provided - before the set operation actually takes place
|
|
// This default implementation checks for cleanContent and extractContent flags to
|
|
// optionally pre-process html string content
|
|
var cont = this.content;
|
|
|
|
if(dojo.isString(cont)){
|
|
if(this.cleanContent){
|
|
cont = dojo.html._secureForInnerHtml(cont);
|
|
}
|
|
|
|
if(this.extractContent){
|
|
var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im);
|
|
if(match){ cont = match[1]; }
|
|
}
|
|
}
|
|
|
|
// clean out the node and any cruft associated with it - like widgets
|
|
this.empty();
|
|
|
|
this.content = cont;
|
|
return this.node; /* DomNode */
|
|
},
|
|
|
|
onEnd: function(){
|
|
// summary
|
|
// Called after set(), when the new content has been pushed into the node
|
|
// It provides an opportunity for post-processing before handing back the node to the caller
|
|
// This default implementation checks a parseContent flag to optionally run the dojo parser over the new content
|
|
if(this.parseContent){
|
|
// populates this.parseResults if you need those..
|
|
this._parse();
|
|
}
|
|
return this.node; /* DomNode */
|
|
},
|
|
|
|
tearDown: function(){
|
|
// summary
|
|
// manually reset the Setter instance if its being re-used for example for another set()
|
|
// description
|
|
// tearDown() is not called automatically.
|
|
// In normal use, the Setter instance properties are simply allowed to fall out of scope
|
|
// but the tearDown method can be called to explicitly reset this instance.
|
|
delete this.parseResults;
|
|
delete this.node;
|
|
delete this.content;
|
|
},
|
|
|
|
onContentError: function(err){
|
|
return "Error occured setting content: " + err;
|
|
},
|
|
|
|
_mixin: function(params){
|
|
// mix properties/methods into the instance
|
|
// TODO: the intention with tearDown is to put the Setter's state
|
|
// back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params)
|
|
// so we could do something here to move the original properties aside for later restoration
|
|
var empty = {}, key;
|
|
for(key in params){
|
|
if(key in empty){ continue; }
|
|
// TODO: here's our opportunity to mask the properties we dont consider configurable/overridable
|
|
// .. but history shows we'll almost always guess wrong
|
|
this[key] = params[key];
|
|
}
|
|
},
|
|
_parse: function(){
|
|
// summary:
|
|
// runs the dojo parser over the node contents, storing any results in this.parseResults
|
|
// Any errors resulting from parsing are passed to _onError for handling
|
|
|
|
var rootNode = this.node;
|
|
try{
|
|
// store the results (widgets, whatever) for potential retrieval
|
|
var inherited = {};
|
|
dojo.forEach(["dir", "lang", "textDir"], function(name){
|
|
if(this[name]){
|
|
inherited[name] = this[name];
|
|
}
|
|
}, this);
|
|
this.parseResults = dojo.parser.parse({
|
|
rootNode: rootNode,
|
|
noStart: !this.startup,
|
|
inherited: inherited,
|
|
scope: this.parserScope
|
|
});
|
|
}catch(e){
|
|
this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id);
|
|
}
|
|
},
|
|
|
|
_onError: function(type, err, consoleText){
|
|
// summary:
|
|
// shows user the string that is returned by on[type]Error
|
|
// overide/implement on[type]Error and return your own string to customize
|
|
var errText = this['on' + type + 'Error'].call(this, err);
|
|
if(consoleText){
|
|
console.error(consoleText, err);
|
|
}else if(errText){ // a empty string won't change current content
|
|
dojo.html._setNodeContent(this.node, errText, true);
|
|
}
|
|
}
|
|
}); // end dojo.declare()
|
|
|
|
dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){
|
|
// summary:
|
|
// inserts (replaces) the given content into the given node. dojo.place(cont, node, "only")
|
|
// may be a better choice for simple HTML insertion.
|
|
// description:
|
|
// Unless you need to use the params capabilities of this method, you should use
|
|
// dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting
|
|
// an HTML string into the DOM, but it only handles inserting an HTML string as DOM
|
|
// elements, or inserting a DOM node. dojo.place does not handle NodeList insertions
|
|
// or the other capabilities as defined by the params object for this method.
|
|
// node:
|
|
// the parent element that will receive the content
|
|
// cont:
|
|
// the content to be set on the parent element.
|
|
// This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes
|
|
// params:
|
|
// Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter
|
|
// example:
|
|
// A safe string/node/nodelist content replacement/injection with hooks for extension
|
|
// Example Usage:
|
|
// dojo.html.set(node, "some string");
|
|
// dojo.html.set(node, contentNode, {options});
|
|
// dojo.html.set(node, myNode.childNodes, {options});
|
|
if(undefined == cont){
|
|
console.warn("dojo.html.set: no cont argument provided, using empty string");
|
|
cont = "";
|
|
}
|
|
if(!params){
|
|
// simple and fast
|
|
return dojo.html._setNodeContent(node, cont, true);
|
|
}else{
|
|
// more options but slower
|
|
// note the arguments are reversed in order, to match the convention for instantiation via the parser
|
|
var op = new dojo.html._ContentSetter(dojo.mixin(
|
|
params,
|
|
{ content: cont, node: node }
|
|
));
|
|
return op.set();
|
|
}
|
|
};
|
|
})();
|
|
|
|
}
|