From 48bfcfadd9e9a6a00452d3f76aabbf2a9ffc6658 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Mon, 2 Dec 2013 13:15:31 -0500 Subject: [PATCH] Upgrade Ember.js, fix swallowing of template errors, report unresolved promises in development mode. --- Gemfile | 4 +- Gemfile_rails4.lock | 16 +- .../controllers/composer_controller.js | 7 +- .../discourse/models/post_stream.js | 11 +- .../javascripts/discourse/models/topic.js | 4 +- .../routes/topic_from_params_route.js | 1 - .../discourse/routes/topic_route.js | 2 - .../common/_discourse_javascript.html.erb | 18 + ...nt_test.js => home_logo_component_test.js} | 53 +- .../assets/javascripts/development/ember.js | 7077 ++++++++++------- .../assets/javascripts/ember-renderspeed.js | 4 + vendor/assets/javascripts/handlebars.js | 4593 ++++++----- vendor/assets/javascripts/production/ember.js | 6689 +++++++++------- 13 files changed, 10620 insertions(+), 7859 deletions(-) rename test/javascripts/components/{logo_component_test.js => home_logo_component_test.js} (84%) diff --git a/Gemfile b/Gemfile index 3a1b44c26a4..65132f8f254 100644 --- a/Gemfile +++ b/Gemfile @@ -64,8 +64,8 @@ gem 'html_truncator' # we had issues with latest, stick to the rev till we figure this out # PR that makes it all hang together welcome gem 'ember-rails' -gem 'ember-source', '1.0.0.rc6.2' -gem 'handlebars-source', '1.0.12' +gem 'ember-source', '~> 1.2.0.1' +gem 'handlebars-source', '~> 1.1.2' gem 'barber' gem 'vestal_versions', git: 'https://github.com/SamSaffron/vestal_versions' diff --git a/Gemfile_rails4.lock b/Gemfile_rails4.lock index 8d4550d746e..64ce6a6a9a3 100644 --- a/Gemfile_rails4.lock +++ b/Gemfile_rails4.lock @@ -160,16 +160,17 @@ GEM diffy (3.0.1) ember-data-source (0.14) ember-source - ember-rails (0.13.0) + ember-rails (0.14.1) active_model_serializers barber (>= 0.4.1) ember-data-source ember-source execjs (>= 1.2) handlebars-source + jquery-rails (>= 1.0.17) railties (>= 3.1) - ember-source (1.0.0.rc6.2) - handlebars-source (= 1.0.12) + ember-source (1.2.0.1) + handlebars-source (~> 1.1.2) erubis (2.7.0) eventmachine (1.0.3) excon (0.28.0) @@ -202,7 +203,7 @@ GEM fspath (2.0.5) given_core (3.1.1) sorcerer (>= 0.3.7) - handlebars-source (1.0.12) + handlebars-source (1.1.2) hashie (2.0.5) highline (1.6.20) hike (1.2.3) @@ -221,6 +222,9 @@ GEM image_size (1.1.3) image_sorcery (1.1.0) in_threads (1.2.0) + jquery-rails (3.0.4) + railties (>= 3.0, < 5.0) + thor (>= 0.14, < 2.0) json (1.8.1) jwt (0.1.8) multi_json (>= 1.5) @@ -471,7 +475,7 @@ DEPENDENCIES discourse_plugin! email_reply_parser! ember-rails - ember-source (= 1.0.0.rc6.2) + ember-source (~> 1.2.0.1) eventmachine fabrication fakeweb (~> 1.3.0) @@ -481,7 +485,7 @@ DEPENDENCIES fastimage flamegraph! fog (= 1.18.0) - handlebars-source (= 1.0.12) + handlebars-source (~> 1.1.2) highline hiredis html_truncator diff --git a/app/assets/javascripts/discourse/controllers/composer_controller.js b/app/assets/javascripts/discourse/controllers/composer_controller.js index 3b3ee5e24b8..37201029e78 100644 --- a/app/assets/javascripts/discourse/controllers/composer_controller.js +++ b/app/assets/javascripts/discourse/controllers/composer_controller.js @@ -283,8 +283,7 @@ Discourse.ComposerController = Discourse.Controller.extend({ } else { opts.tested = true; if (!opts.ignoreIfChanged) { - this.cancelComposer().then(function() { self.open(opts); }, - function() { return promise.reject(); }); + this.cancelComposer().then(function() { self.open(opts); }).fail(function() { return promise.reject(); }); } return promise; } @@ -341,10 +340,8 @@ Discourse.ComposerController = Discourse.Controller.extend({ self.destroyDraft(); self.get('model').clearState(); self.close(); - promise.resolve(); - } else { - promise.reject(); } + promise.resolve(); }); } else { // it is possible there is some sort of crazy draft with no body ... just give up on it diff --git a/app/assets/javascripts/discourse/models/post_stream.js b/app/assets/javascripts/discourse/models/post_stream.js index 5209a33c3e3..6c22b9d8680 100644 --- a/app/assets/javascripts/discourse/models/post_stream.js +++ b/app/assets/javascripts/discourse/models/post_stream.js @@ -284,10 +284,10 @@ Discourse.PostStream = Em.Object.extend({ var self = this; // Make sure we can append more posts - if (!self.get('canAppendMore')) { return Ember.RSVP.reject(); } + if (!self.get('canAppendMore')) { return Ember.RSVP.resolve(); } var postIds = self.get('nextWindow'); - if (Ember.isEmpty(postIds)) { return Ember.RSVP.reject(); } + if (Ember.isEmpty(postIds)) { return Ember.RSVP.resolve(); } self.set('loadingBelow', true); @@ -310,14 +310,13 @@ Discourse.PostStream = Em.Object.extend({ @returns {Ember.Deferred} a promise that's resolved when the posts have been added. **/ prependMore: function() { - var postStream = this, - rejectedPromise = Ember.RSVP.reject(); + var postStream = this; // Make sure we can append more posts - if (!postStream.get('canPrependMore')) { return rejectedPromise; } + if (!postStream.get('canPrependMore')) { return Ember.RSVP.resolve(); } var postIds = postStream.get('previousWindow'); - if (Ember.isEmpty(postIds)) { return rejectedPromise; } + if (Ember.isEmpty(postIds)) { return Ember.RSVP.resolve(); } postStream.set('loadingAbove', true); return postStream.findPostsByIds(postIds.reverse()).then(function(posts) { diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js index 954e212093f..be6df660ea0 100644 --- a/app/assets/javascripts/discourse/models/topic.js +++ b/app/assets/javascripts/discourse/models/topic.js @@ -355,7 +355,7 @@ Discourse.Topic.reopenClass({ data: {destination_topic_id: destinationTopicId} }).then(function (result) { if (result.success) return result; - promise.reject(); + promise.reject(new Error("error merging topic")); }); return promise; }, @@ -366,7 +366,7 @@ Discourse.Topic.reopenClass({ data: opts }).then(function (result) { if (result.success) return result; - promise.reject(); + promise.reject(new Error("error moving posts topic")); }); return promise; } diff --git a/app/assets/javascripts/discourse/routes/topic_from_params_route.js b/app/assets/javascripts/discourse/routes/topic_from_params_route.js index b940df2603b..248de7f3c62 100644 --- a/app/assets/javascripts/discourse/routes/topic_from_params_route.js +++ b/app/assets/javascripts/discourse/routes/topic_from_params_route.js @@ -7,7 +7,6 @@ @module Discourse **/ Discourse.TopicFromParamsRoute = Discourse.Route.extend({ - abc: 'asdfasdf', setupController: function(controller, params) { params = params || {}; diff --git a/app/assets/javascripts/discourse/routes/topic_route.js b/app/assets/javascripts/discourse/routes/topic_route.js index b0804416332..2dcc146fe75 100644 --- a/app/assets/javascripts/discourse/routes/topic_route.js +++ b/app/assets/javascripts/discourse/routes/topic_route.js @@ -7,8 +7,6 @@ @module Discourse **/ Discourse.TopicRoute = Discourse.Route.extend({ - abc: 'def', - redirect: function() { Discourse.redirectIfLoginRequired(this); }, actions: { diff --git a/app/views/common/_discourse_javascript.html.erb b/app/views/common/_discourse_javascript.html.erb index e24109a9ba2..bb8d44c3f5c 100644 --- a/app/views/common/_discourse_javascript.html.erb +++ b/app/views/common/_discourse_javascript.html.erb @@ -11,6 +11,24 @@ })(); +<% if Rails.env.development? %> + +<% end %> + + ```handlebars + {{foo}} + {{partial "nav"}} ``` - The `data-template-name` attribute of a partial template - is prefixed with an underscore. + The above example template will render a template named + "_nav", which has the same context as the parent template + it's rendered into, so if the "_nav" template also referenced + `{{foo}}`, it would print the same thing as the `{{foo}}` + in the above example. - ```html - + If a "_nav" template isn't found, the `partial` helper will + fall back to a template named "nav". + + ## Bound template names + + The parameter supplied to `partial` can also be a path + to a property containing a template name, e.g.: + + ```handlebars + {{partial someTemplateName}} + ``` + + The above example will look up the value of `someTemplateName` + on the template context (e.g. a controller) and use that + value as the name of the template to render. If the resolved + value is falsy, nothing will be rendered. If `someTemplateName` + changes, the partial will be re-rendered using the new template + name. + + ## Setting the partial's context with `with` + + The `partial` helper can be used in conjunction with the `with` + helper to set a context that will be used by the partial: + + ```handlebars + {{#with currentUser}} + {{partial "user_info"}} + {{/with}} ``` @method partial @@ -26975,6 +27366,30 @@ Ember.Handlebars.registerHelper('template', function(name, options) { */ Ember.Handlebars.registerHelper('partial', function(name, options) { + + var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this; + + if (options.types[0] === "ID") { + // Helper was passed a property path; we need to + // create a binding that will re-render whenever + // this property changes. + options.fn = function(context, fnOptions) { + var partialName = Ember.Handlebars.get(context, name, fnOptions); + renderPartial(context, partialName, fnOptions); + }; + + return Ember.Handlebars.bind.call(context, name, options, true, exists); + } else { + // Render the partial right into parent template. + renderPartial(context, name, options); + } +}); + +function exists(value) { + return !Ember.isNone(value); +} + +function renderPartial(context, name, options) { var nameParts = name.split("/"), lastPart = nameParts[nameParts.length - 1]; @@ -26989,8 +27404,8 @@ Ember.Handlebars.registerHelper('partial', function(name, options) { template = template || deprecatedTemplate; - template(this, { data: options.data }); -}); + template(context, { data: options.data }); +} })(); @@ -27231,7 +27646,7 @@ var get = Ember.get, set = Ember.set; Ember.TextSupport = Ember.Mixin.create({ value: "", - attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex'], + attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly'], placeholder: null, disabled: false, maxlength: null, @@ -27410,7 +27825,7 @@ var get = Ember.get, set = Ember.set; The internal class used to create text inputs when the `{{input}}` helper is used with `type` of `text`. - See [handlebars.helpers.input](api/classes/Ember.Handlebars.helpers.html#method_input) for usage details. + See [handlebars.helpers.input](Ember.Handlebars.helpers.html#method_input) for usage details. ## Layout and LayoutName properties @@ -27633,14 +28048,14 @@ Ember.TextArea = Ember.Component.extend(Ember.TextSupport, { rows: null, cols: null, - _updateElementValue: Ember.observer(function() { + _updateElementValue: Ember.observer('value', function() { // We do this check so cursor position doesn't get affected in IE var value = get(this, 'value'), $el = this.$(); if ($el && value !== $el.val()) { $el.val(value); } - }, 'value'), + }), init: function() { this._super(); @@ -27698,7 +28113,7 @@ Ember.SelectOption = Ember.View.extend({ } }).property('content', 'parentView.selection'), - labelPathDidChange: Ember.observer(function() { + labelPathDidChange: Ember.observer('parentView.optionLabelPath', function() { var labelPath = get(this, 'parentView.optionLabelPath'); if (!labelPath) { return; } @@ -27706,9 +28121,9 @@ Ember.SelectOption = Ember.View.extend({ Ember.defineProperty(this, 'label', Ember.computed(function() { return get(this, labelPath); }).property(labelPath)); - }, 'parentView.optionLabelPath'), + }), - valuePathDidChange: Ember.observer(function() { + valuePathDidChange: Ember.observer('parentView.optionValuePath', function() { var valuePath = get(this, 'parentView.optionValuePath'); if (!valuePath) { return; } @@ -27716,7 +28131,7 @@ Ember.SelectOption = Ember.View.extend({ Ember.defineProperty(this, 'value', Ember.computed(function() { return get(this, valuePath); }).property(valuePath)); - }, 'parentView.optionValuePath') + }) }); Ember.SelectOptgroup = Ember.CollectionView.extend({ @@ -28210,7 +28625,7 @@ function program7(depth0,data) { } }, - selectionDidChange: Ember.observer(function() { + selectionDidChange: Ember.observer('selection.@each', function() { var selection = get(this, 'selection'); if (get(this, 'multiple')) { if (!isArray(selection)) { @@ -28221,9 +28636,9 @@ function program7(depth0,data) { } else { this._selectionDidChangeSingle(); } - }, 'selection.@each'), + }), - valueDidChange: Ember.observer(function() { + valueDidChange: Ember.observer('value', function() { var content = get(this, 'content'), value = get(this, 'value'), valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''), @@ -28237,7 +28652,7 @@ function program7(depth0,data) { this.set('selection', selection); } - }, 'value'), + }), _triggerChange: function() { @@ -28419,6 +28834,7 @@ function program7(depth0,data) { * `indeterminate` * `name` + When set to a quoted string, these values will be directly applied to the HTML element. When left unquoted, these values will be bound to a property on the template's current rendering context (most typically a controller instance). @@ -28649,6 +29065,38 @@ Ember.Handlebars.registerHelper('textarea', function(options) { +(function() { +Ember.ComponentLookup = Ember.Object.extend({ + lookupFactory: function(name, container) { + + container = container || this.container; + + var fullName = 'component:' + name, + templateFullName = 'template:components/' + name, + templateRegistered = container && container.has(templateFullName); + + if (templateRegistered) { + container.injection(fullName, 'layout', templateFullName); + } + + var Component = container.lookupFactory(fullName); + + // Only treat as a component if either the component + // or a template has been registered. + if (templateRegistered || Component) { + if (!Component) { + container.register(fullName, Ember.Component); + Component = container.lookupFactory(fullName); + } + return Component; + } + } +}); + +})(); + + + (function() { /*globals Handlebars */ /** @@ -28736,6 +29184,10 @@ function registerComponent(container, name) { Ember.Handlebars.helper(name, Component); } +function registerComponentLookup(container) { + container.register('component-lookup:main', Ember.ComponentLookup); +} + /* We tie this to application.load to ensure that we've at least attempted to bootstrap at the point that the application is loaded. @@ -28754,9 +29206,9 @@ Ember.onLoad('Ember.Application', function(Application) { }); Application.initializer({ - name: 'registerComponents', + name: 'registerComponentLookup', after: 'domTemplates', - initialize: registerComponents + initialize: registerComponentLookup }); }); @@ -28881,7 +29333,7 @@ define("route-recognizer", results.push(new StarSegment(match[1])); names.push(match[1]); types.stars++; - } else if (segment === "") { + } else if(segment === "") { results.push(new EpsilonSegment()); } else { results.push(new StaticSegment(segment)); @@ -29012,7 +29464,7 @@ define("route-recognizer", return states.sort(function(a, b) { if (a.types.stars !== b.types.stars) { return a.types.stars - b.types.stars; } if (a.types.dynamics !== b.types.dynamics) { return a.types.dynamics - b.types.dynamics; } - if (a.types.statics !== b.types.statics) { return a.types.statics - b.types.statics; } + if (a.types.statics !== b.types.statics) { return b.types.statics - a.types.statics; } return 0; }); @@ -29030,19 +29482,31 @@ define("route-recognizer", return nextStates; } - function findHandler(state, path) { + function findHandler(state, path, queryParams) { var handlers = state.handlers, regex = state.regex; var captures = path.match(regex), currentCapture = 1; var result = []; for (var i=0, l=handlers.length; i 0) { + currentResult.queryParams = activeQueryParams; + } + result.push(currentResult); } return result; @@ -29097,7 +29561,11 @@ define("route-recognizer", regex += segment.regex(); } - handlers.push({ handler: route.handler, names: names }); + var handler = { handler: route.handler, names: names }; + if(route.queryParams) { + handler.queryParams = route.queryParams; + } + handlers.push(handler); } if (isEmpty) { @@ -29149,12 +29617,61 @@ define("route-recognizer", if (output.charAt(0) !== '/') { output = '/' + output; } + if (params && params.queryParams) { + output += this.generateQueryString(params.queryParams, route.handlers); + } + return output; }, + generateQueryString: function(params, handlers) { + var pairs = [], allowedParams = []; + for(var i=0; i < handlers.length; i++) { + var currentParamList = handlers[i].queryParams; + if(currentParamList) { + allowedParams.push.apply(allowedParams, currentParamList); + } + } + for(var key in params) { + if (params.hasOwnProperty(key)) { + if(allowedParams.indexOf(key) === -1) { + throw 'Query param "' + key + '" is not specified as a valid param for this route'; + } + var value = params[key]; + var pair = encodeURIComponent(key); + if(value !== true) { + pair += "=" + encodeURIComponent(value); + } + pairs.push(pair); + } + } + + if (pairs.length === 0) { return ''; } + + return "?" + pairs.join("&"); + }, + + parseQueryString: function(queryString) { + var pairs = queryString.split("&"), queryParams = {}; + for(var i=0; i < pairs.length; i++) { + var pair = pairs[i].split('='), + key = decodeURIComponent(pair[0]), + value = pair[1] ? decodeURIComponent(pair[1]) : true; + queryParams[key] = value; + } + return queryParams; + }, + recognize: function(path) { var states = [ this.rootState ], - pathLen, i, l; + pathLen, i, l, queryStart, queryParams = {}; + + queryStart = path.indexOf('?'); + if (queryStart !== -1) { + var queryString = path.substr(queryStart + 1, path.length); + path = path.substr(0, queryStart); + queryParams = this.parseQueryString(queryString); + } // DEBUG GROUP path @@ -29182,7 +29699,7 @@ define("route-recognizer", var state = solutions[0]; if (state && state.handlers) { - return findHandler(state, path); + return findHandler(state, path, queryParams); } } }; @@ -29207,12 +29724,25 @@ define("route-recognizer", if (callback.length === 0) { throw new Error("You must have an argument in the function passed to `to`"); } this.matcher.addChild(this.path, target, callback, this.delegate); } + return this; + }, + + withQueryParams: function() { + if (arguments.length === 0) { throw new Error("you must provide arguments to the withQueryParams method"); } + for (var i = 0; i < arguments.length; i++) { + if (typeof arguments[i] !== "string") { + throw new Error('you should call withQueryParams with a list of strings, e.g. withQueryParams("foo", "bar")'); + } + } + var queryParams = [].slice.call(arguments); + this.matcher.addQueryParams(this.path, queryParams); } }; function Matcher(target) { this.routes = {}; this.children = {}; + this.queryParams = {}; this.target = target; } @@ -29221,6 +29751,10 @@ define("route-recognizer", this.routes[path] = handler; }, + addQueryParams: function(path, params) { + this.queryParams[path] = params; + }, + addChild: function(path, target, callback, delegate) { var matcher = new Matcher(target); this.children[path] = matcher; @@ -29247,23 +29781,26 @@ define("route-recognizer", }; } - function addRoute(routeArray, path, handler) { + function addRoute(routeArray, path, handler, queryParams) { var len = 0; for (var i=0, l=routeArray.length; i 0) { + var err = 'You supplied the params '; + err += missingParams.map(function(param) { + return '"' + param + "=" + queryParams[param] + '"'; + }).join(' and '); + + err += ' which are not valid for the "' + handlerName + '" handler or its parents'; + + throw new Error(err); + } + return this.recognizer.generate(handlerName, params); }, isActive: function(handlerName) { - var contexts = slice.call(arguments, 1); + var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), + contexts = partitionedArgs[0], + queryParams = partitionedArgs[1], + activeQueryParams = {}, + effectiveQueryParams = {}; var targetHandlerInfos = this.targetHandlerInfos, found = false, names, object, handlerInfo, handlerObj; @@ -29601,19 +30219,24 @@ define("router", if (!targetHandlerInfos) { return false; } var recogHandlers = this.recognizer.handlersFor(targetHandlerInfos[targetHandlerInfos.length - 1].name); - for (var i=targetHandlerInfos.length-1; i>=0; i--) { handlerInfo = targetHandlerInfos[i]; if (handlerInfo.name === handlerName) { found = true; } if (found) { - if (contexts.length === 0) { break; } + var recogHandler = recogHandlers[i]; - if (handlerInfo.isDynamic) { + merge(activeQueryParams, handlerInfo.queryParams); + if (queryParams !== false) { + merge(effectiveQueryParams, handlerInfo.queryParams); + mergeSomeKeys(effectiveQueryParams, queryParams, recogHandler.queryParams); + } + + if (handlerInfo.isDynamic && contexts.length > 0) { object = contexts.pop(); if (isParam(object)) { - var recogHandler = recogHandlers[i], name = recogHandler.names[0]; + var name = recogHandler.names[0]; if ("" + object !== this.currentParams[name]) { return false; } } else if (handlerInfo.context !== object) { return false; @@ -29622,7 +30245,8 @@ define("router", } } - return contexts.length === 0 && found; + + return contexts.length === 0 && found && queryParamsEqual(activeQueryParams, effectiveQueryParams); }, trigger: function(name) { @@ -29645,7 +30269,7 @@ define("router", a shared pivot parent route and other data necessary to perform a transition. */ - function getMatchPoint(router, handlers, objects, inputParams) { + function getMatchPoint(router, handlers, objects, inputParams, queryParams) { var matchPoint = handlers.length, providedModels = {}, i, @@ -29700,6 +30324,12 @@ define("router", } } + // If there is an old handler, see if query params are the same. If there isn't an old handler, + // hasChanged will already be true here + if(oldHandlerInfo && !queryParamsEqual(oldHandlerInfo.queryParams, handlerObj.queryParams)) { + hasChanged = true; + } + if (hasChanged) { matchPoint = i; } } @@ -29707,7 +30337,10 @@ define("router", throw new Error("More context objects were passed than there are dynamic segments for the route: " + handlers[handlers.length - 1].handler); } - return { matchPoint: matchPoint, providedModels: providedModels, params: params, handlerParams: handlerParams }; + var pivotHandlerInfo = currentHandlerInfos[matchPoint - 1], + pivotHandler = pivotHandlerInfo && pivotHandlerInfo.handler; + + return { matchPoint: matchPoint, providedModels: providedModels, params: params, handlerParams: handlerParams, pivotHandler: pivotHandler }; } function getMatchPointObject(objects, handlerName, activeTransition, paramName, params) { @@ -29730,9 +30363,31 @@ define("router", } function isParam(object) { - return (typeof object === "string" || object instanceof String || !isNaN(object)); + return (typeof object === "string" || object instanceof String || typeof object === "number" || object instanceof Number); } + + + /** + @private + + This method takes a handler name and returns a list of query params + that are valid to pass to the handler or its parents + + @param {Router} router + @param {String} handlerName + @return {Array[String]} a list of query parameters + */ + function queryParamsForHandler(router, handlerName) { + var handlers = router.recognizer.handlersFor(handlerName), + queryParams = []; + + for (var i = 0; i < handlers.length; i++) { + queryParams.push.apply(queryParams, handlers[i].queryParams || []); + } + + return queryParams; + } /** @private @@ -29744,13 +30399,17 @@ define("router", @param {Array[Object]} objects @return {Object} a serialized parameter hash */ - function paramsForHandler(router, handlerName, objects) { + function paramsForHandler(router, handlerName, objects, queryParams) { var handlers = router.recognizer.handlersFor(handlerName), params = {}, - matchPoint = getMatchPoint(router, handlers, objects).matchPoint, + handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams), + matchPoint = getMatchPoint(router, handlerInfos, objects).matchPoint, + mergedQueryParams = {}, object, handlerObj, handler, names, i; + params.queryParams = {}; + for (i=0; i 0) { + handlerInfo.queryParams = activeQueryParams; + } + + handlerInfos.push(handlerInfo); + } + + return handlerInfos; + } + + /** + @private + */ + function createQueryParamTransition(router, queryParams, isIntermediate) { + var currentHandlers = router.currentHandlerInfos, + currentHandler = currentHandlers[currentHandlers.length - 1], + name = currentHandler.name; + + log(router, "Attempting query param transition"); + + return createNamedTransition(router, [name, queryParams], isIntermediate); + } + + /** + @private + */ + function createNamedTransition(router, args, isIntermediate) { + var partitionedArgs = extractQueryParams(args), + pureArgs = partitionedArgs[0], + queryParams = partitionedArgs[1], + handlers = router.recognizer.handlersFor(pureArgs[0]), + handlerInfos = generateHandlerInfosWithQueryParams(router, handlers, queryParams); + + + log(router, "Attempting transition to " + pureArgs[0]); + + return performTransition(router, + handlerInfos, + slice.call(pureArgs, 1), + router.currentParams, + queryParams, + null, + isIntermediate); + } + + /** + @private + */ + function createURLTransition(router, url, isIntermediate) { var results = router.recognizer.recognize(url), - currentHandlerInfos = router.currentHandlerInfos; + currentHandlerInfos = router.currentHandlerInfos, + queryParams = {}, + i, len; log(router, "Attempting URL transition to " + url); + if (results) { + // Make sure this route is actually accessible by URL. + for (i = 0, len = results.length; i < len; ++i) { + + if (router.getHandler(results[i].handler).inaccessibleByURL) { + results = null; + break; + } + } + } + if (!results) { return errorTransition(router, new Router.UnrecognizedURLError(url)); } - return performTransition(router, results, [], {}); + for(i = 0, len = results.length; i < len; i++) { + merge(queryParams, results[i].queryParams); + } + + return performTransition(router, results, [], {}, queryParams, null, isIntermediate); } @@ -29888,13 +30635,14 @@ define("router", checkAbort(transition); setContext(handler, context); + setQueryParams(handler, handlerInfo.queryParams); - if (handler.setup) { handler.setup(context); } + if (handler.setup) { handler.setup(context, handlerInfo.queryParams); } checkAbort(transition); } catch(e) { if (!(e instanceof Router.TransitionAborted)) { // Trigger the `error` event starting from this failed handler. - trigger(transition.router, currentHandlerInfos.concat(handlerInfo), true, ['error', e, transition]); + transition.trigger(true, 'error', e, transition, handler); } // Propagate the error so that the transition promise will reject. @@ -29920,11 +30668,34 @@ define("router", } } + /** + @private + + determines if two queryparam objects are the same or not + **/ + function queryParamsEqual(a, b) { + a = a || {}; + b = b || {}; + var checkedKeys = [], key; + for(key in a) { + if (!a.hasOwnProperty(key)) { continue; } + if(b[key] !== a[key]) { return false; } + checkedKeys.push(key); + } + for(key in b) { + if (!b.hasOwnProperty(key)) { continue; } + if (~checkedKeys.indexOf(key)) { continue; } + // b has a key not in a + return false; + } + return true; + } + /** @private This function is called when transitioning from one URL to - another to determine which handlers are not longer active, + another to determine which handlers are no longer active, which handlers are newly active, and which handlers remain active but have their context changed. @@ -29969,19 +30740,21 @@ define("router", unchanged: [] }; - var handlerChanged, contextChanged, i, l; + var handlerChanged, contextChanged, queryParamsChanged, i, l; for (i=0, l=newHandlers.length; i 0 && array[len - 1] && array[len - 1].hasOwnProperty('queryParams')) { + queryParams = array[len - 1].queryParams; + head = slice.call(array, 0, len - 1); + return [head, queryParams]; + } else { + return [array, null]; + } + } + + function performIntermediateTransition(router, recogHandlers, matchPointResults) { + + var handlerInfos = generateHandlerInfos(router, recogHandlers); + for (var i = 0; i < handlerInfos.length; ++i) { + var handlerInfo = handlerInfos[i]; + handlerInfo.context = matchPointResults.providedModels[handlerInfo.name]; + } + + var stubbedTransition = { + router: router, + isAborted: false + }; + + setupContexts(stubbedTransition, handlerInfos); + } + /** @private Creates, begins, and returns a Transition. */ - function performTransition(router, recogHandlers, providedModelsArray, params, data) { + function performTransition(router, recogHandlers, providedModelsArray, params, queryParams, data, isIntermediate) { - var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params), + var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params, queryParams), targetName = recogHandlers[recogHandlers.length - 1].handler, wasTransitioning = false, currentHandlerInfos = router.currentHandlerInfos; + if (isIntermediate) { + return performIntermediateTransition(router, recogHandlers, matchPointResults); + } + // Check if there's already a transition underway. if (router.activeTransition) { - if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray)) { + if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray, queryParams)) { return router.activeTransition; } router.activeTransition.abort(); @@ -30063,9 +30880,12 @@ define("router", transition.providedModelsArray = providedModelsArray; transition.params = matchPointResults.params; transition.data = data || {}; + transition.queryParams = queryParams; + transition.pivotHandler = matchPointResults.pivotHandler; router.activeTransition = transition; var handlerInfos = generateHandlerInfos(router, recogHandlers); + transition.handlerInfos = handlerInfos; // Fire 'willTransition' event on current handlers, but don't fire it // if a transition was already underway. @@ -30074,7 +30894,7 @@ define("router", } log(router, transition.sequence, "Beginning validation for transition to " + transition.targetName); - validateEntry(transition, handlerInfos, 0, matchPointResults.matchPoint, matchPointResults.handlerParams) + validateEntry(transition, matchPointResults.matchPoint, matchPointResults.handlerParams) .then(transitionSuccess, transitionFailure); return transition; @@ -30083,14 +30903,10 @@ define("router", checkAbort(transition); try { - log(router, transition.sequence, "Validation succeeded, finalizing transition;"); + finalizeTransition(transition, handlerInfos); - // Don't overwrite contexts / update URL if this was a noop transition. - if (!currentHandlerInfos || !currentHandlerInfos.length || - !router.recognizer.hasRoute(currentHandlerInfos[currentHandlerInfos.length - 1].name) || - currentHandlerInfos.length !== matchPointResults.matchPoint) { - finalizeTransition(transition, handlerInfos); - } + // currentHandlerInfos was updated in finalizeTransition + trigger(router, router.currentHandlerInfos, true, ['didTransition']); if (router.didTransition) { router.didTransition(handlerInfos); @@ -30099,6 +30915,7 @@ define("router", log(router, transition.sequence, "TRANSITION COMPLETE."); // Resolve with the final handler. + transition.isActive = false; deferred.resolve(handlerInfos[handlerInfos.length - 1].handler); } catch(e) { deferred.reject(e); @@ -30129,11 +30946,15 @@ define("router", var handlerObj = recogHandlers[i], isDynamic = handlerObj.isDynamic || (handlerObj.names && handlerObj.names.length); - handlerInfos.push({ + var handlerInfo = { isDynamic: !!isDynamic, name: handlerObj.handler, handler: router.getHandler(handlerObj.handler) - }); + }; + if(handlerObj.queryParams) { + handlerInfo.queryParams = handlerObj.queryParams; + } + handlerInfos.push(handlerInfo); } return handlerInfos; } @@ -30141,7 +30962,7 @@ define("router", /** @private */ - function transitionsIdentical(oldTransition, targetName, providedModelsArray) { + function transitionsIdentical(oldTransition, targetName, providedModelsArray, queryParams) { if (oldTransition.targetName !== targetName) { return false; } @@ -30151,6 +30972,11 @@ define("router", for (var i = 0, len = oldModels.length; i < len; ++i) { if (oldModels[i] !== providedModelsArray[i]) { return false; } } + + if(!queryParamsEqual(oldTransition.queryParams, queryParams)) { + return false; + } + return true; } @@ -30162,25 +30988,39 @@ define("router", */ function finalizeTransition(transition, handlerInfos) { + log(transition.router, transition.sequence, "Validation succeeded, finalizing transition;"); + var router = transition.router, seq = transition.sequence, - handlerName = handlerInfos[handlerInfos.length - 1].name; + handlerName = handlerInfos[handlerInfos.length - 1].name, + urlMethod = transition.urlMethod, + i; // Collect params for URL. var objects = [], providedModels = transition.providedModelsArray.slice(); - for (var i = handlerInfos.length - 1; i>=0; --i) { + for (i = handlerInfos.length - 1; i>=0; --i) { var handlerInfo = handlerInfos[i]; if (handlerInfo.isDynamic) { var providedModel = providedModels.pop(); objects.unshift(isParam(providedModel) ? providedModel.toString() : handlerInfo.context); } + + if (handlerInfo.handler.inaccessibleByURL) { + urlMethod = null; + } } - var params = paramsForHandler(router, handlerName, objects); + var newQueryParams = {}; + for (i = handlerInfos.length - 1; i>=0; --i) { + merge(newQueryParams, handlerInfos[i].queryParams); + } + router.currentQueryParams = newQueryParams; + + + var params = paramsForHandler(router, handlerName, objects, transition.queryParams); router.currentParams = params; - var urlMethod = transition.urlMethod; if (urlMethod) { var url = router.recognizer.generate(handlerName, params); @@ -30203,7 +31043,10 @@ define("router", and `afterModel` in promises, and checks for redirects/aborts between each. */ - function validateEntry(transition, handlerInfos, index, matchPoint, handlerParams) { + function validateEntry(transition, matchPoint, handlerParams) { + + var handlerInfos = transition.handlerInfos, + index = transition.resolveIndex; if (index === handlerInfos.length) { // No more contexts to resolve. @@ -30227,6 +31070,8 @@ define("router", return proceed(); } + transition.trigger(true, 'willResolveModel', transition, handler); + return RSVP.resolve().then(handleAbort) .then(beforeModel) .then(handleAbort) @@ -30247,7 +31092,7 @@ define("router", } function handleError(reason) { - if (reason instanceof Router.TransitionAborted) { + if (reason instanceof Router.TransitionAborted || transition.isAborted) { // if the transition was aborted and *no additional* error was thrown, // reject with the Router.TransitionAborted instance return RSVP.reject(reason); @@ -30260,7 +31105,7 @@ define("router", // An error was thrown / promise rejected, so fire an // `error` event from this handler info up to root. - trigger(router, handlerInfos.slice(0, index + 1), true, ['error', reason, transition]); + transition.trigger(true, 'error', reason, transition, handlerInfo.handler); // Propagate the original error. return RSVP.reject(reason); @@ -30270,13 +31115,20 @@ define("router", log(router, seq, handlerName + ": calling beforeModel hook"); - var p = handler.beforeModel && handler.beforeModel(transition); + var args; + + if (handlerInfo.queryParams) { + args = [handlerInfo.queryParams, transition]; + } else { + args = [transition]; + } + + var p = handler.beforeModel && handler.beforeModel.apply(handler, args); return (p instanceof Transition) ? null : p; } function model() { log(router, seq, handlerName + ": resolving model"); - var p = getModel(handlerInfo, transition, handlerParams[handlerName], index >= matchPoint); return (p instanceof Transition) ? null : p; } @@ -30291,7 +31143,15 @@ define("router", transition.resolvedModels[handlerInfo.name] = context; - var p = handler.afterModel && handler.afterModel(context, transition); + var args; + + if (handlerInfo.queryParams) { + args = [context, handlerInfo.queryParams, transition]; + } else { + args = [context, transition]; + } + + var p = handler.afterModel && handler.afterModel.apply(handler, args); return (p instanceof Transition) ? null : p; } @@ -30299,7 +31159,8 @@ define("router", log(router, seq, handlerName + ": validation succeeded, proceeding"); handlerInfo.context = transition.resolvedModels[handlerInfo.name]; - return validateEntry(transition, handlerInfos, index + 1, matchPoint, handlerParams); + transition.resolveIndex++; + return validateEntry(transition, matchPoint, handlerParams); } } @@ -30322,9 +31183,8 @@ define("router", or use one of the models provided to `transitionTo`. */ function getModel(handlerInfo, transition, handlerParams, needsUpdate) { - var handler = handlerInfo.handler, - handlerName = handlerInfo.name; + handlerName = handlerInfo.name, args; if (!needsUpdate && handler.hasOwnProperty('context')) { return handler.context; @@ -30335,7 +31195,13 @@ define("router", return typeof providedModel === 'function' ? providedModel() : providedModel; } - return handler.model && handler.model(handlerParams || {}, transition); + if (handlerInfo.queryParams) { + args = [handlerParams || {}, handlerInfo.queryParams, transition]; + } else { + args = [handlerParams || {}, transition, handlerInfo.queryParams]; + } + + return handler.model && handler.model.apply(handler, args); } /** @@ -30364,14 +31230,16 @@ define("router", @param {Array[Object]} args arguments passed to transitionTo, replaceWith, or handleURL */ - function doTransition(router, args) { + function doTransition(router, args, isIntermediate) { // Normalize blank transitions to root URL transitions. var name = args[0] || '/'; - if (name.charAt(0) === '/') { - return createURLTransition(router, name); + if(args.length === 1 && args[0].hasOwnProperty('queryParams')) { + return createQueryParamTransition(router, args[0], isIntermediate); + } else if (name.charAt(0) === '/') { + return createURLTransition(router, name, isIntermediate); } else { - return createNamedTransition(router, args); + return createNamedTransition(router, slice.call(args), isIntermediate); } } @@ -30411,9 +31279,6 @@ define("router", } return object; } - - - return Router; }); })(); @@ -30448,34 +31313,25 @@ DSL.prototype = { if (callback) { var dsl = new DSL(name); + route(dsl, 'loading'); + route(dsl, 'error', { path: "/_unused_dummy_error_path_route_" + name + "/:error" }); callback.call(dsl); - this.push(options.path, name, dsl.generate()); + this.push(options.path, name, dsl.generate(), options.queryParams); } else { - this.push(options.path, name); + this.push(options.path, name, null, options.queryParams); } + }, - push: function(url, name, callback) { + push: function(url, name, callback, queryParams) { var parts = name.split('.'); if (url === "" || url === "/" || parts[parts.length-1] === "index") { this.explicitIndex = true; } - this.matches.push([url, name, callback]); + this.matches.push([url, name, callback, queryParams]); }, route: function(name, options) { - Ember.assert("You must use `this.resource` to nest", typeof options !== 'function'); - - options = options || {}; - - if (typeof options.path !== 'string') { - options.path = "/" + name; - } - - if (this.parent && this.parent !== 'application') { - name = this.parent + "." + name; - } - - this.push(options.path, name); + route(this, name, options); }, generate: function() { @@ -30494,6 +31350,22 @@ DSL.prototype = { } }; +function route(dsl, name, options) { + Ember.assert("You must use `this.resource` to nest", typeof options !== 'function'); + + options = options || {}; + + if (typeof options.path !== 'string') { + options.path = "/" + name; + } + + if (dsl.parent && dsl.parent !== 'application') { + name = dsl.parent + "." + name; + } + + dsl.push(options.path, name, null, options.queryParams); +} + DSL.map = function(callback) { var dsl = new DSL(); callback.call(dsl); @@ -30579,7 +31451,7 @@ Ember.generateController = function(container, controllerName, context) { @submodule ember-routing */ -var Router = requireModule("router"); +var Router = requireModule("router")['default']; var get = Ember.get, set = Ember.set; var defineProperty = Ember.defineProperty; @@ -30592,7 +31464,7 @@ var DefaultView = Ember._MetamorphView; @namespace Ember @extends Ember.Object */ -Ember.Router = Ember.Object.extend({ +Ember.Router = Ember.Object.extend(Ember.Evented, { location: 'hash', init: function() { @@ -30626,19 +31498,18 @@ Ember.Router = Ember.Object.extend({ }, didTransition: function(infos) { - var appController = this.container.lookup('controller:application'), - path = Ember.Router._routePath(infos); + updatePaths(this); - if (!('currentPath' in appController)) { defineProperty(appController, 'currentPath'); } - set(appController, 'currentPath', path); - - if (!('currentRouteName' in appController)) { defineProperty(appController, 'currentRouteName'); } - set(appController, 'currentRouteName', infos[infos.length - 1].name); + this._cancelLoadingEvent(); this.notifyPropertyChange('url'); + // Put this in the runloop so url will be accurate. Seems + // less surprising than didTransition being out of sync. + Ember.run.once(this, this.trigger, 'didTransition'); + if (get(this, 'namespace').LOG_TRANSITIONS) { - Ember.Logger.log("Transitioned into '" + path + "'"); + Ember.Logger.log("Transitioned into '" + Ember.Router._routePath(infos) + "'"); } }, @@ -30650,6 +31521,17 @@ Ember.Router = Ember.Object.extend({ return this._doTransition('transitionTo', arguments); }, + intermediateTransitionTo: function() { + this.router.intermediateTransitionTo.apply(this.router, arguments); + + updatePaths(this); + + var infos = this.router.currentHandlerInfos; + if (get(this, 'namespace').LOG_TRANSITIONS) { + Ember.Logger.log("Intermediate-transitioned into '" + Ember.Router._routePath(infos) + "'"); + } + }, + replaceWith: function() { return this._doTransition('replaceWith', arguments); }, @@ -30684,6 +31566,13 @@ Ember.Router = Ember.Object.extend({ this.router.reset(); }, + willDestroy: function(){ + var location = get(this, 'location'); + location.destroy(); + + this._super.apply(this, arguments); + }, + _lookupActiveView: function(templateName) { var active = this._activeViews[templateName]; return active && active[0]; @@ -30733,8 +31622,6 @@ Ember.Router = Ember.Object.extend({ seen[name] = true; if (!handler) { - if (name === 'loading') { return {}; } - container.register(routeName, DefaultRoute.extend()); handler = container.lookup(routeName); @@ -30743,16 +31630,6 @@ Ember.Router = Ember.Object.extend({ } } - if (name === 'application') { - // Inject default `error` handler. - // Note: `events` is deprecated, but we'll let the - // deprecation warnings be handled at event-handling time rather - // than duplicating that logic here. - var actions = handler._actions || handler.events; - if (!actions) { actions = handler._actions = {}; } - actions.error = actions.error || Ember.Router._defaultErrorHandler; - } - handler.routeName = name; return handler; }; @@ -30793,12 +31670,12 @@ Ember.Router = Ember.Object.extend({ args = [].slice.call(args); args[0] = args[0] || '/'; - var passedName = args[0], name, self = this; + var passedName = args[0], name, self = this, + isQueryParamsOnly = false; - if (passedName.charAt(0) === '/') { + if (!isQueryParamsOnly && passedName.charAt(0) === '/') { name = passedName; - } else { - + } else if (!isQueryParamsOnly) { if (!this.router.hasRoute(passedName)) { name = args[0] = passedName + '.index'; } else { @@ -30810,14 +31687,7 @@ Ember.Router = Ember.Object.extend({ var transitionPromise = this.router[method].apply(this.router, args); - // Don't schedule loading state entry if user has already aborted the transition. - if (this.router.activeTransition) { - this._scheduleLoadingStateEntry(); - } - - transitionPromise.then(function(route) { - self._transitionCompleted(route); - }, function(error) { + transitionPromise.then(null, function(error) { if (error.name === "UnrecognizedURLError") { Ember.assert("The URL '" + error.message + "' did not match any routes in your application"); } @@ -30829,49 +31699,152 @@ Ember.Router = Ember.Object.extend({ return transitionPromise; }, - _scheduleLoadingStateEntry: function() { - if (this._loadingStateActive) { return; } - this._shouldEnterLoadingState = true; - Ember.run.scheduleOnce('routerTransitions', this, this._enterLoadingState); + _scheduleLoadingEvent: function(transition, originRoute) { + this._cancelLoadingEvent(); + this._loadingStateTimer = Ember.run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute); }, - _enterLoadingState: function() { - if (this._loadingStateActive || !this._shouldEnterLoadingState) { return; } - - var loadingRoute = this.router.getHandler('loading'); - if (loadingRoute) { - if (loadingRoute.enter) { loadingRoute.enter(); } - if (loadingRoute.setup) { loadingRoute.setup(); } - this._loadingStateActive = true; + _fireLoadingEvent: function(transition, originRoute) { + if (!this.router.activeTransition) { + // Don't fire an event if we've since moved on from + // the transition that put us in a loading state. + return; } + + transition.trigger(true, 'loading', transition, originRoute); }, - _exitLoadingState: function () { - this._shouldEnterLoadingState = false; - if (!this._loadingStateActive) { return; } - - var loadingRoute = this.router.getHandler('loading'); - if (loadingRoute && loadingRoute.exit) { loadingRoute.exit(); } - this._loadingStateActive = false; - }, - - _transitionCompleted: function(route) { - this.notifyPropertyChange('url'); - this._exitLoadingState(); + _cancelLoadingEvent: function () { + if (this._loadingStateTimer) { + Ember.run.cancel(this._loadingStateTimer); + } + this._loadingStateTimer = null; } }); +/** + @private + + Helper function for iterating root-ward, starting + from (but not including) the provided `originRoute`. + + Returns true if the last callback fired requested + to bubble upward. + */ +function forEachRouteAbove(originRoute, transition, callback) { + var handlerInfos = transition.handlerInfos, + originRouteFound = false; + + for (var i = handlerInfos.length - 1; i >= 0; --i) { + var handlerInfo = handlerInfos[i], + route = handlerInfo.handler; + + if (!originRouteFound) { + if (originRoute === route) { + originRouteFound = true; + } + continue; + } + + if (callback(route, handlerInfos[i + 1].handler) !== true) { + return false; + } + } + return true; +} + +// These get invoked when an action bubbles above ApplicationRoute +// and are not meant to be overridable. +var defaultActionHandlers = { + + willResolveModel: function(transition, originRoute) { + originRoute.router._scheduleLoadingEvent(transition, originRoute); + }, + + error: function(error, transition, originRoute) { + // Attempt to find an appropriate error substate to enter. + var router = originRoute.router; + + var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { + var childErrorRouteName = findChildRouteName(route, childRoute, 'error'); + if (childErrorRouteName) { + router.intermediateTransitionTo(childErrorRouteName, error); + return; + } + return true; + }); + + if (tryTopLevel) { + // Check for top-level error state to enter. + if (routeHasBeenDefined(originRoute.router, 'application_error')) { + router.intermediateTransitionTo('application_error', error); + return; + } + } else { + // Don't fire an assertion if we found an error substate. + return; + } + + Ember.Logger.assert(false, 'Error while loading route: ' + Ember.inspect(error)); + }, + + loading: function(transition, originRoute) { + // Attempt to find an appropriate loading substate to enter. + var router = originRoute.router; + + var tryTopLevel = forEachRouteAbove(originRoute, transition, function(route, childRoute) { + var childLoadingRouteName = findChildRouteName(route, childRoute, 'loading'); + + if (childLoadingRouteName) { + router.intermediateTransitionTo(childLoadingRouteName); + return; + } + + // Don't bubble above pivot route. + if (transition.pivotHandler !== route) { + return true; + } + }); + + if (tryTopLevel) { + // Check for top-level loading state to enter. + if (routeHasBeenDefined(originRoute.router, 'application_loading')) { + router.intermediateTransitionTo('application_loading'); + return; + } + } + } +}; + +function findChildRouteName(parentRoute, originatingChildRoute, name) { + var router = parentRoute.router, + childName, + targetChildRouteName = originatingChildRoute.routeName.split('.').pop(), + namespace = parentRoute.routeName === 'application' ? '' : parentRoute.routeName + '.'; + + childName = namespace + name; + if (routeHasBeenDefined(router, childName)) { + return childName; + } +} + +function routeHasBeenDefined(router, name) { + var container = router.container; + return router.hasRoute(name) && + (container.has('template:' + name) || container.has('route:' + name)); +} + function triggerEvent(handlerInfos, ignoreFailure, args) { var name = args.shift(); if (!handlerInfos) { if (ignoreFailure) { return; } - throw new Ember.Error("Could not trigger event '" + name + "'. There are no active handlers"); + throw new Ember.Error("Can't trigger action '" + name + "' because your app hasn't finished transitioning into its first route. To trigger an action on destination routes during a transition, you can call `.send()` on the `Transition` object passed to the `model/beforeModel/afterModel` hooks."); } var eventWasHandled = false; - for (var i=handlerInfos.length-1; i>=0; i--) { + for (var i = handlerInfos.length - 1; i >= 0; i--) { var handlerInfo = handlerInfos[i], handler = handlerInfo.handler; @@ -30881,21 +31854,46 @@ function triggerEvent(handlerInfos, ignoreFailure, args) { } else { return; } - } else if (handler.events && handler.events[name]) { - Ember.deprecate('Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object (' + name + ' on ' + handler + ')', false); - if (handler.events[name].apply(handler, args) === true) { - eventWasHandled = true; - } else { - return; - } } } + if (defaultActionHandlers[name]) { + defaultActionHandlers[name].apply(null, args); + return; + } + if (!eventWasHandled && !ignoreFailure) { - throw new Ember.Error("Nothing handled the event '" + name + "'."); + throw new Ember.Error("Nothing handled the action '" + name + "'."); } } +function updatePaths(router) { + var appController = router.container.lookup('controller:application'); + + if (!appController) { + // appController might not exist when top-level loading/error + // substates have been entered since ApplicationRoute hasn't + // actually been entered at that point. + return; + } + + var infos = router.router.currentHandlerInfos, + path = Ember.Router._routePath(infos); + + if (!('currentPath' in appController)) { + defineProperty(appController, 'currentPath'); + } + + set(appController, 'currentPath', path); + + if (!('currentRouteName' in appController)) { + defineProperty(appController, 'currentRouteName'); + } + + set(appController, 'currentRouteName', infos[infos.length - 1].name); +} + + Ember.Router.reopenClass({ router: null, map: function(callback) { @@ -30925,13 +31923,6 @@ Ember.Router.reopenClass({ return router; }, - _defaultErrorHandler: function(error, transition) { - Ember.Logger.error('Error while loading route:', error); - - // Using setTimeout allows us to escape from the Promise's try/catch block - setTimeout(function() { throw error; }); - }, - _routePath: function(handlerInfos) { var path = []; @@ -30946,6 +31937,9 @@ Ember.Router.reopenClass({ } }); +Router.Transition.prototype.send = Router.Transition.prototype.trigger; + + })(); @@ -30964,6 +31958,7 @@ var get = Ember.get, set = Ember.set, a_forEach = Ember.EnumerableUtils.forEach, a_replace = Ember.EnumerableUtils.replace; + /** The `Ember.Route` class is used to define individual routes. Refer to the [routing guide](http://emberjs.com/guides/routing/) for documentation. @@ -30973,6 +31968,7 @@ var get = Ember.get, set = Ember.set, @extends Ember.Object */ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { + /** @private @@ -31261,6 +32257,24 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { }); ``` + Transition to a nested route + + ```javascript + App.Router.map(function() { + this.resource('articles', { path: '/articles' }, function() { + this.route('new'); + }); + }); + + App.IndexRoute = Ember.Route.extend({ + actions: { + transitionToNewArticle: function() { + this.transitionTo('articles.new'); + } + } + }); + ``` + Multiple Models Example ```javascript @@ -31292,6 +32306,26 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { return router.transitionTo.apply(router, arguments); }, + /** + Perform a synchronous transition into another route with out attempting + to resolve promises, update the URL, or abort any currently active + asynchronous transitions (i.e. regular transitions caused by + `transitionTo` or URL changes). + + This method is handy for performing intermediate transitions on the + way to a final destination route, and is called internally by the + default implementations of the `error` and `loading` handlers. + + @method intermediateTransitionTo + @param {String} name the name of the route + @param {...Object} models the model(s) to be used while transitioning + to the route. + */ + intermediateTransitionTo: function() { + var router = this.router; + router.intermediateTransitionTo.apply(router, arguments); + }, + /** Transition into another route while replacing the current URL, if possible. This will replace the current history entry instead of adding a new one. @@ -31322,7 +32356,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { */ replaceWith: function() { var router = this.router; - return this.router.replaceWith.apply(this.router, arguments); + return router.replaceWith.apply(router, arguments); }, /** @@ -31370,7 +32404,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method setup */ - setup: function(context) { + setup: function(context, queryParams) { var controllerName = this.controllerName || this.routeName, controller = this.controllerFor(controllerName, true); if (!controller) { @@ -31381,18 +32415,20 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { // referenced in action handlers this.controller = controller; + var args = [controller, context]; + if (this.setupControllers) { Ember.deprecate("Ember.Route.setupControllers is deprecated. Please use Ember.Route.setupController(controller, model) instead."); this.setupControllers(controller, context); } else { - this.setupController(controller, context); + this.setupController.apply(this, args); } if (this.renderTemplates) { Ember.deprecate("Ember.Route.renderTemplates is deprecated. Please use Ember.Route.renderTemplate(controller, model) instead."); this.renderTemplates(context); } else { - this.renderTemplate(controller, context); + this.renderTemplate.apply(this, args); } }, @@ -31483,6 +32519,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method beforeModel @param {Transition} transition + @param {Object} queryParams the active query params for this route @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not @@ -31516,12 +32553,13 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @param {Object} resolvedModel the value returned from `model`, or its resolved value if it was a promise @param {Transition} transition + @param {Object} queryParams the active query params for this handler @return {Promise} if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not utilized in any way. */ - afterModel: function(resolvedModel, transition) { + afterModel: function(resolvedModel, transition, queryParams) { this.redirect(resolvedModel, transition); }, @@ -31581,6 +32619,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { @method model @param {Object} params the parameters extracted from the URL @param {Transition} transition + @param {Object} queryParams the query params for this route @return {Object|Promise} the model for this route. If a promise is returned, the transition will pause until the promise resolves, and the resolved value of the promise @@ -31728,6 +32767,7 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { instance would be used. Example + ```js App.PostRoute = Ember.Route.extend({ setupController: function(controller, model) { @@ -31952,9 +32992,18 @@ Ember.Route = Ember.Object.extend(Ember.ActionHandler, { } options = options || {}; - name = name ? name.replace(/\//g, '.') : this.routeName; + + var templateName; + + if (name) { + name = name.replace(/\//g, '.'); + templateName = name; + } else { + name = this.routeName; + templateName = this.templateName || name; + } + var viewName = options.view || this.viewName || name; - var templateName = this.templateName || name; var container = this.container, view = container.lookup('view:' + viewName), @@ -32397,7 +33446,13 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { templateContext = helperParameters.context, paths = getResolvedPaths(helperParameters), length = paths.length, - path, i; + path, i, normalizedPath; + + var linkTextPath = helperParameters.options.linkTextPath; + if (linkTextPath) { + normalizedPath = Ember.Handlebars.normalizePath(templateContext, linkTextPath, helperParameters.options.data); + this.registerObserver(normalizedPath.root, normalizedPath.path, this, this.rerender); + } for(i=0; i < length; i++) { path = paths[i]; @@ -32406,8 +33461,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { continue; } - var normalizedPath = - Ember.Handlebars.normalizePath(templateContext, path, helperParameters.options.data); + normalizedPath = Ember.Handlebars.normalizePath(templateContext, path, helperParameters.options.data); this.registerObserver(normalizedPath.root, normalizedPath.path, this, this._paramsChanged); } }, @@ -32416,13 +33470,25 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @private This method is invoked by observers installed during `init` that fire - whenever the helpers + whenever the params change @method _paramsChanged */ _paramsChanged: function() { this.notifyPropertyChange('resolvedParams'); }, + + /** + @private + + This method is invoked by observers installed during `init` that fire + whenever the query params change + */ + _queryParamsChanged: function (object, path) { + this.notifyPropertyChange('queryParams'); + }, + + /** @private @@ -32545,6 +33611,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { types = options.types, data = options.data; + // Original implementation if query params not enabled return resolveParams(parameters.context, parameters.params, { types: types, data: data }); }).property(), @@ -32558,7 +33625,6 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { @return {Array} An array with the route name and any dynamic segments */ routeArgs: Ember.computed(function() { - var resolvedParams = get(this, 'resolvedParams').slice(0), router = get(this, 'router'), namedRoute = resolvedParams[0]; @@ -32579,8 +33645,37 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { } return resolvedParams; + }).property('resolvedParams', 'queryParams', 'router.url'), + + + _potentialQueryParams: Ember.computed(function () { + var namedRoute = get(this, 'resolvedParams')[0]; + if (!namedRoute) { return null; } + var router = get(this, 'router'); + + namedRoute = fullRouteName(router, namedRoute); + + return router.router.queryParamsForHandler(namedRoute); }).property('resolvedParams'), + queryParams: Ember.computed(function () { + var self = this, + queryParams = null, + allowedQueryParams = get(this, '_potentialQueryParams'); + + if (!allowedQueryParams) { return null; } + allowedQueryParams.forEach(function (param) { + var value = get(self, param); + if (typeof value !== 'undefined') { + queryParams = queryParams || {}; + queryParams[param] = value; + } + }); + + + return queryParams; + }).property('_potentialQueryParams.[]'), + /** Sets the element's `href` attribute to the url for the `LinkView`'s targeted route. @@ -32865,6 +33960,22 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { hash.disabledBinding = hash.disabledWhen; + if (!options.fn) { + var linkTitle = params.shift(); + var linkType = options.types.shift(); + var context = this; + if (linkType === 'ID') { + options.linkTextPath = linkTitle; + options.fn = function() { + return Ember.Handlebars.get(context, linkTitle, options); + }; + } else { + options.fn = function() { + return linkTitle; + }; + } + } + hash.parameters = { context: this, options: options, @@ -33025,15 +34136,15 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { }); ``` - ```handelbars + ```handlebars Hello, {{who}}. ``` ```handelbars - +

My great app

- {{render navigaton}} + {{render navigation}} ``` ```html @@ -33043,7 +34154,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ``` - Optionally you may provide a second argument: a property path + Optionally you may provide a second argument: a property path that will be bound to the `model` property of the controller. If a `model` property path is specified, then a new instance of the @@ -33174,9 +34285,15 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { var keys = ["alt", "shift", "meta", "ctrl"]; - var isAllowedClick = function(event, allowedKeys) { + var POINTER_EVENT_TYPE_REGEX = /^click|mouse|touch/; + + var isAllowedEvent = function(event, allowedKeys) { if (typeof allowedKeys === "undefined") { - return isSimpleClick(event); + if (POINTER_EVENT_TYPE_REGEX.test(event.type)) { + return isSimpleClick(event); + } else { + allowedKeys = []; + } } if (allowedKeys.indexOf("any") >= 0) { @@ -33200,7 +34317,7 @@ Ember.onLoad('Ember.Handlebars', function(Handlebars) { ActionHelper.registeredActions[actionId] = { eventName: options.eventName, handler: function(event) { - if (!isAllowedClick(event, allowedKeys)) { return true; } + if (!isAllowedEvent(event, allowedKeys)) { return true; } event.preventDefault(); @@ -34207,10 +35324,11 @@ Ember.HistoryLocation = Ember.Object.extend({ */ getURL: function() { var rootURL = get(this, 'rootURL'), - url = get(this, 'location').pathname; + location = get(this, 'location'), + path = location.pathname; rootURL = rootURL.replace(/\/$/, ''); - url = url.replace(rootURL, ''); + var url = path.replace(rootURL, ''); return url; }, @@ -34740,6 +35858,10 @@ Ember.DefaultResolver = Ember.Object.extend({ return this.resolveOther(parsedName); }, + resolveHelper: function(parsedName) { + return this.resolveOther(parsedName) || Ember.Handlebars.helpers[parsedName.fullNameWithoutType]; + }, + /** Lookup the model on the Application namespace @@ -35034,8 +36156,7 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin if (!this.$) { this.$ = Ember.$; } this.__container__ = this.buildContainer(); - this.Router = this.Router || this.defaultRouter(); - if (this.Router) { this.Router.namespace = this; } + this.Router = this.defaultRouter(); this._super(); @@ -35094,13 +36215,17 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin @method defaultRouter @return {Ember.Router} the default router */ + defaultRouter: function() { - // Create a default App.Router if one was not supplied to make - // it possible to do App.Router.map(...) without explicitly - // creating a router first. - if (this.router === undefined) { - return Ember.Router.extend(); + if (this.Router === false) { return; } + var container = this.__container__; + + if (this.Router) { + container.unregister('router:main'); + container.register('router:main', this.Router); } + + return container.lookupFactory('router:main'); }, /** @@ -35257,7 +36382,9 @@ var Application = Ember.Application = Ember.Namespace.extend(Ember.DeferredMixin // At this point, the App.Router must already be assigned if (this.Router) { - this.register('router:main', this.Router); + var container = this.__container__; + container.unregister('router:main'); + container.register('router:main', this.Router); } this.runInitializers(); @@ -35535,6 +36662,9 @@ Ember.Application.reopenClass({ container.optionsForType('component', { singleton: false }); container.optionsForType('view', { singleton: false }); container.optionsForType('template', { instantiate: false }); + + container.optionsForType('helper', { instantiate: false }); + container.register('application:main', namespace, { instantiate: false }); container.register('controller:basic', Ember.Controller, { instantiate: false }); @@ -35543,6 +36673,7 @@ Ember.Application.reopenClass({ container.register('route:basic', Ember.Route, { instantiate: false }); container.register('event_dispatcher:main', Ember.EventDispatcher); + container.register('router:main', Ember.Router); container.injection('router:main', 'namespace', 'application:main'); container.injection('controller', 'target', 'router:main'); @@ -36236,7 +37367,7 @@ Ember.Test = { For example: ```javascript Ember.Test.registerHelper('boot', function(app) { - Ember.run(app, app.deferReadiness); + Ember.run(app, app.advanceReadiness); }); ``` @@ -36249,21 +37380,63 @@ Ember.Test = { boot(); ``` - Whenever you register a helper that performs async operations, make sure - you `return wait();` at the end of the helper. - - If an async helper also needs to return a value, pass it to the `wait` - helper as a first argument: - `return wait(val);` - @public @method registerHelper @param {String} name The name of the helper method to add. @param {Function} helperMethod + @param options {Object} */ registerHelper: function(name, helperMethod) { - helpers[name] = helperMethod; + helpers[name] = { + method: helperMethod, + meta: { wait: false } + }; }, + + /** + `registerAsyncHelper` is used to register an async test helper that will be injected + when `App.injectTestHelpers` is called. + + The helper method will always be called with the current Application as + the first parameter. + + For example: + ```javascript + Ember.Test.registerAsyncHelper('boot', function(app) { + Ember.run(app, app.advanceReadiness); + }); + ``` + + The advantage of an async helper is that it will not run + until the last async helper has completed. All async helpers + after it will wait for it complete before running. + + + For example: + ```javascript + Ember.Test.registerAsyncHelper('deletePost', function(app, postId) { + click('.delete-' + postId); + }); + + // ... in your test + visit('/post/2'); + deletePost(2); + visit('/post/3'); + deletePost(3); + ``` + + @public + @method registerAsyncHelper + @param {String} name The name of the helper method to add. + @param {Function} helperMethod + */ + registerAsyncHelper: function(name, helperMethod) { + helpers[name] = { + method: helperMethod, + meta: { wait: true } + }; + }, + /** Remove a previously added helper method. @@ -36279,9 +37452,10 @@ Ember.Test = { unregisterHelper: function(name) { delete helpers[name]; if (originalMethods[name]) { - window[name] = originalMethods[name]; + this.helperContainer[name] = originalMethods[name]; } delete originalMethods[name]; + delete Ember.Test.Promise.prototype[name]; }, /** @@ -36323,30 +37497,7 @@ Ember.Test = { @param {Function} resolver The function used to resolve the promise. */ promise: function(resolver) { - var promise = new Ember.RSVP.Promise(resolver); - var thenable = { - chained: false - }; - thenable.then = function(onSuccess, onFailure) { - var thenPromise, nextPromise; - thenable.chained = true; - thenPromise = promise.then(onSuccess, onFailure); - // this is to ensure all downstream fulfillment - // handlers are wrapped in the error handling - nextPromise = Ember.Test.promise(function(resolve) { - resolve(thenPromise); - }); - thenPromise.then(null, function(reason) { - // ensure this is the last promise in the chain - // if not, ignore and the exception will propagate - // this prevents the same error from being fired multiple times - if (!nextPromise.chained) { - Ember.Test.adapter.exception(reason); - } - }); - return nextPromise; - }; - return thenable; + return new Ember.Test.Promise(resolver); }, /** @@ -36367,19 +37518,130 @@ Ember.Test = { @type {Class} The adapter to be used. @default Ember.Test.QUnitAdapter */ - adapter: null + adapter: null, + + /** + Replacement for `Ember.RSVP.resolve` + The only difference is this uses + and instance of `Ember.Test.Promise` + + @public + @method resolve + @param {Mixed} The value to resolve + */ + resolve: function(val) { + return Ember.Test.promise(function(resolve) { + return resolve(val); + }); + }, + + /** + @public + + This allows ember-testing to play nicely with other asynchronous + events, such as an application that is waiting for a CSS3 + transition or an IndexDB transaction. + + For example: + ```javascript + Ember.Test.registerWaiter(function() { + return myPendingTransactions() == 0; + }); + ``` + The `context` argument allows you to optionally specify the `this` + with which your callback will be invoked. + + For example: + ```javascript + Ember.Test.registerWaiter(MyDB, MyDB.hasPendingTransactions); + ``` + @public + @method registerWaiter + @param {Object} context (optional) + @param {Function} callback + */ + registerWaiter: function(context, callback) { + if (arguments.length === 1) { + callback = context; + context = null; + } + if (!this.waiters) { + this.waiters = Ember.A(); + } + this.waiters.push([context, callback]); + }, + /** + `unregisterWaiter` is used to unregister a callback that was + registered with `registerWaiter`. + + @public + @method unregisterWaiter + @param {Object} context (optional) + @param {Function} callback + */ + unregisterWaiter: function(context, callback) { + var pair; + if (!this.waiters) { return; } + if (arguments.length === 1) { + callback = context; + context = null; + } + pair = [context, callback]; + this.waiters = Ember.A(this.waiters.filter(function(elt) { + return Ember.compare(elt, pair)!==0; + })); + } }; -function curry(app, fn) { +function helper(app, name) { + var fn = helpers[name].method, + meta = helpers[name].meta; + return function() { - var args = slice.call(arguments); + var args = slice.call(arguments), + lastPromise = Ember.Test.lastPromise; + args.unshift(app); - return fn.apply(app, args); + + // some helpers are not async and + // need to return a value immediately. + // example: `find` + if (!meta.wait) { + return fn.apply(app, args); + } + + if (!lastPromise) { + // It's the first async helper in current context + lastPromise = fn.apply(app, args); + } else { + // wait for last helper's promise to resolve + // and then execute + run(function() { + lastPromise = Ember.Test.resolve(lastPromise).then(function() { + return fn.apply(app, args); + }); + }); + } + + return lastPromise; }; } +function run(fn) { + if (!Ember.run.currentRunLoop) { + Ember.run(fn); + } else { + fn(); + } +} + Ember.Application.reopen({ /** + This property contains the testing helpers for the current application. These + are created once you call `injectTestHelpers` on your `Ember.Application` + instance. The included helpers are also available on the `window` object by + default, but can be used from this object on the individual application also. + @property testHelpers @type {Object} @default {} @@ -36416,9 +37678,21 @@ Ember.Application.reopen({ }, /** - This injects the test helpers into the window's scope. If a function of the - same name has already been defined it will be cached (so that it can be reset - if the helper is removed with `unregisterHelper` or `removeTestHelpers`). + This will be used as the container to inject the test helpers into. By + default the helpers are injected into `window`. + + @property helperContainer + @type {Object} The object to be used for test helpers. + @default window + */ + helperContainer: window, + + /** + This injects the test helpers into the `helperContainer` object. If an object is provided + it will be used as the helperContainer. If `helperContainer` is not set it will default + to `window`. If a function of the same name has already been defined it will be cached + (so that it can be reset if the helper is removed with `unregisterHelper` or + `removeTestHelpers`). Any callbacks registered with `onInjectHelpers` will be called once the helpers have been injected. @@ -36430,16 +37704,21 @@ Ember.Application.reopen({ @method injectTestHelpers */ - injectTestHelpers: function() { + injectTestHelpers: function(helperContainer) { + if (helperContainer) { this.helperContainer = helperContainer; } + this.testHelpers = {}; for (var name in helpers) { - originalMethods[name] = window[name]; - this.testHelpers[name] = window[name] = curry(this, helpers[name]); + originalMethods[name] = this.helperContainer[name]; + this.testHelpers[name] = this.helperContainer[name] = helper(this, name); + protoWrap(Ember.Test.Promise.prototype, name, helper(this, name), helpers[name].meta.wait); } for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) { injectHelpersCallbacks[i](this); } + + Ember.RSVP.configure('onerror', onerror); }, /** @@ -36456,13 +37735,84 @@ Ember.Application.reopen({ */ removeTestHelpers: function() { for (var name in helpers) { - window[name] = originalMethods[name]; + this.helperContainer[name] = originalMethods[name]; delete this.testHelpers[name]; delete originalMethods[name]; } + Ember.RSVP.configure('onerror', null); } + }); +// This method is no longer needed +// But still here for backwards compatibility +// of helper chaining +function protoWrap(proto, name, callback, isAsync) { + proto[name] = function() { + var args = arguments; + if (isAsync) { + return callback.apply(this, args); + } else { + return this.then(function() { + return callback.apply(this, args); + }); + } + }; +} + +Ember.Test.Promise = function() { + Ember.RSVP.Promise.apply(this, arguments); + Ember.Test.lastPromise = this; +}; + +Ember.Test.Promise.prototype = Ember.create(Ember.RSVP.Promise.prototype); +Ember.Test.Promise.prototype.constructor = Ember.Test.Promise; + +// Patch `then` to isolate async methods +// specifically `Ember.Test.lastPromise` +var originalThen = Ember.RSVP.Promise.prototype.then; +Ember.Test.Promise.prototype.then = function(onSuccess, onFailure) { + return originalThen.call(this, function(val) { + return isolate(onSuccess, val); + }, onFailure); +}; + +// This method isolates nested async methods +// so that they don't conflict with other last promises. +// +// 1. Set `Ember.Test.lastPromise` to null +// 2. Invoke method +// 3. Return the last promise created during method +// 4. Restore `Ember.Test.lastPromise` to original value +function isolate(fn, val) { + var value, lastPromise; + + // Reset lastPromise for nested helpers + Ember.Test.lastPromise = null; + + value = fn.call(null, val); + + lastPromise = Ember.Test.lastPromise; + + // If the method returned a promise + // return that promise. If not, + // return the last async helper's promise + if ((value && (value instanceof Ember.Test.Promise)) || !lastPromise) { + return value; + } else { + run(function() { + lastPromise = Ember.Test.resolve(lastPromise).then(function() { + return value; + }); + }); + return lastPromise; + } +} + +function onerror(error) { + Ember.Test.adapter.exception(error); +} + })(); @@ -36488,7 +37838,7 @@ function testCheckboxClick(handler) { .css({ position: 'absolute', left: '-1000px', top: '-1000px' }) .appendTo('body') .on('click', handler) - .click() + .trigger('click') .remove(); } @@ -36617,6 +37967,7 @@ Test.QUnitAdapter = Test.Adapter.extend({ var get = Ember.get, Test = Ember.Test, helper = Test.registerHelper, + asyncHelper = Test.registerAsyncHelper, countAsync = 0; Test.pendingAjaxRequests = 0; @@ -36707,89 +38058,44 @@ function find(app, selector, context) { return $el; } -function wait(app, value) { - var promise; +function andThen(app, callback) { + return wait(app, callback(app)); +} - promise = Test.promise(function(resolve) { +function wait(app, value) { + return Test.promise(function(resolve) { + // If this is the first async promise, kick off the async test if (++countAsync === 1) { Test.adapter.asyncStart(); } + + // Every 10ms, poll for the async thing to have finished var watcher = setInterval(function() { + // 1. If the router is loading, keep polling var routerIsLoading = app.__container__.lookup('router:main').router.isLoading; if (routerIsLoading) { return; } + + // 2. If there are pending Ajax requests, keep polling if (Test.pendingAjaxRequests) { return; } + + // 3. If there are scheduled timers or we are inside of a run loop, keep polling if (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop) { return; } + // Stop polling clearInterval(watcher); + // If this is the last async promise, end the async test if (--countAsync === 0) { Test.adapter.asyncEnd(); } + // Synchronously resolve the promise Ember.run(null, resolve, value); }, 10); }); - return buildChainObject(app, promise); } -/* - Builds an object that contains all helper methods. This object will be - returned by helpers and then-promises. - - This allows us to chain helpers: - - ```javascript - visit('posts/new') - .click('.add-btn') - .fillIn('.title', 'Post') - .click('.submit') - .then(function() { - equal('.post-title', 'Post'); - }) - .visit('comments') - .then(function() { - equal(find('.comments'),length, 0); - }); - ``` - - @method buildChainObject - @param {Ember.Application} app - @param {Ember.RSVP.Promise} promise - @return {Object} A new object with properties for each - of app's helpers to be used for continued - method chaining (using promises). -*/ -function buildChainObject(app, promise) { - var helperName, obj = {}; - for(helperName in app.testHelpers) { - obj[helperName] = chain(app, promise, app.testHelpers[helperName]); - } - obj.then = function(fn) { - var thenPromise = promise.then(fn); - return buildChainObject(app, thenPromise); - }; - return obj; -} - -/* - Used in conjunction with buildChainObject to setup a - continued chain of method calls (with promises) - - @method chain - @param {Ember.Application} app - @param {Ember.RSVP.Promise} promise - @param {Function} fn -*/ -function chain(app, promise, fn) { - return function() { - var args = arguments, chainedPromise; - chainedPromise = promise.then(function() { - return fn.apply(null, args); - }); - return buildChainObject(app, chainedPromise); - }; -} /** * Loads a route, sets up any controllers, and renders any templates associated @@ -36808,7 +38114,7 @@ function chain(app, promise, fn) { * @param {String} url the name of the route * @return {RSVP.Promise} */ -helper('visit', visit); +asyncHelper('visit', visit); /** * Clicks an element and triggers any actions triggered by the element's `click` @@ -36826,7 +38132,7 @@ helper('visit', visit); * @param {String} selector jQuery selector for finding element on the DOM * @return {RSVP.Promise} */ -helper('click', click); +asyncHelper('click', click); /** * Simulates a key event, e.g. `keypress`, `keydown`, `keyup` with the desired keyCode @@ -36845,7 +38151,7 @@ helper('click', click); * @param {Number} the keyCode of the simulated key event * @return {RSVP.Promise} */ -helper('keyEvent', keyEvent); +asyncHelper('keyEvent', keyEvent); /** * Fills in an input element with some text. @@ -36864,7 +38170,7 @@ helper('keyEvent', keyEvent); * @param {String} text text to place inside the input element * @return {RSVP.Promise} */ -helper('fillIn', fillIn); +asyncHelper('fillIn', fillIn); /** * Finds an element in the context of the app's container element. A simple alias @@ -36910,13 +38216,13 @@ helper('findWithAssert', findWithAssert); Example: ``` - Ember.Test.registerHelper('loginUser', function(app, username, password) { + Ember.Test.registerAsyncHelper('loginUser', function(app, username, password) { visit('secured/path/here') .fillIn('#username', username) .fillIn('#password', username) .click('.submit') - return wait(app); + return wait(); }); @method wait @@ -36924,7 +38230,8 @@ helper('findWithAssert', findWithAssert); @return {RSVP.Promise} ``` */ -helper('wait', wait); +asyncHelper('wait', wait); +asyncHelper('andThen', andThen); })(); diff --git a/vendor/assets/javascripts/ember-renderspeed.js b/vendor/assets/javascripts/ember-renderspeed.js index c7089c007be..48eb67f426c 100644 --- a/vendor/assets/javascripts/ember-renderspeed.js +++ b/vendor/assets/javascripts/ember-renderspeed.js @@ -77,6 +77,10 @@ if ((typeof console !== 'undefined') && console.groupCollapsed && !window.QUnit) after: function(name, timestamp, payload, profileNode) { + if (payload.exception) { + throw payload.exception; + } + var parent = profileNode.parent; profileNode.time = (timestamp - profileNode.start); this.depth = profileNode.parent; diff --git a/vendor/assets/javascripts/handlebars.js b/vendor/assets/javascripts/handlebars.js index c70f09d1de6..ba717929a8a 100644 --- a/vendor/assets/javascripts/handlebars.js +++ b/vendor/assets/javascripts/handlebars.js @@ -1,4 +1,6 @@ -/* +/*! + + handlebars v1.1.2 Copyright (C) 2011 by Yehuda Katz @@ -20,2259 +22,2574 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +@license */ - -// lib/handlebars/browser-prefix.js -var Handlebars = {}; - -(function(Handlebars, undefined) { -; -// lib/handlebars/base.js - -Handlebars.VERSION = "1.0.0"; -Handlebars.COMPILER_REVISION = 4; - -Handlebars.REVISION_CHANGES = { - 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it - 2: '== 1.0.0-rc.3', - 3: '== 1.0.0-rc.4', - 4: '>= 1.0.0' -}; - -Handlebars.helpers = {}; -Handlebars.partials = {}; - -var toString = Object.prototype.toString, - functionType = '[object Function]', - objectType = '[object Object]'; - -Handlebars.registerHelper = function(name, fn, inverse) { - if (toString.call(name) === objectType) { - if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } - Handlebars.Utils.extend(this.helpers, name); - } else { - if (inverse) { fn.not = inverse; } - this.helpers[name] = fn; - } -}; - -Handlebars.registerPartial = function(name, str) { - if (toString.call(name) === objectType) { - Handlebars.Utils.extend(this.partials, name); - } else { - this.partials[name] = str; - } -}; - -Handlebars.registerHelper('helperMissing', function(arg) { - if(arguments.length === 2) { - return undefined; - } else { - throw new Error("Missing helper: '" + arg + "'"); - } -}); - -Handlebars.registerHelper('blockHelperMissing', function(context, options) { - var inverse = options.inverse || function() {}, fn = options.fn; - - var type = toString.call(context); - - if(type === functionType) { context = context.call(this); } - - if(context === true) { - return fn(this); - } else if(context === false || context == null) { - return inverse(this); - } else if(type === "[object Array]") { - if(context.length > 0) { - return Handlebars.helpers.each(context, options); - } else { - return inverse(this); - } - } else { - return fn(context); - } -}); - -Handlebars.K = function() {}; - -Handlebars.createFrame = Object.create || function(object) { - Handlebars.K.prototype = object; - var obj = new Handlebars.K(); - Handlebars.K.prototype = null; - return obj; -}; - -Handlebars.logger = { - DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, - - methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, - - // can be overridden in the host environment - log: function(level, obj) { - if (Handlebars.logger.level <= level) { - var method = Handlebars.logger.methodMap[level]; - if (typeof console !== 'undefined' && console[method]) { - console[method].call(console, obj); - } - } - } -}; - -Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; - -Handlebars.registerHelper('each', function(context, options) { - var fn = options.fn, inverse = options.inverse; - var i = 0, ret = "", data; - - var type = toString.call(context); - if(type === functionType) { context = context.call(this); } - - if (options.data) { - data = Handlebars.createFrame(options.data); +var Handlebars = (function() { +// handlebars/safe-string.js +var __module4__ = (function() { + "use strict"; + var __exports__; + // Build out our basic SafeString type + function SafeString(string) { + this.string = string; } - if(context && typeof context === 'object') { - if(context instanceof Array){ - for(var j = context.length; i 2) { - expected.push("'" + this.terminals_[p] + "'"); - } - if (this.lexer.showPosition) { - errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; - } else { - errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); - } - this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; - if (ranges) { - yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; - } - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - if (typeof r !== "undefined") { - return r; - } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); - } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; - } - } - return true; -} -}; -/* Jison generated lexer */ -var lexer = (function(){ -var lexer = ({EOF:1, -parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, -setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - if (this.options.ranges) this.yylloc.range = [0,0]; - this.offset = 0; - return this; - }, -input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; - } - if (this.options.ranges) this.yylloc.range[1]++; - - this._input = this._input.slice(1); - return ch; - }, -unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); - - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length-len-1); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length-1); - this.matched = this.matched.substr(0, this.matched.length-1); - - if (lines.length-1) this.yylineno -= lines.length-1; - var r = this.yylloc.range; - - this.yylloc = {first_line: this.yylloc.first_line, - last_line: this.yylineno+1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: - this.yylloc.first_column - len - }; - - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; - } - return this; - }, -more:function () { - this._more = true; - return this; - }, -less:function (n) { - this.unput(this.match.slice(n)); - }, -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - tempMatch, - index, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (!this.options.flex) break; - } - } - if (match) { - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); - if (this.done && this._input) this.done = false; - if (token) return token; - else return; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, -lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, -begin:function begin(condition) { - this.conditionStack.push(condition); - }, -popState:function popState() { - return this.conditionStack.pop(); - }, -_currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, -topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, -pushState:function begin(condition) { - this.begin(condition); - }}); -lexer.options = {}; -lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - -var YYSTATE=YY_START -switch($avoiding_name_collisions) { -case 0: yy_.yytext = "\\"; return 14; -break; -case 1: - if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); - if(yy_.yytext) return 14; - -break; -case 2: return 14; -break; -case 3: - if(yy_.yytext.slice(-1) !== "\\") this.popState(); - if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); - return 14; - -break; -case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; -break; -case 5: return 25; -break; -case 6: return 16; -break; -case 7: return 20; -break; -case 8: return 19; -break; -case 9: return 19; -break; -case 10: return 23; -break; -case 11: return 22; -break; -case 12: this.popState(); this.begin('com'); -break; -case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; -break; -case 14: return 22; -break; -case 15: return 37; -break; -case 16: return 36; -break; -case 17: return 36; -break; -case 18: return 40; -break; -case 19: /*ignore whitespace*/ -break; -case 20: this.popState(); return 24; -break; -case 21: this.popState(); return 18; -break; -case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; -break; -case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; -break; -case 24: return 38; -break; -case 25: return 33; -break; -case 26: return 33; -break; -case 27: return 32; -break; -case 28: return 36; -break; -case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; -break; -case 30: return 'INVALID'; -break; -case 31: return 5; -break; -} -}; -lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; -lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; -return lexer;})() -parser.lexer = lexer; -function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; -return new Parser; -})();; -// lib/handlebars/compiler/base.js - -Handlebars.Parser = handlebars; - -Handlebars.parse = function(input) { - - // Just return if an already-compile AST was passed in. - if(input.constructor === Handlebars.AST.ProgramNode) { return input; } - - Handlebars.Parser.yy = Handlebars.AST; - return Handlebars.Parser.parse(input); -}; -; -// lib/handlebars/compiler/ast.js -Handlebars.AST = {}; - -Handlebars.AST.ProgramNode = function(statements, inverse) { - this.type = "program"; - this.statements = statements; - if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } -}; - -Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { - this.type = "mustache"; - this.escaped = !unescaped; - this.hash = hash; - - var id = this.id = rawParams[0]; - var params = this.params = rawParams.slice(1); - - // a mustache is an eligible helper if: - // * its id is simple (a single part, not `this` or `..`) - var eligibleHelper = this.eligibleHelper = id.isSimple; - - // a mustache is definitely a helper if: - // * it is an eligible helper, and - // * it has at least one parameter or hash segment - this.isHelper = eligibleHelper && (params.length || hash); - - // if a mustache is an eligible helper but not a definite - // helper, it is ambiguous, and will be resolved in a later - // pass or at runtime. -}; - -Handlebars.AST.PartialNode = function(partialName, context) { - this.type = "partial"; - this.partialName = partialName; - this.context = context; -}; - -Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { - var verifyMatch = function(open, close) { - if(open.original !== close.original) { - throw new Handlebars.Exception(open.original + " doesn't match " + close.original); - } + SafeString.prototype.toString = function() { + return "" + this.string; }; - verifyMatch(mustache.id, close); - this.type = "block"; - this.mustache = mustache; - this.program = program; - this.inverse = inverse; + __exports__ = SafeString; + return __exports__; +})(); - if (this.inverse && !this.program) { - this.isInverse = true; - } -}; +// handlebars/utils.js +var __module3__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + var SafeString = __dependency1__; -Handlebars.AST.ContentNode = function(string) { - this.type = "content"; - this.string = string; -}; + var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; -Handlebars.AST.HashNode = function(pairs) { - this.type = "hash"; - this.pairs = pairs; -}; + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; -Handlebars.AST.IdNode = function(parts) { - this.type = "ID"; - - var original = "", - dig = [], - depth = 0; - - for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); } - else if (part === "..") { depth++; } - else { this.isScoped = true; } - } - else { dig.push(part); } + function escapeChar(chr) { + return escape[chr] || "&"; } - this.original = original; - this.parts = dig; - this.string = dig.join('.'); - this.depth = depth; - - // an ID is simple if it only has one part, and that part is not - // `..` or `this`. - this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; - - this.stringModeValue = this.string; -}; - -Handlebars.AST.PartialNameNode = function(name) { - this.type = "PARTIAL_NAME"; - this.name = name.original; -}; - -Handlebars.AST.DataNode = function(id) { - this.type = "DATA"; - this.id = id; -}; - -Handlebars.AST.StringNode = function(string) { - this.type = "STRING"; - this.original = - this.string = - this.stringModeValue = string; -}; - -Handlebars.AST.IntegerNode = function(integer) { - this.type = "INTEGER"; - this.original = - this.integer = integer; - this.stringModeValue = Number(integer); -}; - -Handlebars.AST.BooleanNode = function(bool) { - this.type = "BOOLEAN"; - this.bool = bool; - this.stringModeValue = bool === "true"; -}; - -Handlebars.AST.CommentNode = function(comment) { - this.type = "comment"; - this.comment = comment; -}; -; -// lib/handlebars/utils.js - -var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - -Handlebars.Exception = function(message) { - var tmp = Error.prototype.constructor.apply(this, arguments); - - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; - } -}; -Handlebars.Exception.prototype = new Error(); - -// Build out our basic SafeString type -Handlebars.SafeString = function(string) { - this.string = string; -}; -Handlebars.SafeString.prototype.toString = function() { - return this.string.toString(); -}; - -var escape = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" -}; - -var badChars = /[&<>"'`]/g; -var possible = /[&<>"'`]/; - -var escapeChar = function(chr) { - return escape[chr] || "&"; -}; - -Handlebars.Utils = { - extend: function(obj, value) { + function extend(obj, value) { for(var key in value) { if(value.hasOwnProperty(key)) { obj[key] = value[key]; } } - }, + } - escapeExpression: function(string) { + __exports__.extend = extend;var toString = Object.prototype.toString; + __exports__.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + var isFunction = function(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + var isFunction; + __exports__.isFunction = isFunction; + var isArray = Array.isArray || function(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; + }; + __exports__.isArray = isArray; + + function escapeExpression(string) { // don't escape SafeStrings, since they're already safe - if (string instanceof Handlebars.SafeString) { + if (string instanceof SafeString) { return string.toString(); - } else if (string == null || string === false) { + } else if (!string && string !== 0) { return ""; } // Force a string conversion as this will be done by the append regardless and // the regex test will do this transparently behind the scenes, causing issues if // an object's to string has escaped characters in it. - string = string.toString(); + string = "" + string; if(!possible.test(string)) { return string; } return string.replace(badChars, escapeChar); - }, + } - isEmpty: function(value) { + __exports__.escapeExpression = escapeExpression;function isEmpty(value) { if (!value && value !== 0) { return true; - } else if(toString.call(value) === "[object Array]" && value.length === 0) { + } else if (isArray(value) && value.length === 0) { return true; } else { return false; } } -}; -; -// lib/handlebars/compiler/compiler.js -/*jshint eqnull:true*/ -var Compiler = Handlebars.Compiler = function() {}; -var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; + __exports__.isEmpty = isEmpty; + return __exports__; +})(__module4__); -// the foundHelper register will disambiguate helper lookup from finding a -// function in a context. This is necessary for mustache compatibility, which -// requires that context functions in blocks are evaluated by blockHelperMissing, -// and then proceed as if the resulting value was provided to blockHelperMissing. +// handlebars/exception.js +var __module5__ = (function() { + "use strict"; + var __exports__; -Compiler.prototype = { - compiler: Compiler, + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; - disassemble: function() { - var opcodes = this.opcodes, opcode, out = [], params, param; + function Exception(/* message */) { + var tmp = Error.prototype.constructor.apply(this, arguments); - for (var i=0, l=opcodes.length; i= 1.0.0' + }; + __exports__.REVISION_CHANGES = REVISION_CHANGES; + var isArray = Utils.isArray, + isFunction = Utils.isFunction, + toString = Utils.toString, + objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials) { + this.helpers = helpers || {}; + this.partials = partials || {}; + + registerDefaultHelpers(this); + } + + __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: logger, + log: log, + + registerHelper: function(name, fn, inverse) { + if (toString.call(name) === objectType) { + if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } + Utils.extend(this.helpers, name); } else { - params = []; - for (var j=0; j 0) { + return instance.helpers.each(context, options); + } else { + return inverse(this); } - this.opcode('getContext', val.depth || 0); - this.opcode('pushStringParam', val.stringModeValue, val.type); } else { - this.accept(val); + return fn(context); + } + }); + + instance.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + if (isFunction(context)) { context = context.call(this); } + + if (options.data) { + data = createFrame(options.data); } - this.opcode('assignToHash', pair[0]); - } - this.opcode('popHash'); - }, - - partial: function(partial) { - var partialName = partial.partialName; - this.usePartial = true; - - if(partial.context) { - this.ID(partial.context); - } else { - this.opcode('push', 'depth0'); - } - - this.opcode('invokePartial', partialName.name); - this.opcode('append'); - }, - - content: function(content) { - this.opcode('appendContent', content.string); - }, - - mustache: function(mustache) { - var options = this.options; - var type = this.classifyMustache(mustache); - - if (type === "simple") { - this.simpleMustache(mustache); - } else if (type === "helper") { - this.helperMustache(mustache); - } else { - this.ambiguousMustache(mustache); - } - - if(mustache.escaped && !options.noEscape) { - this.opcode('appendEscaped'); - } else { - this.opcode('append'); - } - }, - - ambiguousMustache: function(mustache, program, inverse) { - var id = mustache.id, - name = id.parts[0], - isBlock = program != null || inverse != null; - - this.opcode('getContext', id.depth); - - this.opcode('pushProgram', program); - this.opcode('pushProgram', inverse); - - this.opcode('invokeAmbiguous', name, isBlock); - }, - - simpleMustache: function(mustache) { - var id = mustache.id; - - if (id.type === 'DATA') { - this.DATA(id); - } else if (id.parts.length) { - this.ID(id); - } else { - // Simplified ID for `this` - this.addDepth(id.depth); - this.opcode('getContext', id.depth); - this.opcode('pushContext'); - } - - this.opcode('resolvePossibleLambda'); - }, - - helperMustache: function(mustache, program, inverse) { - var params = this.setupFullMustacheParams(mustache, program, inverse), - name = mustache.id.parts[0]; - - if (this.options.knownHelpers[name]) { - this.opcode('invokeKnownHelper', params.length, name); - } else if (this.options.knownHelpersOnly) { - throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name); - } else { - this.opcode('invokeHelper', params.length, name); - } - }, - - ID: function(id) { - this.addDepth(id.depth); - this.opcode('getContext', id.depth); - - var name = id.parts[0]; - if (!name) { - this.opcode('pushContext'); - } else { - this.opcode('lookupOnContext', id.parts[0]); - } - - for(var i=1, l=id.parts.length; i 0) { - this.source[1] = this.source[1] + ", " + locals.join(", "); - } - - // Generate minimizer alias mappings - if (!this.isChild) { - for (var alias in this.context.aliases) { - if (this.context.aliases.hasOwnProperty(alias)) { - this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; - } - } - } - - if (this.source[1]) { - this.source[1] = "var " + this.source[1].substring(2) + ";"; - } - - // Merge children - if (!this.isChild) { - this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; - } - - if (!this.environment.isSimple) { - this.source.push("return buffer;"); - } - - var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; - - for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } - return this.topStackName(); - }, - topStackName: function() { - return "stack" + this.stackSlot; - }, - flushInline: function() { - var inlineStack = this.inlineStack; - if (inlineStack.length) { - this.inlineStack = []; - for (var i = 0, len = inlineStack.length; i < len; i++) { - var entry = inlineStack[i]; - if (entry instanceof Literal) { - this.compileStack.push(entry); - } else { - this.pushStack(entry); - } - } - } - }, - isInline: function() { - return this.inlineStack.length; - }, - - popStack: function(wrapped) { - var inline = this.isInline(), - item = (inline ? this.inlineStack : this.compileStack).pop(); - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - if (!inline) { - this.stackSlot--; - } - return item; - } - }, - - topStack: function(wrapped) { - var stack = (this.isInline() ? this.inlineStack : this.compileStack), - item = stack[stack.length - 1]; - - if (!wrapped && (item instanceof Literal)) { - return item.value; - } else { - return item; - } - }, - - quotedString: function(str) { - return '"' + str - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 - .replace(/\u2029/g, '\\u2029') + '"'; - }, - - setupHelper: function(paramSize, name, missingParams) { - var params = []; - this.setupParams(paramSize, params, missingParams); - var foundHelper = this.nameLookup('helpers', name, 'helper'); - - return { - params: params, - name: foundHelper, - callParams: ["depth0"].concat(params).join(", "), - helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") - }; - }, - - // the params and contexts arguments are passed in arrays - // to fill in - setupParams: function(paramSize, params, useRegister) { - var options = [], contexts = [], types = [], param, inverse, program; - - options.push("hash:" + this.popStack()); - - inverse = this.popStack(); - program = this.popStack(); - - // Avoid setting fn and inverse if neither are set. This allows - // helpers to do a check for `if (options.fn)` - if (program || inverse) { - if (!program) { - this.context.aliases.self = "this"; - program = "self.noop"; - } - - if (!inverse) { - this.context.aliases.self = "this"; - inverse = "self.noop"; - } - - options.push("inverse:" + inverse); - options.push("fn:" + program); - } - - for(var i=0; i 0) { throw new Exception("Invalid path: " + original); } + else if (part === "..") { depth++; } + else { this.isScoped = true; } + } + else { dig.push(part); } + } + + this.original = original; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + + this.stringModeValue = this.string; + } + + __exports__.IdNode = IdNode;function PartialNameNode(name) { + this.type = "PARTIAL_NAME"; + this.name = name.original; + } + + __exports__.PartialNameNode = PartialNameNode;function DataNode(id) { + this.type = "DATA"; + this.id = id; + } + + __exports__.DataNode = DataNode;function StringNode(string) { + this.type = "STRING"; + this.original = + this.string = + this.stringModeValue = string; + } + + __exports__.StringNode = StringNode;function IntegerNode(integer) { + this.type = "INTEGER"; + this.original = + this.integer = integer; + this.stringModeValue = Number(integer); + } + + __exports__.IntegerNode = IntegerNode;function BooleanNode(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; + } + + __exports__.BooleanNode = BooleanNode;function CommentNode(comment) { + this.type = "comment"; + this.comment = comment; + } + + __exports__.CommentNode = CommentNode; + return __exports__; +})(__module5__); + +// handlebars/compiler/parser.js +var __module9__ = (function() { + "use strict"; + var __exports__; + /* Jison generated parser */ + var handlebars = (function(){ + var parser = {trace: function trace() { }, + yy: {}, + symbols_: {"error":2,"root":3,"statements":4,"EOF":5,"program":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"inMustache_repetition0":28,"inMustache_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"hash":35,"hash_repetition_plus0":36,"hashSegment":37,"ID":38,"EQUALS":39,"DATA":40,"pathSegments":41,"SEP":42,"$accept":0,"$end":1}, + terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",38:"ID",39:"EQUALS",40:"DATA",42:"SEP"}, + productions_: [0,[3,2],[3,1],[6,2],[6,3],[6,2],[6,1],[6,1],[6,0],[4,1],[4,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[7,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[35,1],[37,3],[26,1],[26,1],[26,1],[30,2],[21,1],[41,3],[41,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[36,1],[36,2]], + performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: return new yy.ProgramNode($$[$0-1]); + break; + case 2: return new yy.ProgramNode([]); + break; + case 3:this.$ = new yy.ProgramNode([], $$[$0-1], $$[$0]); + break; + case 4:this.$ = new yy.ProgramNode($$[$0-2], $$[$0-1], $$[$0]); + break; + case 5:this.$ = new yy.ProgramNode($$[$0-1], $$[$0], []); + break; + case 6:this.$ = new yy.ProgramNode($$[$0]); + break; + case 7:this.$ = new yy.ProgramNode([]); + break; + case 8:this.$ = new yy.ProgramNode([]); + break; + case 9:this.$ = [$$[$0]]; + break; + case 10: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; + break; + case 11:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0]); + break; + case 12:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0]); + break; + case 13:this.$ = $$[$0]; + break; + case 14:this.$ = $$[$0]; + break; + case 15:this.$ = new yy.ContentNode($$[$0]); + break; + case 16:this.$ = new yy.CommentNode($$[$0]); + break; + case 17:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 18:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 19:this.$ = {path: $$[$0-1], strip: stripFlags($$[$0-2], $$[$0])}; + break; + case 20:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 21:this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2], stripFlags($$[$0-2], $$[$0])); + break; + case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1], stripFlags($$[$0-3], $$[$0])); + break; + case 23:this.$ = stripFlags($$[$0-1], $$[$0]); + break; + case 24:this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]]; + break; + case 25:this.$ = [[$$[$0]], null]; + break; + case 26:this.$ = $$[$0]; + break; + case 27:this.$ = new yy.StringNode($$[$0]); + break; + case 28:this.$ = new yy.IntegerNode($$[$0]); + break; + case 29:this.$ = new yy.BooleanNode($$[$0]); + break; + case 30:this.$ = $$[$0]; + break; + case 31:this.$ = new yy.HashNode($$[$0]); + break; + case 32:this.$ = [$$[$0-2], $$[$0]]; + break; + case 33:this.$ = new yy.PartialNameNode($$[$0]); + break; + case 34:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0])); + break; + case 35:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0])); + break; + case 36:this.$ = new yy.DataNode($$[$0]); + break; + case 37:this.$ = new yy.IdNode($$[$0]); + break; + case 38: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; + break; + case 39:this.$ = [{part: $$[$0]}]; + break; + case 42:this.$ = []; + break; + case 43:$$[$0-1].push($$[$0]); + break; + case 46:this.$ = [$$[$0]]; + break; + case 47:$$[$0-1].push($$[$0]); + break; + } + }, + table: [{3:1,4:2,5:[1,3],8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[3]},{5:[1,16],8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[2,2]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{4:20,6:18,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{4:20,6:22,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{17:23,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:29,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:30,21:24,30:25,38:[1,28],40:[1,27],41:26},{17:31,21:24,30:25,38:[1,28],40:[1,27],41:26},{21:33,26:32,32:[1,34],33:[1,35],38:[1,28],41:26},{1:[2,1]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{10:36,20:[1,37]},{4:38,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,7],22:[1,13],23:[1,14],25:[1,15]},{7:39,8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,6],22:[1,13],23:[1,14],25:[1,15]},{17:23,18:[1,40],21:24,30:25,38:[1,28],40:[1,27],41:26},{10:41,20:[1,37]},{18:[1,42]},{18:[2,42],24:[2,42],28:43,32:[2,42],33:[2,42],34:[2,42],38:[2,42],40:[2,42]},{18:[2,25],24:[2,25]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],38:[2,37],40:[2,37],42:[1,44]},{21:45,38:[1,28],41:26},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],40:[2,39],42:[2,39]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,40],21:50,27:49,38:[1,28],41:26},{18:[2,33],38:[2,33]},{18:[2,34],38:[2,34]},{18:[2,35],38:[2,35]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{21:51,38:[1,28],41:26},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,3],22:[1,13],23:[1,14],25:[1,15]},{4:52,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,5],22:[1,13],23:[1,14],25:[1,15]},{14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,44],21:56,24:[2,44],29:53,30:60,31:54,32:[1,57],33:[1,58],34:[1,59],35:55,36:61,37:62,38:[1,63],40:[1,27],41:26},{38:[1,64]},{18:[2,36],24:[2,36],32:[2,36],33:[2,36],34:[2,36],38:[2,36],40:[2,36]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,65]},{18:[2,41]},{18:[1,66]},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],25:[1,15]},{18:[2,24],24:[2,24]},{18:[2,43],24:[2,43],32:[2,43],33:[2,43],34:[2,43],38:[2,43],40:[2,43]},{18:[2,45],24:[2,45]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],38:[2,26],40:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],38:[2,27],40:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],38:[2,28],40:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],38:[2,29],40:[2,29]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],38:[2,30],40:[2,30]},{18:[2,31],24:[2,31],37:67,38:[1,68]},{18:[2,46],24:[2,46],38:[2,46]},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],38:[2,39],39:[1,69],40:[2,39],42:[2,39]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],38:[2,38],40:[2,38],42:[2,38]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{18:[2,47],24:[2,47],38:[2,47]},{39:[1,69]},{21:56,30:60,31:70,32:[1,57],33:[1,58],34:[1,59],38:[1,28],40:[1,27],41:26},{18:[2,32],24:[2,32],38:[2,32]}], + defaultActions: {3:[2,2],16:[2,1],50:[2,41]}, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") + this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) + if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + + + function stripFlags(open, close) { + return { + left: open[2] === '~', + right: close[0] === '~' || close[1] === '~' + }; + } + + /* Jison generated lexer */ + var lexer = (function(){ + var lexer = ({EOF:1, + parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, + input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more:function () { + this._more = true; + return this; + }, + less:function (n) { + this.unput(this.match.slice(n)); + }, + pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, + showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, + next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, + lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin:function begin(condition) { + this.conditionStack.push(condition); + }, + popState:function popState() { + return this.conditionStack.pop(); + }, + _currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, + topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, + pushState:function begin(condition) { + this.begin(condition); + }}); + lexer.options = {}; + lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end); + } + + + var YYSTATE=YY_START + switch($avoiding_name_collisions) { + case 0: + if(yy_.yytext.slice(-2) === "\\\\") { + strip(0,1); + this.begin("mu"); + } else if(yy_.yytext.slice(-1) === "\\") { + strip(0,1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if(yy_.yytext) return 14; + + break; + case 1:return 14; + break; + case 2: + if(yy_.yytext.slice(-1) !== "\\") this.popState(); + if(yy_.yytext.slice(-1) === "\\") strip(0,1); + return 14; + + break; + case 3:strip(0,4); this.popState(); return 15; + break; + case 4:return 25; + break; + case 5:return 16; + break; + case 6:return 20; + break; + case 7:return 19; + break; + case 8:return 19; + break; + case 9:return 23; + break; + case 10:return 22; + break; + case 11:this.popState(); this.begin('com'); + break; + case 12:strip(3,5); this.popState(); return 15; + break; + case 13:return 22; + break; + case 14:return 39; + break; + case 15:return 38; + break; + case 16:return 38; + break; + case 17:return 42; + break; + case 18:/*ignore whitespace*/ + break; + case 19:this.popState(); return 24; + break; + case 20:this.popState(); return 18; + break; + case 21:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32; + break; + case 22:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32; + break; + case 23:return 40; + break; + case 24:return 34; + break; + case 25:return 34; + break; + case 26:return 33; + break; + case 27:return 38; + break; + case 28:yy_.yytext = strip(1,2); return 38; + break; + case 29:return 'INVALID'; + break; + case 30:return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s])))/,/^(?:false(?=([~}\s])))/,/^(?:-?[0-9]+(?=([~}\s])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; + lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,30],"inclusive":true}}; + return lexer;})() + parser.lexer = lexer; + function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; + return new Parser; + })();__exports__ = handlebars; + return __exports__; +})(); + +// handlebars/compiler/base.js +var __module8__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var parser = __dependency1__; + var AST = __dependency2__; + + __exports__.parser = parser; + + function parse(input) { + // Just return if an already-compile AST was passed in. + if(input.constructor === AST.ProgramNode) { return input; } + + parser.yy = AST; + return parser.parse(input); + } + + __exports__.parse = parse; + return __exports__; +})(__module9__, __module7__); + +// handlebars/compiler/javascript-compiler.js +var __module11__ = (function(__dependency1__) { + "use strict"; + var __exports__; + var COMPILER_REVISION = __dependency1__.COMPILER_REVISION; + var REVISION_CHANGES = __dependency1__.REVISION_CHANGES; + var log = __dependency1__.log; + + function Literal(value) { + this.value = value; + } + + function JavaScriptCompiler() {} + + JavaScriptCompiler.prototype = { + // PUBLIC API: You can override these methods in a subclass to provide + // alternative compiled forms for name lookup and buffering semantics + nameLookup: function(parent, name /* , type*/) { + var wrap, + ret; + if (parent.indexOf('depth') === 0) { + wrap = true; + } + + if (/^[0-9]+$/.test(name)) { + ret = parent + "[" + name + "]"; + } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { + ret = parent + "." + name; + } + else { + ret = parent + "['" + name + "']"; + } + + if (wrap) { + return '(' + parent + ' && ' + ret + ')'; + } else { + return ret; + } + }, + + appendToBuffer: function(string) { + if (this.environment.isSimple) { + return "return " + string + ";"; + } else { + return { + appendToBuffer: true, + content: string, + toString: function() { return "buffer += " + string + ";"; } + }; + } + }, + + initializeBuffer: function() { + return this.quotedString(""); + }, + + namespace: "Handlebars", + // END PUBLIC API + + compile: function(environment, options, context, asObject) { + this.environment = environment; + this.options = options || {}; + + log('debug', this.environment.disassemble() + "\n\n"); + + this.name = this.environment.name; + this.isChild = !!context; + this.context = context || { + programs: [], + environments: [], + aliases: { } + }; + + this.preamble(); + + this.stackSlot = 0; + this.stackVars = []; + this.registers = { list: [] }; + this.compileStack = []; + this.inlineStack = []; + + this.compileChildren(environment, options); + + var opcodes = environment.opcodes, opcode; + + this.i = 0; + + for(var l=opcodes.length; this.i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + if (this.context.aliases.hasOwnProperty(alias)) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.pushSource("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + this.stackSlot--; + } + return item; + } + }, + + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = []; + this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + // the params and contexts arguments are passed in arrays + // to fill in + setupParams: function(paramSize, params, useRegister) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i this.changingFrom ? 'green' : 'red'; // logic } - }, 'content.value'), + }), - friendsDidChange: Ember.observer(function(obj, keyName) { + friendsDidChange: Ember.observer('friends.@each.name', function(obj, keyName) { // some logic // obj.get(keyName) returns friends array - }, 'friends.@each.name') + }) }); ``` @@ -7470,12 +7555,25 @@ Ember.immediateObserver = function() { @method beforeObserver @for Ember - @param {Function} func @param {String} propertyNames* + @param {Function} func @return func */ -Ember.beforeObserver = function(func) { - var paths = a_slice.call(arguments, 1); +Ember.beforeObserver = function() { + var func = a_slice.call(arguments, -1)[0]; + var paths = a_slice.call(arguments, 0, -1); + + if (typeof func !== "function") { + // revert to old, soft-deprecated argument ordering + + func = arguments[0]; + paths = a_slice.call(arguments, 1); + } + + if (typeof func !== "function") { + throw new Ember.Error("Ember.beforeObserver called without a function"); + } + func.__ember_observesBefore__ = paths; return func; }; @@ -9389,30 +9487,1997 @@ if (!Ember.keys || Ember.create.isSimulated) { }; } -// .......................................................... -// ERROR -// +})(); -var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +var STRING_DASHERIZE_REGEXP = (/[ _]/g); +var STRING_DASHERIZE_CACHE = {}; +var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g); +var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); +var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); +var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); /** - A subclass of the JavaScript Error object for use in Ember. + Defines the hash of localized strings for the current language. Used by + the `Ember.String.loc()` helper. To localize, add string values to this + hash. - @class Error - @namespace Ember - @extends Error - @constructor + @property STRINGS + @for Ember + @type Hash */ -Ember.Error = function() { - var tmp = Error.apply(this, arguments); +Ember.STRINGS = {}; - // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. - for (var idx = 0; idx < errorProps.length; idx++) { - this[errorProps[idx]] = tmp[errorProps[idx]]; +/** + Defines string helper methods including string formatting and localization. + Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be + added to the `String.prototype` as well. + + @class String + @namespace Ember + @static +*/ +Ember.String = { + + /** + Apply formatting options to the string. This will look for occurrences + of "%@" in your string and substitute them with the arguments you pass into + this method. If you want to control the specific order of replacement, + you can add a number after the key as well to indicate which argument + you want to insert. + + Ordered insertions are most useful when building loc strings where values + you need to insert may appear in different orders. + + ```javascript + "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" + "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" + ``` + + @method fmt + @param {String} str The string to format + @param {Array} formats An array of parameters to interpolate into string. + @return {String} formatted string + */ + fmt: function(str, formats) { + // first, replace any ORDERED replacements. + var idx = 0; // the current index for non-numerical replacements + return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { + argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; + s = formats[argIndex]; + return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); + }) ; + }, + + /** + Formats the passed string, but first looks up the string in the localized + strings hash. This is a convenient way to localize text. See + `Ember.String.fmt()` for more information on formatting. + + Note that it is traditional but not required to prefix localized string + keys with an underscore or other character so you can easily identify + localized strings. + + ```javascript + Ember.STRINGS = { + '_Hello World': 'Bonjour le monde', + '_Hello %@ %@': 'Bonjour %@ %@' + }; + + Ember.String.loc("_Hello World"); // 'Bonjour le monde'; + Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; + ``` + + @method loc + @param {String} str The string to format + @param {Array} formats Optional array of parameters to interpolate into string. + @return {String} formatted string + */ + loc: function(str, formats) { + str = Ember.STRINGS[str] || str; + return Ember.String.fmt(str, formats) ; + }, + + /** + Splits a string into separate units separated by spaces, eliminating any + empty strings in the process. This is a convenience method for split that + is mostly useful when applied to the `String.prototype`. + + ```javascript + Ember.String.w("alpha beta gamma").forEach(function(key) { + console.log(key); + }); + + // > alpha + // > beta + // > gamma + ``` + + @method w + @param {String} str The string to split + @return {String} split string + */ + w: function(str) { return str.split(/\s+/); }, + + /** + Converts a camelized string into all lower case separated by underscores. + + ```javascript + 'innerHTML'.decamelize(); // 'inner_html' + 'action_name'.decamelize(); // 'action_name' + 'css-class-name'.decamelize(); // 'css-class-name' + 'my favorite items'.decamelize(); // 'my favorite items' + ``` + + @method decamelize + @param {String} str The string to decamelize. + @return {String} the decamelized string. + */ + decamelize: function(str) { + return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); + }, + + /** + Replaces underscores, spaces, or camelCase with dashes. + + ```javascript + 'innerHTML'.dasherize(); // 'inner-html' + 'action_name'.dasherize(); // 'action-name' + 'css-class-name'.dasherize(); // 'css-class-name' + 'my favorite items'.dasherize(); // 'my-favorite-items' + ``` + + @method dasherize + @param {String} str The string to dasherize. + @return {String} the dasherized string. + */ + dasherize: function(str) { + var cache = STRING_DASHERIZE_CACHE, + hit = cache.hasOwnProperty(str), + ret; + + if (hit) { + return cache[str]; + } else { + ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); + cache[str] = ret; + } + + return ret; + }, + + /** + Returns the lowerCamelCase form of a string. + + ```javascript + 'innerHTML'.camelize(); // 'innerHTML' + 'action_name'.camelize(); // 'actionName' + 'css-class-name'.camelize(); // 'cssClassName' + 'my favorite items'.camelize(); // 'myFavoriteItems' + 'My Favorite Items'.camelize(); // 'myFavoriteItems' + ``` + + @method camelize + @param {String} str The string to camelize. + @return {String} the camelized string. + */ + camelize: function(str) { + return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { + return chr ? chr.toUpperCase() : ''; + }).replace(/^([A-Z])/, function(match, separator, chr) { + return match.toLowerCase(); + }); + }, + + /** + Returns the UpperCamelCase form of a string. + + ```javascript + 'innerHTML'.classify(); // 'InnerHTML' + 'action_name'.classify(); // 'ActionName' + 'css-class-name'.classify(); // 'CssClassName' + 'my favorite items'.classify(); // 'MyFavoriteItems' + ``` + + @method classify + @param {String} str the string to classify + @return {String} the classified string + */ + classify: function(str) { + var parts = str.split("."), + out = []; + + for (var i=0, l=parts.length; i= 0) { + var baseValue = this[keyName]; + + if (baseValue) { + if ('function' === typeof baseValue.concat) { + value = baseValue.concat(value); + } else { + value = Ember.makeArray(baseValue).concat(value); + } + } else { + value = Ember.makeArray(value); + } + } + + if (desc) { + desc.set(this, keyName, value); + } else { + if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) { + this.setUnknownProperty(keyName, value); + } else if (MANDATORY_SETTER) { + Ember.defineProperty(this, keyName, null, value); // setup mandatory setter + } else { + this[keyName] = value; + } + } + } + } + } + finishPartial(this, m); + this.init.apply(this, arguments); + m.proto = proto; + finishChains(this); + sendEvent(this, "init"); + }; + + Class.toString = Mixin.prototype.toString; + Class.willReopen = function() { + if (wasApplied) { + Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin); + } + + wasApplied = false; + }; + Class._initMixins = function(args) { initMixins = args; }; + Class._initProperties = function(args) { initProperties = args; }; + + Class.proto = function() { + var superclass = Class.superclass; + if (superclass) { superclass.proto(); } + + if (!wasApplied) { + wasApplied = true; + Class.PrototypeMixin.applyPartial(Class.prototype); + rewatch(Class.prototype); + } + + return this.prototype; + }; + + return Class; + +} + +/** + @class CoreObject + @namespace Ember +*/ +var CoreObject = makeCtor(); +CoreObject.toString = function() { return "Ember.CoreObject"; }; + +CoreObject.PrototypeMixin = Mixin.create({ + reopen: function() { + applyMixin(this, arguments, true); + return this; + }, + + /** + An overridable method called when objects are instantiated. By default, + does nothing unless it is overridden during class definition. + + Example: + + ```javascript + App.Person = Ember.Object.extend({ + init: function() { + alert('Name is ' + this.get('name')); + } + }); + + var steve = App.Person.create({ + name: "Steve" + }); + + // alerts 'Name is Steve'. + ``` + + NOTE: If you do override `init` for a framework class like `Ember.View` or + `Ember.ArrayController`, be sure to call `this._super()` in your + `init` declaration! If you don't, Ember may not have an opportunity to + do important setup work, and you'll see strange behavior in your + application. + + @method init + */ + init: function() {}, + + /** + Defines the properties that will be concatenated from the superclass + (instead of overridden). + + By default, when you extend an Ember class a property defined in + the subclass overrides a property with the same name that is defined + in the superclass. However, there are some cases where it is preferable + to build up a property's value by combining the superclass' property + value with the subclass' value. An example of this in use within Ember + is the `classNames` property of `Ember.View`. + + Here is some sample code showing the difference between a concatenated + property and a normal one: + + ```javascript + App.BarView = Ember.View.extend({ + someNonConcatenatedProperty: ['bar'], + classNames: ['bar'] + }); + + App.FooBarView = App.BarView.extend({ + someNonConcatenatedProperty: ['foo'], + classNames: ['foo'], + }); + + var fooBarView = App.FooBarView.create(); + fooBarView.get('someNonConcatenatedProperty'); // ['foo'] + fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo'] + ``` + + This behavior extends to object creation as well. Continuing the + above example: + + ```javascript + var view = App.FooBarView.create({ + someNonConcatenatedProperty: ['baz'], + classNames: ['baz'] + }) + view.get('someNonConcatenatedProperty'); // ['baz'] + view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] + ``` + Adding a single property that is not an array will just add it in the array: + + ```javascript + var view = App.FooBarView.create({ + classNames: 'baz' + }) + view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] + ``` + + Using the `concatenatedProperties` property, we can tell to Ember that mix + the content of the properties. + + In `Ember.View` the `classNameBindings` and `attributeBindings` properties + are also concatenated, in addition to `classNames`. + + This feature is available for you to use throughout the Ember object model, + although typical app developers are likely to use it infrequently. Since + it changes expectations about behavior of properties, you should properly + document its usage in each individual concatenated property (to not + mislead your users to think they can override the property in a subclass). + + @property concatenatedProperties + @type Array + @default null + */ + concatenatedProperties: null, + + /** + Destroyed object property flag. + + if this property is `true` the observers and bindings were already + removed by the effect of calling the `destroy()` method. + + @property isDestroyed + @default false + */ + isDestroyed: false, + + /** + Destruction scheduled flag. The `destroy()` method has been called. + + The object stays intact until the end of the run loop at which point + the `isDestroyed` flag is set. + + @property isDestroying + @default false + */ + isDestroying: false, + + /** + Destroys an object by setting the `isDestroyed` flag and removing its + metadata, which effectively destroys observers and bindings. + + If you try to set a property on a destroyed object, an exception will be + raised. + + Note that destruction is scheduled for the end of the run loop and does not + happen immediately. It will set an isDestroying flag immediately. + + @method destroy + @return {Ember.Object} receiver + */ + destroy: function() { + if (this.isDestroying) { return; } + this.isDestroying = true; + + schedule('actions', this, this.willDestroy); + schedule('destroy', this, this._scheduledDestroy); + return this; + }, + + /** + Override to implement teardown. + + @method willDestroy + */ + willDestroy: Ember.K, + + /** + @private + + Invoked by the run loop to actually destroy the object. This is + scheduled for execution by the `destroy` method. + + @method _scheduledDestroy + */ + _scheduledDestroy: function() { + if (this.isDestroyed) { return; } + destroy(this); + this.isDestroyed = true; + }, + + bind: function(to, from) { + if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); } + from.to(to).connect(this); + return from; + }, + + /** + Returns a string representation which attempts to provide more information + than Javascript's `toString` typically does, in a generic way for all Ember + objects. + + App.Person = Em.Object.extend() + person = App.Person.create() + person.toString() //=> "" + + If the object's class is not defined on an Ember namespace, it will + indicate it is a subclass of the registered superclass: + + Student = App.Person.extend() + student = Student.create() + student.toString() //=> "<(subclass of App.Person):ember1025>" + + If the method `toStringExtension` is defined, its return value will be + included in the output. + + App.Teacher = App.Person.extend({ + toStringExtension: function() { + return this.get('fullName'); + } + }); + teacher = App.Teacher.create() + teacher.toString(); //=> "" + + @method toString + @return {String} string representation + */ + toString: function toString() { + var hasToStringExtension = typeof this.toStringExtension === 'function', + extension = hasToStringExtension ? ":" + this.toStringExtension() : ''; + var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>'; + this.toString = makeToString(ret); + return ret; + } +}); + +CoreObject.PrototypeMixin.ownerConstructor = CoreObject; + +function makeToString(ret) { + return function() { return ret; }; +} + +if (Ember.config.overridePrototypeMixin) { + Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin); +} + +CoreObject.__super__ = null; + +var ClassMixin = Mixin.create({ + + ClassMixin: Ember.required(), + + PrototypeMixin: Ember.required(), + + isClass: true, + + isMethod: false, + + /** + Creates a new subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(thing); + } + }); + ``` + + This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`. + + You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class: + + ```javascript + App.PersonView = Ember.View.extend({ + tagName: 'li', + classNameBindings: ['isAdministrator'] + }); + ``` + + When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method: + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + var name = this.get('name'); + alert(name + ' says: ' + thing); + } + }); + + App.Soldier = App.Person.extend({ + say: function(thing) { + this._super(thing + ", sir!"); + }, + march: function(numberOfHours) { + alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.') + } + }); + + var yehuda = App.Soldier.create({ + name: "Yehuda Katz" + }); + + yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!" + ``` + + The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method. + + You can also pass `Ember.Mixin` classes to add additional properties to the subclass. + + ```javascript + App.Person = Ember.Object.extend({ + say: function(thing) { + alert(this.get('name') + ' says: ' + thing); + } + }); + + App.SingingMixin = Ember.Mixin.create({ + sing: function(thing){ + alert(this.get('name') + ' sings: la la la ' + thing); + } + }); + + App.BroadwayStar = App.Person.extend(App.SingingMixin, { + dance: function() { + alert(this.get('name') + ' dances: tap tap tap tap '); + } + }); + ``` + + The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. + + @method extend + @static + + @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes + @param {Object} [arguments]* Object containing values to use within the new class + */ + extend: function() { + var Class = makeCtor(), proto; + Class.ClassMixin = Mixin.create(this.ClassMixin); + Class.PrototypeMixin = Mixin.create(this.PrototypeMixin); + + Class.ClassMixin.ownerConstructor = Class; + Class.PrototypeMixin.ownerConstructor = Class; + + reopen.apply(Class.PrototypeMixin, arguments); + + Class.superclass = this; + Class.__super__ = this.prototype; + + proto = Class.prototype = o_create(this.prototype); + proto.constructor = Class; + generateGuid(proto); + meta(proto).proto = proto; // this will disable observers on prototype + + Class.ClassMixin.apply(Class); + return Class; + }, + + /** + Equivalent to doing `extend(arguments).create()`. + If possible use the normal `create` method instead. + + @method createWithMixins + @static + @param [arguments]* + */ + createWithMixins: function() { + var C = this; + if (arguments.length>0) { this._initMixins(arguments); } + return new C(); + }, + + /** + Creates an instance of a class. Accepts either no arguments, or an object + containing values to initialize the newly instantiated object with. + + ```javascript + App.Person = Ember.Object.extend({ + helloWorld: function() { + alert("Hi, my name is " + this.get('name')); + } + }); + + var tom = App.Person.create({ + name: 'Tom Dale' + }); + + tom.helloWorld(); // alerts "Hi, my name is Tom Dale". + ``` + + `create` will call the `init` function if defined during + `Ember.AnyObject.extend` + + If no arguments are passed to `create`, it will not set values to the new + instance during initialization: + + ```javascript + var noName = App.Person.create(); + noName.helloWorld(); // alerts undefined + ``` + + NOTE: For performance reasons, you cannot declare methods or computed + properties during `create`. You should instead declare methods and computed + properties when using `extend` or use the `createWithMixins` shorthand. + + @method create + @static + @param [arguments]* + */ + create: function() { + var C = this; + if (arguments.length>0) { this._initProperties(arguments); } + return new C(); + }, + + /** + + Augments a constructor's prototype with additional + properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + o = MyObject.create(); + o.get('name'); // 'an object' + + MyObject.reopen({ + say: function(msg){ + console.log(msg); + } + }) + + o2 = MyObject.create(); + o2.say("hello"); // logs "hello" + + o.say("goodbye"); // logs "goodbye" + ``` + + To add functions and properties to the constructor itself, + see `reopenClass` + + @method reopen + */ + reopen: function() { + this.willReopen(); + reopen.apply(this.PrototypeMixin, arguments); + return this; + }, + + /** + Augments a constructor's own properties and functions: + + ```javascript + MyObject = Ember.Object.extend({ + name: 'an object' + }); + + + MyObject.reopenClass({ + canBuild: false + }); + + MyObject.canBuild; // false + o = MyObject.create(); + ``` + + In other words, this creates static properties and functions for the class. These are only available on the class + and not on any instance of that class. + + ```javascript + App.Person = Ember.Object.extend({ + name : "", + sayHello : function(){ + alert("Hello. My name is " + this.get('name')); + } + }); + + App.Person.reopenClass({ + species : "Homo sapiens", + createPerson: function(newPersonsName){ + return App.Person.create({ + name:newPersonsName + }); + } + }); + + var tom = App.Person.create({ + name : "Tom Dale" + }); + var yehuda = App.Person.createPerson("Yehuda Katz"); + + tom.sayHello(); // "Hello. My name is Tom Dale" + yehuda.sayHello(); // "Hello. My name is Yehuda Katz" + alert(App.Person.species); // "Homo sapiens" + ``` + + Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` + variables. They are only valid on `App.Person`. + + To add functions and properties to instances of + a constructor by extending the constructor's prototype + see `reopen` + + @method reopenClass + */ + reopenClass: function() { + reopen.apply(this.ClassMixin, arguments); + applyMixin(this, arguments, false); + return this; + }, + + detect: function(obj) { + if ('function' !== typeof obj) { return false; } + while(obj) { + if (obj===this) { return true; } + obj = obj.superclass; + } + return false; + }, + + detectInstance: function(obj) { + return obj instanceof this; + }, + + /** + In some cases, you may want to annotate computed properties with additional + metadata about how they function or what values they operate on. For + example, computed property functions may close over variables that are then + no longer available for introspection. + + You can pass a hash of these values to a computed property like this: + + ```javascript + person: function() { + var personId = this.get('personId'); + return App.Person.create({ id: personId }); + }.property().meta({ type: App.Person }) + ``` + + Once you've done this, you can retrieve the values saved to the computed + property from your class like this: + + ```javascript + MyClass.metaForProperty('person'); + ``` + + This will return the original hash that was passed to `meta()`. + + @method metaForProperty + @param key {String} property name + */ + metaForProperty: function(key) { + var desc = meta(this.proto(), false).descs[key]; + + return desc._meta || {}; + }, + + /** + Iterate over each computed property for the class, passing its name + and any associated metadata (see `metaForProperty`) to the callback. + + @method eachComputedProperty + @param {Function} callback + @param {Object} binding + */ + eachComputedProperty: function(callback, binding) { + var proto = this.proto(), + descs = meta(proto).descs, + empty = {}, + property; + + for (var name in descs) { + property = descs[name]; + + if (property instanceof Ember.ComputedProperty) { + callback.call(binding || this, name, property._meta || empty); + } + } + } + +}); + +ClassMixin.ownerConstructor = CoreObject; + +if (Ember.config.overrideClassMixin) { + Ember.config.overrideClassMixin(ClassMixin); +} + +CoreObject.ClassMixin = ClassMixin; +ClassMixin.apply(CoreObject); + +Ember.CoreObject = CoreObject; + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +/** + `Ember.Object` is the main base class for all Ember objects. It is a subclass + of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details, + see the documentation for each of these. + + @class Object + @namespace Ember + @extends Ember.CoreObject + @uses Ember.Observable +*/ +Ember.Object = Ember.CoreObject.extend(Ember.Observable); +Ember.Object.toString = function() { return "Ember.Object"; }; + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf; + +/** + A Namespace is an object usually used to contain other objects or methods + such as an application or framework. Create a namespace anytime you want + to define one of these new containers. + + # Example Usage + + ```javascript + MyFramework = Ember.Namespace.create({ + VERSION: '1.0.0' + }); + ``` + + @class Namespace + @namespace Ember + @extends Ember.Object +*/ +var Namespace = Ember.Namespace = Ember.Object.extend({ + isNamespace: true, + + init: function() { + Ember.Namespace.NAMESPACES.push(this); + Ember.Namespace.PROCESSED = false; + }, + + toString: function() { + var name = get(this, 'name'); + if (name) { return name; } + + findNamespaces(); + return this[Ember.GUID_KEY+'_name']; + }, + + nameClasses: function() { + processNamespace([this.toString()], this, {}); + }, + + destroy: function() { + var namespaces = Ember.Namespace.NAMESPACES; + Ember.lookup[this.toString()] = undefined; + namespaces.splice(indexOf.call(namespaces, this), 1); + this._super(); + } +}); + +Namespace.reopenClass({ + NAMESPACES: [Ember], + NAMESPACES_BY_ID: {}, + PROCESSED: false, + processAll: processAllNamespaces, + byName: function(name) { + if (!Ember.BOOTED) { + processAllNamespaces(); + } + + return NAMESPACES_BY_ID[name]; + } +}); + +var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID; + +var hasOwnProp = ({}).hasOwnProperty, + guidFor = Ember.guidFor; + +function processNamespace(paths, root, seen) { + var idx = paths.length; + + NAMESPACES_BY_ID[paths.join('.')] = root; + + // Loop over all of the keys in the namespace, looking for classes + for(var key in root) { + if (!hasOwnProp.call(root, key)) { continue; } + var obj = root[key]; + + // If we are processing the `Ember` namespace, for example, the + // `paths` will start with `["Ember"]`. Every iteration through + // the loop will update the **second** element of this list with + // the key, so processing `Ember.View` will make the Array + // `['Ember', 'View']`. + paths[idx] = key; + + // If we have found an unprocessed class + if (obj && obj.toString === classToString) { + // Replace the class' `toString` with the dot-separated path + // and set its `NAME_KEY` + obj.toString = makeToString(paths.join('.')); + obj[NAME_KEY] = paths.join('.'); + + // Support nested namespaces + } else if (obj && obj.isNamespace) { + // Skip aliased namespaces + if (seen[guidFor(obj)]) { continue; } + seen[guidFor(obj)] = true; + + // Process the child namespace + processNamespace(paths, obj, seen); + } + } + + paths.length = idx; // cut out last item +} + +function findNamespaces() { + var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace; + + if (Namespace.PROCESSED) { return; } + + for (var prop in lookup) { + // These don't raise exceptions but can cause warnings + if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; } + + // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox. + // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage + if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; } + // Unfortunately, some versions of IE don't support window.hasOwnProperty + if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; } + + // At times we are not allowed to access certain properties for security reasons. + // There are also times where even if we can access them, we are not allowed to access their properties. + try { + obj = Ember.lookup[prop]; + isNamespace = obj && obj.isNamespace; + } catch (e) { + continue; + } + + if (isNamespace) { + + obj[NAME_KEY] = prop; + } + } +} + +var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name'; + +function superClassString(mixin) { + var superclass = mixin.superclass; + if (superclass) { + if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; } + else { return superClassString(superclass); } + } else { + return; + } +} + +function classToString() { + if (!Ember.BOOTED && !this[NAME_KEY]) { + processAllNamespaces(); + } + + var ret; + + if (this[NAME_KEY]) { + ret = this[NAME_KEY]; + } else if (this._toString) { + ret = this._toString; + } else { + var str = superClassString(this); + if (str) { + ret = "(subclass of " + str + ")"; + } else { + ret = "(unknown mixin)"; + } + this.toString = makeToString(ret); + } + + return ret; +} + +function processAllNamespaces() { + var unprocessedNamespaces = !Namespace.PROCESSED, + unprocessedMixins = Ember.anyUnprocessedMixins; + + if (unprocessedNamespaces) { + findNamespaces(); + Namespace.PROCESSED = true; + } + + if (unprocessedNamespaces || unprocessedMixins) { + var namespaces = Namespace.NAMESPACES, namespace; + for (var i=0, l=namespaces.length; i= 0; --sliceIndex) { - itemIndex = index + sliceIndex; + for (sliceIndex = normalizedRemoveCount - 1; sliceIndex >= 0; --sliceIndex) { + itemIndex = normalizedIndex + sliceIndex; + if (itemIndex >= length) { break; } + item = dependentArray.objectAt(itemIndex); forEach(itemPropertyKeys, removeObservers, this); @@ -11056,31 +13177,35 @@ DependentArraysObserver.prototype = { }, dependentArrayDidChange: function (dependentArray, index, removedCount, addedCount) { + if (this.suspended) { return; } + var addedItem = this.callbacks.addedItem, guid = guidFor(dependentArray), dependentKey = this.dependentKeysByGuid[guid], observerContexts = new Array(addedCount), itemPropertyKeys = this.cp._itemPropertyKeys[dependentKey], + length = get(dependentArray, 'length'), + normalizedIndex = normalizeIndex(index, length, addedCount), changeMeta, observerContext; - forEach(dependentArray.slice(index, index + addedCount), function (item, sliceIndex) { + forEach(dependentArray.slice(normalizedIndex, normalizedIndex + addedCount), function (item, sliceIndex) { if (itemPropertyKeys) { observerContext = observerContexts[sliceIndex] = - this.createPropertyObserverContext(dependentArray, index + sliceIndex, this.trackedArraysByGuid[dependentKey]); + this.createPropertyObserverContext(dependentArray, normalizedIndex + sliceIndex, this.trackedArraysByGuid[dependentKey]); forEach(itemPropertyKeys, function (propertyKey) { addBeforeObserver(item, propertyKey, this, observerContext.beforeObserver); addObserver(item, propertyKey, this, observerContext.observer); }, this); } - changeMeta = createChangeMeta(dependentArray, item, index + sliceIndex, this.instanceMeta.propertyName, this.cp); + changeMeta = createChangeMeta(dependentArray, item, normalizedIndex + sliceIndex, this.instanceMeta.propertyName, this.cp); this.setValue( addedItem.call( this.instanceMeta.context, this.getValue(), item, changeMeta, this.instanceMeta.sugarMeta)); }, this); - this.trackAdd(dependentKey, index, observerContexts); + this.trackAdd(dependentKey, normalizedIndex, observerContexts); }, itemPropertyWillChange: function (obj, keyName, array, observerContext) { @@ -11121,6 +13246,20 @@ DependentArraysObserver.prototype = { } }; +function normalizeIndex(index, length, newItemsOffset) { + if (index < 0) { + return Math.max(0, length + index); + } else if (index < length) { + return index; + } else /* index > length */ { + return Math.min(length - newItemsOffset, index); + } +} + +function normalizeRemoveCount(index, length, removedCount) { + return Math.min(removedCount, length - index); +} + function createChangeMeta(dependentArray, item, index, propertyName, property, previousValues) { var meta = { arrayChanged: dependentArray, @@ -11239,30 +13378,32 @@ function ReduceComputedProperty(options) { reset.call(this, cp, propertyName); - forEach(cp._dependentKeys, function (dependentKey) { - var dependentArray = get(this, dependentKey), - previousDependentArray = meta.dependentArrays[dependentKey]; + meta.dependentArraysObserver.suspendArrayObservers(function () { + forEach(cp._dependentKeys, function (dependentKey) { + var dependentArray = get(this, dependentKey), + previousDependentArray = meta.dependentArrays[dependentKey]; - if (dependentArray === previousDependentArray) { - // The array may be the same, but our item property keys may have - // changed, so we set them up again. We can't easily tell if they've - // changed: the array may be the same object, but with different - // contents. - if (cp._previousItemPropertyKeys[dependentKey]) { - delete cp._previousItemPropertyKeys[dependentKey]; - meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]); - } - } else { - meta.dependentArrays[dependentKey] = dependentArray; + if (dependentArray === previousDependentArray) { + // The array may be the same, but our item property keys may have + // changed, so we set them up again. We can't easily tell if they've + // changed: the array may be the same object, but with different + // contents. + if (cp._previousItemPropertyKeys[dependentKey]) { + delete cp._previousItemPropertyKeys[dependentKey]; + meta.dependentArraysObserver.setupPropertyObservers(dependentKey, cp._itemPropertyKeys[dependentKey]); + } + } else { + meta.dependentArrays[dependentKey] = dependentArray; - if (previousDependentArray) { - meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey); - } + if (previousDependentArray) { + meta.dependentArraysObserver.teardownObservers(previousDependentArray, dependentKey); + } - if (dependentArray) { - meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey); + if (dependentArray) { + meta.dependentArraysObserver.setupObservers(dependentArray, dependentKey); + } } - } + }, this); }, this); forEach(cp._dependentKeys, function(dependentKey) { @@ -11485,6 +13626,37 @@ ReduceComputedProperty.prototype.property = function () { }; ``` + Dependent keys may refer to `@this` to observe changes to the object itself, + which must be array-like, rather than a property of the object. This is + mostly useful for array proxies, to ensure objects are retrieved via + `objectAtContent`. This is how you could sort items by properties defined on an item controller. + + Example + + ```javascript + App.PeopleController = Ember.ArrayController.extend({ + itemController: 'person', + + sortedPeople: Ember.computed.sort('@this.@each.reversedName', function(personA, personB) { + // `reversedName` isn't defined on Person, but we have access to it via + // the item controller App.PersonController. If we'd used + // `content.@each.reversedName` above, we would be getting the objects + // directly and not have access to `reversedName`. + // + var reversedNameA = get(personA, 'reversedName'), + reversedNameB = get(personB, 'reversedName'); + + return Ember.compare(reversedNameA, reversedNameB); + }) + }); + + App.PersonController = Ember.ObjectController.extend({ + reversedName: function () { + return reverse(get(this, 'name')); + }.property('name') + }) + ``` + @method reduceComputed @for Ember @param {String} [dependentKeys*] @@ -11711,7 +13883,8 @@ var get = Ember.get, merge = Ember.merge, a_slice = [].slice, forEach = Ember.EnumerableUtils.forEach, - map = Ember.EnumerableUtils.map; + map = Ember.EnumerableUtils.map, + SearchProxy; /** A computed property that calculates the maximum value in the @@ -11811,14 +13984,14 @@ Ember.computed.min = function (dependentKey) { Example ```javascript - App.Hampster = Ember.Object.extend({ + App.Hamster = Ember.Object.extend({ excitingChores: Ember.computed.map('chores', function(chore) { return chore.toUpperCase() + '!'; }) }); - var hampster = App.Hampster.create({chores: ['cook', 'clean', 'write more unit tests']}); - hampster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!'] + var hamster = App.Hamster.create({chores: ['cook', 'clean', 'write more unit tests']}); + hamster.get('excitingChores'); // ['COOK!', 'CLEAN!', 'WRITE MORE UNIT TESTS!'] ``` @method computed.map @@ -11895,18 +14068,18 @@ Ember.computed.mapProperty = Ember.computed.mapBy; Example ```javascript - App.Hampster = Ember.Object.extend({ + App.Hamster = Ember.Object.extend({ remainingChores: Ember.computed.filter('chores', function(chore) { return !chore.done; }) }); - var hampster = App.Hampster.create({chores: [ + var hamster = App.Hamster.create({chores: [ {name: 'cook', done: true}, {name: 'clean', done: true}, {name: 'write more unit tests', done: false} ]}); - hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] ``` @method computed.filter @@ -11952,16 +14125,16 @@ Ember.computed.filter = function(dependentKey, callback) { Example ```javascript - App.Hampster = Ember.Object.extend({ + App.Hamster = Ember.Object.extend({ remainingChores: Ember.computed.filterBy('chores', 'done', false) }); - var hampster = App.Hampster.create({chores: [ + var hamster = App.Hamster.create({chores: [ {name: 'cook', done: true}, {name: 'clean', done: true}, {name: 'write more unit tests', done: false} ]}); - hampster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] + hamster.get('remainingChores'); // [{name: 'write more unit tests', done: false}] ``` @method computed.filterBy @@ -12004,17 +14177,17 @@ Ember.computed.filterProperty = Ember.computed.filterBy; Example ```javascript - App.Hampster = Ember.Object.extend({ + App.Hamster = Ember.Object.extend({ uniqueFruits: Ember.computed.uniq('fruits') }); - var hampster = App.Hampster.create({fruits: [ + var hamster = App.Hamster.create({fruits: [ 'banana', 'grape', 'kale', 'banana' ]}); - hampster.get('uniqueFruits'); // ['banana', 'grape', 'kale'] + hamster.get('uniqueFruits'); // ['banana', 'grape', 'kale'] ``` @method computed.uniq @@ -12149,16 +14322,16 @@ Ember.computed.intersect = function () { Example ```javascript - App.Hampster = Ember.Object.extend({ + App.Hamster = Ember.Object.extend({ likes: ['banana', 'grape', 'kale'], wants: Ember.computed.setDiff('likes', 'fruits') }); - var hampster = App.Hampster.create({fruits: [ + var hamster = App.Hamster.create({fruits: [ 'grape', 'kale', ]}); - hampster.get('wants'); // ['banana'] + hamster.get('wants'); // ['banana'] ``` @method computed.setDiff @@ -12239,13 +14412,16 @@ function binarySearch(array, item, low, high) { return mid; function _guidFor(item) { - if (Ember.ObjectProxy.detectInstance(item)) { + if (SearchProxy.detectInstance(item)) { return guidFor(get(item, 'content')); } return guidFor(item); } } + +SearchProxy = Ember.ObjectProxy.extend(); + /** A computed property which returns a new array with all the properties from the first dependent array sorted based on a property @@ -12386,10 +14562,10 @@ Ember.computed.sort = function (itemsKey, sortDefinition) { if (changeMeta.previousValues) { proxyProperties = merge({ content: item }, changeMeta.previousValues); - searchItem = Ember.ObjectProxy.create(proxyProperties); - } else { - searchItem = item; - } + searchItem = SearchProxy.create(proxyProperties); + } else { + searchItem = item; + } index = instanceMeta.binarySearch(array, searchItem); array.removeAt(index); @@ -12418,378 +14594,6 @@ Ember.RSVP = requireModule('rsvp'); -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var STRING_DASHERIZE_REGEXP = (/[ _]/g); -var STRING_DASHERIZE_CACHE = {}; -var STRING_DECAMELIZE_REGEXP = (/([a-z\d])([A-Z])/g); -var STRING_CAMELIZE_REGEXP = (/(\-|_|\.|\s)+(.)?/g); -var STRING_UNDERSCORE_REGEXP_1 = (/([a-z\d])([A-Z]+)/g); -var STRING_UNDERSCORE_REGEXP_2 = (/\-|\s+/g); - -/** - Defines the hash of localized strings for the current language. Used by - the `Ember.String.loc()` helper. To localize, add string values to this - hash. - - @property STRINGS - @for Ember - @type Hash -*/ -Ember.STRINGS = {}; - -/** - Defines string helper methods including string formatting and localization. - Unless `Ember.EXTEND_PROTOTYPES.String` is `false` these methods will also be - added to the `String.prototype` as well. - - @class String - @namespace Ember - @static -*/ -Ember.String = { - - /** - Apply formatting options to the string. This will look for occurrences - of "%@" in your string and substitute them with the arguments you pass into - this method. If you want to control the specific order of replacement, - you can add a number after the key as well to indicate which argument - you want to insert. - - Ordered insertions are most useful when building loc strings where values - you need to insert may appear in different orders. - - ```javascript - "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" - "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" - ``` - - @method fmt - @param {String} str The string to format - @param {Array} formats An array of parameters to interpolate into string. - @return {String} formatted string - */ - fmt: function(str, formats) { - // first, replace any ORDERED replacements. - var idx = 0; // the current index for non-numerical replacements - return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { - argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; - s = formats[argIndex]; - return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); - }) ; - }, - - /** - Formats the passed string, but first looks up the string in the localized - strings hash. This is a convenient way to localize text. See - `Ember.String.fmt()` for more information on formatting. - - Note that it is traditional but not required to prefix localized string - keys with an underscore or other character so you can easily identify - localized strings. - - ```javascript - Ember.STRINGS = { - '_Hello World': 'Bonjour le monde', - '_Hello %@ %@': 'Bonjour %@ %@' - }; - - Ember.String.loc("_Hello World"); // 'Bonjour le monde'; - Ember.String.loc("_Hello %@ %@", ["John", "Smith"]); // "Bonjour John Smith"; - ``` - - @method loc - @param {String} str The string to format - @param {Array} formats Optional array of parameters to interpolate into string. - @return {String} formatted string - */ - loc: function(str, formats) { - str = Ember.STRINGS[str] || str; - return Ember.String.fmt(str, formats) ; - }, - - /** - Splits a string into separate units separated by spaces, eliminating any - empty strings in the process. This is a convenience method for split that - is mostly useful when applied to the `String.prototype`. - - ```javascript - Ember.String.w("alpha beta gamma").forEach(function(key) { - console.log(key); - }); - - // > alpha - // > beta - // > gamma - ``` - - @method w - @param {String} str The string to split - @return {String} split string - */ - w: function(str) { return str.split(/\s+/); }, - - /** - Converts a camelized string into all lower case separated by underscores. - - ```javascript - 'innerHTML'.decamelize(); // 'inner_html' - 'action_name'.decamelize(); // 'action_name' - 'css-class-name'.decamelize(); // 'css-class-name' - 'my favorite items'.decamelize(); // 'my favorite items' - ``` - - @method decamelize - @param {String} str The string to decamelize. - @return {String} the decamelized string. - */ - decamelize: function(str) { - return str.replace(STRING_DECAMELIZE_REGEXP, '$1_$2').toLowerCase(); - }, - - /** - Replaces underscores, spaces, or camelCase with dashes. - - ```javascript - 'innerHTML'.dasherize(); // 'inner-html' - 'action_name'.dasherize(); // 'action-name' - 'css-class-name'.dasherize(); // 'css-class-name' - 'my favorite items'.dasherize(); // 'my-favorite-items' - ``` - - @method dasherize - @param {String} str The string to dasherize. - @return {String} the dasherized string. - */ - dasherize: function(str) { - var cache = STRING_DASHERIZE_CACHE, - hit = cache.hasOwnProperty(str), - ret; - - if (hit) { - return cache[str]; - } else { - ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); - cache[str] = ret; - } - - return ret; - }, - - /** - Returns the lowerCamelCase form of a string. - - ```javascript - 'innerHTML'.camelize(); // 'innerHTML' - 'action_name'.camelize(); // 'actionName' - 'css-class-name'.camelize(); // 'cssClassName' - 'my favorite items'.camelize(); // 'myFavoriteItems' - 'My Favorite Items'.camelize(); // 'myFavoriteItems' - ``` - - @method camelize - @param {String} str The string to camelize. - @return {String} the camelized string. - */ - camelize: function(str) { - return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { - return chr ? chr.toUpperCase() : ''; - }).replace(/^([A-Z])/, function(match, separator, chr) { - return match.toLowerCase(); - }); - }, - - /** - Returns the UpperCamelCase form of a string. - - ```javascript - 'innerHTML'.classify(); // 'InnerHTML' - 'action_name'.classify(); // 'ActionName' - 'css-class-name'.classify(); // 'CssClassName' - 'my favorite items'.classify(); // 'MyFavoriteItems' - ``` - - @method classify - @param {String} str the string to classify - @return {String} the classified string - */ - classify: function(str) { - var parts = str.split("."), - out = []; - - for (var i=0, l=parts.length; i= 0) { - var baseValue = this[keyName]; - - if (baseValue) { - if ('function' === typeof baseValue.concat) { - value = baseValue.concat(value); - } else { - value = Ember.makeArray(baseValue).concat(value); - } - } else { - value = Ember.makeArray(value); - } - } - - if (desc) { - desc.set(this, keyName, value); - } else { - if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) { - this.setUnknownProperty(keyName, value); - } else if (MANDATORY_SETTER) { - Ember.defineProperty(this, keyName, null, value); // setup mandatory setter - } else { - this[keyName] = value; - } - } - } - } - } - finishPartial(this, m); - this.init.apply(this, arguments); - m.proto = proto; - finishChains(this); - sendEvent(this, "init"); - }; - - Class.toString = Mixin.prototype.toString; - Class.willReopen = function() { - if (wasApplied) { - Class.PrototypeMixin = Mixin.create(Class.PrototypeMixin); - } - - wasApplied = false; - }; - Class._initMixins = function(args) { initMixins = args; }; - Class._initProperties = function(args) { initProperties = args; }; - - Class.proto = function() { - var superclass = Class.superclass; - if (superclass) { superclass.proto(); } - - if (!wasApplied) { - wasApplied = true; - Class.PrototypeMixin.applyPartial(Class.prototype); - rewatch(Class.prototype); - } - - return this.prototype; - }; - - return Class; - -} - -/** - @class CoreObject - @namespace Ember -*/ -var CoreObject = makeCtor(); -CoreObject.toString = function() { return "Ember.CoreObject"; }; - -CoreObject.PrototypeMixin = Mixin.create({ - reopen: function() { - applyMixin(this, arguments, true); - return this; - }, - - /** - An overridable method called when objects are instantiated. By default, - does nothing unless it is overridden during class definition. - - Example: - - ```javascript - App.Person = Ember.Object.extend({ - init: function() { - alert('Name is ' + this.get('name')); - } - }); - - var steve = App.Person.create({ - name: "Steve" - }); - - // alerts 'Name is Steve'. - ``` - - NOTE: If you do override `init` for a framework class like `Ember.View` or - `Ember.ArrayController`, be sure to call `this._super()` in your - `init` declaration! If you don't, Ember may not have an opportunity to - do important setup work, and you'll see strange behavior in your - application. - - @method init - */ - init: function() {}, - - /** - Defines the properties that will be concatenated from the superclass - (instead of overridden). - - By default, when you extend an Ember class a property defined in - the subclass overrides a property with the same name that is defined - in the superclass. However, there are some cases where it is preferable - to build up a property's value by combining the superclass' property - value with the subclass' value. An example of this in use within Ember - is the `classNames` property of `Ember.View`. - - Here is some sample code showing the difference between a concatenated - property and a normal one: - - ```javascript - App.BarView = Ember.View.extend({ - someNonConcatenatedProperty: ['bar'], - classNames: ['bar'] - }); - - App.FooBarView = App.BarView.extend({ - someNonConcatenatedProperty: ['foo'], - classNames: ['foo'], - }); - - var fooBarView = App.FooBarView.create(); - fooBarView.get('someNonConcatenatedProperty'); // ['foo'] - fooBarView.get('classNames'); // ['ember-view', 'bar', 'foo'] - ``` - - This behavior extends to object creation as well. Continuing the - above example: - - ```javascript - var view = App.FooBarView.create({ - someNonConcatenatedProperty: ['baz'], - classNames: ['baz'] - }) - view.get('someNonConcatenatedProperty'); // ['baz'] - view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] - ``` - Adding a single property that is not an array will just add it in the array: - - ```javascript - var view = App.FooBarView.create({ - classNames: 'baz' - }) - view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] - ``` - - Using the `concatenatedProperties` property, we can tell to Ember that mix - the content of the properties. - - In `Ember.View` the `classNameBindings` and `attributeBindings` properties - are also concatenated, in addition to `classNames`. - - This feature is available for you to use throughout the Ember object model, - although typical app developers are likely to use it infrequently. Since - it changes expectations about behavior of properties, you should properly - document its usage in each individual concatenated property (to not - mislead your users to think they can override the property in a subclass). - - @property concatenatedProperties - @type Array - @default null - */ - concatenatedProperties: null, - - /** - Destroyed object property flag. - - if this property is `true` the observers and bindings were already - removed by the effect of calling the `destroy()` method. - - @property isDestroyed - @default false - */ - isDestroyed: false, - - /** - Destruction scheduled flag. The `destroy()` method has been called. - - The object stays intact until the end of the run loop at which point - the `isDestroyed` flag is set. - - @property isDestroying - @default false - */ - isDestroying: false, - - /** - Destroys an object by setting the `isDestroyed` flag and removing its - metadata, which effectively destroys observers and bindings. - - If you try to set a property on a destroyed object, an exception will be - raised. - - Note that destruction is scheduled for the end of the run loop and does not - happen immediately. It will set an isDestroying flag immediately. - - @method destroy - @return {Ember.Object} receiver - */ - destroy: function() { - if (this.isDestroying) { return; } - this.isDestroying = true; - - schedule('actions', this, this.willDestroy); - schedule('destroy', this, this._scheduledDestroy); - return this; - }, - - /** - Override to implement teardown. - - @method willDestroy - */ - willDestroy: Ember.K, - - /** - @private - - Invoked by the run loop to actually destroy the object. This is - scheduled for execution by the `destroy` method. - - @method _scheduledDestroy - */ - _scheduledDestroy: function() { - if (this.isDestroyed) { return; } - destroy(this); - this.isDestroyed = true; - }, - - bind: function(to, from) { - if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); } - from.to(to).connect(this); - return from; - }, - - /** - Returns a string representation which attempts to provide more information - than Javascript's `toString` typically does, in a generic way for all Ember - objects. - - App.Person = Em.Object.extend() - person = App.Person.create() - person.toString() //=> "" - - If the object's class is not defined on an Ember namespace, it will - indicate it is a subclass of the registered superclass: - - Student = App.Person.extend() - student = Student.create() - student.toString() //=> "<(subclass of App.Person):ember1025>" - - If the method `toStringExtension` is defined, its return value will be - included in the output. - - App.Teacher = App.Person.extend({ - toStringExtension: function() { - return this.get('fullName'); - } - }); - teacher = App.Teacher.create() - teacher.toString(); //=> "" - - @method toString - @return {String} string representation - */ - toString: function toString() { - var hasToStringExtension = typeof this.toStringExtension === 'function', - extension = hasToStringExtension ? ":" + this.toStringExtension() : ''; - var ret = '<'+this.constructor.toString()+':'+guidFor(this)+extension+'>'; - this.toString = makeToString(ret); - return ret; - } -}); - -CoreObject.PrototypeMixin.ownerConstructor = CoreObject; - -function makeToString(ret) { - return function() { return ret; }; -} - -if (Ember.config.overridePrototypeMixin) { - Ember.config.overridePrototypeMixin(CoreObject.PrototypeMixin); -} - -CoreObject.__super__ = null; - -var ClassMixin = Mixin.create({ - - ClassMixin: Ember.required(), - - PrototypeMixin: Ember.required(), - - isClass: true, - - isMethod: false, - - /** - Creates a new subclass. - - ```javascript - App.Person = Ember.Object.extend({ - say: function(thing) { - alert(thing); - } - }); - ``` - - This defines a new subclass of Ember.Object: `App.Person`. It contains one method: `say()`. - - You can also create a subclass from any existing class by calling its `extend()` method. For example, you might want to create a subclass of Ember's built-in `Ember.View` class: - - ```javascript - App.PersonView = Ember.View.extend({ - tagName: 'li', - classNameBindings: ['isAdministrator'] - }); - ``` - - When defining a subclass, you can override methods but still access the implementation of your parent class by calling the special `_super()` method: - - ```javascript - App.Person = Ember.Object.extend({ - say: function(thing) { - var name = this.get('name'); - alert(name + ' says: ' + thing); - } - }); - - App.Soldier = App.Person.extend({ - say: function(thing) { - this._super(thing + ", sir!"); - }, - march: function(numberOfHours) { - alert(this.get('name') + ' marches for ' + numberOfHours + ' hours.') - } - }); - - var yehuda = App.Soldier.create({ - name: "Yehuda Katz" - }); - - yehuda.say("Yes"); // alerts "Yehuda Katz says: Yes, sir!" - ``` - - The `create()` on line #17 creates an *instance* of the `App.Soldier` class. The `extend()` on line #8 creates a *subclass* of `App.Person`. Any instance of the `App.Person` class will *not* have the `march()` method. - - You can also pass `Ember.Mixin` classes to add additional properties to the subclass. - - ```javascript - App.Person = Ember.Object.extend({ - say: function(thing) { - alert(this.get('name') + ' says: ' + thing); - } - }); - - App.SingingMixin = Ember.Mixin.create({ - sing: function(thing){ - alert(this.get('name') + ' sings: la la la ' + thing); - } - }); - - App.BroadwayStar = App.Person.extend(App.SingingMixin, { - dance: function() { - alert(this.get('name') + ' dances: tap tap tap tap '); - } - }); - ``` - - The `App.BroadwayStar` class contains three methods: `say()`, `sing()`, and `dance()`. - - @method extend - @static - - @param {Ember.Mixin} [mixins]* One or more Ember.Mixin classes - @param {Object} [arguments]* Object containing values to use within the new class - */ - extend: function() { - var Class = makeCtor(), proto; - Class.ClassMixin = Mixin.create(this.ClassMixin); - Class.PrototypeMixin = Mixin.create(this.PrototypeMixin); - - Class.ClassMixin.ownerConstructor = Class; - Class.PrototypeMixin.ownerConstructor = Class; - - reopen.apply(Class.PrototypeMixin, arguments); - - Class.superclass = this; - Class.__super__ = this.prototype; - - proto = Class.prototype = o_create(this.prototype); - proto.constructor = Class; - generateGuid(proto, 'ember'); - meta(proto).proto = proto; // this will disable observers on prototype - - Class.ClassMixin.apply(Class); - return Class; - }, - - /** - Equivalent to doing `extend(arguments).create()`. - If possible use the normal `create` method instead. - - @method createWithMixins - @static - @param [arguments]* - */ - createWithMixins: function() { - var C = this; - if (arguments.length>0) { this._initMixins(arguments); } - return new C(); - }, - - /** - Creates an instance of a class. Accepts either no arguments, or an object - containing values to initialize the newly instantiated object with. - - ```javascript - App.Person = Ember.Object.extend({ - helloWorld: function() { - alert("Hi, my name is " + this.get('name')); - } - }); - - var tom = App.Person.create({ - name: 'Tom Dale' - }); - - tom.helloWorld(); // alerts "Hi, my name is Tom Dale". - ``` - - `create` will call the `init` function if defined during - `Ember.AnyObject.extend` - - If no arguments are passed to `create`, it will not set values to the new - instance during initialization: - - ```javascript - var noName = App.Person.create(); - noName.helloWorld(); // alerts undefined - ``` - - NOTE: For performance reasons, you cannot declare methods or computed - properties during `create`. You should instead declare methods and computed - properties when using `extend` or use the `createWithMixins` shorthand. - - @method create - @static - @param [arguments]* - */ - create: function() { - var C = this; - if (arguments.length>0) { this._initProperties(arguments); } - return new C(); - }, - - /** - - Augments a constructor's prototype with additional - properties and functions: - - ```javascript - MyObject = Ember.Object.extend({ - name: 'an object' - }); - - o = MyObject.create(); - o.get('name'); // 'an object' - - MyObject.reopen({ - say: function(msg){ - console.log(msg); - } - }) - - o2 = MyObject.create(); - o2.say("hello"); // logs "hello" - - o.say("goodbye"); // logs "goodbye" - ``` - - To add functions and properties to the constructor itself, - see `reopenClass` - - @method reopen - */ - reopen: function() { - this.willReopen(); - reopen.apply(this.PrototypeMixin, arguments); - return this; - }, - - /** - Augments a constructor's own properties and functions: - - ```javascript - MyObject = Ember.Object.extend({ - name: 'an object' - }); - - - MyObject.reopenClass({ - canBuild: false - }); - - MyObject.canBuild; // false - o = MyObject.create(); - ``` - - In other words, this creates static properties and functions for the class. These are only available on the class - and not on any instance of that class. - - ```javascript - App.Person = Ember.Object.extend({ - name : "", - sayHello : function(){ - alert("Hello. My name is " + this.get('name')); - } - }); - - App.Person.reopenClass({ - species : "Homo sapiens", - createPerson: function(newPersonsName){ - return App.Person.create({ - name:newPersonsName - }); - } - }); - - var tom = App.Person.create({ - name : "Tom Dale" - }); - var yehuda = App.Person.createPerson("Yehuda Katz"); - - tom.sayHello(); // "Hello. My name is Tom Dale" - yehuda.sayHello(); // "Hello. My name is Yehuda Katz" - alert(App.Person.species); // "Homo sapiens" - ``` - - Note that `species` and `createPerson` are *not* valid on the `tom` and `yehuda` - variables. They are only valid on `App.Person`. - - To add functions and properties to instances of - a constructor by extending the constructor's prototype - see `reopen` - - @method reopenClass - */ - reopenClass: function() { - reopen.apply(this.ClassMixin, arguments); - applyMixin(this, arguments, false); - return this; - }, - - detect: function(obj) { - if ('function' !== typeof obj) { return false; } - while(obj) { - if (obj===this) { return true; } - obj = obj.superclass; - } - return false; - }, - - detectInstance: function(obj) { - return obj instanceof this; - }, - - /** - In some cases, you may want to annotate computed properties with additional - metadata about how they function or what values they operate on. For - example, computed property functions may close over variables that are then - no longer available for introspection. - - You can pass a hash of these values to a computed property like this: - - ```javascript - person: function() { - var personId = this.get('personId'); - return App.Person.create({ id: personId }); - }.property().meta({ type: App.Person }) - ``` - - Once you've done this, you can retrieve the values saved to the computed - property from your class like this: - - ```javascript - MyClass.metaForProperty('person'); - ``` - - This will return the original hash that was passed to `meta()`. - - @method metaForProperty - @param key {String} property name - */ - metaForProperty: function(key) { - var desc = meta(this.proto(), false).descs[key]; - - return desc._meta || {}; - }, - - /** - Iterate over each computed property for the class, passing its name - and any associated metadata (see `metaForProperty`) to the callback. - - @method eachComputedProperty - @param {Function} callback - @param {Object} binding - */ - eachComputedProperty: function(callback, binding) { - var proto = this.proto(), - descs = meta(proto).descs, - empty = {}, - property; - - for (var name in descs) { - property = descs[name]; - - if (property instanceof Ember.ComputedProperty) { - callback.call(binding || this, name, property._meta || empty); - } - } - } - -}); - -ClassMixin.ownerConstructor = CoreObject; - -if (Ember.config.overrideClassMixin) { - Ember.config.overrideClassMixin(ClassMixin); -} - -CoreObject.ClassMixin = ClassMixin; -ClassMixin.apply(CoreObject); - -Ember.CoreObject = CoreObject; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -/** - `Ember.Object` is the main base class for all Ember objects. It is a subclass - of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details, - see the documentation for each of these. - - @class Object - @namespace Ember - @extends Ember.CoreObject - @uses Ember.Observable -*/ -Ember.Object = Ember.CoreObject.extend(Ember.Observable); -Ember.Object.toString = function() { return "Ember.Object"; }; - -})(); - - - -(function() { -/** -@module ember -@submodule ember-runtime -*/ - -var get = Ember.get, indexOf = Ember.ArrayPolyfills.indexOf; - -/** - A Namespace is an object usually used to contain other objects or methods - such as an application or framework. Create a namespace anytime you want - to define one of these new containers. - - # Example Usage - - ```javascript - MyFramework = Ember.Namespace.create({ - VERSION: '1.0.0' - }); - ``` - - @class Namespace - @namespace Ember - @extends Ember.Object -*/ -var Namespace = Ember.Namespace = Ember.Object.extend({ - isNamespace: true, - - init: function() { - Ember.Namespace.NAMESPACES.push(this); - Ember.Namespace.PROCESSED = false; - }, - - toString: function() { - var name = get(this, 'name'); - if (name) { return name; } - - findNamespaces(); - return this[Ember.GUID_KEY+'_name']; - }, - - nameClasses: function() { - processNamespace([this.toString()], this, {}); - }, - - destroy: function() { - var namespaces = Ember.Namespace.NAMESPACES; - Ember.lookup[this.toString()] = undefined; - namespaces.splice(indexOf.call(namespaces, this), 1); - this._super(); - } -}); - -Namespace.reopenClass({ - NAMESPACES: [Ember], - NAMESPACES_BY_ID: {}, - PROCESSED: false, - processAll: processAllNamespaces, - byName: function(name) { - if (!Ember.BOOTED) { - processAllNamespaces(); - } - - return NAMESPACES_BY_ID[name]; - } -}); - -var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID; - -var hasOwnProp = ({}).hasOwnProperty, - guidFor = Ember.guidFor; - -function processNamespace(paths, root, seen) { - var idx = paths.length; - - NAMESPACES_BY_ID[paths.join('.')] = root; - - // Loop over all of the keys in the namespace, looking for classes - for(var key in root) { - if (!hasOwnProp.call(root, key)) { continue; } - var obj = root[key]; - - // If we are processing the `Ember` namespace, for example, the - // `paths` will start with `["Ember"]`. Every iteration through - // the loop will update the **second** element of this list with - // the key, so processing `Ember.View` will make the Array - // `['Ember', 'View']`. - paths[idx] = key; - - // If we have found an unprocessed class - if (obj && obj.toString === classToString) { - // Replace the class' `toString` with the dot-separated path - // and set its `NAME_KEY` - obj.toString = makeToString(paths.join('.')); - obj[NAME_KEY] = paths.join('.'); - - // Support nested namespaces - } else if (obj && obj.isNamespace) { - // Skip aliased namespaces - if (seen[guidFor(obj)]) { continue; } - seen[guidFor(obj)] = true; - - // Process the child namespace - processNamespace(paths, obj, seen); - } - } - - paths.length = idx; // cut out last item -} - -function findNamespaces() { - var Namespace = Ember.Namespace, lookup = Ember.lookup, obj, isNamespace; - - if (Namespace.PROCESSED) { return; } - - for (var prop in lookup) { - // These don't raise exceptions but can cause warnings - if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; } - - // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox. - // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage - if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; } - // Unfortunately, some versions of IE don't support window.hasOwnProperty - if (lookup.hasOwnProperty && !lookup.hasOwnProperty(prop)) { continue; } - - // At times we are not allowed to access certain properties for security reasons. - // There are also times where even if we can access them, we are not allowed to access their properties. - try { - obj = Ember.lookup[prop]; - isNamespace = obj && obj.isNamespace; - } catch (e) { - continue; - } - - if (isNamespace) { - - obj[NAME_KEY] = prop; - } - } -} - -var NAME_KEY = Ember.NAME_KEY = Ember.GUID_KEY + '_name'; - -function superClassString(mixin) { - var superclass = mixin.superclass; - if (superclass) { - if (superclass[NAME_KEY]) { return superclass[NAME_KEY]; } - else { return superClassString(superclass); } - } else { - return; - } -} - -function classToString() { - if (!Ember.BOOTED && !this[NAME_KEY]) { - processAllNamespaces(); - } - - var ret; - - if (this[NAME_KEY]) { - ret = this[NAME_KEY]; - } else if (this._toString) { - ret = this._toString; - } else { - var str = superClassString(this); - if (str) { - ret = "(subclass of " + str + ")"; - } else { - ret = "(unknown mixin)"; - } - this.toString = makeToString(ret); - } - - return ret; -} - -function processAllNamespaces() { - var unprocessedNamespaces = !Namespace.PROCESSED, - unprocessedMixins = Ember.anyUnprocessedMixins; - - if (unprocessedNamespaces) { - findNamespaces(); - Namespace.PROCESSED = true; - } - - if (unprocessedNamespaces || unprocessedMixins) { - var namespaces = Namespace.NAMESPACES, namespace; - for (var i=0, l=namespaces.length; i