├── lib
├── aop.js
├── event.js
├── dom-event.js
├── main-webworker.js
├── query.js
├── json.js
├── array.js
├── main-neutral.js
├── main-browser.js
├── io-query.js
├── window.js
├── browser.js
├── dom-form.js
├── color.js
├── dom-class.js
├── async.js
├── lang.js
├── dom-geometry.js
├── fx.js
└── declare.js
├── tests
└── main.js
├── package.json
├── README
├── experimental
└── templating
│ ├── ste.html
│ ├── ste.js
│ └── ste2.js
├── LICENSE
└── require.js
/lib/aop.js:
--------------------------------------------------------------------------------
1 | // placeholder
--------------------------------------------------------------------------------
/lib/event.js:
--------------------------------------------------------------------------------
1 | // placeholder
--------------------------------------------------------------------------------
/tests/main.js:
--------------------------------------------------------------------------------
1 | // placeholder
--------------------------------------------------------------------------------
/lib/dom-event.js:
--------------------------------------------------------------------------------
1 | // placeholder
--------------------------------------------------------------------------------
/lib/main-webworker.js:
--------------------------------------------------------------------------------
1 | // placeholder
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pulsar",
3 | "author": "Eugene Lazutkin",
4 | "dependencies": []
5 | }
--------------------------------------------------------------------------------
/lib/query.js:
--------------------------------------------------------------------------------
1 | // placeholder
2 |
3 | // We should use ACME and Sizzle. The problem is they both are too big.
4 | // One possible solution is to keep a simplified query library in the base, and allow to include other libraries
5 | // explicitly. See: http://github.com/uxebu/embedjs/tree/master/src/query/ for possible alternatives.
6 | // In any case we should provide light-weight alternatives as a source for generating a custom base.
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Yet unexplained project.
2 |
3 | Base candidates
4 | ---------------
5 |
6 | Dojo Core modules
7 |
8 | DojoX: dojox.lang, dojox.html, dojox.NodeList
9 |
10 | plugd: connectlive.js, debugging.js, escape.js, keys.js, NodeList-data.js, plugin.js, script.js, trigger.js
11 |
12 | Honorable mentions: dojo-on by Ben Lowery http://github.com/blowery/dojo-on
13 |
14 | Core candidates
15 | ---------------
16 |
17 | plugd modules
--------------------------------------------------------------------------------
/lib/json.js:
--------------------------------------------------------------------------------
1 | // placeholder
2 |
3 | //TODO: decide how to implement JSON functions, if we are to implement them
4 |
5 | // One possible solution is to assume it is always available in non-browser environments, and augment IE < 9 with
6 | // Base2. Pro: all platforms would allow to use JSON as specified by the standards.
7 |
8 | // Another possible solution is to provide a default implementation as in Dojo 1.x, which delegates to native
9 | // JSON functions and replace this module with real implementations for legacy platforms.
10 |
11 | // For now it is skipped.
12 |
--------------------------------------------------------------------------------
/lib/array.js:
--------------------------------------------------------------------------------
1 | // placeholder
2 |
3 | //TODO: decide how to implement array extras, if we are to implement them
4 |
5 | // One possible solution is to assume it is always available in non-browser environments, and augment IE < 9 with
6 | // Base2. Pro: all platforms would allow to call array extras directly on arrays as methods.
7 |
8 | // Another possible solution is to provide a default implementation as in Dojo 1.x, which delegates to native
9 | // array extras and replace this module with real implementations for legacy platforms.
10 |
11 | // For now it is skipped.
12 |
--------------------------------------------------------------------------------
/lib/main-neutral.js:
--------------------------------------------------------------------------------
1 | define(["lang", "Deferred"], function(lang, Deferred){
2 | var dojo = {
3 | version: {
4 | major: 2, minor: 0, patch: 0, flag: "dev", revision: 0,
5 | toString: function(){
6 | return dojo.version.major + "." + dojo.version.minor + "." + dojo.version.patch +
7 | dojo.version.flag + " (" + dojo.version.revision + ")"; // String
8 | }
9 | }
10 | };
11 | // mix all modules in one namespace and return
12 | return lang.mixins.apply(this, [dojo].concat(Array.prototype.slice.call(arguments, 0)));
13 | });
14 |
--------------------------------------------------------------------------------
/lib/main-browser.js:
--------------------------------------------------------------------------------
1 | define([
2 | // platform-neutral modules
3 | "lang",
4 | // additional platform-neutral modules
5 | "color",
6 | // browser-specific modules
7 | "window", "dom", "dom-class"
8 | ], function(lang, color, window, dom, domClass){
9 | var dojo = {
10 | version: {
11 | major: 2, minor: 0, patch: 0, flag: "dev", revision: 0,
12 | toString: function(){
13 | return dojo.version.major + "." + dojo.version.minor + "." + dojo.version.patch +
14 | dojo.version.flag + " (" + dojo.version.revision + ")"; // String
15 | }
16 | }
17 | };
18 | // mix all modules in one namespace and return
19 | return lang.mixins.apply(this, [dojo].concat(Array.prototype.slice.call(arguments, 0)));
20 | });
21 |
--------------------------------------------------------------------------------
/experimental/templating/ste.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | STE
5 |
6 |
7 |
51 |
67 |
68 |
69 | STE
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/lib/io-query.js:
--------------------------------------------------------------------------------
1 | define(["./lang"], function(lang){
2 | var backstop = {};
3 |
4 | function objectToQuery(/*Object*/ map){
5 | // summary:
6 | // takes a name/value mapping object and returns a string representing
7 | // a URL-encoded version of that object.
8 | // example:
9 | // this object:
10 | //
11 | // | {
12 | // | blah: "blah",
13 | // | multi: [
14 | // | "thud",
15 | // | "thonk"
16 | // | ]
17 | // | };
18 | //
19 | // yields the following query string:
20 | //
21 | // | "blah=blah&multi=thud&multi=thonk"
22 |
23 | // FIXME: need to implement encodeAscii!!
24 | var enc = encodeURIComponent, pairs = [];
25 | for(var name in map){
26 | var value = map[name];
27 | if(value != backstop[name]){
28 | var assign = enc(name) + "=";
29 | if(lang.isArray(value)){
30 | for(var i = 0, l = value.length; i < l; ++i){
31 | pairs.push(assign + enc(value[i]));
32 | }
33 | }else{
34 | pairs.push(assign + enc(value));
35 | }
36 | }
37 | }
38 | return pairs.join("&"); // String
39 | }
40 |
41 | function queryToObject(/*String*/ str){
42 | // summary:
43 | // Create an object representing a de-serialized query section of a
44 | // URL. Query keys with multiple values are returned in an array.
45 | //
46 | // example:
47 | // This string:
48 | //
49 | // | "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&"
50 | //
51 | // results in this object structure:
52 | //
53 | // | {
54 | // | foo: [ "bar", "baz" ],
55 | // | thinger: " spaces =blah",
56 | // | zonk: "blarg"
57 | // | }
58 | //
59 | // Note that spaces and other urlencoded entities are correctly
60 | // handled.
61 |
62 | // FIXME: should we grab the URL string if we're not passed one?
63 | var dec = decodeURIComponent, qp = str.split("&"), ret = {}, name, val;
64 | for(var i = 0, l = qp.length, item; i < l; ++i){
65 | item = qp[i];
66 | if(item.length){
67 | var s = item.indexOf("=");
68 | if(s < 0){
69 | name = dec(item);
70 | val = "";
71 | }else{
72 | name = dec(item.slice(0, s));
73 | val = dec(name.slice(s + 1));
74 | }
75 | if(typeof ret[name] == "string"){ // inline'd type check
76 | ret[name] = [ret[name]];
77 | }
78 |
79 | if(lang.isArray(ret[name])){
80 | ret[name].push(val);
81 | }else{
82 | ret[name] = val;
83 | }
84 | }
85 | }
86 | return ret; // Object
87 | }
88 |
89 | return {
90 | objectToQuery: objectToQuery,
91 | queryToObject: queryToObject
92 | };
93 | });
--------------------------------------------------------------------------------
/lib/window.js:
--------------------------------------------------------------------------------
1 | define(function(){
2 | /*=====
3 | dojo.doc = {
4 | // summary:
5 | // Alias for the current document. 'dojo.doc' can be modified
6 | // for temporary context shifting. Also see dojo.withDoc().
7 | // description:
8 | // Refer to dojo.doc rather
9 | // than referring to 'window.document' to ensure your code runs
10 | // correctly in managed contexts.
11 | // example:
12 | // | n.appendChild(dojo.doc.createElement('div'));
13 | }
14 | =====*/
15 | var currentDoc = window["document"] || null, currentGlobal = window;
16 |
17 | function getCurrentDoc(){ return currentDoc; }
18 | function getCurrentGlobal(){ return currentGlobal; }
19 |
20 | function body(){
21 | // summary:
22 | // Return the body element of the document
23 | // return the body object associated with dojo.doc
24 | // example:
25 | // | dojo.body().appendChild(dojo.doc.createElement('div'));
26 |
27 | // Note: document.body is not defined for a strict xhtml document
28 | // Would like to memoize this, but dojo.doc can change vi dojo.withDoc().
29 | return currentDoc.body || currentDoc.getElementsByTagName("body")[0]; // Node
30 | }
31 |
32 | function setContext(/*Object*/globalObject, /*DocumentElement*/globalDocument){
33 | // summary:
34 | // changes the behavior of many core Dojo functions that deal with
35 | // namespace and DOM lookup, changing them to work in a new global
36 | // context (e.g., an iframe). The varibles dojo.global and dojo.doc
37 | // are modified as a result of calling this function and the result of
38 | // `dojo.body()` likewise differs.
39 | currentGlobal = globalObject;
40 | currentDoc = globalDocument;
41 | }
42 |
43 | function withGlobal(/*Object*/globalObject, /*Function*/callback, /*Object?*/thisObject, /*Array?*/cbArguments){
44 | // summary:
45 | // Invoke callback with globalObject as dojo.global and
46 | // globalObject.document as dojo.doc.
47 | // description:
48 | // Invoke callback with globalObject as dojo.global and
49 | // globalObject.document as dojo.doc. If provided, globalObject
50 | // will be executed in the context of object thisObject
51 | // When callback() returns or throws an error, the dojo.global
52 | // and dojo.doc will be restored to its previous state.
53 |
54 | var oldGlob = currentGlobal;
55 | try{
56 | currentGlobal = globalObject;
57 | return withDoc.call(null, globalObject.document, callback, thisObject, cbArguments);
58 | }finally{
59 | currentGlobal = oldGlob;
60 | }
61 | }
62 |
63 | function withDoc(/*DocumentElement*/docObject, /*Function*/callback, /*Object?*/thisObject, /*Array?*/cbArguments){
64 | // summary:
65 | // Invoke callback with documentObject as dojo.doc.
66 | // description:
67 | // Invoke callback with documentObject as dojo.doc. If provided,
68 | // callback will be executed in the context of object thisObject
69 | // When callback() returns or throws an error, the dojo.doc will
70 | // be restored to its previous state.
71 |
72 | var oldDoc = currentDoc;
73 | //TODO: how to represent those two below? see other commented out lines in this function.
74 | //oldLtr = dojo._bodyLtr,
75 | //oldQ = dojo.isQuirks;
76 |
77 | try{
78 | currentDoc = docObject;
79 | //delete dojo._bodyLtr; // uncache
80 | //dojo.isQuirks = dojo.doc.compatMode == "BackCompat"; // no need to check for QuirksMode which was Opera 7 only
81 | currentDoc.compatMode == "BackCompat"; // no need to check for QuirksMode which was Opera 7 only
82 |
83 | if(thisObject && typeof callback == "string"){
84 | callback = thisObject[callback];
85 | }
86 |
87 | return callback.apply(thisObject, cbArguments || []);
88 | }finally{
89 | currentDoc = oldDoc;
90 | //delete dojo._bodyLtr; // in case it was undefined originally, and set to true/false by the alternate document
91 | //if(oldLtr !== undefined){ dojo._bodyLtr = oldLtr; }
92 | //dojo.isQuirks = oldQ;
93 | }
94 | }
95 |
96 | return {
97 | global: getCurrentGlobal,
98 | doc: getCurrentDoc,
99 | body: body,
100 | setContext: setContext,
101 | withGlobal: withGlobal,
102 | withDoc: withDoc
103 | };
104 | });
105 |
--------------------------------------------------------------------------------
/lib/browser.js:
--------------------------------------------------------------------------------
1 | define(function(){
2 | //TODO: do we need browser sniffing in the base?
3 | //TODO: remove old browser detection code.
4 |
5 | /*=====
6 | dojo.isFF = {
7 | // example:
8 | // | if(dojo.isFF > 1){ ... }
9 | };
10 |
11 | dojo.isIE = {
12 | // example:
13 | // | if(dojo.isIE > 6){
14 | // | // we are IE7
15 | // | }
16 | };
17 |
18 | dojo.isSafari = {
19 | // example:
20 | // | if(dojo.isSafari){ ... }
21 | // example:
22 | // Detect iPhone:
23 | // | if(dojo.isSafari && navigator.userAgent.indexOf("iPhone") != -1){
24 | // | // we are iPhone. Note, iPod touch reports "iPod" above and fails this test.
25 | // | }
26 | };
27 |
28 | dojo = {
29 | // isFF: Number | undefined
30 | // Version as a Number if client is FireFox. undefined otherwise. Corresponds to
31 | // major detected FireFox version (1.5, 2, 3, etc.)
32 | isFF: 2,
33 | // isIE: Number | undefined
34 | // Version as a Number if client is MSIE(PC). undefined otherwise. Corresponds to
35 | // major detected IE version (6, 7, 8, etc.)
36 | isIE: 6,
37 | // isKhtml: Number | undefined
38 | // Version as a Number if client is a KHTML browser. undefined otherwise. Corresponds to major
39 | // detected version.
40 | isKhtml: 0,
41 | // isWebKit: Number | undefined
42 | // Version as a Number if client is a WebKit-derived browser (Konqueror,
43 | // Safari, Chrome, etc.). undefined otherwise.
44 | isWebKit: 0,
45 | // isMozilla: Number | undefined
46 | // Version as a Number if client is a Mozilla-based browser (Firefox,
47 | // SeaMonkey). undefined otherwise. Corresponds to major detected version.
48 | isMozilla: 0,
49 | // isOpera: Number | undefined
50 | // Version as a Number if client is Opera. undefined otherwise. Corresponds to
51 | // major detected version.
52 | isOpera: 0,
53 | // isSafari: Number | undefined
54 | // Version as a Number if client is Safari or iPhone. undefined otherwise.
55 | isSafari: 0,
56 | // isChrome: Number | undefined
57 | // Version as a Number if client is Chrome browser. undefined otherwise.
58 | isChrome: 0
59 | // isMac: Boolean
60 | // True if the client runs on Mac
61 | }
62 | =====*/
63 |
64 | // fill in the rendering support information in dojo.render.*
65 | var n = navigator, dua = n.userAgent, dav = n.appVersion, tv = parseFloat(dav), browser = {};
66 |
67 | if(dua.indexOf("Opera") >= 0){ browser.isOpera = tv; }
68 | if(dua.indexOf("AdobeAIR") >= 0){ browser.isAIR = 1; }
69 | browser.isKhtml = dav.indexOf("Konqueror") >= 0 ? tv : 0;
70 | browser.isWebKit = parseFloat(dua.split("WebKit/")[1]) || undefined;
71 | browser.isChrome = parseFloat(dua.split("Chrome/")[1]) || undefined;
72 | browser.isMac = dav.indexOf("Macintosh") >= 0;
73 |
74 | // safari detection derived from:
75 | // http://developer.apple.com/internet/safari/faq.html#anchor2
76 | // http://developer.apple.com/internet/safari/uamatrix.html
77 | var index = Math.max(dav.indexOf("WebKit"), dav.indexOf("Safari"), 0);
78 | if(index && !browser.isChrome){
79 | // try to grab the explicit Safari version first. If we don't get
80 | // one, look for less than 419.3 as the indication that we're on something
81 | // "Safari 2-ish".
82 | browser.isSafari = parseFloat(dav.split("Version/")[1]);
83 | if(!browser.isSafari || parseFloat(dav.substr(index + 7)) <= 419.3){
84 | browser.isSafari = 2;
85 | }
86 | }
87 |
88 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
89 | if(dua.indexOf("Gecko") >= 0 && !browser.isKhtml && !browser.isWebKit){ browser.isMozilla = browser.isMoz = tv; }
90 | if(browser.isMoz){
91 | //We really need to get away from this. Consider a sane isGecko approach for the future.
92 | browser.isFF = parseFloat(dua.split("Firefox/")[1] || dua.split("Minefield/")[1]) || undefined;
93 | }
94 | if(document.all && !browser.isOpera){
95 | browser.isIE = parseFloat(dav.split("MSIE ")[1]) || undefined;
96 | //In cases where the page has an HTTP header or META tag with
97 | //X-UA-Compatible, then it is in emulation mode.
98 | //Make sure isIE reflects the desired version.
99 | //document.documentMode of 5 means quirks mode.
100 | //Only switch the value if documentMode's major version
101 | //is different from isIE's major version.
102 | var mode = document.documentMode;
103 | if(mode && mode != 5 && Math.floor(browser.isIE) != mode){
104 | browser.isIE = mode;
105 | }
106 | }
107 |
108 | browser.isQuirks = document.compatMode == "BackCompat";
109 |
110 | // TODO: is the HTML LANG attribute relevant?
111 | browser.locale = (browser.isIE ? n.userLanguage : n.language).toLowerCase();
112 |
113 | return browser;
114 | });
115 |
--------------------------------------------------------------------------------
/lib/dom-form.js:
--------------------------------------------------------------------------------
1 | define(["./lang", "./dom", "./io-query"], function(lang, dom, ioq){
2 | function setValue(/*Object*/obj, /*String*/name, /*String*/value){
3 | //summary:
4 | // For the named property in object, set the value. If a value
5 | // already exists and it is a string, convert the value to be an
6 | // array of values.
7 |
8 | //Skip it if there is no value
9 | if(value === null){
10 | return;
11 | }
12 |
13 | var val = obj[name];
14 | if(typeof val == "string"){ // inline'd type check
15 | obj[name] = [val, value];
16 | }else if(lang.isArray(val)){
17 | val.push(value);
18 | }else{
19 | obj[name] = value;
20 | }
21 | }
22 |
23 | function fieldToObject(/*DOMNode||String*/ inputNode){
24 | // summary:
25 | // Serialize a form field to a JavaScript object.
26 | //
27 | // description:
28 | // Returns the value encoded in a form field as
29 | // as a string or an array of strings. Disabled form elements
30 | // and unchecked radio and checkboxes are skipped. Multi-select
31 | // elements are returned as an array of string values.
32 | var ret = null;
33 | inputNode = dom.byId(inputNode);
34 | if(inputNode){
35 | var _in = inputNode.name, type = (inputNode.type || "").toLowerCase();
36 | if(_in && type && !inputNode.disabled){
37 | if(type == "radio" || type == "checkbox"){
38 | if(inputNode.checked){ ret = inputNode.value }
39 | }else if(inputNode.multiple){
40 | ret = [];
41 | var nodes = [inputNode.firstChild];
42 | while(nodes.length){
43 | for(var node = nodes.pop(); node; node = node.nextSibling){
44 | if(node.tagName.toLowerCase() == "option"){
45 | if(node.selected){
46 | ret.push(node.value);
47 | }
48 | }else{
49 | nodes.push(node.nextSibling, node.firstChild);
50 | break;
51 | }
52 | }
53 | }
54 | }else{
55 | ret = inputNode.value;
56 | }
57 | }
58 | }
59 | return ret; // Object
60 | }
61 |
62 | var exclude = "file|submit|image|reset|button";
63 |
64 | function formToObject(/*DOMNode||String*/ formNode){
65 | // summary:
66 | // Serialize a form node to a JavaScript object.
67 | // description:
68 | // Returns the values encoded in an HTML form as
69 | // string properties in an object which it then returns. Disabled form
70 | // elements, buttons, and other non-value form elements are skipped.
71 | // Multi-select elements are returned as an array of string values.
72 | //
73 | // example:
74 | // This form:
75 | // |
85 | //
86 | // yields this object structure as the result of a call to
87 | // formToObject():
88 | //
89 | // | {
90 | // | blah: "blah",
91 | // | multi: [
92 | // | "thud",
93 | // | "thonk"
94 | // | ]
95 | // | };
96 |
97 | var ret = {}, elems = dom.byId(formNode).elements;
98 | for(var i = 0, l = elems.length; i < l; ++i){
99 | var item = elems[i], _in = item.name, type = (item.type || "").toLowerCase();
100 | if(_in && type && exclude.indexOf(type) < 0 && !item.disabled){
101 | setValue(ret, _in, fieldToObject(item));
102 | if(type == "image"){
103 | ret[_in + ".x"] = ret[_in + ".y"] = ret[_in].x = ret[_in].y = 0;
104 | }
105 | }
106 | }
107 | return ret; // Object
108 | }
109 |
110 | function formToQuery(/*DOMNode||String*/ formNode){
111 | // summary:
112 | // Returns a URL-encoded string representing the form passed as either a
113 | // node or string ID identifying the form to serialize
114 | return ioq.objectToQuery(formToObject(formNode)); // String
115 | }
116 |
117 | function formToJson(/*DOMNode||String*/ formNode, /*Boolean?*/prettyPrint){
118 | // summary:
119 | // Create a serialized JSON string from a form node or string
120 | // ID identifying the form to serialize
121 | return JSON.stringify(formToObject(formNode), null, prettyPrint ? 4 : 0); // String
122 | }
123 |
124 | return {
125 | fieldToObject: fieldToObject,
126 | formToObject: formToObject,
127 | formToQuery: formToQuery,
128 | formToJson: formToJson
129 | };
130 | });
--------------------------------------------------------------------------------
/lib/color.js:
--------------------------------------------------------------------------------
1 | define(["./lang"], function(lang){
2 |
3 | function Color(/*Array|String|Object?*/ color){
4 | // summary:
5 | // Takes a named string, hex string, array of rgb or rgba values,
6 | // an object with r, g, b, and a properties, or another `dojo.Color` object
7 | // and creates a new Color instance to work from.
8 | //
9 | // example:
10 | // Work with a Color instance:
11 | // | var c = new dojo.Color();
12 | // | c.setColor([0,0,0]); // black
13 | // | var hex = c.toHex(); // #000000
14 | //
15 | // example:
16 | // Work with a node's color:
17 | // | var color = dojo.style("someNode", "backgroundColor");
18 | // | var n = new dojo.Color(color);
19 | // | // adjust the color some
20 | // | n.r *= .5;
21 | // | console.log(n.toString()); // rgb(128, 255, 255);
22 | if(color){ this.setColor(color); }
23 | }
24 |
25 | //TODO: there's got to be a more space-efficient way to encode or discover these!! Use hex?
26 | Color.named = {
27 | black: [0,0,0],
28 | silver: [192,192,192],
29 | gray: [128,128,128],
30 | white: [255,255,255],
31 | maroon: [128,0,0],
32 | red: [255,0,0],
33 | purple: [128,0,128],
34 | fuchsia: [255,0,255],
35 | green: [0,128,0],
36 | lime: [0,255,0],
37 | olive: [128,128,0],
38 | yellow: [255,255,0],
39 | navy: [0,0,128],
40 | blue: [0,0,255],
41 | teal: [0,128,128],
42 | aqua: [0,255,255],
43 | transparent: [255,255,255]
44 | };
45 |
46 | Color.prototype = {
47 | r: 255, g: 255, b: 255, a: 1,
48 | _set: function(r, g, b, a){
49 | this.r = r; this.g = g; this.b = b; this.a = a;
50 | },
51 | setColor: function(/*Array|String|Object*/ color){
52 | // summary:
53 | // Takes a named string, hex string, array of rgb or rgba values,
54 | // an object with r, g, b, and a properties, or another `dojo.Color` object
55 | // and sets this color instance to that value.
56 | //
57 | // example:
58 | // | var c = new dojo.Color(); // no color
59 | // | c.setColor("#ededed"); // greyish
60 | if(lang.isString(color)){
61 | colorFromString(color, this);
62 | }else if(lang.isArray(color)){
63 | colorFromArray(color, this);
64 | }else{
65 | this._set(color.r, color.g, color.b, color.a);
66 | if(!(color instanceof Color)){ this.sanitize(); }
67 | }
68 | return this; // dojo.Color
69 | },
70 | sanitize: function(){
71 | // summary:
72 | // Ensures the object has correct attributes
73 | // description:
74 | // the default implementation does nothing, include dojo.colors to
75 | // augment it with real checks
76 | return this; // dojo.Color
77 | },
78 | toRgb: function(){
79 | // summary:
80 | // Returns 3 component array of rgb values
81 | // example:
82 | // | var c = new dojo.Color("#000000");
83 | // | console.log(c.toRgb()); // [0,0,0]
84 | return [this.r, this.g, this.b]; // Array
85 | },
86 | toRgba: function(){
87 | // summary:
88 | // Returns a 4 component array of rgba values from the color
89 | // represented by this object.
90 | return [this.r, this.g, this.b, this.a]; // Array
91 | },
92 | toHex: function(){
93 | // summary:
94 | // Returns a CSS color string in hexadecimal representation
95 | // example:
96 | // | console.log(new dojo.Color([0,0,0]).toHex()); // #000000
97 | var arr = ["r", "g", "b"].map(function(x){
98 | var s = this[x].toString(16);
99 | return s.length < 2 ? "0" + s : s;
100 | }, this);
101 | return "#" + arr.join(""); // String
102 | },
103 | toCss: function(/*Boolean?*/ includeAlpha){
104 | // summary:
105 | // Returns a css color string in rgb(a) representation
106 | // example:
107 | // | var c = new dojo.Color("#FFF").toCss();
108 | // | console.log(c); // rgb('255','255','255')
109 | var rgb = this.r + ", " + this.g + ", " + this.b;
110 | return (includeAlpha ? "rgba(" + rgb + ", " + this.a : "rgb(" + rgb) + ")"; // String
111 | },
112 | toString: function(){
113 | // summary:
114 | // Returns a visual representation of the color
115 | return this.toCss(true); // String
116 | }
117 | };
118 |
119 | function blendColors(/*dojo.Color*/ start, /*dojo.Color*/ end, /*Number*/ weight, /*dojo.Color?*/ obj){
120 | // summary:
121 | // Blend colors end and start with weight from 0 to 1, 0.5 being a 50/50 blend,
122 | // can reuse a previously allocated dojo.Color object for the result
123 | var t = obj || new Color();
124 | ["r", "g", "b", "a"].forEach(function(x){
125 | t[x] = start[x] + (end[x] - start[x]) * weight;
126 | if(x != "a"){ t[x] = Math.round(t[x]); }
127 | });
128 | return t.sanitize(); // dojo.Color
129 | }
130 |
131 | function colorFromRgb(/*String*/ color, /*dojo.Color?*/ obj){
132 | // summary:
133 | // Returns a `dojo.Color` instance from a string of the form
134 | // "rgb(...)" or "rgba(...)". Optionally accepts a `dojo.Color`
135 | // object to update with the parsed value and return instead of
136 | // creating a new object.
137 | // returns:
138 | // A dojo.Color object. If obj is passed, it will be the return value.
139 | var m = color.toLowerCase().match(/^rgba?\(([\s\.,0-9]+)\)/);
140 | return m && colorFromArray(m[1].split(/\s*,\s*/), obj); // dojo.Color
141 | }
142 |
143 | function colorFromHex(/*String*/ color, /*dojo.Color?*/ obj){
144 | // summary:
145 | // Converts a hex string with a '#' prefix to a color object.
146 | // Supports 12-bit #rgb shorthand. Optionally accepts a
147 | // `dojo.Color` object to update with the parsed value.
148 | //
149 | // returns:
150 | // A dojo.Color object. If obj is passed, it will be the return value.
151 | //
152 | // example:
153 | // | var thing = dojo.colorFromHex("#ededed"); // grey, longhand
154 | //
155 | // example:
156 | // | var thing = dojo.colorFromHex("#000"); // black, shorthand
157 | var t = obj || new Color(), bits = (color.length == 4) ? 4 : 8, mask = (1 << bits) - 1;
158 | color = Number("0x" + color.substr(1));
159 | if(isNaN(color)){
160 | return null; // dojo.Color
161 | }
162 | ["b", "g", "r"].forEach(function(x){
163 | var c = color & mask;
164 | color >>= bits;
165 | t[x] = bits == 4 ? 17 * c : c;
166 | });
167 | t.a = 1;
168 | return t; // dojo.Color
169 | }
170 |
171 | function colorFromArray(/*Array*/ a, /*dojo.Color?*/ obj){
172 | // summary:
173 | // Builds a `dojo.Color` from a 3 or 4 element array, mapping each
174 | // element in sequence to the rgb(a) values of the color.
175 | // example:
176 | // | var myColor = dojo.colorFromArray([237,237,237,0.5]); // grey, 50% alpha
177 | // returns:
178 | // A dojo.Color object. If obj is passed, it will be the return value.
179 | var t = obj || new Color();
180 | t._set(Number(a[0]), Number(a[1]), Number(a[2]), Number(a[3]));
181 | if(isNaN(t.a)){ t.a = 1; }
182 | return t.sanitize(); // dojo.Color
183 | }
184 |
185 | function colorFromString(/*String*/ str, /*dojo.Color?*/ obj){
186 | // summary:
187 | // Parses `str` for a color value. Accepts hex, rgb, and rgba
188 | // style color values.
189 | // description:
190 | // Acceptable input values for str may include arrays of any form
191 | // accepted by dojo.colorFromArray, hex strings such as "#aaaaaa", or
192 | // rgb or rgba strings such as "rgb(133, 200, 16)" or "rgba(10, 10,
193 | // 10, 50)"
194 | // returns:
195 | // A dojo.Color object. If obj is passed, it will be the return value.
196 | var a = Color.named[str];
197 | return a && colorFromArray(a, obj) || colorFromRgb(str, obj) || colorFromHex(str, obj);
198 | }
199 |
200 | return {
201 | Color: Color,
202 | blendColors: blendColors,
203 | colorFromRgb: colorFromRgb,
204 | colorFromHex: colorFromHex,
205 | colorFromArray: colorFromArray,
206 | colorFromString: colorFromString
207 | };
208 | });
209 |
--------------------------------------------------------------------------------
/lib/dom-class.js:
--------------------------------------------------------------------------------
1 | define(["./lang", "./dom"], function(lang, dom){
2 | //TODO: use HTML5 class list methods, example: http://github.com/uxebu/embedjs/blob/master/src/html/classList.js
3 |
4 | // =============================
5 | // (CSS) Class Functions
6 | // =============================
7 | var _className = "className";
8 |
9 | function hasClass(/*DomNode|String*/node, /*String*/classStr){
10 | // summary:
11 | // Returns whether or not the specified classes are a portion of the
12 | // class list currently applied to the node.
13 | //
14 | // node:
15 | // String ID or DomNode reference to check the class for.
16 | //
17 | // classStr:
18 | // A string class name to look for.
19 | //
20 | // example:
21 | // Do something if a node with id="someNode" has class="aSillyClassName" present
22 | // | if(dojo.hasClass("someNode","aSillyClassName")){ ... }
23 |
24 | return ((" "+ dom.byId(node)[_className] +" ").indexOf(" " + classStr + " ") >= 0); // Boolean
25 | }
26 |
27 | var spaces = /\s+/, a1 = [""],
28 | fakeNode = {},
29 | str2array = function(s){
30 | if(typeof s == "string" || s instanceof String){
31 | if(s.indexOf(" ") < 0){
32 | a1[0] = s;
33 | return a1;
34 | }else{
35 | return s.split(spaces);
36 | }
37 | }
38 | // assumed to be an array
39 | return s || "";
40 | };
41 |
42 | function addClass(/*DomNode|String*/node, /*String|Array*/classStr){
43 | // summary:
44 | // Adds the specified classes to the end of the class list on the
45 | // passed node. Will not re-apply duplicate classes.
46 | //
47 | // node:
48 | // String ID or DomNode reference to add a class string too
49 | //
50 | // classStr:
51 | // A String class name to add, or several space-separated class names,
52 | // or an array of class names.
53 | //
54 | // example:
55 | // Add a class to some node:
56 | // | dojo.addClass("someNode", "anewClass");
57 | //
58 | // example:
59 | // Add two classes at once:
60 | // | dojo.addClass("someNode", "firstClass secondClass");
61 | //
62 | // example:
63 | // Add two classes at once (using array):
64 | // | dojo.addClass("someNode", ["firstClass", "secondClass"]);
65 | //
66 | // example:
67 | // Available in `dojo.NodeList` for multiple additions
68 | // | dojo.query("ul > li").addClass("firstLevel");
69 |
70 | node = dom.byId(node);
71 | classStr = str2array(classStr);
72 | var cls = node[_className], oldLen;
73 | cls = cls ? " " + cls + " " : " ";
74 | oldLen = cls.length;
75 | for(var i = 0, len = classStr.length, c; i < len; ++i){
76 | c = classStr[i];
77 | if(c && cls.indexOf(" " + c + " ") < 0){
78 | cls += c + " ";
79 | }
80 | }
81 | if(oldLen < cls.length){
82 | node[_className] = cls.substr(1, cls.length - 2);
83 | }
84 | }
85 |
86 | function removeClass(/*DomNode|String*/node, /*String|Array?*/classStr){
87 | // summary:
88 | // Removes the specified classes from node. No `dojo.hasClass`
89 | // check is required.
90 | //
91 | // node:
92 | // String ID or DomNode reference to remove the class from.
93 | //
94 | // classStr:
95 | // An optional String class name to remove, or several space-separated
96 | // class names, or an array of class names. If omitted, all class names
97 | // will be deleted.
98 | //
99 | // example:
100 | // Remove a class from some node:
101 | // | dojo.removeClass("someNode", "firstClass");
102 | //
103 | // example:
104 | // Remove two classes from some node:
105 | // | dojo.removeClass("someNode", "firstClass secondClass");
106 | //
107 | // example:
108 | // Remove two classes from some node (using array):
109 | // | dojo.removeClass("someNode", ["firstClass", "secondClass"]);
110 | //
111 | // example:
112 | // Remove all classes from some node:
113 | // | dojo.removeClass("someNode");
114 | //
115 | // example:
116 | // Available in `dojo.NodeList()` for multiple removal
117 | // | dojo.query(".foo").removeClass("foo");
118 |
119 | node = dom.byId(node);
120 | var cls;
121 | if(classStr !== undefined){
122 | classStr = str2array(classStr);
123 | cls = " " + node[_className] + " ";
124 | for(var i = 0, len = classStr.length; i < len; ++i){
125 | cls = cls.replace(" " + classStr[i] + " ", " ");
126 | }
127 | cls = lang.trim(cls);
128 | }else{
129 | cls = "";
130 | }
131 | if(node[_className] != cls){ node[_className] = cls; }
132 | }
133 |
134 | function replaceClass(/*DomNode|String*/node, /*String|Array*/addClassStr, /*String|Array?*/removeClassStr){
135 | // summary:
136 | // Replaces one or more classes on a node if not present.
137 | // Operates more quickly than calling dojo.removeClass and dojo.addClass
138 | // node:
139 | // String ID or DomNode reference to remove the class from.
140 | // addClassStr:
141 | // A String class name to add, or several space-separated class names,
142 | // or an array of class names.
143 | // removeClassStr:
144 | // A String class name to remove, or several space-separated class names,
145 | // or an array of class names.
146 | //
147 | // example:
148 | // | dojo.replaceClass("someNode", "add1 add2", "remove1 remove2");
149 | //
150 | // example:
151 | // Replace all classes with addMe
152 | // | dojo.replaceClass("someNode", "addMe");
153 | //
154 | // example:
155 | // Available in `dojo.NodeList()` for multiple toggles
156 | // | dojo.query(".findMe").replaceClass("addMe", "removeMe");
157 | fakeNode[_className] = node[_className];
158 |
159 | removeClass(fakeNode, removeClassStr);
160 | addClass(fakeNode, addClassStr);
161 |
162 | if(node[_className] !== fakeNode[_className]){
163 | node[_className] = fakeNode[_className];
164 | }
165 | }
166 |
167 | function toggleClass(/*DomNode|String*/node, /*String|Array*/classStr, /*Boolean?*/condition){
168 | // summary:
169 | // Adds a class to node if not present, or removes if present.
170 | // Pass a boolean condition if you want to explicitly add or remove.
171 | // condition:
172 | // If passed, true means to add the class, false means to remove.
173 | //
174 | // example:
175 | // | dojo.toggleClass("someNode", "hovered");
176 | //
177 | // example:
178 | // Forcefully add a class
179 | // | dojo.toggleClass("someNode", "hovered", true);
180 | //
181 | // example:
182 | // Available in `dojo.NodeList()` for multiple toggles
183 | // | dojo.query(".toggleMe").toggleClass("toggleMe");
184 |
185 | if(condition === undefined){
186 | condition = !hasClass(node, classStr);
187 | }
188 | (condition ? addClass : removeClass)(node, classStr);
189 | return condition; // Boolean
190 | }
191 |
192 | return {
193 | hasClass: hasClass,
194 | addClass: addClass,
195 | removeClass: removeClass,
196 | replaceClass: replaceClass,
197 | toggleClass: toggleClass
198 | };
199 | });
200 |
--------------------------------------------------------------------------------
/experimental/templating/ste.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Commands:
3 | * {bar} - substitution.
4 | * {bar|foo} - applying filter "foo" to "bar" before substituting.
5 | * {bar|foo:arg1:arg2} - applying filter "foo" with two arguments.
6 | * {?bar} - "if" block.
7 | * {*bar} - "loop" block.
8 | * {-} - "else" block.
9 | * {.} - end of a block.
10 | */
11 |
12 | (function(){
13 | var d = dojo, controls = {"?": "if", "*": "loop", "-": "else", ".": "end"};
14 |
15 | ste = window.ste || {};
16 |
17 | ste.compileTemplate = function(tmpl, pattern, filters){
18 | var tokens = [], previousOffset = 0, needSubstitions = false,
19 | stack = [], token, parent, index, backIndex, object;
20 | d.replace(tmpl, function(match, key, offset, tmpl){
21 | var type = controls[key.charAt(0)];
22 | if(!type){
23 | needSubstitions = true;
24 | return "";
25 | }
26 | if(offset > previousOffset){
27 | tokens.push({
28 | type: needSubstitions ? "replace" : "copy",
29 | text: tmpl.substring(previousOffset, offset)
30 | });
31 | }
32 | previousOffset = offset + match.length;
33 | needSubstitions = false;
34 | token = {
35 | type: type,
36 | text: key.substring(1)
37 | };
38 | index = tokens.length;
39 | tokens.push(token);
40 | switch(type){
41 | case "if":
42 | case "loop":
43 | stack.push(index);
44 | break;
45 | case "else":
46 | if(!stack.length){
47 | throw new Error('STE: "else" should be inside of "if" or "loop".');
48 | }
49 | token.parent = backIndex = stack.pop();
50 | parent = tokens[backIndex];
51 | parent.els = index;
52 | stack.push(index);
53 | break;
54 | case "end":
55 | if(!stack.length){
56 | throw new Error('STE: "end" should close "if" or "loop".');
57 | }
58 | backIndex = stack.pop();
59 | parent = tokens[backIndex];
60 | if(parent.type == "else"){
61 | token.parent = parent.parent;
62 | token.els = backIndex;
63 | tokens[token.parent].end = parent.end = index;
64 | }else{
65 | token.parent = backIndex;
66 | parent.end = index;
67 | }
68 | token.end = index;
69 | break;
70 | }
71 | return "";
72 | }, pattern);
73 | if(stack.length){
74 | throw new Error('STE: some "if" or "loop" blocks were not closed properly.');
75 | }
76 | if(tmpl.length > previousOffset){
77 | tokens.push({
78 | type: needSubstitions ? "replace" : "copy",
79 | text: tmpl.substring(previousOffset)
80 | });
81 | }
82 | object = d.delegate(filters || ste.standardFilters);
83 | object.tokens = tokens;
84 | object.pattern = pattern;
85 | object.exec = execTemplate;
86 | return object;
87 | };
88 |
89 | function execTemplate(dict){
90 | var result = [], stack = [], loopStack = [], token, value, len, top,
91 | resolve = resolveValue(this, dict, loopStack);
92 | for(var t = this.tokens, i = 0, l = t.length; i < l; ++i){
93 | token = t[i];
94 | switch(token.type){
95 | case "copy":
96 | result.push(token.text);
97 | break;
98 | case "replace":
99 | result.push(d.replace(token.text, resolve, this.pattern));
100 | break;
101 | case "if":
102 | value = resolve(0, token.text);
103 | if(!value){
104 | if(!token.els){
105 | // no "else" => skip the whole block
106 | i = token.end;
107 | break;
108 | }
109 | // skip until the "else" block
110 | i = token.els;
111 | }
112 | // push marker
113 | stack.push({
114 | type: "if",
115 | value: value
116 | });
117 | break;
118 | case "loop":
119 | value = resolve(0, token.text);
120 | len = value && value.length || 0;
121 | if(!len){
122 | if(!token.els){
123 | // no "else" => skip the whole block
124 | i = token.end;
125 | continue;
126 | }
127 | // skip until the "else" block
128 | i = token.els;
129 | }
130 | // push marker
131 | top = {
132 | type: "loop",
133 | array: value,
134 | item: value && value[0],
135 | index: 0,
136 | length: len
137 | };
138 | stack.push(top);
139 | loopStack.push(top);
140 | break;
141 | case "else":
142 | case "end":
143 | top = stack[stack.length - 1];
144 | if(top.type == "loop"){
145 | // if loop we have to update it
146 | ++top.index;
147 | if(top.index < top.length){
148 | top.item = top.array[top.index];
149 | i = token.parent;
150 | break;
151 | }
152 | loopStack.pop();
153 | }
154 | stack.pop();
155 | i = token.end;
156 | break;
157 | }
158 | }
159 | return result.join(""); // String
160 | }
161 |
162 | function resolveValue(engine, dict, loopStack){
163 | function resolve(match, expr){
164 | var exprParts = expr.split("|"), parts = exprParts[0].split("."),
165 | top = parts[0], value = dict, i = 0, l = parts.length, old, filter;
166 | if(top.charAt(0) == "%"){
167 | value = loopStack[loopStack.length - 1 - (top == "%" ? 0 : parseInt(top.substr(1)))];
168 | i = 1;
169 | }
170 | // process values
171 | for(; i < l; ++i){
172 | if(value || (value !== undefined && value !== null)){
173 | value = value[parts[i]];
174 | }
175 | }
176 | // process filters
177 | old = engine._nodef; // save old value (to be reenterable)
178 | engine._nodef = false; // set the initial value for a default processing
179 | for(i = 1, l = exprParts.length; i < l; ++i){
180 | parts = exprParts[i].split(":");
181 | filter = parts[0];
182 | if(!engine[filter]){
183 | throw new Error('STE: unknown filter - ' + filter);
184 | }
185 | value = engine[parts[0]](value, parts, resolve, loopStack, dict);
186 | }
187 | // run the default filter, if available
188 | if(match && !engine._nodef && engine.def){
189 | value = engine.def(value, parts, resolve, loopStack, dict);
190 | }
191 | engine._nodef = old; // restore the old value of the flag
192 | return match ? value + "" : value; // Object
193 | }
194 | return resolve; // Function
195 | }
196 |
197 | function resolveOperand(op, resolve){
198 | var c = op.charAt(0);
199 | if("0" <= c && c <= "9" || c == "-" || c == "+"){
200 | return parseFloat(op);
201 | }
202 | if(c == "'" || c == '"'){
203 | return d.fromJson(op);
204 | }
205 | return resolve(0, op); // Object
206 | }
207 |
208 | ste.standardFilters = {
209 | nodef: function(value){ this._nodef = true; return value; },
210 |
211 | // safe output, can be used as the default filter
212 | safeHtml: function(value){
213 | return (value + "").replace(/&(?!\w+([;\s]|$))/g, "&").replace(//g, ">");
214 | },
215 | safeHtmlAttr: function(value){
216 | return (value + "").replace(/&(?!\w+([;\s]|$))/g, "&").replace(/"/g, """).replace(/'/g, "'");
217 | },
218 | safeUri: function(value){
219 | return encodeURI(value);
220 | },
221 | safeUriComponent: function(value){
222 | return encodeURIComponent(value);
223 | },
224 | safeEscape: function(value){
225 | return escape(value);
226 | },
227 |
228 | // value manipulations
229 | call: function(value, parts, resolve){
230 | return parts.length > 1 ? value[resolveOperand(parts[1], resolve)]() : value();
231 | },
232 | sub: function(value, parts){
233 | parts = parts[1].split(".");
234 | for(var i = 0, l = parts.length; i < l; ++i){
235 | if(value || (value !== undefined && value !== null)){
236 | value = value[parts[i]];
237 | }
238 | }
239 | return value;
240 | },
241 | str: function(value){
242 | return value + "";
243 | },
244 | "void": function(){
245 | return "";
246 | },
247 |
248 | // loop functions
249 | first: function(value){
250 | return !value;
251 | },
252 | last: function(value, parts, resolve, loopStack){
253 | return value + 1 == loopStack[loopStack.length - 1].index;
254 | },
255 | even: function(value){
256 | return !(value % 2);
257 | },
258 | odd: function(value){
259 | return value % 2;
260 | },
261 |
262 | // logical functions
263 | /*
264 | is: function(value, parts, resolve){
265 | return parts.length > 1 && value === resolveOperand(parts[1], resolve);
266 | },
267 | eq: function(value, parts, resolve){
268 | return parts.length > 1 && value == resolveOperand(parts[1], resolve);
269 | },
270 | lt: function(value, parts, resolve){
271 | return parts.length > 1 && value < resolveOperand(parts[1], resolve);
272 | },
273 | le: function(value, parts, resolve){
274 | return parts.length > 1 && value <= resolveOperand(parts[1], resolve);
275 | },
276 | gt: function(value, parts, resolve){
277 | return parts.length > 1 && value > resolveOperand(parts[1], resolve);
278 | },
279 | ge: function(value, parts, resolve){
280 | return parts.length > 1 && value >= resolveOperand(parts[1], resolve);
281 | },
282 | */
283 | not: function(value){
284 | return !value;
285 | }
286 | };
287 |
288 | var logicNames = ["eq", "lt", "le", "gt", "ge", "is" ],
289 | logicOps = ["==", "<", "<=", ">", ">=", "==="],
290 | logicDict = {},
291 | logicTmpl = ste.compileTemplate("(function(value, parts, resolve){ return parts.length > 1 && value #{op} resolveOperand(parts[1], resolve); })", /#\{([^\}]+)\}/g);
292 | for(var i = 0, l = logicNames.length; i < l; ++i){
293 | logicDict.op = logicOps[i];
294 | ste.standardFilters[logicNames[i]] = eval(logicTmpl.exec(logicDict));
295 | }
296 |
297 | ste.runTemplate = function(tmpl, dict, pattern, filters){
298 | return ste.compileTemplate(tmpl, pattern, filters).exec(dict); // String
299 | }
300 | })();
301 |
--------------------------------------------------------------------------------
/experimental/templating/ste2.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Commands:
3 | * {bar} - substitution.
4 | * {bar|foo} - applying filter "foo" to "bar" before substituting.
5 | * {bar|foo:arg1:arg2} - applying filter "foo" with two arguments.
6 | * {?bar} - "if" block.
7 | * {*bar} - "loop" block.
8 | * {-} - "else" block.
9 | * {.} - end of a block.
10 | */
11 |
12 | (function(){
13 | var d = dojo, controls = {"?": "if", "*": "loop", "-": "else", ".": "end"};
14 |
15 | ste = window.ste || {};
16 |
17 | ste.compileTemplate = function(tmpl, pattern, filters){
18 | var tokens = [], previousOffset = 0, stack = [],
19 | token, parent, index, backIndex, object, i, l;
20 | d.replace(tmpl, function(match, key, offset, tmpl){
21 | var type = controls[key.charAt(0)] || "var";
22 | if(offset > previousOffset){
23 | tokens.push({
24 | type: "copy",
25 | text: tmpl.substring(previousOffset, offset)
26 | });
27 | }
28 | previousOffset = offset + match.length;
29 | token = {
30 | type: type,
31 | text: type == "var" ? key : key.substring(1)
32 | };
33 | index = tokens.length;
34 | tokens.push(token);
35 | switch(type){
36 | case "if":
37 | case "loop":
38 | stack.push(index);
39 | // intentional fall through
40 | case "var":
41 | compileValue(token);
42 | break;
43 | case "else":
44 | if(!stack.length){
45 | throw new Error('STE: "else" should be inside of "if" or "loop".');
46 | }
47 | token.parent = backIndex = stack.pop();
48 | parent = tokens[backIndex];
49 | parent.els = index;
50 | stack.push(index);
51 | break;
52 | case "end":
53 | if(!stack.length){
54 | throw new Error('STE: "end" should close "if" or "loop".');
55 | }
56 | backIndex = stack.pop();
57 | parent = tokens[backIndex];
58 | if(parent.type == "else"){
59 | token.parent = parent.parent;
60 | token.els = backIndex;
61 | tokens[token.parent].end = parent.end = index;
62 | }else{
63 | token.parent = backIndex;
64 | parent.end = index;
65 | }
66 | token.end = index;
67 | break;
68 | }
69 | return "";
70 | }, pattern);
71 | if(stack.length){
72 | throw new Error('STE: some "if" or "loop" blocks were not closed properly.');
73 | }
74 | if(tmpl.length > previousOffset){
75 | tokens.push({
76 | type: "copy",
77 | text: tmpl.substring(previousOffset)
78 | });
79 | }
80 | object = d.delegate(filters || ste.standardFilters);
81 | object.tokens = tokens;
82 | object.pattern = pattern;
83 | object.exec = execTemplate;
84 | return object;
85 | };
86 |
87 | function compileValue(token){
88 | var expr = token.text.split("|"), i, l;
89 | token.parts = expr[0].split(".");
90 | l = expr.length;
91 | if(l > 1){
92 | token.filters = new Array(l - 1);
93 | for(i = 1; i < l; ++i){
94 | token.filters[i - 1] = expr[i].split(":");
95 | }
96 | }
97 | }
98 |
99 | function execTemplate(dict){
100 | var result = [], stack = [], loopStack = [], token, value, len, top,
101 | resolve = resolveValue(this, dict, loopStack);
102 | for(var t = this.tokens, i = 0, l = t.length; i < l; ++i){
103 | token = t[i];
104 | switch(token.type){
105 | case "copy":
106 | result.push(token.text);
107 | break;
108 | case "var":
109 | result.push(resolve(token, true) + "");
110 | break;
111 | case "if":
112 | value = resolve(token);
113 | if(!value){
114 | if(!token.els){
115 | // no "else" => skip the whole block
116 | i = token.end;
117 | break;
118 | }
119 | // skip until the "else" block
120 | i = token.els;
121 | }
122 | // push marker
123 | stack.push({
124 | type: "if",
125 | value: value
126 | });
127 | break;
128 | case "loop":
129 | value = resolve(token);
130 | len = value && value.length || 0;
131 | if(!len){
132 | if(!token.els){
133 | // no "else" => skip the whole block
134 | i = token.end;
135 | continue;
136 | }
137 | // skip until the "else" block
138 | i = token.els;
139 | }
140 | // push marker
141 | top = {
142 | type: "loop",
143 | array: value,
144 | item: value && value[0],
145 | index: 0,
146 | length: len
147 | };
148 | stack.push(top);
149 | loopStack.push(top);
150 | break;
151 | case "else":
152 | case "end":
153 | top = stack[stack.length - 1];
154 | if(top.type == "loop"){
155 | // if loop we have to update it
156 | ++top.index;
157 | if(top.index < top.length){
158 | top.item = top.array[top.index];
159 | i = token.parent;
160 | break;
161 | }
162 | loopStack.pop();
163 | }
164 | stack.pop();
165 | i = token.end;
166 | break;
167 | }
168 | }
169 | return result.join(""); // String
170 | }
171 |
172 | var noop = ["def"];
173 |
174 | function resolveValue(engine, dict, loopStack){
175 | function resolve(token, useDefault){
176 | var parts = token.parts, filters = token.filters, top = parts[0],
177 | value = dict, i = 0, l = parts.length, old, filter;
178 | if(top.charAt(0) == "%"){
179 | value = loopStack[loopStack.length - 1 - (top == "%" ? 0 : parseInt(top.substr(1)))];
180 | i = 1;
181 | }
182 | // process values
183 | for(; i < l; ++i){
184 | if(value || (value !== undefined && value !== null)){
185 | value = value[parts[i]];
186 | }
187 | }
188 | // process filters
189 | old = engine._nodef; // save old value (to be reenterable)
190 | if(filters){
191 | engine._nodef = false; // set the initial value for a default processing
192 | for(i = 0, l = filters.length; i < l; ++i){
193 | parts = filters[i];
194 | filter = parts[0];
195 | if(!engine[filter]){
196 | throw new Error('STE: unknown filter - ' + filter);
197 | }
198 | value = engine[filter](value, parts, resolve, loopStack, dict);
199 | }
200 | }
201 | // run the default filter, if available
202 | if(useDefault && !engine._nodef && engine.def){
203 | value = engine.def(value, noop, resolve, loopStack, dict);
204 | }
205 | engine._nodef = old; // restore the old value of the flag
206 | return value; // Object
207 | }
208 | return resolve; // Function
209 | }
210 |
211 | function resolveOperand(op, resolve){
212 | var c = op.charAt(0), token;
213 | if("0" <= c && c <= "9" || c == "-" || c == "+"){
214 | return parseFloat(op);
215 | }
216 | if(c == "'" || c == '"'){
217 | return d.fromJson(op);
218 | }
219 | token = {text: op};
220 | compileValue(token);
221 | return resolve(token); // Object
222 | }
223 |
224 | ste.standardFilters = {
225 | nodef: function(value){ this._nodef = true; return value; },
226 |
227 | // safe output, can be used as the default filter
228 | safeHtml: function(value){
229 | return (value + "").replace(/&(?!\w+([;\s]|$))/g, "&").replace(//g, ">");
230 | },
231 | safeHtmlAttr: function(value){
232 | return (value + "").replace(/&(?!\w+([;\s]|$))/g, "&").replace(/"/g, """).replace(/'/g, "'");
233 | },
234 | safeUri: function(value){
235 | return encodeURI(value);
236 | },
237 | safeUriComponent: function(value){
238 | return encodeURIComponent(value);
239 | },
240 | safeEscape: function(value){
241 | return escape(value);
242 | },
243 |
244 | // value manipulations
245 | call: function(value, parts, resolve){
246 | return parts.length > 1 ? value[resolveOperand(parts[1], resolve)]() : value();
247 | },
248 | sub: function(value, parts){
249 | parts = parts[1].split(".");
250 | for(var i = 0, l = parts.length; i < l; ++i){
251 | if(value || (value !== undefined && value !== null)){
252 | value = value[parts[i]];
253 | }
254 | }
255 | return value;
256 | },
257 | str: function(value){
258 | return value + "";
259 | },
260 | "void": function(){
261 | return "";
262 | },
263 |
264 | // loop functions
265 | first: function(value){
266 | return !value;
267 | },
268 | last: function(value, parts, resolve, loopStack){
269 | return value + 1 == loopStack[loopStack.length - 1].index;
270 | },
271 | even: function(value){
272 | return !(value % 2);
273 | },
274 | odd: function(value){
275 | return value % 2;
276 | },
277 |
278 | // logical functions
279 | /*
280 | is: function(value, parts, resolve){
281 | return parts.length > 1 && value === resolveOperand(parts[1], resolve);
282 | },
283 | eq: function(value, parts, resolve){
284 | return parts.length > 1 && value == resolveOperand(parts[1], resolve);
285 | },
286 | lt: function(value, parts, resolve){
287 | return parts.length > 1 && value < resolveOperand(parts[1], resolve);
288 | },
289 | le: function(value, parts, resolve){
290 | return parts.length > 1 && value <= resolveOperand(parts[1], resolve);
291 | },
292 | gt: function(value, parts, resolve){
293 | return parts.length > 1 && value > resolveOperand(parts[1], resolve);
294 | },
295 | ge: function(value, parts, resolve){
296 | return parts.length > 1 && value >= resolveOperand(parts[1], resolve);
297 | },
298 | */
299 | not: function(value){
300 | return !value;
301 | }
302 | };
303 |
304 | var logicNames = ["eq", "lt", "le", "gt", "ge", "is" ],
305 | logicOps = ["==", "<", "<=", ">", ">=", "==="],
306 | logicDict = {},
307 | logicTmpl = ste.compileTemplate("(function(value, parts, resolve){ return parts.length > 1 && value #{op} resolveOperand(parts[1], resolve); })", /#\{([^\}]+)\}/g);
308 | for(var i = 0, l = logicNames.length; i < l; ++i){
309 | logicDict.op = logicOps[i];
310 | ste.standardFilters[logicNames[i]] = eval(logicTmpl.exec(logicDict));
311 | }
312 |
313 | ste.runTemplate = function(tmpl, dict, pattern, filters){
314 | return ste.compileTemplate(tmpl, pattern, filters).exec(dict); // String
315 | }
316 | })();
317 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Pulsar is available under *either* the terms of the modified BSD license *or*
2 | the Academic Free License version 2.1. As a recipient of Pulsar, you may choose
3 | which license to receive this code under (except as noted in per-module LICENSE
4 | files).
5 |
6 | No external contributions are allowed under licenses which are fundamentally
7 | incompatible with the AFL or BSD licenses that Pulsar is distributed under.
8 |
9 | If you want to license Pulsar on different terms (e.g., under MIT or GPL
10 | licenses) and you have valid reasons for that, please contact Eugene Lazutkin.
11 |
12 | The text of the AFL and BSD licenses is reproduced below.
13 |
14 | -------------------------------------------------------------------------------
15 | The "New" BSD License:
16 | **********************
17 |
18 | Copyright (c) 2010, Eugene Lazutkin
19 | All rights reserved.
20 |
21 | Redistribution and use in source and binary forms, with or without
22 | modification, are permitted provided that the following conditions are met:
23 |
24 | * Redistributions of source code must retain the above copyright notice, this
25 | list of conditions and the following disclaimer.
26 | * Redistributions in binary form must reproduce the above copyright notice,
27 | this list of conditions and the following disclaimer in the documentation
28 | and/or other materials provided with the distribution.
29 | * Neither the name of the Eugene Lazutkin nor the names of its contributors
30 | may be used to endorse or promote products derived from this software
31 | without specific prior written permission.
32 |
33 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
34 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
35 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
37 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
38 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
41 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
42 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 |
44 | -------------------------------------------------------------------------------
45 | The Academic Free License, v. 2.1:
46 | **********************************
47 |
48 | This Academic Free License (the "License") applies to any original work of
49 | authorship (the "Original Work") whose owner (the "Licensor") has placed the
50 | following notice immediately following the copyright notice for the Original
51 | Work:
52 |
53 | Licensed under the Academic Free License version 2.1
54 |
55 | 1) Grant of Copyright License. Licensor hereby grants You a world-wide,
56 | royalty-free, non-exclusive, perpetual, sublicenseable license to do the
57 | following:
58 |
59 | a) to reproduce the Original Work in copies;
60 |
61 | b) to prepare derivative works ("Derivative Works") based upon the Original
62 | Work;
63 |
64 | c) to distribute copies of the Original Work and Derivative Works to the
65 | public;
66 |
67 | d) to perform the Original Work publicly; and
68 |
69 | e) to display the Original Work publicly.
70 |
71 | 2) Grant of Patent License. Licensor hereby grants You a world-wide,
72 | royalty-free, non-exclusive, perpetual, sublicenseable license, under patent
73 | claims owned or controlled by the Licensor that are embodied in the Original
74 | Work as furnished by the Licensor, to make, use, sell and offer for sale the
75 | Original Work and Derivative Works.
76 |
77 | 3) Grant of Source Code License. The term "Source Code" means the preferred
78 | form of the Original Work for making modifications to it and all available
79 | documentation describing how to modify the Original Work. Licensor hereby
80 | agrees to provide a machine-readable copy of the Source Code of the Original
81 | Work along with each copy of the Original Work that Licensor distributes.
82 | Licensor reserves the right to satisfy this obligation by placing a
83 | machine-readable copy of the Source Code in an information repository
84 | reasonably calculated to permit inexpensive and convenient access by You for as
85 | long as Licensor continues to distribute the Original Work, and by publishing
86 | the address of that information repository in a notice immediately following
87 | the copyright notice that applies to the Original Work.
88 |
89 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names
90 | of any contributors to the Original Work, nor any of their trademarks or
91 | service marks, may be used to endorse or promote products derived from this
92 | Original Work without express prior written permission of the Licensor. Nothing
93 | in this License shall be deemed to grant any rights to trademarks, copyrights,
94 | patents, trade secrets or any other intellectual property of Licensor except as
95 | expressly stated herein. No patent license is granted to make, use, sell or
96 | offer to sell embodiments of any patent claims other than the licensed claims
97 | defined in Section 2. No right is granted to the trademarks of Licensor even if
98 | such marks are included in the Original Work. Nothing in this License shall be
99 | interpreted to prohibit Licensor from licensing under different terms from this
100 | License any Original Work that Licensor otherwise would have a right to
101 | license.
102 |
103 | 5) This section intentionally omitted.
104 |
105 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative
106 | Works that You create, all copyright, patent or trademark notices from the
107 | Source Code of the Original Work, as well as any notices of licensing and any
108 | descriptive text identified therein as an "Attribution Notice." You must cause
109 | the Source Code for any Derivative Works that You create to carry a prominent
110 | Attribution Notice reasonably calculated to inform recipients that You have
111 | modified the Original Work.
112 |
113 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that
114 | the copyright in and to the Original Work and the patent rights granted herein
115 | by Licensor are owned by the Licensor or are sublicensed to You under the terms
116 | of this License with the permission of the contributor(s) of those copyrights
117 | and patent rights. Except as expressly stated in the immediately proceeding
118 | sentence, the Original Work is provided under this License on an "AS IS" BASIS
119 | and WITHOUT WARRANTY, either express or implied, including, without limitation,
120 | the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR
121 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU.
122 | This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No
123 | license to Original Work is granted hereunder except under this disclaimer.
124 |
125 | 8) Limitation of Liability. Under no circumstances and under no legal theory,
126 | whether in tort (including negligence), contract, or otherwise, shall the
127 | Licensor be liable to any person for any direct, indirect, special, incidental,
128 | or consequential damages of any character arising as a result of this License
129 | or the use of the Original Work including, without limitation, damages for loss
130 | of goodwill, work stoppage, computer failure or malfunction, or any and all
131 | other commercial damages or losses. This limitation of liability shall not
132 | apply to liability for death or personal injury resulting from Licensor's
133 | negligence to the extent applicable law prohibits such limitation. Some
134 | jurisdictions do not allow the exclusion or limitation of incidental or
135 | consequential damages, so this exclusion and limitation may not apply to You.
136 |
137 | 9) Acceptance and Termination. If You distribute copies of the Original Work or
138 | a Derivative Work, You must make a reasonable effort under the circumstances to
139 | obtain the express assent of recipients to the terms of this License. Nothing
140 | else but this License (or another written agreement between Licensor and You)
141 | grants You permission to create Derivative Works based upon the Original Work
142 | or to exercise any of the rights granted in Section 1 herein, and any attempt
143 | to do so except under the terms of this License (or another written agreement
144 | between Licensor and You) is expressly prohibited by U.S. copyright law, the
145 | equivalent laws of other countries, and by international treaty. Therefore, by
146 | exercising any of the rights granted to You in Section 1 herein, You indicate
147 | Your acceptance of this License and all of its terms and conditions.
148 |
149 | 10) Termination for Patent Action. This License shall terminate automatically
150 | and You may no longer exercise any of the rights granted to You by this License
151 | as of the date You commence an action, including a cross-claim or counterclaim,
152 | against Licensor or any licensee alleging that the Original Work infringes a
153 | patent. This termination provision shall not apply for an action alleging
154 | patent infringement by combinations of the Original Work with other software or
155 | hardware.
156 |
157 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this
158 | License may be brought only in the courts of a jurisdiction wherein the
159 | Licensor resides or in which Licensor conducts its primary business, and under
160 | the laws of that jurisdiction excluding its conflict-of-law provisions. The
161 | application of the United Nations Convention on Contracts for the International
162 | Sale of Goods is expressly excluded. Any use of the Original Work outside the
163 | scope of this License or after its termination shall be subject to the
164 | requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et
165 | seq., the equivalent laws of other countries, and international treaty. This
166 | section shall survive the termination of this License.
167 |
168 | 12) Attorneys Fees. In any action to enforce the terms of this License or
169 | seeking damages relating thereto, the prevailing party shall be entitled to
170 | recover its costs and expenses, including, without limitation, reasonable
171 | attorneys' fees and costs incurred in connection with such action, including
172 | any appeal of such action. This section shall survive the termination of this
173 | License.
174 |
175 | 13) Miscellaneous. This License represents the complete agreement concerning
176 | the subject matter hereof. If any provision of this License is held to be
177 | unenforceable, such provision shall be reformed only to the extent necessary to
178 | make it enforceable.
179 |
180 | 14) Definition of "You" in This License. "You" throughout this License, whether
181 | in upper or lower case, means an individual or a legal entity exercising rights
182 | under, and complying with all of the terms of, this License. For legal
183 | entities, "You" includes any entity that controls, is controlled by, or is
184 | under common control with you. For purposes of this definition, "control" means
185 | (i) the power, direct or indirect, to cause the direction or management of such
186 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent
187 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of such
188 | entity.
189 |
190 | 15) Right to Use. You may use the Original Work in all ways not otherwise
191 | restricted or conditioned by this License or by law, and Licensor promises not
192 | to interfere with or be responsible for such uses by You.
193 |
194 | This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved.
195 | Permission is hereby granted to copy and distribute this license without
196 | modification. This license may not be modified without the express written
197 | permission of its copyright owner.
198 |
--------------------------------------------------------------------------------
/require.js:
--------------------------------------------------------------------------------
1 | /*
2 | RequireJS Copyright (c) 2010, The Dojo Foundation All Rights Reserved.
3 | Available via the MIT or new BSD license.
4 | see: http://github.com/jrburke/requirejs for details
5 | */
6 | var require,define;
7 | (function(){function A(a){return H.call(a)==="[object Function]"}function B(a,b,d){var c=h.plugins.defined[a];if(c)c[d.name].apply(null,d.args);else{c=h.plugins.waiting[a]||(h.plugins.waiting[a]=[]);c.push(d);f(["require/"+a],b.contextName)}}function C(a,b){D.apply(f,a);b.loaded[a[0]]=true}function I(a,b,d){var c,e,g;for(c=0;g=b[c];c++){g=typeof g==="string"?{name:g}:g;e=g.location;if(d&&(!e||e.indexOf("/")!==0&&e.indexOf(":")===-1))g.location=d+"/"+(g.location||g.name);g.location=g.location||g.name;
8 | g.lib=g.lib||"lib";g.main=g.main||"main";a[g.name]=g}}function J(a){var b=true,d=a.config.priorityWait,c,e;if(d){for(e=0;c=d[e];e++)if(!a.loaded[c]){b=false;break}b&&delete a.config.priorityWait}return b}function x(a){var b,d=h.paused;if(a.scriptCount<=0){for(a.scriptCount=0;t.length;){b=t.shift();b[0]===null?f.onError(new Error("Mismatched anonymous require.def modules")):C(b,a)}if(!(a.config.priorityWait&&!J(a))){if(d.length)for(a=0;b=d[a];a++)f.checkDeps.apply(f,b);f.checkLoaded(h.ctxName)}}}function R(a,
9 | b){var d=h.plugins.callbacks[a]=[];h.plugins[a]=function(){for(var c=0,e;e=d[c];c++)if(e.apply(null,arguments)===true&&b)return true;return false}}function K(a,b){if(!a.jQuery)if((b=b||(typeof jQuery!=="undefined"?jQuery:null))&&"readyWait"in b){a.jQuery=b;if(!a.defined.jquery&&!a.jQueryDef)a.defined.jquery=b;if(a.scriptCount){b.readyWait+=1;a.jQueryIncremented=true}}}function S(a){return function(b){a.exports=b}}function u(a,b,d){return function(){var c=[].concat(T.call(arguments,0));c.push(b,d);
10 | return(a?require[a]:require).apply(null,c)}}function U(a,b){var d=a.contextName,c=u(null,d,b);f.mixin(c,{modify:u("modify",d,b),def:u("def",d,b),get:u("get",d,b),nameToUrl:u("nameToUrl",d,b),ready:f.ready,context:a,config:a.config,isBrowser:h.isBrowser});return c}var p={},h,o,v=[],E,y,L,w,M,r={},N,V=/^(complete|loaded)$/,W=/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg,X=/require\(["']([\w-_\.\/]+)["']\)/g,D,q=!!(typeof window!=="undefined"&&navigator&&document),O=!q&&typeof importScripts!=="undefined",H=Object.prototype.toString,
11 | P=Array.prototype,T=P.slice,F,f,z,t=[],Q=false,G;if(typeof require!=="undefined")if(A(require))return;else r=require;f=require=function(a,b,d,c,e){var g;if(typeof a==="string"&&!A(b))return require.get(a,b,d,c);if(!require.isArray(a)){g=a;if(require.isArray(b)){a=b;b=d;d=c;c=e}else a=[]}D(null,a,b,g,d,c);(a=h.contexts[d||g&&g.context||h.ctxName])&&a.scriptCount===0&&x(a)};f.onError=function(a){throw a;};define=f.def=function(a,b,d,c){var e,g,i=G;if(typeof a!=="string"){c=d;d=b;b=a;a=null}if(!f.isArray(b)){c=
12 | d;d=b;b=[]}if(!a&&!b.length&&f.isFunction(d)){d.toString().replace(W,"").replace(X,function(j,l){b.push(l)});b=["require","exports","module"].concat(b)}if(!a&&Q){e=document.getElementsByTagName("script");for(a=e.length-1;a>-1&&(g=e[a]);a--)if(g.readyState==="interactive"){i=g;break}i||f.onError(new Error("ERROR: No matching script interactive for "+d));a=i.getAttribute("data-requiremodule")}if(typeof a==="string")h.contexts[h.ctxName].jQueryDef=a==="jquery";t.push([a,b,d,null,c])};D=function(a,b,
13 | d,c,e,g){var i,j,l,m,k;e=e?e:c&&c.context?c.context:h.ctxName;i=h.contexts[e];if(a){j=a.indexOf("!");if(j!==-1){l=a.substring(0,j);a=a.substring(j+1,a.length)}else l=i.defPlugin[a];j=i.waiting[a];if(i&&(i.defined[a]||j&&j!==P[a]))return}if(e!==h.ctxName){j=h.contexts[h.ctxName]&&h.contexts[h.ctxName].loaded;m=true;if(j)for(k in j)if(!(k in p))if(!j[k]){m=false;break}if(m)h.ctxName=e}if(!i){i={contextName:e,config:{waitSeconds:7,baseUrl:h.baseUrl||"./",paths:{},packages:{}},waiting:[],specified:{require:true,
14 | exports:true,module:true},loaded:{},scriptCount:0,urlFetched:{},defPlugin:{},defined:{},modifiers:{}};h.plugins.newContext&&h.plugins.newContext(i);i=h.contexts[e]=i}if(c){if(c.baseUrl)if(c.baseUrl.charAt(c.baseUrl.length-1)!=="/")c.baseUrl+="/";m=i.config.paths;j=i.config.packages;f.mixin(i.config,c,true);if(c.paths){for(k in c.paths)k in p||(m[k]=c.paths[k]);i.config.paths=m}if((m=c.packagePaths)||c.packages){if(m)for(k in m)k in p||I(j,m[k],k);c.packages&&I(j,c.packages);i.config.packages=j}if(c.priority){f(c.priority);
15 | i.config.priorityWait=c.priority}if(c.deps||c.callback)f(c.deps||[],c.callback);c.ready&&f.ready(c.ready);if(!b)return}if(b){k=b;b=[];for(c=0;c0;j--){i=c.slice(0,j).join("/");if(e[i]){c.splice(0,j,e[i]);break}else if(i=g[i]){e=i.location+"/"+i.lib;if(a===i.name)e+="/"+i.main;c.splice(0,j,e);break}}a=c.join("/")+(b||".js");a=(a.charAt(0)==="/"||a.match(/^\w+:/)?
23 | "":d.baseUrl)+a}return d.urlArgs?a+((a.indexOf("?")===-1?"?":"&")+d.urlArgs):a};f.checkLoaded=function(a){var b=h.contexts[a||h.ctxName],d=b.config.waitSeconds*1E3,c=d&&b.startTime+d<(new Date).getTime(),e,g=b.defined,i=b.modifiers,j="",l=false,m=false,k,n=h.plugins.isWaiting,s=h.plugins.orderDeps;if(!b.isCheckLoaded){if(b.config.priorityWait)if(J(b))x(b);else return;b.isCheckLoaded=true;d=b.waiting;e=b.loaded;for(k in e)if(!(k in p)){l=true;if(!e[k])if(c)j+=k+" ";else{m=true;break}}if(!l&&!d.length&&
24 | (!n||!n(b)))b.isCheckLoaded=false;else{if(c&&j){e=new Error("require.js load timeout for modules: "+j);e.requireType="timeout";e.requireModules=j;f.onError(e)}if(m){b.isCheckLoaded=false;if(q||O)setTimeout(function(){f.checkLoaded(a)},50)}else{b.waiting=[];b.loaded={};s&&s(b);for(k in i)k in p||g[k]&&f.execModifiers(k,{},d,b);for(e=0;g=d[e];e++)f.exec(g,{},d,b);b.isCheckLoaded=false;if(b.waiting.length||n&&n(b))f.checkLoaded(a);else if(v.length){e=b.loaded;b=true;for(k in e)if(!(k in p))if(!e[k]){b=
25 | false;break}if(b){h.ctxName=v[0][1];k=v;v=[];for(e=0;b=k[e];e++)f.load.apply(f,b)}}else{h.ctxName="_";h.isDone=true;f.callReady&&f.callReady()}}}}};f.exec=function(a,b,d,c){if(a){var e=a.name,g=a.callback;g=a.deps;var i,j,l=c.defined,m,k=[],n,s=false;if(e){if(b[e]||e in l)return l[e];b[e]=true}if(g)for(i=0;j=g[i];i++){j=j.name;if(j==="require")j=U(c,e);else if(j==="exports"){j=l[e]={};s=true}else if(j==="module"){n=j={id:e,uri:e?f.nameToUrl(e,null,c.contextName):undefined};n.setExports=S(n)}else j=
26 | j in l?l[j]:b[j]?undefined:f.exec(d[d[j]],b,d,c);k.push(j)}if((g=a.callback)&&f.isFunction(g)){m=f.execCb(e,g,k);if(e)if(s&&m===undefined&&(!n||!("exports"in n)))m=l[e];else if(n&&"exports"in n)m=l[e]=n.exports;else{e in l&&!s&&f.onError(new Error(e+" has already been defined"));l[e]=m}}f.execModifiers(e,b,d,c);return m}};f.execCb=function(a,b,d){return b.apply(null,d)};f.execModifiers=function(a,b,d,c){var e=c.modifiers,g=e[a],i,j;if(g){for(j=0;j-1&&(y=E[o]);o--){if(!h.head)h.head=y.parentNode;if(!r.deps)if(w=y.getAttribute("data-main"))r.deps=[w];if((w=y.src)&&!h.baseUrl)if(M=w.match(L)){h.baseUrl=w.substring(0,M.index);break}}}f.pageLoaded=function(){if(!h.isPageLoaded){h.isPageLoaded=true;F&&clearInterval(F);if(N)document.readyState="complete";f.callReady()}};f.callReady=function(){var a=h.readyCalls,b,d,c;if(h.isPageLoaded&&h.isDone){if(a.length){h.readyCalls=[];for(b=0;d=a[b];b++)d()}a=h.contexts;for(c in a)if(!(c in
30 | p)){b=a[c];if(b.jQueryIncremented){b.jQuery.readyWait-=1;b.jQueryIncremented=false}}}};f.ready=function(a){h.isPageLoaded&&h.isDone?a():h.readyCalls.push(a);return f};if(q){if(document.addEventListener){document.addEventListener("DOMContentLoaded",f.pageLoaded,false);window.addEventListener("load",f.pageLoaded,false);if(!document.readyState){N=true;document.readyState="loading"}}else if(window.attachEvent){window.attachEvent("onload",f.pageLoaded);if(self===self.top)F=setInterval(function(){try{if(document.body){document.documentElement.doScroll("left");
31 | f.pageLoaded()}}catch(a){}},30)}document.readyState==="complete"&&f.pageLoaded()}f(r);typeof setTimeout!=="undefined"&&setTimeout(function(){var a=h.contexts[r.context||"_"];K(a);x(a)},0)})();
32 |
--------------------------------------------------------------------------------
/lib/async.js:
--------------------------------------------------------------------------------
1 | define(["./lang"], function(lang){
2 | var mutator = new Function, freeze = Object.freeze || new Function;
3 |
4 | // A deferred provides an API for creating and resolving a promise.
5 | function Deferred(/*Function?*/canceller){
6 | // summary:
7 | // Deferreds provide a generic means for encapsulating an asynchronous
8 | // operation and notifying users of the completion and result of the operation.
9 | // description:
10 | // The dojo.Deferred API is based on the concept of promises that provide a
11 | // generic interface into the eventual completion of an asynchronous action.
12 | // The motivation for promises fundamentally is about creating a
13 | // separation of concerns that allows one to achieve the same type of
14 | // call patterns and logical data flow in asynchronous code as can be
15 | // achieved in synchronous code. Promises allows one
16 | // to be able to call a function purely with arguments needed for
17 | // execution, without conflating the call with concerns of whether it is
18 | // sync or async. One shouldn't need to alter a call's arguments if the
19 | // implementation switches from sync to async (or vice versa). By having
20 | // async functions return promises, the concerns of making the call are
21 | // separated from the concerns of asynchronous interaction (which are
22 | // handled by the promise).
23 | //
24 | // The dojo.Deferred is a type of promise that provides methods for fulfilling the
25 | // promise with a successful result or an error. The most important method for
26 | // working with Dojo's promises is the then() method, which follows the
27 | // CommonJS proposed promise API. An example of using a Dojo promise:
28 | //
29 | // | var resultingPromise = someAsyncOperation.then(function(result){
30 | // | ... handle result ...
31 | // | },
32 | // | function(error){
33 | // | ... handle error ...
34 | // | });
35 | //
36 | // The .then() call returns a new promise that represents the result of the
37 | // execution of the callback. The callbacks will never affect the original promises value.
38 | //
39 | // The dojo.Deferred instances also provide the following functions for backwards compatibility:
40 | //
41 | // * addCallback(handler)
42 | // * addErrback(handler)
43 | // * callback(result)
44 | // * errback(result)
45 | //
46 | // Callbacks are allowed to return promisesthemselves, so
47 | // you can build complicated sequences of events with ease.
48 | //
49 | // The creator of the Deferred may specify a canceller. The canceller
50 | // is a function that will be called if Deferred.cancel is called
51 | // before the Deferred fires. You can use this to implement clean
52 | // aborting of an XMLHttpRequest, etc. Note that cancel will fire the
53 | // deferred with a CancelledError (unless your canceller returns
54 | // another kind of error), so the errbacks should be prepared to
55 | // handle that error for cancellable Deferreds.
56 | // example:
57 | // | var deferred = new dojo.Deferred();
58 | // | setTimeout(function(){ deferred.callback({success: true}); }, 1000);
59 | // | return deferred;
60 | // example:
61 | // Deferred objects are often used when making code asynchronous. It
62 | // may be easiest to write functions in a synchronous manner and then
63 | // split code using a deferred to trigger a response to a long-lived
64 | // operation. For example, instead of register a callback function to
65 | // denote when a rendering operation completes, the function can
66 | // simply return a deferred:
67 | //
68 | // | // callback style:
69 | // | function renderLotsOfData(data, callback){
70 | // | var success = false
71 | // | try{
72 | // | for(var x in data){
73 | // | renderDataitem(data[x]);
74 | // | }
75 | // | success = true;
76 | // | }catch(e){ }
77 | // | if(callback){
78 | // | callback(success);
79 | // | }
80 | // | }
81 | //
82 | // | // using callback style
83 | // | renderLotsOfData(someDataObj, function(success){
84 | // | // handles success or failure
85 | // | if(!success){
86 | // | promptUserToRecover();
87 | // | }
88 | // | });
89 | // | // NOTE: no way to add another callback here!!
90 | // example:
91 | // Using a Deferred doesn't simplify the sending code any, but it
92 | // provides a standard interface for callers and senders alike,
93 | // providing both with a simple way to service multiple callbacks for
94 | // an operation and freeing both sides from worrying about details
95 | // such as "did this get called already?". With Deferreds, new
96 | // callbacks can be added at any time.
97 | //
98 | // | // Deferred style:
99 | // | function renderLotsOfData(data){
100 | // | var d = new dojo.Deferred();
101 | // | try{
102 | // | for(var x in data){
103 | // | renderDataitem(data[x]);
104 | // | }
105 | // | d.callback(true);
106 | // | }catch(e){
107 | // | d.errback(new Error("rendering failed"));
108 | // | }
109 | // | return d;
110 | // | }
111 | //
112 | // | // using Deferred style
113 | // | renderLotsOfData(someDataObj).then(null, function(){
114 | // | promptUserToRecover();
115 | // | });
116 | // | // NOTE: addErrback and addCallback both return the Deferred
117 | // | // again, so we could chain adding callbacks or save the
118 | // | // deferred for later should we need to be notified again.
119 | // example:
120 | // In this example, renderLotsOfData is syncrhonous and so both
121 | // versions are pretty artificial. Putting the data display on a
122 | // timeout helps show why Deferreds rock:
123 | //
124 | // | // Deferred style and async func
125 | // | function renderLotsOfData(data){
126 | // | var d = new dojo.Deferred();
127 | // | setTimeout(function(){
128 | // | try{
129 | // | for(var x in data){
130 | // | renderDataitem(data[x]);
131 | // | }
132 | // | d.callback(true);
133 | // | }catch(e){
134 | // | d.errback(new Error("rendering failed"));
135 | // | }
136 | // | }, 100);
137 | // | return d;
138 | // | }
139 | //
140 | // | // using Deferred style
141 | // | renderLotsOfData(someDataObj).then(null, function(){
142 | // | promptUserToRecover();
143 | // | });
144 | //
145 | // Note that the caller doesn't have to change his code at all to
146 | // handle the asynchronous case.
147 | var result, finished, isError, head, nextListener;
148 | var promise = this.promise = {};
149 |
150 | function complete(value){
151 | if(finished){
152 | throw new Error("This deferred has already been resolved");
153 | }
154 | result = value;
155 | finished = true;
156 | notify();
157 | }
158 |
159 | function notify(){
160 | var mutated;
161 | while(!mutated && nextListener){
162 | var listener = nextListener;
163 | nextListener = nextListener.next;
164 | if(mutated = (listener.progress == mutator)){ // assignment and check
165 | finished = false;
166 | }
167 | var func = (isError ? listener.error : listener.resolved);
168 | if (func) {
169 | try {
170 | var newResult = func(result);
171 | if (newResult && typeof newResult.then === "function") {
172 | newResult.then(lang.hitch(listener.deferred, "resolve"), lang.hitch(listener.deferred, "reject"));
173 | continue;
174 | }
175 | var unchanged = mutated && newResult === undefined;
176 | listener.deferred[unchanged && isError ? "reject" : "resolve"](unchanged ? result : newResult);
177 | }
178 | catch (e) {
179 | listener.deferred.reject(e);
180 | }
181 | }else {
182 | if(isError){
183 | listener.deferred.reject(result);
184 | }else{
185 | listener.deferred.resolve(result);
186 | }
187 | }
188 | }
189 | }
190 |
191 | // calling resolve will resolve the promise
192 | this.resolve = this.callback = function(value){
193 | // summary:
194 | // Fulfills the Deferred instance successfully with the provide value
195 | isError = false;
196 | this.fired = 0;
197 | this.results = [value, null];
198 | complete(value);
199 | };
200 |
201 | // calling error will indicate that the promise failed
202 | this.reject = this.errback = function(error){
203 | // summary:
204 | // Fulfills the Deferred instance as an error with the provided error
205 | isError = true;
206 | this.fired = 1;
207 | complete(error);
208 | this.results = [null, error];
209 | if(!error || error.log !== false){
210 | //TODO: we need to decide what we do with global settings
211 | //(dojo.config.deferredOnError || function(x){ console.error(x); })(error);
212 | }
213 | };
214 |
215 | // call progress to provide updates on the progress on the completion of the promise
216 | this.progress = function(update){
217 | // summary
218 | // Send progress events to all listeners
219 | var listener = nextListener;
220 | while(listener){
221 | var progress = listener.progress;
222 | progress && progress(update);
223 | listener = listener.next;
224 | }
225 | };
226 |
227 | this.addCallbacks = function(/*Function?*/callback, /*Function?*/errback){
228 | this.then(callback, errback, mutator);
229 | return this;
230 | };
231 |
232 | // provide the implementation of the promise
233 | this.then = promise.then = function(/*Function?*/resolvedCallback, /*Function?*/errorCallback, /*Function?*/progressCallback){
234 | // summary
235 | // Adds a fulfilledHandler, errorHandler, and progressHandler to be called for
236 | // completion of a promise. The fulfilledHandler is called when the promise
237 | // is fulfilled. The errorHandler is called when a promise fails. The
238 | // progressHandler is called for progress events. All arguments are optional
239 | // and non-function values are ignored. The progressHandler is not only an
240 | // optional argument, but progress events are purely optional. Promise
241 | // providers are not required to ever create progress events.
242 | //
243 | // This function will return a new promise that is fulfilled when the given
244 | // fulfilledHandler or errorHandler callback is finished. This allows promise
245 | // operations to be chained together. The value returned from the callback
246 | // handler is the fulfillment value for the returned promise. If the callback
247 | // throws an error, the returned promise will be moved to failed state.
248 | //
249 | // example:
250 | // An example of using a CommonJS compliant promise:
251 | // | asyncComputeTheAnswerToEverything().
252 | // | then(addTwo).
253 | // | then(printResult, onError);
254 | // | >44
255 | //
256 | var returnDeferred = progressCallback == mutator ? this : new Deferred(promise.cancel);
257 | var listener = {
258 | resolved: resolvedCallback,
259 | error: errorCallback,
260 | progress: progressCallback,
261 | deferred: returnDeferred
262 | };
263 | if(nextListener){
264 | head = head.next = listener;
265 | }
266 | else{
267 | nextListener = head = listener;
268 | }
269 | if(finished){
270 | notify();
271 | }
272 | return returnDeferred.promise;
273 | };
274 | var deferred = this;
275 | this.cancel = promise.cancel = function () {
276 | // summary:
277 | // Cancels the asynchronous operation
278 | if(!finished){
279 | var error = canceller && canceller(deferred);
280 | if(!finished){
281 | if (!(error instanceof Error)) {
282 | error = new Error(error);
283 | }
284 | error.log = false;
285 | deferred.reject(error);
286 | }
287 | }
288 | };
289 | freeze(promise);
290 | }
291 |
292 | Deferred.prototype = {
293 | addCallback: function (/*Function*/callback) {
294 | return this.addCallbacks(lang.hitch.apply(lang, arguments));
295 | },
296 |
297 | addErrback: function (/*Function*/errback) {
298 | return this.addCallbacks(null, lang.hitch.apply(lang, arguments));
299 | },
300 |
301 | addBoth: function (/*Function*/callback) {
302 | var enclosed = lang.hitch.apply(lang, arguments);
303 | return this.addCallbacks(enclosed, enclosed);
304 | },
305 | fired: -1
306 | };
307 |
308 | function when(promiseOrValue, /*Function?*/callback, /*Function?*/errback, /*Function?*/progressHandler){
309 | // summary:
310 | // This provides normalization between normal synchronous values and
311 | // asynchronous promises, so you can interact with them in a common way
312 | // example:
313 | // | function printFirstAndList(items){
314 | // | dojo.when(findFirst(items), console.log);
315 | // | dojo.when(findLast(items), console.log);
316 | // | }
317 | // | function findFirst(items){
318 | // | return dojo.when(items, function(items){
319 | // | return items[0];
320 | // | });
321 | // | }
322 | // | function findLast(items){
323 | // | return dojo.when(items, function(items){
324 | // | return items[items.length];
325 | // | });
326 | // | }
327 | // And now all three of his functions can be used sync or async.
328 | // | printFirstAndLast([1,2,3,4]) will work just as well as
329 | // | printFirstAndLast(dojo.xhrGet(...));
330 |
331 | if(promiseOrValue && typeof promiseOrValue.then == "function"){
332 | return promiseOrValue.then(callback, errback, progressHandler);
333 | }
334 | return callback(promiseOrValue);
335 | }
336 |
337 | //TODO: add some utilities from dojox.lang.async
338 |
339 | return {
340 | Deferred: Deferred,
341 | when: when
342 | };
343 | });
344 |
--------------------------------------------------------------------------------
/lib/lang.js:
--------------------------------------------------------------------------------
1 | define(function(){
2 | var opts = Object.prototype.toString;
3 |
4 | // Crockford (ish) functions
5 |
6 | function isString(/*anything*/ it){
7 | // summary:
8 | // Return true if it is a String
9 | return typeof it == "string" || it instanceof String || opts.call(it) == "[object String]"; // Boolean
10 | }
11 |
12 | function isArray(/*anything*/ it){
13 | // summary:
14 | // Return true if it is an Array.
15 | // Does not work on Arrays created in other windows.
16 | //TODO: use Array.isArray() if available
17 | return opts.call(it) == "[object Array]"; // Boolean
18 | }
19 |
20 | function isFunction(/*anything*/ it){
21 | // summary:
22 | // Return true if it is a Function
23 | return opts.call(it) === "[object Function]"; // Boolean
24 | }
25 |
26 | function isObject(/*anything*/ it){
27 | // summary:
28 | // Returns true if it is a JavaScript object (or an Array, a Function
29 | // or null)
30 | return it !== undefined && (it === null || typeof it == "object" || isArray(it) || isFunction(it)); // Boolean
31 | }
32 |
33 | //TODO: do we really need it?
34 | function isArrayLike(/*anything*/ it){
35 | // summary:
36 | // similar to dojo.isArray() but more permissive
37 | // description:
38 | // Doesn't strongly test for "arrayness". Instead, settles for "isn't
39 | // a string or number and has a length property". Arguments objects
40 | // and DOM collections will return true when passed to
41 | // dojo.isArrayLike(), but will return false when passed to
42 | // dojo.isArray().
43 | // returns:
44 | // If it walks like a duck and quacks like a duck, return `true`
45 | return it && // Boolean
46 | // keep out built-in constructors (Number, String, ...) which have length
47 | // properties
48 | !isString(it) && !isFunction(it) &&
49 | !(it.tagName && it.tagName.toLowerCase() == 'form') &&
50 | (isArray(it) || isFinite(it.length));
51 | }
52 |
53 | //TODO: do we really need it?
54 | function isAlien(/*anything*/ it){
55 | // summary:
56 | // Returns true if it is a built-in function or some other kind of
57 | // oddball that *should* report as a function but doesn't
58 | return it && !isFunction(it) && /\{\s*\[native code\]\s*\}/.test(String(it)); // Boolean
59 | }
60 |
61 | // Object access
62 |
63 | function _getProp(/*Array*/parts, /*Boolean*/create, /*Object*/context){
64 | context = context || this;
65 | for(var i = 0, p; context && (p = parts[i]); ++i){
66 | context = (p in context ? context[p] : (create ? context[p] = {} : undefined));
67 | }
68 | return context; // mixed
69 | }
70 |
71 | function setObject(/*String*/name, /*Object*/value, /*Object?*/context){
72 | // summary:
73 | // Set a property from a dot-separated string, such as "A.B.C"
74 | // description:
75 | // Useful for longer api chains where you have to test each object in
76 | // the chain, or when you have an object reference in string format.
77 | // Objects are created as needed along `path`. Returns the passed
78 | // value if setting is successful or `undefined` if not.
79 | // name:
80 | // Path to a property, in the form "A.B.C".
81 | // context:
82 | // Optional. Object to use as root of path. Defaults to
83 | // `dojo.global`.
84 | // example:
85 | // set the value of `foo.bar.baz`, regardless of whether
86 | // intermediate objects already exist:
87 | // | dojo.setObject("foo.bar.baz", value);
88 | // example:
89 | // without `dojo.setObject`, we often see code like this:
90 | // | // ensure that intermediate objects are available
91 | // | if(!obj["parent"]){ obj.parent = {}; }
92 | // | if(!obj.parent["child"]){ obj.parent.child= {}; }
93 | // | // now we can safely set the property
94 | // | obj.parent.child.prop = "some value";
95 | // wheras with `dojo.setObject`, we can shorten that to:
96 | // | dojo.setObject("parent.child.prop", "some value", obj);
97 | var parts = name.split("."), p = parts.pop(), obj = _getProp(parts, true, context);
98 | return obj && p ? (obj[p] = value) : undefined; // Object
99 | }
100 |
101 | function getObject(/*String*/name, /*Boolean?*/create, /*Object?*/context){
102 | // summary:
103 | // Get a property from a dot-separated string, such as "A.B.C"
104 | // description:
105 | // Useful for longer api chains where you have to test each object in
106 | // the chain, or when you have an object reference in string format.
107 | // name:
108 | // Path to an property, in the form "A.B.C".
109 | // create:
110 | // Optional. Defaults to `false`. If `true`, Objects will be
111 | // created at any point along the 'path' that is undefined.
112 | // context:
113 | // Optional. Object to use as root of path. Defaults to
114 | // 'dojo.global'. Null may be passed.
115 | return _getProp(name.split("."), create, context); // Object
116 | }
117 |
118 | //TODO: do we really need this one-liner?
119 | function exists(/*String*/name, /*Object?*/obj){
120 | // summary:
121 | // determine if an object supports a given method
122 | // description:
123 | // useful for longer api chains where you have to test each object in
124 | // the chain. Useful for object and method detection.
125 | // name:
126 | // Path to an object, in the form "A.B.C".
127 | // obj:
128 | // Object to use as root of path. Defaults to
129 | // 'dojo.global'. Null may be passed.
130 | // example:
131 | // | // define an object
132 | // | var foo = {
133 | // | bar: { }
134 | // | };
135 | // |
136 | // | // search the global scope
137 | // | dojo.exists("foo.bar"); // true
138 | // | dojo.exists("foo.bar.baz"); // false
139 | // |
140 | // | // search from a particular scope
141 | // | dojo.exists("bar", foo); // true
142 | // | dojo.exists("bar.baz", foo); // false
143 | return getObject(name, false, obj) !== undefined; // Boolean
144 | }
145 |
146 | // object mixers
147 |
148 | var empty = {};
149 |
150 | function mixin(/*Object*/ target, /*Object*/ source){
151 | // summary:
152 | // Adds all properties and methods of source to target. This addition
153 | // is "prototype extension safe", so that instances of objects
154 | // will not pass along prototype defaults.
155 | var name, s;
156 | for(name in source){
157 | // the "target" condition avoid copying properties in "source"
158 | // inherited from Object.prototype. For example, if target has a custom
159 | // toString() method, don't overwrite it with the toString() method
160 | // that source inherited from Object.prototype
161 | s = source[name];
162 | if(!(name in target) || (target[name] !== s && (!(name in empty) || empty[name] !== s))){
163 | target[name] = s;
164 | }
165 | }
166 | return target; // Object
167 | }
168 |
169 | function mixins(/*Object*/obj, /*Object...*/props){
170 | // summary:
171 | // Adds all properties and methods of props to obj and returns the
172 | // (now modified) obj.
173 | // description:
174 | // `dojo.mixin` can mix multiple source objects into a
175 | // destination object which is then returned. Unlike regular
176 | // `for...in` iteration, `dojo.mixin` is also smart about avoiding
177 | // extensions which other toolkits may unwisely add to the root
178 | // object prototype
179 | // obj:
180 | // The object to mix properties into. Also the return value.
181 | // props:
182 | // One or more objects whose values are successively copied into
183 | // obj. If more than one of these objects contain the same value,
184 | // the one specified last in the function call will "win".
185 | // example:
186 | // make a shallow copy of an object
187 | // | var copy = dojo.mixin({}, source);
188 | // example:
189 | // many class constructors often take an object which specifies
190 | // values to be configured on the object. In this case, it is
191 | // often simplest to call `dojo.mixin` on the `this` object:
192 | // | dojo.declare("acme.Base", null, {
193 | // | constructor: function(properties){
194 | // | // property configuration:
195 | // | dojo.mixin(this, properties);
196 | // |
197 | // | console.log(this.quip);
198 | // | // ...
199 | // | },
200 | // | quip: "I wasn't born yesterday, you know - I've seen movies.",
201 | // | // ...
202 | // | });
203 | // |
204 | // | // create an instance of the class and configure it
205 | // | var b = new acme.Base({quip: "That's what it does!" });
206 | // example:
207 | // copy in properties from multiple objects
208 | // | var flattened = dojo.mixin(
209 | // | {
210 | // | name: "Frylock",
211 | // | braces: true
212 | // | },
213 | // | {
214 | // | name: "Carl Brutanananadilewski"
215 | // | }
216 | // | );
217 | // |
218 | // | // will print "Carl Brutanananadilewski"
219 | // | console.log(flattened.name);
220 | // | // will print "true"
221 | // | console.log(flattened.braces);
222 | if(!obj){ obj = {}; }
223 | for(var i = 1, l = arguments.length; i < l; ++i){
224 | mixin(obj, arguments[i]);
225 | }
226 | return obj; // Object
227 | }
228 |
229 | function clone(/*anything*/ o){
230 | // summary:
231 | // Clones objects (including DOM nodes) and all children.
232 | // Warning: do not clone cyclic structures.
233 | if(!o || typeof o != "object" || isFunction(o)){
234 | // null, undefined, any non-object, or function
235 | return o; // anything
236 | }
237 | // we don't clone nodes
238 | //if(o.nodeType && "cloneNode" in o){
239 | // // DOM Node
240 | // return o.cloneNode(true); // Node
241 | //}
242 | if(o instanceof Date){
243 | // Date
244 | return new Date(o.getTime()); // Date
245 | }
246 | var r, i, l, s, name;
247 | if(isArray(o)){
248 | // array
249 | r = [];
250 | for(i = 0, l = o.length; i < l; ++i){
251 | if(i in o){
252 | r.push(clone(o[i]));
253 | }
254 | }
255 | // we don't clone functions for performance reasons
256 | // }else if(d.isFunction(o)){
257 | // // function
258 | // r = function(){ return o.apply(this, arguments); };
259 | }else{
260 | // generic objects
261 | r = o.constructor ? new o.constructor() : {};
262 | }
263 | for(name in o){
264 | // the "tobj" condition avoid copying properties in "source"
265 | // inherited from Object.prototype. For example, if target has a custom
266 | // toString() method, don't overwrite it with the toString() method
267 | // that source inherited from Object.prototype
268 | s = o[name];
269 | if(!(name in r) || (r[name] !== s && (!(name in empty) || empty[name] !== s))){
270 | r[name] = clone(s);
271 | }
272 | }
273 | return r; // Object
274 | }
275 |
276 | function extend(/*Object*/ constructor, /*Object...*/ props){
277 | // summary:
278 | // Adds all properties and methods of props to constructor's
279 | // prototype, making them available to all instances created with
280 | // constructor.
281 | var args = Array.prototype.slice.call(arguments, 0);
282 | args[0] = constructor.prototype;
283 | mixins.apply(this, args);
284 | return constructor; // Object
285 | }
286 |
287 | /*=====
288 | dojo.delegate = function(obj, props){
289 | // summary:
290 | // Returns a new object which "looks" to obj for properties which it
291 | // does not have a value for. Optionally takes a bag of properties to
292 | // seed the returned object with initially.
293 | // description:
294 | // This is a small implementaton of the Boodman/Crockford delegation
295 | // pattern in JavaScript. An intermediate object constructor mediates
296 | // the prototype chain for the returned object, using it to delegate
297 | // down to obj for property lookup when object-local lookup fails.
298 | // This can be thought of similarly to ES4's "wrap", save that it does
299 | // not act on types but rather on pure objects.
300 | // obj:
301 | // The object to delegate to for properties not found directly on the
302 | // return object or in props.
303 | // props:
304 | // an object containing properties to assign to the returned object
305 | // returns:
306 | // an Object of anonymous type
307 | // example:
308 | // | var foo = { bar: "baz" };
309 | // | var thinger = dojo.delegate(foo, { thud: "xyzzy"});
310 | // | thinger.bar == "baz"; // delegated to foo
311 | // | foo.thud == undefined; // by definition
312 | // | thinger.thud == "xyzzy"; // mixed in from props
313 | // | foo.bar = "thonk";
314 | // | thinger.bar == "thonk"; // still delegated to foo's bar
315 | }
316 | =====*/
317 |
318 | var delegate = (function(){
319 | // boodman/crockford delegation w/ cornford optimization
320 | function TMP(){}
321 | return function delegate(obj, props){
322 | TMP.prototype = obj;
323 | var tmp = new TMP();
324 | TMP.prototype = null;
325 | if(props){
326 | mixin(tmp, props);
327 | }
328 | return tmp; // Object
329 | }
330 | })();
331 |
332 | // functional helpers
333 |
334 | function _hitchArgs(scope, method /*,...*/){
335 | var pre = Array.prototype.slice.call(arguments, 2),
336 | named = isString(method);
337 | return function(){
338 | // arrayify arguments
339 | var args = Array.prototype.slice.call(arguments, 0),
340 | context = scope || this,
341 | // locate our method
342 | f = named ? context[method] : method;
343 | // invoke with collected args
344 | return f && f.apply(context, pre.concat(args)); // mixed
345 | }; // Function
346 | }
347 |
348 | function hitch(/*Object*/scope, /*Function|String*/method /*,...*/){
349 | // summary:
350 | // Returns a function that will only ever execute in the a given scope.
351 | // This allows for easy use of object member functions
352 | // in callbacks and other places in which the "this" keyword may
353 | // otherwise not reference the expected scope.
354 | // Any number of default positional arguments may be passed as parameters
355 | // beyond "method".
356 | // Each of these values will be used to "placehold" (similar to curry)
357 | // for the hitched function.
358 | // scope:
359 | // The scope to use when method executes. If method is a string,
360 | // scope is also the object containing method.
361 | // method:
362 | // A function to be hitched to scope, or the name of the method in
363 | // scope to be hitched.
364 | // example:
365 | // | dojo.hitch(foo, "bar")();
366 | // runs foo.bar() in the scope of foo
367 | // example:
368 | // | dojo.hitch(foo, myFunction);
369 | // returns a function that runs myFunction in the scope of foo
370 | // example:
371 | // Expansion on the default positional arguments passed along from
372 | // hitch. Passed args are mixed first, additional args after.
373 | // | var foo = { bar: function(a, b, c){ console.log(a, b, c); } };
374 | // | var fn = dojo.hitch(foo, "bar", 1, 2);
375 | // | fn(3); // logs "1, 2, 3"
376 | // example:
377 | // | var foo = { bar: 2 };
378 | // | dojo.hitch(foo, function(){ this.bar = 10; })();
379 | // execute an anonymous function in scope of foo
380 |
381 | if(arguments.length > 2){
382 | return _hitchArgs.apply(this, arguments); // Function
383 | }
384 | if(!method){
385 | method = scope;
386 | scope = null;
387 | }
388 | if(isString(method)){
389 | scope = scope || this;
390 | if(!scope[method]){ throw(['dojo.hitch: scope["', method, '"] is null (scope="', scope, '")'].join('')); }
391 | return function(){ return scope[method].apply(scope, arguments); }; // Function
392 | }
393 | return !scope ? method : function(){ return method.apply(scope, arguments); }; // Function
394 | }
395 |
396 | function partial(/*Function|String*/method /*, ...*/){
397 | // summary:
398 | // similar to hitch() except that the scope object is left to be
399 | // whatever the execution context eventually becomes.
400 | // description:
401 | // Calling dojo.partial is the functional equivalent of calling:
402 | // | dojo.hitch(null, funcName, ...);
403 | return hitch.apply(this, [null].concat(Array.prototype.slice.call(arguments, 0))); // Function
404 | }
405 |
406 | // string helpers
407 |
408 | /*=====
409 | dojo.trim = function(str){
410 | // summary:
411 | // Trims whitespace from both sides of the string
412 | // str: String
413 | // String to be trimmed
414 | // returns: String
415 | // Returns the trimmed string
416 | // description:
417 | // This version of trim() was selected for inclusion into the base due
418 | // to its compact size and relatively good performance
419 | // (see [Steven Levithan's blog](http://blog.stevenlevithan.com/archives/faster-trim-javascript)
420 | // Uses String.prototype.trim instead, if available.
421 | // The fastest but longest version of this function is located at
422 | // dojo.string.trim()
423 | return ""; // String
424 | }
425 | =====*/
426 |
427 | var trim = String.prototype.trim ?
428 | function(str){ return str.trim(); } :
429 | function(str){ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); };
430 |
431 | /*=====
432 | dojo.replace = function(tmpl, map, pattern){
433 | // summary:
434 | // Performs parameterized substitutions on a string. Throws an
435 | // exception if any parameter is unmatched.
436 | // tmpl: String
437 | // String to be used as a template.
438 | // map: Object|Function
439 | // If an object, it is used as a dictionary to look up substitutions.
440 | // If a function, it is called for every substitution with following
441 | // parameters: a whole match, a name, an offset, and the whole template
442 | // string (see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String/replace
443 | // for more details).
444 | // pattern: RegEx?
445 | // Optional regular expression objects that overrides the default pattern.
446 | // Must be global and match one item. The default is: /\{([^\}]+)\}/g,
447 | // which matches patterns like that: "{xxx}", where "xxx" is any sequence
448 | // of characters, which doesn't include "}".
449 | // returns: String
450 | // Returns the substituted string.
451 | // example:
452 | // | // uses a dictionary for substitutions:
453 | // | dojo.replace("Hello, {name.first} {name.last} AKA {nick}!",
454 | // | {
455 | // | nick: "Bob",
456 | // | name: {
457 | // | first: "Robert",
458 | // | middle: "X",
459 | // | last: "Cringely"
460 | // | }
461 | // | });
462 | // | // returns: Hello, Robert Cringely AKA Bob!
463 | // example:
464 | // | // uses an array for substitutions:
465 | // | dojo.replace("Hello, {0} {2}!",
466 | // | ["Robert", "X", "Cringely"]);
467 | // | // returns: Hello, Robert Cringely!
468 | // example:
469 | // | // uses a function for substitutions:
470 | // | function sum(a){
471 | // | var t = 0;
472 | // | dojo.forEach(a, function(x){ t += x; });
473 | // | return t;
474 | // | }
475 | // | dojo.replace(
476 | // | "{count} payments averaging {avg} USD per payment.",
477 | // | dojo.hitch(
478 | // | { payments: [11, 16, 12] },
479 | // | function(_, key){
480 | // | switch(key){
481 | // | case "count": return this.payments.length;
482 | // | case "min": return Math.min.apply(Math, this.payments);
483 | // | case "max": return Math.max.apply(Math, this.payments);
484 | // | case "sum": return sum(this.payments);
485 | // | case "avg": return sum(this.payments) / this.payments.length;
486 | // | }
487 | // | }
488 | // | )
489 | // | );
490 | // | // prints: 3 payments averaging 13 USD per payment.
491 | // example:
492 | // | // uses an alternative PHP-like pattern for substitutions:
493 | // | dojo.replace("Hello, ${0} ${2}!",
494 | // | ["Robert", "X", "Cringely"], /\$\{([^\}]+)\}/g);
495 | // | // returns: Hello, Robert Cringely!
496 | return ""; // String
497 | }
498 | =====*/
499 |
500 | var _pattern = /\{([^\}]+)\}/g;
501 | function replace(tmpl, map, pattern){
502 | return tmpl.replace(pattern || _pattern, isFunction(map) ?
503 | map : function(_, k){ return getObject(k, false, map); });
504 | }
505 |
506 | return {
507 | // Crockford (ish) functions
508 | isString: isString,
509 | isArray: isArray,
510 | isFunction: isFunction,
511 | isObject: isObject,
512 | isArrayLike: isArrayLike, //TODO: do we really need it?
513 | isAlien: isAlien, //TODO: do we really need it?
514 | // object access
515 | setObject: setObject,
516 | getObject: getObject,
517 | exists: exists, //TODO: do we really need it?
518 | // object mixers
519 | mixin: mixin,
520 | mixins: mixins,
521 | clone: clone,
522 | extend: extend,
523 | delegate: delegate,
524 | // functional helpers
525 | hitch: hitch,
526 | partial: partial,
527 | // string helpers
528 | trim: trim,
529 | replace: replace
530 | };
531 | });
532 |
--------------------------------------------------------------------------------
/lib/dom-geometry.js:
--------------------------------------------------------------------------------
1 | declare(["./dom", "./window", "./browser"], function(dom, win, browser){
2 | //TODO: we need to decide what functions to keep in 2.0
3 | //TODO: it should be a stand-alone module rather than a part of the base
4 |
5 | // Box functions will assume this model.
6 | // On IE/Opera, BORDER_BOX will be set if the primary document is in quirks mode.
7 | // Can be set to change behavior of box setters.
8 |
9 | // can be either:
10 | // "border-box"
11 | // "content-box" (default)
12 | var boxModel = "content-box";
13 |
14 | // We punt per-node box mode testing completely.
15 | // If anybody cares, we can provide an additional (optional) unit
16 | // that overrides existing code to include per-node box sensitivity.
17 |
18 | // Opera documentation claims that Opera 9 uses border-box in BackCompat mode.
19 | // but experiments (Opera 9.10.8679 on Windows Vista) indicate that it actually continues to use content-box.
20 | // IIRC, earlier versions of Opera did in fact use border-box.
21 | // Opera guys, this is really confusing. Opera being broken in quirks mode is not our fault.
22 |
23 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
24 | if(browser.isIE /*|| browser.isOpera*/){
25 | // client code may have to adjust if compatMode varies across iframes
26 | boxModel = document.compatMode == "BackCompat" ? "border-box" : "content-box";
27 | }
28 | //>>excludeEnd("webkitMobile");
29 |
30 | function getBoxModel(){ return boxModel; }
31 | function setBoxModel(bm){ boxModel = bm; }
32 |
33 | // =============================
34 | // Box Functions
35 | // =============================
36 |
37 | function _getPadExtents(/*DomNode*/n, /*Object*/computedStyle){
38 | // summary:
39 | // Returns object with special values specifically useful for node
40 | // fitting.
41 | // description:
42 | // Returns an object with `w`, `h`, `l`, `t` properties:
43 | // | l/t = left/top padding (respectively)
44 | // | w = the total of the left and right padding
45 | // | h = the total of the top and bottom padding
46 | // If 'node' has position, l/t forms the origin for child nodes.
47 | // The w/h are used for calculating boxes.
48 | // Normally application code will not need to invoke this
49 | // directly, and will use the ...box... functions instead.
50 | var s = computedStyle || dom.getComputedStyle(n),
51 | px = dom.toPixel,
52 | l = px(n, s.paddingLeft),
53 | t = px(n, s.paddingTop);
54 | return {
55 | l: l,
56 | t: t,
57 | w: l + px(n, s.paddingRight),
58 | h: t + px(n, s.paddingBottom)
59 | };
60 | }
61 |
62 | function _getBorderExtents(/*DomNode*/n, /*Object*/computedStyle){
63 | // summary:
64 | // returns an object with properties useful for noting the border
65 | // dimensions.
66 | // description:
67 | // * l/t = the sum of left/top border (respectively)
68 | // * w = the sum of the left and right border
69 | // * h = the sum of the top and bottom border
70 | //
71 | // The w/h are used for calculating boxes.
72 | // Normally application code will not need to invoke this
73 | // directly, and will use the ...box... functions instead.
74 | var ne = "none",
75 | px = dom.toPixel,
76 | s = computedStyle || dom.getComputedStyle(n),
77 | bl = (s.borderLeftStyle != ne ? px(n, s.borderLeftWidth) : 0),
78 | bt = (s.borderTopStyle != ne ? px(n, s.borderTopWidth) : 0);
79 | return {
80 | l: bl,
81 | t: bt,
82 | w: bl + (s.borderRightStyle != ne ? px(n, s.borderRightWidth) : 0),
83 | h: bt + (s.borderBottomStyle != ne ? px(n, s.borderBottomWidth) : 0)
84 | };
85 | }
86 |
87 | function _getPadBorderExtents(/*DomNode*/n, /*Object*/computedStyle){
88 | // summary:
89 | // Returns object with properties useful for box fitting with
90 | // regards to padding.
91 | // description:
92 | // * l/t = the sum of left/top padding and left/top border (respectively)
93 | // * w = the sum of the left and right padding and border
94 | // * h = the sum of the top and bottom padding and border
95 | //
96 | // The w/h are used for calculating boxes.
97 | // Normally application code will not need to invoke this
98 | // directly, and will use the ...box... functions instead.
99 | var s = computedStyle || dom.getComputedStyle(n),
100 | p = _getPadExtents(n, s),
101 | b = _getBorderExtents(n, s);
102 | return {
103 | l: p.l + b.l,
104 | t: p.t + b.t,
105 | w: p.w + b.w,
106 | h: p.h + b.h
107 | };
108 | }
109 |
110 | function _getMarginExtents(n, computedStyle){
111 | // summary:
112 | // returns object with properties useful for box fitting with
113 | // regards to box margins (i.e., the outer-box).
114 | //
115 | // * l/t = marginLeft, marginTop, respectively
116 | // * w = total width, margin inclusive
117 | // * h = total height, margin inclusive
118 | //
119 | // The w/h are used for calculating boxes.
120 | // Normally application code will not need to invoke this
121 | // directly, and will use the ...box... functions instead.
122 | var s = computedStyle || dom.getComputedStyle(n),
123 | px = dom.toPixel,
124 | l = px(n, s.marginLeft),
125 | t = px(n, s.marginTop),
126 | r = px(n, s.marginRight),
127 | b = px(n, s.marginBottom);
128 | if(browser.isWebKit && (s.position != "absolute")){
129 | // FIXME: Safari's version of the computed right margin
130 | // is the space between our right edge and the right edge
131 | // of our offsetParent.
132 | // What we are looking for is the actual margin value as
133 | // determined by CSS.
134 | // Hack solution is to assume left/right margins are the same.
135 | r = l;
136 | }
137 | return {l: l, t: t, w: l + r, h: t + b};
138 | }
139 |
140 | // Box getters work in any box context because offsetWidth/clientWidth
141 | // are invariant wrt box context
142 | //
143 | // They do *not* work for display: inline objects that have padding styles
144 | // because the user agent ignores padding (it's bogus styling in any case)
145 | //
146 | // Be careful with IMGs because they are inline or block depending on
147 | // browser and browser mode.
148 |
149 | // Although it would be easier to read, there are not separate versions of
150 | // _getMarginBox for each browser because:
151 | // 1. the branching is not expensive
152 | // 2. factoring the shared code wastes cycles (function call overhead)
153 | // 3. duplicating the shared code wastes bytes
154 |
155 | function _getMarginBox(/*DomNode*/node, /*Object*/computedStyle){
156 | // summary:
157 | // returns an object that encodes the width, height, left and top
158 | // positions of the node's margin box.
159 | var s = computedStyle || dom.getComputedStyle(node), me = _getMarginExtents(node, s),
160 | l = node.offsetLeft - me.l, t = node.offsetTop - me.t, p = node.parentNode;
161 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
162 | if(browser.isMoz){
163 | // Mozilla:
164 | // If offsetParent has a computed overflow != visible, the offsetLeft is decreased
165 | // by the parent's border.
166 | // We don't want to compute the parent's style, so instead we examine node's
167 | // computed left/top which is more stable.
168 | var sl = parseFloat(s.left), st = parseFloat(s.top);
169 | if(!isNaN(sl) && !isNaN(st)){
170 | l = sl, t = st;
171 | }else{
172 | // If child's computed left/top are not parseable as a number (e.g. "auto"), we
173 | // have no choice but to examine the parent's computed style.
174 | if(p && p.style){
175 | var pcs = dom.getComputedStyle(p);
176 | if(pcs.overflow != "visible"){
177 | var be = _getBorderExtents(p, pcs);
178 | l += be.l, t += be.t;
179 | }
180 | }
181 | }
182 | }else if(browser.isOpera || (browser.isIE > 7 && !browser.isQuirks)){
183 | // On Opera and IE 8, offsetLeft/Top includes the parent's border
184 | if(p){
185 | be = _getBorderExtents(p);
186 | l -= be.l;
187 | t -= be.t;
188 | }
189 | }
190 | //>>excludeEnd("webkitMobile");
191 | return {
192 | l: l,
193 | t: t,
194 | w: node.offsetWidth + me.w,
195 | h: node.offsetHeight + me.h
196 | };
197 | }
198 |
199 | //TODO: unused function - remove?
200 | /*
201 | function _getMarginSize(node, computedStyle){
202 | // summary:
203 | // returns an object that encodes the width and height of
204 | // the node's margin box
205 | node = byId(node);
206 | var me = _getMarginExtents(node, computedStyle || dom.getComputedStyle(node)),
207 | size = node.getBoundingClientRect();
208 | return {
209 | w: (size.right - size.left) + me.w,
210 | h: (size.bottom - size.top) + me.h
211 | };
212 | }
213 | */
214 |
215 | function _getContentBox(node, computedStyle){
216 | // summary:
217 | // Returns an object that encodes the width, height, left and top
218 | // positions of the node's content box, irrespective of the
219 | // current box model.
220 |
221 | // clientWidth/Height are important since the automatically account for scrollbars
222 | // fallback to offsetWidth/Height for special cases (see #3378)
223 | var s = computedStyle || dom.getComputedStyle(node),
224 | pe = _getPadExtents(node, s),
225 | be = _getBorderExtents(node, s),
226 | w = node.clientWidth, h;
227 | if(!w){
228 | w = node.offsetWidth, h = node.offsetHeight;
229 | }else{
230 | h = node.clientHeight, be.w = be.h = 0;
231 | }
232 | // On Opera, offsetLeft includes the parent's border
233 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
234 | if(browser.isOpera){ pe.l += be.l; pe.t += be.t; }
235 | //>>excludeEnd("webkitMobile");
236 | return {
237 | l: pe.l,
238 | t: pe.t,
239 | w: w - pe.w - be.w,
240 | h: h - pe.h - be.h
241 | };
242 | }
243 |
244 | //TODO: unused function - remove?
245 | /*
246 | function _getBorderBox(node, computedStyle){
247 | var s = computedStyle || dom.getComputedStyle(node),
248 | pe = _getPadExtents(node, s),
249 | cb = _getContentBox(node, s);
250 | return {
251 | l: cb.l - pe.l,
252 | t: cb.t - pe.t,
253 | w: cb.w + pe.w,
254 | h: cb.h + pe.h
255 | };
256 | }
257 | */
258 |
259 | // Box setters depend on box context because interpretation of width/height styles
260 | // vary wrt box context.
261 | //
262 | // The value of dojo.boxModel is used to determine box context.
263 | // dojo.boxModel can be set directly to change behavior.
264 | //
265 | // Beware of display: inline objects that have padding styles
266 | // because the user agent ignores padding (it's a bogus setup anyway)
267 | //
268 | // Be careful with IMGs because they are inline or block depending on
269 | // browser and browser mode.
270 | //
271 | // Elements other than DIV may have special quirks, like built-in
272 | // margins or padding, or values not detectable via computedStyle.
273 | // In particular, margins on TABLE do not seems to appear
274 | // at all in computedStyle on Mozilla.
275 |
276 | function _setBox(/*DomNode*/node, /*Number?*/l, /*Number?*/t, /*Number?*/w, /*Number?*/h, /*String?*/u){
277 | // summary:
278 | // sets width/height/left/top in the current (native) box-model
279 | // dimentions. Uses the unit passed in u.
280 | // node:
281 | // DOM Node reference. Id string not supported for performance
282 | // reasons.
283 | // l:
284 | // left offset from parent.
285 | // t:
286 | // top offset from parent.
287 | // w:
288 | // width in current box model.
289 | // h:
290 | // width in current box model.
291 | // u:
292 | // unit measure to use for other measures. Defaults to "px".
293 | u = u || "px";
294 | var s = node.style;
295 | if(!isNaN(l)){ s.left = l + u; }
296 | if(!isNaN(t)){ s.top = t + u; }
297 | if(w >= 0){ s.width = w + u; }
298 | if(h >= 0){ s.height = h + u; }
299 | }
300 |
301 | function _isButtonTag(/*DomNode*/node){
302 | // summary:
303 | // True if the node is BUTTON or INPUT.type="button".
304 | return node.tagName == "BUTTON"
305 | || node.tagName == "INPUT" && (node.getAttribute("type") || '').toUpperCase() == "BUTTON"; // boolean
306 | }
307 |
308 | function _usesBorderBox(/*DomNode*/node){
309 | // summary:
310 | // True if the node uses border-box layout.
311 |
312 | // We could test the computed style of node to see if a particular box
313 | // has been specified, but there are details and we choose not to bother.
314 |
315 | // TABLE and BUTTON (and INPUT type=button) are always border-box by default.
316 | // If you have assigned a different box to either one via CSS then
317 | // box functions will break.
318 |
319 | var n = node.tagName;
320 | return boxModel == "border-box" || n == "TABLE" || _isButtonTag(node); // boolean
321 | }
322 |
323 | function _setContentSize(/*DomNode*/node, /*Number*/widthPx, /*Number*/heightPx, /*Object*/computedStyle){
324 | // summary:
325 | // Sets the size of the node's contents, irrespective of margins,
326 | // padding, or borders.
327 | if(_usesBorderBox(node)){
328 | var pb = _getPadBorderExtents(node, computedStyle);
329 | if(widthPx >= 0){ widthPx += pb.w; }
330 | if(heightPx >= 0){ heightPx += pb.h; }
331 | }
332 | _setBox(node, NaN, NaN, widthPx, heightPx);
333 | }
334 |
335 | function _setMarginBox(/*DomNode*/node, /*Number?*/leftPx, /*Number?*/topPx, /*Number?*/widthPx,
336 | /*Number?*/heightPx, /*Object*/computedStyle){
337 | // summary:
338 | // sets the size of the node's margin box and placement
339 | // (left/top), irrespective of box model. Think of it as a
340 | // passthrough to dojo._setBox that handles box-model vagaries for
341 | // you.
342 |
343 | var s = computedStyle || dom.getComputedStyle(node),
344 | // Some elements have special padding, margin, and box-model settings.
345 | // To use box functions you may need to set padding, margin explicitly.
346 | // Controlling box-model is harder, in a pinch you might set dojo.boxModel.
347 | bb = _usesBorderBox(node),
348 | pb = bb ? _nilExtents : _getPadBorderExtents(node, s)
349 | ;
350 | if(browser.isWebKit){
351 | // on Safari (3.1.2), button nodes with no explicit size have a default margin
352 | // setting an explicit size eliminates the margin.
353 | // We have to swizzle the width to get correct margin reading.
354 | if(_isButtonTag(node)){
355 | var ns = node.style;
356 | if(widthPx >= 0 && !ns.width){ ns.width = "4px"; }
357 | if(heightPx >= 0 && !ns.height){ ns.height = "4px"; }
358 | }
359 | }
360 | var mb = _getMarginExtents(node, s);
361 | if(widthPx >= 0){ widthPx = Math.max(widthPx - pb.w - mb.w, 0); }
362 | if(heightPx >= 0){ heightPx = Math.max(heightPx - pb.h - mb.h, 0); }
363 | _setBox(node, leftPx, topPx, widthPx, heightPx);
364 | }
365 |
366 | var _nilExtents = { l:0, t:0, w:0, h:0 };
367 |
368 | // public API
369 |
370 | function marginBox(/*DomNode|String*/node, /*Object?*/box){
371 | // summary:
372 | // Getter/setter for the margin-box of node.
373 | // description:
374 | // Getter/setter for the margin-box of node.
375 | // Returns an object in the expected format of box (regardless
376 | // if box is passed). The object might look like:
377 | // `{ l: 50, t: 200, w: 300: h: 150 }`
378 | // for a node offset from its parent 50px to the left, 200px from
379 | // the top with a margin width of 300px and a margin-height of
380 | // 150px.
381 | // node:
382 | // id or reference to DOM Node to get/set box for
383 | // box:
384 | // If passed, denotes that dojo.marginBox() should
385 | // update/set the margin box for node. Box is an object in the
386 | // above format. All properties are optional if passed.
387 | // example:
388 | // Retrieve the marginbox of a passed node
389 | // | var box = dojo.marginBox("someNodeId");
390 | // | console.dir(box);
391 | //
392 | // example:
393 | // Set a node's marginbox to the size of another node
394 | // | var box = dojo.marginBox("someNodeId");
395 | // | dojo.marginBox("someOtherNode", box);
396 |
397 | var n = dom.byId(node), s = dom.getComputedStyle(n);
398 | return !box ? _getMarginBox(n, s) : _setMarginBox(n, box.l, box.t, box.w, box.h, s); // Object
399 | }
400 |
401 | function contentBox(/*DomNode|String*/node, /*Object?*/box){
402 | // summary:
403 | // Getter/setter for the content-box of node.
404 | // description:
405 | // Returns an object in the expected format of box (regardless if box is passed).
406 | // The object might look like:
407 | // `{ l: 50, t: 200, w: 300: h: 150 }`
408 | // for a node offset from its parent 50px to the left, 200px from
409 | // the top with a content width of 300px and a content-height of
410 | // 150px. Note that the content box may have a much larger border
411 | // or margin box, depending on the box model currently in use and
412 | // CSS values set/inherited for node.
413 | // While the getter will return top and left values, the
414 | // setter only accepts setting the width and height.
415 | // node:
416 | // id or reference to DOM Node to get/set box for
417 | // box:
418 | // If passed, denotes that dojo.contentBox() should
419 | // update/set the content box for node. Box is an object in the
420 | // above format, but only w (width) and h (height) are supported.
421 | // All properties are optional if passed.
422 | var n = dom.byId(node), s = dom.getComputedStyle(n);
423 | return !box ? _getContentBox(n, s) : _setContentSize(n, box.w, box.h, s); // Object
424 | }
425 |
426 | // =============================
427 | // Positioning
428 | // =============================
429 |
430 | //TODO: unused function - remove?
431 | /*
432 | function _sumAncestorProperties(node, prop){
433 | if(!(node = (node || 0).parentNode)){return 0}
434 | var val, retVal = 0, _b = win.body();
435 | while(node && node.style){
436 | if(dom.getComputedStyle(node).position == "fixed"){
437 | return 0;
438 | }
439 | val = node[prop];
440 | if(val){
441 | retVal += val - 0;
442 | // opera and khtml #body & #html has the same values, we only
443 | // need one value
444 | if(node == _b){ break; }
445 | }
446 | node = node.parentNode;
447 | }
448 | return retVal; // integer
449 | }
450 | */
451 |
452 | function _docScroll(){
453 | var n = win.global();
454 | return "pageXOffset" in n ? {x: n.pageXOffset, y: n.pageYOffset } :
455 | (n = browser.isQuirks? win.body() : win.doc().documentElement,
456 | {x: _fixIeBiDiScrollLeft(n.scrollLeft || 0), y: n.scrollTop || 0 });
457 | }
458 |
459 | function _isBodyLtr(){
460 | //TODO: we need to decide how where to keep _bodyLtr
461 | //return "_bodyLtr" in d ? d._bodyLtr :
462 | // d._bodyLtr = (win.body().dir || win.doc().documentElement.dir || "ltr").toLowerCase() == "ltr"; // Boolean
463 | return (win.body().dir || win.doc().documentElement.dir || "ltr").toLowerCase() == "ltr"; // Boolean
464 | }
465 |
466 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
467 | function _getIeDocumentElementOffset(){
468 | // summary:
469 | // returns the offset in x and y from the document body to the
470 | // visual edge of the page
471 | // description:
472 | // The following values in IE contain an offset:
473 | // | event.clientX
474 | // | event.clientY
475 | // | node.getBoundingClientRect().left
476 | // | node.getBoundingClientRect().top
477 | // But other position related values do not contain this offset,
478 | // such as node.offsetLeft, node.offsetTop, node.style.left and
479 | // node.style.top. The offset is always (2, 2) in LTR direction.
480 | // When the body is in RTL direction, the offset counts the width
481 | // of left scroll bar's width. This function computes the actual
482 | // offset.
483 |
484 | //NOTE: assumes we're being called in an IE browser
485 |
486 | var de = win.doc().documentElement; // only deal with HTML element here, position() handles body/quirks
487 |
488 | if(browser.isIE < 8){
489 | var r = de.getBoundingClientRect(), // works well for IE6+
490 | l = r.left, t = r.top;
491 | if(browser.isIE < 7){
492 | l += de.clientLeft; // scrollbar size in strict/RTL, or,
493 | t += de.clientTop; // HTML border size in strict
494 | }
495 | return {
496 | x: l < 0 ? 0 : l, // FRAME element border size can lead to inaccurate negative values
497 | y: t < 0 ? 0 : t
498 | };
499 | }else{
500 | return {
501 | x: 0,
502 | y: 0
503 | };
504 | }
505 | }
506 | //>>excludeEnd("webkitMobile");
507 |
508 | function _fixIeBiDiScrollLeft(/*Integer*/ scrollLeft){
509 | // In RTL direction, scrollLeft should be a negative value, but IE
510 | // returns a positive one. All codes using documentElement.scrollLeft
511 | // must call this function to fix this error, otherwise the position
512 | // will offset to right when there is a horizontal scrollbar.
513 |
514 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
515 | if(browser.isIE && !_isBodyLtr()){
516 | var de = browser.isQuirks ? win.body() : win.doc().documentElement;
517 | return (browser.isIE < 8 || browser.isQuirks) ? (scrollLeft + de.clientWidth - de.scrollWidth) : -scrollLeft; // Integer
518 | }
519 | //>>excludeEnd("webkitMobile");
520 | return scrollLeft; // Integer
521 | }
522 |
523 | // FIXME: need a setter for coords or a moveTo!!
524 | function position(/*DomNode*/node, /*Boolean?*/includeScroll){
525 | // summary:
526 | // Gets the position and size of the passed element relative to
527 | // the viewport (if includeScroll==false), or relative to the
528 | // document root (if includeScroll==true).
529 | //
530 | // description:
531 | // Returns an object of the form:
532 | // { x: 100, y: 300, w: 20, h: 15 }
533 | // If includeScroll==true, the x and y values will include any
534 | // document offsets that may affect the position relative to the
535 | // viewport.
536 | // Uses the border-box model (inclusive of border and padding but
537 | // not margin). Does not act as a setter.
538 |
539 | node = dom.byId(node);
540 | var db = win.body(),
541 | dh = db.parentNode,
542 | ret = node.getBoundingClientRect();
543 | ret = {x: ret.left, y: ret.top, w: ret.right - ret.left, h: ret.bottom - ret.top};
544 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
545 | if(browser.isIE){
546 | // On IE there's a 2px offset that we need to adjust for, see _getIeDocumentElementOffset()
547 | var offset = _getIeDocumentElementOffset();
548 |
549 | // fixes the position in IE, quirks mode
550 | ret.x -= offset.x + (browser.isQuirks ? db.clientLeft + db.offsetLeft : 0);
551 | ret.y -= offset.y + (browser.isQuirks ? db.clientTop + db.offsetTop : 0);
552 | }else if(browser.isFF == 3){
553 | // In FF3 you have to subtract the document element margins.
554 | // Fixed in FF3.5 though.
555 | var cs = dom.getComputedStyle(dh), px = toPixel;
556 | ret.x -= px(dh, cs.marginLeft) + px(dh, cs.borderLeftWidth);
557 | ret.y -= px(dh, cs.marginTop) + px(dh, cs.borderTopWidth);
558 | }
559 | //>>excludeEnd("webkitMobile");
560 | // account for document scrolling
561 | // if offsetParent is used, ret value already includes scroll position
562 | // so we may have to actually remove that value if !includeScroll
563 | if(includeScroll){
564 | var scroll = _docScroll();
565 | ret.x += scroll.x;
566 | ret.y += scroll.y;
567 | }
568 |
569 | return ret; // Object
570 | }
571 |
572 | return {
573 | getBoxModel: getBoxModel,
574 | setBoxModel: setBoxModel,
575 | marginBox: marginBox,
576 | contentBox: contentBox,
577 | position: position
578 | };
579 | });
580 |
--------------------------------------------------------------------------------
/lib/fx.js:
--------------------------------------------------------------------------------
1 | define(["./lang", "./color", "./browser", "./dom"], function(lang, color, browser, dom){
2 | // This module implements animation and of little value for non-interactive environments.
3 |
4 | //TODO: needs AOP to replace dojo.connect() and dojo.disconnect()
5 |
6 | //TODO: we should do something with the configuration
7 | var debugFlag = false;
8 |
9 | function _Line(/*int*/ start, /*int*/ end){
10 | // summary:
11 | // dojo._Line is the object used to generate values from a start value
12 | // to an end value
13 | // start: int
14 | // Beginning value for range
15 | // end: int
16 | // Ending value for range
17 | this.start = start;
18 | this.end = end;
19 | }
20 |
21 | _Line.prototype.getValue = function(/*float*/ n){
22 | // summary: Returns the point on the line
23 | // n: a floating point number greater than 0 and less than 1
24 | return ((this.end - this.start) * n) + this.start; // Decimal
25 | };
26 |
27 | function Animation(args){
28 | // summary:
29 | // A generic animation class that fires callbacks into its handlers
30 | // object at various states.
31 | // description:
32 | // A generic animation class that fires callbacks into its handlers
33 | // object at various states. Nearly all dojo animation functions
34 | // return an instance of this method, usually without calling the
35 | // .play() method beforehand. Therefore, you will likely need to
36 | // call .play() on instances of `dojo.Animation` when one is
37 | // returned.
38 | // args: Object
39 | // The 'magic argument', mixing all the properties into this
40 | // animation instance.
41 |
42 | lang.mixin(this, args);
43 | if(lang.isArray(this.curve)){
44 | this.curve = new _Line(this.curve[0], this.curve[1]);
45 | }
46 |
47 | }
48 |
49 | // the local timer, stubbed into all Animation instances
50 | var ctr = 0,
51 | timer = null,
52 | runner = {
53 | run: function(){}
54 | };
55 |
56 |
57 | Animation.prototype = {
58 | // duration: Integer
59 | // The time in milliseconds the animation will take to run
60 | duration: 350,
61 |
62 | /*=====
63 | // curve: dojo._Line|Array
64 | // A two element array of start and end values, or a `dojo._Line` instance to be
65 | // used in the Animation.
66 | curve: null,
67 |
68 | // easing: Function?
69 | // A Function to adjust the acceleration (or deceleration) of the progress
70 | // across a dojo._Line
71 | easing: null,
72 | =====*/
73 |
74 | // repeat: Integer?
75 | // The number of times to loop the animation
76 | repeat: 0,
77 |
78 | // rate: Integer?
79 | // the time in milliseconds to wait before advancing to next frame
80 | // (used as a fps timer: 1000/rate = fps)
81 | rate: 20 /* 50 fps */,
82 |
83 | /*=====
84 | // delay: Integer?
85 | // The time in milliseconds to wait before starting animation after it
86 | // has been .play()'ed
87 | delay: null,
88 |
89 | // beforeBegin: Event?
90 | // Synthetic event fired before a dojo.Animation begins playing (synchronous)
91 | beforeBegin: null,
92 |
93 | // onBegin: Event?
94 | // Synthetic event fired as a dojo.Animation begins playing (useful?)
95 | onBegin: null,
96 |
97 | // onAnimate: Event?
98 | // Synthetic event fired at each interval of a `dojo.Animation`
99 | onAnimate: null,
100 |
101 | // onEnd: Event?
102 | // Synthetic event fired after the final frame of a `dojo.Animation`
103 | onEnd: null,
104 |
105 | // onPlay: Event?
106 | // Synthetic event fired any time a `dojo.Animation` is play()'ed
107 | onPlay: null,
108 |
109 | // onPause: Event?
110 | // Synthetic event fired when a `dojo.Animation` is paused
111 | onPause: null,
112 |
113 | // onStop: Event
114 | // Synthetic event fires when a `dojo.Animation` is stopped
115 | onStop: null,
116 |
117 | =====*/
118 |
119 | _percent: 0,
120 | _startRepeatCount: 0,
121 |
122 | _getStep: function(){
123 | var _p = this._percent,
124 | _e = this.easing
125 | ;
126 | return _e ? _e(_p) : _p;
127 | },
128 | _fire: function(/*Event*/ evt, /*Array?*/ args){
129 | // summary:
130 | // Convenience function. Fire event "evt" and pass it the
131 | // arguments specified in "args".
132 | // description:
133 | // Convenience function. Fire event "evt" and pass it the
134 | // arguments specified in "args".
135 | // Fires the callback in the scope of the `dojo.Animation`
136 | // instance.
137 | // evt:
138 | // The event to fire.
139 | // args:
140 | // The arguments to pass to the event.
141 | var a = args||[];
142 | if(this[evt]){
143 | if(debugFlag){
144 | this[evt].apply(this, a);
145 | }else{
146 | try{
147 | this[evt].apply(this, a);
148 | }catch(e){
149 | // squelch and log because we shouldn't allow exceptions in
150 | // synthetic event handlers to cause the internal timer to run
151 | // amuck, potentially pegging the CPU. I'm not a fan of this
152 | // squelch, but hopefully logging will make it clear what's
153 | // going on
154 | console.error("exception in animation handler for:", evt);
155 | console.error(e);
156 | }
157 | }
158 | }
159 | return this; // dojo.Animation
160 | },
161 |
162 | play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
163 | // summary:
164 | // Start the animation.
165 | // delay:
166 | // How many milliseconds to delay before starting.
167 | // gotoStart:
168 | // If true, starts the animation from the beginning; otherwise,
169 | // starts it from its current position.
170 | // returns: dojo.Animation
171 | // The instance to allow chaining.
172 |
173 | var _t = this;
174 | if(_t._delayTimer){ _t._clearTimer(); }
175 | if(gotoStart){
176 | _t._stopTimer();
177 | _t._active = _t._paused = false;
178 | _t._percent = 0;
179 | }else if(_t._active && !_t._paused){
180 | return _t;
181 | }
182 |
183 | _t._fire("beforeBegin", [_t.node]);
184 |
185 | var de = delay || _t.delay,
186 | _p = lang.hitch(_t, "_play");
187 |
188 | if(de > 0){
189 | _t._delayTimer = setTimeout(_p, de);
190 | return _t;
191 | }
192 | _p();
193 | return _t;
194 | },
195 |
196 | _play: function(){
197 | var _t = this;
198 | if(_t._delayTimer){ _t._clearTimer(); }
199 | _t._startTime = new Date().getTime();
200 | if(_t._paused){
201 | _t._startTime -= _t.duration * _t._percent;
202 | }
203 |
204 | _t._active = true;
205 | _t._paused = false;
206 | var value = _t.curve.getValue(_t._getStep());
207 | if(!_t._percent){
208 | if(!_t._startRepeatCount){
209 | _t._startRepeatCount = _t.repeat;
210 | }
211 | _t._fire("onBegin", [value]);
212 | }
213 |
214 | _t._fire("onPlay", [value]);
215 |
216 | _t._cycle();
217 | return _t; // dojo.Animation
218 | },
219 |
220 | pause: function(){
221 | // summary: Pauses a running animation.
222 | var _t = this;
223 | if(_t._delayTimer){ _t._clearTimer(); }
224 | _t._stopTimer();
225 | if(!_t._active){ return _t; /*dojo.Animation*/ }
226 | _t._paused = true;
227 | _t._fire("onPause", [_t.curve.getValue(_t._getStep())]);
228 | return _t; // dojo.Animation
229 | },
230 |
231 | gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){
232 | // summary:
233 | // Sets the progress of the animation.
234 | // percent:
235 | // A percentage in decimal notation (between and including 0.0 and 1.0).
236 | // andPlay:
237 | // If true, play the animation after setting the progress.
238 | var _t = this;
239 | _t._stopTimer();
240 | _t._active = _t._paused = true;
241 | _t._percent = percent;
242 | if(andPlay){ _t.play(); }
243 | return _t; // dojo.Animation
244 | },
245 |
246 | stop: function(/*boolean?*/ gotoEnd){
247 | // summary: Stops a running animation.
248 | // gotoEnd: If true, the animation will end.
249 | var _t = this;
250 | if(_t._delayTimer){ _t._clearTimer(); }
251 | if(!_t._timer){ return _t; /* dojo.Animation */ }
252 | _t._stopTimer();
253 | if(gotoEnd){
254 | _t._percent = 1;
255 | }
256 | _t._fire("onStop", [_t.curve.getValue(_t._getStep())]);
257 | _t._active = _t._paused = false;
258 | return _t; // dojo.Animation
259 | },
260 |
261 | status: function(){
262 | // summary:
263 | // Returns a string token representation of the status of
264 | // the animation, one of: "paused", "playing", "stopped"
265 | if(this._active){
266 | return this._paused ? "paused" : "playing"; // String
267 | }
268 | return "stopped"; // String
269 | },
270 |
271 | _cycle: function(){
272 | var _t = this;
273 | if(_t._active){
274 | var curr = new Date().getTime();
275 | var step = (curr - _t._startTime) / (_t.duration);
276 |
277 | if(step >= 1){
278 | step = 1;
279 | }
280 | _t._percent = step;
281 |
282 | // Perform easing
283 | if(_t.easing){
284 | step = _t.easing(step);
285 | }
286 |
287 | _t._fire("onAnimate", [_t.curve.getValue(step)]);
288 |
289 | if(_t._percent < 1){
290 | _t._startTimer();
291 | }else{
292 | _t._active = false;
293 |
294 | if(_t.repeat > 0){
295 | _t.repeat--;
296 | _t.play(null, true);
297 | }else if(_t.repeat == -1){
298 | _t.play(null, true);
299 | }else{
300 | if(_t._startRepeatCount){
301 | _t.repeat = _t._startRepeatCount;
302 | _t._startRepeatCount = 0;
303 | }
304 | }
305 | _t._percent = 0;
306 | _t._fire("onEnd", [_t.node]);
307 | !_t.repeat && _t._stopTimer();
308 | }
309 | }
310 | return _t; // dojo.Animation
311 | },
312 |
313 | _clearTimer: function(){
314 | // summary: Clear the play delay timer
315 | clearTimeout(this._delayTimer);
316 | delete this._delayTimer;
317 | },
318 |
319 | _startTimer: function(){
320 | if(!this._timer){
321 | this._timer = d.connect(runner, "run", this, "_cycle");
322 | ctr++;
323 | }
324 | if(!timer){
325 | timer = setInterval(lang.hitch(runner, "run"), this.rate);
326 | }
327 | },
328 |
329 | _stopTimer: function(){
330 | if(this._timer){
331 | d.disconnect(this._timer);
332 | this._timer = null;
333 | ctr--;
334 | }
335 | if(ctr <= 0){
336 | clearInterval(timer);
337 | timer = null;
338 | ctr = 0;
339 | }
340 | }
341 | };
342 |
343 | var _makeFadeable =
344 | //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
345 | browser.isIE ? function(node){
346 | // only set the zoom if the "tickle" value would be the same as the
347 | // default
348 | var ns = node.style;
349 | // don't set the width to auto if it didn't already cascade that way.
350 | // We don't want to f anyones designs
351 | if(!ns.width.length && dom.style(node, "width") == "auto"){
352 | ns.width = "auto";
353 | }
354 | } :
355 | //>>excludeEnd("webkitMobile");
356 | function(){};
357 |
358 | function _fade(/*Object*/ args){
359 | // summary:
360 | // Returns an animation that will fade the node defined by
361 | // args.node from the start to end values passed (args.start
362 | // args.end) (end is mandatory, start is optional)
363 |
364 | args.node = dom.byId(args.node);
365 | var fArgs = lang.mixin({properties: {}}, args),
366 | props = fArgs.properties.opacity = {};
367 |
368 | props.start = !("start" in fArgs) ?
369 | function(){
370 | return +dom.style(fArgs.node, "opacity") || 0;
371 | } : fArgs.start;
372 | props.end = fArgs.end;
373 |
374 | var anim = animateProperty(fArgs);
375 | d.connect(anim, "beforeBegin", d.partial(_makeFadeable, fArgs.node));
376 |
377 | return anim; // dojo.Animation
378 | }
379 |
380 | /*=====
381 | dojo.__FadeArgs = function(node, duration, easing){
382 | // node: DOMNode|String
383 | // The node referenced in the animation
384 | // duration: Integer?
385 | // Duration of the animation in milliseconds.
386 | // easing: Function?
387 | // An easing function.
388 | this.node = node;
389 | this.duration = duration;
390 | this.easing = easing;
391 | }
392 | =====*/
393 |
394 | function fadeIn(/*dojo.__FadeArgs*/ args){
395 | // summary:
396 | // Returns an animation that will fade node defined in 'args' from
397 | // its current opacity to fully opaque.
398 | return _fade(lang.mixin({end: 1}, args)); // dojo.Animation
399 | }
400 |
401 | function fadeOut(/*dojo.__FadeArgs*/ args){
402 | // summary:
403 | // Returns an animation that will fade node defined in 'args'
404 | // from its current opacity to fully transparent.
405 | return _fade(lang.mixin({end: 0}, args)); // dojo.Animation
406 | }
407 |
408 | function _defaultEasing(/*Decimal?*/ n){
409 | // summary: The default easing function for dojo.Animation(s)
410 | return 0.5 + Math.sin((n + 1.5) * Math.PI) / 2;
411 | }
412 |
413 | function PropLine(properties){
414 | // PropLine is an internal class which is used to model the values of
415 | // an a group of CSS properties across an animation lifecycle. In
416 | // particular, the "getValue" function handles getting interpolated
417 | // values between start and end for a particular CSS value.
418 | this._properties = properties;
419 | for(var p in properties){
420 | var prop = properties[p];
421 | if(prop.start instanceof color.Color){
422 | // create a reusable temp color object to keep intermediate results
423 | prop.tempColor = new color.Color();
424 | }
425 | }
426 | }
427 |
428 | PropLine.prototype.getValue = function(r){
429 | var ret = {};
430 | for(var p in this._properties){
431 | var prop = this._properties[p], start = prop.start;
432 | if(start instanceof color.Color){
433 | ret[p] = color.blendColors(start, prop.end, r, prop.tempColor).toCss();
434 | }else if(!lang.isArray(start)){
435 | ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units || "px" : 0);
436 | }
437 | }
438 | return ret;
439 | };
440 |
441 | /*=====
442 | dojo.declare("dojo.__AnimArgs", [dojo.__FadeArgs], {
443 | // Properties: Object?
444 | // A hash map of style properties to Objects describing the transition,
445 | // such as the properties of dojo._Line with an additional 'units' property
446 | properties: {}
447 |
448 | //TODOC: add event callbacks
449 | });
450 | =====*/
451 |
452 | function animateProperty(/*dojo.__AnimArgs*/ args){
453 | // summary:
454 | // Returns an animation that will transition the properties of
455 | // node defined in `args` depending how they are defined in
456 | // `args.properties`
457 | //
458 | // description:
459 | // `dojo.animateProperty` is the foundation of most `dojo.fx`
460 | // animations. It takes an object of "properties" corresponding to
461 | // style properties, and animates them in parallel over a set
462 | // duration.
463 | //
464 | // example:
465 | // A simple animation that changes the width of the specified node.
466 | // | dojo.animateProperty({
467 | // | node: "nodeId",
468 | // | properties: { width: 400 },
469 | // | }).play();
470 | // Dojo figures out the start value for the width and converts the
471 | // integer specified for the width to the more expressive but
472 | // verbose form `{ width: { end: '400', units: 'px' } }` which you
473 | // can also specify directly. Defaults to 'px' if ommitted.
474 | //
475 | // example:
476 | // Animate width, height, and padding over 2 seconds... the
477 | // pedantic way:
478 | // | dojo.animateProperty({ node: node, duration:2000,
479 | // | properties: {
480 | // | width: { start: '200', end: '400', units:"px" },
481 | // | height: { start:'200', end: '400', units:"px" },
482 | // | paddingTop: { start:'5', end:'50', units:"px" }
483 | // | }
484 | // | }).play();
485 | // Note 'paddingTop' is used over 'padding-top'. Multi-name CSS properties
486 | // are written using "mixed case", as the hyphen is illegal as an object key.
487 | //
488 | // example:
489 | // Plug in a different easing function and register a callback for
490 | // when the animation ends. Easing functions accept values between
491 | // zero and one and return a value on that basis. In this case, an
492 | // exponential-in curve.
493 | // | dojo.animateProperty({
494 | // | node: "nodeId",
495 | // | // dojo figures out the start value
496 | // | properties: { width: { end: 400 } },
497 | // | easing: function(n){
498 | // | return (n==0) ? 0 : Math.pow(2, 10 * (n - 1));
499 | // | },
500 | // | onEnd: function(node){
501 | // | // called when the animation finishes. The animation
502 | // | // target is passed to this function
503 | // | }
504 | // | }).play(500); // delay playing half a second
505 | //
506 | // example:
507 | // Like all `dojo.Animation`s, animateProperty returns a handle to the
508 | // Animation instance, which fires the events common to Dojo FX. Use `dojo.connect`
509 | // to access these events outside of the Animation definiton:
510 | // | var anim = dojo.animateProperty({
511 | // | node:"someId",
512 | // | properties:{
513 | // | width:400, height:500
514 | // | }
515 | // | });
516 | // | dojo.connect(anim,"onEnd", function(){
517 | // | console.log("animation ended");
518 | // | });
519 | // | // play the animation now:
520 | // | anim.play();
521 | //
522 | // example:
523 | // Each property can be a function whose return value is substituted along.
524 | // Additionally, each measurement (eg: start, end) can be a function. The node
525 | // reference is passed direcly to callbacks.
526 | // | dojo.animateProperty({
527 | // | node:"mine",
528 | // | properties:{
529 | // | height:function(node){
530 | // | // shrink this node by 50%
531 | // | return dojo.position(node).h / 2
532 | // | },
533 | // | width:{
534 | // | start:function(node){ return 100; },
535 | // | end:function(node){ return 200; }
536 | // | }
537 | // | }
538 | // | }).play();
539 | //
540 |
541 | var n = args.node = dom.byId(args.node);
542 | if(!args.easing){ args.easing = _defaultEasing; }
543 |
544 | var anim = new Animation(args);
545 | d.connect(anim, "beforeBegin", anim, function(){
546 | var pm = {};
547 | for(var p in this.properties){
548 | // Make shallow copy of properties into pm because we overwrite
549 | // some values below. In particular if start/end are functions
550 | // we don't want to overwrite them or the functions won't be
551 | // called if the animation is reused.
552 | if(p == "width" || p == "height"){
553 | this.node.display = "block";
554 | }
555 | var prop = this.properties[p];
556 | if(lang.isFunction(prop)){
557 | prop = prop(n);
558 | }
559 | prop = pm[p] = lang.mixin({}, lang.isObject(prop) ? prop: {end: prop});
560 |
561 | if(lang.isFunction(prop.start)){
562 | prop.start = prop.start(n);
563 | }
564 | if(lang.isFunction(prop.end)){
565 | prop.end = prop.end(n);
566 | }
567 | var isColor = (p.toLowerCase().indexOf("color") >= 0);
568 | function getStyle(node, p){
569 | // dojo.style(node, "height") can return "auto" or "" on IE; this is more reliable:
570 | var v = { height: node.offsetHeight, width: node.offsetWidth }[p];
571 | if(v !== undefined){ return v; }
572 | v = dom.style(node, p);
573 | return (p == "opacity") ? +v : (isColor ? v : parseFloat(v));
574 | }
575 | if(!("end" in prop)){
576 | prop.end = getStyle(n, p);
577 | }else if(!("start" in prop)){
578 | prop.start = getStyle(n, p);
579 | }
580 |
581 | if(isColor){
582 | prop.start = new color.Color(prop.start);
583 | prop.end = new color.Color(prop.end);
584 | }else{
585 | prop.start = (p == "opacity") ? +prop.start : parseFloat(prop.start);
586 | }
587 | }
588 | this.curve = new PropLine(pm);
589 | });
590 | d.connect(anim, "onAnimate", lang.hitch(d, "style", anim.node));
591 | return anim; // dojo.Animation
592 | }
593 |
594 | function anim(/*DOMNode|String*/ node,
595 | /*Object*/ properties,
596 | /*Integer?*/ duration,
597 | /*Function?*/ easing,
598 | /*Function?*/ onEnd,
599 | /*Integer?*/ delay){
600 | // summary:
601 | // A simpler interface to `dojo.animateProperty()`, also returns
602 | // an instance of `dojo.Animation` but begins the animation
603 | // immediately, unlike nearly every other Dojo animation API.
604 | // description:
605 | // `dojo.anim` is a simpler (but somewhat less powerful) version
606 | // of `dojo.animateProperty`. It uses defaults for many basic properties
607 | // and allows for positional parameters to be used in place of the
608 | // packed "property bag" which is used for other Dojo animation
609 | // methods.
610 | //
611 | // The `dojo.Animation` object returned from `dojo.anim` will be
612 | // already playing when it is returned from this function, so
613 | // calling play() on it again is (usually) a no-op.
614 | // node:
615 | // a DOM node or the id of a node to animate CSS properties on
616 | // duration:
617 | // The number of milliseconds over which the animation
618 | // should run. Defaults to the global animation default duration
619 | // (350ms).
620 | // easing:
621 | // An easing function over which to calculate acceleration
622 | // and deceleration of the animation through its duration.
623 | // A default easing algorithm is provided, but you may
624 | // plug in any you wish. A large selection of easing algorithms
625 | // are available in `dojo.fx.easing`.
626 | // onEnd:
627 | // A function to be called when the animation finishes
628 | // running.
629 | // delay:
630 | // The number of milliseconds to delay beginning the
631 | // animation by. The default is 0.
632 | // example:
633 | // Fade out a node
634 | // | dojo.anim("id", { opacity: 0 });
635 | // example:
636 | // Fade out a node over a full second
637 | // | dojo.anim("id", { opacity: 0 }, 1000);
638 | return animateProperty({ // dojo.Animation
639 | node: node,
640 | duration: duration || Animation.prototype.duration,
641 | properties: properties,
642 | easing: easing,
643 | onEnd: onEnd
644 | }).play(delay || 0);
645 | }
646 |
647 | return {
648 | Animation: Animation,
649 | fadeIn: fadeIn,
650 | fadeOut: fadeOut,
651 | PropLine: PropLine,
652 | animateProperty: animateProperty,
653 | anim: anim
654 | };
655 | });
656 |
--------------------------------------------------------------------------------
/lib/declare.js:
--------------------------------------------------------------------------------
1 | define(["./lang"], function(lang){
2 | //TODO: do we include it in the base or provide as a standalone module?
3 | //TODO: the only external function needed is lang.mixin(), should we eliminate it?
4 |
5 | // I removed the legacy support and ensured that it can be used in the strict mode.
6 |
7 | var mix = lang.mixin, op = Object.prototype, opts = op.toString,
8 | xtor = new Function, counter = 0, cname = "constructor";
9 |
10 | function err(msg){ throw new Error("declare: " + msg); }
11 |
12 | // C3 Method Resolution Order (see http://www.python.org/download/releases/2.3/mro/)
13 | function c3mro(bases){
14 | var result = [], roots = [{cls: 0, refs: []}], nameMap = {}, clsCount = 1,
15 | l = bases.length, i = 0, j, lin, base, top, proto, rec, name, refs;
16 |
17 | // build a list of bases naming them if needed
18 | for(; i < l; ++i){
19 | base = bases[i];
20 | if(!base){
21 | err("mixin #" + i + " is unknown. Did you use dojo.require to pull it in?");
22 | }else if(opts.call(base) != "[object Function]"){
23 | err("mixin #" + i + " is not a callable constructor.");
24 | }
25 | lin = base._meta ? base._meta.bases : [base];
26 | top = 0;
27 | // add bases to the name map
28 | for(j = lin.length - 1; j >= 0; --j){
29 | proto = lin[j].prototype;
30 | if(!proto.hasOwnProperty("declaredClass")){
31 | proto.declaredClass = "uniqName_" + (counter++);
32 | }
33 | name = proto.declaredClass;
34 | if(!nameMap.hasOwnProperty(name)){
35 | nameMap[name] = {count: 0, refs: [], cls: lin[j]};
36 | ++clsCount;
37 | }
38 | rec = nameMap[name];
39 | if(top && top !== rec){
40 | rec.refs.push(top);
41 | ++top.count;
42 | }
43 | top = rec;
44 | }
45 | ++top.count;
46 | roots[0].refs.push(top);
47 | }
48 |
49 | // remove classes without external references recursively
50 | while(roots.length){
51 | top = roots.pop();
52 | result.push(top.cls);
53 | --clsCount;
54 | // optimization: follow a single-linked chain
55 | while(refs = top.refs, refs.length == 1){
56 | top = refs[0];
57 | if(!top || --top.count){
58 | // branch or end of chain => do not end to roots
59 | top = 0;
60 | break;
61 | }
62 | result.push(top.cls);
63 | --clsCount;
64 | }
65 | if(top){
66 | // branch
67 | for(i = 0, l = refs.length; i < l; ++i){
68 | top = refs[i];
69 | if(!--top.count){
70 | roots.push(top);
71 | }
72 | }
73 | }
74 | }
75 | if(clsCount){
76 | err("can't build consistent linearization");
77 | }
78 |
79 | // calculate the superclass offset
80 | base = bases[0];
81 | result[0] = base ?
82 | base._meta && base === result[result.length - base._meta.bases.length] ?
83 | base._meta.bases.length : 1 : 0;
84 |
85 | return result;
86 | }
87 |
88 | function inherited(args, a, f){
89 | var name, chains, bases, caller, meta, base, proto, opf, pos,
90 | cache = this._inherited = this._inherited || {};
91 |
92 | // crack arguments
93 | if(typeof args == "string"){
94 | name = args;
95 | args = a;
96 | a = f;
97 | }
98 | caller = opts.call(args) == "[object Function]" ? args : args.callee;
99 | f = 0;
100 |
101 | name = name || caller.nom;
102 | if(!name){
103 | err("can't deduce a name to call inherited()");
104 | }
105 |
106 | meta = this.constructor._meta;
107 | bases = meta.bases;
108 |
109 | pos = cache.p;
110 | if(name != cname){
111 | // method
112 | if(cache.c !== caller){
113 | // cache bust
114 | pos = 0;
115 | base = bases[0];
116 | meta = base._meta;
117 | if(meta.hidden[name] !== caller){
118 | // error detection
119 | chains = meta.chains;
120 | if(chains && typeof chains[name] == "string"){
121 | err("calling chained method with inherited: " + name);
122 | }
123 | // find caller
124 | do{
125 | meta = base._meta;
126 | proto = base.prototype;
127 | if(meta && (proto[name] === caller && proto.hasOwnProperty(name) || meta.hidden[name] === caller)){
128 | break;
129 | }
130 | }while(base = bases[++pos]); // intentional assignment
131 | pos = base ? pos : -1;
132 | }
133 | }
134 | // find next
135 | base = bases[++pos];
136 | if(base){
137 | proto = base.prototype;
138 | if(base._meta && proto.hasOwnProperty(name)){
139 | f = proto[name];
140 | }else{
141 | opf = op[name];
142 | do{
143 | proto = base.prototype;
144 | f = proto[name];
145 | if(f && (base._meta ? proto.hasOwnProperty(name) : f !== opf)){
146 | break;
147 | }
148 | }while(base = bases[++pos]); // intentional assignment
149 | }
150 | }
151 | f = base && f || op[name];
152 | }else{
153 | // constructor
154 | if(cache.c !== caller){
155 | // cache bust
156 | pos = 0;
157 | meta = bases[0]._meta;
158 | if(meta && meta.ctor !== caller){
159 | // error detection
160 | chains = meta.chains;
161 | if(!chains || chains.constructor !== "manual"){
162 | err("calling chained constructor with inherited");
163 | }
164 | // find caller
165 | while(base = bases[++pos]){ // intentional assignment
166 | meta = base._meta;
167 | if(meta && meta.ctor === caller){
168 | break;
169 | }
170 | }
171 | pos = base ? pos : -1;
172 | }
173 | }
174 | // find next
175 | while(base = bases[++pos]){ // intentional assignment
176 | meta = base._meta;
177 | f = meta ? meta.ctor : base;
178 | if(f){
179 | break;
180 | }
181 | }
182 | f = base && f;
183 | }
184 |
185 | // cache the found super method
186 | cache.c = f;
187 | cache.p = pos;
188 |
189 | // now we have the result
190 | if(f){
191 | return a === true ? f : f.apply(this, a || args);
192 | }
193 | // intentionally if a super method was not found
194 | }
195 |
196 | function getInherited(name, args){
197 | if(typeof name == "string"){
198 | return this.inherited(name, args, true);
199 | }
200 | return this.inherited(name, true);
201 | }
202 |
203 | // emulation of "instanceof"
204 | function isInstanceOf(cls){
205 | var bases = this.constructor._meta.bases;
206 | for(var i = 0, l = bases.length; i < l; ++i){
207 | if(bases[i] === cls){
208 | return true;
209 | }
210 | }
211 | return this instanceof cls;
212 | }
213 |
214 | function mixOwn(target, source){
215 | // add props adding metadata for incoming functions skipping a constructor
216 | for(var name in source){
217 | if(name != cname && source.hasOwnProperty(name)){
218 | target[name] = source[name];
219 | }
220 | }
221 | }
222 |
223 | // implementation of safe mixin function
224 | function safeMixin(target, source){
225 | var name, t;
226 | // add props adding metadata for incoming functions skipping a constructor
227 | for(name in source){
228 | t = source[name];
229 | if((t !== op[name] || !(name in op)) && name != cname){
230 | if(opts.call(t) == "[object Function]"){
231 | // non-trivial function method => attach its name
232 | t.nom = name;
233 | }
234 | target[name] = t;
235 | }
236 | }
237 | return target;
238 | }
239 |
240 | function extend(source){
241 | safeMixin(this.prototype, source);
242 | return this;
243 | }
244 |
245 | // chained constructor
246 | function chainedConstructor(bases){
247 | return function(){
248 | var f, m;
249 | if(!(this instanceof arguments.callee)){
250 | // not called via new, so force it
251 | return applyNew(arguments);
252 | }
253 | for(var i = bases.length - 1; i >= 0; --i){
254 | f = bases[i];
255 | m = f._meta;
256 | f = m ? m.ctor : f;
257 | if(f){
258 | f.apply(this, arguments);
259 | }
260 | }
261 | f = this.postscript;
262 | if(f){
263 | f.apply(this, arguments);
264 | }
265 | };
266 | }
267 |
268 | // single constructor
269 | function singleConstructor(ctor){
270 | return function(){
271 | if(!(this instanceof arguments.callee)){
272 | // not called via new, so force it
273 | return applyNew(arguments);
274 | }
275 | if(ctor){
276 | ctor.apply(this, arguments);
277 | }
278 | var f = this.postscript;
279 | if(f){
280 | f.apply(this, arguments);
281 | }
282 | };
283 | }
284 |
285 | // plain vanilla constructor (can use inherited() to call its base constructor)
286 | function simpleConstructor(bases){
287 | return function(){
288 | var i = 0, f, m;
289 | if(!(this instanceof arguments.callee)){
290 | // not called via new, so force it
291 | return applyNew(arguments);
292 | }
293 | for(; f = bases[i]; ++i){ // intentional assignment
294 | m = f._meta;
295 | f = m ? m.ctor : f;
296 | if(f){
297 | f.apply(this, arguments);
298 | break;
299 | }
300 | }
301 | f = this.postscript;
302 | if(f){
303 | f.apply(this, arguments);
304 | }
305 | };
306 | }
307 |
308 | function chain(name, bases, reversed){
309 | return function(){
310 | var b, m, f, i = 0, step = 1;
311 | if(reversed){
312 | i = bases.length - 1;
313 | step = -1;
314 | }
315 | for(; b = bases[i]; i += step){ // intentional assignment
316 | m = b._meta;
317 | f = (m ? m.hidden : b.prototype)[name];
318 | if(f){
319 | f.apply(this, arguments);
320 | }
321 | }
322 | };
323 | }
324 |
325 | // forceNew(ctor)
326 | // return a new object that inherits from ctor.prototype but
327 | // without actually running ctor on the object.
328 | function forceNew(ctor){
329 | // create object with correct prototype using a do-nothing
330 | // constructor
331 | xtor.prototype = ctor.prototype;
332 | var t = new xtor;
333 | xtor.prototype = null; // clean up
334 | return t;
335 | }
336 |
337 | // applyNew(args)
338 | // just like 'new ctor()' except that the constructor and its arguments come
339 | // from args, which must be an array or an arguments object
340 | function applyNew(args){
341 | // create an object with ctor's prototype but without
342 | // calling ctor on it.
343 | var ctor = args.callee, t = forceNew(ctor);
344 | // execute the real constructor on the new object
345 | ctor.apply(t, args);
346 | return t;
347 | }
348 |
349 | function declare(className, superclass, props){
350 | // crack parameters
351 | if(typeof className != "string"){
352 | props = superclass;
353 | superclass = className;
354 | className = "";
355 | }
356 | props = props || {};
357 |
358 | var proto, i, t, ctor, name, bases, chains, mixins = 1, parents = superclass;
359 |
360 | // build a prototype
361 | if(opts.call(superclass) == "[object Array]"){
362 | // C3 MRO
363 | bases = c3mro(superclass);
364 | t = bases[0];
365 | mixins = bases.length - t;
366 | superclass = bases[mixins];
367 | }else{
368 | bases = [0];
369 | if(superclass){
370 | if(opts.call(superclass) == "[object Function]"){
371 | t = superclass._meta;
372 | bases = bases.concat(t ? t.bases : superclass);
373 | }else{
374 | err("base class is not a callable constructor.");
375 | }
376 | }else if(superclass !== null){
377 | err("unknown base class. Did you use dojo.require to pull it in?")
378 | }
379 | }
380 | if(superclass){
381 | for(i = mixins - 1;; --i){
382 | proto = forceNew(superclass);
383 | if(!i){
384 | // stop if nothing to add (the last base)
385 | break;
386 | }
387 | // mix in properties
388 | t = bases[i];
389 | (t._meta ? mixOwn : mix)(proto, t.prototype);
390 | // chain in new constructor
391 | ctor = new Function;
392 | ctor.superclass = superclass;
393 | ctor.prototype = proto;
394 | superclass = proto.constructor = ctor;
395 | }
396 | }else{
397 | proto = {};
398 | }
399 | // add all properties
400 | safeMixin(proto, props);
401 | // add constructor
402 | t = props.constructor;
403 | if(t !== op.constructor){
404 | t.nom = cname;
405 | proto.constructor = t;
406 | }
407 |
408 | // collect chains and flags
409 | for(i = mixins - 1; i; --i){ // intentional assignment
410 | t = bases[i]._meta;
411 | if(t && t.chains){
412 | chains = mix(chains || {}, t.chains);
413 | }
414 | }
415 | if(proto["-chains-"]){
416 | chains = mix(chains || {}, proto["-chains-"]);
417 | }
418 |
419 | // build ctor
420 | bases[0] = ctor = (chains && chains.constructor === "manual") ? simpleConstructor(bases) :
421 | (bases.length == 1 ? singleConstructor(props.constructor) : chainedConstructor(bases));
422 |
423 | // add meta information to the constructor
424 | ctor._meta = {bases: bases, hidden: props, chains: chains,
425 | parents: parents, ctor: props.constructor};
426 | ctor.superclass = superclass && superclass.prototype;
427 | ctor.extend = extend;
428 | ctor.prototype = proto;
429 | proto.constructor = ctor;
430 |
431 | // add "standard" methods to the prototype
432 | proto.getInherited = getInherited;
433 | proto.inherited = inherited;
434 | proto.isInstanceOf = isInstanceOf;
435 |
436 | // add name if specified
437 | proto.declaredClass = className || "";
438 |
439 | // build chains and add them to the prototype
440 | if(chains){
441 | for(name in chains){
442 | if(proto[name] && typeof chains[name] == "string" && name != cname){
443 | t = proto[name] = chain(name, bases, chains[name] === "after");
444 | t.nom = name;
445 | }
446 | }
447 | }
448 | // chained methods do not return values
449 | // no need to chain "invisible" functions
450 |
451 | return ctor; // Function
452 | }
453 |
454 | /*=====
455 | dojo.declare = function(className, superclass, props){
456 | // summary:
457 | // Create a feature-rich constructor from compact notation.
458 | // className: String?:
459 | // The optional name of the constructor (loosely, a "class")
460 | // stored in the "declaredClass" property in the created prototype.
461 | // It will be used as a global name for a created constructor.
462 | // superclass: Function|Function[]:
463 | // May be null, a Function, or an Array of Functions. This argument
464 | // specifies a list of bases (the left-most one is the most deepest
465 | // base).
466 | // props: Object:
467 | // An object whose properties are copied to the created prototype.
468 | // Add an instance-initialization function by making it a property
469 | // named "constructor".
470 | // returns:
471 | // New constructor function.
472 | // description:
473 | // Create a constructor using a compact notation for inheritance and
474 | // prototype extension.
475 | //
476 | // Mixin ancestors provide a type of multiple inheritance.
477 | // Prototypes of mixin ancestors are copied to the new class:
478 | // changes to mixin prototypes will not affect classes to which
479 | // they have been mixed in.
480 | //
481 | // Ancestors can be compound classes created by this version of
482 | // dojo.declare. In complex cases all base classes are going to be
483 | // linearized according to C3 MRO algorithm
484 | // (see http://www.python.org/download/releases/2.3/mro/ for more
485 | // details).
486 | //
487 | // "className" is cached in "declaredClass" property of the new class,
488 | // if it was supplied. The immediate super class will be cached in
489 | // "superclass" property of the new class.
490 | //
491 | // Methods in "props" will be copied and modified: "nom" property
492 | // (the declared name of the method) will be added to all copied
493 | // functions to help identify them for the internal machinery. Be
494 | // very careful, while reusing methods: if you use the same
495 | // function under different names, it can produce errors in some
496 | // cases.
497 | //
498 | // It is possible to use constructors created "manually" (without
499 | // dojo.declare) as bases. They will be called as usual during the
500 | // creation of an instance, their methods will be chained, and even
501 | // called by "this.inherited()".
502 | //
503 | // Special property "-chains-" governs how to chain methods. It is
504 | // a dictionary, which uses method names as keys, and hint strings
505 | // as values. If a hint string is "after", this method will be
506 | // called after methods of its base classes. If a hint string is
507 | // "before", this method will be called before methods of its base
508 | // classes.
509 | //
510 | // If "constructor" is not mentioned in "-chains-" property, it will
511 | // be chained using the legacy mode: using "after" chaining,
512 | // calling preamble() method before each constructor, if available,
513 | // and calling postscript() after all constructors were executed.
514 | // If the hint is "after", it is chained as a regular method, but
515 | // postscript() will be called after the chain of constructors.
516 | // "constructor" cannot be chained "before", but it allows
517 | // a special hint string: "manual", which means that constructors
518 | // are not going to be chained in any way, and programmer will call
519 | // them manually using this.inherited(). In the latter case
520 | // postscript() will be called after the construction.
521 | //
522 | // All chaining hints are "inherited" from base classes and
523 | // potentially can be overridden. Be very careful when overriding
524 | // hints! Make sure that all chained methods can work in a proposed
525 | // manner of chaining.
526 | //
527 | // Once a method was chained, it is impossible to unchain it. The
528 | // only exception is "constructor". You don't need to define a
529 | // method in order to supply a chaining hint.
530 | //
531 | // If a method is chained, it cannot use this.inherited() because
532 | // all other methods in the hierarchy will be called automatically.
533 | //
534 | // Usually constructors and initializers of any kind are chained
535 | // using "after" and destructors of any kind are chained as
536 | // "before". Note that chaining assumes that chained methods do not
537 | // return any value: any returned value will be discarded.
538 | //
539 | // example:
540 | // | dojo.declare("my.classes.bar", my.classes.foo, {
541 | // | // properties to be added to the class prototype
542 | // | someValue: 2,
543 | // | // initialization function
544 | // | constructor: function(){
545 | // | this.myComplicatedObject = new ReallyComplicatedObject();
546 | // | },
547 | // | // other functions
548 | // | someMethod: function(){
549 | // | doStuff();
550 | // | }
551 | // | });
552 | //
553 | // example:
554 | // | var MyBase = dojo.declare(null, {
555 | // | // constructor, properties, and methods go here
556 | // | // ...
557 | // | });
558 | // | var MyClass1 = dojo.declare(MyBase, {
559 | // | // constructor, properties, and methods go here
560 | // | // ...
561 | // | });
562 | // | var MyClass2 = dojo.declare(MyBase, {
563 | // | // constructor, properties, and methods go here
564 | // | // ...
565 | // | });
566 | // | var MyDiamond = dojo.declare([MyClass1, MyClass2], {
567 | // | // constructor, properties, and methods go here
568 | // | // ...
569 | // | });
570 | //
571 | // example:
572 | // | var F = function(){ console.log("raw constructor"); };
573 | // | F.prototype.method = function(){
574 | // | console.log("raw method");
575 | // | };
576 | // | var A = dojo.declare(F, {
577 | // | constructor: function(){
578 | // | console.log("A.constructor");
579 | // | },
580 | // | method: function(){
581 | // | console.log("before calling F.method...");
582 | // | this.inherited(arguments);
583 | // | console.log("...back in A");
584 | // | }
585 | // | });
586 | // | new A().method();
587 | // | // will print:
588 | // | // raw constructor
589 | // | // A.constructor
590 | // | // before calling F.method...
591 | // | // raw method
592 | // | // ...back in A
593 | //
594 | // example:
595 | // | var A = dojo.declare(null, {
596 | // | "-chains-": {
597 | // | destroy: "before"
598 | // | }
599 | // | });
600 | // | var B = dojo.declare(A, {
601 | // | constructor: function(){
602 | // | console.log("B.constructor");
603 | // | },
604 | // | destroy: function(){
605 | // | console.log("B.destroy");
606 | // | }
607 | // | });
608 | // | var C = dojo.declare(B, {
609 | // | constructor: function(){
610 | // | console.log("C.constructor");
611 | // | },
612 | // | destroy: function(){
613 | // | console.log("C.destroy");
614 | // | }
615 | // | });
616 | // | new C().destroy();
617 | // | // prints:
618 | // | // B.constructor
619 | // | // C.constructor
620 | // | // C.destroy
621 | // | // B.destroy
622 | //
623 | // example:
624 | // | var A = dojo.declare(null, {
625 | // | "-chains-": {
626 | // | constructor: "manual"
627 | // | }
628 | // | });
629 | // | var B = dojo.declare(A, {
630 | // | constructor: function(){
631 | // | // ...
632 | // | // call the base constructor with new parameters
633 | // | this.inherited(arguments, [1, 2, 3]);
634 | // | // ...
635 | // | }
636 | // | });
637 | //
638 | // example:
639 | // | var A = dojo.declare(null, {
640 | // | "-chains-": {
641 | // | m1: "before"
642 | // | },
643 | // | m1: function(){
644 | // | console.log("A.m1");
645 | // | },
646 | // | m2: function(){
647 | // | console.log("A.m2");
648 | // | }
649 | // | });
650 | // | var B = dojo.declare(A, {
651 | // | "-chains-": {
652 | // | m2: "after"
653 | // | },
654 | // | m1: function(){
655 | // | console.log("B.m1");
656 | // | },
657 | // | m2: function(){
658 | // | console.log("B.m2");
659 | // | }
660 | // | });
661 | // | var x = new B();
662 | // | x.m1();
663 | // | // prints:
664 | // | // B.m1
665 | // | // A.m1
666 | // | x.m2();
667 | // | // prints:
668 | // | // A.m2
669 | // | // B.m2
670 | return new Function(); // Function
671 | };
672 | =====*/
673 |
674 | /*=====
675 | dojo.safeMixin = function(target, source){
676 | // summary:
677 | // Mix in properties skipping a constructor and decorating functions
678 | // like it is done by dojo.declare.
679 | // target: Object
680 | // Target object to accept new properties.
681 | // source: Object
682 | // Source object for new properties.
683 | // description:
684 | // This function is used to mix in properties like dojo._mixin does,
685 | // but it skips a constructor property and decorates functions like
686 | // dojo.declare does.
687 | //
688 | // It is meant to be used with classes and objects produced with
689 | // dojo.declare. Functions mixed in with dojo.safeMixin can use
690 | // this.inherited() like normal methods.
691 | //
692 | // This function is used to implement extend() method of a constructor
693 | // produced with dojo.declare().
694 | //
695 | // example:
696 | // | var A = dojo.declare(null, {
697 | // | m1: function(){
698 | // | console.log("A.m1");
699 | // | },
700 | // | m2: function(){
701 | // | console.log("A.m2");
702 | // | }
703 | // | });
704 | // | var B = dojo.declare(A, {
705 | // | m1: function(){
706 | // | this.inherited(arguments);
707 | // | console.log("B.m1");
708 | // | }
709 | // | });
710 | // | B.extend({
711 | // | m2: function(){
712 | // | this.inherited(arguments);
713 | // | console.log("B.m2");
714 | // | }
715 | // | });
716 | // | var x = new B();
717 | // | dojo.safeMixin(x, {
718 | // | m1: function(){
719 | // | this.inherited(arguments);
720 | // | console.log("X.m1");
721 | // | },
722 | // | m2: function(){
723 | // | this.inherited(arguments);
724 | // | console.log("X.m2");
725 | // | }
726 | // | });
727 | // | x.m2();
728 | // | // prints:
729 | // | // A.m1
730 | // | // B.m1
731 | // | // X.m1
732 | };
733 | =====*/
734 |
735 | /*=====
736 | Object.inherited = function(name, args, newArgs){
737 | // summary:
738 | // Calls a super method.
739 | // name: String?
740 | // The optional method name. Should be the same as the caller's
741 | // name. Usually "name" is specified in complex dynamic cases, when
742 | // the calling method was dynamically added, undecorated by
743 | // dojo.declare, and it cannot be determined.
744 | // args: Arguments
745 | // The caller supply this argument, which should be the original
746 | // "arguments".
747 | // newArgs: Object?
748 | // If "true", the found function will be returned without
749 | // executing it.
750 | // If Array, it will be used to call a super method. Otherwise
751 | // "args" will be used.
752 | // returns:
753 | // Whatever is returned by a super method, or a super method itself,
754 | // if "true" was specified as newArgs.
755 | // description:
756 | // This method is used inside method of classes produced with
757 | // dojo.declare to call a super method (next in the chain). It is
758 | // used for manually controlled chaining. Consider using the regular
759 | // chaining, because it is faster. Use "this.inherited()" only in
760 | // complex cases.
761 | //
762 | // This method cannot me called from automatically chained
763 | // constructors including the case of a special (legacy)
764 | // constructor chaining. It cannot be called from chained methods.
765 | //
766 | // If "this.inherited()" cannot find the next-in-chain method, it
767 | // does nothing and returns "undefined". The last method in chain
768 | // can be a default method implemented in Object, which will be
769 | // called last.
770 | //
771 | // If "name" is specified, it is assumed that the method that
772 | // received "args" is the parent method for this call. It is looked
773 | // up in the chain list and if it is found the next-in-chain method
774 | // is called. If it is not found, the first-in-chain method is
775 | // called.
776 | //
777 | // If "name" is not specified, it will be derived from the calling
778 | // method (using a methoid property "nom").
779 | //
780 | // example:
781 | // | var B = dojo.declare(A, {
782 | // | method1: function(a, b, c){
783 | // | this.inherited(arguments);
784 | // | },
785 | // | method2: function(a, b){
786 | // | return this.inherited(arguments, [a + b]);
787 | // | }
788 | // | });
789 | // | // next method is not in the chain list because it is added
790 | // | // manually after the class was created.
791 | // | B.prototype.method3 = function(){
792 | // | console.log("This is a dynamically-added method.");
793 | // | this.inherited("method3", arguments);
794 | // | };
795 | // example:
796 | // | var B = dojo.declare(A, {
797 | // | method: function(a, b){
798 | // | var super = this.inherited(arguments, true);
799 | // | // ...
800 | // | if(!super){
801 | // | console.log("there is no super method");
802 | // | return 0;
803 | // | }
804 | // | return super.apply(this, arguments);
805 | // | }
806 | // | });
807 | return {}; // Object
808 | }
809 | =====*/
810 |
811 | /*=====
812 | Object.getInherited = function(name, args){
813 | // summary:
814 | // Returns a super method.
815 | // name: String?
816 | // The optional method name. Should be the same as the caller's
817 | // name. Usually "name" is specified in complex dynamic cases, when
818 | // the calling method was dynamically added, undecorated by
819 | // dojo.declare, and it cannot be determined.
820 | // args: Arguments
821 | // The caller supply this argument, which should be the original
822 | // "arguments".
823 | // returns:
824 | // Returns a super method (Function) or "undefined".
825 | // description:
826 | // This method is a convenience method for "this.inherited()".
827 | // It uses the same algorithm but instead of executing a super
828 | // method, it returns it, or "undefined" if not found.
829 | //
830 | // example:
831 | // | var B = dojo.declare(A, {
832 | // | method: function(a, b){
833 | // | var super = this.getInherited(arguments);
834 | // | // ...
835 | // | if(!super){
836 | // | console.log("there is no super method");
837 | // | return 0;
838 | // | }
839 | // | return super.apply(this, arguments);
840 | // | }
841 | // | });
842 | return {}; // Object
843 | }
844 | =====*/
845 |
846 | /*=====
847 | Object.isInstanceOf = function(cls){
848 | // summary:
849 | // Checks the inheritance chain to see if it is inherited from this
850 | // class.
851 | // cls: Function
852 | // Class constructor.
853 | // returns:
854 | // "true", if this object is inherited from this class, "false"
855 | // otherwise.
856 | // description:
857 | // This method is used with instances of classes produced with
858 | // dojo.declare to determine of they support a certain interface or
859 | // not. It models "instanceof" operator.
860 | //
861 | // example:
862 | // | var A = dojo.declare(null, {
863 | // | // constructor, properties, and methods go here
864 | // | // ...
865 | // | });
866 | // | var B = dojo.declare(null, {
867 | // | // constructor, properties, and methods go here
868 | // | // ...
869 | // | });
870 | // | var C = dojo.declare([A, B], {
871 | // | // constructor, properties, and methods go here
872 | // | // ...
873 | // | });
874 | // | var D = dojo.declare(A, {
875 | // | // constructor, properties, and methods go here
876 | // | // ...
877 | // | });
878 | // |
879 | // | var a = new A(), b = new B(), c = new C(), d = new D();
880 | // |
881 | // | console.log(a.isInstanceOf(A)); // true
882 | // | console.log(b.isInstanceOf(A)); // false
883 | // | console.log(c.isInstanceOf(A)); // true
884 | // | console.log(d.isInstanceOf(A)); // true
885 | // |
886 | // | console.log(a.isInstanceOf(B)); // false
887 | // | console.log(b.isInstanceOf(B)); // true
888 | // | console.log(c.isInstanceOf(B)); // true
889 | // | console.log(d.isInstanceOf(B)); // false
890 | // |
891 | // | console.log(a.isInstanceOf(C)); // false
892 | // | console.log(b.isInstanceOf(C)); // false
893 | // | console.log(c.isInstanceOf(C)); // true
894 | // | console.log(d.isInstanceOf(C)); // false
895 | // |
896 | // | console.log(a.isInstanceOf(D)); // false
897 | // | console.log(b.isInstanceOf(D)); // false
898 | // | console.log(c.isInstanceOf(D)); // false
899 | // | console.log(d.isInstanceOf(D)); // true
900 | return {}; // Object
901 | }
902 | =====*/
903 |
904 | /*=====
905 | Object.extend = function(source){
906 | // summary:
907 | // Adds all properties and methods of source to constructor's
908 | // prototype, making them available to all instances created with
909 | // constructor. This method is specific to constructors created with
910 | // dojo.declare.
911 | // source: Object
912 | // Source object which properties are going to be copied to the
913 | // constructor's prototype.
914 | // description:
915 | // Adds source properties to the constructor's prototype. It can
916 | // override existing properties.
917 | //
918 | // This method is similar to dojo.extend function, but it is specific
919 | // to constructors produced by dojo.declare. It is implemented
920 | // using dojo.safeMixin, and it skips a constructor property,
921 | // and properly decorates copied functions.
922 | //
923 | // example:
924 | // | var A = dojo.declare(null, {
925 | // | m1: function(){},
926 | // | s1: "Popokatepetl"
927 | // | });
928 | // | A.extend({
929 | // | m1: function(){},
930 | // | m2: function(){},
931 | // | f1: true,
932 | // | d1: 42
933 | // | });
934 | };
935 | =====*/
936 |
937 | return {
938 | declare: declare,
939 | safeMixin: safeMixin
940 | };
941 | });
942 |
--------------------------------------------------------------------------------