]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/parser.js
build custom layer of Dojo to speed up loading of tt-rss (refs #293)
[tt-rss.git] / lib / dojo / parser.js
CommitLineData
2f01fe57
AD
1/*
2 Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
5*/
6
7
a089699c
AD
8if(!dojo._hasResource["dojo.parser"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dojo.parser"] = true;
2f01fe57
AD
10dojo.provide("dojo.parser");
11dojo.require("dojo.date.stamp");
a089699c
AD
12
13new Date("X"); // workaround for #11279, new Date("") == NaN
14
15dojo.parser = new function(){
16 // summary: The Dom/Widget parsing package
17
18 var d = dojo;
19 this._attrName = d._scopeName + "Type";
20 this._query = "[" + this._attrName + "]";
21
22 function val2type(/*Object*/ value){
23 // summary:
24 // Returns name of type of given value.
25
26 if(d.isString(value)){ return "string"; }
27 if(typeof value == "number"){ return "number"; }
28 if(typeof value == "boolean"){ return "boolean"; }
29 if(d.isFunction(value)){ return "function"; }
30 if(d.isArray(value)){ return "array"; } // typeof [] == "object"
31 if(value instanceof Date) { return "date"; } // assume timestamp
32 if(value instanceof d._Url){ return "url"; }
33 return "object";
34 }
35
36 function str2obj(/*String*/ value, /*String*/ type){
37 // summary:
38 // Convert given string value to given type
39 switch(type){
40 case "string":
41 return value;
42 case "number":
43 return value.length ? Number(value) : NaN;
44 case "boolean":
45 // for checked/disabled value might be "" or "checked". interpret as true.
46 return typeof value == "boolean" ? value : !(value.toLowerCase()=="false");
47 case "function":
48 if(d.isFunction(value)){
49 // IE gives us a function, even when we say something like onClick="foo"
50 // (in which case it gives us an invalid function "function(){ foo }").
51 // Therefore, convert to string
52 value=value.toString();
53 value=d.trim(value.substring(value.indexOf('{')+1, value.length-1));
54 }
55 try{
56 if(value === "" || value.search(/[^\w\.]+/i) != -1){
57 // The user has specified some text for a function like "return x+5"
58 return new Function(value);
59 }else{
60 // The user has specified the name of a function like "myOnClick"
61 // or a single word function "return"
62 return d.getObject(value, false) || new Function(value);
63 }
64 }catch(e){ return new Function(); }
65 case "array":
66 return value ? value.split(/\s*,\s*/) : [];
67 case "date":
68 switch(value){
69 case "": return new Date(""); // the NaN of dates
70 case "now": return new Date(); // current date
71 default: return d.date.stamp.fromISOString(value);
72 }
73 case "url":
74 return d.baseUrl + value;
75 default:
76 return d.fromJson(value);
77 }
78 }
79
80 var instanceClasses = {
81 // map from fully qualified name (like "dijit.Button") to structure like
82 // { cls: dijit.Button, params: {label: "string", disabled: "boolean"} }
83 };
84
85 // Widgets like BorderContainer add properties to _Widget via dojo.extend().
86 // If BorderContainer is loaded after _Widget's parameter list has been cached,
87 // we need to refresh that parameter list (for _Widget and all widgets that extend _Widget).
88 dojo.connect(dojo, "extend", function(){
89 instanceClasses = {};
90 });
91
92 function getClassInfo(/*String*/ className){
93 // className:
94 // fully qualified name (like "dijit.form.Button")
95 // returns:
96 // structure like
97 // {
98 // cls: dijit.Button,
99 // params: { label: "string", disabled: "boolean"}
100 // }
101
102 if(!instanceClasses[className]){
103 // get pointer to widget class
104 var cls = d.getObject(className);
105 if(!cls){ return null; } // class not defined [yet]
106
107 var proto = cls.prototype;
108
109 // get table of parameter names & types
110 var params = {}, dummyClass = {};
111 for(var name in proto){
112 if(name.charAt(0)=="_"){ continue; } // skip internal properties
113 if(name in dummyClass){ continue; } // skip "constructor" and "toString"
114 var defVal = proto[name];
115 params[name]=val2type(defVal);
116 }
117
118 instanceClasses[className] = { cls: cls, params: params };
119 }
120 return instanceClasses[className];
121 }
122
123 this._functionFromScript = function(script){
124 var preamble = "";
125 var suffix = "";
126 var argsStr = script.getAttribute("args");
127 if(argsStr){
128 d.forEach(argsStr.split(/\s*,\s*/), function(part, idx){
129 preamble += "var "+part+" = arguments["+idx+"]; ";
130 });
131 }
132 var withStr = script.getAttribute("with");
133 if(withStr && withStr.length){
134 d.forEach(withStr.split(/\s*,\s*/), function(part){
135 preamble += "with("+part+"){";
136 suffix += "}";
137 });
138 }
139 return new Function(preamble+script.innerHTML+suffix);
140 }
141
142 this.instantiate = function(/* Array */nodes, /* Object? */mixin, /* Object? */args){
143 // summary:
144 // Takes array of nodes, and turns them into class instances and
145 // potentially calls a startup method to allow them to connect with
146 // any children.
147 // nodes: Array
148 // Array of nodes or objects like
149 // | {
150 // | type: "dijit.form.Button",
151 // | node: DOMNode,
152 // | scripts: [ ... ], // array of <script type="dojo/..."> children of node
153 // | inherited: { ... } // settings inherited from ancestors like dir, theme, etc.
154 // | }
155 // mixin: Object?
156 // An object that will be mixed in with each node in the array.
157 // Values in the mixin will override values in the node, if they
158 // exist.
159 // args: Object?
160 // An object used to hold kwArgs for instantiation.
161 // Supports 'noStart' and inherited.
162 var thelist = [], dp = dojo.parser;
163 mixin = mixin||{};
164 args = args||{};
165
166 d.forEach(nodes, function(obj){
167 if(!obj){ return; }
168
169 // Get pointers to DOMNode, dojoType string, and clsInfo (metadata about the dojoType), etc.s
170 var node, type, clsInfo, clazz, scripts;
171 if(obj.node){
172 // new format of nodes[] array, object w/lots of properties pre-computed for me
173 node = obj.node;
174 type = obj.type;
175 clsInfo = obj.clsInfo || (type && getClassInfo(type));
176 clazz = clsInfo && clsInfo.cls;
177 scripts = obj.scripts;
178 }else{
179 // old (backwards compatible) format of nodes[] array, simple array of DOMNodes
180 node = obj;
181 type = dp._attrName in mixin ? mixin[dp._attrName] : node.getAttribute(dp._attrName);
182 clsInfo = type && getClassInfo(type);
183 clazz = clsInfo && clsInfo.cls;
184 scripts = (clazz && (clazz._noScript || clazz.prototype._noScript) ? [] :
185 d.query("> script[type^='dojo/']", node));
186 }
187 if(!clsInfo){
188 throw new Error("Could not load class '" + type);
189 }
190
191 // Setup hash to hold parameter settings for this widget. Start with the parameter
192 // settings inherited from ancestors ("dir" and "lang").
193 // Inherited setting may later be overridden by explicit settings on node itself.
194 var params = {},
195 attributes = node.attributes;
196 if(args.defaults){
197 // settings for the document itself (or whatever subtree is being parsed)
198 dojo.mixin(params, args.defaults);
199 }
200 if(obj.inherited){
201 // settings from dir=rtl or lang=... on a node above this node
202 dojo.mixin(params, obj.inherited);
203 }
204
205 // read parameters (ie, attributes) specified on DOMNode
206 // clsInfo.params lists expected params like {"checked": "boolean", "n": "number"}
207 for(var name in clsInfo.params){
208 var item = name in mixin?{value:mixin[name],specified:true}:attributes.getNamedItem(name);
209 if(!item || (!item.specified && (!dojo.isIE || name.toLowerCase()!="value"))){ continue; }
210 var value = item.value;
211 // Deal with IE quirks for 'class' and 'style'
212 switch(name){
213 case "class":
214 value = "className" in mixin?mixin.className:node.className;
215 break;
216 case "style":
217 value = "style" in mixin?mixin.style:(node.style && node.style.cssText); // FIXME: Opera?
218 }
219 var _type = clsInfo.params[name];
220 if(typeof value == "string"){
221 params[name] = str2obj(value, _type);
222 }else{
223 params[name] = value;
224 }
225 }
226
227 // Process <script type="dojo/*"> script tags
228 // <script type="dojo/method" event="foo"> tags are added to params, and passed to
229 // the widget on instantiation.
230 // <script type="dojo/method"> tags (with no event) are executed after instantiation
231 // <script type="dojo/connect" event="foo"> tags are dojo.connected after instantiation
232 // note: dojo/* script tags cannot exist in self closing widgets, like <input />
233 var connects = [], // functions to connect after instantiation
234 calls = []; // functions to call after instantiation
235
236 d.forEach(scripts, function(script){
237 node.removeChild(script);
238 var event = script.getAttribute("event"),
239 type = script.getAttribute("type"),
240 nf = d.parser._functionFromScript(script);
241 if(event){
242 if(type == "dojo/connect"){
243 connects.push({event: event, func: nf});
244 }else{
245 params[event] = nf;
246 }
247 }else{
248 calls.push(nf);
249 }
250 });
251
252 var markupFactory = clazz.markupFactory || clazz.prototype && clazz.prototype.markupFactory;
253 // create the instance
254 var instance = markupFactory ? markupFactory(params, node, clazz) : new clazz(params, node);
255 thelist.push(instance);
256
257 // map it to the JS namespace if that makes sense
258 var jsname = node.getAttribute("jsId");
259 if(jsname){
260 d.setObject(jsname, instance);
261 }
262
263 // process connections and startup functions
264 d.forEach(connects, function(connect){
265 d.connect(instance, connect.event, null, connect.func);
266 });
267 d.forEach(calls, function(func){
268 func.call(instance);
269 });
270 });
271
272 // Call startup on each top level instance if it makes sense (as for
273 // widgets). Parent widgets will recursively call startup on their
274 // (non-top level) children
275 if(!mixin._started){
276 // TODO: for 2.0, when old instantiate() API is desupported, store parent-child
277 // relationships in the nodes[] array so that no getParent() call is needed.
278 // Note that will require a parse() call from ContentPane setting a param that the
279 // ContentPane is the parent widget (so that the parse doesn't call startup() on the
280 // ContentPane's children)
281 d.forEach(thelist, function(instance){
282 if( !args.noStart && instance &&
283 instance.startup &&
284 !instance._started &&
285 (!instance.getParent || !instance.getParent())
286 ){
287 instance.startup();
288 }
289 });
290 }
291 return thelist;
292 };
293
294 this.parse = function(/*DomNode?*/ rootNode, /* Object? */ args){
295 // summary:
296 // Scan the DOM for class instances, and instantiate them.
297 //
298 // description:
299 // Search specified node (or root node) recursively for class instances,
300 // and instantiate them Searches for
301 // dojoType="qualified.class.name"
302 //
303 // rootNode: DomNode?
304 // A default starting root node from which to start the parsing. Can be
305 // omitted, defaulting to the entire document. If omitted, the `args`
306 // object can be passed in this place. If the `args` object has a
307 // `rootNode` member, that is used.
308 //
309 // args:
310 // a kwArgs object passed along to instantiate()
311 //
312 // * noStart: Boolean?
313 // when set will prevent the parser from calling .startup()
314 // when locating the nodes.
315 // * rootNode: DomNode?
316 // identical to the function's `rootNode` argument, though
317 // allowed to be passed in via this `args object.
318 // * inherited: Object
319 // Hash possibly containing dir and lang settings to be applied to
320 // parsed widgets, unless there's another setting on a sub-node that overrides
321 //
322 //
323 // example:
324 // Parse all widgets on a page:
325 // | dojo.parser.parse();
326 //
327 // example:
328 // Parse all classes within the node with id="foo"
329 // | dojo.parser.parse(dojo.byId(foo));
330 //
331 // example:
332 // Parse all classes in a page, but do not call .startup() on any
333 // child
334 // | dojo.parser.parse({ noStart: true })
335 //
336 // example:
337 // Parse all classes in a node, but do not call .startup()
338 // | dojo.parser.parse(someNode, { noStart:true });
339 // | // or
340 // | dojo.parser.parse({ noStart:true, rootNode: someNode });
341
342 // determine the root node based on the passed arguments.
343 var root;
344 if(!args && rootNode && rootNode.rootNode){
345 args = rootNode;
346 root = args.rootNode;
347 }else{
348 root = rootNode;
349 }
350
351 var attrName = this._attrName;
352 function scan(parent, list){
353 // summary:
354 // Parent is an Object representing a DOMNode, with or without a dojoType specified.
355 // Scan parent's children looking for nodes with dojoType specified, storing in list[].
356 // If parent has a dojoType, also collects <script type=dojo/*> children and stores in parent.scripts[].
357 // parent: Object
358 // Object representing the parent node, like
359 // | {
360 // | node: DomNode, // scan children of this node
361 // | inherited: {dir: "rtl"}, // dir/lang setting inherited from above node
362 // |
363 // | // attributes only set if node has dojoType specified
364 // | scripts: [], // empty array, put <script type=dojo/*> in here
365 // | clsInfo: { cls: dijit.form.Button, ...}
366 // | }
367 // list: DomNode[]
368 // Output array of objects (same format as parent) representing nodes to be turned into widgets
369
370 // Effective dir and lang settings on parent node, either set directly or inherited from grandparent
371 var inherited = dojo.clone(parent.inherited);
372 dojo.forEach(["dir", "lang"], function(name){
373 var val = parent.node.getAttribute(name);
374 if(val){
375 inherited[name] = val;
376 }
377 });
378
379 // if parent is a widget, then search for <script type=dojo/*> tags and put them in scripts[].
380 var scripts = parent.scripts;
381
382 // unless parent is a widget with the stopParser flag set, continue search for dojoType, recursively
383 var recurse = !parent.clsInfo || !parent.clsInfo.cls.prototype.stopParser;
384
385 // scan parent's children looking for dojoType and <script type=dojo/*>
386 for(var child = parent.node.firstChild; child; child = child.nextSibling){
387 if(child.nodeType == 1){
388 var type = recurse && child.getAttribute(attrName);
389 if(type){
390 // if dojoType specified, add to output array of nodes to instantiate
391 var params = {
392 "type": type,
393 clsInfo: getClassInfo(type), // note: won't find classes declared via dojo.Declaration
394 node: child,
395 scripts: [], // <script> nodes that are parent's children
396 inherited: inherited // dir & lang attributes inherited from parent
397 };
398 list.push(params);
399
400 // Recurse, collecting <script type="dojo/..."> children, and also looking for
401 // descendant nodes with dojoType specified (unless the widget has the stopParser flag),
402 scan(params, list);
403 }else if(scripts && child.nodeName.toLowerCase() == "script"){
404 // if <script type="dojo/...">, save in scripts[]
405 type = child.getAttribute("type");
406 if (type && /^dojo\//i.test(type)) {
407 scripts.push(child);
408 }
409 }else if(recurse){
410 // Recurse, looking for grandchild nodes with dojoType specified
411 scan({
412 node: child,
413 inherited: inherited
414 }, list);
415 }
416 }
417 }
418 }
419
420 // Make list of all nodes on page w/dojoType specified
421 var list = [];
422 scan({
423 node: root ? dojo.byId(root) : dojo.body(),
424 inherited: (args && args.inherited) || {
425 dir: dojo._isBodyLtr() ? "ltr" : "rtl"
426 }
427 }, list);
428
429 // go build the object instances
430 return this.instantiate(list, null, args); // Array
431 };
2f01fe57 432}();
a089699c
AD
433
434//Register the parser callback. It should be the first callback
435//after the a11y test.
436
2f01fe57 437(function(){
a089699c
AD
438 var parseRunner = function(){
439 if(dojo.config.parseOnLoad){
440 dojo.parser.parse();
441 }
442 };
443
444 // FIXME: need to clobber cross-dependency!!
445 if(dojo.exists("dijit.wai.onload") && (dijit.wai.onload === dojo._loaders[0])){
446 dojo._loaders.splice(1, 0, parseRunner);
447 }else{
448 dojo._loaders.unshift(parseRunner);
449 }
2f01fe57 450})();
a089699c 451
2f01fe57 452}