diff --git a/.gitignore b/.gitignore index e28257efcb6..fd03e7bc0bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ build_tmp*/ build_rollup_tmp*/ +docs/ +node_modules/ tmp/ CVS/ .DS_Store .*.swp .svn *~ +src/build.log diff --git a/build/anim/anim-debug.js b/build/anim/anim-debug.js index d3168ed972f..fb3cc78565f 100644 --- a/build/anim/anim-debug.js +++ b/build/anim/anim-debug.js @@ -1204,5 +1204,5 @@ Y.Anim.behaviors.xy = { }, '@VERSION@' ,{requires:['anim-base', 'node-screen']}); -YUI.add('anim', function(Y){}, '@VERSION@' ,{use:['anim-base', 'anim-color', 'anim-curve', 'anim-easing', 'anim-node-plugin', 'anim-scroll', 'anim-xy'], skinnable:false}); +YUI.add('anim', function(Y){}, '@VERSION@' ,{skinnable:false, use:['anim-base', 'anim-color', 'anim-curve', 'anim-easing', 'anim-node-plugin', 'anim-scroll', 'anim-xy']}); diff --git a/build/anim/anim-min.js b/build/anim/anim-min.js index 5e5cbc7bd57..dbc28bb64a4 100644 --- a/build/anim/anim-min.js +++ b/build/anim/anim-min.js @@ -1,2 +1,2 @@ YUI.add("anim-base",function(b){var c="running",n="startTime",l="elapsedTime",j="start",i="tween",m="end",d="node",k="paused",o="reverse",h="iterationCount",a=Number;var f={},e;b.Anim=function(){b.Anim.superclass.constructor.apply(this,arguments);b.Anim._instances[b.stamp(this)]=this;};b.Anim.NAME="anim";b.Anim._instances={};b.Anim.RE_DEFAULT_UNIT=/^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i;b.Anim.DEFAULT_UNIT="px";b.Anim.DEFAULT_EASING=function(q,p,s,r){return s*q/r+p;};b.Anim._intervalTime=20;b.Anim.behaviors={left:{get:function(q,p){return q._getOffset(p);}}};b.Anim.behaviors.top=b.Anim.behaviors.left;b.Anim.DEFAULT_SETTER=function(s,t,v,w,y,r,u,x){var q=s._node,p=u(y,a(v),a(w)-a(v),r);if(t in q._node.style||t in b.DOM.CUSTOM_STYLES){x=x||"";q.setStyle(t,p+x);}else{if(q._node.attributes[t]){q.setAttribute(t,p);}else{q.set(t,p);}}};b.Anim.DEFAULT_GETTER=function(r,p){var q=r._node,s="";if(p in q._node.style||p in b.DOM.CUSTOM_STYLES){s=q.getComputedStyle(p);}else{if(q._node.attributes[p]){s=q.getAttribute(p);}else{s=q.get(p);}}return s;};b.Anim.ATTRS={node:{setter:function(p){p=b.one(p);this._node=p;if(!p){}return p;}},duration:{value:1},easing:{value:b.Anim.DEFAULT_EASING,setter:function(p){if(typeof p==="string"&&b.Easing){return b.Easing[p];}}},from:{},to:{},startTime:{value:0,readOnly:true},elapsedTime:{value:0,readOnly:true},running:{getter:function(){return !!f[b.stamp(this)];},value:false,readOnly:true},iterations:{value:1},iterationCount:{value:0,readOnly:true},direction:{value:"normal"},paused:{readOnly:true,value:false},reverse:{value:false}};b.Anim.run=function(){var q=b.Anim._instances;for(var p in q){if(q[p].run){q[p].run();}}};b.Anim.pause=function(){for(var p in f){if(f[p].pause){f[p].pause();}}b.Anim._stopTimer();};b.Anim.stop=function(){for(var p in f){if(f[p].stop){f[p].stop();}}b.Anim._stopTimer();};b.Anim._startTimer=function(){if(!e){e=setInterval(b.Anim._runFrame,b.Anim._intervalTime);}};b.Anim._stopTimer=function(){clearInterval(e);e=0;};b.Anim._runFrame=function(){var p=true;for(var q in f){if(f[q]._runFrame){p=false;f[q]._runFrame();}}if(p){b.Anim._stopTimer();}};b.Anim.RE_UNITS=/^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;var g={run:function(){if(this.get(k)){this._resume();}else{if(!this.get(c)){this._start();}}return this;},pause:function(){if(this.get(c)){this._pause();}return this;},stop:function(p){if(this.get(c)||this.get(k)){this._end(p);}return this;},_added:false,_start:function(){this._set(n,new Date()-this.get(l));this._actualFrames=0;if(!this.get(k)){this._initAnimAttr();}f[b.stamp(this)]=this;b.Anim._startTimer();this.fire(j);},_pause:function(){this._set(n,null);this._set(k,true);delete f[b.stamp(this)];this.fire("pause");},_resume:function(){this._set(k,false);f[b.stamp(this)]=this;this._set(n,new Date()-this.get(l));b.Anim._startTimer();this.fire("resume");},_end:function(p){var q=this.get("duration")*1000;if(p){this._runAttrs(q,q,this.get(o));}this._set(n,null);this._set(l,0);this._set(k,false);delete f[b.stamp(this)];this.fire(m,{elapsed:this.get(l)});},_runFrame:function(){var u=this._runtimeAttr.duration,r=new Date()-this.get(n),q=this.get(o),p=(r>=u),s,v;this._runAttrs(r,u,q);this._actualFrames+=1;this._set(l,r);this.fire(i);if(p){this._lastFrame();}},_runAttrs:function(A,z,w){var x=this._runtimeAttr,r=b.Anim.behaviors,y=x.easing,p=z,u=false,q,s,v;if(A>=z){u=true;}if(w){A=z-A;p=0;}for(v in x){if(x[v].to){q=x[v];s=(v in r&&"set" in r[v])?r[v].set:b.Anim.DEFAULT_SETTER;if(!u){s(this,v,q.from,q.to,A,z,y,q.unit);}else{s(this,v,q.from,q.to,p,z,y,q.unit);}}}},_lastFrame:function(){var p=this.get("iterations"),q=this.get(h);q+=1;if(p==="infinite"||q= 3), - location = Y.config.win.location; + win = Y.config.win, + location = win.location, + + /** + Fired when the controller is ready to begin dispatching to route handlers. + + You shouldn't need to wait for this event unless you plan to implement some + kind of custom dispatching logic. It's used internally in order to avoid + dispatching to an initial route if a browser history change occurs first. + + @event ready + @param {Boolean} dispatched `true` if routes have already been dispatched + (most likely due to a history change). + @fireOnce + **/ + EVT_READY = 'ready'; function Controller() { Controller.superclass.constructor.apply(this, arguments); @@ -40,23 +57,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Public Properties ---------------------------------------------------- /** - Base path or URL from which all routes should be evaluated. + If `true`, the controller will dispatch to the first route handler that + matches the current URL immediately after the controller is initialized, + even if there was no browser history change to trigger a dispatch. + + If you're rendering the initial pageview on the server, then you'll probably + want this to be `false`, but if you're doing all your rendering and route + handling entirely on the client, then setting this to `true` will allow your + client-side routes to handle the initial request of all pageviews without + depending on any server-side handling. + + This property defaults to `false` for HTML5 browsers, `true` for browsers + that rely on hash-based history (since the hash is never sent to the + server). + + @property dispatchOnInit + @type Boolean + @default `false` for HTML5 browsers, `true` for hash-based browsers + **/ + dispatchOnInit: !html5, + + /** + Root path from which all routes should be evaluated. For example, if your controller is running on a page at `http://example.com/myapp/` and you add a route with the path `/`, your route will never execute, because the path will always be preceded by - `/myapp`. Setting _base_ to `/myapp` would cause all routes to be evaluated - relative to that base path, so the `/` route would then execute. + `/myapp`. Setting `root` to `/myapp` would cause all routes to be evaluated + relative to that root URL, so the `/` route would then execute when the + user browses to `http://example.com/myapp/`. This property may be overridden in a subclass, set after instantiation, or passed as a config attribute when instantiating a `Y.Controller`-based class. - @property base + @property root @type String @default `''` **/ - base : '', + root: '', /** Array of route objects specifying routes to be created at instantiation @@ -85,6 +124,34 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Protected Properties ------------------------------------------------- + /** + Whether or not `_dispatch()` has been called since this controller was + instantiated. + + @property _dispatched + @type Boolean + @default undefined + @protected + **/ + + /** + Whether or not this browser is capable of using HTML5 history. + + @property _html5 + @type Boolean + @protected + **/ + _html5: html5, + + /** + Whether or not the `ready` event has fired yet. + + @property _ready + @type Boolean + @default undefined + @protected + **/ + /** Regex used to match parameter placeholders in route paths. @@ -109,31 +176,58 @@ Y.Controller = Y.extend(Controller, Y.Base, { @type RegExp @protected **/ - _regexUrlQuery : /\?([^#]*).*$/, + _regexUrlQuery: /\?([^#]*).*$/, // -- Lifecycle Methods ---------------------------------------------------- initializer: function (config) { + var self = this; + + // Set config properties. config || (config = {}); - this._routes = []; + config.routes && (self.routes = config.routes); + + Lang.isValue(config.root) && (self.root = config.root); + Lang.isValue(config.dispatchOnInit) && + (self.dispatchOnInit = config.dispatchOnInit); + + // Create routes. + self._routes = []; - config.base && (this.base = config.base); - config.routes && (this.routes = config.routes); + YArray.each(self.routes, function (route) { + self.route(route.path, route.callback, true); + }); - YArray.each(this.routes, function (route) { - this.route(route.path, route.callback, true); - }, this); + // Set up a history instance or hashchange listener. + if (html5) { + self._history = new Y.HistoryHTML5({force: true}); + self._history.after('change', self._afterHistoryChange, self); + } else { + Y.on('hashchange', self._afterHistoryChange, win, self); + } - // Set up a history instance. - this._history = html5 ? new Y.HistoryHTML5() : new Y.HistoryHash(); - this._history.after('change', this._afterHistoryChange, this); + // Fire a 'ready' event once we're ready to route. We wait first for all + // subclass initializers to finish, and then an additional 20ms to allow + // the browser to fire an initial `popstate` event if it wants to. + self.publish(EVT_READY, { + defaultFn : self._defReadyFn, + fireOnce : true, + preventable: false + }); - // Handle the initial route. - this._dispatch(this._getPath(), this._getState()); + self.once('initializedChange', function () { + setTimeout(function () { + self.fire(EVT_READY, {dispatched: !!self._dispatched}); + }, 20); + }); }, destructor: function () { - this._history.detachAll(); + if (html5) { + this._history.detachAll(); + } else { + Y.detach('hashchange', this._afterHistoryChange, win); + } }, // -- Public Methods ------------------------------------------------------- @@ -177,7 +271,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -185,25 +279,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.replace('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.replace('/path/'); + // New URL: http://example.com/path/ - controller.replace('/', 'You are now at example.com'); + controller.replace('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar + + controller.replace('/'); // New URL: http://example.com/ @method replace - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see save() **/ - replace: function (url, title, state) { - return this._save(url, title, state, true); + replace: function (url) { + return this._save(url, true); }, /** @@ -258,8 +351,6 @@ Y.Controller = Y.extend(Controller, Y.Base, { @param {Object} callback.req.query Query hash representing the URL query string, if any. Parameter names are keys, and are mapped to parameter values. - @param {Object} callback.req.state State object associated with this - URL, if any. @param {Function} callback.next Callback to pass control to the next matching route. If you don't call this function, then no further route handlers will be executed, even if there are more that match. If you do @@ -288,7 +379,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL and create a history entry. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -296,25 +387,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.save('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.save('/path/'); + // New URL: http://example.com/path/ + + controller.save('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar - controller.save('/', 'You are now at example.com'); + controller.save('/'); // New URL: http://example.com/ @method save - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see replace() **/ - save: function (url, title, state) { - return this._save(url, title, state); + save: function (url) { + return this._save(url); }, // -- Protected Methods ---------------------------------------------------- @@ -335,24 +425,29 @@ Y.Controller = Y.extend(Controller, Y.Base, { /** Dispatches to the first route handler that matches the specified _path_. + If called before the `ready` event has fired, the dispatch will be aborted. + This ensures normalized behavior between Chrome (which fires a `popstate` + event on every pageview) and other browsers (which do not). + @method _dispatch @param {String} path URL path. - @param {Object} state State to pass to route handlers. @protected **/ - _dispatch: function (path, state) { - var routes = this.match(path), - req, route, self; + _dispatch: function (path) { + var self = this, + routes = self.match(path), + req; + + self._dispatched = true; if (!routes || !routes.length) { return; } - req = this._getRequest(path, state); - self = this; + req = self._getRequest(path); function next(err) { - var callback, matches; + var callback, matches, route; if (err) { Y.error(err); @@ -380,6 +475,18 @@ Y.Controller = Y.extend(Controller, Y.Base, { next(); }, + /** + Gets the current path from the location hash, or an empty string if the + hash is empty. + + @method _getHashPath + @return {String} Current hash path, or an empty string if the hash is empty. + @protected + **/ + _getHashPath: function () { + return HistoryHash.getHash().replace(this._regexUrlQuery, ''); + }, + /** Gets the current route path. @@ -388,16 +495,9 @@ Y.Controller = Y.extend(Controller, Y.Base, { @protected **/ _getPath: html5 ? function () { - var base = this.base, - path = location.pathname; - - if (base && path.indexOf(base) === 0) { - path = path.substring(base.length); - } - - return path; + return this._removeRoot(location.pathname); } : function () { - return this._history.get('path') || this.base + location.pathname; + return this._getHashPath() || this._removeRoot(location.pathname); }, /** @@ -410,12 +510,15 @@ Y.Controller = Y.extend(Controller, Y.Base, { _getQuery: html5 ? function () { return location.search.substring(1); } : function () { - return this._history.get('query') || location.search.substring(1); + var hash = HistoryHash.getHash(), + matches = hash.match(this._regexUrlQuery); + + return hash && matches ? matches[1] : location.search.substring(1); }, /** - Creates a regular expression from the specified route specification. If - _path_ is already a regex, it will be returned unmodified. + Creates a regular expression from the given route specification. If _path_ + is already a regex, it will be returned unmodified. @method _getRegex @param {String|RegExp} path Route path specification. @@ -442,30 +545,44 @@ Y.Controller = Y.extend(Controller, Y.Base, { @method _getRequest @param {String} path Current path being dispatched. - @param {Object} state Current state. @return {Object} Request object. @protected **/ - _getRequest: function (path, state) { + _getRequest: function (path) { return { path : path, - query: this._parseQuery(this._getQuery()), - state: state + query: this._parseQuery(this._getQuery()) }; }, /** - Gets the current state, if any. + Joins the `root` URL to the specified _url_, normalizing leading/trailing + `/` characters. + + @example + controller.root = '/foo' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + controller.root = '/foo/' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' - @method _getState - @return {Object} Current state. + @method _joinURL + @param {String} url URL to append to the `root` URL. + @return {String} Joined URL. @protected **/ - _getState: html5 ? function () { - return this._history.get(); - } : function () { - var jsonState = this._history.get('state'); - return jsonState ? Y.JSON.parse(jsonState) : {}; + _joinURL: function (url) { + var root = this.root; + + if (url.charAt(0) === '/') { + url = url.substring(1); + } + + return root && root.charAt(root.length - 1) === '/' ? + root + url : + root + '/' + url; }, /** @@ -496,58 +613,59 @@ Y.Controller = Y.extend(Controller, Y.Base, { return result; }, + /** + Removes the `root` URL from the from of _path_ (if it's there) and returns + the result. The returned path will always have a leading `/`. + + @method _removeRoot + @param {String} path URL path. + @return {String} Rootless path. + @protected + **/ + _removeRoot: function (path) { + var root = this.root; + + if (root && path.indexOf(root) === 0) { + path = path.substring(root.length); + } + + return path.charAt(0) === '/' ? path : '/' + path; + }, + /** Saves a history entry using either `pushState()` or the location hash. @method _save @param {String} [url] URL for the history entry. - @param {String} [title] Page title associated with the history entry. - @param {Object} [state] State object associated with the history entry. @param {Boolean} [replace=false] If `true`, the current history entry will be replaced instead of a new one being added. @chainable @protected **/ - _save: function (url, title, state, replace) { - var jsonState, query; + _save: html5 ? function (url, replace) { + // Force _ready to true to ensure that the history change is handled + // even if _save is called before the `ready` event fires. + this._ready = true; - if (html5) { - if (typeof url === 'string') { - url = this.base + url; - } - } else { - // If we're not using HTML5 history, take over the history state for - // our own purposes and shove the implementer's state inside it as a - // JSON string. - jsonState = state && Y.JSON.stringify(state); - - // Extract a query string from the URL if there is one, then remove - // both the query and the hash portions of the URL so we can store - // just the path. - url = url.replace(this._regexUrlQuery, function (match, params) { - query = params; - return ''; - }); - - state = {path: url || this._getPath()}; + this._history[replace ? 'replace' : 'add'](null, { + url: typeof url === 'string' ? this._joinURL(url) : url + }); + return this; + } : function (url, replace) { + this._ready = true; - query && (state.query = query); - jsonState && (state.state = jsonState); + if (typeof url === 'string' && url.charAt(0) !== '/') { + url = '/' + url; } - this._history[replace ? 'replace' : 'add'](state || {}, { - merge: false, - title: title, - url : url - }); - + HistoryHash[replace ? 'replaceHash' : 'setHash'](url); return this; }, // -- Protected Event Handlers --------------------------------------------- /** - Handles `history:change` events. + Handles `history:change` and `hashchange` events. @method _afterHistoryChange @param {EventFacade} e @@ -556,18 +674,48 @@ Y.Controller = Y.extend(Controller, Y.Base, { _afterHistoryChange: function (e) { var self = this; - // We need to yield control to the UI thread to allow the browser to - // update document.location before we dispatch. - setTimeout(function () { - self._dispatch(self._getPath(), self._getState()); - }, 1); + if (self._ready) { + // We need to yield control to the UI thread to allow the browser to + // update window.location before we dispatch. + setTimeout(function () { + self._dispatch(self._getPath()); + }, 1); + } + }, + + // -- Default Event Handlers ----------------------------------------------- + + /** + Default handler for the `ready` event. + + @method _defReadyFn + @param {EventFacade} e + @protected + **/ + _defReadyFn: function (e) { + var hash; + + this._ready = true; + + if (this.dispatchOnInit && !this._dispatched) { + if (html5 && (hash = this._getHashPath()) + && hash.charAt(0) === '/') { + + // This is an HTML5 browser and we have a hash-based path in the + // URL, so we need to upgrade the URL to a non-hash URL. This + // will trigger a `history:change` event. + this._history.replace(null, {url: this._joinURL(hash)}); + } else { + this._dispatch(this._getPath()); + } + } } }, { NAME: 'controller' }); -}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history', 'json']}); +}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'history'], optional:['querystring-parse']}); YUI.add('model', function(Y) { /** diff --git a/build/app/app-min.js b/build/app/app-min.js index cb7bd5a18fc..af1547fe90e 100644 --- a/build/app/app-min.js +++ b/build/app/app-min.js @@ -1,2 +1,3 @@ -YUI.add("controller",function(f){var d=f.Array,b=f.QueryString,c=f.HistoryBase.html5&&(!f.UA.android||f.UA.android>=3),a=f.config.win.location;function e(){e.superclass.constructor.apply(this,arguments);}f.Controller=f.extend(e,f.Base,{base:"",routes:[],_regexPathParam:/([:*])([\w\d-]+)/g,_regexUrlQuery:/\?([^#]*).*$/,initializer:function(g){g||(g={});this._routes=[];g.base&&(this.base=g.base);g.routes&&(this.routes=g.routes);d.each(this.routes,function(h){this.route(h.path,h.callback,true);},this);this._history=c?new f.HistoryHTML5():new f.HistoryHash();this._history.after("change",this._afterHistoryChange,this);this._dispatch(this._getPath(),this._getState());},destructor:function(){this._history.detachAll();},match:function(g){return d.filter(this._routes,function(h){return g.search(h.regex)>-1;});},replace:function(g,i,h){return this._save(g,i,h,true);},route:function(h,i){var g=[];this._routes.push({callback:i,keys:g,path:h,regex:this._getRegex(h,g)});return this;},save:function(g,i,h){return this._save(g,i,h);},_decode:function(g){return decodeURIComponent(g.replace(/\+/g," "));},_dispatch:function(m,l){var g=this.match(m),k,i,h;if(!g||!g.length){return;}k=this._getRequest(m,l);h=this;function j(n){var p,o;if(n){f.error(n);}else{if((i=g.shift())){o=i.regex.exec(m);p=typeof i.callback==="string"?h[i.callback]:i.callback;if(o.length===i.keys.length+1){k.params=d.hash(i.keys,o.slice(1));}else{k.params={};d.each(o,function(r,q){k.params[q]=r;});}p.call(h,k,j);}}}j();},_getPath:c?function(){var g=this.base,h=a.pathname;if(g&&h.indexOf(g)===0){h=h.substring(g.length);}return h;}:function(){return this._history.get("path")||this.base+a.pathname;},_getQuery:c?function(){return a.search.substring(1);}:function(){return this._history.get("query")||a.search.substring(1);},_getRegex:function(h,g){if(h instanceof RegExp){return h;}h=h.replace(this._regexPathParam,function(j,i,k){g.push(k);return i==="*"?"(.*?)":"([^/]*)";});return new RegExp("^"+h+"$");},_getRequest:function(h,g){return{path:h,query:this._parseQuery(this._getQuery()),state:g};},_getState:c?function(){return this._history.get();}:function(){var g=this._history.get("state");return g?f.JSON.parse(g):{};},_parseQuery:b&&b.parse?b.parse:function(k){var l=this._decode,n=k.split("&"),j=0,h=n.length,g={},m;for(;jn?1:0);});j=g.merge(i,{models:k,src:"sort"});i.silent?this._defRefreshFn(j):this.fire(f,j);return this;},sync:function(){var h=d(arguments,0,true).pop();if(typeof h==="function"){h();}},toArray:function(){return this._items.concat();},toJSON:function(){return this.map(function(h){return h.toJSON();});},_add:function(i,h){var j;h||(h={});if(!(i instanceof g.Model)){i=new this.model(i);}if(this._clientIdMap[i.get("clientId")]){g.error("Model already in list.");return;}j=g.merge(h,{index:this._findIndex(i),model:i});h.silent?this._defAddFn(j):this.fire(c,j);return i;},_attachList:function(h){h.lists.push(this);h.addTarget(this);},_clear:function(){d.each(this._items,this._detachList,this);this._clientIdMap={};this._idMap={};this._items=[];},_detachList:function(i){var h=d.indexOf(i.lists,this);if(h>-1){i.lists.splice(h,1);i.removeTarget(this);}},_findIndex:function(l){var i=this.comparator,j=this._items,h=j.length-1,m=0,n,k,o;if(!i||!j.length){return j.length;}o=i(l);while(m",events:{},template:"",initializer:function(c){c||(c={});this.container=this.create(c.container||this.container);c.model&&(this.model=c.model);c.template&&(this.template=c.template);this.events=c.events?b.merge(this.events,c.events):this.events;this.attachEvents(this.events);},destructor:function(){this.container&&this.container.remove(true);},attachEvents:function(g){var d=this.container,i=b.Object.owns,h,e,f,c;for(c in g){if(!i(g,c)){continue;}e=g[c];for(f in e){if(!i(e,f)){continue;}h=e[f];if(typeof h==="string"){h=this[h];}d.delegate(f,h,c,this);}}},create:function(c){return typeof c==="string"?b.Node.create(c):b.one(c);},remove:function(){this.container&&this.container.remove();return this;},render:function(){return this;}},{NAME:"view"});},"@VERSION@",{requires:["base-build","node-event-delegate"]});YUI.add("app",function(a){},"@VERSION@",{use:["controller","model","model-list","view"]}); \ No newline at end of file +YUI.add("controller",function(a){var h=a.HistoryHash,e=a.Lang,b=a.QueryString,i=a.Array,f=a.HistoryBase.html5&&(!a.UA.android||a.UA.android>=3),g=a.config.win,j=g.location,d="ready";function c(){c.superclass.constructor.apply(this,arguments);}a.Controller=a.extend(c,a.Base,{dispatchOnInit:!f,root:"",routes:[],_html5:f,_regexPathParam:/([:*])([\w\d-]+)/g,_regexUrlQuery:/\?([^#]*).*$/,initializer:function(l){var k=this;l||(l={});l.routes&&(k.routes=l.routes);e.isValue(l.root)&&(k.root=l.root);e.isValue(l.dispatchOnInit)&&(k.dispatchOnInit=l.dispatchOnInit);k._routes=[];i.each(k.routes,function(m){k.route(m.path,m.callback,true);});if(f){k._history=new a.HistoryHTML5({force:true});k._history.after("change",k._afterHistoryChange,k);}else{a.on("hashchange",k._afterHistoryChange,g,k);}k.publish(d,{defaultFn:k._defReadyFn,fireOnce:true,preventable:false});k.once("initializedChange",function(){setTimeout(function(){k.fire(d,{dispatched:!!k._dispatched});},20);});},destructor:function(){if(f){this._history.detachAll();}else{a.detach("hashchange",this._afterHistoryChange,g);}},match:function(k){return i.filter(this._routes,function(l){return k.search(l.regex)>-1;});},replace:function(k){return this._save(k,true);},route:function(l,m){var k=[];this._routes.push({callback:m,keys:k,path:l,regex:this._getRegex(l,k)});return this;},save:function(k){return this._save(k);},_decode:function(k){return decodeURIComponent(k.replace(/\+/g," "));},_dispatch:function(o){var l=this,k=l.match(o),n;l._dispatched=true;if(!k||!k.length){return;}n=l._getRequest(o);function m(q){var s,r,p;if(q){a.error(q);}else{if((p=k.shift())){r=p.regex.exec(o);s=typeof p.callback==="string"?l[p.callback]:p.callback;if(r.length===p.keys.length+1){n.params=i.hash(p.keys,r.slice(1));}else{n.params={};i.each(r,function(u,t){n.params[t]=u;});}s.call(l,n,m);}}}m();},_getHashPath:function(){return h.getHash().replace(this._regexUrlQuery,"");},_getPath:f?function(){return this._removeRoot(j.pathname);}:function(){return this._getHashPath()||this._removeRoot(j.pathname);},_getQuery:f?function(){return j.search.substring(1);}:function(){var l=h.getHash(),k=l.match(this._regexUrlQuery);return l&&k?k[1]:j.search.substring(1);},_getRegex:function(l,k){if(l instanceof RegExp){return l;}l=l.replace(this._regexPathParam,function(n,m,o){k.push(o);return m==="*"?"(.*?)":"([^/]*)";});return new RegExp("^"+l+"$");},_getRequest:function(k){return{path:k,query:this._parseQuery(this._getQuery())};},_joinURL:function(l){var k=this.root;if(l.charAt(0)==="/"){l=l.substring(1);}return k&&k.charAt(k.length-1)==="/"?k+l:k+"/"+l;},_parseQuery:b&&b.parse?b.parse:function(n){var o=this._decode,q=n.split("&"),m=0,l=q.length,k={},p;for(;mn?1:0);});j=g.merge(i,{models:k,src:"sort"});i.silent?this._defRefreshFn(j):this.fire(f,j);return this;},sync:function(){var h=d(arguments,0,true).pop();if(typeof h==="function"){h();}},toArray:function(){return this._items.concat();},toJSON:function(){return this.map(function(h){return h.toJSON();});},_add:function(i,h){var j;h||(h={});if(!(i instanceof g.Model)){i=new this.model(i);}if(this._clientIdMap[i.get("clientId")]){g.error("Model already in list.");return;}j=g.merge(h,{index:this._findIndex(i),model:i});h.silent?this._defAddFn(j):this.fire(c,j);return i;},_attachList:function(h){h.lists.push(this);h.addTarget(this);},_clear:function(){d.each(this._items,this._detachList,this);this._clientIdMap={};this._idMap={};this._items=[];},_detachList:function(i){var h=d.indexOf(i.lists,this);if(h>-1){i.lists.splice(h,1);i.removeTarget(this);}},_findIndex:function(l){var i=this.comparator,j=this._items,h=j.length-1,m=0,n,k,o;if(!i||!j.length){return j.length;}o=i(l);while(m",events:{},template:"",initializer:function(c){c||(c={});this.container=this.create(c.container||this.container);c.model&&(this.model=c.model);c.template&&(this.template=c.template);this.events=c.events?b.merge(this.events,c.events):this.events;this.attachEvents(this.events);},destructor:function(){this.container&&this.container.remove(true);},attachEvents:function(g){var d=this.container,i=b.Object.owns,h,e,f,c;for(c in g){if(!i(g,c)){continue;}e=g[c];for(f in e){if(!i(e,f)){continue;}h=e[f];if(typeof h==="string"){h=this[h];}d.delegate(f,h,c,this);}}},create:function(c){return typeof c==="string"?b.Node.create(c):b.one(c);},remove:function(){this.container&&this.container.remove(); +return this;},render:function(){return this;}},{NAME:"view"});},"@VERSION@",{requires:["base-build","node-event-delegate"]});YUI.add("app",function(a){},"@VERSION@",{use:["controller","model","model-list","view"]}); \ No newline at end of file diff --git a/build/app/app.js b/build/app/app.js index 5a5ba432206..e1075efde02 100644 --- a/build/app/app.js +++ b/build/app/app.js @@ -21,8 +21,10 @@ URLs. @uses Base **/ -var YArray = Y.Array, - QS = Y.QueryString, +var HistoryHash = Y.HistoryHash, + Lang = Y.Lang, + QS = Y.QueryString, + YArray = Y.Array, // Android versions lower than 3.0 are buggy and don't update // window.location after a pushState() call, so we fall back to hash-based @@ -30,7 +32,22 @@ var YArray = Y.Array, // // See http://code.google.com/p/android/issues/detail?id=17471 html5 = Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3), - location = Y.config.win.location; + win = Y.config.win, + location = win.location, + + /** + Fired when the controller is ready to begin dispatching to route handlers. + + You shouldn't need to wait for this event unless you plan to implement some + kind of custom dispatching logic. It's used internally in order to avoid + dispatching to an initial route if a browser history change occurs first. + + @event ready + @param {Boolean} dispatched `true` if routes have already been dispatched + (most likely due to a history change). + @fireOnce + **/ + EVT_READY = 'ready'; function Controller() { Controller.superclass.constructor.apply(this, arguments); @@ -40,23 +57,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Public Properties ---------------------------------------------------- /** - Base path or URL from which all routes should be evaluated. + If `true`, the controller will dispatch to the first route handler that + matches the current URL immediately after the controller is initialized, + even if there was no browser history change to trigger a dispatch. + + If you're rendering the initial pageview on the server, then you'll probably + want this to be `false`, but if you're doing all your rendering and route + handling entirely on the client, then setting this to `true` will allow your + client-side routes to handle the initial request of all pageviews without + depending on any server-side handling. + + This property defaults to `false` for HTML5 browsers, `true` for browsers + that rely on hash-based history (since the hash is never sent to the + server). + + @property dispatchOnInit + @type Boolean + @default `false` for HTML5 browsers, `true` for hash-based browsers + **/ + dispatchOnInit: !html5, + + /** + Root path from which all routes should be evaluated. For example, if your controller is running on a page at `http://example.com/myapp/` and you add a route with the path `/`, your route will never execute, because the path will always be preceded by - `/myapp`. Setting _base_ to `/myapp` would cause all routes to be evaluated - relative to that base path, so the `/` route would then execute. + `/myapp`. Setting `root` to `/myapp` would cause all routes to be evaluated + relative to that root URL, so the `/` route would then execute when the + user browses to `http://example.com/myapp/`. This property may be overridden in a subclass, set after instantiation, or passed as a config attribute when instantiating a `Y.Controller`-based class. - @property base + @property root @type String @default `''` **/ - base : '', + root: '', /** Array of route objects specifying routes to be created at instantiation @@ -85,6 +124,34 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Protected Properties ------------------------------------------------- + /** + Whether or not `_dispatch()` has been called since this controller was + instantiated. + + @property _dispatched + @type Boolean + @default undefined + @protected + **/ + + /** + Whether or not this browser is capable of using HTML5 history. + + @property _html5 + @type Boolean + @protected + **/ + _html5: html5, + + /** + Whether or not the `ready` event has fired yet. + + @property _ready + @type Boolean + @default undefined + @protected + **/ + /** Regex used to match parameter placeholders in route paths. @@ -109,31 +176,58 @@ Y.Controller = Y.extend(Controller, Y.Base, { @type RegExp @protected **/ - _regexUrlQuery : /\?([^#]*).*$/, + _regexUrlQuery: /\?([^#]*).*$/, // -- Lifecycle Methods ---------------------------------------------------- initializer: function (config) { + var self = this; + + // Set config properties. config || (config = {}); - this._routes = []; + config.routes && (self.routes = config.routes); + + Lang.isValue(config.root) && (self.root = config.root); + Lang.isValue(config.dispatchOnInit) && + (self.dispatchOnInit = config.dispatchOnInit); + + // Create routes. + self._routes = []; - config.base && (this.base = config.base); - config.routes && (this.routes = config.routes); + YArray.each(self.routes, function (route) { + self.route(route.path, route.callback, true); + }); - YArray.each(this.routes, function (route) { - this.route(route.path, route.callback, true); - }, this); + // Set up a history instance or hashchange listener. + if (html5) { + self._history = new Y.HistoryHTML5({force: true}); + self._history.after('change', self._afterHistoryChange, self); + } else { + Y.on('hashchange', self._afterHistoryChange, win, self); + } - // Set up a history instance. - this._history = html5 ? new Y.HistoryHTML5() : new Y.HistoryHash(); - this._history.after('change', this._afterHistoryChange, this); + // Fire a 'ready' event once we're ready to route. We wait first for all + // subclass initializers to finish, and then an additional 20ms to allow + // the browser to fire an initial `popstate` event if it wants to. + self.publish(EVT_READY, { + defaultFn : self._defReadyFn, + fireOnce : true, + preventable: false + }); - // Handle the initial route. - this._dispatch(this._getPath(), this._getState()); + self.once('initializedChange', function () { + setTimeout(function () { + self.fire(EVT_READY, {dispatched: !!self._dispatched}); + }, 20); + }); }, destructor: function () { - this._history.detachAll(); + if (html5) { + this._history.detachAll(); + } else { + Y.detach('hashchange', this._afterHistoryChange, win); + } }, // -- Public Methods ------------------------------------------------------- @@ -177,7 +271,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -185,25 +279,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.replace('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.replace('/path/'); + // New URL: http://example.com/path/ - controller.replace('/', 'You are now at example.com'); + controller.replace('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar + + controller.replace('/'); // New URL: http://example.com/ @method replace - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see save() **/ - replace: function (url, title, state) { - return this._save(url, title, state, true); + replace: function (url) { + return this._save(url, true); }, /** @@ -256,8 +349,6 @@ Y.Controller = Y.extend(Controller, Y.Base, { @param {Object} callback.req.query Query hash representing the URL query string, if any. Parameter names are keys, and are mapped to parameter values. - @param {Object} callback.req.state State object associated with this - URL, if any. @param {Function} callback.next Callback to pass control to the next matching route. If you don't call this function, then no further route handlers will be executed, even if there are more that match. If you do @@ -286,7 +377,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL and create a history entry. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -294,25 +385,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.save('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.save('/path/'); + // New URL: http://example.com/path/ + + controller.save('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar - controller.save('/', 'You are now at example.com'); + controller.save('/'); // New URL: http://example.com/ @method save - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see replace() **/ - save: function (url, title, state) { - return this._save(url, title, state); + save: function (url) { + return this._save(url); }, // -- Protected Methods ---------------------------------------------------- @@ -333,24 +423,29 @@ Y.Controller = Y.extend(Controller, Y.Base, { /** Dispatches to the first route handler that matches the specified _path_. + If called before the `ready` event has fired, the dispatch will be aborted. + This ensures normalized behavior between Chrome (which fires a `popstate` + event on every pageview) and other browsers (which do not). + @method _dispatch @param {String} path URL path. - @param {Object} state State to pass to route handlers. @protected **/ - _dispatch: function (path, state) { - var routes = this.match(path), - req, route, self; + _dispatch: function (path) { + var self = this, + routes = self.match(path), + req; + + self._dispatched = true; if (!routes || !routes.length) { return; } - req = this._getRequest(path, state); - self = this; + req = self._getRequest(path); function next(err) { - var callback, matches; + var callback, matches, route; if (err) { Y.error(err); @@ -378,6 +473,18 @@ Y.Controller = Y.extend(Controller, Y.Base, { next(); }, + /** + Gets the current path from the location hash, or an empty string if the + hash is empty. + + @method _getHashPath + @return {String} Current hash path, or an empty string if the hash is empty. + @protected + **/ + _getHashPath: function () { + return HistoryHash.getHash().replace(this._regexUrlQuery, ''); + }, + /** Gets the current route path. @@ -386,16 +493,9 @@ Y.Controller = Y.extend(Controller, Y.Base, { @protected **/ _getPath: html5 ? function () { - var base = this.base, - path = location.pathname; - - if (base && path.indexOf(base) === 0) { - path = path.substring(base.length); - } - - return path; + return this._removeRoot(location.pathname); } : function () { - return this._history.get('path') || this.base + location.pathname; + return this._getHashPath() || this._removeRoot(location.pathname); }, /** @@ -408,12 +508,15 @@ Y.Controller = Y.extend(Controller, Y.Base, { _getQuery: html5 ? function () { return location.search.substring(1); } : function () { - return this._history.get('query') || location.search.substring(1); + var hash = HistoryHash.getHash(), + matches = hash.match(this._regexUrlQuery); + + return hash && matches ? matches[1] : location.search.substring(1); }, /** - Creates a regular expression from the specified route specification. If - _path_ is already a regex, it will be returned unmodified. + Creates a regular expression from the given route specification. If _path_ + is already a regex, it will be returned unmodified. @method _getRegex @param {String|RegExp} path Route path specification. @@ -440,30 +543,44 @@ Y.Controller = Y.extend(Controller, Y.Base, { @method _getRequest @param {String} path Current path being dispatched. - @param {Object} state Current state. @return {Object} Request object. @protected **/ - _getRequest: function (path, state) { + _getRequest: function (path) { return { path : path, - query: this._parseQuery(this._getQuery()), - state: state + query: this._parseQuery(this._getQuery()) }; }, /** - Gets the current state, if any. + Joins the `root` URL to the specified _url_, normalizing leading/trailing + `/` characters. + + @example + controller.root = '/foo' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + controller.root = '/foo/' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' - @method _getState - @return {Object} Current state. + @method _joinURL + @param {String} url URL to append to the `root` URL. + @return {String} Joined URL. @protected **/ - _getState: html5 ? function () { - return this._history.get(); - } : function () { - var jsonState = this._history.get('state'); - return jsonState ? Y.JSON.parse(jsonState) : {}; + _joinURL: function (url) { + var root = this.root; + + if (url.charAt(0) === '/') { + url = url.substring(1); + } + + return root && root.charAt(root.length - 1) === '/' ? + root + url : + root + '/' + url; }, /** @@ -494,58 +611,59 @@ Y.Controller = Y.extend(Controller, Y.Base, { return result; }, + /** + Removes the `root` URL from the from of _path_ (if it's there) and returns + the result. The returned path will always have a leading `/`. + + @method _removeRoot + @param {String} path URL path. + @return {String} Rootless path. + @protected + **/ + _removeRoot: function (path) { + var root = this.root; + + if (root && path.indexOf(root) === 0) { + path = path.substring(root.length); + } + + return path.charAt(0) === '/' ? path : '/' + path; + }, + /** Saves a history entry using either `pushState()` or the location hash. @method _save @param {String} [url] URL for the history entry. - @param {String} [title] Page title associated with the history entry. - @param {Object} [state] State object associated with the history entry. @param {Boolean} [replace=false] If `true`, the current history entry will be replaced instead of a new one being added. @chainable @protected **/ - _save: function (url, title, state, replace) { - var jsonState, query; + _save: html5 ? function (url, replace) { + // Force _ready to true to ensure that the history change is handled + // even if _save is called before the `ready` event fires. + this._ready = true; - if (html5) { - if (typeof url === 'string') { - url = this.base + url; - } - } else { - // If we're not using HTML5 history, take over the history state for - // our own purposes and shove the implementer's state inside it as a - // JSON string. - jsonState = state && Y.JSON.stringify(state); - - // Extract a query string from the URL if there is one, then remove - // both the query and the hash portions of the URL so we can store - // just the path. - url = url.replace(this._regexUrlQuery, function (match, params) { - query = params; - return ''; - }); - - state = {path: url || this._getPath()}; + this._history[replace ? 'replace' : 'add'](null, { + url: typeof url === 'string' ? this._joinURL(url) : url + }); + return this; + } : function (url, replace) { + this._ready = true; - query && (state.query = query); - jsonState && (state.state = jsonState); + if (typeof url === 'string' && url.charAt(0) !== '/') { + url = '/' + url; } - this._history[replace ? 'replace' : 'add'](state || {}, { - merge: false, - title: title, - url : url - }); - + HistoryHash[replace ? 'replaceHash' : 'setHash'](url); return this; }, // -- Protected Event Handlers --------------------------------------------- /** - Handles `history:change` events. + Handles `history:change` and `hashchange` events. @method _afterHistoryChange @param {EventFacade} e @@ -554,18 +672,48 @@ Y.Controller = Y.extend(Controller, Y.Base, { _afterHistoryChange: function (e) { var self = this; - // We need to yield control to the UI thread to allow the browser to - // update document.location before we dispatch. - setTimeout(function () { - self._dispatch(self._getPath(), self._getState()); - }, 1); + if (self._ready) { + // We need to yield control to the UI thread to allow the browser to + // update window.location before we dispatch. + setTimeout(function () { + self._dispatch(self._getPath()); + }, 1); + } + }, + + // -- Default Event Handlers ----------------------------------------------- + + /** + Default handler for the `ready` event. + + @method _defReadyFn + @param {EventFacade} e + @protected + **/ + _defReadyFn: function (e) { + var hash; + + this._ready = true; + + if (this.dispatchOnInit && !this._dispatched) { + if (html5 && (hash = this._getHashPath()) + && hash.charAt(0) === '/') { + + // This is an HTML5 browser and we have a hash-based path in the + // URL, so we need to upgrade the URL to a non-hash URL. This + // will trigger a `history:change` event. + this._history.replace(null, {url: this._joinURL(hash)}); + } else { + this._dispatch(this._getPath()); + } + } } }, { NAME: 'controller' }); -}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history', 'json']}); +}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'history'], optional:['querystring-parse']}); YUI.add('model', function(Y) { /** diff --git a/build/app/controller-debug.js b/build/app/controller-debug.js index fed5ca744c5..9cba33479e3 100644 --- a/build/app/controller-debug.js +++ b/build/app/controller-debug.js @@ -21,8 +21,10 @@ URLs. @uses Base **/ -var YArray = Y.Array, - QS = Y.QueryString, +var HistoryHash = Y.HistoryHash, + Lang = Y.Lang, + QS = Y.QueryString, + YArray = Y.Array, // Android versions lower than 3.0 are buggy and don't update // window.location after a pushState() call, so we fall back to hash-based @@ -30,7 +32,22 @@ var YArray = Y.Array, // // See http://code.google.com/p/android/issues/detail?id=17471 html5 = Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3), - location = Y.config.win.location; + win = Y.config.win, + location = win.location, + + /** + Fired when the controller is ready to begin dispatching to route handlers. + + You shouldn't need to wait for this event unless you plan to implement some + kind of custom dispatching logic. It's used internally in order to avoid + dispatching to an initial route if a browser history change occurs first. + + @event ready + @param {Boolean} dispatched `true` if routes have already been dispatched + (most likely due to a history change). + @fireOnce + **/ + EVT_READY = 'ready'; function Controller() { Controller.superclass.constructor.apply(this, arguments); @@ -40,23 +57,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Public Properties ---------------------------------------------------- /** - Base path or URL from which all routes should be evaluated. + If `true`, the controller will dispatch to the first route handler that + matches the current URL immediately after the controller is initialized, + even if there was no browser history change to trigger a dispatch. + + If you're rendering the initial pageview on the server, then you'll probably + want this to be `false`, but if you're doing all your rendering and route + handling entirely on the client, then setting this to `true` will allow your + client-side routes to handle the initial request of all pageviews without + depending on any server-side handling. + + This property defaults to `false` for HTML5 browsers, `true` for browsers + that rely on hash-based history (since the hash is never sent to the + server). + + @property dispatchOnInit + @type Boolean + @default `false` for HTML5 browsers, `true` for hash-based browsers + **/ + dispatchOnInit: !html5, + + /** + Root path from which all routes should be evaluated. For example, if your controller is running on a page at `http://example.com/myapp/` and you add a route with the path `/`, your route will never execute, because the path will always be preceded by - `/myapp`. Setting _base_ to `/myapp` would cause all routes to be evaluated - relative to that base path, so the `/` route would then execute. + `/myapp`. Setting `root` to `/myapp` would cause all routes to be evaluated + relative to that root URL, so the `/` route would then execute when the + user browses to `http://example.com/myapp/`. This property may be overridden in a subclass, set after instantiation, or passed as a config attribute when instantiating a `Y.Controller`-based class. - @property base + @property root @type String @default `''` **/ - base : '', + root: '', /** Array of route objects specifying routes to be created at instantiation @@ -85,6 +124,34 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Protected Properties ------------------------------------------------- + /** + Whether or not `_dispatch()` has been called since this controller was + instantiated. + + @property _dispatched + @type Boolean + @default undefined + @protected + **/ + + /** + Whether or not this browser is capable of using HTML5 history. + + @property _html5 + @type Boolean + @protected + **/ + _html5: html5, + + /** + Whether or not the `ready` event has fired yet. + + @property _ready + @type Boolean + @default undefined + @protected + **/ + /** Regex used to match parameter placeholders in route paths. @@ -109,31 +176,58 @@ Y.Controller = Y.extend(Controller, Y.Base, { @type RegExp @protected **/ - _regexUrlQuery : /\?([^#]*).*$/, + _regexUrlQuery: /\?([^#]*).*$/, // -- Lifecycle Methods ---------------------------------------------------- initializer: function (config) { + var self = this; + + // Set config properties. config || (config = {}); - this._routes = []; + config.routes && (self.routes = config.routes); - config.base && (this.base = config.base); - config.routes && (this.routes = config.routes); + Lang.isValue(config.root) && (self.root = config.root); + Lang.isValue(config.dispatchOnInit) && + (self.dispatchOnInit = config.dispatchOnInit); - YArray.each(this.routes, function (route) { - this.route(route.path, route.callback, true); - }, this); + // Create routes. + self._routes = []; - // Set up a history instance. - this._history = html5 ? new Y.HistoryHTML5() : new Y.HistoryHash(); - this._history.after('change', this._afterHistoryChange, this); + YArray.each(self.routes, function (route) { + self.route(route.path, route.callback, true); + }); - // Handle the initial route. - this._dispatch(this._getPath(), this._getState()); + // Set up a history instance or hashchange listener. + if (html5) { + self._history = new Y.HistoryHTML5({force: true}); + self._history.after('change', self._afterHistoryChange, self); + } else { + Y.on('hashchange', self._afterHistoryChange, win, self); + } + + // Fire a 'ready' event once we're ready to route. We wait first for all + // subclass initializers to finish, and then an additional 20ms to allow + // the browser to fire an initial `popstate` event if it wants to. + self.publish(EVT_READY, { + defaultFn : self._defReadyFn, + fireOnce : true, + preventable: false + }); + + self.once('initializedChange', function () { + setTimeout(function () { + self.fire(EVT_READY, {dispatched: !!self._dispatched}); + }, 20); + }); }, destructor: function () { - this._history.detachAll(); + if (html5) { + this._history.detachAll(); + } else { + Y.detach('hashchange', this._afterHistoryChange, win); + } }, // -- Public Methods ------------------------------------------------------- @@ -177,7 +271,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -185,25 +279,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.replace('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.replace('/path/'); + // New URL: http://example.com/path/ + + controller.replace('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar - controller.replace('/', 'You are now at example.com'); + controller.replace('/'); // New URL: http://example.com/ @method replace - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see save() **/ - replace: function (url, title, state) { - return this._save(url, title, state, true); + replace: function (url) { + return this._save(url, true); }, /** @@ -258,8 +351,6 @@ Y.Controller = Y.extend(Controller, Y.Base, { @param {Object} callback.req.query Query hash representing the URL query string, if any. Parameter names are keys, and are mapped to parameter values. - @param {Object} callback.req.state State object associated with this - URL, if any. @param {Function} callback.next Callback to pass control to the next matching route. If you don't call this function, then no further route handlers will be executed, even if there are more that match. If you do @@ -288,7 +379,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL and create a history entry. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -296,25 +387,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.save('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.save('/path/'); + // New URL: http://example.com/path/ - controller.save('/', 'You are now at example.com'); + controller.save('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar + + controller.save('/'); // New URL: http://example.com/ @method save - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see replace() **/ - save: function (url, title, state) { - return this._save(url, title, state); + save: function (url) { + return this._save(url); }, // -- Protected Methods ---------------------------------------------------- @@ -335,24 +425,29 @@ Y.Controller = Y.extend(Controller, Y.Base, { /** Dispatches to the first route handler that matches the specified _path_. + If called before the `ready` event has fired, the dispatch will be aborted. + This ensures normalized behavior between Chrome (which fires a `popstate` + event on every pageview) and other browsers (which do not). + @method _dispatch @param {String} path URL path. - @param {Object} state State to pass to route handlers. @protected **/ - _dispatch: function (path, state) { - var routes = this.match(path), - req, route, self; + _dispatch: function (path) { + var self = this, + routes = self.match(path), + req; + + self._dispatched = true; if (!routes || !routes.length) { return; } - req = this._getRequest(path, state); - self = this; + req = self._getRequest(path); function next(err) { - var callback, matches; + var callback, matches, route; if (err) { Y.error(err); @@ -380,6 +475,18 @@ Y.Controller = Y.extend(Controller, Y.Base, { next(); }, + /** + Gets the current path from the location hash, or an empty string if the + hash is empty. + + @method _getHashPath + @return {String} Current hash path, or an empty string if the hash is empty. + @protected + **/ + _getHashPath: function () { + return HistoryHash.getHash().replace(this._regexUrlQuery, ''); + }, + /** Gets the current route path. @@ -388,16 +495,9 @@ Y.Controller = Y.extend(Controller, Y.Base, { @protected **/ _getPath: html5 ? function () { - var base = this.base, - path = location.pathname; - - if (base && path.indexOf(base) === 0) { - path = path.substring(base.length); - } - - return path; + return this._removeRoot(location.pathname); } : function () { - return this._history.get('path') || this.base + location.pathname; + return this._getHashPath() || this._removeRoot(location.pathname); }, /** @@ -410,12 +510,15 @@ Y.Controller = Y.extend(Controller, Y.Base, { _getQuery: html5 ? function () { return location.search.substring(1); } : function () { - return this._history.get('query') || location.search.substring(1); + var hash = HistoryHash.getHash(), + matches = hash.match(this._regexUrlQuery); + + return hash && matches ? matches[1] : location.search.substring(1); }, /** - Creates a regular expression from the specified route specification. If - _path_ is already a regex, it will be returned unmodified. + Creates a regular expression from the given route specification. If _path_ + is already a regex, it will be returned unmodified. @method _getRegex @param {String|RegExp} path Route path specification. @@ -442,30 +545,44 @@ Y.Controller = Y.extend(Controller, Y.Base, { @method _getRequest @param {String} path Current path being dispatched. - @param {Object} state Current state. @return {Object} Request object. @protected **/ - _getRequest: function (path, state) { + _getRequest: function (path) { return { path : path, - query: this._parseQuery(this._getQuery()), - state: state + query: this._parseQuery(this._getQuery()) }; }, /** - Gets the current state, if any. + Joins the `root` URL to the specified _url_, normalizing leading/trailing + `/` characters. - @method _getState - @return {Object} Current state. + @example + controller.root = '/foo' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + controller.root = '/foo/' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + @method _joinURL + @param {String} url URL to append to the `root` URL. + @return {String} Joined URL. @protected **/ - _getState: html5 ? function () { - return this._history.get(); - } : function () { - var jsonState = this._history.get('state'); - return jsonState ? Y.JSON.parse(jsonState) : {}; + _joinURL: function (url) { + var root = this.root; + + if (url.charAt(0) === '/') { + url = url.substring(1); + } + + return root && root.charAt(root.length - 1) === '/' ? + root + url : + root + '/' + url; }, /** @@ -496,58 +613,59 @@ Y.Controller = Y.extend(Controller, Y.Base, { return result; }, + /** + Removes the `root` URL from the from of _path_ (if it's there) and returns + the result. The returned path will always have a leading `/`. + + @method _removeRoot + @param {String} path URL path. + @return {String} Rootless path. + @protected + **/ + _removeRoot: function (path) { + var root = this.root; + + if (root && path.indexOf(root) === 0) { + path = path.substring(root.length); + } + + return path.charAt(0) === '/' ? path : '/' + path; + }, + /** Saves a history entry using either `pushState()` or the location hash. @method _save @param {String} [url] URL for the history entry. - @param {String} [title] Page title associated with the history entry. - @param {Object} [state] State object associated with the history entry. @param {Boolean} [replace=false] If `true`, the current history entry will be replaced instead of a new one being added. @chainable @protected **/ - _save: function (url, title, state, replace) { - var jsonState, query; + _save: html5 ? function (url, replace) { + // Force _ready to true to ensure that the history change is handled + // even if _save is called before the `ready` event fires. + this._ready = true; - if (html5) { - if (typeof url === 'string') { - url = this.base + url; - } - } else { - // If we're not using HTML5 history, take over the history state for - // our own purposes and shove the implementer's state inside it as a - // JSON string. - jsonState = state && Y.JSON.stringify(state); - - // Extract a query string from the URL if there is one, then remove - // both the query and the hash portions of the URL so we can store - // just the path. - url = url.replace(this._regexUrlQuery, function (match, params) { - query = params; - return ''; - }); - - state = {path: url || this._getPath()}; - - query && (state.query = query); - jsonState && (state.state = jsonState); - } - - this._history[replace ? 'replace' : 'add'](state || {}, { - merge: false, - title: title, - url : url + this._history[replace ? 'replace' : 'add'](null, { + url: typeof url === 'string' ? this._joinURL(url) : url }); + return this; + } : function (url, replace) { + this._ready = true; + if (typeof url === 'string' && url.charAt(0) !== '/') { + url = '/' + url; + } + + HistoryHash[replace ? 'replaceHash' : 'setHash'](url); return this; }, // -- Protected Event Handlers --------------------------------------------- /** - Handles `history:change` events. + Handles `history:change` and `hashchange` events. @method _afterHistoryChange @param {EventFacade} e @@ -556,15 +674,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { _afterHistoryChange: function (e) { var self = this; - // We need to yield control to the UI thread to allow the browser to - // update document.location before we dispatch. - setTimeout(function () { - self._dispatch(self._getPath(), self._getState()); - }, 1); + if (self._ready) { + // We need to yield control to the UI thread to allow the browser to + // update window.location before we dispatch. + setTimeout(function () { + self._dispatch(self._getPath()); + }, 1); + } + }, + + // -- Default Event Handlers ----------------------------------------------- + + /** + Default handler for the `ready` event. + + @method _defReadyFn + @param {EventFacade} e + @protected + **/ + _defReadyFn: function (e) { + var hash; + + this._ready = true; + + if (this.dispatchOnInit && !this._dispatched) { + if (html5 && (hash = this._getHashPath()) + && hash.charAt(0) === '/') { + + // This is an HTML5 browser and we have a hash-based path in the + // URL, so we need to upgrade the URL to a non-hash URL. This + // will trigger a `history:change` event. + this._history.replace(null, {url: this._joinURL(hash)}); + } else { + this._dispatch(this._getPath()); + } + } } }, { NAME: 'controller' }); -}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history', 'json']}); +}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history']}); diff --git a/build/app/controller-min.js b/build/app/controller-min.js index 7a06b667c02..d06e134a8a3 100644 --- a/build/app/controller-min.js +++ b/build/app/controller-min.js @@ -1 +1 @@ -YUI.add("controller",function(f){var d=f.Array,b=f.QueryString,c=f.HistoryBase.html5&&(!f.UA.android||f.UA.android>=3),a=f.config.win.location;function e(){e.superclass.constructor.apply(this,arguments);}f.Controller=f.extend(e,f.Base,{base:"",routes:[],_regexPathParam:/([:*])([\w\d-]+)/g,_regexUrlQuery:/\?([^#]*).*$/,initializer:function(g){g||(g={});this._routes=[];g.base&&(this.base=g.base);g.routes&&(this.routes=g.routes);d.each(this.routes,function(h){this.route(h.path,h.callback,true);},this);this._history=c?new f.HistoryHTML5():new f.HistoryHash();this._history.after("change",this._afterHistoryChange,this);this._dispatch(this._getPath(),this._getState());},destructor:function(){this._history.detachAll();},match:function(g){return d.filter(this._routes,function(h){return g.search(h.regex)>-1;});},replace:function(g,i,h){return this._save(g,i,h,true);},route:function(h,i){var g=[];this._routes.push({callback:i,keys:g,path:h,regex:this._getRegex(h,g)});return this;},save:function(g,i,h){return this._save(g,i,h);},_decode:function(g){return decodeURIComponent(g.replace(/\+/g," "));},_dispatch:function(m,l){var g=this.match(m),k,i,h;if(!g||!g.length){return;}k=this._getRequest(m,l);h=this;function j(n){var p,o;if(n){f.error(n);}else{if((i=g.shift())){o=i.regex.exec(m);p=typeof i.callback==="string"?h[i.callback]:i.callback;if(o.length===i.keys.length+1){k.params=d.hash(i.keys,o.slice(1));}else{k.params={};d.each(o,function(r,q){k.params[q]=r;});}p.call(h,k,j);}}}j();},_getPath:c?function(){var g=this.base,h=a.pathname;if(g&&h.indexOf(g)===0){h=h.substring(g.length);}return h;}:function(){return this._history.get("path")||this.base+a.pathname;},_getQuery:c?function(){return a.search.substring(1);}:function(){return this._history.get("query")||a.search.substring(1);},_getRegex:function(h,g){if(h instanceof RegExp){return h;}h=h.replace(this._regexPathParam,function(j,i,k){g.push(k);return i==="*"?"(.*?)":"([^/]*)";});return new RegExp("^"+h+"$");},_getRequest:function(h,g){return{path:h,query:this._parseQuery(this._getQuery()),state:g};},_getState:c?function(){return this._history.get();}:function(){var g=this._history.get("state");return g?f.JSON.parse(g):{};},_parseQuery:b&&b.parse?b.parse:function(k){var l=this._decode,n=k.split("&"),j=0,h=n.length,g={},m;for(;j=3),g=a.config.win,j=g.location,d="ready";function c(){c.superclass.constructor.apply(this,arguments);}a.Controller=a.extend(c,a.Base,{dispatchOnInit:!f,root:"",routes:[],_html5:f,_regexPathParam:/([:*])([\w\d-]+)/g,_regexUrlQuery:/\?([^#]*).*$/,initializer:function(l){var k=this;l||(l={});l.routes&&(k.routes=l.routes);e.isValue(l.root)&&(k.root=l.root);e.isValue(l.dispatchOnInit)&&(k.dispatchOnInit=l.dispatchOnInit);k._routes=[];i.each(k.routes,function(m){k.route(m.path,m.callback,true);});if(f){k._history=new a.HistoryHTML5({force:true});k._history.after("change",k._afterHistoryChange,k);}else{a.on("hashchange",k._afterHistoryChange,g,k);}k.publish(d,{defaultFn:k._defReadyFn,fireOnce:true,preventable:false});k.once("initializedChange",function(){setTimeout(function(){k.fire(d,{dispatched:!!k._dispatched});},20);});},destructor:function(){if(f){this._history.detachAll();}else{a.detach("hashchange",this._afterHistoryChange,g);}},match:function(k){return i.filter(this._routes,function(l){return k.search(l.regex)>-1;});},replace:function(k){return this._save(k,true);},route:function(l,m){var k=[];this._routes.push({callback:m,keys:k,path:l,regex:this._getRegex(l,k)});return this;},save:function(k){return this._save(k);},_decode:function(k){return decodeURIComponent(k.replace(/\+/g," "));},_dispatch:function(o){var l=this,k=l.match(o),n;l._dispatched=true;if(!k||!k.length){return;}n=l._getRequest(o);function m(q){var s,r,p;if(q){a.error(q);}else{if((p=k.shift())){r=p.regex.exec(o);s=typeof p.callback==="string"?l[p.callback]:p.callback;if(r.length===p.keys.length+1){n.params=i.hash(p.keys,r.slice(1));}else{n.params={};i.each(r,function(u,t){n.params[t]=u;});}s.call(l,n,m);}}}m();},_getHashPath:function(){return h.getHash().replace(this._regexUrlQuery,"");},_getPath:f?function(){return this._removeRoot(j.pathname);}:function(){return this._getHashPath()||this._removeRoot(j.pathname);},_getQuery:f?function(){return j.search.substring(1);}:function(){var l=h.getHash(),k=l.match(this._regexUrlQuery);return l&&k?k[1]:j.search.substring(1);},_getRegex:function(l,k){if(l instanceof RegExp){return l;}l=l.replace(this._regexPathParam,function(n,m,o){k.push(o);return m==="*"?"(.*?)":"([^/]*)";});return new RegExp("^"+l+"$");},_getRequest:function(k){return{path:k,query:this._parseQuery(this._getQuery())};},_joinURL:function(l){var k=this.root;if(l.charAt(0)==="/"){l=l.substring(1);}return k&&k.charAt(k.length-1)==="/"?k+l:k+"/"+l;},_parseQuery:b&&b.parse?b.parse:function(n){var o=this._decode,q=n.split("&"),m=0,l=q.length,k={},p;for(;m= 3), - location = Y.config.win.location; + win = Y.config.win, + location = win.location, + + /** + Fired when the controller is ready to begin dispatching to route handlers. + + You shouldn't need to wait for this event unless you plan to implement some + kind of custom dispatching logic. It's used internally in order to avoid + dispatching to an initial route if a browser history change occurs first. + + @event ready + @param {Boolean} dispatched `true` if routes have already been dispatched + (most likely due to a history change). + @fireOnce + **/ + EVT_READY = 'ready'; function Controller() { Controller.superclass.constructor.apply(this, arguments); @@ -40,23 +57,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Public Properties ---------------------------------------------------- /** - Base path or URL from which all routes should be evaluated. + If `true`, the controller will dispatch to the first route handler that + matches the current URL immediately after the controller is initialized, + even if there was no browser history change to trigger a dispatch. + + If you're rendering the initial pageview on the server, then you'll probably + want this to be `false`, but if you're doing all your rendering and route + handling entirely on the client, then setting this to `true` will allow your + client-side routes to handle the initial request of all pageviews without + depending on any server-side handling. + + This property defaults to `false` for HTML5 browsers, `true` for browsers + that rely on hash-based history (since the hash is never sent to the + server). + + @property dispatchOnInit + @type Boolean + @default `false` for HTML5 browsers, `true` for hash-based browsers + **/ + dispatchOnInit: !html5, + + /** + Root path from which all routes should be evaluated. For example, if your controller is running on a page at `http://example.com/myapp/` and you add a route with the path `/`, your route will never execute, because the path will always be preceded by - `/myapp`. Setting _base_ to `/myapp` would cause all routes to be evaluated - relative to that base path, so the `/` route would then execute. + `/myapp`. Setting `root` to `/myapp` would cause all routes to be evaluated + relative to that root URL, so the `/` route would then execute when the + user browses to `http://example.com/myapp/`. This property may be overridden in a subclass, set after instantiation, or passed as a config attribute when instantiating a `Y.Controller`-based class. - @property base + @property root @type String @default `''` **/ - base : '', + root: '', /** Array of route objects specifying routes to be created at instantiation @@ -85,6 +124,34 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Protected Properties ------------------------------------------------- + /** + Whether or not `_dispatch()` has been called since this controller was + instantiated. + + @property _dispatched + @type Boolean + @default undefined + @protected + **/ + + /** + Whether or not this browser is capable of using HTML5 history. + + @property _html5 + @type Boolean + @protected + **/ + _html5: html5, + + /** + Whether or not the `ready` event has fired yet. + + @property _ready + @type Boolean + @default undefined + @protected + **/ + /** Regex used to match parameter placeholders in route paths. @@ -109,31 +176,58 @@ Y.Controller = Y.extend(Controller, Y.Base, { @type RegExp @protected **/ - _regexUrlQuery : /\?([^#]*).*$/, + _regexUrlQuery: /\?([^#]*).*$/, // -- Lifecycle Methods ---------------------------------------------------- initializer: function (config) { + var self = this; + + // Set config properties. config || (config = {}); - this._routes = []; + config.routes && (self.routes = config.routes); - config.base && (this.base = config.base); - config.routes && (this.routes = config.routes); + Lang.isValue(config.root) && (self.root = config.root); + Lang.isValue(config.dispatchOnInit) && + (self.dispatchOnInit = config.dispatchOnInit); - YArray.each(this.routes, function (route) { - this.route(route.path, route.callback, true); - }, this); + // Create routes. + self._routes = []; - // Set up a history instance. - this._history = html5 ? new Y.HistoryHTML5() : new Y.HistoryHash(); - this._history.after('change', this._afterHistoryChange, this); + YArray.each(self.routes, function (route) { + self.route(route.path, route.callback, true); + }); - // Handle the initial route. - this._dispatch(this._getPath(), this._getState()); + // Set up a history instance or hashchange listener. + if (html5) { + self._history = new Y.HistoryHTML5({force: true}); + self._history.after('change', self._afterHistoryChange, self); + } else { + Y.on('hashchange', self._afterHistoryChange, win, self); + } + + // Fire a 'ready' event once we're ready to route. We wait first for all + // subclass initializers to finish, and then an additional 20ms to allow + // the browser to fire an initial `popstate` event if it wants to. + self.publish(EVT_READY, { + defaultFn : self._defReadyFn, + fireOnce : true, + preventable: false + }); + + self.once('initializedChange', function () { + setTimeout(function () { + self.fire(EVT_READY, {dispatched: !!self._dispatched}); + }, 20); + }); }, destructor: function () { - this._history.detachAll(); + if (html5) { + this._history.detachAll(); + } else { + Y.detach('hashchange', this._afterHistoryChange, win); + } }, // -- Public Methods ------------------------------------------------------- @@ -177,7 +271,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -185,25 +279,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.replace('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.replace('/path/'); + // New URL: http://example.com/path/ + + controller.replace('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar - controller.replace('/', 'You are now at example.com'); + controller.replace('/'); // New URL: http://example.com/ @method replace - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see save() **/ - replace: function (url, title, state) { - return this._save(url, title, state, true); + replace: function (url) { + return this._save(url, true); }, /** @@ -256,8 +349,6 @@ Y.Controller = Y.extend(Controller, Y.Base, { @param {Object} callback.req.query Query hash representing the URL query string, if any. Parameter names are keys, and are mapped to parameter values. - @param {Object} callback.req.state State object associated with this - URL, if any. @param {Function} callback.next Callback to pass control to the next matching route. If you don't call this function, then no further route handlers will be executed, even if there are more that match. If you do @@ -286,7 +377,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL and create a history entry. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -294,25 +385,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.save('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.save('/path/'); + // New URL: http://example.com/path/ - controller.save('/', 'You are now at example.com'); + controller.save('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar + + controller.save('/'); // New URL: http://example.com/ @method save - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see replace() **/ - save: function (url, title, state) { - return this._save(url, title, state); + save: function (url) { + return this._save(url); }, // -- Protected Methods ---------------------------------------------------- @@ -333,24 +423,29 @@ Y.Controller = Y.extend(Controller, Y.Base, { /** Dispatches to the first route handler that matches the specified _path_. + If called before the `ready` event has fired, the dispatch will be aborted. + This ensures normalized behavior between Chrome (which fires a `popstate` + event on every pageview) and other browsers (which do not). + @method _dispatch @param {String} path URL path. - @param {Object} state State to pass to route handlers. @protected **/ - _dispatch: function (path, state) { - var routes = this.match(path), - req, route, self; + _dispatch: function (path) { + var self = this, + routes = self.match(path), + req; + + self._dispatched = true; if (!routes || !routes.length) { return; } - req = this._getRequest(path, state); - self = this; + req = self._getRequest(path); function next(err) { - var callback, matches; + var callback, matches, route; if (err) { Y.error(err); @@ -378,6 +473,18 @@ Y.Controller = Y.extend(Controller, Y.Base, { next(); }, + /** + Gets the current path from the location hash, or an empty string if the + hash is empty. + + @method _getHashPath + @return {String} Current hash path, or an empty string if the hash is empty. + @protected + **/ + _getHashPath: function () { + return HistoryHash.getHash().replace(this._regexUrlQuery, ''); + }, + /** Gets the current route path. @@ -386,16 +493,9 @@ Y.Controller = Y.extend(Controller, Y.Base, { @protected **/ _getPath: html5 ? function () { - var base = this.base, - path = location.pathname; - - if (base && path.indexOf(base) === 0) { - path = path.substring(base.length); - } - - return path; + return this._removeRoot(location.pathname); } : function () { - return this._history.get('path') || this.base + location.pathname; + return this._getHashPath() || this._removeRoot(location.pathname); }, /** @@ -408,12 +508,15 @@ Y.Controller = Y.extend(Controller, Y.Base, { _getQuery: html5 ? function () { return location.search.substring(1); } : function () { - return this._history.get('query') || location.search.substring(1); + var hash = HistoryHash.getHash(), + matches = hash.match(this._regexUrlQuery); + + return hash && matches ? matches[1] : location.search.substring(1); }, /** - Creates a regular expression from the specified route specification. If - _path_ is already a regex, it will be returned unmodified. + Creates a regular expression from the given route specification. If _path_ + is already a regex, it will be returned unmodified. @method _getRegex @param {String|RegExp} path Route path specification. @@ -440,30 +543,44 @@ Y.Controller = Y.extend(Controller, Y.Base, { @method _getRequest @param {String} path Current path being dispatched. - @param {Object} state Current state. @return {Object} Request object. @protected **/ - _getRequest: function (path, state) { + _getRequest: function (path) { return { path : path, - query: this._parseQuery(this._getQuery()), - state: state + query: this._parseQuery(this._getQuery()) }; }, /** - Gets the current state, if any. + Joins the `root` URL to the specified _url_, normalizing leading/trailing + `/` characters. - @method _getState - @return {Object} Current state. + @example + controller.root = '/foo' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + controller.root = '/foo/' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + @method _joinURL + @param {String} url URL to append to the `root` URL. + @return {String} Joined URL. @protected **/ - _getState: html5 ? function () { - return this._history.get(); - } : function () { - var jsonState = this._history.get('state'); - return jsonState ? Y.JSON.parse(jsonState) : {}; + _joinURL: function (url) { + var root = this.root; + + if (url.charAt(0) === '/') { + url = url.substring(1); + } + + return root && root.charAt(root.length - 1) === '/' ? + root + url : + root + '/' + url; }, /** @@ -494,58 +611,59 @@ Y.Controller = Y.extend(Controller, Y.Base, { return result; }, + /** + Removes the `root` URL from the from of _path_ (if it's there) and returns + the result. The returned path will always have a leading `/`. + + @method _removeRoot + @param {String} path URL path. + @return {String} Rootless path. + @protected + **/ + _removeRoot: function (path) { + var root = this.root; + + if (root && path.indexOf(root) === 0) { + path = path.substring(root.length); + } + + return path.charAt(0) === '/' ? path : '/' + path; + }, + /** Saves a history entry using either `pushState()` or the location hash. @method _save @param {String} [url] URL for the history entry. - @param {String} [title] Page title associated with the history entry. - @param {Object} [state] State object associated with the history entry. @param {Boolean} [replace=false] If `true`, the current history entry will be replaced instead of a new one being added. @chainable @protected **/ - _save: function (url, title, state, replace) { - var jsonState, query; + _save: html5 ? function (url, replace) { + // Force _ready to true to ensure that the history change is handled + // even if _save is called before the `ready` event fires. + this._ready = true; - if (html5) { - if (typeof url === 'string') { - url = this.base + url; - } - } else { - // If we're not using HTML5 history, take over the history state for - // our own purposes and shove the implementer's state inside it as a - // JSON string. - jsonState = state && Y.JSON.stringify(state); - - // Extract a query string from the URL if there is one, then remove - // both the query and the hash portions of the URL so we can store - // just the path. - url = url.replace(this._regexUrlQuery, function (match, params) { - query = params; - return ''; - }); - - state = {path: url || this._getPath()}; - - query && (state.query = query); - jsonState && (state.state = jsonState); - } - - this._history[replace ? 'replace' : 'add'](state || {}, { - merge: false, - title: title, - url : url + this._history[replace ? 'replace' : 'add'](null, { + url: typeof url === 'string' ? this._joinURL(url) : url }); + return this; + } : function (url, replace) { + this._ready = true; + if (typeof url === 'string' && url.charAt(0) !== '/') { + url = '/' + url; + } + + HistoryHash[replace ? 'replaceHash' : 'setHash'](url); return this; }, // -- Protected Event Handlers --------------------------------------------- /** - Handles `history:change` events. + Handles `history:change` and `hashchange` events. @method _afterHistoryChange @param {EventFacade} e @@ -554,15 +672,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { _afterHistoryChange: function (e) { var self = this; - // We need to yield control to the UI thread to allow the browser to - // update document.location before we dispatch. - setTimeout(function () { - self._dispatch(self._getPath(), self._getState()); - }, 1); + if (self._ready) { + // We need to yield control to the UI thread to allow the browser to + // update window.location before we dispatch. + setTimeout(function () { + self._dispatch(self._getPath()); + }, 1); + } + }, + + // -- Default Event Handlers ----------------------------------------------- + + /** + Default handler for the `ready` event. + + @method _defReadyFn + @param {EventFacade} e + @protected + **/ + _defReadyFn: function (e) { + var hash; + + this._ready = true; + + if (this.dispatchOnInit && !this._dispatched) { + if (html5 && (hash = this._getHashPath()) + && hash.charAt(0) === '/') { + + // This is an HTML5 browser and we have a hash-based path in the + // URL, so we need to upgrade the URL to a non-hash URL. This + // will trigger a `history:change` event. + this._history.replace(null, {url: this._joinURL(hash)}); + } else { + this._dispatch(this._getPath()); + } + } } }, { NAME: 'controller' }); -}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history', 'json']}); +}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history']}); diff --git a/build/arraylist/arraylist-debug.js b/build/arraylist/arraylist-debug.js index 5d5c120eb73..9e018c94748 100644 --- a/build/arraylist/arraylist-debug.js +++ b/build/arraylist/arraylist-debug.js @@ -171,10 +171,15 @@ Y.mix( ArrayList, { * _item method in case there is any special behavior that is * appropriate for API mirroring.

* + *

If the iterated method returns a value, the return value from the + * added method will be an array of values with each value being at the + * corresponding index for that item. If the iterated method does not + * return a value, the added method will be chainable. + * * @method addMethod * @static - * @param dest { Object } Object or prototype to receive the iterator method - * @param name { String | Array } Name of method of methods to create + * @param dest {Object} Object or prototype to receive the iterator method + * @param name {String|String[]} Name of method of methods to create */ addMethod: function ( dest, names ) { @@ -191,7 +196,7 @@ Y.mix( ArrayList, { var result = item[ name ].apply( item, args ); if ( result !== undefined && result !== item ) { - ret.push( result ); + ret[i] = result; } }, this); diff --git a/build/arraylist/arraylist-min.js b/build/arraylist/arraylist-min.js index 74f3ebe2bc0..a4c0e74cf0e 100644 --- a/build/arraylist/arraylist-min.js +++ b/build/arraylist/arraylist-min.js @@ -1 +1 @@ -YUI.add("arraylist",function(e){var d=e.Array,c=d.each,a;function b(f){if(f!==undefined){this._items=e.Lang.isArray(f)?f:d(f);}else{this._items=this._items||[];}}a={item:function(f){return this._items[f];},each:function(g,f){c(this._items,function(j,h){j=this.item(h);g.call(f||j,j,h,this);},this);return this;},some:function(g,f){return d.some(this._items,function(j,h){j=this.item(h);return g.call(f||j,j,h,this);},this);},indexOf:function(f){return d.indexOf(this._items,f);},size:function(){return this._items.length;},isEmpty:function(){return !this.size();},toJSON:function(){return this._items;}};a._item=a.item;b.prototype=a;e.mix(b,{addMethod:function(f,g){g=d(g);c(g,function(h){f[h]=function(){var j=d(arguments,0,true),i=[];c(this._items,function(m,l){m=this._item(l);var k=m[h].apply(m,j);if(k!==undefined&&k!==m){i.push(k);}},this);return i.length?i:this;};});}});e.ArrayList=b;},"@VERSION@"); \ No newline at end of file +YUI.add("arraylist",function(e){var d=e.Array,c=d.each,a;function b(f){if(f!==undefined){this._items=e.Lang.isArray(f)?f:d(f);}else{this._items=this._items||[];}}a={item:function(f){return this._items[f];},each:function(g,f){c(this._items,function(j,h){j=this.item(h);g.call(f||j,j,h,this);},this);return this;},some:function(g,f){return d.some(this._items,function(j,h){j=this.item(h);return g.call(f||j,j,h,this);},this);},indexOf:function(f){return d.indexOf(this._items,f);},size:function(){return this._items.length;},isEmpty:function(){return !this.size();},toJSON:function(){return this._items;}};a._item=a.item;b.prototype=a;e.mix(b,{addMethod:function(f,g){g=d(g);c(g,function(h){f[h]=function(){var j=d(arguments,0,true),i=[];c(this._items,function(m,l){m=this._item(l);var k=m[h].apply(m,j);if(k!==undefined&&k!==m){i[l]=k;}},this);return i.length?i:this;};});}});e.ArrayList=b;},"@VERSION@"); \ No newline at end of file diff --git a/build/arraylist/arraylist.js b/build/arraylist/arraylist.js index 5d5c120eb73..9e018c94748 100644 --- a/build/arraylist/arraylist.js +++ b/build/arraylist/arraylist.js @@ -171,10 +171,15 @@ Y.mix( ArrayList, { * _item method in case there is any special behavior that is * appropriate for API mirroring.

* + *

If the iterated method returns a value, the return value from the + * added method will be an array of values with each value being at the + * corresponding index for that item. If the iterated method does not + * return a value, the added method will be chainable. + * * @method addMethod * @static - * @param dest { Object } Object or prototype to receive the iterator method - * @param name { String | Array } Name of method of methods to create + * @param dest {Object} Object or prototype to receive the iterator method + * @param name {String|String[]} Name of method of methods to create */ addMethod: function ( dest, names ) { @@ -191,7 +196,7 @@ Y.mix( ArrayList, { var result = item[ name ].apply( item, args ); if ( result !== undefined && result !== item ) { - ret.push( result ); + ret[i] = result; } }, this); diff --git a/build/autocomplete-base/autocomplete-base-debug.js b/build/autocomplete-base/autocomplete-base-debug.js index e4dab16bd1a..239b2d49d7c 100644 --- a/build/autocomplete-base/autocomplete-base-debug.js +++ b/build/autocomplete-base/autocomplete-base-debug.js @@ -1628,4 +1628,4 @@ AutoCompleteBase.prototype = { Y.AutoCompleteBase = AutoCompleteBase; -}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base'], optional:['autocomplete-sources']}); +}, '@VERSION@' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']}); diff --git a/build/autocomplete-base/autocomplete-base-min.js b/build/autocomplete-base/autocomplete-base-min.js index 157db0f924a..eb5c73bc334 100644 --- a/build/autocomplete-base/autocomplete-base-min.js +++ b/build/autocomplete-base/autocomplete-base-min.js @@ -1,2 +1,2 @@ YUI.add("autocomplete-base",function(f){var g=f.Escape,j=f.Lang,q=f.Array,i=f.Object,d=j.isFunction,r=j.isString,u=j.trim,l=f.Attribute.INVALID_VALUE,o="_functionValidator",x="_sourceSuccess",c="allowBrowserAutocomplete",h="inputNode",w="query",e="queryDelimiter",b="requestTemplate",m="results",n="resultListLocator",k="value",s="valueChange",a="clear",t=w,p=m;function v(){f.before(this._bindUIACBase,this,"bindUI");f.before(this._destructorACBase,this,"destructor");f.before(this._syncUIACBase,this,"syncUI");this.publish(a,{defaultFn:this._defClearFn});this.publish(t,{defaultFn:this._defQueryFn});this.publish(p,{defaultFn:this._defResultsFn});}v.ATTRS={allowBrowserAutocomplete:{value:false},allowTrailingDelimiter:{value:false},inputNode:{setter:f.one,writeOnce:"initOnly"},maxResults:{value:0},minQueryLength:{value:1},query:{readOnly:true,value:null},queryDelay:{value:100},queryDelimiter:{value:null},requestTemplate:{setter:"_setRequestTemplate",value:null},resultFilters:{setter:"_setResultFilters",value:[]},resultFormatter:{validator:o},resultHighlighter:{setter:"_setResultHighlighter"},resultListLocator:{setter:"_setLocator"},results:{readOnly:true,value:[]},resultTextLocator:{setter:"_setLocator"},source:{setter:"_setSource"},sourceType:{value:null},tokenInput:{readOnly:true},value:{value:""}};v.CSS_PREFIX="ac";v.UI_SRC=(f.Widget&&f.Widget.UI_SRC)||"ui";v.SOURCE_TYPES={array:"_createArraySource","function":"_createFunctionSource",object:"_createObjectSource"};v.prototype={sendRequest:function(A,B){var y,z=this.get("source");if(A||A===""){this._set(w,A);}else{A=this.get(w);}if(z){if(!B){B=this.get(b);}y=B?B(A):A;z.sendRequest({query:A,request:y,callback:{success:f.bind(this._onResponse,this,A)}});}return this;},_bindUIACBase:function(){var z=this.get(h),y=z&&z.tokenInput;if(y){z=y.get(h);this._set("tokenInput",y);}if(!z){f.error("No inputNode specified.");return;}this._inputNode=z;this._acBaseEvents=new f.EventHandle([z.on(s,this._onInputValueChange,this),z.on("blur",this._onInputBlur,this),this.after(c+"Change",this._syncBrowserAutocomplete),this.after("sourceTypeChange",this._afterSourceTypeChange),this.after(s,this._afterValueChange)]);},_destructorACBase:function(){this._acBaseEvents.detach();},_syncUIACBase:function(){this._syncBrowserAutocomplete();this.set(k,this.get(h).get(k));},_createArraySource:function(z){var y=this;return{type:"array",sendRequest:function(A){y[x](z.concat(),A);}};},_createFunctionSource:function(z){var y=this;return{type:"function",sendRequest:function(A){var B;function C(D){y[x](D||[],A);}if((B=z(A.query,C))){C(B);}}};},_createObjectSource:function(z){var y=this;return{type:"object",sendRequest:function(A){var B=A.query;y[x](i.owns(z,B)?z[B]:[],A);}};},_functionValidator:function(y){return y===null||d(y);},_getObjectValue:function(B,A){if(!B){return;}for(var z=0,y=A.length;B&&z0&&H.length>M){H.length=M;}if(O){B=O(A,H.concat());if(!B){return;}for(K=0,L=B.length;K1){C[y-1]=z;z=C.join(A+" ");}z=z+A+" ";}this.set(k,z);},_afterSourceTypeChange:function(y){if(this._rawSource){this.set("source",this._rawSource);}},_afterValueChange:function(E){var A,B,y,z=E.newVal,D,C;if(E.src!==v.UI_SRC){this._inputNode.set(k,z);return;}y=this.get("minQueryLength");D=this._parseValue(z)||"";if(y>=0&&D.length>=y){A=this.get("queryDelay");C=this;B=function(){C.fire(t,{inputValue:z,query:D});};if(A){clearTimeout(this._delay);this._delay=setTimeout(B,A);}else{B();}}else{clearTimeout(this._delay);this.fire(a,{prevVal:E.prevVal?this._parseValue(E.prevVal):null});}},_onInputBlur:function(B){var C=this.get(e),y,z,A;if(C&&!this.get("allowTrailingDelimiter")){C=j.trimRight(C);A=z=this._inputNode.get(k);if(C){while((z=j.trimRight(z))&&(y=z.length-C.length)&&z.lastIndexOf(C)===y){z=z.substring(0,y);}}else{z=j.trimRight(z); -}if(z!==A){this.set(k,z);}}},_onInputValueChange:function(z){var y=z.newVal;if(y===this.get(k)){return;}this.set(k,y,{src:v.UI_SRC});},_onResponse:function(y,z){if(y===this.get(w)){this._parseResponse(y,z.response,z.data);}},_defClearFn:function(){this._set(w,null);this._set(m,[]);},_defQueryFn:function(z){var y=z.query;this.sendRequest(y);},_defResultsFn:function(y){this._set(m,y[m]);}};f.AutoCompleteBase=v;},"@VERSION@",{requires:["array-extras","base-build","escape","event-valuechange","node-base"],optional:["autocomplete-sources"]}); \ No newline at end of file +}if(z!==A){this.set(k,z);}}},_onInputValueChange:function(z){var y=z.newVal;if(y===this.get(k)){return;}this.set(k,y,{src:v.UI_SRC});},_onResponse:function(y,z){if(y===this.get(w)){this._parseResponse(y,z.response,z.data);}},_defClearFn:function(){this._set(w,null);this._set(m,[]);},_defQueryFn:function(z){var y=z.query;this.sendRequest(y);},_defResultsFn:function(y){this._set(m,y[m]);}};f.AutoCompleteBase=v;},"@VERSION@",{optional:["autocomplete-sources"],requires:["array-extras","base-build","escape","event-valuechange","node-base"]}); \ No newline at end of file diff --git a/build/autocomplete-base/autocomplete-base.js b/build/autocomplete-base/autocomplete-base.js index eff07dd803c..103c61bf16d 100644 --- a/build/autocomplete-base/autocomplete-base.js +++ b/build/autocomplete-base/autocomplete-base.js @@ -1621,4 +1621,4 @@ AutoCompleteBase.prototype = { Y.AutoCompleteBase = AutoCompleteBase; -}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base'], optional:['autocomplete-sources']}); +}, '@VERSION@' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']}); diff --git a/build/autocomplete-list/autocomplete-list-debug.js b/build/autocomplete-list/autocomplete-list-debug.js index 1a21c740890..c3ee968383e 100644 --- a/build/autocomplete-list/autocomplete-list-debug.js +++ b/build/autocomplete-list/autocomplete-list-debug.js @@ -830,4 +830,4 @@ Y.AutoCompleteList = List; Y.AutoComplete = List; -}, '@VERSION@' ,{after:['autocomplete-sources'], lang:['en'], skinnable:true, requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align']}); +}, '@VERSION@' ,{requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align'], after:['autocomplete-sources'], lang:['en'], skinnable:true}); diff --git a/build/autocomplete-list/autocomplete-list-min.js b/build/autocomplete-list/autocomplete-list-min.js index 9c12ff3053c..f34752853d1 100644 --- a/build/autocomplete-list/autocomplete-list-min.js +++ b/build/autocomplete-list/autocomplete-list-min.js @@ -1,2 +1,2 @@ YUI.add("autocomplete-list",function(b){var i=b.Lang,v=b.Node,l=b.Array,h=b.UA.ie&&b.UA.ie<7,p=9,s="_CLASS_ITEM",t="_CLASS_ITEM_ACTIVE",d="_CLASS_ITEM_HOVER",u="_SELECTOR_ITEM",f="activeItem",k="alwaysShowList",o="circular",r="hoveredItem",m="id",e="item",c="list",w="result",j="results",q="visible",g="width",n="select",a=b.Base.create("autocompleteList",b.Widget,[b.AutoCompleteBase,b.WidgetPosition,b.WidgetPositionAlign],{ARIA_TEMPLATE:"

",ITEM_TEMPLATE:"
  • ",LIST_TEMPLATE:"
      ",initializer:function(){var x=this.get("inputNode");if(!x){b.error("No inputNode specified.");return;}this._inputNode=x;this._listEvents=[];this.DEF_PARENT_NODE=x.get("parentNode");this[s]=this.getClassName(e);this[t]=this.getClassName(e,"active");this[d]=this.getClassName(e,"hover");this[u]="."+this[s];this.publish(n,{defaultFn:this._defSelectFn});},destructor:function(){while(this._listEvents.length){this._listEvents.pop().detach();}if(this._ariaNode){this._ariaNode.remove().destroy(true);}},bindUI:function(){this._bindInput();this._bindList();},renderUI:function(){var C=this._createAriaNode(),z=this.get("boundingBox"),y=this.get("contentBox"),B=this._inputNode,A,x=B.get("parentNode");A=this._createListNode();this._set("listNode",A);y.append(A);B.addClass(this.getClassName("input")).setAttrs({"aria-autocomplete":c,"aria-expanded":false,"aria-owns":A.get("id"),role:"combobox"});x.append(C);if(h){z.plug(b.Plugin.Shim);}z.setStyle("position","absolute");this._ariaNode=C;this._boundingBox=z;this._contentBox=y;this._listNode=A;this._parentNode=x;},syncUI:function(){this._syncResults();this._syncVisibility();},hide:function(){return this.get(k)?this:this.set(q,false);},selectItem:function(y,x){if(y){if(!y.hasClass(this[s])){return this;}}else{y=this.get(f);if(!y){return this;}}this.fire(n,{itemNode:y,originEvent:x||null,result:y.getData(w)});return this;},_activateNextItem:function(){var y=this.get(f),x;if(y){x=y.next(this[u])||(this.get(o)?null:y);}else{x=this._getFirstItemNode();}this.set(f,x);return this;},_activatePrevItem:function(){var y=this.get(f),x=y?y.previous(this[u]):this.get(o)&&this._getLastItemNode();this.set(f,x||null);return this;},_add:function(x){var y=[];l.each(i.isArray(x)?x:[x],function(z){y.push(this._createItemNode(z).setData(w,z));},this);y=b.all(y);this._listNode.append(y.toFrag());return y;},_ariaSay:function(z,x){var y=this.get("strings."+z);this._ariaNode.setContent(x?i.sub(y,x):y);},_bindInput:function(){var A=this._inputNode,y,z,x;if(this.get("align")===null){x=this.get("tokenInput");y=(x&&x.get("boundingBox"))||A;this.set("align",{node:y,points:["tl","bl"]});if(!this.get(g)&&(z=y.get("offsetWidth"))){this.set(g,z);}}this._listEvents.push(A.on("blur",this._onListInputBlur,this));},_bindList:function(){this._listEvents.concat([b.on("windowresize",this._syncPosition,this),this.after({mouseover:this._afterMouseOver,mouseout:this._afterMouseOut,activeItemChange:this._afterActiveItemChange,alwaysShowListChange:this._afterAlwaysShowListChange,hoveredItemChange:this._afterHoveredItemChange,resultsChange:this._afterResultsChange,visibleChange:this._afterVisibleChange}),this._listNode.delegate("click",this._onItemClick,this[u],this)]);},_clear:function(){this.set(f,null);this._set(r,null);this._listNode.get("children").remove(true);},_createAriaNode:function(){var x=v.create(this.ARIA_TEMPLATE);return x.addClass(this.getClassName("aria")).setAttrs({"aria-live":"polite",role:"status"});},_createItemNode:function(x){var y=v.create(this.ITEM_TEMPLATE);return y.addClass(this[s]).setAttrs({id:b.stamp(y),role:"option"}).setAttribute("data-text",x.text).append(x.display);},_createListNode:function(){var x=v.create(this.LIST_TEMPLATE);return x.addClass(this.getClassName(c)).setAttrs({id:b.stamp(x),role:"listbox"});},_getFirstItemNode:function(){return this._listNode.one(this[u]);},_getLastItemNode:function(){return this._listNode.one(this[u]+":last-child");},_syncPosition:function(){this._syncUIPosAlign();this._syncShim();},_syncResults:function(x){if(!x){x=this.get(j);}this._clear();if(x.length){this._add(x);this._ariaSay("items_available");}this._syncPosition();if(this.get("activateFirstItem")&&!this.get(f)){this.set(f,this._getFirstItemNode());}},_syncShim:h?function(){this._boundingBox.shim.sync();}:function(){},_syncVisibility:function(x){if(this.get(k)){x=true;this.set(q,x);}if(typeof x==="undefined"){x=this.get(q);}this._inputNode.set("aria-expanded",x);this._boundingBox.set("aria-hidden",!x);if(x){this._syncPosition();}else{this.set(f,null);this._set(r,null);this._boundingBox.get("offsetWidth");}},_afterActiveItemChange:function(z){var y=this._inputNode,x=z.newVal,A=z.prevVal;if(A&&A._node){A.removeClass(this[t]);}if(x){x.addClass(this[t]);y.set("aria-activedescendant",x.get(m));}else{y.removeAttribute("aria-activedescendant");}if(this.get("scrollIntoView")){(x||y).scrollIntoView();}},_afterAlwaysShowListChange:function(x){this.set(q,x.newVal||this.get(j).length>0);},_afterHoveredItemChange:function(y){var x=y.newVal,z=y.prevVal;if(z){z.removeClass(this[d]);}if(x){x.addClass(this[d]);}},_afterMouseOver:function(x){var y=x.domEvent.target.ancestor(this[u],true);this._mouseOverList=true;if(y){this._set(r,y);}},_afterMouseOut:function(){this._mouseOverList=false;this._set(r,null);},_afterResultsChange:function(x){this._syncResults(x.newVal);if(!this.get(k)){this.set(q,!!x.newVal.length);}},_afterVisibleChange:function(x){this._syncVisibility(!!x.newVal);},_onListInputBlur:function(x){if(!this._mouseOverList||this._lastInputKey===p){this.hide();}},_onItemClick:function(x){var y=x.currentTarget;this.set(f,y);this.selectItem(y,x);},_defSelectFn:function(x){var y=x.result.text;this._inputNode.focus();this._updateValue(y);this._ariaSay("item_selected",{item:y});this.hide();}},{ATTRS:{activateFirstItem:{value:false},activeItem:{setter:b.one,value:null},alwaysShowList:{value:false},circular:{value:true},hoveredItem:{readOnly:true,value:null},listNode:{readOnly:true,value:null},scrollIntoView:{value:false},strings:{valueFn:function(){return b.Intl.get("autocomplete-list"); -}},tabSelect:{value:true},visible:{value:false}},CSS_PREFIX:b.ClassNameManager.getClassName("aclist")});b.AutoCompleteList=a;b.AutoComplete=a;},"@VERSION@",{after:["autocomplete-sources"],lang:["en"],skinnable:true,requires:["autocomplete-base","event-resize","selector-css3","shim-plugin","widget","widget-position","widget-position-align"]}); \ No newline at end of file +}},tabSelect:{value:true},visible:{value:false}},CSS_PREFIX:b.ClassNameManager.getClassName("aclist")});b.AutoCompleteList=a;b.AutoComplete=a;},"@VERSION@",{requires:["autocomplete-base","event-resize","selector-css3","shim-plugin","widget","widget-position","widget-position-align"],after:["autocomplete-sources"],lang:["en"],skinnable:true}); \ No newline at end of file diff --git a/build/autocomplete-list/autocomplete-list.js b/build/autocomplete-list/autocomplete-list.js index 1a21c740890..c3ee968383e 100644 --- a/build/autocomplete-list/autocomplete-list.js +++ b/build/autocomplete-list/autocomplete-list.js @@ -830,4 +830,4 @@ Y.AutoCompleteList = List; Y.AutoComplete = List; -}, '@VERSION@' ,{after:['autocomplete-sources'], lang:['en'], skinnable:true, requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align']}); +}, '@VERSION@' ,{requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align'], after:['autocomplete-sources'], lang:['en'], skinnable:true}); diff --git a/build/autocomplete-sources/autocomplete-sources-debug.js b/build/autocomplete-sources/autocomplete-sources-debug.js index 94458110dec..ea7a8237068 100644 --- a/build/autocomplete-sources/autocomplete-sources-debug.js +++ b/build/autocomplete-sources/autocomplete-sources-debug.js @@ -477,4 +477,4 @@ Y.mix(ACBase.SOURCE_TYPES, { }, true); -}, '@VERSION@' ,{requires:['autocomplete-base'], optional:['io-base', 'json-parse', 'jsonp', 'yql']}); +}, '@VERSION@' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']}); diff --git a/build/autocomplete-sources/autocomplete-sources-min.js b/build/autocomplete-sources/autocomplete-sources-min.js index 83d8a316f90..1c6459c078b 100644 --- a/build/autocomplete-sources/autocomplete-sources-min.js +++ b/build/autocomplete-sources/autocomplete-sources-min.js @@ -1 +1 @@ -YUI.add("autocomplete-sources",function(g){var a=g.AutoCompleteBase,f=g.Lang,b="_sourceSuccess",d="maxResults",e="requestTemplate",c="resultListLocator";g.mix(a.prototype,{_YQL_SOURCE_REGEX:/^(?:select|set|use)\s+/i,_beforeCreateObjectSource:function(h){if(h instanceof g.Node&&h.get("nodeName").toLowerCase()==="select"){return this._createSelectSource(h);}if(g.JSONPRequest&&h instanceof g.JSONPRequest){return this._createJSONPSource(h);}return this._createObjectSource(h);},_createIOSource:function(m){var j={},k={type:"io"},l=this,o,i,n;function h(p){var q=p.query;if(j[q]){l[b](j[q],p);return;}if(o&&o.isInProgress()){o.abort();}o=g.io(l._getXHRUrl(m,q),{on:{success:function(u,r){var t;try{t=g.JSON.parse(r.responseText);}catch(s){g.error("JSON parse error",s);}if(t){j[q]=t;l[b](t,p);}}}});}k.sendRequest=function(p){i=p;if(n){return;}n=true;g.use("io-base","json-parse",function(){k.sendRequest=h;h(i);});};return k;},_createJSONPSource:function(m){var j={},k={type:"jsonp"},l=this,i,n;function h(o){var p=o.query;if(j[p]){l[b](j[p],o);return;}m._config.on.success=function(q){j[p]=q;l[b](q,o);};m.send(p);}k.sendRequest=function(o){i=o;if(n){return;}n=true;g.use("jsonp",function(){if(!(m instanceof g.JSONPRequest)){m=new g.JSONPRequest(m,{format:g.bind(l._jsonpFormatter,l)});}k.sendRequest=h;h(i);});};return k;},_createSelectSource:function(i){var h=this;return{type:"select",sendRequest:function(k){var j=[];i.get("options").each(function(l){j.push({html:l.get("innerHTML"),index:l.get("index"),node:l,selected:l.get("selected"),text:l.get("text"),value:l.get("value")});});h[b](j,k);}};},_createStringSource:function(h){if(this._YQL_SOURCE_REGEX.test(h)){return this._createYQLSource(h);}else{if(h.indexOf("{callback}")!==-1){return this._createJSONPSource(h);}else{return this._createIOSource(h);}}},_createYQLSource:function(m){var j={},n={type:"yql"},l=this,i,o,k;if(!this.get(c)){this.set(c,this._defaultYQLLocator);}function h(t){var u=t.query,v,r,p,s,q;if(j[u]){l[b](j[u],t);return;}v=function(w){j[u]=w;l[b](w,t);};r=l.get("yqlEnv");p=l.get(d);s={proto:l.get("yqlProtocol")};q=f.sub(m,{maxResults:p>0?p:1000,query:u});if(k){k._callback=v;k._opts=s;k._params.q=q;if(r){k._params.env=r;}}else{k=new g.YQLRequest(q,{on:{success:v},allowCache:false},r?{env:r}:null,s);}k.send();}n.sendRequest=function(p){i=p;if(!o){o=true;g.use("yql",function(){n.sendRequest=h;h(i);});}};return n;},_defaultYQLLocator:function(i){var j=i&&i.query&&i.query.results,h;if(j&&f.isObject(j)){h=g.Object.values(j)||[];j=h.length===1?h[0]:h;if(!f.isArray(j)){j=[j];}}else{j=[];}return j;},_getXHRUrl:function(i,j){var h=this.get(d),k=this.get(e);if(k){i+=k(j);}return f.sub(i,{maxResults:h>0?h:1000,query:encodeURIComponent(j)});},_jsonpFormatter:function(i,j,k){var h=this.get(d),l=this.get(e);if(l){i+=l(k);}return f.sub(i,{callback:j,maxResults:h>0?h:1000,query:encodeURIComponent(k)});}});g.mix(a.ATTRS,{yqlEnv:{value:null},yqlProtocol:{value:"http"}});g.mix(a.SOURCE_TYPES,{io:"_createIOSource",jsonp:"_createJSONPSource",object:"_beforeCreateObjectSource",select:"_createSelectSource",string:"_createStringSource",yql:"_createYQLSource"},true);},"@VERSION@",{requires:["autocomplete-base"],optional:["io-base","json-parse","jsonp","yql"]}); \ No newline at end of file +YUI.add("autocomplete-sources",function(g){var a=g.AutoCompleteBase,f=g.Lang,b="_sourceSuccess",d="maxResults",e="requestTemplate",c="resultListLocator";g.mix(a.prototype,{_YQL_SOURCE_REGEX:/^(?:select|set|use)\s+/i,_beforeCreateObjectSource:function(h){if(h instanceof g.Node&&h.get("nodeName").toLowerCase()==="select"){return this._createSelectSource(h);}if(g.JSONPRequest&&h instanceof g.JSONPRequest){return this._createJSONPSource(h);}return this._createObjectSource(h);},_createIOSource:function(m){var j={},k={type:"io"},l=this,o,i,n;function h(p){var q=p.query;if(j[q]){l[b](j[q],p);return;}if(o&&o.isInProgress()){o.abort();}o=g.io(l._getXHRUrl(m,q),{on:{success:function(u,r){var t;try{t=g.JSON.parse(r.responseText);}catch(s){g.error("JSON parse error",s);}if(t){j[q]=t;l[b](t,p);}}}});}k.sendRequest=function(p){i=p;if(n){return;}n=true;g.use("io-base","json-parse",function(){k.sendRequest=h;h(i);});};return k;},_createJSONPSource:function(m){var j={},k={type:"jsonp"},l=this,i,n;function h(o){var p=o.query;if(j[p]){l[b](j[p],o);return;}m._config.on.success=function(q){j[p]=q;l[b](q,o);};m.send(p);}k.sendRequest=function(o){i=o;if(n){return;}n=true;g.use("jsonp",function(){if(!(m instanceof g.JSONPRequest)){m=new g.JSONPRequest(m,{format:g.bind(l._jsonpFormatter,l)});}k.sendRequest=h;h(i);});};return k;},_createSelectSource:function(i){var h=this;return{type:"select",sendRequest:function(k){var j=[];i.get("options").each(function(l){j.push({html:l.get("innerHTML"),index:l.get("index"),node:l,selected:l.get("selected"),text:l.get("text"),value:l.get("value")});});h[b](j,k);}};},_createStringSource:function(h){if(this._YQL_SOURCE_REGEX.test(h)){return this._createYQLSource(h);}else{if(h.indexOf("{callback}")!==-1){return this._createJSONPSource(h);}else{return this._createIOSource(h);}}},_createYQLSource:function(m){var j={},n={type:"yql"},l=this,i,o,k;if(!this.get(c)){this.set(c,this._defaultYQLLocator);}function h(t){var u=t.query,v,r,p,s,q;if(j[u]){l[b](j[u],t);return;}v=function(w){j[u]=w;l[b](w,t);};r=l.get("yqlEnv");p=l.get(d);s={proto:l.get("yqlProtocol")};q=f.sub(m,{maxResults:p>0?p:1000,query:u});if(k){k._callback=v;k._opts=s;k._params.q=q;if(r){k._params.env=r;}}else{k=new g.YQLRequest(q,{on:{success:v},allowCache:false},r?{env:r}:null,s);}k.send();}n.sendRequest=function(p){i=p;if(!o){o=true;g.use("yql",function(){n.sendRequest=h;h(i);});}};return n;},_defaultYQLLocator:function(i){var j=i&&i.query&&i.query.results,h;if(j&&f.isObject(j)){h=g.Object.values(j)||[];j=h.length===1?h[0]:h;if(!f.isArray(j)){j=[j];}}else{j=[];}return j;},_getXHRUrl:function(i,j){var h=this.get(d),k=this.get(e);if(k){i+=k(j);}return f.sub(i,{maxResults:h>0?h:1000,query:encodeURIComponent(j)});},_jsonpFormatter:function(i,j,k){var h=this.get(d),l=this.get(e);if(l){i+=l(k);}return f.sub(i,{callback:j,maxResults:h>0?h:1000,query:encodeURIComponent(k)});}});g.mix(a.ATTRS,{yqlEnv:{value:null},yqlProtocol:{value:"http"}});g.mix(a.SOURCE_TYPES,{io:"_createIOSource",jsonp:"_createJSONPSource",object:"_beforeCreateObjectSource",select:"_createSelectSource",string:"_createStringSource",yql:"_createYQLSource"},true);},"@VERSION@",{optional:["io-base","json-parse","jsonp","yql"],requires:["autocomplete-base"]}); \ No newline at end of file diff --git a/build/autocomplete-sources/autocomplete-sources.js b/build/autocomplete-sources/autocomplete-sources.js index 94458110dec..ea7a8237068 100644 --- a/build/autocomplete-sources/autocomplete-sources.js +++ b/build/autocomplete-sources/autocomplete-sources.js @@ -477,4 +477,4 @@ Y.mix(ACBase.SOURCE_TYPES, { }, true); -}, '@VERSION@' ,{requires:['autocomplete-base'], optional:['io-base', 'json-parse', 'jsonp', 'yql']}); +}, '@VERSION@' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']}); diff --git a/build/autocomplete/autocomplete-debug.js b/build/autocomplete/autocomplete-debug.js index 0312c48b534..261b8792d6b 100644 --- a/build/autocomplete/autocomplete-debug.js +++ b/build/autocomplete/autocomplete-debug.js @@ -1628,7 +1628,7 @@ AutoCompleteBase.prototype = { Y.AutoCompleteBase = AutoCompleteBase; -}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base'], optional:['autocomplete-sources']}); +}, '@VERSION@' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']}); YUI.add('autocomplete-sources', function(Y) { /** @@ -2108,7 +2108,7 @@ Y.mix(ACBase.SOURCE_TYPES, { }, true); -}, '@VERSION@' ,{requires:['autocomplete-base'], optional:['io-base', 'json-parse', 'jsonp', 'yql']}); +}, '@VERSION@' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']}); YUI.add('autocomplete-list', function(Y) { /** @@ -2941,7 +2941,7 @@ Y.AutoCompleteList = List; Y.AutoComplete = List; -}, '@VERSION@' ,{after:['autocomplete-sources'], lang:['en'], skinnable:true, requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align']}); +}, '@VERSION@' ,{requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align'], after:['autocomplete-sources'], lang:['en'], skinnable:true}); YUI.add('autocomplete-plugin', function(Y) { /** diff --git a/build/autocomplete/autocomplete-min.js b/build/autocomplete/autocomplete-min.js index 1624410c975..3bfaee2c27e 100644 --- a/build/autocomplete/autocomplete-min.js +++ b/build/autocomplete/autocomplete-min.js @@ -1,3 +1,3 @@ YUI.add("autocomplete-base",function(f){var g=f.Escape,j=f.Lang,q=f.Array,i=f.Object,d=j.isFunction,r=j.isString,u=j.trim,l=f.Attribute.INVALID_VALUE,o="_functionValidator",x="_sourceSuccess",c="allowBrowserAutocomplete",h="inputNode",w="query",e="queryDelimiter",b="requestTemplate",m="results",n="resultListLocator",k="value",s="valueChange",a="clear",t=w,p=m;function v(){f.before(this._bindUIACBase,this,"bindUI");f.before(this._destructorACBase,this,"destructor");f.before(this._syncUIACBase,this,"syncUI");this.publish(a,{defaultFn:this._defClearFn});this.publish(t,{defaultFn:this._defQueryFn});this.publish(p,{defaultFn:this._defResultsFn});}v.ATTRS={allowBrowserAutocomplete:{value:false},allowTrailingDelimiter:{value:false},inputNode:{setter:f.one,writeOnce:"initOnly"},maxResults:{value:0},minQueryLength:{value:1},query:{readOnly:true,value:null},queryDelay:{value:100},queryDelimiter:{value:null},requestTemplate:{setter:"_setRequestTemplate",value:null},resultFilters:{setter:"_setResultFilters",value:[]},resultFormatter:{validator:o},resultHighlighter:{setter:"_setResultHighlighter"},resultListLocator:{setter:"_setLocator"},results:{readOnly:true,value:[]},resultTextLocator:{setter:"_setLocator"},source:{setter:"_setSource"},sourceType:{value:null},tokenInput:{readOnly:true},value:{value:""}};v.CSS_PREFIX="ac";v.UI_SRC=(f.Widget&&f.Widget.UI_SRC)||"ui";v.SOURCE_TYPES={array:"_createArraySource","function":"_createFunctionSource",object:"_createObjectSource"};v.prototype={sendRequest:function(A,B){var y,z=this.get("source");if(A||A===""){this._set(w,A);}else{A=this.get(w);}if(z){if(!B){B=this.get(b);}y=B?B(A):A;z.sendRequest({query:A,request:y,callback:{success:f.bind(this._onResponse,this,A)}});}return this;},_bindUIACBase:function(){var z=this.get(h),y=z&&z.tokenInput;if(y){z=y.get(h);this._set("tokenInput",y);}if(!z){f.error("No inputNode specified.");return;}this._inputNode=z;this._acBaseEvents=new f.EventHandle([z.on(s,this._onInputValueChange,this),z.on("blur",this._onInputBlur,this),this.after(c+"Change",this._syncBrowserAutocomplete),this.after("sourceTypeChange",this._afterSourceTypeChange),this.after(s,this._afterValueChange)]);},_destructorACBase:function(){this._acBaseEvents.detach();},_syncUIACBase:function(){this._syncBrowserAutocomplete();this.set(k,this.get(h).get(k));},_createArraySource:function(z){var y=this;return{type:"array",sendRequest:function(A){y[x](z.concat(),A);}};},_createFunctionSource:function(z){var y=this;return{type:"function",sendRequest:function(A){var B;function C(D){y[x](D||[],A);}if((B=z(A.query,C))){C(B);}}};},_createObjectSource:function(z){var y=this;return{type:"object",sendRequest:function(A){var B=A.query;y[x](i.owns(z,B)?z[B]:[],A);}};},_functionValidator:function(y){return y===null||d(y);},_getObjectValue:function(B,A){if(!B){return;}for(var z=0,y=A.length;B&&z0&&H.length>M){H.length=M;}if(O){B=O(A,H.concat());if(!B){return;}for(K=0,L=B.length;K1){C[y-1]=z;z=C.join(A+" ");}z=z+A+" ";}this.set(k,z);},_afterSourceTypeChange:function(y){if(this._rawSource){this.set("source",this._rawSource);}},_afterValueChange:function(E){var A,B,y,z=E.newVal,D,C;if(E.src!==v.UI_SRC){this._inputNode.set(k,z);return;}y=this.get("minQueryLength");D=this._parseValue(z)||"";if(y>=0&&D.length>=y){A=this.get("queryDelay");C=this;B=function(){C.fire(t,{inputValue:z,query:D});};if(A){clearTimeout(this._delay);this._delay=setTimeout(B,A);}else{B();}}else{clearTimeout(this._delay);this.fire(a,{prevVal:E.prevVal?this._parseValue(E.prevVal):null});}},_onInputBlur:function(B){var C=this.get(e),y,z,A;if(C&&!this.get("allowTrailingDelimiter")){C=j.trimRight(C);A=z=this._inputNode.get(k);if(C){while((z=j.trimRight(z))&&(y=z.length-C.length)&&z.lastIndexOf(C)===y){z=z.substring(0,y);}}else{z=j.trimRight(z); -}if(z!==A){this.set(k,z);}}},_onInputValueChange:function(z){var y=z.newVal;if(y===this.get(k)){return;}this.set(k,y,{src:v.UI_SRC});},_onResponse:function(y,z){if(y===this.get(w)){this._parseResponse(y,z.response,z.data);}},_defClearFn:function(){this._set(w,null);this._set(m,[]);},_defQueryFn:function(z){var y=z.query;this.sendRequest(y);},_defResultsFn:function(y){this._set(m,y[m]);}};f.AutoCompleteBase=v;},"@VERSION@",{requires:["array-extras","base-build","escape","event-valuechange","node-base"],optional:["autocomplete-sources"]});YUI.add("autocomplete-sources",function(g){var a=g.AutoCompleteBase,f=g.Lang,b="_sourceSuccess",d="maxResults",e="requestTemplate",c="resultListLocator";g.mix(a.prototype,{_YQL_SOURCE_REGEX:/^(?:select|set|use)\s+/i,_beforeCreateObjectSource:function(h){if(h instanceof g.Node&&h.get("nodeName").toLowerCase()==="select"){return this._createSelectSource(h);}if(g.JSONPRequest&&h instanceof g.JSONPRequest){return this._createJSONPSource(h);}return this._createObjectSource(h);},_createIOSource:function(m){var j={},k={type:"io"},l=this,o,i,n;function h(p){var q=p.query;if(j[q]){l[b](j[q],p);return;}if(o&&o.isInProgress()){o.abort();}o=g.io(l._getXHRUrl(m,q),{on:{success:function(u,r){var t;try{t=g.JSON.parse(r.responseText);}catch(s){g.error("JSON parse error",s);}if(t){j[q]=t;l[b](t,p);}}}});}k.sendRequest=function(p){i=p;if(n){return;}n=true;g.use("io-base","json-parse",function(){k.sendRequest=h;h(i);});};return k;},_createJSONPSource:function(m){var j={},k={type:"jsonp"},l=this,i,n;function h(o){var p=o.query;if(j[p]){l[b](j[p],o);return;}m._config.on.success=function(q){j[p]=q;l[b](q,o);};m.send(p);}k.sendRequest=function(o){i=o;if(n){return;}n=true;g.use("jsonp",function(){if(!(m instanceof g.JSONPRequest)){m=new g.JSONPRequest(m,{format:g.bind(l._jsonpFormatter,l)});}k.sendRequest=h;h(i);});};return k;},_createSelectSource:function(i){var h=this;return{type:"select",sendRequest:function(k){var j=[];i.get("options").each(function(l){j.push({html:l.get("innerHTML"),index:l.get("index"),node:l,selected:l.get("selected"),text:l.get("text"),value:l.get("value")});});h[b](j,k);}};},_createStringSource:function(h){if(this._YQL_SOURCE_REGEX.test(h)){return this._createYQLSource(h);}else{if(h.indexOf("{callback}")!==-1){return this._createJSONPSource(h);}else{return this._createIOSource(h);}}},_createYQLSource:function(m){var j={},n={type:"yql"},l=this,i,o,k;if(!this.get(c)){this.set(c,this._defaultYQLLocator);}function h(t){var u=t.query,v,r,p,s,q;if(j[u]){l[b](j[u],t);return;}v=function(w){j[u]=w;l[b](w,t);};r=l.get("yqlEnv");p=l.get(d);s={proto:l.get("yqlProtocol")};q=f.sub(m,{maxResults:p>0?p:1000,query:u});if(k){k._callback=v;k._opts=s;k._params.q=q;if(r){k._params.env=r;}}else{k=new g.YQLRequest(q,{on:{success:v},allowCache:false},r?{env:r}:null,s);}k.send();}n.sendRequest=function(p){i=p;if(!o){o=true;g.use("yql",function(){n.sendRequest=h;h(i);});}};return n;},_defaultYQLLocator:function(i){var j=i&&i.query&&i.query.results,h;if(j&&f.isObject(j)){h=g.Object.values(j)||[];j=h.length===1?h[0]:h;if(!f.isArray(j)){j=[j];}}else{j=[];}return j;},_getXHRUrl:function(i,j){var h=this.get(d),k=this.get(e);if(k){i+=k(j);}return f.sub(i,{maxResults:h>0?h:1000,query:encodeURIComponent(j)});},_jsonpFormatter:function(i,j,k){var h=this.get(d),l=this.get(e);if(l){i+=l(k);}return f.sub(i,{callback:j,maxResults:h>0?h:1000,query:encodeURIComponent(k)});}});g.mix(a.ATTRS,{yqlEnv:{value:null},yqlProtocol:{value:"http"}});g.mix(a.SOURCE_TYPES,{io:"_createIOSource",jsonp:"_createJSONPSource",object:"_beforeCreateObjectSource",select:"_createSelectSource",string:"_createStringSource",yql:"_createYQLSource"},true);},"@VERSION@",{requires:["autocomplete-base"],optional:["io-base","json-parse","jsonp","yql"]});YUI.add("autocomplete-list",function(b){var i=b.Lang,v=b.Node,l=b.Array,h=b.UA.ie&&b.UA.ie<7,p=9,s="_CLASS_ITEM",t="_CLASS_ITEM_ACTIVE",d="_CLASS_ITEM_HOVER",u="_SELECTOR_ITEM",f="activeItem",k="alwaysShowList",o="circular",r="hoveredItem",m="id",e="item",c="list",w="result",j="results",q="visible",g="width",n="select",a=b.Base.create("autocompleteList",b.Widget,[b.AutoCompleteBase,b.WidgetPosition,b.WidgetPositionAlign],{ARIA_TEMPLATE:"
      ",ITEM_TEMPLATE:"
    • ",LIST_TEMPLATE:"
        ",initializer:function(){var x=this.get("inputNode");if(!x){b.error("No inputNode specified.");return;}this._inputNode=x;this._listEvents=[];this.DEF_PARENT_NODE=x.get("parentNode");this[s]=this.getClassName(e);this[t]=this.getClassName(e,"active");this[d]=this.getClassName(e,"hover");this[u]="."+this[s];this.publish(n,{defaultFn:this._defSelectFn});},destructor:function(){while(this._listEvents.length){this._listEvents.pop().detach();}if(this._ariaNode){this._ariaNode.remove().destroy(true);}},bindUI:function(){this._bindInput();this._bindList();},renderUI:function(){var C=this._createAriaNode(),z=this.get("boundingBox"),y=this.get("contentBox"),B=this._inputNode,A,x=B.get("parentNode");A=this._createListNode();this._set("listNode",A);y.append(A);B.addClass(this.getClassName("input")).setAttrs({"aria-autocomplete":c,"aria-expanded":false,"aria-owns":A.get("id"),role:"combobox"});x.append(C);if(h){z.plug(b.Plugin.Shim);}z.setStyle("position","absolute");this._ariaNode=C;this._boundingBox=z;this._contentBox=y;this._listNode=A;this._parentNode=x;},syncUI:function(){this._syncResults();this._syncVisibility();},hide:function(){return this.get(k)?this:this.set(q,false);},selectItem:function(y,x){if(y){if(!y.hasClass(this[s])){return this;}}else{y=this.get(f);if(!y){return this;}}this.fire(n,{itemNode:y,originEvent:x||null,result:y.getData(w)});return this;},_activateNextItem:function(){var y=this.get(f),x;if(y){x=y.next(this[u])||(this.get(o)?null:y);}else{x=this._getFirstItemNode();}this.set(f,x);return this;},_activatePrevItem:function(){var y=this.get(f),x=y?y.previous(this[u]):this.get(o)&&this._getLastItemNode();this.set(f,x||null);return this;},_add:function(x){var y=[];l.each(i.isArray(x)?x:[x],function(z){y.push(this._createItemNode(z).setData(w,z)); -},this);y=b.all(y);this._listNode.append(y.toFrag());return y;},_ariaSay:function(z,x){var y=this.get("strings."+z);this._ariaNode.setContent(x?i.sub(y,x):y);},_bindInput:function(){var A=this._inputNode,y,z,x;if(this.get("align")===null){x=this.get("tokenInput");y=(x&&x.get("boundingBox"))||A;this.set("align",{node:y,points:["tl","bl"]});if(!this.get(g)&&(z=y.get("offsetWidth"))){this.set(g,z);}}this._listEvents.push(A.on("blur",this._onListInputBlur,this));},_bindList:function(){this._listEvents.concat([b.on("windowresize",this._syncPosition,this),this.after({mouseover:this._afterMouseOver,mouseout:this._afterMouseOut,activeItemChange:this._afterActiveItemChange,alwaysShowListChange:this._afterAlwaysShowListChange,hoveredItemChange:this._afterHoveredItemChange,resultsChange:this._afterResultsChange,visibleChange:this._afterVisibleChange}),this._listNode.delegate("click",this._onItemClick,this[u],this)]);},_clear:function(){this.set(f,null);this._set(r,null);this._listNode.get("children").remove(true);},_createAriaNode:function(){var x=v.create(this.ARIA_TEMPLATE);return x.addClass(this.getClassName("aria")).setAttrs({"aria-live":"polite",role:"status"});},_createItemNode:function(x){var y=v.create(this.ITEM_TEMPLATE);return y.addClass(this[s]).setAttrs({id:b.stamp(y),role:"option"}).setAttribute("data-text",x.text).append(x.display);},_createListNode:function(){var x=v.create(this.LIST_TEMPLATE);return x.addClass(this.getClassName(c)).setAttrs({id:b.stamp(x),role:"listbox"});},_getFirstItemNode:function(){return this._listNode.one(this[u]);},_getLastItemNode:function(){return this._listNode.one(this[u]+":last-child");},_syncPosition:function(){this._syncUIPosAlign();this._syncShim();},_syncResults:function(x){if(!x){x=this.get(j);}this._clear();if(x.length){this._add(x);this._ariaSay("items_available");}this._syncPosition();if(this.get("activateFirstItem")&&!this.get(f)){this.set(f,this._getFirstItemNode());}},_syncShim:h?function(){this._boundingBox.shim.sync();}:function(){},_syncVisibility:function(x){if(this.get(k)){x=true;this.set(q,x);}if(typeof x==="undefined"){x=this.get(q);}this._inputNode.set("aria-expanded",x);this._boundingBox.set("aria-hidden",!x);if(x){this._syncPosition();}else{this.set(f,null);this._set(r,null);this._boundingBox.get("offsetWidth");}},_afterActiveItemChange:function(z){var y=this._inputNode,x=z.newVal,A=z.prevVal;if(A&&A._node){A.removeClass(this[t]);}if(x){x.addClass(this[t]);y.set("aria-activedescendant",x.get(m));}else{y.removeAttribute("aria-activedescendant");}if(this.get("scrollIntoView")){(x||y).scrollIntoView();}},_afterAlwaysShowListChange:function(x){this.set(q,x.newVal||this.get(j).length>0);},_afterHoveredItemChange:function(y){var x=y.newVal,z=y.prevVal;if(z){z.removeClass(this[d]);}if(x){x.addClass(this[d]);}},_afterMouseOver:function(x){var y=x.domEvent.target.ancestor(this[u],true);this._mouseOverList=true;if(y){this._set(r,y);}},_afterMouseOut:function(){this._mouseOverList=false;this._set(r,null);},_afterResultsChange:function(x){this._syncResults(x.newVal);if(!this.get(k)){this.set(q,!!x.newVal.length);}},_afterVisibleChange:function(x){this._syncVisibility(!!x.newVal);},_onListInputBlur:function(x){if(!this._mouseOverList||this._lastInputKey===p){this.hide();}},_onItemClick:function(x){var y=x.currentTarget;this.set(f,y);this.selectItem(y,x);},_defSelectFn:function(x){var y=x.result.text;this._inputNode.focus();this._updateValue(y);this._ariaSay("item_selected",{item:y});this.hide();}},{ATTRS:{activateFirstItem:{value:false},activeItem:{setter:b.one,value:null},alwaysShowList:{value:false},circular:{value:true},hoveredItem:{readOnly:true,value:null},listNode:{readOnly:true,value:null},scrollIntoView:{value:false},strings:{valueFn:function(){return b.Intl.get("autocomplete-list");}},tabSelect:{value:true},visible:{value:false}},CSS_PREFIX:b.ClassNameManager.getClassName("aclist")});b.AutoCompleteList=a;b.AutoComplete=a;},"@VERSION@",{after:["autocomplete-sources"],lang:["en"],skinnable:true,requires:["autocomplete-base","event-resize","selector-css3","shim-plugin","widget","widget-position","widget-position-align"]});YUI.add("autocomplete-plugin",function(b){var a=b.Plugin;function c(d){d.inputNode=d.host;if(!d.render&&d.render!==false){d.render=true;}c.superclass.constructor.apply(this,arguments);}b.extend(c,b.AutoCompleteList,{},{NAME:"autocompleteListPlugin",NS:"ac",CSS_PREFIX:b.ClassNameManager.getClassName("aclist")});a.AutoComplete=c;a.AutoCompleteList=c;},"@VERSION@",{requires:["autocomplete-list","node-pluginhost"]});YUI.add("autocomplete",function(a){},"@VERSION@",{use:["autocomplete-base","autocomplete-sources","autocomplete-list","autocomplete-plugin"]}); \ No newline at end of file +}if(z!==A){this.set(k,z);}}},_onInputValueChange:function(z){var y=z.newVal;if(y===this.get(k)){return;}this.set(k,y,{src:v.UI_SRC});},_onResponse:function(y,z){if(y===this.get(w)){this._parseResponse(y,z.response,z.data);}},_defClearFn:function(){this._set(w,null);this._set(m,[]);},_defQueryFn:function(z){var y=z.query;this.sendRequest(y);},_defResultsFn:function(y){this._set(m,y[m]);}};f.AutoCompleteBase=v;},"@VERSION@",{optional:["autocomplete-sources"],requires:["array-extras","base-build","escape","event-valuechange","node-base"]});YUI.add("autocomplete-sources",function(g){var a=g.AutoCompleteBase,f=g.Lang,b="_sourceSuccess",d="maxResults",e="requestTemplate",c="resultListLocator";g.mix(a.prototype,{_YQL_SOURCE_REGEX:/^(?:select|set|use)\s+/i,_beforeCreateObjectSource:function(h){if(h instanceof g.Node&&h.get("nodeName").toLowerCase()==="select"){return this._createSelectSource(h);}if(g.JSONPRequest&&h instanceof g.JSONPRequest){return this._createJSONPSource(h);}return this._createObjectSource(h);},_createIOSource:function(m){var j={},k={type:"io"},l=this,o,i,n;function h(p){var q=p.query;if(j[q]){l[b](j[q],p);return;}if(o&&o.isInProgress()){o.abort();}o=g.io(l._getXHRUrl(m,q),{on:{success:function(u,r){var t;try{t=g.JSON.parse(r.responseText);}catch(s){g.error("JSON parse error",s);}if(t){j[q]=t;l[b](t,p);}}}});}k.sendRequest=function(p){i=p;if(n){return;}n=true;g.use("io-base","json-parse",function(){k.sendRequest=h;h(i);});};return k;},_createJSONPSource:function(m){var j={},k={type:"jsonp"},l=this,i,n;function h(o){var p=o.query;if(j[p]){l[b](j[p],o);return;}m._config.on.success=function(q){j[p]=q;l[b](q,o);};m.send(p);}k.sendRequest=function(o){i=o;if(n){return;}n=true;g.use("jsonp",function(){if(!(m instanceof g.JSONPRequest)){m=new g.JSONPRequest(m,{format:g.bind(l._jsonpFormatter,l)});}k.sendRequest=h;h(i);});};return k;},_createSelectSource:function(i){var h=this;return{type:"select",sendRequest:function(k){var j=[];i.get("options").each(function(l){j.push({html:l.get("innerHTML"),index:l.get("index"),node:l,selected:l.get("selected"),text:l.get("text"),value:l.get("value")});});h[b](j,k);}};},_createStringSource:function(h){if(this._YQL_SOURCE_REGEX.test(h)){return this._createYQLSource(h);}else{if(h.indexOf("{callback}")!==-1){return this._createJSONPSource(h);}else{return this._createIOSource(h);}}},_createYQLSource:function(m){var j={},n={type:"yql"},l=this,i,o,k;if(!this.get(c)){this.set(c,this._defaultYQLLocator);}function h(t){var u=t.query,v,r,p,s,q;if(j[u]){l[b](j[u],t);return;}v=function(w){j[u]=w;l[b](w,t);};r=l.get("yqlEnv");p=l.get(d);s={proto:l.get("yqlProtocol")};q=f.sub(m,{maxResults:p>0?p:1000,query:u});if(k){k._callback=v;k._opts=s;k._params.q=q;if(r){k._params.env=r;}}else{k=new g.YQLRequest(q,{on:{success:v},allowCache:false},r?{env:r}:null,s);}k.send();}n.sendRequest=function(p){i=p;if(!o){o=true;g.use("yql",function(){n.sendRequest=h;h(i);});}};return n;},_defaultYQLLocator:function(i){var j=i&&i.query&&i.query.results,h;if(j&&f.isObject(j)){h=g.Object.values(j)||[];j=h.length===1?h[0]:h;if(!f.isArray(j)){j=[j];}}else{j=[];}return j;},_getXHRUrl:function(i,j){var h=this.get(d),k=this.get(e);if(k){i+=k(j);}return f.sub(i,{maxResults:h>0?h:1000,query:encodeURIComponent(j)});},_jsonpFormatter:function(i,j,k){var h=this.get(d),l=this.get(e);if(l){i+=l(k);}return f.sub(i,{callback:j,maxResults:h>0?h:1000,query:encodeURIComponent(k)});}});g.mix(a.ATTRS,{yqlEnv:{value:null},yqlProtocol:{value:"http"}});g.mix(a.SOURCE_TYPES,{io:"_createIOSource",jsonp:"_createJSONPSource",object:"_beforeCreateObjectSource",select:"_createSelectSource",string:"_createStringSource",yql:"_createYQLSource"},true);},"@VERSION@",{optional:["io-base","json-parse","jsonp","yql"],requires:["autocomplete-base"]});YUI.add("autocomplete-list",function(b){var i=b.Lang,v=b.Node,l=b.Array,h=b.UA.ie&&b.UA.ie<7,p=9,s="_CLASS_ITEM",t="_CLASS_ITEM_ACTIVE",d="_CLASS_ITEM_HOVER",u="_SELECTOR_ITEM",f="activeItem",k="alwaysShowList",o="circular",r="hoveredItem",m="id",e="item",c="list",w="result",j="results",q="visible",g="width",n="select",a=b.Base.create("autocompleteList",b.Widget,[b.AutoCompleteBase,b.WidgetPosition,b.WidgetPositionAlign],{ARIA_TEMPLATE:"
        ",ITEM_TEMPLATE:"
      • ",LIST_TEMPLATE:"
          ",initializer:function(){var x=this.get("inputNode");if(!x){b.error("No inputNode specified.");return;}this._inputNode=x;this._listEvents=[];this.DEF_PARENT_NODE=x.get("parentNode");this[s]=this.getClassName(e);this[t]=this.getClassName(e,"active");this[d]=this.getClassName(e,"hover");this[u]="."+this[s];this.publish(n,{defaultFn:this._defSelectFn});},destructor:function(){while(this._listEvents.length){this._listEvents.pop().detach();}if(this._ariaNode){this._ariaNode.remove().destroy(true);}},bindUI:function(){this._bindInput();this._bindList();},renderUI:function(){var C=this._createAriaNode(),z=this.get("boundingBox"),y=this.get("contentBox"),B=this._inputNode,A,x=B.get("parentNode");A=this._createListNode();this._set("listNode",A);y.append(A);B.addClass(this.getClassName("input")).setAttrs({"aria-autocomplete":c,"aria-expanded":false,"aria-owns":A.get("id"),role:"combobox"});x.append(C);if(h){z.plug(b.Plugin.Shim);}z.setStyle("position","absolute");this._ariaNode=C;this._boundingBox=z;this._contentBox=y;this._listNode=A;this._parentNode=x;},syncUI:function(){this._syncResults();this._syncVisibility();},hide:function(){return this.get(k)?this:this.set(q,false);},selectItem:function(y,x){if(y){if(!y.hasClass(this[s])){return this;}}else{y=this.get(f);if(!y){return this;}}this.fire(n,{itemNode:y,originEvent:x||null,result:y.getData(w)});return this;},_activateNextItem:function(){var y=this.get(f),x;if(y){x=y.next(this[u])||(this.get(o)?null:y);}else{x=this._getFirstItemNode();}this.set(f,x);return this;},_activatePrevItem:function(){var y=this.get(f),x=y?y.previous(this[u]):this.get(o)&&this._getLastItemNode();this.set(f,x||null);return this;},_add:function(x){var y=[];l.each(i.isArray(x)?x:[x],function(z){y.push(this._createItemNode(z).setData(w,z)); +},this);y=b.all(y);this._listNode.append(y.toFrag());return y;},_ariaSay:function(z,x){var y=this.get("strings."+z);this._ariaNode.setContent(x?i.sub(y,x):y);},_bindInput:function(){var A=this._inputNode,y,z,x;if(this.get("align")===null){x=this.get("tokenInput");y=(x&&x.get("boundingBox"))||A;this.set("align",{node:y,points:["tl","bl"]});if(!this.get(g)&&(z=y.get("offsetWidth"))){this.set(g,z);}}this._listEvents.push(A.on("blur",this._onListInputBlur,this));},_bindList:function(){this._listEvents.concat([b.on("windowresize",this._syncPosition,this),this.after({mouseover:this._afterMouseOver,mouseout:this._afterMouseOut,activeItemChange:this._afterActiveItemChange,alwaysShowListChange:this._afterAlwaysShowListChange,hoveredItemChange:this._afterHoveredItemChange,resultsChange:this._afterResultsChange,visibleChange:this._afterVisibleChange}),this._listNode.delegate("click",this._onItemClick,this[u],this)]);},_clear:function(){this.set(f,null);this._set(r,null);this._listNode.get("children").remove(true);},_createAriaNode:function(){var x=v.create(this.ARIA_TEMPLATE);return x.addClass(this.getClassName("aria")).setAttrs({"aria-live":"polite",role:"status"});},_createItemNode:function(x){var y=v.create(this.ITEM_TEMPLATE);return y.addClass(this[s]).setAttrs({id:b.stamp(y),role:"option"}).setAttribute("data-text",x.text).append(x.display);},_createListNode:function(){var x=v.create(this.LIST_TEMPLATE);return x.addClass(this.getClassName(c)).setAttrs({id:b.stamp(x),role:"listbox"});},_getFirstItemNode:function(){return this._listNode.one(this[u]);},_getLastItemNode:function(){return this._listNode.one(this[u]+":last-child");},_syncPosition:function(){this._syncUIPosAlign();this._syncShim();},_syncResults:function(x){if(!x){x=this.get(j);}this._clear();if(x.length){this._add(x);this._ariaSay("items_available");}this._syncPosition();if(this.get("activateFirstItem")&&!this.get(f)){this.set(f,this._getFirstItemNode());}},_syncShim:h?function(){this._boundingBox.shim.sync();}:function(){},_syncVisibility:function(x){if(this.get(k)){x=true;this.set(q,x);}if(typeof x==="undefined"){x=this.get(q);}this._inputNode.set("aria-expanded",x);this._boundingBox.set("aria-hidden",!x);if(x){this._syncPosition();}else{this.set(f,null);this._set(r,null);this._boundingBox.get("offsetWidth");}},_afterActiveItemChange:function(z){var y=this._inputNode,x=z.newVal,A=z.prevVal;if(A&&A._node){A.removeClass(this[t]);}if(x){x.addClass(this[t]);y.set("aria-activedescendant",x.get(m));}else{y.removeAttribute("aria-activedescendant");}if(this.get("scrollIntoView")){(x||y).scrollIntoView();}},_afterAlwaysShowListChange:function(x){this.set(q,x.newVal||this.get(j).length>0);},_afterHoveredItemChange:function(y){var x=y.newVal,z=y.prevVal;if(z){z.removeClass(this[d]);}if(x){x.addClass(this[d]);}},_afterMouseOver:function(x){var y=x.domEvent.target.ancestor(this[u],true);this._mouseOverList=true;if(y){this._set(r,y);}},_afterMouseOut:function(){this._mouseOverList=false;this._set(r,null);},_afterResultsChange:function(x){this._syncResults(x.newVal);if(!this.get(k)){this.set(q,!!x.newVal.length);}},_afterVisibleChange:function(x){this._syncVisibility(!!x.newVal);},_onListInputBlur:function(x){if(!this._mouseOverList||this._lastInputKey===p){this.hide();}},_onItemClick:function(x){var y=x.currentTarget;this.set(f,y);this.selectItem(y,x);},_defSelectFn:function(x){var y=x.result.text;this._inputNode.focus();this._updateValue(y);this._ariaSay("item_selected",{item:y});this.hide();}},{ATTRS:{activateFirstItem:{value:false},activeItem:{setter:b.one,value:null},alwaysShowList:{value:false},circular:{value:true},hoveredItem:{readOnly:true,value:null},listNode:{readOnly:true,value:null},scrollIntoView:{value:false},strings:{valueFn:function(){return b.Intl.get("autocomplete-list");}},tabSelect:{value:true},visible:{value:false}},CSS_PREFIX:b.ClassNameManager.getClassName("aclist")});b.AutoCompleteList=a;b.AutoComplete=a;},"@VERSION@",{requires:["autocomplete-base","event-resize","selector-css3","shim-plugin","widget","widget-position","widget-position-align"],after:["autocomplete-sources"],lang:["en"],skinnable:true});YUI.add("autocomplete-plugin",function(b){var a=b.Plugin;function c(d){d.inputNode=d.host;if(!d.render&&d.render!==false){d.render=true;}c.superclass.constructor.apply(this,arguments);}b.extend(c,b.AutoCompleteList,{},{NAME:"autocompleteListPlugin",NS:"ac",CSS_PREFIX:b.ClassNameManager.getClassName("aclist")});a.AutoComplete=c;a.AutoCompleteList=c;},"@VERSION@",{requires:["autocomplete-list","node-pluginhost"]});YUI.add("autocomplete",function(a){},"@VERSION@",{use:["autocomplete-base","autocomplete-sources","autocomplete-list","autocomplete-plugin"]}); \ No newline at end of file diff --git a/build/autocomplete/autocomplete.js b/build/autocomplete/autocomplete.js index 60b3f5ec314..f485388811c 100644 --- a/build/autocomplete/autocomplete.js +++ b/build/autocomplete/autocomplete.js @@ -1621,7 +1621,7 @@ AutoCompleteBase.prototype = { Y.AutoCompleteBase = AutoCompleteBase; -}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base'], optional:['autocomplete-sources']}); +}, '@VERSION@' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']}); YUI.add('autocomplete-sources', function(Y) { /** @@ -2101,7 +2101,7 @@ Y.mix(ACBase.SOURCE_TYPES, { }, true); -}, '@VERSION@' ,{requires:['autocomplete-base'], optional:['io-base', 'json-parse', 'jsonp', 'yql']}); +}, '@VERSION@' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']}); YUI.add('autocomplete-list', function(Y) { /** @@ -2934,7 +2934,7 @@ Y.AutoCompleteList = List; Y.AutoComplete = List; -}, '@VERSION@' ,{after:['autocomplete-sources'], lang:['en'], skinnable:true, requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align']}); +}, '@VERSION@' ,{requires:['autocomplete-base', 'event-resize', 'selector-css3', 'shim-plugin', 'widget', 'widget-position', 'widget-position-align'], after:['autocomplete-sources'], lang:['en'], skinnable:true}); YUI.add('autocomplete-plugin', function(Y) { /** diff --git a/build/controller/controller-debug.js b/build/controller/controller-debug.js index fed5ca744c5..9ff16a28721 100644 --- a/build/controller/controller-debug.js +++ b/build/controller/controller-debug.js @@ -21,8 +21,10 @@ URLs. @uses Base **/ -var YArray = Y.Array, - QS = Y.QueryString, +var HistoryHash = Y.HistoryHash, + Lang = Y.Lang, + QS = Y.QueryString, + YArray = Y.Array, // Android versions lower than 3.0 are buggy and don't update // window.location after a pushState() call, so we fall back to hash-based @@ -30,7 +32,22 @@ var YArray = Y.Array, // // See http://code.google.com/p/android/issues/detail?id=17471 html5 = Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3), - location = Y.config.win.location; + win = Y.config.win, + location = win.location, + + /** + Fired when the controller is ready to begin dispatching to route handlers. + + You shouldn't need to wait for this event unless you plan to implement some + kind of custom dispatching logic. It's used internally in order to avoid + dispatching to an initial route if a browser history change occurs first. + + @event ready + @param {Boolean} dispatched `true` if routes have already been dispatched + (most likely due to a history change). + @fireOnce + **/ + EVT_READY = 'ready'; function Controller() { Controller.superclass.constructor.apply(this, arguments); @@ -40,23 +57,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Public Properties ---------------------------------------------------- /** - Base path or URL from which all routes should be evaluated. + If `true`, the controller will dispatch to the first route handler that + matches the current URL immediately after the controller is initialized, + even if there was no browser history change to trigger a dispatch. + + If you're rendering the initial pageview on the server, then you'll probably + want this to be `false`, but if you're doing all your rendering and route + handling entirely on the client, then setting this to `true` will allow your + client-side routes to handle the initial request of all pageviews without + depending on any server-side handling. + + This property defaults to `false` for HTML5 browsers, `true` for browsers + that rely on hash-based history (since the hash is never sent to the + server). + + @property dispatchOnInit + @type Boolean + @default `false` for HTML5 browsers, `true` for hash-based browsers + **/ + dispatchOnInit: !html5, + + /** + Root path from which all routes should be evaluated. For example, if your controller is running on a page at `http://example.com/myapp/` and you add a route with the path `/`, your route will never execute, because the path will always be preceded by - `/myapp`. Setting _base_ to `/myapp` would cause all routes to be evaluated - relative to that base path, so the `/` route would then execute. + `/myapp`. Setting `root` to `/myapp` would cause all routes to be evaluated + relative to that root URL, so the `/` route would then execute when the + user browses to `http://example.com/myapp/`. This property may be overridden in a subclass, set after instantiation, or passed as a config attribute when instantiating a `Y.Controller`-based class. - @property base + @property root @type String @default `''` **/ - base : '', + root: '', /** Array of route objects specifying routes to be created at instantiation @@ -85,6 +124,34 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Protected Properties ------------------------------------------------- + /** + Whether or not `_dispatch()` has been called since this controller was + instantiated. + + @property _dispatched + @type Boolean + @default undefined + @protected + **/ + + /** + Whether or not this browser is capable of using HTML5 history. + + @property _html5 + @type Boolean + @protected + **/ + _html5: html5, + + /** + Whether or not the `ready` event has fired yet. + + @property _ready + @type Boolean + @default undefined + @protected + **/ + /** Regex used to match parameter placeholders in route paths. @@ -109,31 +176,58 @@ Y.Controller = Y.extend(Controller, Y.Base, { @type RegExp @protected **/ - _regexUrlQuery : /\?([^#]*).*$/, + _regexUrlQuery: /\?([^#]*).*$/, // -- Lifecycle Methods ---------------------------------------------------- initializer: function (config) { + var self = this; + + // Set config properties. config || (config = {}); - this._routes = []; + config.routes && (self.routes = config.routes); - config.base && (this.base = config.base); - config.routes && (this.routes = config.routes); + Lang.isValue(config.root) && (self.root = config.root); + Lang.isValue(config.dispatchOnInit) && + (self.dispatchOnInit = config.dispatchOnInit); - YArray.each(this.routes, function (route) { - this.route(route.path, route.callback, true); - }, this); + // Create routes. + self._routes = []; - // Set up a history instance. - this._history = html5 ? new Y.HistoryHTML5() : new Y.HistoryHash(); - this._history.after('change', this._afterHistoryChange, this); + YArray.each(self.routes, function (route) { + self.route(route.path, route.callback, true); + }); - // Handle the initial route. - this._dispatch(this._getPath(), this._getState()); + // Set up a history instance or hashchange listener. + if (html5) { + self._history = new Y.HistoryHTML5({force: true}); + self._history.after('change', self._afterHistoryChange, self); + } else { + Y.on('hashchange', self._afterHistoryChange, win, self); + } + + // Fire a 'ready' event once we're ready to route. We wait first for all + // subclass initializers to finish, and then an additional 20ms to allow + // the browser to fire an initial `popstate` event if it wants to. + self.publish(EVT_READY, { + defaultFn : self._defReadyFn, + fireOnce : true, + preventable: false + }); + + self.once('initializedChange', function () { + setTimeout(function () { + self.fire(EVT_READY, {dispatched: !!self._dispatched}); + }, 20); + }); }, destructor: function () { - this._history.detachAll(); + if (html5) { + this._history.detachAll(); + } else { + Y.detach('hashchange', this._afterHistoryChange, win); + } }, // -- Public Methods ------------------------------------------------------- @@ -177,7 +271,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -185,25 +279,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.replace('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.replace('/path/'); + // New URL: http://example.com/path/ + + controller.replace('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar - controller.replace('/', 'You are now at example.com'); + controller.replace('/'); // New URL: http://example.com/ @method replace - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see save() **/ - replace: function (url, title, state) { - return this._save(url, title, state, true); + replace: function (url) { + return this._save(url, true); }, /** @@ -258,8 +351,6 @@ Y.Controller = Y.extend(Controller, Y.Base, { @param {Object} callback.req.query Query hash representing the URL query string, if any. Parameter names are keys, and are mapped to parameter values. - @param {Object} callback.req.state State object associated with this - URL, if any. @param {Function} callback.next Callback to pass control to the next matching route. If you don't call this function, then no further route handlers will be executed, even if there are more that match. If you do @@ -288,7 +379,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL and create a history entry. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -296,25 +387,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.save('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.save('/path/'); + // New URL: http://example.com/path/ - controller.save('/', 'You are now at example.com'); + controller.save('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar + + controller.save('/'); // New URL: http://example.com/ @method save - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see replace() **/ - save: function (url, title, state) { - return this._save(url, title, state); + save: function (url) { + return this._save(url); }, // -- Protected Methods ---------------------------------------------------- @@ -335,24 +425,29 @@ Y.Controller = Y.extend(Controller, Y.Base, { /** Dispatches to the first route handler that matches the specified _path_. + If called before the `ready` event has fired, the dispatch will be aborted. + This ensures normalized behavior between Chrome (which fires a `popstate` + event on every pageview) and other browsers (which do not). + @method _dispatch @param {String} path URL path. - @param {Object} state State to pass to route handlers. @protected **/ - _dispatch: function (path, state) { - var routes = this.match(path), - req, route, self; + _dispatch: function (path) { + var self = this, + routes = self.match(path), + req; + + self._dispatched = true; if (!routes || !routes.length) { return; } - req = this._getRequest(path, state); - self = this; + req = self._getRequest(path); function next(err) { - var callback, matches; + var callback, matches, route; if (err) { Y.error(err); @@ -380,6 +475,18 @@ Y.Controller = Y.extend(Controller, Y.Base, { next(); }, + /** + Gets the current path from the location hash, or an empty string if the + hash is empty. + + @method _getHashPath + @return {String} Current hash path, or an empty string if the hash is empty. + @protected + **/ + _getHashPath: function () { + return HistoryHash.getHash().replace(this._regexUrlQuery, ''); + }, + /** Gets the current route path. @@ -388,16 +495,9 @@ Y.Controller = Y.extend(Controller, Y.Base, { @protected **/ _getPath: html5 ? function () { - var base = this.base, - path = location.pathname; - - if (base && path.indexOf(base) === 0) { - path = path.substring(base.length); - } - - return path; + return this._removeRoot(location.pathname); } : function () { - return this._history.get('path') || this.base + location.pathname; + return this._getHashPath() || this._removeRoot(location.pathname); }, /** @@ -410,12 +510,15 @@ Y.Controller = Y.extend(Controller, Y.Base, { _getQuery: html5 ? function () { return location.search.substring(1); } : function () { - return this._history.get('query') || location.search.substring(1); + var hash = HistoryHash.getHash(), + matches = hash.match(this._regexUrlQuery); + + return hash && matches ? matches[1] : location.search.substring(1); }, /** - Creates a regular expression from the specified route specification. If - _path_ is already a regex, it will be returned unmodified. + Creates a regular expression from the given route specification. If _path_ + is already a regex, it will be returned unmodified. @method _getRegex @param {String|RegExp} path Route path specification. @@ -442,30 +545,44 @@ Y.Controller = Y.extend(Controller, Y.Base, { @method _getRequest @param {String} path Current path being dispatched. - @param {Object} state Current state. @return {Object} Request object. @protected **/ - _getRequest: function (path, state) { + _getRequest: function (path) { return { path : path, - query: this._parseQuery(this._getQuery()), - state: state + query: this._parseQuery(this._getQuery()) }; }, /** - Gets the current state, if any. + Joins the `root` URL to the specified _url_, normalizing leading/trailing + `/` characters. - @method _getState - @return {Object} Current state. + @example + controller.root = '/foo' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + controller.root = '/foo/' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + @method _joinURL + @param {String} url URL to append to the `root` URL. + @return {String} Joined URL. @protected **/ - _getState: html5 ? function () { - return this._history.get(); - } : function () { - var jsonState = this._history.get('state'); - return jsonState ? Y.JSON.parse(jsonState) : {}; + _joinURL: function (url) { + var root = this.root; + + if (url.charAt(0) === '/') { + url = url.substring(1); + } + + return root && root.charAt(root.length - 1) === '/' ? + root + url : + root + '/' + url; }, /** @@ -496,58 +613,59 @@ Y.Controller = Y.extend(Controller, Y.Base, { return result; }, + /** + Removes the `root` URL from the from of _path_ (if it's there) and returns + the result. The returned path will always have a leading `/`. + + @method _removeRoot + @param {String} path URL path. + @return {String} Rootless path. + @protected + **/ + _removeRoot: function (path) { + var root = this.root; + + if (root && path.indexOf(root) === 0) { + path = path.substring(root.length); + } + + return path.charAt(0) === '/' ? path : '/' + path; + }, + /** Saves a history entry using either `pushState()` or the location hash. @method _save @param {String} [url] URL for the history entry. - @param {String} [title] Page title associated with the history entry. - @param {Object} [state] State object associated with the history entry. @param {Boolean} [replace=false] If `true`, the current history entry will be replaced instead of a new one being added. @chainable @protected **/ - _save: function (url, title, state, replace) { - var jsonState, query; + _save: html5 ? function (url, replace) { + // Force _ready to true to ensure that the history change is handled + // even if _save is called before the `ready` event fires. + this._ready = true; - if (html5) { - if (typeof url === 'string') { - url = this.base + url; - } - } else { - // If we're not using HTML5 history, take over the history state for - // our own purposes and shove the implementer's state inside it as a - // JSON string. - jsonState = state && Y.JSON.stringify(state); - - // Extract a query string from the URL if there is one, then remove - // both the query and the hash portions of the URL so we can store - // just the path. - url = url.replace(this._regexUrlQuery, function (match, params) { - query = params; - return ''; - }); - - state = {path: url || this._getPath()}; - - query && (state.query = query); - jsonState && (state.state = jsonState); - } - - this._history[replace ? 'replace' : 'add'](state || {}, { - merge: false, - title: title, - url : url + this._history[replace ? 'replace' : 'add'](null, { + url: typeof url === 'string' ? this._joinURL(url) : url }); + return this; + } : function (url, replace) { + this._ready = true; + if (typeof url === 'string' && url.charAt(0) !== '/') { + url = '/' + url; + } + + HistoryHash[replace ? 'replaceHash' : 'setHash'](url); return this; }, // -- Protected Event Handlers --------------------------------------------- /** - Handles `history:change` events. + Handles `history:change` and `hashchange` events. @method _afterHistoryChange @param {EventFacade} e @@ -556,15 +674,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { _afterHistoryChange: function (e) { var self = this; - // We need to yield control to the UI thread to allow the browser to - // update document.location before we dispatch. - setTimeout(function () { - self._dispatch(self._getPath(), self._getState()); - }, 1); + if (self._ready) { + // We need to yield control to the UI thread to allow the browser to + // update window.location before we dispatch. + setTimeout(function () { + self._dispatch(self._getPath()); + }, 1); + } + }, + + // -- Default Event Handlers ----------------------------------------------- + + /** + Default handler for the `ready` event. + + @method _defReadyFn + @param {EventFacade} e + @protected + **/ + _defReadyFn: function (e) { + var hash; + + this._ready = true; + + if (this.dispatchOnInit && !this._dispatched) { + if (html5 && (hash = this._getHashPath()) + && hash.charAt(0) === '/') { + + // This is an HTML5 browser and we have a hash-based path in the + // URL, so we need to upgrade the URL to a non-hash URL. This + // will trigger a `history:change` event. + this._history.replace(null, {url: this._joinURL(hash)}); + } else { + this._dispatch(this._getPath()); + } + } } }, { NAME: 'controller' }); -}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history', 'json']}); +}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'history'], optional:['querystring-parse']}); diff --git a/build/controller/controller-min.js b/build/controller/controller-min.js index 7a06b667c02..f3523e17d57 100644 --- a/build/controller/controller-min.js +++ b/build/controller/controller-min.js @@ -1 +1 @@ -YUI.add("controller",function(f){var d=f.Array,b=f.QueryString,c=f.HistoryBase.html5&&(!f.UA.android||f.UA.android>=3),a=f.config.win.location;function e(){e.superclass.constructor.apply(this,arguments);}f.Controller=f.extend(e,f.Base,{base:"",routes:[],_regexPathParam:/([:*])([\w\d-]+)/g,_regexUrlQuery:/\?([^#]*).*$/,initializer:function(g){g||(g={});this._routes=[];g.base&&(this.base=g.base);g.routes&&(this.routes=g.routes);d.each(this.routes,function(h){this.route(h.path,h.callback,true);},this);this._history=c?new f.HistoryHTML5():new f.HistoryHash();this._history.after("change",this._afterHistoryChange,this);this._dispatch(this._getPath(),this._getState());},destructor:function(){this._history.detachAll();},match:function(g){return d.filter(this._routes,function(h){return g.search(h.regex)>-1;});},replace:function(g,i,h){return this._save(g,i,h,true);},route:function(h,i){var g=[];this._routes.push({callback:i,keys:g,path:h,regex:this._getRegex(h,g)});return this;},save:function(g,i,h){return this._save(g,i,h);},_decode:function(g){return decodeURIComponent(g.replace(/\+/g," "));},_dispatch:function(m,l){var g=this.match(m),k,i,h;if(!g||!g.length){return;}k=this._getRequest(m,l);h=this;function j(n){var p,o;if(n){f.error(n);}else{if((i=g.shift())){o=i.regex.exec(m);p=typeof i.callback==="string"?h[i.callback]:i.callback;if(o.length===i.keys.length+1){k.params=d.hash(i.keys,o.slice(1));}else{k.params={};d.each(o,function(r,q){k.params[q]=r;});}p.call(h,k,j);}}}j();},_getPath:c?function(){var g=this.base,h=a.pathname;if(g&&h.indexOf(g)===0){h=h.substring(g.length);}return h;}:function(){return this._history.get("path")||this.base+a.pathname;},_getQuery:c?function(){return a.search.substring(1);}:function(){return this._history.get("query")||a.search.substring(1);},_getRegex:function(h,g){if(h instanceof RegExp){return h;}h=h.replace(this._regexPathParam,function(j,i,k){g.push(k);return i==="*"?"(.*?)":"([^/]*)";});return new RegExp("^"+h+"$");},_getRequest:function(h,g){return{path:h,query:this._parseQuery(this._getQuery()),state:g};},_getState:c?function(){return this._history.get();}:function(){var g=this._history.get("state");return g?f.JSON.parse(g):{};},_parseQuery:b&&b.parse?b.parse:function(k){var l=this._decode,n=k.split("&"),j=0,h=n.length,g={},m;for(;j=3),g=a.config.win,j=g.location,d="ready";function c(){c.superclass.constructor.apply(this,arguments);}a.Controller=a.extend(c,a.Base,{dispatchOnInit:!f,root:"",routes:[],_html5:f,_regexPathParam:/([:*])([\w\d-]+)/g,_regexUrlQuery:/\?([^#]*).*$/,initializer:function(l){var k=this;l||(l={});l.routes&&(k.routes=l.routes);e.isValue(l.root)&&(k.root=l.root);e.isValue(l.dispatchOnInit)&&(k.dispatchOnInit=l.dispatchOnInit);k._routes=[];i.each(k.routes,function(m){k.route(m.path,m.callback,true);});if(f){k._history=new a.HistoryHTML5({force:true});k._history.after("change",k._afterHistoryChange,k);}else{a.on("hashchange",k._afterHistoryChange,g,k);}k.publish(d,{defaultFn:k._defReadyFn,fireOnce:true,preventable:false});k.once("initializedChange",function(){setTimeout(function(){k.fire(d,{dispatched:!!k._dispatched});},20);});},destructor:function(){if(f){this._history.detachAll();}else{a.detach("hashchange",this._afterHistoryChange,g);}},match:function(k){return i.filter(this._routes,function(l){return k.search(l.regex)>-1;});},replace:function(k){return this._save(k,true);},route:function(l,m){var k=[];this._routes.push({callback:m,keys:k,path:l,regex:this._getRegex(l,k)});return this;},save:function(k){return this._save(k);},_decode:function(k){return decodeURIComponent(k.replace(/\+/g," "));},_dispatch:function(o){var l=this,k=l.match(o),n;l._dispatched=true;if(!k||!k.length){return;}n=l._getRequest(o);function m(q){var s,r,p;if(q){a.error(q);}else{if((p=k.shift())){r=p.regex.exec(o);s=typeof p.callback==="string"?l[p.callback]:p.callback;if(r.length===p.keys.length+1){n.params=i.hash(p.keys,r.slice(1));}else{n.params={};i.each(r,function(u,t){n.params[t]=u;});}s.call(l,n,m);}}}m();},_getHashPath:function(){return h.getHash().replace(this._regexUrlQuery,"");},_getPath:f?function(){return this._removeRoot(j.pathname);}:function(){return this._getHashPath()||this._removeRoot(j.pathname);},_getQuery:f?function(){return j.search.substring(1);}:function(){var l=h.getHash(),k=l.match(this._regexUrlQuery);return l&&k?k[1]:j.search.substring(1);},_getRegex:function(l,k){if(l instanceof RegExp){return l;}l=l.replace(this._regexPathParam,function(n,m,o){k.push(o);return m==="*"?"(.*?)":"([^/]*)";});return new RegExp("^"+l+"$");},_getRequest:function(k){return{path:k,query:this._parseQuery(this._getQuery())};},_joinURL:function(l){var k=this.root;if(l.charAt(0)==="/"){l=l.substring(1);}return k&&k.charAt(k.length-1)==="/"?k+l:k+"/"+l;},_parseQuery:b&&b.parse?b.parse:function(n){var o=this._decode,q=n.split("&"),m=0,l=q.length,k={},p;for(;m= 3), - location = Y.config.win.location; + win = Y.config.win, + location = win.location, + + /** + Fired when the controller is ready to begin dispatching to route handlers. + + You shouldn't need to wait for this event unless you plan to implement some + kind of custom dispatching logic. It's used internally in order to avoid + dispatching to an initial route if a browser history change occurs first. + + @event ready + @param {Boolean} dispatched `true` if routes have already been dispatched + (most likely due to a history change). + @fireOnce + **/ + EVT_READY = 'ready'; function Controller() { Controller.superclass.constructor.apply(this, arguments); @@ -40,23 +57,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Public Properties ---------------------------------------------------- /** - Base path or URL from which all routes should be evaluated. + If `true`, the controller will dispatch to the first route handler that + matches the current URL immediately after the controller is initialized, + even if there was no browser history change to trigger a dispatch. + + If you're rendering the initial pageview on the server, then you'll probably + want this to be `false`, but if you're doing all your rendering and route + handling entirely on the client, then setting this to `true` will allow your + client-side routes to handle the initial request of all pageviews without + depending on any server-side handling. + + This property defaults to `false` for HTML5 browsers, `true` for browsers + that rely on hash-based history (since the hash is never sent to the + server). + + @property dispatchOnInit + @type Boolean + @default `false` for HTML5 browsers, `true` for hash-based browsers + **/ + dispatchOnInit: !html5, + + /** + Root path from which all routes should be evaluated. For example, if your controller is running on a page at `http://example.com/myapp/` and you add a route with the path `/`, your route will never execute, because the path will always be preceded by - `/myapp`. Setting _base_ to `/myapp` would cause all routes to be evaluated - relative to that base path, so the `/` route would then execute. + `/myapp`. Setting `root` to `/myapp` would cause all routes to be evaluated + relative to that root URL, so the `/` route would then execute when the + user browses to `http://example.com/myapp/`. This property may be overridden in a subclass, set after instantiation, or passed as a config attribute when instantiating a `Y.Controller`-based class. - @property base + @property root @type String @default `''` **/ - base : '', + root: '', /** Array of route objects specifying routes to be created at instantiation @@ -85,6 +124,34 @@ Y.Controller = Y.extend(Controller, Y.Base, { // -- Protected Properties ------------------------------------------------- + /** + Whether or not `_dispatch()` has been called since this controller was + instantiated. + + @property _dispatched + @type Boolean + @default undefined + @protected + **/ + + /** + Whether or not this browser is capable of using HTML5 history. + + @property _html5 + @type Boolean + @protected + **/ + _html5: html5, + + /** + Whether or not the `ready` event has fired yet. + + @property _ready + @type Boolean + @default undefined + @protected + **/ + /** Regex used to match parameter placeholders in route paths. @@ -109,31 +176,58 @@ Y.Controller = Y.extend(Controller, Y.Base, { @type RegExp @protected **/ - _regexUrlQuery : /\?([^#]*).*$/, + _regexUrlQuery: /\?([^#]*).*$/, // -- Lifecycle Methods ---------------------------------------------------- initializer: function (config) { + var self = this; + + // Set config properties. config || (config = {}); - this._routes = []; + config.routes && (self.routes = config.routes); - config.base && (this.base = config.base); - config.routes && (this.routes = config.routes); + Lang.isValue(config.root) && (self.root = config.root); + Lang.isValue(config.dispatchOnInit) && + (self.dispatchOnInit = config.dispatchOnInit); - YArray.each(this.routes, function (route) { - this.route(route.path, route.callback, true); - }, this); + // Create routes. + self._routes = []; - // Set up a history instance. - this._history = html5 ? new Y.HistoryHTML5() : new Y.HistoryHash(); - this._history.after('change', this._afterHistoryChange, this); + YArray.each(self.routes, function (route) { + self.route(route.path, route.callback, true); + }); - // Handle the initial route. - this._dispatch(this._getPath(), this._getState()); + // Set up a history instance or hashchange listener. + if (html5) { + self._history = new Y.HistoryHTML5({force: true}); + self._history.after('change', self._afterHistoryChange, self); + } else { + Y.on('hashchange', self._afterHistoryChange, win, self); + } + + // Fire a 'ready' event once we're ready to route. We wait first for all + // subclass initializers to finish, and then an additional 20ms to allow + // the browser to fire an initial `popstate` event if it wants to. + self.publish(EVT_READY, { + defaultFn : self._defReadyFn, + fireOnce : true, + preventable: false + }); + + self.once('initializedChange', function () { + setTimeout(function () { + self.fire(EVT_READY, {dispatched: !!self._dispatched}); + }, 20); + }); }, destructor: function () { - this._history.detachAll(); + if (html5) { + this._history.detachAll(); + } else { + Y.detach('hashchange', this._afterHistoryChange, win); + } }, // -- Public Methods ------------------------------------------------------- @@ -177,7 +271,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -185,25 +279,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.replace('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.replace('/path/'); + // New URL: http://example.com/path/ + + controller.replace('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar - controller.replace('/', 'You are now at example.com'); + controller.replace('/'); // New URL: http://example.com/ @method replace - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see save() **/ - replace: function (url, title, state) { - return this._save(url, title, state, true); + replace: function (url) { + return this._save(url, true); }, /** @@ -256,8 +349,6 @@ Y.Controller = Y.extend(Controller, Y.Base, { @param {Object} callback.req.query Query hash representing the URL query string, if any. Parameter names are keys, and are mapped to parameter values. - @param {Object} callback.req.state State object associated with this - URL, if any. @param {Function} callback.next Callback to pass control to the next matching route. If you don't call this function, then no further route handlers will be executed, even if there are more that match. If you do @@ -286,7 +377,7 @@ Y.Controller = Y.extend(Controller, Y.Base, { Behind the scenes, this method uses HTML5 `pushState()` in browsers that support it (or the location hash in older browsers and IE) to change the - URL and create a history entry for the given state. + URL and create a history entry. The specified URL must share the same origin (i.e., protocol, host, and port) as the current page, or an error will occur. @@ -294,25 +385,24 @@ Y.Controller = Y.extend(Controller, Y.Base, { @example // Starting URL: http://example.com/ - controller.save('/bar/', 'You are now at /bar/', {bar: true}); - // New URL: http://example.com/bar/ + controller.save('/path/'); + // New URL: http://example.com/path/ - controller.save('/', 'You are now at example.com'); + controller.save('/path?foo=bar'); + // New URL: http://example.com/path?foo=bar + + controller.save('/'); // New URL: http://example.com/ @method save - @param {String} [url] URL to set. May be relative or absolute, but if a - `base` property is specified, this URL must be relative to that property. - If not specified, the page's current URL will be used. - @param {String} [title] Page title to set. If not specified, the page's - current title will be used. - @param {Object} [state] State object to associate with this history entry. - May be any object that can be serialized to JSON. + @param {String} [url] URL to set. Should be a relative URL. If this + controller's `root` property is set, this URL must be relative to the + root URL. If no URL is specified, the page's current URL will be used. @chainable @see replace() **/ - save: function (url, title, state) { - return this._save(url, title, state); + save: function (url) { + return this._save(url); }, // -- Protected Methods ---------------------------------------------------- @@ -333,24 +423,29 @@ Y.Controller = Y.extend(Controller, Y.Base, { /** Dispatches to the first route handler that matches the specified _path_. + If called before the `ready` event has fired, the dispatch will be aborted. + This ensures normalized behavior between Chrome (which fires a `popstate` + event on every pageview) and other browsers (which do not). + @method _dispatch @param {String} path URL path. - @param {Object} state State to pass to route handlers. @protected **/ - _dispatch: function (path, state) { - var routes = this.match(path), - req, route, self; + _dispatch: function (path) { + var self = this, + routes = self.match(path), + req; + + self._dispatched = true; if (!routes || !routes.length) { return; } - req = this._getRequest(path, state); - self = this; + req = self._getRequest(path); function next(err) { - var callback, matches; + var callback, matches, route; if (err) { Y.error(err); @@ -378,6 +473,18 @@ Y.Controller = Y.extend(Controller, Y.Base, { next(); }, + /** + Gets the current path from the location hash, or an empty string if the + hash is empty. + + @method _getHashPath + @return {String} Current hash path, or an empty string if the hash is empty. + @protected + **/ + _getHashPath: function () { + return HistoryHash.getHash().replace(this._regexUrlQuery, ''); + }, + /** Gets the current route path. @@ -386,16 +493,9 @@ Y.Controller = Y.extend(Controller, Y.Base, { @protected **/ _getPath: html5 ? function () { - var base = this.base, - path = location.pathname; - - if (base && path.indexOf(base) === 0) { - path = path.substring(base.length); - } - - return path; + return this._removeRoot(location.pathname); } : function () { - return this._history.get('path') || this.base + location.pathname; + return this._getHashPath() || this._removeRoot(location.pathname); }, /** @@ -408,12 +508,15 @@ Y.Controller = Y.extend(Controller, Y.Base, { _getQuery: html5 ? function () { return location.search.substring(1); } : function () { - return this._history.get('query') || location.search.substring(1); + var hash = HistoryHash.getHash(), + matches = hash.match(this._regexUrlQuery); + + return hash && matches ? matches[1] : location.search.substring(1); }, /** - Creates a regular expression from the specified route specification. If - _path_ is already a regex, it will be returned unmodified. + Creates a regular expression from the given route specification. If _path_ + is already a regex, it will be returned unmodified. @method _getRegex @param {String|RegExp} path Route path specification. @@ -440,30 +543,44 @@ Y.Controller = Y.extend(Controller, Y.Base, { @method _getRequest @param {String} path Current path being dispatched. - @param {Object} state Current state. @return {Object} Request object. @protected **/ - _getRequest: function (path, state) { + _getRequest: function (path) { return { path : path, - query: this._parseQuery(this._getQuery()), - state: state + query: this._parseQuery(this._getQuery()) }; }, /** - Gets the current state, if any. + Joins the `root` URL to the specified _url_, normalizing leading/trailing + `/` characters. - @method _getState - @return {Object} Current state. + @example + controller.root = '/foo' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + controller.root = '/foo/' + controller._joinURL('bar'); // => '/foo/bar' + controller._joinURL('/bar'); // => '/foo/bar' + + @method _joinURL + @param {String} url URL to append to the `root` URL. + @return {String} Joined URL. @protected **/ - _getState: html5 ? function () { - return this._history.get(); - } : function () { - var jsonState = this._history.get('state'); - return jsonState ? Y.JSON.parse(jsonState) : {}; + _joinURL: function (url) { + var root = this.root; + + if (url.charAt(0) === '/') { + url = url.substring(1); + } + + return root && root.charAt(root.length - 1) === '/' ? + root + url : + root + '/' + url; }, /** @@ -494,58 +611,59 @@ Y.Controller = Y.extend(Controller, Y.Base, { return result; }, + /** + Removes the `root` URL from the from of _path_ (if it's there) and returns + the result. The returned path will always have a leading `/`. + + @method _removeRoot + @param {String} path URL path. + @return {String} Rootless path. + @protected + **/ + _removeRoot: function (path) { + var root = this.root; + + if (root && path.indexOf(root) === 0) { + path = path.substring(root.length); + } + + return path.charAt(0) === '/' ? path : '/' + path; + }, + /** Saves a history entry using either `pushState()` or the location hash. @method _save @param {String} [url] URL for the history entry. - @param {String} [title] Page title associated with the history entry. - @param {Object} [state] State object associated with the history entry. @param {Boolean} [replace=false] If `true`, the current history entry will be replaced instead of a new one being added. @chainable @protected **/ - _save: function (url, title, state, replace) { - var jsonState, query; + _save: html5 ? function (url, replace) { + // Force _ready to true to ensure that the history change is handled + // even if _save is called before the `ready` event fires. + this._ready = true; - if (html5) { - if (typeof url === 'string') { - url = this.base + url; - } - } else { - // If we're not using HTML5 history, take over the history state for - // our own purposes and shove the implementer's state inside it as a - // JSON string. - jsonState = state && Y.JSON.stringify(state); - - // Extract a query string from the URL if there is one, then remove - // both the query and the hash portions of the URL so we can store - // just the path. - url = url.replace(this._regexUrlQuery, function (match, params) { - query = params; - return ''; - }); - - state = {path: url || this._getPath()}; - - query && (state.query = query); - jsonState && (state.state = jsonState); - } - - this._history[replace ? 'replace' : 'add'](state || {}, { - merge: false, - title: title, - url : url + this._history[replace ? 'replace' : 'add'](null, { + url: typeof url === 'string' ? this._joinURL(url) : url }); + return this; + } : function (url, replace) { + this._ready = true; + if (typeof url === 'string' && url.charAt(0) !== '/') { + url = '/' + url; + } + + HistoryHash[replace ? 'replaceHash' : 'setHash'](url); return this; }, // -- Protected Event Handlers --------------------------------------------- /** - Handles `history:change` events. + Handles `history:change` and `hashchange` events. @method _afterHistoryChange @param {EventFacade} e @@ -554,15 +672,45 @@ Y.Controller = Y.extend(Controller, Y.Base, { _afterHistoryChange: function (e) { var self = this; - // We need to yield control to the UI thread to allow the browser to - // update document.location before we dispatch. - setTimeout(function () { - self._dispatch(self._getPath(), self._getState()); - }, 1); + if (self._ready) { + // We need to yield control to the UI thread to allow the browser to + // update window.location before we dispatch. + setTimeout(function () { + self._dispatch(self._getPath()); + }, 1); + } + }, + + // -- Default Event Handlers ----------------------------------------------- + + /** + Default handler for the `ready` event. + + @method _defReadyFn + @param {EventFacade} e + @protected + **/ + _defReadyFn: function (e) { + var hash; + + this._ready = true; + + if (this.dispatchOnInit && !this._dispatched) { + if (html5 && (hash = this._getHashPath()) + && hash.charAt(0) === '/') { + + // This is an HTML5 browser and we have a hash-based path in the + // URL, so we need to upgrade the URL to a non-hash URL. This + // will trigger a `history:change` event. + this._history.replace(null, {url: this._joinURL(hash)}); + } else { + this._dispatch(this._getPath()); + } + } } }, { NAME: 'controller' }); -}, '@VERSION@' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history', 'json']}); +}, '@VERSION@' ,{requires:['array-extras', 'base-build', 'history'], optional:['querystring-parse']}); diff --git a/build/datatable-sort/datatable-sort-debug.js b/build/datatable-sort/datatable-sort-debug.js index 813602327f5..17b9d866091 100644 --- a/build/datatable-sort/datatable-sort-debug.js +++ b/build/datatable-sort/datatable-sort-debug.js @@ -310,4 +310,4 @@ Y.namespace("Plugin").DataTableSort = DataTableSort; -}, '@VERSION@' ,{lang:['en'], requires:['datatable-base','plugin','recordset-sort']}); +}, '@VERSION@' ,{requires:['datatable-base','plugin','recordset-sort'], lang:['en']}); diff --git a/build/datatable-sort/datatable-sort-min.js b/build/datatable-sort/datatable-sort-min.js index 621621b7999..33a1cbe2c30 100644 --- a/build/datatable-sort/datatable-sort-min.js +++ b/build/datatable-sort/datatable-sort-min.js @@ -1 +1 @@ -YUI.add("datatable-sort",function(g){var f=g.ClassNameManager.getClassName,h="datatable",b="column",d="asc",c="desc",a='{value}';function e(){e.superclass.constructor.apply(this,arguments);}g.mix(e,{NS:"sort",NAME:"dataTableSort",ATTRS:{trigger:{value:{event:"click",selector:"th"},writeOnce:"initOnly"},lastSortedBy:{setter:"_setLastSortedBy",lazyAdd:false},template:{value:a}}});g.extend(e,g.Plugin.Base,{initializer:function(j){var k=this.get("host"),i=this.get("trigger");k.get("recordset").plug(g.Plugin.RecordsetSort,{dt:k});k.get("recordset").sort.addTarget(k);this.doBefore("_createTheadThNode",this._beforeCreateTheadThNode);this.doBefore("_attachTheadThNode",this._beforeAttachTheadThNode);this.doBefore("_attachTbodyTdNode",this._beforeAttachTbodyTdNode);k.delegate(i.event,g.bind(this._onEventSortColumn,this),i.selector);k.after("recordsetSort:sort",function(){this._uiSetRecordset(this.get("recordset"));});this.on("lastSortedByChange",function(l){this._uiSetLastSortedBy(l.prevVal,l.newVal,k);});if(k.get("rendered")){k._uiSetColumnset(k.get("columnset"));this._uiSetLastSortedBy(null,this.get("lastSortedBy"),k);}},_setLastSortedBy:function(i){if(g.Lang.isString(i)){return{key:i,dir:"asc",notdir:"desc"};}else{if(i&&i.key){if(i.dir==="desc"){return{key:i.key,dir:"desc",notdir:"asc"};}else{return{key:i.key,dir:"asc",notdir:"desc"};}}else{return null;}}},_uiSetLastSortedBy:function(m,j,i){var u=m&&m.key,n=m&&m.dir,t=j&&j.key,k=j&&j.dir,q=i.get("columnset"),s=q.keyHash[u],o=q.keyHash[t],r=i._tbodyNode,l,p;if(s){s.thNode.removeClass(f(h,n));l=r.all("."+f(b,s.get("id")));l.removeClass(f(h,n));}if(o){o.thNode.addClass(f(h,k));p=r.all("."+f(b,o.get("id")));p.addClass(f(h,k));}},_beforeCreateTheadThNode:function(i){if(i.column.get("sortable")){i.value=g.substitute(this.get("template"),{link_class:i.link_class||"",link_title:"title",link_href:"#",value:i.value});}},_beforeAttachTheadThNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.th.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.th.replaceClass(f(h,j),f(h,i));}},_beforeAttachTbodyTdNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.td.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.td.replaceClass(f(h,j),f(h,i));}},_onEventSortColumn:function(n){n.halt();var l=this.get("host"),k=l.get("columnset").idHash[n.currentTarget.get("id")],j=k.get("key"),m=k.get("field"),o=this.get("lastSortedBy"),i=(o&&o.key===j&&o.dir===d)?c:d,p=k.get("sortFn");if(k.get("sortable")){l.get("recordset").sort.sort(m,i===c,p);this.set("lastSortedBy",{key:j,dir:i});}}});g.namespace("Plugin").DataTableSort=e;},"@VERSION@",{lang:["en"],requires:["datatable-base","plugin","recordset-sort"]}); \ No newline at end of file +YUI.add("datatable-sort",function(g){var f=g.ClassNameManager.getClassName,h="datatable",b="column",d="asc",c="desc",a='{value}';function e(){e.superclass.constructor.apply(this,arguments);}g.mix(e,{NS:"sort",NAME:"dataTableSort",ATTRS:{trigger:{value:{event:"click",selector:"th"},writeOnce:"initOnly"},lastSortedBy:{setter:"_setLastSortedBy",lazyAdd:false},template:{value:a}}});g.extend(e,g.Plugin.Base,{initializer:function(j){var k=this.get("host"),i=this.get("trigger");k.get("recordset").plug(g.Plugin.RecordsetSort,{dt:k});k.get("recordset").sort.addTarget(k);this.doBefore("_createTheadThNode",this._beforeCreateTheadThNode);this.doBefore("_attachTheadThNode",this._beforeAttachTheadThNode);this.doBefore("_attachTbodyTdNode",this._beforeAttachTbodyTdNode);k.delegate(i.event,g.bind(this._onEventSortColumn,this),i.selector);k.after("recordsetSort:sort",function(){this._uiSetRecordset(this.get("recordset"));});this.on("lastSortedByChange",function(l){this._uiSetLastSortedBy(l.prevVal,l.newVal,k);});if(k.get("rendered")){k._uiSetColumnset(k.get("columnset"));this._uiSetLastSortedBy(null,this.get("lastSortedBy"),k);}},_setLastSortedBy:function(i){if(g.Lang.isString(i)){return{key:i,dir:"asc",notdir:"desc"};}else{if(i&&i.key){if(i.dir==="desc"){return{key:i.key,dir:"desc",notdir:"asc"};}else{return{key:i.key,dir:"asc",notdir:"desc"};}}else{return null;}}},_uiSetLastSortedBy:function(m,j,i){var u=m&&m.key,n=m&&m.dir,t=j&&j.key,k=j&&j.dir,q=i.get("columnset"),s=q.keyHash[u],o=q.keyHash[t],r=i._tbodyNode,l,p;if(s){s.thNode.removeClass(f(h,n));l=r.all("."+f(b,s.get("id")));l.removeClass(f(h,n));}if(o){o.thNode.addClass(f(h,k));p=r.all("."+f(b,o.get("id")));p.addClass(f(h,k));}},_beforeCreateTheadThNode:function(i){if(i.column.get("sortable")){i.value=g.substitute(this.get("template"),{link_class:i.link_class||"",link_title:"title",link_href:"#",value:i.value});}},_beforeAttachTheadThNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.th.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.th.replaceClass(f(h,j),f(h,i));}},_beforeAttachTbodyTdNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.td.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.td.replaceClass(f(h,j),f(h,i));}},_onEventSortColumn:function(n){n.halt();var l=this.get("host"),k=l.get("columnset").idHash[n.currentTarget.get("id")],j=k.get("key"),m=k.get("field"),o=this.get("lastSortedBy"),i=(o&&o.key===j&&o.dir===d)?c:d,p=k.get("sortFn");if(k.get("sortable")){l.get("recordset").sort.sort(m,i===c,p);this.set("lastSortedBy",{key:j,dir:i});}}});g.namespace("Plugin").DataTableSort=e;},"@VERSION@",{requires:["datatable-base","plugin","recordset-sort"],lang:["en"]}); \ No newline at end of file diff --git a/build/datatable-sort/datatable-sort.js b/build/datatable-sort/datatable-sort.js index 813602327f5..17b9d866091 100644 --- a/build/datatable-sort/datatable-sort.js +++ b/build/datatable-sort/datatable-sort.js @@ -310,4 +310,4 @@ Y.namespace("Plugin").DataTableSort = DataTableSort; -}, '@VERSION@' ,{lang:['en'], requires:['datatable-base','plugin','recordset-sort']}); +}, '@VERSION@' ,{requires:['datatable-base','plugin','recordset-sort'], lang:['en']}); diff --git a/build/datatable/datatable-debug.js b/build/datatable/datatable-debug.js index aecf42f01f6..3b677fdb1e1 100644 --- a/build/datatable/datatable-debug.js +++ b/build/datatable/datatable-debug.js @@ -2127,7 +2127,7 @@ Y.namespace("Plugin").DataTableSort = DataTableSort; -}, '@VERSION@' ,{lang:['en'], requires:['datatable-base','plugin','recordset-sort']}); +}, '@VERSION@' ,{requires:['datatable-base','plugin','recordset-sort'], lang:['en']}); YUI.add('datatable-scroll', function(Y) { /** diff --git a/build/datatable/datatable-min.js b/build/datatable/datatable-min.js index 5eff49ab358..dc30010fd16 100644 --- a/build/datatable/datatable-min.js +++ b/build/datatable/datatable-min.js @@ -1,4 +1,4 @@ YUI.add("datatable-base",function(c){var w=c.Lang,g=w.isValue,F=c.Lang.substitute,d=c.Node,t=d.create,p=c.ClassNameManager.getClassName,q="datatable",r="column",H="focus",K="keydown",h="mouseenter",o="mouseleave",k="mouseup",z="mousedown",C="click",v="dblclick",e=p(q,"columns"),B=p(q,"data"),b=p(q,"msg"),l=p(q,"liner"),E=p(q,"first"),i=p(q,"last"),u=p(q,"even"),A=p(q,"odd"),D="
          ",x="",I='',f='',J='
          {value}
          ',G='',a='
          {value}
          ',j="{value}",n='';function m(L){m.superclass.constructor.apply(this,arguments);}c.mix(m,{NAME:"column",ATTRS:{id:{valueFn:"_defaultId",readOnly:true},key:{valueFn:"_defaultKey"},field:{valueFn:"_defaultField"},label:{valueFn:"_defaultLabel"},children:{value:null},abbr:{value:""},classnames:{readOnly:true,getter:"_getClassnames"},formatter:{},sortable:{value:false},editor:{},width:{},resizeable:{},minimized:{},minWidth:{},maxAutoWidth:{}}});c.extend(m,c.Widget,{_defaultId:function(){return c.guid();},_defaultKey:function(L){return L||c.guid();},_defaultField:function(L){return L||this.get("key");},_defaultLabel:function(L){return L||this.get("key");},_afterAbbrChange:function(L){this._uiSetAbbr(L.newVal);},keyIndex:null,headers:null,colSpan:1,rowSpan:1,parent:null,thNode:null,initializer:function(L){},destructor:function(){},_getClassnames:function(){return c.ClassNameManager.getClassName(r,this.get("id"));},syncUI:function(){this._uiSetAbbr(this.get("abbr"));},_uiSetAbbr:function(L){this.thNode.set("abbr",L);}});c.Column=m;function y(L){y.superclass.constructor.apply(this,arguments);}c.mix(y,{NAME:"columnset",ATTRS:{definitions:{setter:"_setDefinitions"}}});c.extend(y,c.Base,{_setDefinitions:function(L){return c.clone(L);},tree:null,idHash:null,keyHash:null,keys:null,initializer:function(){var L=[],Q={},R={},P=[],O=this.get("definitions"),M=this;function N(Z,Y,X){var U=0,T=Y.length,W,V,S;Z++;if(!L[Z]){L[Z]=[];}for(;UO){O=W;}}}}}for(M=0;M"];for(;M");this._colgroupNode=N.insertBefore(t(O.join("")),N.get("firstChild")); return this._colgroupNode;},_addTheadNode:function(L){if(L){this._theadNode=L.insertBefore(t(I),this._colgroupNode.next());return this._theadNode;}},_addTbodyNode:function(L){this._tbodyNode=L.appendChild(t(f));return this._tbodyNode;},_addMessageNode:function(L){this._msgNode=L.insertBefore(t(n),this._tbodyNode);return this._msgNode;},_addCaptionNode:function(L){this._captionNode=L.createCaption();return this._captionNode;},bindUI:function(){var M="thead."+e+">tr>th",N="tbody."+B+">tr>td",L="tbody."+b+">tr>td";},delegate:function(L){if(L==="dblclick"){this.get("boundingBox").delegate.apply(this.get("boundingBox"),arguments);}else{this.get("contentBox").delegate.apply(this.get("contentBox"),arguments);}},syncUI:function(){this._uiSetColumnset(this.get("columnset"));this._uiSetRecordset(this.get("recordset"));this._uiSetSummary(this.get("summary"));this._uiSetCaption(this.get("caption"));},_uiSetSummary:function(L){L=g(L)?L:"";this._tableNode.set("summary",L);},_uiSetCaption:function(L){L=g(L)?L:"";this._captionNode.setContent(L);},_uiSetColumnset:function(P){var M=P.tree,R=this._theadNode,N=0,L=M.length,O=R.get("parentNode"),Q=R.next();R.remove();R.get("children").remove(true);for(;N{value}';function e(){e.superclass.constructor.apply(this,arguments);}g.mix(e,{NS:"sort",NAME:"dataTableSort",ATTRS:{trigger:{value:{event:"click",selector:"th"},writeOnce:"initOnly"},lastSortedBy:{setter:"_setLastSortedBy",lazyAdd:false},template:{value:a}}});g.extend(e,g.Plugin.Base,{initializer:function(j){var k=this.get("host"),i=this.get("trigger");k.get("recordset").plug(g.Plugin.RecordsetSort,{dt:k});k.get("recordset").sort.addTarget(k);this.doBefore("_createTheadThNode",this._beforeCreateTheadThNode);this.doBefore("_attachTheadThNode",this._beforeAttachTheadThNode);this.doBefore("_attachTbodyTdNode",this._beforeAttachTbodyTdNode);k.delegate(i.event,g.bind(this._onEventSortColumn,this),i.selector);k.after("recordsetSort:sort",function(){this._uiSetRecordset(this.get("recordset"));});this.on("lastSortedByChange",function(l){this._uiSetLastSortedBy(l.prevVal,l.newVal,k);});if(k.get("rendered")){k._uiSetColumnset(k.get("columnset"));this._uiSetLastSortedBy(null,this.get("lastSortedBy"),k);}},_setLastSortedBy:function(i){if(g.Lang.isString(i)){return{key:i,dir:"asc",notdir:"desc"};}else{if(i&&i.key){if(i.dir==="desc"){return{key:i.key,dir:"desc",notdir:"asc"}; -}else{return{key:i.key,dir:"asc",notdir:"desc"};}}else{return null;}}},_uiSetLastSortedBy:function(m,j,i){var u=m&&m.key,n=m&&m.dir,t=j&&j.key,k=j&&j.dir,q=i.get("columnset"),s=q.keyHash[u],o=q.keyHash[t],r=i._tbodyNode,l,p;if(s){s.thNode.removeClass(f(h,n));l=r.all("."+f(b,s.get("id")));l.removeClass(f(h,n));}if(o){o.thNode.addClass(f(h,k));p=r.all("."+f(b,o.get("id")));p.addClass(f(h,k));}},_beforeCreateTheadThNode:function(i){if(i.column.get("sortable")){i.value=g.substitute(this.get("template"),{link_class:i.link_class||"",link_title:"title",link_href:"#",value:i.value});}},_beforeAttachTheadThNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.th.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.th.replaceClass(f(h,j),f(h,i));}},_beforeAttachTbodyTdNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.td.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.td.replaceClass(f(h,j),f(h,i));}},_onEventSortColumn:function(n){n.halt();var l=this.get("host"),k=l.get("columnset").idHash[n.currentTarget.get("id")],j=k.get("key"),m=k.get("field"),o=this.get("lastSortedBy"),i=(o&&o.key===j&&o.dir===d)?c:d,p=k.get("sortFn");if(k.get("sortable")){l.get("recordset").sort.sort(m,i===c,p);this.set("lastSortedBy",{key:j,dir:i});}}});g.namespace("Plugin").DataTableSort=e;},"@VERSION@",{lang:["en"],requires:["datatable-base","plugin","recordset-sort"]});YUI.add("datatable-scroll",function(b){var k=b.Node,j=b.Lang,m=b.UA,f=b.ClassNameManager.getClassName,l="datatable",a=f(l,"hd"),e=f(l,"bd"),d=f(l,"data"),i=f(l,"scrollable"),h='
          ',c='
          ',g="
          ";function n(){n.superclass.constructor.apply(this,arguments);}b.mix(n,{NS:"scroll",NAME:"dataTableScroll",ATTRS:{width:{value:undefined,writeOnce:"initOnly"},height:{value:undefined,writeOnce:"initOnly"},_scroll:{valueFn:function(){var o=this.get("width"),p=this.get("height");if(o&&p){return"xy";}else{if(o){return"x";}else{if(p){return"y";}else{return null;}}}}},COLOR_COLUMNFILLER:{value:"#f2f2f2",validator:j.isString,setter:function(o){if(this._headerContainerNode){this._headerContainerNode.setStyle("backgroundColor",o);}}}}});b.extend(n,b.Plugin.Base,{_parentTableNode:null,_parentTheadNode:null,_parentTbodyNode:null,_parentMsgNode:null,_parentContainer:null,_bodyContainerNode:null,_headerContainerNode:null,initializer:function(o){var p=this.get("host");this._parentContainer=p.get("contentBox");this._parentContainer.addClass(i);this._setUpNodes();},_setUpNodes:function(){this.afterHostMethod("_addTableNode",this._setUpParentTableNode);this.afterHostMethod("_addTheadNode",this._setUpParentTheadNode);this.afterHostMethod("_addTbodyNode",this._setUpParentTbodyNode);this.afterHostMethod("_addMessageNode",this._setUpParentMessageNode);this.afterHostMethod("renderUI",this.renderUI);this.afterHostMethod("syncUI",this.syncUI);if(this.get("_scroll")!=="x"){this.afterHostMethod("_attachTheadThNode",this._attachTheadThNode);this.afterHostMethod("_attachTbodyTdNode",this._attachTbodyTdNode);}},_setUpParentTableNode:function(){this._parentTableNode=this.get("host")._tableNode;},_setUpParentTheadNode:function(){this._parentTheadNode=this.get("host")._theadNode;},_setUpParentTbodyNode:function(){this._parentTbodyNode=this.get("host")._tbodyNode;},_setUpParentMessageNode:function(){this._parentMsgNode=this.get("host")._msgNode;},renderUI:function(){this._createBodyContainer();this._createHeaderContainer();this._setContentBoxDimensions();},syncUI:function(){this._removeCaptionNode();this._syncWidths();this._syncScroll();},_removeCaptionNode:function(){this.get("host")._captionNode.remove();},_syncWidths:function(){var p=k.all("#"+this._parentContainer.get("id")+" ."+a+" table thead th"),q=k.one("#"+this._parentContainer.get("id")+" ."+e+" table ."+d).get("firstChild").get("children"),r,u,w,t,v,s,o=m.ie;for(r=0,u=p.size();rt){s.setStyle("width",(w-20+"px"));}else{if(t>w){v.setStyle("width",(t-20+"px"));s.setStyle("width",(t-20+"px"));}}}},_attachTheadThNode:function(q){var p=q.column.get("width")||"auto";if(p!=="auto"){q.th.get("firstChild").setStyles({width:p,overflow:"hidden"});}return q;},_attachTbodyTdNode:function(q){var p=q.column.get("width")||"auto";if(p!=="auto"){q.td.get("firstChild").setStyles({width:p,overflow:"hidden"});}return q;},_createBodyContainer:function(){var p=k.create(c),o=b.bind("_onScroll",this);this._bodyContainerNode=p;this._setStylesForTbody();p.appendChild(this._parentTableNode);this._parentContainer.appendChild(p);p.on("scroll",o);},_createHeaderContainer:function(){var p=k.create(h),o=k.create(g);this._headerContainerNode=p;this._setStylesForThead();o.appendChild(this._parentTheadNode);p.appendChild(o);this._parentContainer.prepend(p);},_setStylesForTbody:function(){var p=this.get("_scroll"),o=this.get("width")||"",r=this.get("height")||"",q=this._bodyContainerNode,s={width:"",height:r};if(p==="x"){s.overflowY="hidden";s.width=o;}else{if(p==="y"){s.overflowX="hidden";}else{if(p==="xy"){s.width=o;}else{s.overflowX="hidden";s.overflowY="hidden";s.width=o;}}}q.setStyles(s);return q;},_setStylesForThead:function(){var o=this.get("width")||"",p=this._headerContainerNode;p.setStyles({"width":o,"overflow":"hidden"});},_setContentBoxDimensions:function(){if(this.get("_scroll")==="y"||(!this.get("width"))){this._parentContainer.setStyle("width","auto");}},_onScroll:function(){this._headerContainerNode.set("scrollLeft",this._bodyContainerNode.get("scrollLeft"));},_syncScroll:function(){this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();if(m.opera){this._headerContainerNode.set("scrollLeft",this._bodyContainerNode.get("scrollLeft"));if(!this.get("width")){document.body.style+=""; +}else{return{key:i.key,dir:"asc",notdir:"desc"};}}else{return null;}}},_uiSetLastSortedBy:function(m,j,i){var u=m&&m.key,n=m&&m.dir,t=j&&j.key,k=j&&j.dir,q=i.get("columnset"),s=q.keyHash[u],o=q.keyHash[t],r=i._tbodyNode,l,p;if(s){s.thNode.removeClass(f(h,n));l=r.all("."+f(b,s.get("id")));l.removeClass(f(h,n));}if(o){o.thNode.addClass(f(h,k));p=r.all("."+f(b,o.get("id")));p.addClass(f(h,k));}},_beforeCreateTheadThNode:function(i){if(i.column.get("sortable")){i.value=g.substitute(this.get("template"),{link_class:i.link_class||"",link_title:"title",link_href:"#",value:i.value});}},_beforeAttachTheadThNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.th.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.th.replaceClass(f(h,j),f(h,i));}},_beforeAttachTbodyTdNode:function(m){var l=this.get("lastSortedBy"),k=l&&l.key,i=l&&l.dir,j=l&&l.notdir;if(m.column.get("sortable")){m.td.addClass(f(h,"sortable"));}if(k&&(k===m.column.get("key"))){m.td.replaceClass(f(h,j),f(h,i));}},_onEventSortColumn:function(n){n.halt();var l=this.get("host"),k=l.get("columnset").idHash[n.currentTarget.get("id")],j=k.get("key"),m=k.get("field"),o=this.get("lastSortedBy"),i=(o&&o.key===j&&o.dir===d)?c:d,p=k.get("sortFn");if(k.get("sortable")){l.get("recordset").sort.sort(m,i===c,p);this.set("lastSortedBy",{key:j,dir:i});}}});g.namespace("Plugin").DataTableSort=e;},"@VERSION@",{requires:["datatable-base","plugin","recordset-sort"],lang:["en"]});YUI.add("datatable-scroll",function(b){var k=b.Node,j=b.Lang,m=b.UA,f=b.ClassNameManager.getClassName,l="datatable",a=f(l,"hd"),e=f(l,"bd"),d=f(l,"data"),i=f(l,"scrollable"),h='
          ',c='
          ',g="
          ";function n(){n.superclass.constructor.apply(this,arguments);}b.mix(n,{NS:"scroll",NAME:"dataTableScroll",ATTRS:{width:{value:undefined,writeOnce:"initOnly"},height:{value:undefined,writeOnce:"initOnly"},_scroll:{valueFn:function(){var o=this.get("width"),p=this.get("height");if(o&&p){return"xy";}else{if(o){return"x";}else{if(p){return"y";}else{return null;}}}}},COLOR_COLUMNFILLER:{value:"#f2f2f2",validator:j.isString,setter:function(o){if(this._headerContainerNode){this._headerContainerNode.setStyle("backgroundColor",o);}}}}});b.extend(n,b.Plugin.Base,{_parentTableNode:null,_parentTheadNode:null,_parentTbodyNode:null,_parentMsgNode:null,_parentContainer:null,_bodyContainerNode:null,_headerContainerNode:null,initializer:function(o){var p=this.get("host");this._parentContainer=p.get("contentBox");this._parentContainer.addClass(i);this._setUpNodes();},_setUpNodes:function(){this.afterHostMethod("_addTableNode",this._setUpParentTableNode);this.afterHostMethod("_addTheadNode",this._setUpParentTheadNode);this.afterHostMethod("_addTbodyNode",this._setUpParentTbodyNode);this.afterHostMethod("_addMessageNode",this._setUpParentMessageNode);this.afterHostMethod("renderUI",this.renderUI);this.afterHostMethod("syncUI",this.syncUI);if(this.get("_scroll")!=="x"){this.afterHostMethod("_attachTheadThNode",this._attachTheadThNode);this.afterHostMethod("_attachTbodyTdNode",this._attachTbodyTdNode);}},_setUpParentTableNode:function(){this._parentTableNode=this.get("host")._tableNode;},_setUpParentTheadNode:function(){this._parentTheadNode=this.get("host")._theadNode;},_setUpParentTbodyNode:function(){this._parentTbodyNode=this.get("host")._tbodyNode;},_setUpParentMessageNode:function(){this._parentMsgNode=this.get("host")._msgNode;},renderUI:function(){this._createBodyContainer();this._createHeaderContainer();this._setContentBoxDimensions();},syncUI:function(){this._removeCaptionNode();this._syncWidths();this._syncScroll();},_removeCaptionNode:function(){this.get("host")._captionNode.remove();},_syncWidths:function(){var p=k.all("#"+this._parentContainer.get("id")+" ."+a+" table thead th"),q=k.one("#"+this._parentContainer.get("id")+" ."+e+" table ."+d).get("firstChild").get("children"),r,u,w,t,v,s,o=m.ie;for(r=0,u=p.size();rt){s.setStyle("width",(w-20+"px"));}else{if(t>w){v.setStyle("width",(t-20+"px"));s.setStyle("width",(t-20+"px"));}}}},_attachTheadThNode:function(q){var p=q.column.get("width")||"auto";if(p!=="auto"){q.th.get("firstChild").setStyles({width:p,overflow:"hidden"});}return q;},_attachTbodyTdNode:function(q){var p=q.column.get("width")||"auto";if(p!=="auto"){q.td.get("firstChild").setStyles({width:p,overflow:"hidden"});}return q;},_createBodyContainer:function(){var p=k.create(c),o=b.bind("_onScroll",this);this._bodyContainerNode=p;this._setStylesForTbody();p.appendChild(this._parentTableNode);this._parentContainer.appendChild(p);p.on("scroll",o);},_createHeaderContainer:function(){var p=k.create(h),o=k.create(g);this._headerContainerNode=p;this._setStylesForThead();o.appendChild(this._parentTheadNode);p.appendChild(o);this._parentContainer.prepend(p);},_setStylesForTbody:function(){var p=this.get("_scroll"),o=this.get("width")||"",r=this.get("height")||"",q=this._bodyContainerNode,s={width:"",height:r};if(p==="x"){s.overflowY="hidden";s.width=o;}else{if(p==="y"){s.overflowX="hidden";}else{if(p==="xy"){s.width=o;}else{s.overflowX="hidden";s.overflowY="hidden";s.width=o;}}}q.setStyles(s);return q;},_setStylesForThead:function(){var o=this.get("width")||"",p=this._headerContainerNode;p.setStyles({"width":o,"overflow":"hidden"});},_setContentBoxDimensions:function(){if(this.get("_scroll")==="y"||(!this.get("width"))){this._parentContainer.setStyle("width","auto");}},_onScroll:function(){this._headerContainerNode.set("scrollLeft",this._bodyContainerNode.get("scrollLeft"));},_syncScroll:function(){this._syncScrollX();this._syncScrollY();this._syncScrollOverhang();if(m.opera){this._headerContainerNode.set("scrollLeft",this._bodyContainerNode.get("scrollLeft"));if(!this.get("width")){document.body.style+=""; }}},_syncScrollY:function(){var o=this._parentTbodyNode,q=this._bodyContainerNode,p;if(!this.get("width")){p=(q.get("scrollHeight")>q.get("clientHeight"))?(o.get("parentNode").get("clientWidth")+19)+"px":(o.get("parentNode").get("clientWidth")+2)+"px";this._parentContainer.setStyle("width",p);}},_syncScrollX:function(){var o=this._parentTbodyNode,q=this._bodyContainerNode,p;this._headerContainerNode.set("scrollLeft",this._bodyContainerNode.get("scrollLeft"));if(!this.get("height")&&(m.ie)){p=(q.get("scrollWidth")>q.get("offsetWidth"))?(o.get("parentNode").get("offsetHeight")+18)+"px":o.get("parentNode").get("offsetHeight")+"px";q.setStyle("height",p);}if(o.get("rows").length===0){this._parentMsgNode.get("parentNode").setStyle("width",this._parentTheadNode.get("parentNode").get("offsetWidth")+"px");}else{this._parentMsgNode.get("parentNode").setStyle("width","");}},_syncScrollOverhang:function(){var o=this._bodyContainerNode,p=1;if((o.get("scrollHeight")>o.get("clientHeight"))||(o.get("scrollWidth")>o.get("clientWidth"))){p=18;}this._setOverhangValue(p);if(m.ie!==0&&this.get("_scroll")==="y"&&this._bodyContainerNode.get("scrollHeight")>this._bodyContainerNode.get("offsetHeight")){this._headerContainerNode.setStyle("width",this._parentContainer.get("width"));}},_setOverhangValue:function(p){var r=this.get("host"),t=r.get("columnset").get("definitions"),o=t.length,s=p+"px solid "+this.get("COLOR_COLUMNFILLER"),q=k.all("#"+this._parentContainer.get("id")+" ."+a+" table thead th");q.item(o-1).setStyle("borderRight",s);}});b.namespace("Plugin").DataTableScroll=n;},"@VERSION@",{requires:["datatable-base","plugin","stylesheet"]});YUI.add("datatable",function(a){},"@VERSION@",{use:["datatable-base","datatable-datasource","datatable-sort","datatable-scroll"]}); \ No newline at end of file diff --git a/build/datatable/datatable.js b/build/datatable/datatable.js index aecf42f01f6..3b677fdb1e1 100644 --- a/build/datatable/datatable.js +++ b/build/datatable/datatable.js @@ -2127,7 +2127,7 @@ Y.namespace("Plugin").DataTableSort = DataTableSort; -}, '@VERSION@' ,{lang:['en'], requires:['datatable-base','plugin','recordset-sort']}); +}, '@VERSION@' ,{requires:['datatable-base','plugin','recordset-sort'], lang:['en']}); YUI.add('datatable-scroll', function(Y) { /** diff --git a/build/datatype-date-format/datatype-date-format-debug.js b/build/datatype-date-format/datatype-date-format-debug.js index f2ff56b54a3..3afa92dac44 100644 --- a/build/datatype-date-format/datatype-date-format-debug.js +++ b/build/datatype-date-format/datatype-date-format-debug.js @@ -356,7 +356,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt); *
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype-date-format/datatype-date-format.js b/build/datatype-date-format/datatype-date-format.js
          index 7de62272ebd..8e4c736db79 100644
          --- a/build/datatype-date-format/datatype-date-format.js
          +++ b/build/datatype-date-format/datatype-date-format.js
          @@ -352,7 +352,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype-date/datatype-date-debug.js b/build/datatype-date/datatype-date-debug.js
          index bd497b2b256..5941846bcef 100644
          --- a/build/datatype-date/datatype-date-debug.js
          +++ b/build/datatype-date/datatype-date-debug.js
          @@ -402,7 +402,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype-date/datatype-date.js b/build/datatype-date/datatype-date.js
          index 8746c46a92b..d2eb1a1fc2d 100644
          --- a/build/datatype-date/datatype-date.js
          +++ b/build/datatype-date/datatype-date.js
          @@ -397,7 +397,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype/datatype-date-debug.js b/build/datatype/datatype-date-debug.js
          index bd497b2b256..5941846bcef 100644
          --- a/build/datatype/datatype-date-debug.js
          +++ b/build/datatype/datatype-date-debug.js
          @@ -402,7 +402,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype/datatype-date-format-debug.js b/build/datatype/datatype-date-format-debug.js
          index f2ff56b54a3..3afa92dac44 100644
          --- a/build/datatype/datatype-date-format-debug.js
          +++ b/build/datatype/datatype-date-format-debug.js
          @@ -356,7 +356,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype/datatype-date-format.js b/build/datatype/datatype-date-format.js
          index 7de62272ebd..8e4c736db79 100644
          --- a/build/datatype/datatype-date-format.js
          +++ b/build/datatype/datatype-date-format.js
          @@ -352,7 +352,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype/datatype-date.js b/build/datatype/datatype-date.js
          index 8746c46a92b..d2eb1a1fc2d 100644
          --- a/build/datatype/datatype-date.js
          +++ b/build/datatype/datatype-date.js
          @@ -397,7 +397,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype/datatype-debug.js b/build/datatype/datatype-debug.js
          index b8f08d4aa93..95b95897126 100644
          --- a/build/datatype/datatype-debug.js
          +++ b/build/datatype/datatype-debug.js
          @@ -549,7 +549,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/datatype/datatype.js b/build/datatype/datatype.js
          index 3758d118e79..8a555afc271 100644
          --- a/build/datatype/datatype.js
          +++ b/build/datatype/datatype.js
          @@ -542,7 +542,7 @@ Y.mix(Y.namespace("DataType.Date"), Dt);
            *  
        • For French french, we have no existing similar locale, so use * Y.DataType.Date.Locale["en"] as the base, and extend it: *
          - *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale, {
          + *      Y.DataType.Date.Locale["fr"] = Y.merge(Y.DataType.Date.Locale["en"], {
            *          a: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
            *          A: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
            *          b: ["jan", "fév", "mar", "avr", "mai", "jun", "jui", "aoû", "sep", "oct", "nov", "déc"],
          diff --git a/build/dd-drop-plugin/dd-drop-plugin-debug.js b/build/dd-drop-plugin/dd-drop-plugin-debug.js
          index 3f0264bb6b7..f11754c2c65 100644
          --- a/build/dd-drop-plugin/dd-drop-plugin-debug.js
          +++ b/build/dd-drop-plugin/dd-drop-plugin-debug.js
          @@ -42,4 +42,4 @@ YUI.add('dd-drop-plugin', function(Y) {
           
           
           
          -}, '@VERSION@' ,{requires:['dd-drop'], skinnable:false});
          +}, '@VERSION@' ,{skinnable:false, requires:['dd-drop']});
          diff --git a/build/dd-drop-plugin/dd-drop-plugin-min.js b/build/dd-drop-plugin/dd-drop-plugin-min.js
          index abf2030d863..7005d2e6c42 100644
          --- a/build/dd-drop-plugin/dd-drop-plugin-min.js
          +++ b/build/dd-drop-plugin/dd-drop-plugin-min.js
          @@ -1 +1 @@
          -YUI.add("dd-drop-plugin",function(a){var b=function(c){c.node=c.host;b.superclass.constructor.apply(this,arguments);};b.NAME="dd-drop-plugin";b.NS="drop";a.extend(b,a.DD.Drop);a.namespace("Plugin");a.Plugin.Drop=b;},"@VERSION@",{requires:["dd-drop"],skinnable:false});
          \ No newline at end of file
          +YUI.add("dd-drop-plugin",function(a){var b=function(c){c.node=c.host;b.superclass.constructor.apply(this,arguments);};b.NAME="dd-drop-plugin";b.NS="drop";a.extend(b,a.DD.Drop);a.namespace("Plugin");a.Plugin.Drop=b;},"@VERSION@",{skinnable:false,requires:["dd-drop"]});
          \ No newline at end of file
          diff --git a/build/dd-drop-plugin/dd-drop-plugin.js b/build/dd-drop-plugin/dd-drop-plugin.js
          index 3f0264bb6b7..f11754c2c65 100644
          --- a/build/dd-drop-plugin/dd-drop-plugin.js
          +++ b/build/dd-drop-plugin/dd-drop-plugin.js
          @@ -42,4 +42,4 @@ YUI.add('dd-drop-plugin', function(Y) {
           
           
           
          -}, '@VERSION@' ,{requires:['dd-drop'], skinnable:false});
          +}, '@VERSION@' ,{skinnable:false, requires:['dd-drop']});
          diff --git a/build/dd-gestures/dd-gestures-debug.js b/build/dd-gestures/dd-gestures-debug.js
          index e902aac29ec..fec1d752fa7 100644
          --- a/build/dd-gestures/dd-gestures-debug.js
          +++ b/build/dd-gestures/dd-gestures-debug.js
          @@ -37,4 +37,4 @@ YUI.add('dd-gestures', function(Y) {
           
           
           
          -}, '@VERSION@' ,{requires:['dd-drag', 'event-synthetic', 'event-gestures'], skinnable:false});
          +}, '@VERSION@' ,{skinnable:false, requires:['dd-drag', 'event-synthetic', 'event-gestures']});
          diff --git a/build/dd-gestures/dd-gestures-min.js b/build/dd-gestures/dd-gestures-min.js
          index b1d40aa6224..c360848bfc1 100644
          --- a/build/dd-gestures/dd-gestures-min.js
          +++ b/build/dd-gestures/dd-gestures-min.js
          @@ -1 +1 @@
          -YUI.add("dd-gestures",function(a){a.DD.Drag.START_EVENT="gesturemovestart";a.DD.Drag.prototype._prep=function(){this._dragThreshMet=false;var c=this.get("node"),b=a.DD.DDM;c.addClass(b.CSS_PREFIX+"-draggable");c.on(a.DD.Drag.START_EVENT,a.bind(this._handleMouseDownEvent,this),{minDistance:0,minTime:0});c.on("gesturemoveend",a.bind(this._handleMouseUp,this),{standAlone:true});c.on("dragstart",a.bind(this._fixDragStart,this));};a.DD.DDM._setupListeners=function(){var b=a.DD.DDM;this._createPG();this._active=true;a.one(a.config.doc).on("gesturemove",a.throttle(a.bind(b._move,b),b.get("throttleTime")),{standAlone:true});};},"@VERSION@",{requires:["dd-drag","event-synthetic","event-gestures"],skinnable:false});
          \ No newline at end of file
          +YUI.add("dd-gestures",function(a){a.DD.Drag.START_EVENT="gesturemovestart";a.DD.Drag.prototype._prep=function(){this._dragThreshMet=false;var c=this.get("node"),b=a.DD.DDM;c.addClass(b.CSS_PREFIX+"-draggable");c.on(a.DD.Drag.START_EVENT,a.bind(this._handleMouseDownEvent,this),{minDistance:0,minTime:0});c.on("gesturemoveend",a.bind(this._handleMouseUp,this),{standAlone:true});c.on("dragstart",a.bind(this._fixDragStart,this));};a.DD.DDM._setupListeners=function(){var b=a.DD.DDM;this._createPG();this._active=true;a.one(a.config.doc).on("gesturemove",a.throttle(a.bind(b._move,b),b.get("throttleTime")),{standAlone:true});};},"@VERSION@",{skinnable:false,requires:["dd-drag","event-synthetic","event-gestures"]});
          \ No newline at end of file
          diff --git a/build/dd-gestures/dd-gestures.js b/build/dd-gestures/dd-gestures.js
          index a5e0717d5d6..40eb98cec31 100644
          --- a/build/dd-gestures/dd-gestures.js
          +++ b/build/dd-gestures/dd-gestures.js
          @@ -35,4 +35,4 @@ YUI.add('dd-gestures', function(Y) {
           
           
           
          -}, '@VERSION@' ,{requires:['dd-drag', 'event-synthetic', 'event-gestures'], skinnable:false});
          +}, '@VERSION@' ,{skinnable:false, requires:['dd-drag', 'event-synthetic', 'event-gestures']});
          diff --git a/build/dd-plugin/dd-plugin-debug.js b/build/dd-plugin/dd-plugin-debug.js
          index cc8e7a9b408..5e9044522bf 100644
          --- a/build/dd-plugin/dd-plugin-debug.js
          +++ b/build/dd-plugin/dd-plugin-debug.js
          @@ -153,4 +153,4 @@ YUI.add('dd-plugin', function(Y) {
           
           
           
          -}, '@VERSION@' ,{requires:['dd-drag'], skinnable:false, optional:['dd-constrain', 'dd-proxy']});
          +}, '@VERSION@' ,{optional:['dd-constrain', 'dd-proxy'], skinnable:false, requires:['dd-drag']});
          diff --git a/build/dd-plugin/dd-plugin-min.js b/build/dd-plugin/dd-plugin-min.js
          index 738a80919dc..a724b92b9c0 100644
          --- a/build/dd-plugin/dd-plugin-min.js
          +++ b/build/dd-plugin/dd-plugin-min.js
          @@ -1 +1 @@
          -YUI.add("dd-plugin",function(c){var a=function(e){if(c.Widget&&e.host instanceof c.Widget){e.node=e.host.get("boundingBox");e.widget=e.host;}else{e.node=e.host;e.widget=false;}a.superclass.constructor.call(this,e);},b="drag:drag",d="drag:end";a.NAME="dd-plugin";a.NS="dd";c.extend(a,c.DD.Drag,{_widget:undefined,_stoppedPosition:undefined,_usesWidgetPosition:function(f){var e=false;if(f){e=(f.hasImpl&&f.hasImpl(c.WidgetPosition))?true:false;}return e;},initializer:function(e){this._widget=e.widget;if(this._usesWidgetPosition(this._widget)){this.on(b,this._setWidgetCoords);this.on(d,this._updateStopPosition);}},_setWidgetCoords:function(i){var h=this._stoppedPosition||i.target.nodeXY,f=i.target.realXY,g=[f[0]-h[0],f[1]-h[0]];if(g[0]!==0&&g[1]!==0){this._widget.set("xy",f);}else{if(g[0]===0){this._widget.set("y",f[1]);}else{if(g[1]===0){this._widget.set("x",f[0]);}}}},updateStopPosition:function(f){this._stoppedPosition=f.target.realXY;}});c.namespace("Plugin");c.Plugin.Drag=a;},"@VERSION@",{requires:["dd-drag"],skinnable:false,optional:["dd-constrain","dd-proxy"]});
          \ No newline at end of file
          +YUI.add("dd-plugin",function(c){var a=function(e){if(c.Widget&&e.host instanceof c.Widget){e.node=e.host.get("boundingBox");e.widget=e.host;}else{e.node=e.host;e.widget=false;}a.superclass.constructor.call(this,e);},b="drag:drag",d="drag:end";a.NAME="dd-plugin";a.NS="dd";c.extend(a,c.DD.Drag,{_widget:undefined,_stoppedPosition:undefined,_usesWidgetPosition:function(f){var e=false;if(f){e=(f.hasImpl&&f.hasImpl(c.WidgetPosition))?true:false;}return e;},initializer:function(e){this._widget=e.widget;if(this._usesWidgetPosition(this._widget)){this.on(b,this._setWidgetCoords);this.on(d,this._updateStopPosition);}},_setWidgetCoords:function(i){var h=this._stoppedPosition||i.target.nodeXY,f=i.target.realXY,g=[f[0]-h[0],f[1]-h[0]];if(g[0]!==0&&g[1]!==0){this._widget.set("xy",f);}else{if(g[0]===0){this._widget.set("y",f[1]);}else{if(g[1]===0){this._widget.set("x",f[0]);}}}},updateStopPosition:function(f){this._stoppedPosition=f.target.realXY;}});c.namespace("Plugin");c.Plugin.Drag=a;},"@VERSION@",{optional:["dd-constrain","dd-proxy"],skinnable:false,requires:["dd-drag"]});
          \ No newline at end of file
          diff --git a/build/dd-plugin/dd-plugin.js b/build/dd-plugin/dd-plugin.js
          index cc8e7a9b408..5e9044522bf 100644
          --- a/build/dd-plugin/dd-plugin.js
          +++ b/build/dd-plugin/dd-plugin.js
          @@ -153,4 +153,4 @@ YUI.add('dd-plugin', function(Y) {
           
           
           
          -}, '@VERSION@' ,{requires:['dd-drag'], skinnable:false, optional:['dd-constrain', 'dd-proxy']});
          +}, '@VERSION@' ,{optional:['dd-constrain', 'dd-proxy'], skinnable:false, requires:['dd-drag']});
          diff --git a/build/dd-scroll/dd-scroll-debug.js b/build/dd-scroll/dd-scroll-debug.js
          index 2df4fdf94cd..801dbf5d8b0 100644
          --- a/build/dd-scroll/dd-scroll-debug.js
          +++ b/build/dd-scroll/dd-scroll-debug.js
          @@ -423,4 +423,4 @@ YUI.add('dd-scroll', function(Y) {
           
           
           
          -}, '@VERSION@' ,{skinnable:false, optional:['dd-proxy'], requires:['dd-drag']});
          +}, '@VERSION@' ,{optional:['dd-proxy'], skinnable:false, requires:['dd-drag']});
          diff --git a/build/dd-scroll/dd-scroll-min.js b/build/dd-scroll/dd-scroll-min.js
          index 7ab30e02d13..415676a48c4 100644
          --- a/build/dd-scroll/dd-scroll-min.js
          +++ b/build/dd-scroll/dd-scroll-min.js
          @@ -1 +1 @@
          -YUI.add("dd-scroll",function(b){var h=function(){h.superclass.constructor.apply(this,arguments);},c,d,l="host",a="buffer",j="parentScroll",g="windowScroll",i="scrollTop",f="scrollLeft",e="offsetWidth",k="offsetHeight";h.ATTRS={parentScroll:{value:false,setter:function(m){if(m){return m;}return false;}},buffer:{value:30,validator:b.Lang.isNumber},scrollDelay:{value:235,validator:b.Lang.isNumber},host:{value:null},windowScroll:{value:false,validator:b.Lang.isBoolean},vertical:{value:true,validator:b.Lang.isBoolean},horizontal:{value:true,validator:b.Lang.isBoolean}};b.extend(h,b.Base,{_scrolling:null,_vpRegionCache:null,_dimCache:null,_scrollTimer:null,_getVPRegion:function(){var m={},o=this.get(j),u=this.get(a),s=this.get(g),y=((s)?[]:o.getXY()),v=((s)?"winWidth":e),q=((s)?"winHeight":k),x=((s)?o.get(i):y[1]),p=((s)?o.get(f):y[0]);m={top:x+u,right:(o.get(v)+p)-u,bottom:(o.get(q)+x)-u,left:p+u};this._vpRegionCache=m;return m;},initializer:function(){var m=this.get(l);m.after("drag:start",b.bind(this.start,this));m.after("drag:end",b.bind(this.end,this));m.on("drag:align",b.bind(this.align,this));b.one("win").on("scroll",b.bind(function(){this._vpRegionCache=null;},this));},_checkWinScroll:function(A){var z=this._getVPRegion(),m=this.get(l),o=this.get(g),t=m.lastXY,n=false,F=this.get(a),s=this.get(j),H=s.get(i),v=s.get(f),x=this._dimCache.w,C=this._dimCache.h,u=t[1]+C,y=t[1],E=t[0]+x,q=t[0],G=y,p=q,B=H,D=v;if(this.get("horizontal")){if(q<=z.left){n=true;p=t[0]-((o)?F:0);D=v-F;}if(E>=z.right){n=true;p=t[0]+((o)?F:0);D=v+F;}}if(this.get("vertical")){if(u>=z.bottom){n=true;G=t[1]+((o)?F:0);B=H+F;}if(y<=z.top){n=true;G=t[1]-((o)?F:0);B=H-F;}}if(B<0){B=0;G=t[1];}if(D<0){D=0;p=t[0];}if(G<0){G=t[1];}if(p<0){p=t[0];}if(A){m.actXY=[p,G];m._moveNode({node:s,top:B,left:D});if(!B&&!D){this._cancelScroll();}}else{if(n){this._initScroll();}else{this._cancelScroll();}}},_initScroll:function(){this._cancelScroll();this._scrollTimer=b.Lang.later(this.get("scrollDelay"),this,this._checkWinScroll,[true],true);},_cancelScroll:function(){this._scrolling=false;if(this._scrollTimer){this._scrollTimer.cancel();delete this._scrollTimer;}},align:function(m){if(this._scrolling){this._cancelScroll();m.preventDefault();}if(!this._scrolling){this._checkWinScroll();}},_setDimCache:function(){var m=this.get(l).get("dragNode");this._dimCache={h:m.get(k),w:m.get(e)};},start:function(){this._setDimCache();},end:function(m){this._dimCache=null;this._cancelScroll();},toString:function(){return h.NAME+" #"+this.get("node").get("id");}});b.namespace("Plugin");c=function(){c.superclass.constructor.apply(this,arguments);};c.ATTRS=b.merge(h.ATTRS,{windowScroll:{value:true,setter:function(m){if(m){this.set(j,b.one("win"));}return m;}}});b.extend(c,h,{initializer:function(){this.set("windowScroll",this.get("windowScroll"));}});c.NAME=c.NS="winscroll";b.Plugin.DDWinScroll=c;d=function(){d.superclass.constructor.apply(this,arguments);};d.ATTRS=b.merge(h.ATTRS,{node:{value:false,setter:function(m){var o=b.one(m);if(!o){if(m!==false){b.error("DDNodeScroll: Invalid Node Given: "+m);}}else{this.set(j,o);}return o;}}});b.extend(d,h,{initializer:function(){this.set("node",this.get("node"));}});d.NAME=d.NS="nodescroll";b.Plugin.DDNodeScroll=d;b.DD.Scroll=h;},"@VERSION@",{skinnable:false,optional:["dd-proxy"],requires:["dd-drag"]});
          \ No newline at end of file
          +YUI.add("dd-scroll",function(b){var h=function(){h.superclass.constructor.apply(this,arguments);},c,d,l="host",a="buffer",j="parentScroll",g="windowScroll",i="scrollTop",f="scrollLeft",e="offsetWidth",k="offsetHeight";h.ATTRS={parentScroll:{value:false,setter:function(m){if(m){return m;}return false;}},buffer:{value:30,validator:b.Lang.isNumber},scrollDelay:{value:235,validator:b.Lang.isNumber},host:{value:null},windowScroll:{value:false,validator:b.Lang.isBoolean},vertical:{value:true,validator:b.Lang.isBoolean},horizontal:{value:true,validator:b.Lang.isBoolean}};b.extend(h,b.Base,{_scrolling:null,_vpRegionCache:null,_dimCache:null,_scrollTimer:null,_getVPRegion:function(){var m={},o=this.get(j),u=this.get(a),s=this.get(g),y=((s)?[]:o.getXY()),v=((s)?"winWidth":e),q=((s)?"winHeight":k),x=((s)?o.get(i):y[1]),p=((s)?o.get(f):y[0]);m={top:x+u,right:(o.get(v)+p)-u,bottom:(o.get(q)+x)-u,left:p+u};this._vpRegionCache=m;return m;},initializer:function(){var m=this.get(l);m.after("drag:start",b.bind(this.start,this));m.after("drag:end",b.bind(this.end,this));m.on("drag:align",b.bind(this.align,this));b.one("win").on("scroll",b.bind(function(){this._vpRegionCache=null;},this));},_checkWinScroll:function(A){var z=this._getVPRegion(),m=this.get(l),o=this.get(g),t=m.lastXY,n=false,F=this.get(a),s=this.get(j),H=s.get(i),v=s.get(f),x=this._dimCache.w,C=this._dimCache.h,u=t[1]+C,y=t[1],E=t[0]+x,q=t[0],G=y,p=q,B=H,D=v;if(this.get("horizontal")){if(q<=z.left){n=true;p=t[0]-((o)?F:0);D=v-F;}if(E>=z.right){n=true;p=t[0]+((o)?F:0);D=v+F;}}if(this.get("vertical")){if(u>=z.bottom){n=true;G=t[1]+((o)?F:0);B=H+F;}if(y<=z.top){n=true;G=t[1]-((o)?F:0);B=H-F;}}if(B<0){B=0;G=t[1];}if(D<0){D=0;p=t[0];}if(G<0){G=t[1];}if(p<0){p=t[0];}if(A){m.actXY=[p,G];m._moveNode({node:s,top:B,left:D});if(!B&&!D){this._cancelScroll();}}else{if(n){this._initScroll();}else{this._cancelScroll();}}},_initScroll:function(){this._cancelScroll();this._scrollTimer=b.Lang.later(this.get("scrollDelay"),this,this._checkWinScroll,[true],true);},_cancelScroll:function(){this._scrolling=false;if(this._scrollTimer){this._scrollTimer.cancel();delete this._scrollTimer;}},align:function(m){if(this._scrolling){this._cancelScroll();m.preventDefault();}if(!this._scrolling){this._checkWinScroll();}},_setDimCache:function(){var m=this.get(l).get("dragNode");this._dimCache={h:m.get(k),w:m.get(e)};},start:function(){this._setDimCache();},end:function(m){this._dimCache=null;this._cancelScroll();},toString:function(){return h.NAME+" #"+this.get("node").get("id");}});b.namespace("Plugin");c=function(){c.superclass.constructor.apply(this,arguments);};c.ATTRS=b.merge(h.ATTRS,{windowScroll:{value:true,setter:function(m){if(m){this.set(j,b.one("win"));}return m;}}});b.extend(c,h,{initializer:function(){this.set("windowScroll",this.get("windowScroll"));}});c.NAME=c.NS="winscroll";b.Plugin.DDWinScroll=c;d=function(){d.superclass.constructor.apply(this,arguments);};d.ATTRS=b.merge(h.ATTRS,{node:{value:false,setter:function(m){var o=b.one(m);if(!o){if(m!==false){b.error("DDNodeScroll: Invalid Node Given: "+m);}}else{this.set(j,o);}return o;}}});b.extend(d,h,{initializer:function(){this.set("node",this.get("node"));}});d.NAME=d.NS="nodescroll";b.Plugin.DDNodeScroll=d;b.DD.Scroll=h;},"@VERSION@",{optional:["dd-proxy"],skinnable:false,requires:["dd-drag"]});
          \ No newline at end of file
          diff --git a/build/dd-scroll/dd-scroll.js b/build/dd-scroll/dd-scroll.js
          index 2df4fdf94cd..801dbf5d8b0 100644
          --- a/build/dd-scroll/dd-scroll.js
          +++ b/build/dd-scroll/dd-scroll.js
          @@ -423,4 +423,4 @@ YUI.add('dd-scroll', function(Y) {
           
           
           
          -}, '@VERSION@' ,{skinnable:false, optional:['dd-proxy'], requires:['dd-drag']});
          +}, '@VERSION@' ,{optional:['dd-proxy'], skinnable:false, requires:['dd-drag']});
          diff --git a/build/dd/dd-debug.js b/build/dd/dd-debug.js
          index 4402316b9b2..a1178319ff2 100644
          --- a/build/dd/dd-debug.js
          +++ b/build/dd/dd-debug.js
          @@ -3331,7 +3331,7 @@ YUI.add('dd-scroll', function(Y) {
           
           
           
          -}, '@VERSION@' ,{skinnable:false, optional:['dd-proxy'], requires:['dd-drag']});
          +}, '@VERSION@' ,{optional:['dd-proxy'], skinnable:false, requires:['dd-drag']});
           YUI.add('dd-drop', function(Y) {
           
           
          @@ -4216,5 +4216,5 @@ YUI.add('dd-delegate', function(Y) {
           }, '@VERSION@' ,{skinnable:false, requires:['dd-drag', 'event-mouseenter', 'dd-drop-plugin']});
           
           
          -YUI.add('dd', function(Y){}, '@VERSION@' ,{use:['dd-ddm-base', 'dd-ddm', 'dd-ddm-drop', 'dd-drag', 'dd-proxy', 'dd-constrain', 'dd-drop', 'dd-scroll', 'dd-delegate'], skinnable:false});
          +YUI.add('dd', function(Y){}, '@VERSION@' ,{skinnable:false, use:['dd-ddm-base', 'dd-ddm', 'dd-ddm-drop', 'dd-drag', 'dd-proxy', 'dd-constrain', 'dd-drop', 'dd-scroll', 'dd-delegate']});
           
          diff --git a/build/dd/dd-min.js b/build/dd/dd-min.js
          index d3742af85f0..840c660c304 100644
          --- a/build/dd/dd-min.js
          +++ b/build/dd/dd-min.js
          @@ -2,5 +2,5 @@ YUI.add("dd-ddm-base",function(b){var a=function(){a.superclass.constructor.appl
           }},this);return[c,b];}else{return c;}},_deactivateTargets:function(){var b=[],c,e=this.activeDrag,d=this.activeDrop;if(e&&d&&this.otherDrops[d]){if(!e.get("dragMode")){b=this.otherDrops;delete b[d];}else{c=this.getBestMatch(this.otherDrops,true);d=c[0];b=c[1];}e.get("node").removeClass(this.CSS_PREFIX+"-drag-over");if(d){d.fire("drop:hit",{drag:e,drop:d,others:b});e.fire("drag:drophit",{drag:e,drop:d,others:b});}}else{if(e&&e.get("dragging")){e.get("node").removeClass(this.CSS_PREFIX+"-drag-over");e.fire("drag:dropmiss",{pageX:e.lastXY[0],pageY:e.lastXY[1]});}else{}}this.activeDrop=null;a.each(this.targets,function(g,f){g._deactivateShim([]);},this);},_dropMove:function(){if(this._hasActiveShim()){this._handleTargetOver();}else{a.each(this.otherDrops,function(c,b){c._handleOut.apply(c,[]);});}},_lookup:function(){if(!this.useHash||this._noShim){return this.validDrops;}var b=[];a.each(this.validDrops,function(d,c){if(d.shim&&d.shim.inViewportRegion(false,d.region)){b[b.length]=d;}});return b;},_handleTargetOver:function(){var b=this._lookup();a.each(b,function(d,c){d._handleTargetOver.call(d);},this);},_regTarget:function(b){this.targets[this.targets.length]=b;},_unregTarget:function(c){var b=[],d;a.each(this.targets,function(f,e){if(f!=c){b[b.length]=f;}},this);this.targets=b;d=[];a.each(this.validDrops,function(f,e){if(f!==c){d[d.length]=f;}});this.validDrops=d;},getDrop:function(c){var b=false,d=a.one(c);if(d instanceof a.Node){a.each(this.targets,function(f,e){if(d.compareTo(f.get("node"))){b=f;}});}return b;}},true);},"@VERSION@",{skinnable:false,requires:["dd-ddm"]});YUI.add("dd-drag",function(d){var e=d.DD.DDM,r="node",g="dragging",m="dragNode",c="offsetHeight",k="offsetWidth",h="drag:mouseDown",b="drag:afterMouseDown",f="drag:removeHandle",l="drag:addHandle",p="drag:removeInvalid",q="drag:addInvalid",j="drag:start",i="drag:end",n="drag:drag",o="drag:align",a=function(t){this._lazyAddAttrs=false;a.superclass.constructor.apply(this,arguments);var s=e._regDrag(this);if(!s){d.error("Failed to register node, already in use: "+t.node);}};a.NAME="drag";a.START_EVENT="mousedown";a.ATTRS={node:{setter:function(s){var t=d.one(s);if(!t){d.error("DD.Drag: Invalid Node Given: "+s);}return t;}},dragNode:{setter:function(s){var t=d.one(s);if(!t){d.error("DD.Drag: Invalid dragNode Given: "+s);}return t;}},offsetNode:{value:true},startCentered:{value:false},clickPixelThresh:{value:e.get("clickPixelThresh")},clickTimeThresh:{value:e.get("clickTimeThresh")},lock:{value:false,setter:function(s){if(s){this.get(r).addClass(e.CSS_PREFIX+"-locked");}else{this.get(r).removeClass(e.CSS_PREFIX+"-locked");}return s;}},data:{value:false},move:{value:true},useShim:{value:true},activeHandle:{value:false},primaryButtonOnly:{value:true},dragging:{value:false},parent:{value:false},target:{value:false,setter:function(s){this._handleTarget(s);return s;}},dragMode:{value:null,setter:function(s){return e._setDragMode(s);}},groups:{value:["default"],getter:function(){if(!this._groups){this._groups={};}var s=[];d.each(this._groups,function(u,t){s[s.length]=t;});return s;},setter:function(s){this._groups={};d.each(s,function(u,t){this._groups[u]=true;},this);return s;}},handles:{value:null,setter:function(s){if(s){this._handles={};d.each(s,function(u,t){var w=u;if(u instanceof d.Node||u instanceof d.NodeList){w=u._yuid;}this._handles[w]=u;},this);}else{this._handles=null;}return s;}},bubbles:{setter:function(s){this.addTarget(s);return s;}},haltDown:{value:true}};d.extend(a,d.Base,{_bubbleTargets:d.DD.DDM,addToGroup:function(s){this._groups[s]=true;e._activateTargets();return this;},removeFromGroup:function(s){delete this._groups[s];e._activateTargets();return this;},target:null,_handleTarget:function(s){if(d.DD.Drop){if(s===false){if(this.target){e._unregTarget(this.target);this.target=null;}return false;}else{if(!d.Lang.isObject(s)){s={};}s.bubbleTargets=("bubbleTargets" in s)?s.bubbleTargets:d.Object.values(this._yuievt.targets);s.node=this.get(r);s.groups=s.groups||this.get("groups");this.target=new d.DD.Drop(s);}}else{return false;}},_groups:null,_createEvents:function(){this.publish(h,{defaultFn:this._defMouseDownFn,queuable:false,emitFacade:true,bubbles:true,prefix:"drag"});this.publish(o,{defaultFn:this._defAlignFn,queuable:false,emitFacade:true,bubbles:true,prefix:"drag"});this.publish(n,{defaultFn:this._defDragFn,queuable:false,emitFacade:true,bubbles:true,prefix:"drag"});this.publish(i,{defaultFn:this._defEndFn,preventedFn:this._prevEndFn,queuable:false,emitFacade:true,bubbles:true,prefix:"drag"});var s=[b,f,l,p,q,j,"drag:drophit","drag:dropmiss","drag:over","drag:enter","drag:exit"];d.each(s,function(u,t){this.publish(u,{type:u,emitFacade:true,bubbles:true,preventable:false,queuable:false,prefix:"drag"});},this);},_ev_md:null,_startTime:null,_endTime:null,_handles:null,_invalids:null,_invalidsDefault:{"textarea":true,"input":true,"a":true,"button":true,"select":true},_dragThreshMet:null,_fromTimeout:null,_clickTimeout:null,deltaXY:null,startXY:null,nodeXY:null,lastXY:null,actXY:null,realXY:null,mouseXY:null,region:null,_handleMouseUp:function(s){this.fire("drag:mouseup");this._fixIEMouseUp();if(e.activeDrag){e._end();}},_fixDragStart:function(s){s.preventDefault();},_ieSelectFix:function(){return false;},_ieSelectBack:null,_fixIEMouseDown:function(s){if(d.UA.ie){this._ieSelectBack=d.config.doc.body.onselectstart;d.config.doc.body.onselectstart=this._ieSelectFix;}},_fixIEMouseUp:function(){if(d.UA.ie){d.config.doc.body.onselectstart=this._ieSelectBack;}},_handleMouseDownEvent:function(s){this.fire(h,{ev:s});},_defMouseDownFn:function(t){var s=t.ev;this._dragThreshMet=false;this._ev_md=s;if(this.get("primaryButtonOnly")&&s.button>1){return false;}if(this.validClick(s)){this._fixIEMouseDown(s);if(this.get("haltDown")){s.halt();}else{s.preventDefault();}this._setStartPosition([s.pageX,s.pageY]);e.activeDrag=this;this._clickTimeout=d.later(this.get("clickTimeThresh"),this,this._timeoutCheck);}this.fire(b,{ev:s});},validClick:function(w){var v=false,z=false,s=w.target,u=null,t=null,x=null,y=false;
           if(this._handles){d.each(this._handles,function(A,B){if(A instanceof d.Node||A instanceof d.NodeList){if(!v){x=A;if(x instanceof d.Node){x=new d.NodeList(A._node);}x.each(function(C){if(C.contains(s)){v=true;}});}}else{if(d.Lang.isString(B)){if(s.test(B+", "+B+" *")&&!u){u=B;v=true;}}}});}else{z=this.get(r);if(z.contains(s)||z.compareTo(s)){v=true;}}if(v){if(this._invalids){d.each(this._invalids,function(A,B){if(d.Lang.isString(B)){if(s.test(B+", "+B+" *")){v=false;}}});}}if(v){if(u){t=w.currentTarget.all(u);y=false;t.each(function(B,A){if((B.contains(s)||B.compareTo(s))&&!y){y=true;this.set("activeHandle",B);}},this);}else{this.set("activeHandle",this.get(r));}}return v;},_setStartPosition:function(s){this.startXY=s;this.nodeXY=this.lastXY=this.realXY=this.get(r).getXY();if(this.get("offsetNode")){this.deltaXY=[(this.startXY[0]-this.nodeXY[0]),(this.startXY[1]-this.nodeXY[1])];}else{this.deltaXY=[0,0];}},_timeoutCheck:function(){if(!this.get("lock")&&!this._dragThreshMet&&this._ev_md){this._fromTimeout=this._dragThreshMet=true;this.start();this._alignNode([this._ev_md.pageX,this._ev_md.pageY],true);}},removeHandle:function(t){var s=t;if(t instanceof d.Node||t instanceof d.NodeList){s=t._yuid;}if(this._handles[s]){delete this._handles[s];this.fire(f,{handle:t});}return this;},addHandle:function(t){if(!this._handles){this._handles={};}var s=t;if(t instanceof d.Node||t instanceof d.NodeList){s=t._yuid;}this._handles[s]=t;this.fire(l,{handle:t});return this;},removeInvalid:function(s){if(this._invalids[s]){this._invalids[s]=null;delete this._invalids[s];this.fire(p,{handle:s});}return this;},addInvalid:function(s){if(d.Lang.isString(s)){this._invalids[s]=true;this.fire(q,{handle:s});}return this;},initializer:function(s){this.get(r).dd=this;if(!this.get(r).get("id")){var t=d.stamp(this.get(r));this.get(r).set("id",t);}this.actXY=[];this._invalids=d.clone(this._invalidsDefault,true);this._createEvents();if(!this.get(m)){this.set(m,this.get(r));}this.on("initializedChange",d.bind(this._prep,this));this.set("groups",this.get("groups"));},_prep:function(){this._dragThreshMet=false;var s=this.get(r);s.addClass(e.CSS_PREFIX+"-draggable");s.on(a.START_EVENT,d.bind(this._handleMouseDownEvent,this));s.on("mouseup",d.bind(this._handleMouseUp,this));s.on("dragstart",d.bind(this._fixDragStart,this));},_unprep:function(){var s=this.get(r);s.removeClass(e.CSS_PREFIX+"-draggable");s.detachAll();},start:function(){if(!this.get("lock")&&!this.get(g)){var t=this.get(r),s,u,v;this._startTime=(new Date()).getTime();e._start();t.addClass(e.CSS_PREFIX+"-dragging");this.fire(j,{pageX:this.nodeXY[0],pageY:this.nodeXY[1],startTime:this._startTime});t=this.get(m);v=this.nodeXY;s=t.get(k);u=t.get(c);if(this.get("startCentered")){this._setStartPosition([v[0]+(s/2),v[1]+(u/2)]);}this.region={"0":v[0],"1":v[1],area:0,top:v[1],right:v[0]+s,bottom:v[1]+u,left:v[0]};this.set(g,true);}return this;},end:function(){this._endTime=(new Date()).getTime();if(this._clickTimeout){this._clickTimeout.cancel();}this._dragThreshMet=this._fromTimeout=false;if(!this.get("lock")&&this.get(g)){this.fire(i,{pageX:this.lastXY[0],pageY:this.lastXY[1],startTime:this._startTime,endTime:this._endTime});}this.get(r).removeClass(e.CSS_PREFIX+"-dragging");this.set(g,false);this.deltaXY=[0,0];return this;},_defEndFn:function(s){this._fixIEMouseUp();this._ev_md=null;},_prevEndFn:function(s){this._fixIEMouseUp();this.get(m).setXY(this.nodeXY);this._ev_md=null;this.region=null;},_align:function(s){this.fire(o,{pageX:s[0],pageY:s[1]});},_defAlignFn:function(s){this.actXY=[s.pageX-this.deltaXY[0],s.pageY-this.deltaXY[1]];},_alignNode:function(s){this._align(s);this._moveNode();},_moveNode:function(s){var t=[],u=[],w=this.nodeXY,v=this.actXY;t[0]=(v[0]-this.lastXY[0]);t[1]=(v[1]-this.lastXY[1]);u[0]=(v[0]-this.nodeXY[0]);u[1]=(v[1]-this.nodeXY[1]);this.region={"0":v[0],"1":v[1],area:0,top:v[1],right:v[0]+this.get(m).get(k),bottom:v[1]+this.get(m).get(c),left:v[0]};this.fire(n,{pageX:v[0],pageY:v[1],scroll:s,info:{start:w,xy:v,delta:t,offset:u}});this.lastXY=v;},_defDragFn:function(s){if(this.get("move")){if(s.scroll){s.scroll.node.set("scrollTop",s.scroll.top);s.scroll.node.set("scrollLeft",s.scroll.left);}this.get(m).setXY([s.pageX,s.pageY]);this.realXY=[s.pageX,s.pageY];}},_move:function(u){if(this.get("lock")){return false;}else{this.mouseXY=[u.pageX,u.pageY];if(!this._dragThreshMet){var t=Math.abs(this.startXY[0]-u.pageX),s=Math.abs(this.startXY[1]-u.pageY);if(t>this.get("clickPixelThresh")||s>this.get("clickPixelThresh")){this._dragThreshMet=true;this.start();this._alignNode([u.pageX,u.pageY]);}}else{if(this._clickTimeout){this._clickTimeout.cancel();}this._alignNode([u.pageX,u.pageY]);}}},stopDrag:function(){if(this.get(g)){e._end();}return this;},destructor:function(){this._unprep();this.detachAll();if(this.target){this.target.destroy();}e._unregDrag(this);}});d.namespace("DD");d.DD.Drag=a;},"@VERSION@",{skinnable:false,requires:["dd-ddm-base"]});YUI.add("dd-proxy",function(h){var f=h.DD.DDM,b="node",c="dragNode",a="host",d=true,e,g=function(i){g.superclass.constructor.apply(this,arguments);};g.NAME="DDProxy";g.NS="proxy";g.ATTRS={host:{},moveOnEnd:{value:d},hideOnEnd:{value:d},resizeFrame:{value:d},positionProxy:{value:d},borderStyle:{value:"1px solid #808080"},cloneNode:{value:false}};e={_hands:null,_init:function(){if(!f._proxy){f._createFrame();h.on("domready",h.bind(this._init,this));return;}if(!this._hands){this._hands=[];}var k,j,l=this.get(a),i=l.get(c);if(i.compareTo(l.get(b))){if(f._proxy){l.set(c,f._proxy);}}h.each(this._hands,function(m){m.detach();});k=f.on("ddm:start",h.bind(function(){if(f.activeDrag===l){f._setFrame(l);}},this));j=f.on("ddm:end",h.bind(function(){if(l.get("dragging")){if(this.get("moveOnEnd")){l.get(b).setXY(l.lastXY);}if(this.get("hideOnEnd")){l.get(c).setStyle("display","none");}if(this.get("cloneNode")){l.get(c).remove();l.set(c,f._proxy);}}},this));this._hands=[k,j];},initializer:function(){this._init();},destructor:function(){var i=this.get(a);
           h.each(this._hands,function(j){j.detach();});i.set(c,i.get(b));},clone:function(){var i=this.get(a),k=i.get(b),j=k.cloneNode(true);delete j._yuid;j.setAttribute("id",h.guid());j.setStyle("position","absolute");k.get("parentNode").appendChild(j);i.set(c,j);return j;}};h.namespace("Plugin");h.extend(g,h.Base,e);h.Plugin.DDProxy=g;h.mix(f,{_createFrame:function(){if(!f._proxy){f._proxy=d;var j=h.Node.create("
          "),i=h.one("body");j.setStyles({position:"absolute",display:"none",zIndex:"999",top:"-999px",left:"-999px"});i.prepend(j);j.set("id",h.guid());j.addClass(f.CSS_PREFIX+"-proxy");f._proxy=j;}},_setFrame:function(j){var m=j.get(b),l=j.get(c),i,k="auto";i=f.activeDrag.get("activeHandle");if(i){k=i.getStyle("cursor");}if(k=="auto"){k=f.get("dragCursor");}l.setStyles({visibility:"hidden",display:"block",cursor:k,border:j.proxy.get("borderStyle")});if(j.proxy.get("cloneNode")){l=j.proxy.clone();}if(j.proxy.get("resizeFrame")){l.setStyles({height:m.get("offsetHeight")+"px",width:m.get("offsetWidth")+"px"});}if(j.proxy.get("positionProxy")){l.setXY(j.nodeXY);}l.setStyle("visibility","visible");}});},"@VERSION@",{skinnable:false,requires:["dd-drag"]});YUI.add("dd-constrain",function(b){var l="dragNode",n="offsetHeight",e="offsetWidth",q="host",f="tickXArray",o="tickYArray",p=b.DD.DDM,d="top",j="right",m="bottom",c="left",k="view",h=null,i="drag:tickAlignX",g="drag:tickAlignY",a=function(r){this._lazyAddAttrs=false;a.superclass.constructor.apply(this,arguments);};a.NAME="ddConstrained";a.NS="con";a.ATTRS={host:{},stickX:{value:false},stickY:{value:false},tickX:{value:false},tickY:{value:false},tickXArray:{value:false},tickYArray:{value:false},gutter:{value:"0",setter:function(r){return b.DD.DDM.cssSizestoObject(r);}},constrain:{value:k,setter:function(r){var s=b.one(r);if(s){r=s;}return r;}},constrain2region:{setter:function(s){return this.set("constrain",s);}},constrain2node:{setter:function(r){return this.set("constrain",b.one(r));}},constrain2view:{setter:function(r){return this.set("constrain",k);}},cacheRegion:{value:true}};h={_lastTickXFired:null,_lastTickYFired:null,initializer:function(){this._createEvents();this.get(q).on("drag:end",b.bind(this._handleEnd,this));this.get(q).on("drag:start",b.bind(this._handleStart,this));this.get(q).after("drag:align",b.bind(this.align,this));this.get(q).after("drag:drag",b.bind(this.drag,this));},_createEvents:function(){var r=this;var s=[i,g];b.each(s,function(u,t){this.publish(u,{type:u,emitFacade:true,bubbles:true,queuable:false,prefix:"drag"});},this);},_handleEnd:function(){this._lastTickYFired=null;this._lastTickXFired=null;},_handleStart:function(){this.resetCache();},_regionCache:null,_cacheRegion:function(){this._regionCache=this.get("constrain").get("region");},resetCache:function(){this._regionCache=null;},_getConstraint:function(){var r=this.get("constrain"),s=this.get("gutter"),t;if(r){if(r instanceof b.Node){if(!this._regionCache){b.on("resize",b.bind(this._cacheRegion,this),b.config.win);this._cacheRegion();}t=b.clone(this._regionCache);if(!this.get("cacheRegion")){this.resetCache();}}else{if(b.Lang.isObject(r)){t=b.clone(r);}}}if(!r||!t){r=k;}if(r===k){t=this.get(q).get(l).get("viewportRegion");}b.each(s,function(u,v){if((v==j)||(v==m)){t[v]-=u;}else{t[v]+=u;}});return t;},getRegion:function(w){var u={},v=null,s=null,t=this.get(q);u=this._getConstraint();if(w){v=t.get(l).get(n);s=t.get(l).get(e);u[j]=u[j]-s;u[m]=u[m]-v;}return u;},_checkRegion:function(s){var u=s,w=this.getRegion(),v=this.get(q),x=v.get(l).get(n),t=v.get(l).get(e);if(u[1]>(w[m]-x)){s[1]=(w[m]-x);}if(w[d]>u[1]){s[1]=w[d];}if(u[0]>(w[j]-t)){s[0]=(w[j]-t);}if(w[c]>u[0]){s[0]=w[c];}return s;},inRegion:function(t){t=t||this.get(q).get(l).getXY();var s=this._checkRegion([t[0],t[1]]),r=false;if((t[0]===s[0])&&(t[1]===s[1])){r=true;}return r;},align:function(){var u=this.get(q),s=[u.actXY[0],u.actXY[1]],t=this.getRegion(true);if(this.get("stickX")){s[1]=(u.startXY[1]-u.deltaXY[1]);}if(this.get("stickY")){s[0]=(u.startXY[0]-u.deltaXY[0]);}if(t){s=this._checkRegion(s);}s=this._checkTicks(s,t);u.actXY=s;},drag:function(v){var u=this.get(q),s=this.get("tickX"),t=this.get("tickY"),r=[u.actXY[0],u.actXY[1]];if((b.Lang.isNumber(s)||this.get(f))&&(this._lastTickXFired!==r[0])){this._tickAlignX();this._lastTickXFired=r[0];}if((b.Lang.isNumber(t)||this.get(o))&&(this._lastTickYFired!==r[1])){this._tickAlignY();this._lastTickYFired=r[1];}},_checkTicks:function(y,w){var v=this.get(q),x=(v.startXY[0]-v.deltaXY[0]),u=(v.startXY[1]-v.deltaXY[1]),s=this.get("tickX"),t=this.get("tickY");if(s&&!this.get(f)){y[0]=p._calcTicks(y[0],x,s,w[c],w[j]);}if(t&&!this.get(o)){y[1]=p._calcTicks(y[1],u,t,w[d],w[m]);}if(this.get(f)){y[0]=p._calcTickArray(y[0],this.get(f),w[c],w[j]);}if(this.get(o)){y[1]=p._calcTickArray(y[1],this.get(o),w[d],w[m]);}return y;},_tickAlignX:function(){this.fire(i);},_tickAlignY:function(){this.fire(g);}};b.namespace("Plugin");b.extend(a,b.Base,h);b.Plugin.DDConstrained=a;b.mix(p,{_calcTicks:function(y,x,u,w,v){var s=((y-x)/u),t=Math.floor(s),r=Math.ceil(s);if((t!==0)||(r!==0)){if((s>=t)&&(s<=r)){y=(x+(u*t));if(w&&v){if(yv){y=(x+(u*(t-1)));}}}}return y;},_calcTickArray:function(z,A,y,v){var s=0,w=A.length,u=0,t,r,x;if(!A||(A.length===0)){return z;}else{if(A[0]>=z){return A[0];}else{for(s=0;s=z){t=z-A[s];r=A[u]-z;x=(r>t)?A[s]:A[u];if(y&&v){if(x>v){if(A[s]){x=A[s];}else{x=A[w-1];}}}return x;}}return A[A.length-1];}}}});},"@VERSION@",{skinnable:false,requires:["dd-drag"]});YUI.add("dd-scroll",function(b){var h=function(){h.superclass.constructor.apply(this,arguments);},c,d,l="host",a="buffer",j="parentScroll",g="windowScroll",i="scrollTop",f="scrollLeft",e="offsetWidth",k="offsetHeight";h.ATTRS={parentScroll:{value:false,setter:function(m){if(m){return m;}return false;}},buffer:{value:30,validator:b.Lang.isNumber},scrollDelay:{value:235,validator:b.Lang.isNumber},host:{value:null},windowScroll:{value:false,validator:b.Lang.isBoolean},vertical:{value:true,validator:b.Lang.isBoolean},horizontal:{value:true,validator:b.Lang.isBoolean}}; -b.extend(h,b.Base,{_scrolling:null,_vpRegionCache:null,_dimCache:null,_scrollTimer:null,_getVPRegion:function(){var m={},o=this.get(j),u=this.get(a),s=this.get(g),y=((s)?[]:o.getXY()),v=((s)?"winWidth":e),q=((s)?"winHeight":k),x=((s)?o.get(i):y[1]),p=((s)?o.get(f):y[0]);m={top:x+u,right:(o.get(v)+p)-u,bottom:(o.get(q)+x)-u,left:p+u};this._vpRegionCache=m;return m;},initializer:function(){var m=this.get(l);m.after("drag:start",b.bind(this.start,this));m.after("drag:end",b.bind(this.end,this));m.on("drag:align",b.bind(this.align,this));b.one("win").on("scroll",b.bind(function(){this._vpRegionCache=null;},this));},_checkWinScroll:function(A){var z=this._getVPRegion(),m=this.get(l),o=this.get(g),t=m.lastXY,n=false,F=this.get(a),s=this.get(j),H=s.get(i),v=s.get(f),x=this._dimCache.w,C=this._dimCache.h,u=t[1]+C,y=t[1],E=t[0]+x,q=t[0],G=y,p=q,B=H,D=v;if(this.get("horizontal")){if(q<=z.left){n=true;p=t[0]-((o)?F:0);D=v-F;}if(E>=z.right){n=true;p=t[0]+((o)?F:0);D=v+F;}}if(this.get("vertical")){if(u>=z.bottom){n=true;G=t[1]+((o)?F:0);B=H+F;}if(y<=z.top){n=true;G=t[1]-((o)?F:0);B=H-F;}}if(B<0){B=0;G=t[1];}if(D<0){D=0;p=t[0];}if(G<0){G=t[1];}if(p<0){p=t[0];}if(A){m.actXY=[p,G];m._moveNode({node:s,top:B,left:D});if(!B&&!D){this._cancelScroll();}}else{if(n){this._initScroll();}else{this._cancelScroll();}}},_initScroll:function(){this._cancelScroll();this._scrollTimer=b.Lang.later(this.get("scrollDelay"),this,this._checkWinScroll,[true],true);},_cancelScroll:function(){this._scrolling=false;if(this._scrollTimer){this._scrollTimer.cancel();delete this._scrollTimer;}},align:function(m){if(this._scrolling){this._cancelScroll();m.preventDefault();}if(!this._scrolling){this._checkWinScroll();}},_setDimCache:function(){var m=this.get(l).get("dragNode");this._dimCache={h:m.get(k),w:m.get(e)};},start:function(){this._setDimCache();},end:function(m){this._dimCache=null;this._cancelScroll();},toString:function(){return h.NAME+" #"+this.get("node").get("id");}});b.namespace("Plugin");c=function(){c.superclass.constructor.apply(this,arguments);};c.ATTRS=b.merge(h.ATTRS,{windowScroll:{value:true,setter:function(m){if(m){this.set(j,b.one("win"));}return m;}}});b.extend(c,h,{initializer:function(){this.set("windowScroll",this.get("windowScroll"));}});c.NAME=c.NS="winscroll";b.Plugin.DDWinScroll=c;d=function(){d.superclass.constructor.apply(this,arguments);};d.ATTRS=b.merge(h.ATTRS,{node:{value:false,setter:function(m){var o=b.one(m);if(!o){if(m!==false){b.error("DDNodeScroll: Invalid Node Given: "+m);}}else{this.set(j,o);}return o;}}});b.extend(d,h,{initializer:function(){this.set("node",this.get("node"));}});d.NAME=d.NS="nodescroll";b.Plugin.DDNodeScroll=d;b.DD.Scroll=h;},"@VERSION@",{skinnable:false,optional:["dd-proxy"],requires:["dd-drag"]});YUI.add("dd-drop",function(a){var b="node",g=a.DD.DDM,f="offsetHeight",c="offsetWidth",i="drop:over",h="drop:enter",d="drop:exit",e=function(){this._lazyAddAttrs=false;e.superclass.constructor.apply(this,arguments);a.on("domready",a.bind(function(){a.later(100,this,this._createShim);},this));g._regTarget(this);};e.NAME="drop";e.ATTRS={node:{setter:function(j){var k=a.one(j);if(!k){a.error("DD.Drop: Invalid Node Given: "+j);}return k;}},groups:{value:["default"],setter:function(j){this._groups={};a.each(j,function(m,l){this._groups[m]=true;},this);return j;}},padding:{value:"0",setter:function(j){return g.cssSizestoObject(j);}},lock:{value:false,setter:function(j){if(j){this.get(b).addClass(g.CSS_PREFIX+"-drop-locked");}else{this.get(b).removeClass(g.CSS_PREFIX+"-drop-locked");}return j;}},bubbles:{setter:function(j){this.addTarget(j);return j;}},useShim:{value:true,setter:function(j){a.DD.DDM._noShim=!j;return j;}}};a.extend(e,a.Base,{_bubbleTargets:a.DD.DDM,addToGroup:function(j){this._groups[j]=true;return this;},removeFromGroup:function(j){delete this._groups[j];return this;},_createEvents:function(){var j=[i,h,d,"drop:hit"];a.each(j,function(m,l){this.publish(m,{type:m,emitFacade:true,preventable:false,bubbles:true,queuable:false,prefix:"drop"});},this);},_valid:null,_groups:null,shim:null,region:null,overTarget:null,inGroup:function(j){this._valid=false;var k=false;a.each(j,function(m,l){if(this._groups[m]){k=true;this._valid=true;}},this);return k;},initializer:function(j){a.later(100,this,this._createEvents);var k=this.get(b),l;if(!k.get("id")){l=a.stamp(k);k.set("id",l);}k.addClass(g.CSS_PREFIX+"-drop");this.set("groups",this.get("groups"));},destructor:function(){g._unregTarget(this);if(this.shim&&(this.shim!==this.get(b))){this.shim.detachAll();this.shim.remove();this.shim=null;}this.get(b).removeClass(g.CSS_PREFIX+"-drop");this.detachAll();},_deactivateShim:function(){if(!this.shim){return false;}this.get(b).removeClass(g.CSS_PREFIX+"-drop-active-valid");this.get(b).removeClass(g.CSS_PREFIX+"-drop-active-invalid");this.get(b).removeClass(g.CSS_PREFIX+"-drop-over");if(this.get("useShim")){this.shim.setStyles({top:"-999px",left:"-999px",zIndex:"1"});}this.overTarget=false;},_activateShim:function(){if(!g.activeDrag){return false;}if(this.get(b)===g.activeDrag.get(b)){return false;}if(this.get("lock")){return false;}var j=this.get(b);if(this.inGroup(g.activeDrag.get("groups"))){j.removeClass(g.CSS_PREFIX+"-drop-active-invalid");j.addClass(g.CSS_PREFIX+"-drop-active-valid");g._addValid(this);this.overTarget=false;if(!this.get("useShim")){this.shim=this.get(b);}this.sizeShim();}else{g._removeValid(this);j.removeClass(g.CSS_PREFIX+"-drop-active-valid");j.addClass(g.CSS_PREFIX+"-drop-active-invalid");}},sizeShim:function(){if(!g.activeDrag){return false;}if(this.get(b)===g.activeDrag.get(b)){return false;}if(this.get("lock")){return false;}if(!this.shim){a.later(100,this,this.sizeShim);return false;}var o=this.get(b),m=o.get(f),k=o.get(c),r=o.getXY(),q=this.get("padding"),j,n,l;k=k+q.left+q.right;m=m+q.top+q.bottom;r[0]=r[0]-q.left;r[1]=r[1]-q.top;if(g.activeDrag.get("dragMode")===g.INTERSECT){j=g.activeDrag;n=j.get(b).get(f);l=j.get(b).get(c);m=(m+n);k=(k+l);r[0]=r[0]-(l-j.deltaXY[0]); -r[1]=r[1]-(n-j.deltaXY[1]);}if(this.get("useShim")){this.shim.setStyles({height:m+"px",width:k+"px",top:r[1]+"px",left:r[0]+"px"});}this.region={"0":r[0],"1":r[1],area:0,top:r[1],right:r[0]+k,bottom:r[1]+m,left:r[0]};},_createShim:function(){if(!g._pg){a.later(10,this,this._createShim);return;}if(this.shim){return;}var j=this.get("node");if(this.get("useShim")){j=a.Node.create('
          ');j.setStyles({height:this.get(b).get(f)+"px",width:this.get(b).get(c)+"px",backgroundColor:"yellow",opacity:".5",zIndex:"1",overflow:"hidden",top:"-900px",left:"-900px",position:"absolute"});g._pg.appendChild(j);j.on("mouseover",a.bind(this._handleOverEvent,this));j.on("mouseout",a.bind(this._handleOutEvent,this));}this.shim=j;},_handleTargetOver:function(){if(g.isOverTarget(this)){this.get(b).addClass(g.CSS_PREFIX+"-drop-over");g.activeDrop=this;g.otherDrops[this]=this;if(this.overTarget){g.activeDrag.fire("drag:over",{drop:this,drag:g.activeDrag});this.fire(i,{drop:this,drag:g.activeDrag});}else{if(g.activeDrag.get("dragging")){this.overTarget=true;this.fire(h,{drop:this,drag:g.activeDrag});g.activeDrag.fire("drag:enter",{drop:this,drag:g.activeDrag});g.activeDrag.get(b).addClass(g.CSS_PREFIX+"-drag-over");}}}else{this._handleOut();}},_handleOverEvent:function(){this.shim.setStyle("zIndex","999");g._addActiveShim(this);},_handleOutEvent:function(){this.shim.setStyle("zIndex","1");g._removeActiveShim(this);},_handleOut:function(j){if(!g.isOverTarget(this)||j){if(this.overTarget){this.overTarget=false;if(!j){g._removeActiveShim(this);}if(g.activeDrag){this.get(b).removeClass(g.CSS_PREFIX+"-drop-over");g.activeDrag.get(b).removeClass(g.CSS_PREFIX+"-drag-over");this.fire(d);g.activeDrag.fire("drag:exit",{drop:this});delete g.otherDrops[this];}}}}});a.DD.Drop=e;},"@VERSION@",{skinnable:false,requires:["dd-ddm-drop","dd-drag"]});YUI.add("dd-delegate",function(e){var d=function(f){d.superclass.constructor.apply(this,arguments);},c="container",b="nodes",a=e.Node.create("
          Temp Node
          ");e.extend(d,e.Base,{_bubbleTargets:e.DD.DDM,dd:null,_shimState:null,_handles:null,_onNodeChange:function(f){this.set("dragNode",f.newVal);},_afterDragEnd:function(f){e.DD.DDM._noShim=this._shimState;this.set("lastNode",this.dd.get("node"));this.get("lastNode").removeClass(e.DD.DDM.CSS_PREFIX+"-dragging");this.dd._unprep();this.dd.set("node",a);},_delMouseDown:function(h){var g=h.currentTarget,f=this.dd;if(g.test(this.get(b))&&!g.test(this.get("invalid"))){this._shimState=e.DD.DDM._noShim;e.DD.DDM._noShim=true;this.set("currentNode",g);f.set("node",g);if(f.proxy){f.set("dragNode",e.DD.DDM._proxy);}else{f.set("dragNode",g);}f._prep();f.fire("drag:mouseDown",{ev:h});}},_onMouseEnter:function(f){this._shimState=e.DD.DDM._noShim;e.DD.DDM._noShim=true;},_onMouseLeave:function(f){e.DD.DDM._noShim=this._shimState;},initializer:function(g){this._handles=[];var h=e.clone(this.get("dragConfig")||{}),f=this.get(c);h.node=a.cloneNode(true);h.bubbleTargets=this;if(this.get("handles")){h.handles=this.get("handles");}this.dd=new e.DD.Drag(h);this.dd.after("drag:end",e.bind(this._afterDragEnd,this));this.dd.on("dragNodeChange",e.bind(this._onNodeChange,this));this.dd.after("drag:mouseup",function(){this._unprep();});this._handles.push(e.delegate(e.DD.Drag.START_EVENT,e.bind(this._delMouseDown,this),f,this.get(b)));this._handles.push(e.on("mouseenter",e.bind(this._onMouseEnter,this),f));this._handles.push(e.on("mouseleave",e.bind(this._onMouseLeave,this),f));e.later(50,this,this.syncTargets);e.DD.DDM.regDelegate(this);},syncTargets:function(){if(!e.Plugin.Drop||this.get("destroyed")){return;}var g,f,h;if(this.get("target")){g=e.one(this.get(c)).all(this.get(b));f=this.dd.get("groups");h=this.get("dragConfig");if(h&&"groups" in h){f=h.groups;}g.each(function(j){this.createDrop(j,f);},this);}return this;},createDrop:function(h,f){var g={useShim:false,bubbleTargets:this};if(!h.drop){h.plug(e.Plugin.Drop,g);}h.drop.set("groups",f);return h;},destructor:function(){if(this.dd){this.dd.destroy();}if(e.Plugin.Drop){var f=e.one(this.get(c)).all(this.get(b));f.unplug(e.Plugin.Drop);}e.each(this._handles,function(g){g.detach();});}},{NAME:"delegate",ATTRS:{container:{value:"body"},nodes:{value:".dd-draggable"},invalid:{value:"input, select, button, a, textarea"},lastNode:{value:a},currentNode:{value:a},dragNode:{value:a},over:{value:false},target:{value:false},dragConfig:{value:null},handles:{value:null}}});e.mix(e.DD.DDM,{_delegates:[],regDelegate:function(f){this._delegates.push(f);},getDelegate:function(g){var f=null;g=e.one(g);e.each(this._delegates,function(h){if(g.test(h.get(c))){f=h;}},this);return f;}});e.namespace("DD");e.DD.Delegate=d;},"@VERSION@",{skinnable:false,requires:["dd-drag","event-mouseenter","dd-drop-plugin"]});YUI.add("dd",function(a){},"@VERSION@",{use:["dd-ddm-base","dd-ddm","dd-ddm-drop","dd-drag","dd-proxy","dd-constrain","dd-drop","dd-scroll","dd-delegate"],skinnable:false}); \ No newline at end of file +b.extend(h,b.Base,{_scrolling:null,_vpRegionCache:null,_dimCache:null,_scrollTimer:null,_getVPRegion:function(){var m={},o=this.get(j),u=this.get(a),s=this.get(g),y=((s)?[]:o.getXY()),v=((s)?"winWidth":e),q=((s)?"winHeight":k),x=((s)?o.get(i):y[1]),p=((s)?o.get(f):y[0]);m={top:x+u,right:(o.get(v)+p)-u,bottom:(o.get(q)+x)-u,left:p+u};this._vpRegionCache=m;return m;},initializer:function(){var m=this.get(l);m.after("drag:start",b.bind(this.start,this));m.after("drag:end",b.bind(this.end,this));m.on("drag:align",b.bind(this.align,this));b.one("win").on("scroll",b.bind(function(){this._vpRegionCache=null;},this));},_checkWinScroll:function(A){var z=this._getVPRegion(),m=this.get(l),o=this.get(g),t=m.lastXY,n=false,F=this.get(a),s=this.get(j),H=s.get(i),v=s.get(f),x=this._dimCache.w,C=this._dimCache.h,u=t[1]+C,y=t[1],E=t[0]+x,q=t[0],G=y,p=q,B=H,D=v;if(this.get("horizontal")){if(q<=z.left){n=true;p=t[0]-((o)?F:0);D=v-F;}if(E>=z.right){n=true;p=t[0]+((o)?F:0);D=v+F;}}if(this.get("vertical")){if(u>=z.bottom){n=true;G=t[1]+((o)?F:0);B=H+F;}if(y<=z.top){n=true;G=t[1]-((o)?F:0);B=H-F;}}if(B<0){B=0;G=t[1];}if(D<0){D=0;p=t[0];}if(G<0){G=t[1];}if(p<0){p=t[0];}if(A){m.actXY=[p,G];m._moveNode({node:s,top:B,left:D});if(!B&&!D){this._cancelScroll();}}else{if(n){this._initScroll();}else{this._cancelScroll();}}},_initScroll:function(){this._cancelScroll();this._scrollTimer=b.Lang.later(this.get("scrollDelay"),this,this._checkWinScroll,[true],true);},_cancelScroll:function(){this._scrolling=false;if(this._scrollTimer){this._scrollTimer.cancel();delete this._scrollTimer;}},align:function(m){if(this._scrolling){this._cancelScroll();m.preventDefault();}if(!this._scrolling){this._checkWinScroll();}},_setDimCache:function(){var m=this.get(l).get("dragNode");this._dimCache={h:m.get(k),w:m.get(e)};},start:function(){this._setDimCache();},end:function(m){this._dimCache=null;this._cancelScroll();},toString:function(){return h.NAME+" #"+this.get("node").get("id");}});b.namespace("Plugin");c=function(){c.superclass.constructor.apply(this,arguments);};c.ATTRS=b.merge(h.ATTRS,{windowScroll:{value:true,setter:function(m){if(m){this.set(j,b.one("win"));}return m;}}});b.extend(c,h,{initializer:function(){this.set("windowScroll",this.get("windowScroll"));}});c.NAME=c.NS="winscroll";b.Plugin.DDWinScroll=c;d=function(){d.superclass.constructor.apply(this,arguments);};d.ATTRS=b.merge(h.ATTRS,{node:{value:false,setter:function(m){var o=b.one(m);if(!o){if(m!==false){b.error("DDNodeScroll: Invalid Node Given: "+m);}}else{this.set(j,o);}return o;}}});b.extend(d,h,{initializer:function(){this.set("node",this.get("node"));}});d.NAME=d.NS="nodescroll";b.Plugin.DDNodeScroll=d;b.DD.Scroll=h;},"@VERSION@",{optional:["dd-proxy"],skinnable:false,requires:["dd-drag"]});YUI.add("dd-drop",function(a){var b="node",g=a.DD.DDM,f="offsetHeight",c="offsetWidth",i="drop:over",h="drop:enter",d="drop:exit",e=function(){this._lazyAddAttrs=false;e.superclass.constructor.apply(this,arguments);a.on("domready",a.bind(function(){a.later(100,this,this._createShim);},this));g._regTarget(this);};e.NAME="drop";e.ATTRS={node:{setter:function(j){var k=a.one(j);if(!k){a.error("DD.Drop: Invalid Node Given: "+j);}return k;}},groups:{value:["default"],setter:function(j){this._groups={};a.each(j,function(m,l){this._groups[m]=true;},this);return j;}},padding:{value:"0",setter:function(j){return g.cssSizestoObject(j);}},lock:{value:false,setter:function(j){if(j){this.get(b).addClass(g.CSS_PREFIX+"-drop-locked");}else{this.get(b).removeClass(g.CSS_PREFIX+"-drop-locked");}return j;}},bubbles:{setter:function(j){this.addTarget(j);return j;}},useShim:{value:true,setter:function(j){a.DD.DDM._noShim=!j;return j;}}};a.extend(e,a.Base,{_bubbleTargets:a.DD.DDM,addToGroup:function(j){this._groups[j]=true;return this;},removeFromGroup:function(j){delete this._groups[j];return this;},_createEvents:function(){var j=[i,h,d,"drop:hit"];a.each(j,function(m,l){this.publish(m,{type:m,emitFacade:true,preventable:false,bubbles:true,queuable:false,prefix:"drop"});},this);},_valid:null,_groups:null,shim:null,region:null,overTarget:null,inGroup:function(j){this._valid=false;var k=false;a.each(j,function(m,l){if(this._groups[m]){k=true;this._valid=true;}},this);return k;},initializer:function(j){a.later(100,this,this._createEvents);var k=this.get(b),l;if(!k.get("id")){l=a.stamp(k);k.set("id",l);}k.addClass(g.CSS_PREFIX+"-drop");this.set("groups",this.get("groups"));},destructor:function(){g._unregTarget(this);if(this.shim&&(this.shim!==this.get(b))){this.shim.detachAll();this.shim.remove();this.shim=null;}this.get(b).removeClass(g.CSS_PREFIX+"-drop");this.detachAll();},_deactivateShim:function(){if(!this.shim){return false;}this.get(b).removeClass(g.CSS_PREFIX+"-drop-active-valid");this.get(b).removeClass(g.CSS_PREFIX+"-drop-active-invalid");this.get(b).removeClass(g.CSS_PREFIX+"-drop-over");if(this.get("useShim")){this.shim.setStyles({top:"-999px",left:"-999px",zIndex:"1"});}this.overTarget=false;},_activateShim:function(){if(!g.activeDrag){return false;}if(this.get(b)===g.activeDrag.get(b)){return false;}if(this.get("lock")){return false;}var j=this.get(b);if(this.inGroup(g.activeDrag.get("groups"))){j.removeClass(g.CSS_PREFIX+"-drop-active-invalid");j.addClass(g.CSS_PREFIX+"-drop-active-valid");g._addValid(this);this.overTarget=false;if(!this.get("useShim")){this.shim=this.get(b);}this.sizeShim();}else{g._removeValid(this);j.removeClass(g.CSS_PREFIX+"-drop-active-valid");j.addClass(g.CSS_PREFIX+"-drop-active-invalid");}},sizeShim:function(){if(!g.activeDrag){return false;}if(this.get(b)===g.activeDrag.get(b)){return false;}if(this.get("lock")){return false;}if(!this.shim){a.later(100,this,this.sizeShim);return false;}var o=this.get(b),m=o.get(f),k=o.get(c),r=o.getXY(),q=this.get("padding"),j,n,l;k=k+q.left+q.right;m=m+q.top+q.bottom;r[0]=r[0]-q.left;r[1]=r[1]-q.top;if(g.activeDrag.get("dragMode")===g.INTERSECT){j=g.activeDrag;n=j.get(b).get(f);l=j.get(b).get(c);m=(m+n);k=(k+l);r[0]=r[0]-(l-j.deltaXY[0]); +r[1]=r[1]-(n-j.deltaXY[1]);}if(this.get("useShim")){this.shim.setStyles({height:m+"px",width:k+"px",top:r[1]+"px",left:r[0]+"px"});}this.region={"0":r[0],"1":r[1],area:0,top:r[1],right:r[0]+k,bottom:r[1]+m,left:r[0]};},_createShim:function(){if(!g._pg){a.later(10,this,this._createShim);return;}if(this.shim){return;}var j=this.get("node");if(this.get("useShim")){j=a.Node.create('
          ');j.setStyles({height:this.get(b).get(f)+"px",width:this.get(b).get(c)+"px",backgroundColor:"yellow",opacity:".5",zIndex:"1",overflow:"hidden",top:"-900px",left:"-900px",position:"absolute"});g._pg.appendChild(j);j.on("mouseover",a.bind(this._handleOverEvent,this));j.on("mouseout",a.bind(this._handleOutEvent,this));}this.shim=j;},_handleTargetOver:function(){if(g.isOverTarget(this)){this.get(b).addClass(g.CSS_PREFIX+"-drop-over");g.activeDrop=this;g.otherDrops[this]=this;if(this.overTarget){g.activeDrag.fire("drag:over",{drop:this,drag:g.activeDrag});this.fire(i,{drop:this,drag:g.activeDrag});}else{if(g.activeDrag.get("dragging")){this.overTarget=true;this.fire(h,{drop:this,drag:g.activeDrag});g.activeDrag.fire("drag:enter",{drop:this,drag:g.activeDrag});g.activeDrag.get(b).addClass(g.CSS_PREFIX+"-drag-over");}}}else{this._handleOut();}},_handleOverEvent:function(){this.shim.setStyle("zIndex","999");g._addActiveShim(this);},_handleOutEvent:function(){this.shim.setStyle("zIndex","1");g._removeActiveShim(this);},_handleOut:function(j){if(!g.isOverTarget(this)||j){if(this.overTarget){this.overTarget=false;if(!j){g._removeActiveShim(this);}if(g.activeDrag){this.get(b).removeClass(g.CSS_PREFIX+"-drop-over");g.activeDrag.get(b).removeClass(g.CSS_PREFIX+"-drag-over");this.fire(d);g.activeDrag.fire("drag:exit",{drop:this});delete g.otherDrops[this];}}}}});a.DD.Drop=e;},"@VERSION@",{skinnable:false,requires:["dd-ddm-drop","dd-drag"]});YUI.add("dd-delegate",function(e){var d=function(f){d.superclass.constructor.apply(this,arguments);},c="container",b="nodes",a=e.Node.create("
          Temp Node
          ");e.extend(d,e.Base,{_bubbleTargets:e.DD.DDM,dd:null,_shimState:null,_handles:null,_onNodeChange:function(f){this.set("dragNode",f.newVal);},_afterDragEnd:function(f){e.DD.DDM._noShim=this._shimState;this.set("lastNode",this.dd.get("node"));this.get("lastNode").removeClass(e.DD.DDM.CSS_PREFIX+"-dragging");this.dd._unprep();this.dd.set("node",a);},_delMouseDown:function(h){var g=h.currentTarget,f=this.dd;if(g.test(this.get(b))&&!g.test(this.get("invalid"))){this._shimState=e.DD.DDM._noShim;e.DD.DDM._noShim=true;this.set("currentNode",g);f.set("node",g);if(f.proxy){f.set("dragNode",e.DD.DDM._proxy);}else{f.set("dragNode",g);}f._prep();f.fire("drag:mouseDown",{ev:h});}},_onMouseEnter:function(f){this._shimState=e.DD.DDM._noShim;e.DD.DDM._noShim=true;},_onMouseLeave:function(f){e.DD.DDM._noShim=this._shimState;},initializer:function(g){this._handles=[];var h=e.clone(this.get("dragConfig")||{}),f=this.get(c);h.node=a.cloneNode(true);h.bubbleTargets=this;if(this.get("handles")){h.handles=this.get("handles");}this.dd=new e.DD.Drag(h);this.dd.after("drag:end",e.bind(this._afterDragEnd,this));this.dd.on("dragNodeChange",e.bind(this._onNodeChange,this));this.dd.after("drag:mouseup",function(){this._unprep();});this._handles.push(e.delegate(e.DD.Drag.START_EVENT,e.bind(this._delMouseDown,this),f,this.get(b)));this._handles.push(e.on("mouseenter",e.bind(this._onMouseEnter,this),f));this._handles.push(e.on("mouseleave",e.bind(this._onMouseLeave,this),f));e.later(50,this,this.syncTargets);e.DD.DDM.regDelegate(this);},syncTargets:function(){if(!e.Plugin.Drop||this.get("destroyed")){return;}var g,f,h;if(this.get("target")){g=e.one(this.get(c)).all(this.get(b));f=this.dd.get("groups");h=this.get("dragConfig");if(h&&"groups" in h){f=h.groups;}g.each(function(j){this.createDrop(j,f);},this);}return this;},createDrop:function(h,f){var g={useShim:false,bubbleTargets:this};if(!h.drop){h.plug(e.Plugin.Drop,g);}h.drop.set("groups",f);return h;},destructor:function(){if(this.dd){this.dd.destroy();}if(e.Plugin.Drop){var f=e.one(this.get(c)).all(this.get(b));f.unplug(e.Plugin.Drop);}e.each(this._handles,function(g){g.detach();});}},{NAME:"delegate",ATTRS:{container:{value:"body"},nodes:{value:".dd-draggable"},invalid:{value:"input, select, button, a, textarea"},lastNode:{value:a},currentNode:{value:a},dragNode:{value:a},over:{value:false},target:{value:false},dragConfig:{value:null},handles:{value:null}}});e.mix(e.DD.DDM,{_delegates:[],regDelegate:function(f){this._delegates.push(f);},getDelegate:function(g){var f=null;g=e.one(g);e.each(this._delegates,function(h){if(g.test(h.get(c))){f=h;}},this);return f;}});e.namespace("DD");e.DD.Delegate=d;},"@VERSION@",{skinnable:false,requires:["dd-drag","event-mouseenter","dd-drop-plugin"]});YUI.add("dd",function(a){},"@VERSION@",{skinnable:false,use:["dd-ddm-base","dd-ddm","dd-ddm-drop","dd-drag","dd-proxy","dd-constrain","dd-drop","dd-scroll","dd-delegate"]}); \ No newline at end of file diff --git a/build/dd/dd.js b/build/dd/dd.js index 37c0c403d6b..42eb134402e 100644 --- a/build/dd/dd.js +++ b/build/dd/dd.js @@ -3328,7 +3328,7 @@ YUI.add('dd-scroll', function(Y) { -}, '@VERSION@' ,{skinnable:false, optional:['dd-proxy'], requires:['dd-drag']}); +}, '@VERSION@' ,{optional:['dd-proxy'], skinnable:false, requires:['dd-drag']}); YUI.add('dd-drop', function(Y) { @@ -4212,5 +4212,5 @@ YUI.add('dd-delegate', function(Y) { }, '@VERSION@' ,{skinnable:false, requires:['dd-drag', 'event-mouseenter', 'dd-drop-plugin']}); -YUI.add('dd', function(Y){}, '@VERSION@' ,{use:['dd-ddm-base', 'dd-ddm', 'dd-ddm-drop', 'dd-drag', 'dd-proxy', 'dd-constrain', 'dd-drop', 'dd-scroll', 'dd-delegate'], skinnable:false}); +YUI.add('dd', function(Y){}, '@VERSION@' ,{skinnable:false, use:['dd-ddm-base', 'dd-ddm', 'dd-ddm-drop', 'dd-drag', 'dd-proxy', 'dd-constrain', 'dd-drop', 'dd-scroll', 'dd-delegate']}); diff --git a/build/editor/editor-debug.js b/build/editor/editor-debug.js index 75d853bf40a..5b76590426f 100644 --- a/build/editor/editor-debug.js +++ b/build/editor/editor-debug.js @@ -4678,5 +4678,5 @@ YUI.add('editor-br', function(Y) { }, '@VERSION@' ,{skinnable:false, requires:['editor-base']}); -YUI.add('editor', function(Y){}, '@VERSION@' ,{use:['frame', 'selection', 'exec-command', 'editor-base', 'editor-para', 'editor-br', 'editor-bidi', 'editor-tab', 'createlink-base'], skinnable:false}); +YUI.add('editor', function(Y){}, '@VERSION@' ,{skinnable:false, use:['frame', 'selection', 'exec-command', 'editor-base', 'editor-para', 'editor-br', 'editor-bidi', 'editor-tab', 'createlink-base']}); diff --git a/build/editor/editor-min.js b/build/editor/editor-min.js index 90d58fefabd..d7acf504047 100644 --- a/build/editor/editor-min.js +++ b/build/editor/editor-min.js @@ -6,4 +6,4 @@ var e=this.getInstance(),d=new e.Selection();if(d.isCollapsed&&(this._lastKey!=3 e.after("ready",d.bind(this._afterFrameReady,this));e.addTarget(this);this.frame=e;this.publish("nodeChange",{emitFacade:true,bubbles:true,defaultFn:this._defNodeChangeFn});},destructor:function(){this.frame.destroy();this.detachAll();},copyStyles:function(h,g){if(h.test("a")){return;}var e=["color","fontSize","fontFamily","backgroundColor","fontStyle"],f={};d.each(e,function(i){f[i]=h.getStyle(i);});if(h.ancestor("b,strong")){f.fontWeight="bold";}if(h.ancestor("u")){if(!f.textDecoration){f.textDecoration="underline";}}g.setStyles(f);},_lastBookmark:null,_resolveChangedNode:function(i){var h=this.getInstance(),f,e,g;if(h&&i&&i.test("html")){f=h.one(a).one(b);while(!g){if(f){e=f.one(b);if(e){f=e;}else{g=true;}}else{g=true;}}if(f){if(f.test("br")){if(f.previous()){f=f.previous();}else{f=f.get("parentNode");}}if(f){i=f;}}}return i;},_defNodeChangeFn:function(s){var j=(new Date()).getTime();var q=this.getInstance(),i,t,p=q.Selection.DEFAULT_BLOCK_TAG;if(d.UA.ie){try{i=q.config.doc.selection.createRange();if(i.getBookmark){this._lastBookmark=i.getBookmark();}}catch(g){}}s.changedNode=this._resolveChangedNode(s.changedNode);switch(s.changedType){case"keydown":if(!d.UA.gecko){if(!c.NC_KEYS[s.changedEvent.keyCode]&&!s.changedEvent.shiftKey&&!s.changedEvent.ctrlKey&&(s.changedEvent.keyCode!==13)){}}break;case"tab":if(!s.changedNode.test("li, li *")&&!s.changedEvent.shiftKey){s.changedEvent.frameEvent.preventDefault();if(d.UA.webkit){this.execCommand("inserttext","\t");}else{if(d.UA.gecko){this.frame.exec._command("inserthtml",c.TABKEY);}else{if(d.UA.ie){i=new q.Selection();if(i._selection.pasteHTML){i._selection.pasteHTML(c.TABKEY);}else{console.log("IE9 is here.. SHould be default behaviour now");this.execCommand("inserthtml",c.TABKEY);}}}}}break;}if(d.UA.webkit&&s.commands&&(s.commands.indent||s.commands.outdent)){var u=q.all(".webkit-indent-blockquote");if(u.size()){u.setStyle("margin","");}}var o=this.getDomPath(s.changedNode,false),f={},n,h,l=[],m="",k="";if(s.commands){f=s.commands;}d.each(o,function(z){var v=z.tagName.toLowerCase(),A=c.TAG2CMD[v];if(A){f[A]=1;}var y=z.currentStyle||z.style;if((""+y.fontWeight)=="bold"){f.bold=1;}if(d.UA.ie){if(y.fontWeight>400){f.bold=1;}}if(y.fontStyle=="italic"){f.italic=1;}if(y.textDecoration=="underline"){f.underline=1;}if(y.textDecoration=="line-through"){f.strikethrough=1;}var B=q.one(z);if(B.getStyle("fontFamily")){var x=B.getStyle("fontFamily").split(",")[0].toLowerCase();if(x){n=x;}if(n){n=n.replace(/'/g,"").replace(/"/g,"");}}h=c.NORMALIZE_FONTSIZE(B);var w=z.className.split(" ");d.each(w,function(C){if(C!==""&&(C.substr(0,4)!=="yui_")){l.push(C);}});m=c.FILTER_RGB(B.getStyle("color"));var e=c.FILTER_RGB(y.backgroundColor);if(e!=="transparent"){if(e!==""){k=e;}}});s.dompath=q.all(o);s.classNames=l;s.commands=f;if(!s.fontFamily){s.fontFamily=n;}if(!s.fontSize){s.fontSize=h;}if(!s.fontColor){s.fontColor=m;}if(!s.backgroundColor){s.backgroundColor=k;}var r=(new Date()).getTime();},getDomPath:function(g,e){var i=[],f,h=this.frame.getInstance();f=h.Node.getDOMNode(g);while(f!==null){if((f===h.config.doc.documentElement)||(f===h.config.doc)||!f.tagName){f=null;break;}if(!h.DOM.inDoc(f)){f=null;break;}if(f.nodeName&&f.nodeType&&(f.nodeType==1)){i.push(f);}if(f==h.config.doc.body){f=null;break;}f=f.parentNode;}if(i.length===0){i[0]=h.config.doc.body;}if(e){return h.all(i.reverse());}else{return i.reverse();}},_afterFrameReady:function(){var e=this.frame.getInstance();this.frame.on("dom:mouseup",d.bind(this._onFrameMouseUp,this));this.frame.on("dom:mousedown",d.bind(this._onFrameMouseDown,this));this.frame.on("dom:keydown",d.bind(this._onFrameKeyDown,this));if(d.UA.ie){this.frame.on("dom:activate",d.bind(this._onFrameActivate,this));this.frame.on("dom:beforedeactivate",d.bind(this._beforeFrameDeactivate,this));}this.frame.on("dom:keyup",d.bind(this._onFrameKeyUp,this));this.frame.on("dom:keypress",d.bind(this._onFrameKeyPress,this));this.frame.on("dom:paste",d.bind(this._onPaste,this));e.Selection.filter();this.fire("ready");},_beforeFrameDeactivate:function(h){if(h.frameTarget.test("html")){return;}var g=this.getInstance(),f=g.config.doc.selection.createRange();if(f.compareEndPoints&&!f.compareEndPoints("StartToEnd",f)){f.pasteHTML('');}},_onFrameActivate:function(i){if(i.frameTarget.test("html")){return;}var h=this.getInstance(),g=new h.Selection(),f=g.createRange(),j=h.all("#yui-ie-cursor");if(j.size()){j.each(function(m){m.set("id","");if(f.moveToElementText){try{f.moveToElementText(m._node);var k=f.move("character",-1);if(k===-1){f.move("character",1);}f.select();f.text="";}catch(l){}}m.remove();});}},_onPaste:function(f){this.fire("nodeChange",{changedNode:f.frameTarget,changedType:"paste",changedEvent:f.frameEvent});},_onFrameMouseUp:function(f){this.fire("nodeChange",{changedNode:f.frameTarget,changedType:"mouseup",changedEvent:f.frameEvent});},_onFrameMouseDown:function(f){this.fire("nodeChange",{changedNode:f.frameTarget,changedType:"mousedown",changedEvent:f.frameEvent});},_currentSelection:null,_currentSelectionTimer:null,_currentSelectionClear:null,_onFrameKeyDown:function(h){var g,f;if(!this._currentSelection){if(this._currentSelectionTimer){this._currentSelectionTimer.cancel();}this._currentSelectionTimer=d.later(850,this,function(){this._currentSelectionClear=true;});g=this.frame.getInstance();f=new g.Selection(h);this._currentSelection=f;}else{f=this._currentSelection;}g=this.frame.getInstance();f=new g.Selection();this._currentSelection=f;if(f&&f.anchorNode){this.fire("nodeChange",{changedNode:f.anchorNode,changedType:"keydown",changedEvent:h.frameEvent});if(c.NC_KEYS[h.keyCode]){this.fire("nodeChange",{changedNode:f.anchorNode,changedType:c.NC_KEYS[h.keyCode],changedEvent:h.frameEvent});this.fire("nodeChange",{changedNode:f.anchorNode,changedType:c.NC_KEYS[h.keyCode]+"-down",changedEvent:h.frameEvent});}}},_onFrameKeyPress:function(g){var f=this._currentSelection;if(f&&f.anchorNode){this.fire("nodeChange",{changedNode:f.anchorNode,changedType:"keypress",changedEvent:g.frameEvent}); if(c.NC_KEYS[g.keyCode]){this.fire("nodeChange",{changedNode:f.anchorNode,changedType:c.NC_KEYS[g.keyCode]+"-press",changedEvent:g.frameEvent});}}},_onFrameKeyUp:function(h){var g=this.frame.getInstance(),f=new g.Selection(h);if(f&&f.anchorNode){this.fire("nodeChange",{changedNode:f.anchorNode,changedType:"keyup",selection:f,changedEvent:h.frameEvent});if(c.NC_KEYS[h.keyCode]){this.fire("nodeChange",{changedNode:f.anchorNode,changedType:c.NC_KEYS[h.keyCode]+"-up",selection:f,changedEvent:h.frameEvent});}}if(this._currentSelectionClear){this._currentSelectionClear=this._currentSelection=null;}},execCommand:function(j,l){var g=this.frame.execCommand(j,l),i=this.frame.getInstance(),h=new i.Selection(),f={},k={changedNode:h.anchorNode,changedType:"execcommand",nodes:g};switch(j){case"forecolor":k.fontColor=l;break;case"backcolor":k.backgroundColor=l;break;case"fontsize":k.fontSize=l;break;case"fontname":k.fontFamily=l;break;}f[j]=1;k.commands=f;this.fire("nodeChange",k);return g;},getInstance:function(){return this.frame.getInstance();},render:function(e){this.frame.set("content",this.get("content"));this.frame.render(e);return this;},focus:function(e){this.frame.focus(e);return this;},show:function(){this.frame.show();return this;},hide:function(){this.frame.hide();return this;},getContent:function(){var e="",f=this.getInstance();if(f&&f.Selection){e=f.Selection.unfilter();}e=e.replace(/ _yuid="([^>]*)"/g,"");return e;}},{NORMALIZE_FONTSIZE:function(g){var e=g.getStyle("fontSize"),f=e;switch(e){case"-webkit-xxx-large":e="48px";break;case"xx-large":e="32px";break;case"x-large":e="24px";break;case"large":e="18px";break;case"medium":e="16px";break;case"small":e="13px";break;case"x-small":e="10px";break;}if(f!==e){g.setStyle("fontSize",e);}return e;},TABKEY:'    ',FILTER_RGB:function(h){if(h.toLowerCase().indexOf("rgb")!=-1){var k=new RegExp("(.*?)rgb\\s*?\\(\\s*?([0-9]+).*?,\\s*?([0-9]+).*?,\\s*?([0-9]+).*?\\)(.*?)","gi");var f=h.replace(k,"$1,$2,$3,$4,$5").split(",");if(f.length==5){var j=parseInt(f[1],10).toString(16);var i=parseInt(f[2],10).toString(16);var e=parseInt(f[3],10).toString(16);j=j.length==1?"0"+j:j;i=i.length==1?"0"+i:i;e=e.length==1?"0"+e:e;h="#"+j+i+e;}}return h;},TAG2CMD:{"b":"bold","strong":"bold","i":"italic","em":"italic","u":"underline","sup":"superscript","sub":"subscript","img":"insertimage","a":"createlink","ul":"insertunorderedlist","ol":"insertorderedlist"},NC_KEYS:{8:"backspace",9:"tab",13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",46:"delete"},USE:["substitute","node","selector-css3","selection","stylesheet"],NAME:"editorBase",STRINGS:{title:"Rich Text Editor"},ATTRS:{content:{value:'
          ',setter:function(e){if(e.substr(0,1)==="\n"){e=e.substr(1);}if(e===""){e='
          ';}if(e===" "){if(d.UA.gecko){e='
          ';}}return this.frame.set("content",e);},getter:function(){return this.frame.get("content");}},dir:{writeOnce:true,value:"ltr"},linkedcss:{value:"",setter:function(e){if(this.frame){this.frame.set("linkedcss",e);}return e;}},extracss:{value:false,setter:function(e){if(this.frame){this.frame.set("extracss",e);}return e;}},defaultblock:{value:"p"}}});d.EditorBase=c;},"@VERSION@",{skinnable:false,requires:["base","frame","node","exec-command","selection"]});YUI.add("editor-lists",function(f){var e=function(){e.superclass.constructor.apply(this,arguments);},b="li",c="ol",d="ul",a="host";f.extend(e,f.Base,{_onNodeChange:function(l){var j=this.get(a).getInstance(),g,o,p,h,i,m,n=false,q,k=false;if(l.changedType==="tab"){if(l.changedNode.test(b+", "+b+" *")){l.changedEvent.halt();l.preventDefault();o=l.changedNode;i=l.changedEvent.shiftKey;m=o.ancestor(c+","+d);q=d;if(m.get("tagName").toLowerCase()===c){q=c;}if(!o.test(b)){o=o.ancestor(b);}if(i){if(o.ancestor(b)){o.ancestor(b).insert(o,"after");n=true;k=true;}}else{if(o.previous(b)){h=j.Node.create("<"+q+">");o.previous(b).append(h);h.append(o);n=true;}}}if(n){if(!o.test(b)){o=o.ancestor(b);}o.all(e.REMOVE).remove();if(f.UA.ie){o=o.append(e.NON).one(e.NON_SEL);}(new j.Selection()).selectNode(o,true,k);}}},initializer:function(){this.get(a).on("nodeChange",f.bind(this._onNodeChange,this));}},{NON:' ',NON_SEL:"span.yui-non",REMOVE:"br",NAME:"editorLists",NS:"lists",ATTRS:{host:{value:false}}});f.namespace("Plugin");f.Plugin.EditorLists=e;},"@VERSION@",{skinnable:false,requires:["editor-base"]});YUI.add("editor-bidi",function(a){var b=function(){b.superclass.constructor.apply(this,arguments);},i="host",h="dir",f="BODY",d="nodeChange",e="bidiContextChange",c=f+" > p",g="style";a.extend(b,a.Base,{lastDirection:null,firstEvent:null,_checkForChange:function(){var k=this.get(i),m=k.getInstance(),l=new m.Selection(),j,n;if(l.isCollapsed){j=b.blockParent(l.focusNode);if(j){n=j.getStyle("direction");if(n!==this.lastDirection){k.fire(e,{changedTo:n});this.lastDirection=n;}}}else{k.fire(e,{changedTo:"select"});this.lastDirection=null;}},_afterNodeChange:function(j){if(this.firstEvent||b.EVENTS[j.changedType]){this._checkForChange();this.firstEvent=false;}},_afterMouseUp:function(j){this._checkForChange();this.firstEvent=false;},initializer:function(){var j=this.get(i);this.firstEvent=true;j.after(d,a.bind(this._afterNodeChange,this));j.after("dom:mouseup",a.bind(this._afterMouseUp,this));}},{EVENTS:{"backspace-up":true,"pageup-up":true,"pagedown-down":true,"end-up":true,"home-up":true,"left-up":true,"up-up":true,"right-up":true,"down-up":true,"delete-up":true},BLOCKS:a.Selection.BLOCKS,DIV_WRAPPER:"
          ",blockParent:function(l,k){var j=l,n,m;if(!j){j=a.one(f);}if(!j.test(b.BLOCKS)){j=j.ancestor(b.BLOCKS);}if(k&&j.test(f)){n=a.Node.create(b.DIV_WRAPPER);j.get("children").each(function(p,o){if(o===0){m=p;}else{n.append(p);}});m.replace(n);n.prepend(m);j=n;}return j;},_NODE_SELECTED:"bidiSelected",addParents:function(m){var j,l,k;for(j=0; j p",h="p",g="
          ",i="firstChild",e="li";a.extend(d,a.Base,{_fixFirstPara:function(){var p=this.get(k),r=p.getInstance(),q,s,l=r.config.doc.body,o=l.innerHTML,m=((o.length)?true:false);if(o===g){o="";m=false;}l.innerHTML="<"+h+">"+o+r.Selection.CURSOR+"";s=r.one(b);q=new r.Selection();q.selectNode(s,true,m);},_onNodeChange:function(Q){var F=this.get(k),q=F.getInstance(),x,D,C,S,N,H=q.Selection.DEFAULT_BLOCK_TAG,z,o,s,O,v,l,G,L,u,M,U,R,J,B,A,P=":last-child";switch(Q.changedType){case"enter-up":var m=((this._lastPara)?this._lastPara:Q.changedNode),T=m.one("br.yui-cursor");if(this._lastPara){delete this._lastPara;}if(T){if(T.previous()||T.next()){if(T.ancestor(h)){T.remove();}}}if(!m.test(H)){var E=m.ancestor(H);if(E){m=E;E=null;}}if(m.test(H)){var I=m.previous(),K,w,y=false;if(I){K=I.one(P);while(!y){if(K){w=K.one(P);if(w){K=w;}else{y=true;}}else{y=true;}}if(K){F.copyStyles(K,m);}}}break;case"enter":if(a.UA.ie){if(Q.changedNode.test("br")){Q.changedNode.remove();}else{if(Q.changedNode.test("p, span")){var T=Q.changedNode.one("br.yui-cursor");if(T){T.remove();}}}}if(a.UA.webkit){if(Q.changedEvent.shiftKey){F.execCommand("insertbr");Q.changedEvent.preventDefault();}}if(a.UA.gecko&&F.get("defaultblock")!=="p"){C=Q.changedNode;if(!C.test(e)&&!C.ancestor(e)){if(!C.test(H)){C=C.ancestor(H);}S=q.Node.create("<"+H+">");C.insert(S,"after");N=new q.Selection();if(N.anchorOffset){z=N.anchorNode.get("textContent");D=q.one(q.config.doc.createTextNode(z.substr(0,N.anchorOffset)));o=q.one(q.config.doc.createTextNode(z.substr(N.anchorOffset)));O=N.anchorNode;O.setContent("");l=O.cloneNode();l.append(o);G=false;u=O;while(!G){u=u.get(j);if(u&&!u.test(H)){L=u.cloneNode();L.set("innerHTML","");L.append(l);s=u.get("childNodes");var r=false;s.each(function(n){if(r){L.append(n);}if(n===O){r=true;}});O=u;l=L;}else{G=true;}}o=l;N.anchorNode.append(D);if(o){S.append(o);}}if(S.get(i)){S=S.get(i);}S.prepend(q.Selection.CURSOR);N.focusCursor(true,true);x=q.Selection.getText(S);if(x!==""){q.Selection.cleanCursor();}Q.changedEvent.preventDefault();}}break;case"keyup":if(a.UA.gecko){if(q.config.doc&&q.config.doc.body&&q.config.doc.body.innerHTML.length<2){this._fixFirstPara();}}break;case"backspace-up":case"backspace-down":case"delete-up":if(!a.UA.ie){M=q.all(b);R=q.one(f);if(M.item(0)){R=M.item(0);}U=R.one("br");if(U){U.removeAttribute("id");U.removeAttribute("class");}D=q.Selection.getText(R);D=D.replace(/ /g,"").replace(/\n/g,"");B=R.all("img");if(D.length===0&&!B.size()){if(!R.test(h)){this._fixFirstPara();}J=null;if(Q.changedNode&&Q.changedNode.test(h)){J=Q.changedNode;}if(!J&&F._lastPara&&F._lastPara.inDoc()){J=F._lastPara;}if(J&&!J.test(h)){J=J.ancestor(h);}if(J){if(!J.previous()&&J.get(j)&&J.get(j).test(f)){Q.changedEvent.frameEvent.halt();}}}if(a.UA.webkit){if(Q.changedNode){R=Q.changedNode;if(R.test("li")&&(!R.previous()&&!R.next())){x=R.get("innerHTML").replace(g,"");if(x===""){if(R.get(j)){R.get(j).replace(q.Node.create(g));Q.changedEvent.frameEvent.halt();Q.preventDefault();q.Selection.filterBlocks();}}}}}}if(a.UA.gecko){S=Q.changedNode;A=q.config.doc.createTextNode(" ");S.appendChild(A);S.removeChild(A);}break;}if(a.UA.gecko){if(Q.changedNode&&!Q.changedNode.test(H)){J=Q.changedNode.ancestor(H);if(J){this._lastPara=J;}}}},_afterEditorReady:function(){var m=this.get(k),n=m.getInstance(),l;if(n){n.Selection.filterBlocks();l=n.Selection.DEFAULT_BLOCK_TAG;b=f+" > "+l;h=l;}},_afterContentChange:function(){var l=this.get(k),m=l.getInstance();if(m&&m.Selection){m.Selection.filterBlocks();}},_afterPaste:function(){var l=this.get(k),n=l.getInstance(),m=new n.Selection();a.later(50,l,function(){n.Selection.filterBlocks();});},initializer:function(){var l=this.get(k);if(l.editorBR){a.error("Can not plug EditorPara and EditorBR at the same time.");return;}l.on(c,a.bind(this._onNodeChange,this));l.after("ready",a.bind(this._afterEditorReady,this));l.after("contentChange",a.bind(this._afterContentChange,this));if(a.Env.webkit){l.after("dom:paste",a.bind(this._afterPaste,this));}}},{NAME:"editorPara",NS:"editorPara",ATTRS:{host:{value:false}}});a.namespace("Plugin");a.Plugin.EditorPara=d;},"@VERSION@",{skinnable:false,requires:["editor-base"]});YUI.add("editor-br",function(c){var d=function(){d.superclass.constructor.apply(this,arguments); -},a="host",b="li";c.extend(d,c.Base,{_onKeyDown:function(j){if(j.stopped){j.halt();return;}if(j.keyCode==13){var g=this.get(a),i=g.getInstance(),h=new i.Selection(),f="";if(h){if(c.UA.ie){if(!h.anchorNode||(!h.anchorNode.test(b)&&!h.anchorNode.ancestor(b))){h._selection.pasteHTML("
          ");h._selection.collapse(false);h._selection.select();j.halt();}}if(c.UA.webkit){if(!h.anchorNode.test(b)&&!h.anchorNode.ancestor(b)){g.frame._execCommand("insertlinebreak",null);j.halt();}}}}},_afterEditorReady:function(){var e=this.get(a).getInstance();try{e.config.doc.execCommand("insertbronreturn",null,true);}catch(f){}if(c.UA.ie||c.UA.webkit){e.on("keydown",c.bind(this._onKeyDown,this),e.config.doc);}},_onNodeChange:function(h){switch(h.changedType){case"backspace-up":case"backspace-down":case"delete-up":var g=this.get(a).getInstance();var i=h.changedNode;var f=g.config.doc.createTextNode(" ");i.appendChild(f);i.removeChild(f);break;}},initializer:function(){var e=this.get(a);if(e.editorPara){c.error("Can not plug EditorBR and EditorPara at the same time.");return;}e.after("ready",c.bind(this._afterEditorReady,this));if(c.UA.gecko){e.on("nodeChange",c.bind(this._onNodeChange,this));}}},{NAME:"editorBR",NS:"editorBR",ATTRS:{host:{value:false}}});c.namespace("Plugin");c.Plugin.EditorBR=d;},"@VERSION@",{skinnable:false,requires:["editor-base"]});YUI.add("editor",function(a){},"@VERSION@",{use:["frame","selection","exec-command","editor-base","editor-para","editor-br","editor-bidi","editor-tab","createlink-base"],skinnable:false}); \ No newline at end of file +},a="host",b="li";c.extend(d,c.Base,{_onKeyDown:function(j){if(j.stopped){j.halt();return;}if(j.keyCode==13){var g=this.get(a),i=g.getInstance(),h=new i.Selection(),f="";if(h){if(c.UA.ie){if(!h.anchorNode||(!h.anchorNode.test(b)&&!h.anchorNode.ancestor(b))){h._selection.pasteHTML("
          ");h._selection.collapse(false);h._selection.select();j.halt();}}if(c.UA.webkit){if(!h.anchorNode.test(b)&&!h.anchorNode.ancestor(b)){g.frame._execCommand("insertlinebreak",null);j.halt();}}}}},_afterEditorReady:function(){var e=this.get(a).getInstance();try{e.config.doc.execCommand("insertbronreturn",null,true);}catch(f){}if(c.UA.ie||c.UA.webkit){e.on("keydown",c.bind(this._onKeyDown,this),e.config.doc);}},_onNodeChange:function(h){switch(h.changedType){case"backspace-up":case"backspace-down":case"delete-up":var g=this.get(a).getInstance();var i=h.changedNode;var f=g.config.doc.createTextNode(" ");i.appendChild(f);i.removeChild(f);break;}},initializer:function(){var e=this.get(a);if(e.editorPara){c.error("Can not plug EditorBR and EditorPara at the same time.");return;}e.after("ready",c.bind(this._afterEditorReady,this));if(c.UA.gecko){e.on("nodeChange",c.bind(this._onNodeChange,this));}}},{NAME:"editorBR",NS:"editorBR",ATTRS:{host:{value:false}}});c.namespace("Plugin");c.Plugin.EditorBR=d;},"@VERSION@",{skinnable:false,requires:["editor-base"]});YUI.add("editor",function(a){},"@VERSION@",{skinnable:false,use:["frame","selection","exec-command","editor-base","editor-para","editor-br","editor-bidi","editor-tab","createlink-base"]}); \ No newline at end of file diff --git a/build/editor/editor.js b/build/editor/editor.js index de2e7046c87..817418c1f26 100644 --- a/build/editor/editor.js +++ b/build/editor/editor.js @@ -4613,5 +4613,5 @@ YUI.add('editor-br', function(Y) { }, '@VERSION@' ,{skinnable:false, requires:['editor-base']}); -YUI.add('editor', function(Y){}, '@VERSION@' ,{use:['frame', 'selection', 'exec-command', 'editor-base', 'editor-para', 'editor-br', 'editor-bidi', 'editor-tab', 'createlink-base'], skinnable:false}); +YUI.add('editor', function(Y){}, '@VERSION@' ,{skinnable:false, use:['frame', 'selection', 'exec-command', 'editor-base', 'editor-para', 'editor-br', 'editor-bidi', 'editor-tab', 'createlink-base']}); diff --git a/build/get/get-debug.js b/build/get/get-debug.js index c14fad6955d..9c93f50a1bd 100644 --- a/build/get/get-debug.js +++ b/build/get/get-debug.js @@ -1,6 +1,5 @@ YUI.add('get', function(Y) { - /** * Provides a mechanism to fetch remote resources and * insert them into a document. @@ -8,26 +7,41 @@ YUI.add('get', function(Y) { * @submodule get */ -var ua = Y.UA, - L = Y.Lang, - TYPE_JS = 'text/javascript', - TYPE_CSS = 'text/css', - STYLESHEET = 'stylesheet'; - /** * Fetches and inserts one or more script or link nodes into the document * @class Get * @static */ -Y.Get = function() { + +var ua = Y.UA, + L = Y.Lang, + TYPE_JS = 'text/javascript', + TYPE_CSS = 'text/css', + STYLESHEET = 'stylesheet', + SCRIPT = 'script', + AUTOPURGE = 'autopurge', + UTF8 = 'utf-8', + LINK = 'link', + ASYNC = 'async', + ALL = true, + + // FireFox does not support the onload event for link nodes, so + // there is no way to make the css requests synchronous. This means + // that the css rules in multiple files could be applied out of order + // in this browser if a later request returns before an earlier one. + + // Safari too. + + ONLOAD_SUPPORTED = { + script: ALL, + css: !(ua.webkit || ua.gecko) + }, /** * hash of queues to manage multiple requests * @property queues * @private */ - var _get, _purge, _track, - queues = {}, /** @@ -47,22 +61,41 @@ Y.Get = function() { */ purging, + /** + * Clear timeout state + * + * @method _clearTimeout + * @param {Object} q Queue data + * @private + */ + _clearTimeout = function(q) { + var timer = q.timer; + if (timer) { + clearTimeout(timer); + q.timer = null; + } + }, /** * Generates an HTML element, this is not appended to a document * @method _node * @param {string} type the type of element. - * @param {string} attr the attributes. + * @param {Object} attr the fixed set of attribute for the type. + * @param {Object} custAttrs optional Any custom attributes provided by the user. * @param {Window} win optional window to create the element in. * @return {HTMLElement} the generated node. * @private */ - _node = function(type, attr, win) { + _node = function(type, attr, custAttrs, win) { var w = win || Y.config.win, d = w.document, n = d.createElement(type), i; + if (custAttrs) { + Y.mix(attr, custAttrs); + } + for (i in attr) { if (attr[i] && attr.hasOwnProperty(i)) { n.setAttribute(i, attr[i]); @@ -83,16 +116,12 @@ Y.Get = function() { * @private */ _linkNode = function(url, win, attributes) { - var o = { - id: Y.guid(), - type: TYPE_CSS, - rel: STYLESHEET, - href: url - }; - if (attributes) { - Y.mix(o, attributes); - } - return _node('link', o, win); + return _node(LINK, { + id: Y.guid(), + type: TYPE_CSS, + rel: STYLESHEET, + href: url + }, attributes, win); }, /** @@ -106,18 +135,11 @@ Y.Get = function() { * @private */ _scriptNode = function(url, win, attributes) { - var o = { - id: Y.guid(), - type: TYPE_JS - }; - - if (attributes) { - Y.mix(o, attributes); - } - - o.src = url; - - return _node('script', o, win); + return _node(SCRIPT, { + id: Y.guid(), + type: TYPE_JS, + src: url + }, attributes, win); }, /** @@ -131,16 +153,17 @@ Y.Get = function() { */ _returnData = function(q, msg, result) { return { - tId: q.tId, - win: q.win, - data: q.data, - nodes: q.nodes, - msg: msg, - statusText: result, - purge: function() { - _purge(this.tId); - } - }; + tId: q.tId, + win: q.win, + data: q.data, + nodes: q.nodes, + msg: msg, + statusText: result, + + purge: function() { + _purge(this.tId); + } + }; }, /** @@ -152,14 +175,17 @@ Y.Get = function() { * @private */ _end = function(id, msg, result) { - var q = queues[id], sc; - if (q && q.onEnd) { - sc = q.context || q; - q.onEnd.call(sc, _returnData(q, msg, result)); + var q = queues[id], + onEnd = q && q.onEnd; + + q.finished = true; + + if (onEnd) { + onEnd.call(q.context, _returnData(q, msg, result)); } }, - /* + /** * The request failed, execute fail handler with whatever * was accomplished. There isn't a failure case at the * moment unless you count aborted transactions @@ -170,49 +196,144 @@ Y.Get = function() { _fail = function(id, msg) { Y.log('get failure: ' + msg, 'warn', 'get'); - var q = queues[id], sc; - if (q.timer) { - // q.timer.cancel(); - clearTimeout(q.timer); - } + var q = queues[id], + onFailure = q.onFailure; + + _clearTimeout(q); - // execute failure callback - if (q.onFailure) { - sc = q.context || q; - q.onFailure.call(sc, _returnData(q, msg)); + if (onFailure) { + onFailure.call(q.context, _returnData(q, msg)); } _end(id, msg, 'failure'); }, + + /** + * Abort the transaction + * + * @method _abort + * @param {Object} id + * @private + */ + _abort = function(id) { + _fail(id, 'transaction ' + id + ' was aborted'); + }, + /** * The request is complete, so executing the requester's callback - * @method _finish + * @method _complete * @param {string} id the id of the request. * @private */ - _finish = function(id) { - // Y.log("Finishing transaction " + id, "info", "get"); - var q = queues[id], msg, sc; - if (q.timer) { - // q.timer.cancel(); - clearTimeout(q.timer); - } - q.finished = true; + _complete = function(id) { + Y.log("Finishing transaction " + id, "info", "get"); + + var q = queues[id], + onSuccess = q.onSuccess; + + _clearTimeout(q); if (q.aborted) { - msg = 'transaction ' + id + ' was aborted'; - _fail(id, msg); - return; + _abort(id); + } else { + + if (onSuccess) { + onSuccess.call(q.context, _returnData(q)); + } + + // 3.3.0 had undefined msg for this path. + _end(id, undefined, 'OK'); } + }, + + /** + * Get node reference, from string + * + * @method _getNodeRef + * @param {String|HTMLElement} nId The node id to find. If an HTMLElement is passed in, it will be returned. + * @param {String} tId Queue id, used to determine document for queue + * @private + */ + _getNodeRef = function(nId, tId) { + var q = queues[tId], + n = (L.isString(nId)) ? q.win.document.getElementById(nId) : nId; + if (!n) { + _fail(tId, 'target node not found: ' + nId); + } + + return n; + }, + + /** + * Removes the nodes for the specified queue + * @method _purge + * @param {string} tId the transaction id. + * @private + */ + _purge = function(tId) { + var nodes, doc, parent, sibling, node, attr, insertBefore, + i, l, + q = queues[tId]; + + if (q) { + nodes = q.nodes; + l = nodes.length; + + // TODO: Why is node.parentNode undefined? Which forces us to do this... + /* + doc = q.win.document; + parent = doc.getElementsByTagName('head')[0]; + insertBefore = q.insertBefore || doc.getElementsByTagName('base')[0]; + + if (insertBefore) { + sibling = _getNodeRef(insertBefore, tId); + if (sibling) { + parent = sibling.parentNode; + } + } + */ + + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parentNode; - // execute success callback - if (q.onSuccess) { - sc = q.context || q; - q.onSuccess.call(sc, _returnData(q)); + if (node.clearAttributes) { + node.clearAttributes(); + } else { + // This destroys parentNode ref, so we hold onto it above first. + for (attr in node) { + if (node.hasOwnProperty(attr)) { + delete node[attr]; + } + } + } + + parent.removeChild(node); + } } - _end(id, msg, 'OK'); + q.nodes = []; + }, + + /** + * Progress callback + * + * @method _progress + * @param {string} id The id of the request. + * @param {string} The url which just completed. + * @private + */ + _progress = function(id, url) { + var q = queues[id], + onProgress = q.onProgress, + o; + + if (onProgress) { + o = _returnData(q); + o.url = url; + onProgress.call(q.context, o); + } }, /** @@ -223,120 +344,185 @@ Y.Get = function() { */ _timeout = function(id) { Y.log('Timeout ' + id, 'info', 'get'); - var q = queues[id], sc; - if (q.onTimeout) { - sc = q.context || q; - q.onTimeout.call(sc, _returnData(q)); + + var q = queues[id], + onTimeout = q.onTimeout; + + if (onTimeout) { + onTimeout.call(q.context, _returnData(q)); } _end(id, 'timeout', 'timeout'); }, - /** - * Loads the next item for a given request - * @method _next + * onload callback + * @method _loaded * @param {string} id the id of the request. - * @param {string} loaded the url that was just loaded, if any. * @return {string} the result. * @private */ - _next = function(id, loaded) { -// Y.log("_next: " + id + ", loaded: " + (loaded || "nothing"), "info", "get"); - var q = queues[id], msg, w, d, h, n, url, s, - insertBefore; - - if (q.timer) { - // Y.log('cancel timer'); - // q.timer.cancel(); - clearTimeout(q.timer); - } + _loaded = function(id, url) { - if (q.aborted) { - msg = 'transaction ' + id + ' was aborted'; - _fail(id, msg); - return; + var q = queues[id], + sync = !q.async; + + if (sync) { + _clearTimeout(q); } - if (loaded) { - q.url.shift(); - if (q.varName) { - q.varName.shift(); - } - } else { - // This is the first pass: make sure the url is an array - q.url = (L.isString(q.url)) ? [q.url] : q.url; - if (q.varName) { - q.varName = (L.isString(q.varName)) ? [q.varName] : q.varName; + _progress(id, url); + + // TODO: Cleaning up flow to have a consistent end point + + // !q.finished check is for the async case, + // where scripts may still be loading when we've + // already aborted. Ideally there should be a single path + // for this. + + if (!q.finished) { + if (q.aborted) { + _abort(id); + } else { + if ((--q.remaining) === 0) { + _complete(id); + } else if (sync) { + _next(id); + } } } + }, - w = q.win; - d = w.document; - h = d.getElementsByTagName('head')[0]; + /** + * Detects when a node has been loaded. In the case of + * script nodes, this does not guarantee that contained + * script is ready to use. + * @method _trackLoad + * @param {string} type the type of node to track. + * @param {HTMLElement} n the node to track. + * @param {string} id the id of the request. + * @param {string} url the url that is being loaded. + * @private + */ + _trackLoad = function(type, n, id, url) { - if (q.url.length === 0) { - _finish(id); - return; - } + // TODO: Can we massage this to use ONLOAD_SUPPORTED[type]? - url = q.url[0]; + // IE supports the readystatechange event for script and css nodes + // Opera only for script nodes. Opera support onload for script + // nodes, but this doesn't fire when there is a load failure. + // The onreadystatechange appears to be a better way to respond + // to both success and failure. - // if the url is undefined, this is probably a trailing comma - // problem in IE. - if (!url) { - q.url.shift(); - Y.log('skipping empty url'); - return _next(id); - } + if (ua.ie) { - Y.log('attempting to load ' + url, 'info', 'get'); + n.onreadystatechange = function() { + var rs = this.readyState; + if ('loaded' === rs || 'complete' === rs) { + // Y.log(id + " onreadstatechange " + url, "info", "get"); + n.onreadystatechange = null; + _loaded(id, url); + } + }; - if (q.timeout) { - // Y.log('create timer'); - // q.timer = L.later(q.timeout, q, _timeout, id); - q.timer = setTimeout(function() { - _timeout(id); - }, q.timeout); - } + } else if (ua.webkit) { + + // webkit prior to 3.x is no longer supported + if (type === SCRIPT) { + // Safari 3.x supports the load event for script nodes (DOM2) + n.addEventListener('load', function() { + _loaded(id, url); + }, false); + } - if (q.type === 'script') { - n = _scriptNode(url, w, q.attributes); } else { - n = _linkNode(url, w, q.attributes); - } - // track this node's load progress - _track(q.type, n, id, url, w, q.url.length); + // FireFox and Opera support onload (but not DOM2 in FF) handlers for + // script nodes. Opera, but not FF, supports the onload event for link nodes. - // add the node to the queue so we can return it to the user supplied - // callback - q.nodes.push(n); + n.onload = function() { + // Y.log(id + " onload " + url, "info", "get"); + _loaded(id, url); + }; - // add it to the head or insert it before 'insertBefore'. Work around - // IE bug if there is a base tag. - insertBefore = q.insertBefore || - d.getElementsByTagName('base')[0]; + n.onerror = function(e) { + _fail(id, e + ': ' + url); + }; + } + }, + + _insertInDoc = function(node, id, win) { + + // Add it to the head or insert it before 'insertBefore'. + // Work around IE bug if there is a base tag. + var q = queues[id], + doc = win.document, + insertBefore = q.insertBefore || doc.getElementsByTagName('base')[0], + sibling; if (insertBefore) { - s = _get(insertBefore, id); - if (s) { + sibling = _getNodeRef(insertBefore, id); + if (sibling) { Y.log('inserting before: ' + insertBefore, 'info', 'get'); - s.parentNode.insertBefore(n, s); + sibling.parentNode.insertBefore(node, sibling); } } else { - h.appendChild(n); + // 3.3.0 assumed head is always around. + doc.getElementsByTagName('head')[0].appendChild(node); } + }, + + /** + * Loads the next item for a given request + * @method _next + * @param {string} id the id of the request. + * @return {string} the result. + * @private + */ + _next = function(id) { + + // Assigning out here for readability + var q = queues[id], + type = q.type, + attrs = q.attributes, + win = q.win, + timeout = q.timeout, + node, + url; + + if (q.url.length > 0) { + + url = q.url.shift(); - // Y.log("Appending node: " + url, "info", "get"); + Y.log('attempting to load ' + url, 'info', 'get'); - // FireFox does not support the onload event for link nodes, so - // there is no way to make the css requests synchronous. This means - // that the css rules in multiple files could be applied out of order - // in this browser if a later request returns before an earlier one. - // Safari too. - if ((ua.webkit || ua.gecko) && q.type === 'css') { - _next(id, url); + // !q.timer ensures that this only happens once for async + if (timeout && !q.timer) { + q.timer = setTimeout(function() { + _timeout(id); + }, timeout); + } + + if (type === SCRIPT) { + node = _scriptNode(url, win, attrs); + } else { + node = _linkNode(url, win, attrs); + } + + // add the node to the queue so we can return it in the callback + q.nodes.push(node); + + _trackLoad(type, node, id, url); + _insertInDoc(node, id, win); + + if (!ONLOAD_SUPPORTED[type]) { + _loaded(id, url); + } + + if (q.async) { + // For sync, the _next call is chained in _loaded + _next(id); + } } }, @@ -377,31 +563,47 @@ Y.Get = function() { * @private */ _queue = function(type, url, opts) { + opts = opts || {}; - var id = 'q' + (qidx++), q, - thresh = opts.purgethreshold || Y.Get.PURGE_THRESH; + var id = 'q' + (qidx++), + thresh = opts.purgethreshold || Y.Get.PURGE_THRESH, + q; if (qidx % thresh === 0) { _autoPurge(); } - queues[id] = Y.merge(opts, { - tId: id, - type: type, - url: url, - finished: false, - nodes: [] - }); + // Merge to protect opts (grandfathered in). + q = queues[id] = Y.merge(opts); + + // Avoid mix, merge overhead. Known set of props. + q.tId = id; + q.type = type; + q.url = url; + q.finished = false; + q.nodes = []; - q = queues[id]; q.win = q.win || Y.config.win; q.context = q.context || q; - q.autopurge = ('autopurge' in q) ? q.autopurge : - (type === 'script') ? true : false; - + q.autopurge = (AUTOPURGE in q) ? q.autopurge : (type === SCRIPT) ? true : false; q.attributes = q.attributes || {}; - q.attributes.charset = opts.charset || q.attributes.charset || 'utf-8'; + q.attributes.charset = opts.charset || q.attributes.charset || UTF8; + + if (ASYNC in q && type === SCRIPT) { + q.attributes.async = q.async; + } + + q.url = (L.isString(q.url)) ? [q.url] : q.url; + + // TODO: Do we really need to account for this developer error? + // If the url is undefined, this is probably a trailing comma problem in IE. + if (!q.url[0]) { + q.url.shift(); + Y.log('skipping empty url'); + } + + q.remaining = q.url.length; _next(id); @@ -410,364 +612,264 @@ Y.Get = function() { }; }; + +Y.Get = { + /** - * Detects when a node has been loaded. In the case of - * script nodes, this does not guarantee that contained - * script is ready to use. - * @method _track - * @param {string} type the type of node to track. - * @param {HTMLElement} n the node to track. - * @param {string} id the id of the request. - * @param {string} url the url that is being loaded. - * @param {Window} win the targeted window. - * @param {int} qlength the number of remaining items in the queue, - * including this one. - * @param {Function} trackfn function to execute when finished - * the default is _next. + * The number of request required before an automatic purge. + * Can be configured via the 'purgethreshold' config + * property PURGE_THRESH + * @static + * @type int + * @default 20 * @private */ - _track = function(type, n, id, url, win, qlength, trackfn) { - var f = trackfn || _next; - - // IE supports the readystatechange event for script and css nodes - // Opera only for script nodes. Opera support onload for script - // nodes, but this doesn't fire when there is a load failure. - // The onreadystatechange appears to be a better way to respond - // to both success and failure. - if (ua.ie) { - n.onreadystatechange = function() { - var rs = this.readyState; - if ('loaded' === rs || 'complete' === rs) { - // Y.log(id + " onreadstatechange " + url, "info", "get"); - n.onreadystatechange = null; - f(id, url); - } - }; - - // webkit prior to 3.x is no longer supported - } else if (ua.webkit) { - if (type === 'script') { - // Safari 3.x supports the load event for script nodes (DOM2) - n.addEventListener('load', function() { - // Y.log(id + " DOM2 onload " + url, "info", "get"); - f(id, url); - }, false); - } - - // FireFox and Opera support onload (but not DOM2 in FF) handlers for - // script nodes. Opera, but not FF, supports the onload event for link - // nodes. - } else { - n.onload = function() { - // Y.log(id + " onload " + url, "info", "get"); - f(id, url); - }; - - n.onerror = function(e) { - _fail(id, e + ': ' + url); - }; - } - }; - - _get = function(nId, tId) { - var q = queues[tId], - n = (L.isString(nId)) ? q.win.document.getElementById(nId) : nId; - if (!n) { - _fail(tId, 'target node not found: ' + nId); - } - - return n; - }; + PURGE_THRESH: 20, /** - * Removes the nodes for the specified queue - * @method _purge - * @param {string} tId the transaction id. - * @private + * Abort a transaction + * @method abort + * @static + * @param {string|object} o Either the tId or the object returned from + * script() or css(). */ - _purge = function(tId) { - var n, l, d, h, s, i, node, attr, insertBefore, - q = queues[tId]; + abort : function(o) { + var id = (L.isString(o)) ? o : o.tId, + q = queues[id]; if (q) { - n = q.nodes; - l = n.length; - d = q.win.document; - h = d.getElementsByTagName('head')[0]; - - insertBefore = q.insertBefore || - d.getElementsByTagName('base')[0]; - - if (insertBefore) { - s = _get(insertBefore, tId); - if (s) { - h = s.parentNode; - } - } - - for (i = 0; i < l; i = i + 1) { - node = n[i]; - if (node.clearAttributes) { - node.clearAttributes(); - } else { - for (attr in node) { - if (node.hasOwnProperty(attr)) { - delete node[attr]; - } - } - } - - h.removeChild(node); - } + Y.log('Aborting ' + id, 'info', 'get'); + q.aborted = true; } - q.nodes = []; - }; + }, - return { - - /** - * The number of request required before an automatic purge. - * Can be configured via the 'purgethreshold' config - * property PURGE_THRESH - * @static - * @type int - * @default 20 - * @private - */ - PURGE_THRESH: 20, - - /** - * Called by the the helper for detecting script load in Safari - * @method _finalize - * @static - * @param {string} id the transaction id. - * @private - */ - _finalize: function(id) { - Y.log(id + ' finalized ', 'info', 'get'); - setTimeout(function() { - _finish(id); - }, 0); - }, - - /** - * Abort a transaction - * @method abort - * @static - * @param {string|object} o Either the tId or the object returned from - * script() or css(). - */ - abort: function(o) { - var id = (L.isString(o)) ? o : o.tId, - q = queues[id]; - if (q) { - Y.log('Aborting ' + id, 'info', 'get'); - q.aborted = true; - } - }, - - /** - * Fetches and inserts one or more script nodes into the head - * of the current document or the document in a specified window. - * - * @method script - * @static - * @param {string|string[]} url the url or urls to the script(s). - * @param {object} opts Options: - *
          - *
          onSuccess
          - *
          - * callback to execute when the script(s) are finished loading - * The callback receives an object back with the following - * data: - *
          - *
          win
          - *
          the window the script(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted
          - *
          purge
          - *
          A function that, when executed, will remove the nodes - * that were inserted
          - *
          - *
          - *
          - *
          onTimeout
          - *
          - * callback to execute when a timeout occurs. - * The callback receives an object back with the following - * data: - *
          - *
          win
          - *
          the window the script(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted
          - *
          purge
          - *
          A function that, when executed, will remove the nodes - * that were inserted
          - *
          - *
          - *
          - *
          onEnd
          - *
          a function that executes when the transaction finishes, - * regardless of the exit path
          - *
          onFailure
          - *
          - * callback to execute when the script load operation fails - * The callback receives an object back with the following - * data: - *
          - *
          win
          - *
          the window the script(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted successfully
          - *
          purge
          - *
          A function that, when executed, will remove any nodes - * that were inserted
          - *
          - *
          - *
          - *
          context
          - *
          the execution context for the callbacks
          - *
          win
          - *
          a window other than the one the utility occupies
          - *
          autopurge
          - *
          - * setting to true will let the utilities cleanup routine purge - * the script once loaded - *
          - *
          purgethreshold
          - *
          - * The number of transaction before autopurge should be initiated - *
          - *
          data
          - *
          - * data that is supplied to the callback when the script(s) are - * loaded. - *
          - *
          insertBefore
          - *
          node or node id that will become the new node's nextSibling. - * If this is not specified, nodes will be inserted before a base - * tag should it exist. Otherwise, the nodes will be appended to the - * end of the document head.
          - *
          - *
          charset
          - *
          Node charset, default utf-8 (deprecated, use the attributes - * config)
          - *
          attributes
          - *
          An object literal containing additional attributes to add to - * the link tags
          - *
          timeout
          - *
          Number of milliseconds to wait before aborting and firing - * the timeout event
          - *
          -         *   Y.Get.script(
          -         *   ["http://yui.yahooapis.com/2.5.2/build/yahoo/yahoo-min.js",
          -         *    "http://yui.yahooapis.com/2.5.2/build/event/event-min.js"],
          -         *   {
          -         *     onSuccess: function(o) {
          -         *       this.log("won't cause error because Y is the context");
          -         *       Y.log(o.data); // foo
          -         *       Y.log(o.nodes.length === 2) // true
          -         *       // o.purge(); // optionally remove the script nodes
          -         *                     // immediately
          -         *     },
          -         *     onFailure: function(o) {
          -         *       Y.log("transaction failed");
          -         *     },
          -         *     onTimeout: function(o) {
          -         *       Y.log("transaction timed out");
          -         *     },
          -         *     data: "foo",
          -         *     timeout: 10000, // 10 second timeout
          -         *     context: Y, // make the YUI instance
          -         *     // win: otherframe // target another window/frame
          -         *     autopurge: true // allow the utility to choose when to
          -         *                     // remove the nodes
          -         *     purgetheshold: 1 // purge previous transaction before
          -         *                      // next transaction
          -         *   });.
          -         * 
          - * @return {tId: string} an object containing info about the - * transaction. - */ - script: function(url, opts) { - return _queue('script', url, opts); - }, - - /** - * Fetches and inserts one or more css link nodes into the - * head of the current document or the document in a specified - * window. - * @method css - * @static - * @param {string} url the url or urls to the css file(s). - * @param {object} opts Options: - *
          - *
          onSuccess
          - *
          - * callback to execute when the css file(s) are finished loading - * The callback receives an object back with the following - * data: - *
          win
          - *
          the window the link nodes(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted
          - *
          purge
          - *
          A function that, when executed, will remove the nodes - * that were inserted
          - *
          - *
          - * - *
          context
          - *
          the execution context for the callbacks
          - *
          win
          - *
          a window other than the one the utility occupies
          - *
          data
          - *
          - * data that is supplied to the callbacks when the nodes(s) are - * loaded. - *
          - *
          insertBefore
          - *
          node or node id that will become the new node's nextSibling
          - *
          charset
          - *
          Node charset, default utf-8 (deprecated, use the attributes - * config)
          - *
          attributes
          - *
          An object literal containing additional attributes to add to - * the link tags
          - * - *
          -         * Y.Get.css("http://localhost/css/menu.css");
          -         * 
          - *
          -         *   Y.Get.css(
          -         *   ["http://localhost/css/menu.css",
          -         *    "http://localhost/css/logger.css"], {
          -         *     insertBefore: 'custom-styles' // nodes will be inserted
          -         *                                   // before the specified node
          -         *   });.
          -         * 
          - * @return {tId: string} an object containing info about the - * transaction. - */ - css: function(url, opts) { - return _queue('css', url, opts); - } - }; -}(); + /** + * Fetches and inserts one or more script nodes into the head + * of the current document or the document in a specified window. + * + * @method script + * @static + * @param {string|string[]} url the url or urls to the script(s). + * @param {object} opts Options: + *
          + *
          onSuccess
          + *
          + * callback to execute when the script(s) are finished loading + * The callback receives an object back with the following + * data: + *
          + *
          win
          + *
          the window the script(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted
          + *
          purge
          + *
          A function that, when executed, will remove the nodes + * that were inserted
          + *
          + *
          + *
          + *
          onTimeout
          + *
          + * callback to execute when a timeout occurs. + * The callback receives an object back with the following + * data: + *
          + *
          win
          + *
          the window the script(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted
          + *
          purge
          + *
          A function that, when executed, will remove the nodes + * that were inserted
          + *
          + *
          + *
          + *
          onEnd
          + *
          a function that executes when the transaction finishes, + * regardless of the exit path
          + *
          onFailure
          + *
          + * callback to execute when the script load operation fails + * The callback receives an object back with the following + * data: + *
          + *
          win
          + *
          the window the script(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted successfully
          + *
          purge
          + *
          A function that, when executed, will remove any nodes + * that were inserted
          + *
          + *
          + *
          + *
          onProgress
          + *
          callback to execute when each individual file is done loading + * (useful when passing in an array of js files). Receives the same + * payload as onSuccess, with the addition of a url + * property, which identifies the file which was loaded.
          + *
          async
          + *
          + *

          When passing in an array of JS files, setting this flag to true + * will insert them into the document in parallel, as opposed to the + * default behavior, which is to chain load them serially. It will also + * set the async attribute on the script node to true.

          + *

          Setting async:true + * will lead to optimal file download performance allowing the browser to + * download multiple scripts in parallel, and execute them as soon as they + * are available.

          + *

          Note that async:true does not guarantee execution order of the + * scripts being downloaded. They are executed in whichever order they + * are received.

          + *
          + *
          context
          + *
          the execution context for the callbacks
          + *
          win
          + *
          a window other than the one the utility occupies
          + *
          autopurge
          + *
          + * setting to true will let the utilities cleanup routine purge + * the script once loaded + *
          + *
          purgethreshold
          + *
          + * The number of transaction before autopurge should be initiated + *
          + *
          data
          + *
          + * data that is supplied to the callback when the script(s) are + * loaded. + *
          + *
          insertBefore
          + *
          node or node id that will become the new node's nextSibling. + * If this is not specified, nodes will be inserted before a base + * tag should it exist. Otherwise, the nodes will be appended to the + * end of the document head.
          + *
          + *
          charset
          + *
          Node charset, default utf-8 (deprecated, use the attributes + * config)
          + *
          attributes
          + *
          An object literal containing additional attributes to add to + * the link tags
          + *
          timeout
          + *
          Number of milliseconds to wait before aborting and firing + * the timeout event
          + *
          +     *   Y.Get.script(
          +     *   ["http://yui.yahooapis.com/2.5.2/build/yahoo/yahoo-min.js",
          +     *    "http://yui.yahooapis.com/2.5.2/build/event/event-min.js"],
          +     *   {
          +     *     onSuccess: function(o) {
          +     *       this.log("won't cause error because Y is the context");
          +     *       Y.log(o.data); // foo
          +     *       Y.log(o.nodes.length === 2) // true
          +     *       // o.purge(); // optionally remove the script nodes
          +     *                     // immediately
          +     *     },
          +     *     onFailure: function(o) {
          +     *       Y.log("transaction failed");
          +     *     },
          +     *     onTimeout: function(o) {
          +     *       Y.log("transaction timed out");
          +     *     },
          +     *     data: "foo",
          +     *     timeout: 10000, // 10 second timeout
          +     *     context: Y, // make the YUI instance
          +     *     // win: otherframe // target another window/frame
          +     *     autopurge: true // allow the utility to choose when to
          +     *                     // remove the nodes
          +     *     purgetheshold: 1 // purge previous transaction before
          +     *                      // next transaction
          +     *   });.
          +     * 
          + * @return {tId: string} an object containing info about the + * transaction. + */ + script: function(url, opts) { + return _queue(SCRIPT, url, opts); + }, + /** + * Fetches and inserts one or more css link nodes into the + * head of the current document or the document in a specified + * window. + * @method css + * @static + * @param {string} url the url or urls to the css file(s). + * @param {object} opts Options: + *
          + *
          onSuccess
          + *
          + * callback to execute when the css file(s) are finished loading + * The callback receives an object back with the following + * data: + *
          win
          + *
          the window the link nodes(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted
          + *
          purge
          + *
          A function that, when executed, will remove the nodes + * that were inserted
          + *
          + *
          + * + *
          onProgress
          + *
          callback to execute when each individual file is done loading (useful when passing in an array of css files). Receives the same + * payload as onSuccess, with the addition of a url property, which identifies the file which was loaded. Currently only useful for non Webkit/Gecko browsers, + * where onload for css is detected accurately.
          + *
          async
          + *
          When passing in an array of css files, setting this flag to true will insert them + * into the document in parallel, as oppposed to the default behavior, which is to chain load them (where possible). + * This flag is more useful for scripts currently, since for css Get only chains if not Webkit/Gecko.
          + *
          context
          + *
          the execution context for the callbacks
          + *
          win
          + *
          a window other than the one the utility occupies
          + *
          data
          + *
          + * data that is supplied to the callbacks when the nodes(s) are + * loaded. + *
          + *
          insertBefore
          + *
          node or node id that will become the new node's nextSibling
          + *
          charset
          + *
          Node charset, default utf-8 (deprecated, use the attributes + * config)
          + *
          attributes
          + *
          An object literal containing additional attributes to add to + * the link tags
          + * + *
          +     * Y.Get.css("http://localhost/css/menu.css");
          +     * 
          + *
          +     *   Y.Get.css(
          +     *   ["http://localhost/css/menu.css",
          +     *    "http://localhost/css/logger.css"], {
          +     *     insertBefore: 'custom-styles' // nodes will be inserted
          +     *                                   // before the specified node
          +     *   });.
          +     * 
          + * @return {tId: string} an object containing info about the + * transaction. + */ + css: function(url, opts) { + return _queue('css', url, opts); + } +}; }, '@VERSION@' ,{requires:['yui-base']}); diff --git a/build/get/get-min.js b/build/get/get-min.js index 2374f8f10c7..cea319ba41f 100644 --- a/build/get/get-min.js +++ b/build/get/get-min.js @@ -1 +1 @@ -YUI.add("get",function(f){var b=f.UA,a=f.Lang,d="text/javascript",e="text/css",c="stylesheet";f.Get=function(){var m,n,j,l={},k=0,u,w=function(A,x,B){var y=B||f.config.win,C=y.document,D=C.createElement(A),z;for(z in x){if(x[z]&&x.hasOwnProperty(z)){D.setAttribute(z,x[z]);}}return D;},t=function(y,z,x){var A={id:f.guid(),type:e,rel:c,href:y};if(x){f.mix(A,x);}return w("link",A,z);},s=function(y,z,x){var A={id:f.guid(),type:d};if(x){f.mix(A,x);}A.src=y;return w("script",A,z);},p=function(y,z,x){return{tId:y.tId,win:y.win,data:y.data,nodes:y.nodes,msg:z,statusText:x,purge:function(){n(this.tId);}};},o=function(B,A,x){var y=l[B],z;if(y&&y.onEnd){z=y.context||y;y.onEnd.call(z,p(y,A,x));}},v=function(A,z){var x=l[A],y;if(x.timer){clearTimeout(x.timer);}if(x.onFailure){y=x.context||x;x.onFailure.call(y,p(x,z));}o(A,z,"failure");},i=function(A){var x=l[A],z,y;if(x.timer){clearTimeout(x.timer);}x.finished=true;if(x.aborted){z="transaction "+A+" was aborted";v(A,z);return;}if(x.onSuccess){y=x.context||x;x.onSuccess.call(y,p(x));}o(A,z,"OK");},q=function(z){var x=l[z],y;if(x.onTimeout){y=x.context||x;x.onTimeout.call(y,p(x));}o(z,"timeout","timeout");},h=function(z,C){var y=l[z],B,G,F,D,A,x,H,E;if(y.timer){clearTimeout(y.timer);}if(y.aborted){B="transaction "+z+" was aborted";v(z,B);return;}if(C){y.url.shift();if(y.varName){y.varName.shift();}}else{y.url=(a.isString(y.url))?[y.url]:y.url;if(y.varName){y.varName=(a.isString(y.varName))?[y.varName]:y.varName;}}G=y.win;F=G.document;D=F.getElementsByTagName("head")[0];if(y.url.length===0){i(z);return;}x=y.url[0];if(!x){y.url.shift();return h(z);}if(y.timeout){y.timer=setTimeout(function(){q(z);},y.timeout);}if(y.type==="script"){A=s(x,G,y.attributes);}else{A=t(x,G,y.attributes);}j(y.type,A,z,x,G,y.url.length);y.nodes.push(A);E=y.insertBefore||F.getElementsByTagName("base")[0];if(E){H=m(E,z);if(H){H.parentNode.insertBefore(A,H);}}else{D.appendChild(A);}if((b.webkit||b.gecko)&&y.type==="css"){h(z,x);}},g=function(){if(u){return;}u=true;var x,y;for(x in l){if(l.hasOwnProperty(x)){y=l[x];if(y.autopurge&&y.finished){n(y.tId);delete l[x];}}}u=false;},r=function(y,x,z){z=z||{};var C="q"+(k++),A,B=z.purgethreshold||f.Get.PURGE_THRESH;if(k%B===0){g();}l[C]=f.merge(z,{tId:C,type:y,url:x,finished:false,nodes:[]});A=l[C];A.win=A.win||f.config.win;A.context=A.context||A;A.autopurge=("autopurge" in A)?A.autopurge:(y==="script")?true:false;A.attributes=A.attributes||{};A.attributes.charset=z.charset||A.attributes.charset||"utf-8";h(C);return{tId:C};};j=function(z,E,D,y,C,B,x){var A=x||h;if(b.ie){E.onreadystatechange=function(){var F=this.readyState;if("loaded"===F||"complete"===F){E.onreadystatechange=null;A(D,y);}};}else{if(b.webkit){if(z==="script"){E.addEventListener("load",function(){A(D,y);},false);}}else{E.onload=function(){A(D,y);};E.onerror=function(F){v(D,F+": "+y);};}}};m=function(x,A){var y=l[A],z=(a.isString(x))?y.win.document.getElementById(x):x;if(!z){v(A,"target node not found: "+x);}return z;};n=function(C){var y,A,G,D,H,B,z,F,E,x=l[C];if(x){y=x.nodes;A=y.length;G=x.win.document;D=G.getElementsByTagName("head")[0];E=x.insertBefore||G.getElementsByTagName("base")[0];if(E){H=m(E,C);if(H){D=H.parentNode;}}for(B=0;B0){J=O.url.shift();if(N&&!O.timer){O.timer=setTimeout(function(){D(Q);},N);}if(L===s){M=E(J,P,K);}else{M=k(J,P,K);}O.nodes.push(M);c(L,M,Q,J);G(M,Q,P);if(!l[L]){f(Q,J);}if(O.async){i(Q);}}},n=function(){if(g){return;}g=true;var J,K;for(J in z){if(z.hasOwnProperty(J)){K=z[J];if(K.autopurge&&K.finished){d(K.tId);delete z[J];}}}g=false;},j=function(K,J,L){L=L||{};var O="q"+(r++),N=L.purgethreshold||e.Get.PURGE_THRESH,M;if(r%N===0){n();}M=z[O]=e.merge(L);M.tId=O;M.type=K;M.url=J;M.finished=false;M.nodes=[];M.win=M.win||e.config.win;M.context=M.context||M;M.autopurge=(q in M)?M.autopurge:(K===s)?true:false;M.attributes=M.attributes||{};M.attributes.charset=L.charset||M.attributes.charset||A;if(C in M&&K===s){M.attributes.async=M.async;}M.url=(p.isString(M.url))?[M.url]:M.url;if(!M.url[0]){M.url.shift();}M.remaining=M.url.length;i(O);return{tId:O};};e.Get={PURGE_THRESH:20,abort:function(K){var L=(p.isString(K))?K:K.tId,J=z[L];if(J){J.aborted=true;}},script:function(J,K){return j(s,J,K);},css:function(J,K){return j("css",J,K);}};},"@VERSION@",{requires:["yui-base"]}); \ No newline at end of file diff --git a/build/get/get.js b/build/get/get.js index 0bce5470756..33eca5cb117 100644 --- a/build/get/get.js +++ b/build/get/get.js @@ -1,6 +1,5 @@ YUI.add('get', function(Y) { - /** * Provides a mechanism to fetch remote resources and * insert them into a document. @@ -8,26 +7,41 @@ YUI.add('get', function(Y) { * @submodule get */ -var ua = Y.UA, - L = Y.Lang, - TYPE_JS = 'text/javascript', - TYPE_CSS = 'text/css', - STYLESHEET = 'stylesheet'; - /** * Fetches and inserts one or more script or link nodes into the document * @class Get * @static */ -Y.Get = function() { + +var ua = Y.UA, + L = Y.Lang, + TYPE_JS = 'text/javascript', + TYPE_CSS = 'text/css', + STYLESHEET = 'stylesheet', + SCRIPT = 'script', + AUTOPURGE = 'autopurge', + UTF8 = 'utf-8', + LINK = 'link', + ASYNC = 'async', + ALL = true, + + // FireFox does not support the onload event for link nodes, so + // there is no way to make the css requests synchronous. This means + // that the css rules in multiple files could be applied out of order + // in this browser if a later request returns before an earlier one. + + // Safari too. + + ONLOAD_SUPPORTED = { + script: ALL, + css: !(ua.webkit || ua.gecko) + }, /** * hash of queues to manage multiple requests * @property queues * @private */ - var _get, _purge, _track, - queues = {}, /** @@ -47,22 +61,41 @@ Y.Get = function() { */ purging, + /** + * Clear timeout state + * + * @method _clearTimeout + * @param {Object} q Queue data + * @private + */ + _clearTimeout = function(q) { + var timer = q.timer; + if (timer) { + clearTimeout(timer); + q.timer = null; + } + }, /** * Generates an HTML element, this is not appended to a document * @method _node * @param {string} type the type of element. - * @param {string} attr the attributes. + * @param {Object} attr the fixed set of attribute for the type. + * @param {Object} custAttrs optional Any custom attributes provided by the user. * @param {Window} win optional window to create the element in. * @return {HTMLElement} the generated node. * @private */ - _node = function(type, attr, win) { + _node = function(type, attr, custAttrs, win) { var w = win || Y.config.win, d = w.document, n = d.createElement(type), i; + if (custAttrs) { + Y.mix(attr, custAttrs); + } + for (i in attr) { if (attr[i] && attr.hasOwnProperty(i)) { n.setAttribute(i, attr[i]); @@ -83,16 +116,12 @@ Y.Get = function() { * @private */ _linkNode = function(url, win, attributes) { - var o = { - id: Y.guid(), - type: TYPE_CSS, - rel: STYLESHEET, - href: url - }; - if (attributes) { - Y.mix(o, attributes); - } - return _node('link', o, win); + return _node(LINK, { + id: Y.guid(), + type: TYPE_CSS, + rel: STYLESHEET, + href: url + }, attributes, win); }, /** @@ -106,18 +135,11 @@ Y.Get = function() { * @private */ _scriptNode = function(url, win, attributes) { - var o = { - id: Y.guid(), - type: TYPE_JS - }; - - if (attributes) { - Y.mix(o, attributes); - } - - o.src = url; - - return _node('script', o, win); + return _node(SCRIPT, { + id: Y.guid(), + type: TYPE_JS, + src: url + }, attributes, win); }, /** @@ -131,16 +153,17 @@ Y.Get = function() { */ _returnData = function(q, msg, result) { return { - tId: q.tId, - win: q.win, - data: q.data, - nodes: q.nodes, - msg: msg, - statusText: result, - purge: function() { - _purge(this.tId); - } - }; + tId: q.tId, + win: q.win, + data: q.data, + nodes: q.nodes, + msg: msg, + statusText: result, + + purge: function() { + _purge(this.tId); + } + }; }, /** @@ -152,14 +175,17 @@ Y.Get = function() { * @private */ _end = function(id, msg, result) { - var q = queues[id], sc; - if (q && q.onEnd) { - sc = q.context || q; - q.onEnd.call(sc, _returnData(q, msg, result)); + var q = queues[id], + onEnd = q && q.onEnd; + + q.finished = true; + + if (onEnd) { + onEnd.call(q.context, _returnData(q, msg, result)); } }, - /* + /** * The request failed, execute fail handler with whatever * was accomplished. There isn't a failure case at the * moment unless you count aborted transactions @@ -169,48 +195,143 @@ Y.Get = function() { */ _fail = function(id, msg) { - var q = queues[id], sc; - if (q.timer) { - // q.timer.cancel(); - clearTimeout(q.timer); - } + var q = queues[id], + onFailure = q.onFailure; + + _clearTimeout(q); - // execute failure callback - if (q.onFailure) { - sc = q.context || q; - q.onFailure.call(sc, _returnData(q, msg)); + if (onFailure) { + onFailure.call(q.context, _returnData(q, msg)); } _end(id, msg, 'failure'); }, + + /** + * Abort the transaction + * + * @method _abort + * @param {Object} id + * @private + */ + _abort = function(id) { + _fail(id, 'transaction ' + id + ' was aborted'); + }, + /** * The request is complete, so executing the requester's callback - * @method _finish + * @method _complete * @param {string} id the id of the request. * @private */ - _finish = function(id) { - var q = queues[id], msg, sc; - if (q.timer) { - // q.timer.cancel(); - clearTimeout(q.timer); - } - q.finished = true; + _complete = function(id) { + + var q = queues[id], + onSuccess = q.onSuccess; + + _clearTimeout(q); if (q.aborted) { - msg = 'transaction ' + id + ' was aborted'; - _fail(id, msg); - return; + _abort(id); + } else { + + if (onSuccess) { + onSuccess.call(q.context, _returnData(q)); + } + + // 3.3.0 had undefined msg for this path. + _end(id, undefined, 'OK'); } + }, + + /** + * Get node reference, from string + * + * @method _getNodeRef + * @param {String|HTMLElement} nId The node id to find. If an HTMLElement is passed in, it will be returned. + * @param {String} tId Queue id, used to determine document for queue + * @private + */ + _getNodeRef = function(nId, tId) { + var q = queues[tId], + n = (L.isString(nId)) ? q.win.document.getElementById(nId) : nId; + if (!n) { + _fail(tId, 'target node not found: ' + nId); + } + + return n; + }, + + /** + * Removes the nodes for the specified queue + * @method _purge + * @param {string} tId the transaction id. + * @private + */ + _purge = function(tId) { + var nodes, doc, parent, sibling, node, attr, insertBefore, + i, l, + q = queues[tId]; + + if (q) { + nodes = q.nodes; + l = nodes.length; + + // TODO: Why is node.parentNode undefined? Which forces us to do this... + /* + doc = q.win.document; + parent = doc.getElementsByTagName('head')[0]; + insertBefore = q.insertBefore || doc.getElementsByTagName('base')[0]; + + if (insertBefore) { + sibling = _getNodeRef(insertBefore, tId); + if (sibling) { + parent = sibling.parentNode; + } + } + */ + + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parentNode; + + if (node.clearAttributes) { + node.clearAttributes(); + } else { + // This destroys parentNode ref, so we hold onto it above first. + for (attr in node) { + if (node.hasOwnProperty(attr)) { + delete node[attr]; + } + } + } - // execute success callback - if (q.onSuccess) { - sc = q.context || q; - q.onSuccess.call(sc, _returnData(q)); + parent.removeChild(node); + } } - _end(id, msg, 'OK'); + q.nodes = []; + }, + + /** + * Progress callback + * + * @method _progress + * @param {string} id The id of the request. + * @param {string} The url which just completed. + * @private + */ + _progress = function(id, url) { + var q = queues[id], + onProgress = q.onProgress, + o; + + if (onProgress) { + o = _returnData(q); + o.url = url; + onProgress.call(q.context, o); + } }, /** @@ -220,113 +341,181 @@ Y.Get = function() { * @private */ _timeout = function(id) { - var q = queues[id], sc; - if (q.onTimeout) { - sc = q.context || q; - q.onTimeout.call(sc, _returnData(q)); + + var q = queues[id], + onTimeout = q.onTimeout; + + if (onTimeout) { + onTimeout.call(q.context, _returnData(q)); } _end(id, 'timeout', 'timeout'); }, - /** - * Loads the next item for a given request - * @method _next + * onload callback + * @method _loaded * @param {string} id the id of the request. - * @param {string} loaded the url that was just loaded, if any. * @return {string} the result. * @private */ - _next = function(id, loaded) { - var q = queues[id], msg, w, d, h, n, url, s, - insertBefore; + _loaded = function(id, url) { - if (q.timer) { - // q.timer.cancel(); - clearTimeout(q.timer); - } + var q = queues[id], + sync = !q.async; - if (q.aborted) { - msg = 'transaction ' + id + ' was aborted'; - _fail(id, msg); - return; + if (sync) { + _clearTimeout(q); } - if (loaded) { - q.url.shift(); - if (q.varName) { - q.varName.shift(); - } - } else { - // This is the first pass: make sure the url is an array - q.url = (L.isString(q.url)) ? [q.url] : q.url; - if (q.varName) { - q.varName = (L.isString(q.varName)) ? [q.varName] : q.varName; + _progress(id, url); + + // TODO: Cleaning up flow to have a consistent end point + + // !q.finished check is for the async case, + // where scripts may still be loading when we've + // already aborted. Ideally there should be a single path + // for this. + + if (!q.finished) { + if (q.aborted) { + _abort(id); + } else { + if ((--q.remaining) === 0) { + _complete(id); + } else if (sync) { + _next(id); + } } } + }, - w = q.win; - d = w.document; - h = d.getElementsByTagName('head')[0]; + /** + * Detects when a node has been loaded. In the case of + * script nodes, this does not guarantee that contained + * script is ready to use. + * @method _trackLoad + * @param {string} type the type of node to track. + * @param {HTMLElement} n the node to track. + * @param {string} id the id of the request. + * @param {string} url the url that is being loaded. + * @private + */ + _trackLoad = function(type, n, id, url) { - if (q.url.length === 0) { - _finish(id); - return; - } + // TODO: Can we massage this to use ONLOAD_SUPPORTED[type]? - url = q.url[0]; + // IE supports the readystatechange event for script and css nodes + // Opera only for script nodes. Opera support onload for script + // nodes, but this doesn't fire when there is a load failure. + // The onreadystatechange appears to be a better way to respond + // to both success and failure. - // if the url is undefined, this is probably a trailing comma - // problem in IE. - if (!url) { - q.url.shift(); - return _next(id); - } + if (ua.ie) { + n.onreadystatechange = function() { + var rs = this.readyState; + if ('loaded' === rs || 'complete' === rs) { + n.onreadystatechange = null; + _loaded(id, url); + } + }; - if (q.timeout) { - // q.timer = L.later(q.timeout, q, _timeout, id); - q.timer = setTimeout(function() { - _timeout(id); - }, q.timeout); - } + } else if (ua.webkit) { + + // webkit prior to 3.x is no longer supported + if (type === SCRIPT) { + // Safari 3.x supports the load event for script nodes (DOM2) + n.addEventListener('load', function() { + _loaded(id, url); + }, false); + } - if (q.type === 'script') { - n = _scriptNode(url, w, q.attributes); } else { - n = _linkNode(url, w, q.attributes); - } - // track this node's load progress - _track(q.type, n, id, url, w, q.url.length); + // FireFox and Opera support onload (but not DOM2 in FF) handlers for + // script nodes. Opera, but not FF, supports the onload event for link nodes. + + n.onload = function() { + _loaded(id, url); + }; + + n.onerror = function(e) { + _fail(id, e + ': ' + url); + }; + } + }, - // add the node to the queue so we can return it to the user supplied - // callback - q.nodes.push(n); + _insertInDoc = function(node, id, win) { - // add it to the head or insert it before 'insertBefore'. Work around - // IE bug if there is a base tag. - insertBefore = q.insertBefore || - d.getElementsByTagName('base')[0]; + // Add it to the head or insert it before 'insertBefore'. + // Work around IE bug if there is a base tag. + var q = queues[id], + doc = win.document, + insertBefore = q.insertBefore || doc.getElementsByTagName('base')[0], + sibling; if (insertBefore) { - s = _get(insertBefore, id); - if (s) { - s.parentNode.insertBefore(n, s); + sibling = _getNodeRef(insertBefore, id); + if (sibling) { + sibling.parentNode.insertBefore(node, sibling); } } else { - h.appendChild(n); + // 3.3.0 assumed head is always around. + doc.getElementsByTagName('head')[0].appendChild(node); } + }, + + /** + * Loads the next item for a given request + * @method _next + * @param {string} id the id of the request. + * @return {string} the result. + * @private + */ + _next = function(id) { + + // Assigning out here for readability + var q = queues[id], + type = q.type, + attrs = q.attributes, + win = q.win, + timeout = q.timeout, + node, + url; + + if (q.url.length > 0) { + url = q.url.shift(); - // FireFox does not support the onload event for link nodes, so - // there is no way to make the css requests synchronous. This means - // that the css rules in multiple files could be applied out of order - // in this browser if a later request returns before an earlier one. - // Safari too. - if ((ua.webkit || ua.gecko) && q.type === 'css') { - _next(id, url); + + // !q.timer ensures that this only happens once for async + if (timeout && !q.timer) { + q.timer = setTimeout(function() { + _timeout(id); + }, timeout); + } + + if (type === SCRIPT) { + node = _scriptNode(url, win, attrs); + } else { + node = _linkNode(url, win, attrs); + } + + // add the node to the queue so we can return it in the callback + q.nodes.push(node); + + _trackLoad(type, node, id, url); + _insertInDoc(node, id, win); + + if (!ONLOAD_SUPPORTED[type]) { + _loaded(id, url); + } + + if (q.async) { + // For sync, the _next call is chained in _loaded + _next(id); + } } }, @@ -367,31 +556,46 @@ Y.Get = function() { * @private */ _queue = function(type, url, opts) { + opts = opts || {}; - var id = 'q' + (qidx++), q, - thresh = opts.purgethreshold || Y.Get.PURGE_THRESH; + var id = 'q' + (qidx++), + thresh = opts.purgethreshold || Y.Get.PURGE_THRESH, + q; if (qidx % thresh === 0) { _autoPurge(); } - queues[id] = Y.merge(opts, { - tId: id, - type: type, - url: url, - finished: false, - nodes: [] - }); + // Merge to protect opts (grandfathered in). + q = queues[id] = Y.merge(opts); + + // Avoid mix, merge overhead. Known set of props. + q.tId = id; + q.type = type; + q.url = url; + q.finished = false; + q.nodes = []; - q = queues[id]; q.win = q.win || Y.config.win; q.context = q.context || q; - q.autopurge = ('autopurge' in q) ? q.autopurge : - (type === 'script') ? true : false; - + q.autopurge = (AUTOPURGE in q) ? q.autopurge : (type === SCRIPT) ? true : false; q.attributes = q.attributes || {}; - q.attributes.charset = opts.charset || q.attributes.charset || 'utf-8'; + q.attributes.charset = opts.charset || q.attributes.charset || UTF8; + + if (ASYNC in q && type === SCRIPT) { + q.attributes.async = q.async; + } + + q.url = (L.isString(q.url)) ? [q.url] : q.url; + + // TODO: Do we really need to account for this developer error? + // If the url is undefined, this is probably a trailing comma problem in IE. + if (!q.url[0]) { + q.url.shift(); + } + + q.remaining = q.url.length; _next(id); @@ -400,353 +604,257 @@ Y.Get = function() { }; }; + +Y.Get = { + /** - * Detects when a node has been loaded. In the case of - * script nodes, this does not guarantee that contained - * script is ready to use. - * @method _track - * @param {string} type the type of node to track. - * @param {HTMLElement} n the node to track. - * @param {string} id the id of the request. - * @param {string} url the url that is being loaded. - * @param {Window} win the targeted window. - * @param {int} qlength the number of remaining items in the queue, - * including this one. - * @param {Function} trackfn function to execute when finished - * the default is _next. + * The number of request required before an automatic purge. + * Can be configured via the 'purgethreshold' config + * property PURGE_THRESH + * @static + * @type int + * @default 20 * @private */ - _track = function(type, n, id, url, win, qlength, trackfn) { - var f = trackfn || _next; - - // IE supports the readystatechange event for script and css nodes - // Opera only for script nodes. Opera support onload for script - // nodes, but this doesn't fire when there is a load failure. - // The onreadystatechange appears to be a better way to respond - // to both success and failure. - if (ua.ie) { - n.onreadystatechange = function() { - var rs = this.readyState; - if ('loaded' === rs || 'complete' === rs) { - n.onreadystatechange = null; - f(id, url); - } - }; - - // webkit prior to 3.x is no longer supported - } else if (ua.webkit) { - if (type === 'script') { - // Safari 3.x supports the load event for script nodes (DOM2) - n.addEventListener('load', function() { - f(id, url); - }, false); - } - - // FireFox and Opera support onload (but not DOM2 in FF) handlers for - // script nodes. Opera, but not FF, supports the onload event for link - // nodes. - } else { - n.onload = function() { - f(id, url); - }; - - n.onerror = function(e) { - _fail(id, e + ': ' + url); - }; - } - }; - - _get = function(nId, tId) { - var q = queues[tId], - n = (L.isString(nId)) ? q.win.document.getElementById(nId) : nId; - if (!n) { - _fail(tId, 'target node not found: ' + nId); - } - - return n; - }; + PURGE_THRESH: 20, /** - * Removes the nodes for the specified queue - * @method _purge - * @param {string} tId the transaction id. - * @private + * Abort a transaction + * @method abort + * @static + * @param {string|object} o Either the tId or the object returned from + * script() or css(). */ - _purge = function(tId) { - var n, l, d, h, s, i, node, attr, insertBefore, - q = queues[tId]; + abort : function(o) { + var id = (L.isString(o)) ? o : o.tId, + q = queues[id]; if (q) { - n = q.nodes; - l = n.length; - d = q.win.document; - h = d.getElementsByTagName('head')[0]; - - insertBefore = q.insertBefore || - d.getElementsByTagName('base')[0]; - - if (insertBefore) { - s = _get(insertBefore, tId); - if (s) { - h = s.parentNode; - } - } - - for (i = 0; i < l; i = i + 1) { - node = n[i]; - if (node.clearAttributes) { - node.clearAttributes(); - } else { - for (attr in node) { - if (node.hasOwnProperty(attr)) { - delete node[attr]; - } - } - } - - h.removeChild(node); - } + q.aborted = true; } - q.nodes = []; - }; + }, - return { - - /** - * The number of request required before an automatic purge. - * Can be configured via the 'purgethreshold' config - * property PURGE_THRESH - * @static - * @type int - * @default 20 - * @private - */ - PURGE_THRESH: 20, - - /** - * Called by the the helper for detecting script load in Safari - * @method _finalize - * @static - * @param {string} id the transaction id. - * @private - */ - _finalize: function(id) { - setTimeout(function() { - _finish(id); - }, 0); - }, - - /** - * Abort a transaction - * @method abort - * @static - * @param {string|object} o Either the tId or the object returned from - * script() or css(). - */ - abort: function(o) { - var id = (L.isString(o)) ? o : o.tId, - q = queues[id]; - if (q) { - q.aborted = true; - } - }, - - /** - * Fetches and inserts one or more script nodes into the head - * of the current document or the document in a specified window. - * - * @method script - * @static - * @param {string|string[]} url the url or urls to the script(s). - * @param {object} opts Options: - *
          - *
          onSuccess
          - *
          - * callback to execute when the script(s) are finished loading - * The callback receives an object back with the following - * data: - *
          - *
          win
          - *
          the window the script(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted
          - *
          purge
          - *
          A function that, when executed, will remove the nodes - * that were inserted
          - *
          - *
          - *
          - *
          onTimeout
          - *
          - * callback to execute when a timeout occurs. - * The callback receives an object back with the following - * data: - *
          - *
          win
          - *
          the window the script(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted
          - *
          purge
          - *
          A function that, when executed, will remove the nodes - * that were inserted
          - *
          - *
          - *
          - *
          onEnd
          - *
          a function that executes when the transaction finishes, - * regardless of the exit path
          - *
          onFailure
          - *
          - * callback to execute when the script load operation fails - * The callback receives an object back with the following - * data: - *
          - *
          win
          - *
          the window the script(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted successfully
          - *
          purge
          - *
          A function that, when executed, will remove any nodes - * that were inserted
          - *
          - *
          - *
          - *
          context
          - *
          the execution context for the callbacks
          - *
          win
          - *
          a window other than the one the utility occupies
          - *
          autopurge
          - *
          - * setting to true will let the utilities cleanup routine purge - * the script once loaded - *
          - *
          purgethreshold
          - *
          - * The number of transaction before autopurge should be initiated - *
          - *
          data
          - *
          - * data that is supplied to the callback when the script(s) are - * loaded. - *
          - *
          insertBefore
          - *
          node or node id that will become the new node's nextSibling. - * If this is not specified, nodes will be inserted before a base - * tag should it exist. Otherwise, the nodes will be appended to the - * end of the document head.
          - *
          - *
          charset
          - *
          Node charset, default utf-8 (deprecated, use the attributes - * config)
          - *
          attributes
          - *
          An object literal containing additional attributes to add to - * the link tags
          - *
          timeout
          - *
          Number of milliseconds to wait before aborting and firing - * the timeout event
          - *
          -         *   Y.Get.script(
          -         *   ["http://yui.yahooapis.com/2.5.2/build/yahoo/yahoo-min.js",
          -         *    "http://yui.yahooapis.com/2.5.2/build/event/event-min.js"],
          -         *   {
          -         *     onSuccess: function(o) {
          -         *       this.log("won't cause error because Y is the context");
          -         *                     // immediately
          -         *     },
          -         *     onFailure: function(o) {
          -         *     },
          -         *     onTimeout: function(o) {
          -         *     },
          -         *     data: "foo",
          -         *     timeout: 10000, // 10 second timeout
          -         *     context: Y, // make the YUI instance
          -         *     // win: otherframe // target another window/frame
          -         *     autopurge: true // allow the utility to choose when to
          -         *                     // remove the nodes
          -         *     purgetheshold: 1 // purge previous transaction before
          -         *                      // next transaction
          -         *   });.
          -         * 
          - * @return {tId: string} an object containing info about the - * transaction. - */ - script: function(url, opts) { - return _queue('script', url, opts); - }, - - /** - * Fetches and inserts one or more css link nodes into the - * head of the current document or the document in a specified - * window. - * @method css - * @static - * @param {string} url the url or urls to the css file(s). - * @param {object} opts Options: - *
          - *
          onSuccess
          - *
          - * callback to execute when the css file(s) are finished loading - * The callback receives an object back with the following - * data: - *
          win
          - *
          the window the link nodes(s) were inserted into
          - *
          data
          - *
          the data object passed in when the request was made
          - *
          nodes
          - *
          An array containing references to the nodes that were - * inserted
          - *
          purge
          - *
          A function that, when executed, will remove the nodes - * that were inserted
          - *
          - *
          - * - *
          context
          - *
          the execution context for the callbacks
          - *
          win
          - *
          a window other than the one the utility occupies
          - *
          data
          - *
          - * data that is supplied to the callbacks when the nodes(s) are - * loaded. - *
          - *
          insertBefore
          - *
          node or node id that will become the new node's nextSibling
          - *
          charset
          - *
          Node charset, default utf-8 (deprecated, use the attributes - * config)
          - *
          attributes
          - *
          An object literal containing additional attributes to add to - * the link tags
          - * - *
          -         * Y.Get.css("http://localhost/css/menu.css");
          -         * 
          - *
          -         *   Y.Get.css(
          -         *   ["http://localhost/css/menu.css",
          -         *     insertBefore: 'custom-styles' // nodes will be inserted
          -         *                                   // before the specified node
          -         *   });.
          -         * 
          - * @return {tId: string} an object containing info about the - * transaction. - */ - css: function(url, opts) { - return _queue('css', url, opts); - } - }; -}(); + /** + * Fetches and inserts one or more script nodes into the head + * of the current document or the document in a specified window. + * + * @method script + * @static + * @param {string|string[]} url the url or urls to the script(s). + * @param {object} opts Options: + *
          + *
          onSuccess
          + *
          + * callback to execute when the script(s) are finished loading + * The callback receives an object back with the following + * data: + *
          + *
          win
          + *
          the window the script(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted
          + *
          purge
          + *
          A function that, when executed, will remove the nodes + * that were inserted
          + *
          + *
          + *
          + *
          onTimeout
          + *
          + * callback to execute when a timeout occurs. + * The callback receives an object back with the following + * data: + *
          + *
          win
          + *
          the window the script(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted
          + *
          purge
          + *
          A function that, when executed, will remove the nodes + * that were inserted
          + *
          + *
          + *
          + *
          onEnd
          + *
          a function that executes when the transaction finishes, + * regardless of the exit path
          + *
          onFailure
          + *
          + * callback to execute when the script load operation fails + * The callback receives an object back with the following + * data: + *
          + *
          win
          + *
          the window the script(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted successfully
          + *
          purge
          + *
          A function that, when executed, will remove any nodes + * that were inserted
          + *
          + *
          + *
          + *
          onProgress
          + *
          callback to execute when each individual file is done loading + * (useful when passing in an array of js files). Receives the same + * payload as onSuccess, with the addition of a url + * property, which identifies the file which was loaded.
          + *
          async
          + *
          + *

          When passing in an array of JS files, setting this flag to true + * will insert them into the document in parallel, as opposed to the + * default behavior, which is to chain load them serially. It will also + * set the async attribute on the script node to true.

          + *

          Setting async:true + * will lead to optimal file download performance allowing the browser to + * download multiple scripts in parallel, and execute them as soon as they + * are available.

          + *

          Note that async:true does not guarantee execution order of the + * scripts being downloaded. They are executed in whichever order they + * are received.

          + *
          + *
          context
          + *
          the execution context for the callbacks
          + *
          win
          + *
          a window other than the one the utility occupies
          + *
          autopurge
          + *
          + * setting to true will let the utilities cleanup routine purge + * the script once loaded + *
          + *
          purgethreshold
          + *
          + * The number of transaction before autopurge should be initiated + *
          + *
          data
          + *
          + * data that is supplied to the callback when the script(s) are + * loaded. + *
          + *
          insertBefore
          + *
          node or node id that will become the new node's nextSibling. + * If this is not specified, nodes will be inserted before a base + * tag should it exist. Otherwise, the nodes will be appended to the + * end of the document head.
          + *
          + *
          charset
          + *
          Node charset, default utf-8 (deprecated, use the attributes + * config)
          + *
          attributes
          + *
          An object literal containing additional attributes to add to + * the link tags
          + *
          timeout
          + *
          Number of milliseconds to wait before aborting and firing + * the timeout event
          + *
          +     *   Y.Get.script(
          +     *   ["http://yui.yahooapis.com/2.5.2/build/yahoo/yahoo-min.js",
          +     *    "http://yui.yahooapis.com/2.5.2/build/event/event-min.js"],
          +     *   {
          +     *     onSuccess: function(o) {
          +     *       this.log("won't cause error because Y is the context");
          +     *                     // immediately
          +     *     },
          +     *     onFailure: function(o) {
          +     *     },
          +     *     onTimeout: function(o) {
          +     *     },
          +     *     data: "foo",
          +     *     timeout: 10000, // 10 second timeout
          +     *     context: Y, // make the YUI instance
          +     *     // win: otherframe // target another window/frame
          +     *     autopurge: true // allow the utility to choose when to
          +     *                     // remove the nodes
          +     *     purgetheshold: 1 // purge previous transaction before
          +     *                      // next transaction
          +     *   });.
          +     * 
          + * @return {tId: string} an object containing info about the + * transaction. + */ + script: function(url, opts) { + return _queue(SCRIPT, url, opts); + }, + /** + * Fetches and inserts one or more css link nodes into the + * head of the current document or the document in a specified + * window. + * @method css + * @static + * @param {string} url the url or urls to the css file(s). + * @param {object} opts Options: + *
          + *
          onSuccess
          + *
          + * callback to execute when the css file(s) are finished loading + * The callback receives an object back with the following + * data: + *
          win
          + *
          the window the link nodes(s) were inserted into
          + *
          data
          + *
          the data object passed in when the request was made
          + *
          nodes
          + *
          An array containing references to the nodes that were + * inserted
          + *
          purge
          + *
          A function that, when executed, will remove the nodes + * that were inserted
          + *
          + *
          + * + *
          onProgress
          + *
          callback to execute when each individual file is done loading (useful when passing in an array of css files). Receives the same + * payload as onSuccess, with the addition of a url property, which identifies the file which was loaded. Currently only useful for non Webkit/Gecko browsers, + * where onload for css is detected accurately.
          + *
          async
          + *
          When passing in an array of css files, setting this flag to true will insert them + * into the document in parallel, as oppposed to the default behavior, which is to chain load them (where possible). + * This flag is more useful for scripts currently, since for css Get only chains if not Webkit/Gecko.
          + *
          context
          + *
          the execution context for the callbacks
          + *
          win
          + *
          a window other than the one the utility occupies
          + *
          data
          + *
          + * data that is supplied to the callbacks when the nodes(s) are + * loaded. + *
          + *
          insertBefore
          + *
          node or node id that will become the new node's nextSibling
          + *
          charset
          + *
          Node charset, default utf-8 (deprecated, use the attributes + * config)
          + *
          attributes
          + *
          An object literal containing additional attributes to add to + * the link tags
          + * + *
          +     * Y.Get.css("http://localhost/css/menu.css");
          +     * 
          + *
          +     *   Y.Get.css(
          +     *   ["http://localhost/css/menu.css",
          +     *     insertBefore: 'custom-styles' // nodes will be inserted
          +     *                                   // before the specified node
          +     *   });.
          +     * 
          + * @return {tId: string} an object containing info about the + * transaction. + */ + css: function(url, opts) { + return _queue('css', url, opts); + } +}; }, '@VERSION@' ,{requires:['yui-base']}); diff --git a/build/history-base/history-base-debug.js b/build/history-base/history-base-debug.js index d8b5d259c77..e262e4e1ed6 100644 --- a/build/history-base/history-base-debug.js +++ b/build/history-base/history-base-debug.js @@ -25,6 +25,12 @@ YUI.add('history-base', function(Y) { * zero or more of the following properties: * *
          + *
          force (Boolean)
          + *
          + * If `true`, a `history:change` event will be fired whenever the URL + * changes, even if there is no associated state change. Default is `false`. + *
          + * *
          initialState (Object)
          *
          * Initial state to set, as an object hash of key/value pairs. This will be @@ -171,6 +177,16 @@ Y.mix(HistoryBase.prototype, { */ config = this._config = config || {}; + /** + * If `true`, a `history:change` event will be fired whenever the URL + * changes, even if there is no associated state change. + * + * @property force + * @type Boolean + * @default false + */ + this.force = !!config.force; + /** * Resolved initial state: a merge of the user-supplied initial state * (if any) and any initial state provided by a subclass. This may @@ -544,13 +560,8 @@ Y.mix(HistoryBase.prototype, { prevState = GlobalEnv._state, removed = {}; - if (!newState) { - newState = {}; - } - - if (!options) { - options = {}; - } + newState || (newState = {}); + options || (options = {}); if (_isSimpleObject(newState) && _isSimpleObject(prevState)) { // Figure out what was added or changed. @@ -579,7 +590,7 @@ Y.mix(HistoryBase.prototype, { isChanged = newState !== prevState; } - if (isChanged) { + if (isChanged || this.force) { this._fireEvents(src, { changed : changed, newState : newState, diff --git a/build/history-base/history-base-min.js b/build/history-base/history-base-min.js index dd19f765682..b22e45f7f5c 100644 --- a/build/history-base/history-base-min.js +++ b/build/history-base/history-base-min.js @@ -1 +1 @@ -YUI.add("history-base",function(b){var i=b.Lang,e=b.Object,l=YUI.namespace("Env.History"),m=b.Array,n=b.config.doc,f=n.documentMode,j=b.config.win,c={merge:true},h="change",a="add",g="replace";function d(){this._init.apply(this,arguments);}b.augment(d,b.EventTarget,null,null,{emitFacade:true,prefix:"history",preventable:false,queueable:true});if(!l._state){l._state={};}function k(o){return i.type(o)==="object";}d.NAME="historyBase";d.SRC_ADD=a;d.SRC_REPLACE=g;d.html5=!!(j.history&&j.history.pushState&&j.history.replaceState&&("onpopstate" in j||b.UA.gecko>=2));d.nativeHashChange=("onhashchange" in j||"onhashchange" in n)&&(!f||f>7);b.mix(d.prototype,{_init:function(p){var o;p=this._config=p||{};o=this._initialState=this._initialState||p.initialState||null;this.publish(h,{broadcast:2,defaultFn:this._defChangeFn});if(o){this.replace(o);}},add:function(){var o=m(arguments,0,true);o.unshift(a);return this._change.apply(this,o);},addValue:function(p,r,o){var q={};q[p]=r;return this._change(a,q,o);},get:function(p){var q=l._state,o=k(q);if(p){return o&&e.owns(q,p)?q[p]:undefined;}else{return o?b.mix({},q,true):q;}},replace:function(){var o=m(arguments,0,true);o.unshift(g);return this._change.apply(this,o);},replaceValue:function(p,r,o){var q={};q[p]=r;return this._change(g,q,o);},_change:function(q,p,o){o=o?b.merge(c,o):c;if(o.merge&&k(p)&&k(l._state)){p=b.merge(l._state,p);}this._resolveChanges(q,p,o);return this;},_fireEvents:function(q,p,o){this.fire(h,{_options:o,changed:p.changed,newVal:p.newState,prevVal:p.prevState,removed:p.removed,src:q});e.each(p.changed,function(s,r){this._fireChangeEvent(q,r,s);},this);e.each(p.removed,function(s,r){this._fireRemoveEvent(q,r,s);},this);},_fireChangeEvent:function(q,o,p){this.fire(o+"Change",{newVal:p.newVal,prevVal:p.prevVal,src:q});},_fireRemoveEvent:function(q,o,p){this.fire(o+"Remove",{prevVal:p,src:q});},_resolveChanges:function(u,s,p){var t={},o,r=l._state,q={};if(!s){s={};}if(!p){p={};}if(k(s)&&k(r)){e.each(s,function(v,w){var x=r[w];if(v!==x){t[w]={newVal:v,prevVal:x};o=true;}},this);e.each(r,function(w,v){if(!e.owns(s,v)||s[v]===null){delete s[v];q[v]=w;o=true;}},this);}else{o=s!==r;}if(o){this._fireEvents(u,{changed:t,newState:s,prevState:r,removed:q},p);}},_storeState:function(p,o){l._state=o||{};},_defChangeFn:function(o){this._storeState(o.src,o.newVal,o._options);}},true);b.HistoryBase=d;},"@VERSION@",{requires:["event-custom-complex"]}); \ No newline at end of file +YUI.add("history-base",function(b){var i=b.Lang,e=b.Object,l=YUI.namespace("Env.History"),m=b.Array,n=b.config.doc,f=n.documentMode,j=b.config.win,c={merge:true},h="change",a="add",g="replace";function d(){this._init.apply(this,arguments);}b.augment(d,b.EventTarget,null,null,{emitFacade:true,prefix:"history",preventable:false,queueable:true});if(!l._state){l._state={};}function k(o){return i.type(o)==="object";}d.NAME="historyBase";d.SRC_ADD=a;d.SRC_REPLACE=g;d.html5=!!(j.history&&j.history.pushState&&j.history.replaceState&&("onpopstate" in j||b.UA.gecko>=2));d.nativeHashChange=("onhashchange" in j||"onhashchange" in n)&&(!f||f>7);b.mix(d.prototype,{_init:function(p){var o;p=this._config=p||{};this.force=!!p.force;o=this._initialState=this._initialState||p.initialState||null;this.publish(h,{broadcast:2,defaultFn:this._defChangeFn});if(o){this.replace(o);}},add:function(){var o=m(arguments,0,true);o.unshift(a);return this._change.apply(this,o);},addValue:function(p,r,o){var q={};q[p]=r;return this._change(a,q,o);},get:function(p){var q=l._state,o=k(q);if(p){return o&&e.owns(q,p)?q[p]:undefined;}else{return o?b.mix({},q,true):q;}},replace:function(){var o=m(arguments,0,true);o.unshift(g);return this._change.apply(this,o);},replaceValue:function(p,r,o){var q={};q[p]=r;return this._change(g,q,o);},_change:function(q,p,o){o=o?b.merge(c,o):c;if(o.merge&&k(p)&&k(l._state)){p=b.merge(l._state,p);}this._resolveChanges(q,p,o);return this;},_fireEvents:function(q,p,o){this.fire(h,{_options:o,changed:p.changed,newVal:p.newState,prevVal:p.prevState,removed:p.removed,src:q});e.each(p.changed,function(s,r){this._fireChangeEvent(q,r,s);},this);e.each(p.removed,function(s,r){this._fireRemoveEvent(q,r,s);},this);},_fireChangeEvent:function(q,o,p){this.fire(o+"Change",{newVal:p.newVal,prevVal:p.prevVal,src:q});},_fireRemoveEvent:function(q,o,p){this.fire(o+"Remove",{prevVal:p,src:q});},_resolveChanges:function(u,s,p){var t={},o,r=l._state,q={};s||(s={});p||(p={});if(k(s)&&k(r)){e.each(s,function(v,w){var x=r[w];if(v!==x){t[w]={newVal:v,prevVal:x};o=true;}},this);e.each(r,function(w,v){if(!e.owns(s,v)||s[v]===null){delete s[v];q[v]=w;o=true;}},this);}else{o=s!==r;}if(o||this.force){this._fireEvents(u,{changed:t,newState:s,prevState:r,removed:q},p);}},_storeState:function(p,o){l._state=o||{};},_defChangeFn:function(o){this._storeState(o.src,o.newVal,o._options);}},true);b.HistoryBase=d;},"@VERSION@",{requires:["event-custom-complex"]}); \ No newline at end of file diff --git a/build/history-base/history-base.js b/build/history-base/history-base.js index d8b5d259c77..e262e4e1ed6 100644 --- a/build/history-base/history-base.js +++ b/build/history-base/history-base.js @@ -25,6 +25,12 @@ YUI.add('history-base', function(Y) { * zero or more of the following properties: * *
          + *
          force (Boolean)
          + *
          + * If `true`, a `history:change` event will be fired whenever the URL + * changes, even if there is no associated state change. Default is `false`. + *
          + * *
          initialState (Object)
          *
          * Initial state to set, as an object hash of key/value pairs. This will be @@ -171,6 +177,16 @@ Y.mix(HistoryBase.prototype, { */ config = this._config = config || {}; + /** + * If `true`, a `history:change` event will be fired whenever the URL + * changes, even if there is no associated state change. + * + * @property force + * @type Boolean + * @default false + */ + this.force = !!config.force; + /** * Resolved initial state: a merge of the user-supplied initial state * (if any) and any initial state provided by a subclass. This may @@ -544,13 +560,8 @@ Y.mix(HistoryBase.prototype, { prevState = GlobalEnv._state, removed = {}; - if (!newState) { - newState = {}; - } - - if (!options) { - options = {}; - } + newState || (newState = {}); + options || (options = {}); if (_isSimpleObject(newState) && _isSimpleObject(prevState)) { // Figure out what was added or changed. @@ -579,7 +590,7 @@ Y.mix(HistoryBase.prototype, { isChanged = newState !== prevState; } - if (isChanged) { + if (isChanged || this.force) { this._fireEvents(src, { changed : changed, newState : newState, diff --git a/build/history-hash-ie/history-hash-ie-debug.js b/build/history-hash-ie/history-hash-ie-debug.js index 425f5fbce7f..1e2e9a0969f 100644 --- a/build/history-hash-ie/history-hash-ie-debug.js +++ b/build/history-hash-ie/history-hash-ie-debug.js @@ -61,18 +61,17 @@ if (Y.UA.ie && !Y.HistoryBase.nativeHashChange) { return; } - Y.log('updating history iframe: ' + hash, 'info', 'history'); - - iframeDoc.open().close(); + Y.log('updating history iframe: ' + hash + ', replace: ' + !!replace, 'info', 'history'); if (replace) { iframeLocation.replace(hash.charAt(0) === '#' ? hash : '#' + hash); } else { + iframeDoc.open().close(); iframeLocation.hash = hash; } }; - Do.after(HistoryHash._updateIframe, HistoryHash, 'replaceHash', HistoryHash, true); + Do.before(HistoryHash._updateIframe, HistoryHash, 'replaceHash', HistoryHash, true); if (!iframe) { Y.on('domready', function () { diff --git a/build/history-hash-ie/history-hash-ie-min.js b/build/history-hash-ie/history-hash-ie-min.js index af7996d9fd6..0e4cf68ccb8 100644 --- a/build/history-hash-ie/history-hash-ie-min.js +++ b/build/history-hash-ie/history-hash-ie-min.js @@ -1 +1 @@ -YUI.add("history-hash-ie",function(g){if(g.UA.ie&&!g.HistoryBase.nativeHashChange){var c=g.Do,d=YUI.namespace("Env.HistoryHash"),b=g.HistoryHash,e=d._iframe,f=g.config.win,a=f.location;b.getIframeHash=function(){if(!e||!e.contentWindow){return"";}var h=b.hashPrefix,i=e.contentWindow.location.hash.substr(1);return h&&i.indexOf(h)===0?i.replace(h,""):i;};b._updateIframe=function(i,h){var j=e&&e.contentWindow&&e.contentWindow.document,k=j&&j.location;if(!j||!k){return;}j.open().close();if(h){k.replace(i.charAt(0)==="#"?i:"#"+i);}else{k.hash=i;}};c.after(b._updateIframe,b,"replaceHash",b,true);if(!e){g.on("domready",function(){var h=b.getHash();e=d._iframe=g.Node.getDOMNode(g.Node.create('