├── 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 | // |
76 | // | 77 | // | 78 | // | 79 | // | 84 | // |
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 | --------------------------------------------------------------------------------