diff --git a/angular_app/js/angular-resource.js b/angular_app/js/angular-resource.js
new file mode 100644
index 0000000..712b32e
--- /dev/null
+++ b/angular_app/js/angular-resource.js
@@ -0,0 +1,610 @@
+/**
+ * @license AngularJS v1.2.17
+ * (c) 2010-2014 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular, undefined) {'use strict';
+
+var $resourceMinErr = angular.$$minErr('$resource');
+
+// Helper functions and regex to lookup a dotted path on an object
+// stopping at undefined/null. The path must be composed of ASCII
+// identifiers (just like $parse)
+var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
+
+function isValidDottedPath(path) {
+ return (path != null && path !== '' && path !== 'hasOwnProperty' &&
+ MEMBER_NAME_REGEX.test('.' + path));
+}
+
+function lookupDottedPath(obj, path) {
+ if (!isValidDottedPath(path)) {
+ throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
+ }
+ var keys = path.split('.');
+ for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) {
+ var key = keys[i];
+ obj = (obj !== null) ? obj[key] : undefined;
+ }
+ return obj;
+}
+
+/**
+ * Create a shallow copy of an object and clear other fields from the destination
+ */
+function shallowClearAndCopy(src, dst) {
+ dst = dst || {};
+
+ angular.forEach(dst, function(value, key){
+ delete dst[key];
+ });
+
+ for (var key in src) {
+ if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
+ dst[key] = src[key];
+ }
+ }
+
+ return dst;
+}
+
+/**
+ * @ngdoc module
+ * @name ngResource
+ * @description
+ *
+ * # ngResource
+ *
+ * The `ngResource` module provides interaction support with RESTful services
+ * via the $resource service.
+ *
+ *
+ *
+ *
+ * See {@link ngResource.$resource `$resource`} for usage.
+ */
+
+/**
+ * @ngdoc service
+ * @name $resource
+ * @requires $http
+ *
+ * @description
+ * A factory which creates a resource object that lets you interact with
+ * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
+ *
+ * The returned resource object has action methods which provide high-level behaviors without
+ * the need to interact with the low level {@link ng.$http $http} service.
+ *
+ * Requires the {@link ngResource `ngResource`} module to be installed.
+ *
+ * @param {string} url A parametrized URL template with parameters prefixed by `:` as in
+ * `/user/:username`. If you are using a URL with a port number (e.g.
+ * `http://example.com:8080/api`), it will be respected.
+ *
+ * If you are using a url with a suffix, just add the suffix, like this:
+ * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
+ * or even `$resource('http://example.com/resource/:resource_id.:format')`
+ * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
+ * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
+ * can escape it with `/\.`.
+ *
+ * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
+ * `actions` methods. If any of the parameter value is a function, it will be executed every time
+ * when a param value needs to be obtained for a request (unless the param was overridden).
+ *
+ * Each key value in the parameter object is first bound to url template if present and then any
+ * excess keys are appended to the url search query after the `?`.
+ *
+ * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
+ * URL `/path/greet?salutation=Hello`.
+ *
+ * If the parameter value is prefixed with `@` then the value of that parameter will be taken
+ * from the corresponding key on the data object (useful for non-GET operations).
+ *
+ * @param {Object.
";
- * div.firstChild.firstChild.tagName //=> ""
- *
- * If our script markers are inside such a node, we need to find that
- * node and use *it* as the marker.
- */
- var realNode = function(start) {
- while (start.parentNode.tagName === "") {
- start = start.parentNode;
- }
+ // If no path is provided, treat path param as options.
+ if (path && path.data && path.data.isRenderData) {
+ options = path;
+ path = undefined;
+ Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 1);
+ } else {
+ Ember.assert("You cannot pass more than one argument to the collection helper", arguments.length === 2);
+ }
- return start;
- };
+ var fn = options.fn;
+ var data = options.data;
+ var inverse = options.inverse;
+ var view = options.data.view;
- /*
- * When automatically adding a tbody, Internet Explorer inserts the
- * tbody immediately before the first
. Other browsers create it
- * before the first node, no matter what.
- *
- * This means the the following code:
- *
- * div = document.createElement("div");
- * div.innerHTML = "
hi
- *
- * Generates the following DOM in IE:
- *
- * + div
- * + table
- * - script id='first'
- * + tbody
- * + tr
- * + td
- * - "hi"
- * - script id='last'
- *
- * Which means that the two script tags, even though they were
- * inserted at the same point in the hierarchy in the original
- * HTML, now have different parents.
- *
- * This code reparents the first script tag by making it the tbody's
- * first child.
- *
- */
- var fixParentage = function(start, end) {
- if (start.parentNode !== end.parentNode) {
- end.parentNode.insertBefore(start, end.parentNode.firstChild);
- }
- };
- htmlFunc = function(html, outerToo) {
- // get the real starting node. see realNode for details.
- var start = realNode(document.getElementById(this.start));
- var end = document.getElementById(this.end);
- var parentNode = end.parentNode;
- var node, nextSibling, last;
+ var controller, container;
+ // If passed a path string, convert that into an object.
+ // Otherwise, just default to the standard class.
+ var collectionClass;
+ if (path) {
+ controller = data.keywords.controller;
+ container = controller && controller.container;
+ collectionClass = handlebarsGet(this, path, options) || container.lookupFactory('view:' + path);
+ Ember.assert(fmt("%@ #collection: Could not find collection class %@", [data.view, path]), !!collectionClass);
+ }
+ else {
+ collectionClass = Ember.CollectionView;
+ }
- // make sure that the start and end nodes share the same
- // parent. If not, fix it.
- fixParentage(start, end);
+ var hash = options.hash, itemHash = {}, match;
- // remove all of the nodes after the starting placeholder and
- // before the ending placeholder.
- node = start.nextSibling;
- while (node) {
- nextSibling = node.nextSibling;
- last = node === end;
+ // Extract item view class if provided else default to the standard class
+ var collectionPrototype = collectionClass.proto(),
+ itemViewClass;
- // if this is the last node, and we want to remove it as well,
- // set the `end` node to the next sibling. This is because
- // for the rest of the function, we insert the new nodes
- // before the end (note that insertBefore(node, null) is
- // the same as appendChild(node)).
- //
- // if we do not want to remove it, just break.
- if (last) {
- if (outerToo) { end = node.nextSibling; } else { break; }
- }
+ if (hash.itemView) {
+ controller = data.keywords.controller;
+ Ember.assert('You specified an itemView, but the current context has no ' +
+ 'container to look the itemView up in. This probably means ' +
+ 'that you created a view manually, instead of through the ' +
+ 'container. Instead, use container.lookup("view:viewName"), ' +
+ 'which will properly instantiate your view.',
+ controller && controller.container);
+ container = controller.container;
+ itemViewClass = container.lookupFactory('view:' + hash.itemView);
+ Ember.assert('You specified the itemView ' + hash.itemView + ", but it was " +
+ "not found at " + container.describe("view:" + hash.itemView) +
+ " (and it was not registered in the container)", !!itemViewClass);
+ } else if (hash.itemViewClass) {
+ itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options);
+ } else {
+ itemViewClass = collectionPrototype.itemViewClass;
+ }
- node.parentNode.removeChild(node);
+ Ember.assert(fmt("%@ #collection: Could not find itemViewClass %@", [data.view, itemViewClass]), !!itemViewClass);
- // if this is the last node and we didn't break before
- // (because we wanted to remove the outer nodes), break
- // now.
- if (last) { break; }
+ delete hash.itemViewClass;
+ delete hash.itemView;
- node = nextSibling;
- }
+ // Go through options passed to the {{collection}} helper and extract options
+ // that configure item views instead of the collection itself.
+ for (var prop in hash) {
+ if (hash.hasOwnProperty(prop)) {
+ match = prop.match(/^item(.)(.*)$/);
- // get the first node for the HTML string, even in cases like
- // tables and lists where a simple innerHTML on a div would
- // swallow some of the content.
- node = firstNodeFor(start.parentNode, html);
+ if (match && prop !== 'itemController') {
+ // Convert itemShouldFoo -> shouldFoo
+ itemHash[match[1].toLowerCase() + match[2]] = hash[prop];
+ // Delete from hash as this will end up getting passed to the
+ // {{view}} helper method.
+ delete hash[prop];
+ }
+ }
+ }
- // copy the nodes for the HTML between the starting and ending
- // placeholder.
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.insertBefore(node, end);
- node = nextSibling;
- }
- };
+ if (fn) {
+ itemHash.template = fn;
+ delete options.fn;
+ }
- // remove the nodes in the DOM representing this metamorph.
- //
- // this includes the starting and ending placeholders.
- removeFunc = function() {
- var start = realNode(document.getElementById(this.start));
- var end = document.getElementById(this.end);
+ var emptyViewClass;
+ if (inverse && inverse !== Ember.Handlebars.VM.noop) {
+ emptyViewClass = get(collectionPrototype, 'emptyViewClass');
+ emptyViewClass = emptyViewClass.extend({
+ template: inverse,
+ tagName: itemHash.tagName
+ });
+ } else if (hash.emptyViewClass) {
+ emptyViewClass = handlebarsGet(this, hash.emptyViewClass, options);
+ }
+ if (emptyViewClass) { hash.emptyView = emptyViewClass; }
- this.html('');
- start.parentNode.removeChild(start);
- end.parentNode.removeChild(end);
- };
+ if (!hash.keyword) {
+ itemHash._context = Ember.computed.alias('content');
+ }
- appendToFunc = function(parentNode) {
- var node = firstNodeFor(parentNode, this.outerHTML());
- var nextSibling;
+ var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
+ hash.itemViewClass = itemViewClass.extend(viewOptions);
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.appendChild(node);
- node = nextSibling;
- }
- };
+ return Ember.Handlebars.helpers.view.call(this, collectionClass, options);
+});
- afterFunc = function(html) {
- // get the real starting node. see realNode for details.
- var end = document.getElementById(this.end);
- var insertBefore = end.nextSibling;
- var parentNode = end.parentNode;
- var nextSibling;
- var node;
- // get the first node for the HTML string, even in cases like
- // tables and lists where a simple innerHTML on a div would
- // swallow some of the content.
- node = firstNodeFor(parentNode, html);
+})();
- // copy the nodes for the HTML between the starting and ending
- // placeholder.
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.insertBefore(node, insertBefore);
- node = nextSibling;
- }
- };
- prependFunc = function(html) {
- var start = document.getElementById(this.start);
- var parentNode = start.parentNode;
- var nextSibling;
- var node;
- node = firstNodeFor(parentNode, html);
- var insertBefore = start.nextSibling;
+(function() {
+/*globals Handlebars */
+/**
+@module ember
+@submodule ember-handlebars
+*/
- while (node) {
- nextSibling = node.nextSibling;
- parentNode.insertBefore(node, insertBefore);
- node = nextSibling;
- }
- };
- }
+var handlebarsGet = Ember.Handlebars.get;
- Metamorph.prototype.html = function(html) {
- this.checkRemoved();
- if (html === undefined) { return this.innerHTML; }
+/**
+ `unbound` allows you to output a property without binding. *Important:* The
+ output will not be updated if the property changes. Use with caution.
- htmlFunc.call(this, html);
+ ```handlebars
+
{{unbound somePropertyThatDoesntChange}}
+ ```
- this.innerHTML = html;
- };
+ `unbound` can also be used in conjunction with a bound helper to
+ render it in its unbound form:
- Metamorph.prototype.replaceWith = function(html) {
- this.checkRemoved();
- htmlFunc.call(this, html, true);
- };
+ ```handlebars
+
+ ```
- Metamorph.prototype.remove = removeFunc;
- Metamorph.prototype.outerHTML = outerHTMLFunc;
- Metamorph.prototype.appendTo = appendToFunc;
- Metamorph.prototype.after = afterFunc;
- Metamorph.prototype.prepend = prependFunc;
- Metamorph.prototype.startTag = startTagFunc;
- Metamorph.prototype.endTag = endTagFunc;
+ @method unbound
+ @for Ember.Handlebars.helpers
+ @param {String} property
+ @return {String} HTML string
+*/
+Ember.Handlebars.registerHelper('unbound', function unboundHelper(property, fn) {
+ var options = arguments[arguments.length - 1], helper, context, out;
- Metamorph.prototype.isRemoved = function() {
- var before = document.getElementById(this.start);
- var after = document.getElementById(this.end);
+ if (arguments.length > 2) {
+ // Unbound helper call.
+ options.data.isUnbound = true;
+ helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helpers.helperMissing;
+ out = helper.apply(this, Array.prototype.slice.call(arguments, 1));
+ delete options.data.isUnbound;
+ return out;
+ }
- return !before || !after;
- };
+ context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
+ return handlebarsGet(context, property, fn);
+});
- Metamorph.prototype.checkRemoved = function() {
- if (this.isRemoved()) {
- throw new Error("Cannot perform operations on a Metamorph that is not in the DOM.");
- }
- };
+})();
- return Metamorph;
- });
-})();
(function() {
+/*jshint debug:true*/
/**
@module ember
-@submodule ember-handlebars-compiler
+@submodule ember-handlebars
*/
-// Eliminate dependency on any Ember to simplify precompilation workflow
-var objectCreate = Object.create || function(parent) {
- function F() {}
- F.prototype = parent;
- return new F();
-};
-
-var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars);
-if (!Handlebars && typeof require === 'function') {
- Handlebars = require('handlebars');
-}
-
-Ember.assert("Ember Handlebars requires Handlebars version 1.0.0. Include a SCRIPT tag in the HTML HEAD linking to the Handlebars file before you link to Ember.", Handlebars);
-Ember.assert("Ember Handlebars requires Handlebars version 1.0.0, COMPILER_REVISION expected: 4, got: " + Handlebars.COMPILER_REVISION + " - Please note: Builds of master may have other COMPILER_REVISION values.", Handlebars.COMPILER_REVISION === 4);
+var get = Ember.get;
+var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
+var a_slice = [].slice;
/**
- Prepares the Handlebars templating library for use inside Ember's view
- system.
-
- The `Ember.Handlebars` object is the standard Handlebars library, extended to
- use Ember's `get()` method instead of direct property access, which allows
- computed properties to be used inside templates.
+ `log` allows you to output the value of variables in the current rendering
+ context. `log` also accepts primitive types such as strings or numbers.
- To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`.
- This will return a function that can be used by `Ember.View` for rendering.
+ ```handlebars
+ {{log "myVariable:" myVariable }}
+ ```
- @class Handlebars
- @namespace Ember
+ @method log
+ @for Ember.Handlebars.helpers
+ @param {String} property
*/
-Ember.Handlebars = objectCreate(Handlebars);
+Ember.Handlebars.registerHelper('log', function logHelper() {
+ var params = a_slice.call(arguments, 0, -1),
+ options = arguments[arguments.length - 1],
+ logger = Ember.Logger.log,
+ values = [],
+ allowPrimitives = false;
-/**
- Register a bound helper or custom view helper.
+
+ allowPrimitives = true;
+
- ## Simple bound helper example
+ for (var i = 0; i < params.length; i++) {
+ var type = options.types[i];
- ```javascript
- Ember.Handlebars.helper('capitalize', function(value) {
- return value.toUpperCase();
- });
- ```
+ if (type === 'ID' || !allowPrimitives) {
+ var context = (options.contexts && options.contexts[i]) || this,
+ normalized = normalizePath(context, params[i], options.data);
- The above bound helper can be used inside of templates as follows:
+ if (normalized.path === 'this') {
+ values.push(normalized.root);
+ } else {
+ values.push(handlebarsGet(normalized.root, normalized.path, options));
+ }
+ } else {
+ values.push(params[i]);
+ }
+ }
+
+ logger.apply(logger, values);
+});
+
+/**
+ Execute the `debugger` statement in the current context.
```handlebars
- {{capitalize name}}
+ {{debugger}}
```
- In this case, when the `name` property of the template's context changes,
- the rendered value of the helper will update to reflect this change.
-
- For more examples of bound helpers, see documentation for
- `Ember.Handlebars.registerBoundHelper`.
+ Before invoking the `debugger` statement, there
+ are a few helpful variables defined in the
+ body of this helper that you can inspect while
+ debugging that describe how and where this
+ helper was invoked:
- ## Custom view helper example
+ - templateContext: this is most likely a controller
+ from which this template looks up / displays properties
+ - typeOfTemplateContext: a string description of
+ what the templateContext is
- Assuming a view subclass named `App.CalendarView` were defined, a helper
- for rendering instances of this view could be registered as follows:
+ For example, if you're wondering why a value `{{foo}}`
+ isn't rendering as expected within a template, you
+ could place a `{{debugger}}` statement, and when
+ the `debugger;` breakpoint is hit, you can inspect
+ `templateContext`, determine if it's the object you
+ expect, and/or evaluate expressions in the console
+ to perform property lookups on the `templateContext`:
- ```javascript
- Ember.Handlebars.helper('calendar', App.CalendarView):
```
+ > templateContext.get('foo') // -> ""
+ ```
+
+ @method debugger
+ @for Ember.Handlebars.helpers
+ @param {String} property
+*/
+Ember.Handlebars.registerHelper('debugger', function debuggerHelper(options) {
- The above bound helper can be used inside of templates as follows:
+ // These are helpful values you can inspect while debugging.
+ var templateContext = this;
+ var typeOfTemplateContext = Ember.inspect(templateContext);
- ```handlebars
- {{calendar}}
- ```
+ debugger;
+});
- Which is functionally equivalent to:
- ```handlebars
- {{view App.CalendarView}}
- ```
- Options in the helper will be passed to the view in exactly the same
- manner as with the `view` helper.
+})();
- @method helper
- @for Ember.Handlebars
- @param {String} name
- @param {Function|Ember.View} function or view class constructor
- @param {String} dependentKeys*
-*/
-Ember.Handlebars.helper = function(name, value) {
- Ember.assert("You tried to register a component named '" + name + "', but component names must include a '-'", !Ember.Component.detect(value) || name.match(/-/));
- if (Ember.View.detect(value)) {
- Ember.Handlebars.registerHelper(name, function(options) {
- Ember.assert("You can only pass attributes (such as name=value) not bare values to a helper for a View", arguments.length < 2);
- return Ember.Handlebars.helpers.view.call(this, value, options);
- });
- } else {
- Ember.Handlebars.registerBoundHelper.apply(null, arguments);
- }
-};
+(function() {
/**
-@class helpers
-@namespace Ember.Handlebars
+@module ember
+@submodule ember-handlebars
*/
-Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
-
-/**
- Override the the opcode compiler and JavaScript compiler for Handlebars.
- @class Compiler
- @namespace Ember.Handlebars
- @private
- @constructor
-*/
-Ember.Handlebars.Compiler = function() {};
+var get = Ember.get, set = Ember.set;
+var fmt = Ember.String.fmt;
-// Handlebars.Compiler doesn't exist in runtime-only
-if (Handlebars.Compiler) {
- Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
-}
+Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
+ init: function() {
+ var itemController = get(this, 'itemController');
+ var binding;
-Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;
+ if (itemController) {
+ var controller = get(this, 'controller.container').lookupFactory('controller:array').create({
+ _isVirtual: true,
+ parentController: get(this, 'controller'),
+ itemController: itemController,
+ target: get(this, 'controller'),
+ _eachView: this
+ });
-/**
- @class JavaScriptCompiler
- @namespace Ember.Handlebars
- @private
- @constructor
-*/
-Ember.Handlebars.JavaScriptCompiler = function() {};
+ this.disableContentObservers(function() {
+ set(this, 'content', controller);
+ binding = new Ember.Binding('content', '_eachView.dataSource').oneWay();
+ binding.connect(controller);
+ });
-// Handlebars.JavaScriptCompiler doesn't exist in runtime-only
-if (Handlebars.JavaScriptCompiler) {
- Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
- Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
-}
+ set(this, '_arrayController', controller);
+ } else {
+ this.disableContentObservers(function() {
+ binding = new Ember.Binding('content', 'dataSource').oneWay();
+ binding.connect(this);
+ });
+ }
+ return this._super();
+ },
-Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";
+ _assertArrayLike: function(content) {
+ Ember.assert(fmt("The value that #each loops over must be an Array. You " +
+ "passed %@, but it should have been an ArrayController",
+ [content.constructor]),
+ !Ember.ControllerMixin.detect(content) ||
+ (content && content.isGenerated) ||
+ content instanceof Ember.ArrayController);
+ Ember.assert(fmt("The value that #each loops over must be an Array. You passed %@", [(Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? fmt("'%@' (wrapped in %@)", [content.get('model'), content]) : content]), Ember.Array.detect(content));
+ },
-Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
- return "''";
-};
+ disableContentObservers: function(callback) {
+ Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange');
+ Ember.removeObserver(this, 'content', null, '_contentDidChange');
-/**
- @private
+ callback.call(this);
- Override the default buffer for Ember Handlebars. By default, Handlebars
- creates an empty String at the beginning of each invocation and appends to
- it. Ember's Handlebars overrides this to append to a single shared buffer.
+ Ember.addBeforeObserver(this, 'content', null, '_contentWillChange');
+ Ember.addObserver(this, 'content', null, '_contentDidChange');
+ },
- @method appendToBuffer
- @param string {String}
-*/
-Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
- return "data.buffer.push("+string+");";
-};
+ itemViewClass: Ember._MetamorphView,
+ emptyViewClass: Ember._MetamorphView,
-var prefix = "ember" + (+new Date()), incr = 1;
+ createChildView: function(view, attrs) {
+ view = this._super(view, attrs);
-/**
- @private
+ // At the moment, if a container view subclass wants
+ // to insert keywords, it is responsible for cloning
+ // the keywords hash. This will be fixed momentarily.
+ var keyword = get(this, 'keyword');
+ var content = get(view, 'content');
- Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
- all simple mustaches in Ember's Handlebars will also set up an observer to
- keep the DOM up to date when the underlying property changes.
+ if (keyword) {
+ var data = get(view, 'templateData');
- @method mustache
- @for Ember.Handlebars.Compiler
- @param mustache
-*/
-Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
- if (mustache.isHelper && mustache.id.string === 'control') {
- mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
- mustache.hash.pairs.push(["controlID", new Handlebars.AST.StringNode(prefix + incr++)]);
- } else if (mustache.params.length || mustache.hash) {
- // no changes required
- } else {
- var id = new Handlebars.AST.IdNode([{ part: '_triageMustache' }]);
+ data = Ember.copy(data);
+ data.keywords = view.cloneKeywords();
+ set(view, 'templateData', data);
- // Update the mustache node to include a hash value indicating whether the original node
- // was escaped. This will allow us to properly escape values when the underlying value
- // changes and we need to re-render the value.
- if (!mustache.escaped) {
- mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
- mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
+ // In this case, we do not bind, because the `content` of
+ // a #each item cannot change.
+ data.keywords[keyword] = content;
}
- mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
- }
- return Handlebars.Compiler.prototype.mustache.call(this, mustache);
-};
+ // If {{#each}} is looping over an array of controllers,
+ // point each child view at their respective controller.
+ if (content && content.isController) {
+ set(view, 'controller', content);
+ }
-/**
- Used for precompilation of Ember Handlebars templates. This will not be used
- during normal app execution.
+ return view;
+ },
- @method precompile
- @for Ember.Handlebars
- @static
- @param {String} string The template to precompile
-*/
-Ember.Handlebars.precompile = function(string) {
- var ast = Handlebars.parse(string);
+ destroy: function() {
+ if (!this._super()) { return; }
- var options = {
- knownHelpers: {
- action: true,
- unbound: true,
- bindAttr: true,
- template: true,
- view: true,
- _triageMustache: true
- },
- data: true,
- stringParams: true
- };
+ var arrayController = get(this, '_arrayController');
- var environment = new Ember.Handlebars.Compiler().compile(ast, options);
- return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
-};
+ if (arrayController) {
+ arrayController.destroy();
+ }
-// We don't support this for Handlebars runtime-only
-if (Handlebars.compile) {
- /**
- The entry point for Ember Handlebars. This replaces the default
- `Handlebars.compile` and turns on template-local data and String
- parameters.
+ return this;
+ }
+});
- @method compile
- @for Ember.Handlebars
- @static
- @param {String} string The template to compile
- @return {Function}
- */
- Ember.Handlebars.compile = function(string) {
- var ast = Handlebars.parse(string);
- var options = { data: true, stringParams: true };
- var environment = new Ember.Handlebars.Compiler().compile(ast, options);
- var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
+// Defeatureify doesn't seem to like nested functions that need to be removed
+function _addMetamorphCheck() {
+ Ember.Handlebars.EachView.reopen({
+ _checkMetamorph: Ember.on('didInsertElement', function() {
+ Ember.assert("The metamorph tags, " +
+ this.morph.start + " and " + this.morph.end +
+ ", have different parents.\nThe browser has fixed your template to output valid HTML (for example, check that you have properly closed all tags and have used a TBODY tag when creating a table with '{{#each}}')",
+ document.getElementById( this.morph.start ).parentNode ===
+ document.getElementById( this.morph.end ).parentNode
+ );
+ })
+ });
+}
- var template = Ember.Handlebars.template(templateSpec);
- template.isMethod = false; //Make sure we don't wrap templates with ._super
+Ember.runInDebug( function() {
+ _addMetamorphCheck();
+});
- return template;
- };
-}
+var GroupedEach = Ember.Handlebars.GroupedEach = function(context, path, options) {
+ var self = this,
+ normalized = Ember.Handlebars.normalizePath(context, path, options.data);
+ this.context = context;
+ this.path = path;
+ this.options = options;
+ this.template = options.fn;
+ this.containingView = options.data.view;
+ this.normalizedRoot = normalized.root;
+ this.normalizedPath = normalized.path;
+ this.content = this.lookupContent();
-})();
+ this.addContentObservers();
+ this.addArrayObservers();
-(function() {
-var slice = Array.prototype.slice;
+ this.containingView.on('willClearRender', function() {
+ self.destroy();
+ });
+};
-/**
- @private
+GroupedEach.prototype = {
+ contentWillChange: function() {
+ this.removeArrayObservers();
+ },
- If a path starts with a reserved keyword, returns the root
- that should be used.
+ contentDidChange: function() {
+ this.content = this.lookupContent();
+ this.addArrayObservers();
+ this.rerenderContainingView();
+ },
- @method normalizePath
- @for Ember
- @param root {Object}
- @param path {String}
- @param data {Hash}
-*/
-var normalizePath = Ember.Handlebars.normalizePath = function(root, path, data) {
- var keywords = (data && data.keywords) || {},
- keyword, isKeyword;
+ contentArrayWillChange: Ember.K,
- // Get the first segment of the path. For example, if the
- // path is "foo.bar.baz", returns "foo".
- keyword = path.split('.', 1)[0];
+ contentArrayDidChange: function() {
+ this.rerenderContainingView();
+ },
- // Test to see if the first path is a keyword that has been
- // passed along in the view's data hash. If so, we will treat
- // that object as the new root.
- if (keywords.hasOwnProperty(keyword)) {
- // Look up the value in the template's data hash.
- root = keywords[keyword];
- isKeyword = true;
+ lookupContent: function() {
+ return Ember.Handlebars.get(this.normalizedRoot, this.normalizedPath, this.options);
+ },
- // Handle cases where the entire path is the reserved
- // word. In that case, return the object itself.
- if (path === keyword) {
- path = '';
- } else {
- // Strip the keyword from the path and look up
- // the remainder from the newly found root.
- path = path.substr(keyword.length+1);
- }
- }
+ addArrayObservers: function() {
+ if (!this.content) { return; }
- return { root: root, path: path, isKeyword: isKeyword };
-};
+ this.content.addArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
+ removeArrayObservers: function() {
+ if (!this.content) { return; }
-/**
- Lookup both on root and on window. If the path starts with
- a keyword, the corresponding object will be looked up in the
- template's data hash and used to resolve the path.
+ this.content.removeArrayObserver(this, {
+ willChange: 'contentArrayWillChange',
+ didChange: 'contentArrayDidChange'
+ });
+ },
- @method get
- @for Ember.Handlebars
- @param {Object} root The object to look up the property on
- @param {String} path The path to be lookedup
- @param {Object} options The template's option hash
-*/
-var handlebarsGet = Ember.Handlebars.get = function(root, path, options) {
- var data = options && options.data,
- normalizedPath = normalizePath(root, path, data),
- value;
+ addContentObservers: function() {
+ Ember.addBeforeObserver(this.normalizedRoot, this.normalizedPath, this, this.contentWillChange);
+ Ember.addObserver(this.normalizedRoot, this.normalizedPath, this, this.contentDidChange);
+ },
- // In cases where the path begins with a keyword, change the
- // root to the value represented by that keyword, and ensure
- // the path is relative to it.
- root = normalizedPath.root;
- path = normalizedPath.path;
+ removeContentObservers: function() {
+ Ember.removeBeforeObserver(this.normalizedRoot, this.normalizedPath, this.contentWillChange);
+ Ember.removeObserver(this.normalizedRoot, this.normalizedPath, this.contentDidChange);
+ },
- value = Ember.get(root, path);
+ render: function() {
+ if (!this.content) { return; }
- // If the path starts with a capital letter, look it up on Ember.lookup,
- // which defaults to the `window` object in browsers.
- if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
- value = Ember.get(Ember.lookup, path);
- }
- return value;
-};
-Ember.Handlebars.getPath = Ember.deprecateFunc('`Ember.Handlebars.getPath` has been changed to `Ember.Handlebars.get` for consistency.', Ember.Handlebars.get);
+ var content = this.content,
+ contentLength = get(content, 'length'),
+ data = this.options.data,
+ template = this.template;
-Ember.Handlebars.resolveParams = function(context, params, options) {
- var resolvedParams = [], types = options.types, param, type;
+ data.insideEach = true;
+ for (var i = 0; i < contentLength; i++) {
+ template(content.objectAt(i), { data: data });
+ }
+ },
- for (var i=0, l=params.length; iSorry, nobody is available for this task.
+ {{/each}}
+ ```
+ ### Specifying a View class for items
+ If you provide an `itemViewClass` option that references a view class
+ with its own `template` you can omit the block.
- error = "%@ Handlebars error: Could not find property '%@' on object %@.";
- if (options.data) {
- view = options.data.view;
- }
- throw new Ember.Error(Ember.String.fmt(error, [view, path, this]));
-});
+ The following template:
-/**
- Register a bound handlebars helper. Bound helpers behave similarly to regular
- handlebars helpers, with the added ability to re-render when the underlying data
- changes.
+ ```handlebars
+ {{#view App.MyView }}
+ {{each view.items itemViewClass="App.AnItemView"}}
+ {{/view}}
+ ```
- ## Simple example
+ And application code
```javascript
- Ember.Handlebars.registerBoundHelper('capitalize', function(value) {
- return value.toUpperCase();
+ App = Ember.Application.create({
+ MyView: Ember.View.extend({
+ items: [
+ Ember.Object.create({name: 'Dave'}),
+ Ember.Object.create({name: 'Mary'}),
+ Ember.Object.create({name: 'Sara'})
+ ]
+ })
+ });
+
+ App.AnItemView = Ember.View.extend({
+ template: Ember.Handlebars.compile("Greetings {{name}}")
});
```
- The above bound helper can be used inside of templates as follows:
+ Will result in the HTML structure below
- ```handlebars
- {{capitalize name}}
+ ```html
+
+
Greetings Dave
+
Greetings Mary
+
Greetings Sara
+
```
- In this case, when the `name` property of the template's context changes,
- the rendered value of the helper will update to reflect this change.
+ If an `itemViewClass` is defined on the helper, and therefore the helper is not
+ being used as a block, an `emptyViewClass` can also be provided optionally.
+ The `emptyViewClass` will match the behavior of the `{{else}}` condition
+ described above. That is, the `emptyViewClass` will render if the collection
+ is empty.
- ## Example with options
+ ### Representing each item with a Controller.
+ By default the controller lookup within an `{{#each}}` block will be
+ the controller of the template where the `{{#each}}` was used. If each
+ item needs to be presented by a custom controller you can provide a
+ `itemController` option which references a controller by lookup name.
+ Each item in the loop will be wrapped in an instance of this controller
+ and the item itself will be set to the `content` property of that controller.
- Like normal handlebars helpers, bound helpers have access to the options
- passed into the helper call.
+ This is useful in cases where properties of model objects need transformation
+ or synthesis for display:
```javascript
- Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
- var count = options.hash.count;
- var a = [];
- while(a.length < count) {
- a.push(value);
- }
- return a.join('');
- });
+ App.DeveloperController = Ember.ObjectController.extend({
+ isAvailableForHire: function() {
+ return !this.get('content.isEmployed') && this.get('content.isSeekingWork');
+ }.property('isEmployed', 'isSeekingWork')
+ })
```
- This helper could be used in a template as follows:
-
```handlebars
- {{repeat text count=3}}
+ {{#each person in developers itemController="developer"}}
+ {{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
+ {{/each}}
```
- ## Example with bound options
+ Each itemController will receive a reference to the current controller as
+ a `parentController` property.
+
+ ### (Experimental) Grouped Each
- Bound hash options are also supported. Example:
+ When used in conjunction with the experimental [group helper](https://github.com/emberjs/group-helper),
+ you can inform Handlebars to re-render an entire group of items instead of
+ re-rendering them one at a time (in the event that they are changed en masse
+ or an item is added/removed).
```handlebars
- {{repeat text countBinding="numRepeats"}}
+ {{#group}}
+ {{#each people}}
+ {{firstName}} {{lastName}}
+ {{/each}}
+ {{/group}}
```
- In this example, count will be bound to the value of
- the `numRepeats` property on the context. If that property
- changes, the helper will be re-rendered.
-
- ## Example with extra dependencies
+ This can be faster than the normal way that Handlebars re-renders items
+ in some cases.
- The `Ember.Handlebars.registerBoundHelper` method takes a variable length
- third parameter which indicates extra dependencies on the passed in value.
- This allows the handlebars helper to update when these dependencies change.
+ If for some reason you have a group with more than one `#each`, you can make
+ one of the collections be updated in normal (non-grouped) fashion by setting
+ the option `groupedRows=true` (counter-intuitive, I know).
- ```javascript
- Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
- return value.get('name').toUpperCase();
- }, 'name');
- ```
+ For example,
- ## Example with multiple bound properties
+ ```handlebars
+ {{dealershipName}}
- `Ember.Handlebars.registerBoundHelper` supports binding to
- multiple properties, e.g.:
+ {{#group}}
+ {{#each dealers}}
+ {{firstName}} {{lastName}}
+ {{/each}}
- ```javascript
- Ember.Handlebars.registerBoundHelper('concatenate', function() {
- var values = Array.prototype.slice.call(arguments, 0, -1);
- return values.join('||');
- });
+ {{#each car in cars groupedRows=true}}
+ {{car.make}} {{car.model}} {{car.color}}
+ {{/each}}
+ {{/group}}
```
+ Any change to `dealershipName` or the `dealers` collection will cause the
+ entire group to be re-rendered. However, changes to the `cars` collection
+ will be re-rendered individually (as normal).
- Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
- `{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
- the helpr will re-render. Note that dependency keys cannot be
- using in conjunction with multi-property helpers, since it is ambiguous
- which property the dependent keys would belong to.
-
- ## Use with unbound helper
+ Note that `group` behavior is also disabled by specifying an `itemViewClass`.
- The `{{unbound}}` helper can be used with bound helper invocations
- to render them in their unbound form, e.g.
+ @method each
+ @for Ember.Handlebars.helpers
+ @param [name] {String} name for item (used with `in`)
+ @param [path] {String} path
+ @param [options] {Object} Handlebars key/value pairs of options
+ @param [options.itemViewClass] {String} a path to a view class used for each item
+ @param [options.itemController] {String} name of a controller to be created for each item
+ @param [options.groupedRows] {boolean} enable normal item-by-item rendering when inside a `#group` helper
+*/
+Ember.Handlebars.registerHelper('each', function eachHelper(path, options) {
+ if (arguments.length === 4) {
+ Ember.assert("If you pass more than one argument to the each helper, it must be in the form #each foo in bar", arguments[1] === "in");
- ```handlebars
- {{unbound capitalize name}}
- ```
+ var keywordName = arguments[0];
- In this example, if the name property changes, the helper
- will not re-render.
+ options = arguments[3];
+ path = arguments[2];
+ if (path === '') { path = "this"; }
- ## Use with blocks not supported
+ options.hash.keyword = keywordName;
+ }
- Bound helpers do not support use with Handlebars blocks or
- the addition of child views of any kind.
+ if (arguments.length === 1) {
+ options = path;
+ path = 'this';
+ }
- @method registerBoundHelper
- @for Ember.Handlebars
- @param {String} name
- @param {Function} function
- @param {String} dependentKeys*
-*/
-Ember.Handlebars.registerBoundHelper = function(name, fn) {
- var dependentKeys = slice.call(arguments, 2);
+ options.hash.dataSourceBinding = path;
+ // Set up emptyView as a metamorph with no tag
+ //options.hash.emptyViewClass = Ember._MetamorphView;
- function helper() {
- var properties = slice.call(arguments, 0, -1),
- numProperties = properties.length,
- options = arguments[arguments.length - 1],
- normalizedProperties = [],
- types = options.types,
- data = options.data,
- hash = options.hash,
- view = data.view,
- contexts = options.contexts,
- currentContext = (contexts && contexts.length) ? contexts[0] : this,
- prefixPathForDependentKeys = '',
- loc, len, hashOption,
- boundOption, property,
- normalizedValue = Ember._SimpleHandlebarsView.prototype.normalizedValue;
+ if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
+ new Ember.Handlebars.GroupedEach(this, path, options).render();
+ } else {
+ return Ember.Handlebars.helpers.collection.call(this, 'Ember.Handlebars.EachView', options);
+ }
+});
- Ember.assert("registerBoundHelper-generated helpers do not support use with Handlebars blocks.", !options.fn);
+})();
- // Detect bound options (e.g. countBinding="otherCount")
- var boundOptions = hash.boundOptions = {};
- for (hashOption in hash) {
- if (Ember.IS_BINDING.test(hashOption)) {
- // Lop off 'Binding' suffix.
- boundOptions[hashOption.slice(0, -7)] = hash[hashOption];
- }
- }
- // Expose property names on data.properties object.
- var watchedProperties = [];
- data.properties = [];
- for (loc = 0; loc < numProperties; ++loc) {
- data.properties.push(properties[loc]);
- if (types[loc] === 'ID') {
- var normalizedProp = normalizePath(currentContext, properties[loc], data);
- normalizedProperties.push(normalizedProp);
- watchedProperties.push(normalizedProp);
- } else {
- normalizedProperties.push(null);
- }
- }
- // Handle case when helper invocation is preceded by `unbound`, e.g.
- // {{unbound myHelper foo}}
- if (data.isUnbound) {
- return evaluateUnboundHelper(this, fn, normalizedProperties, options);
- }
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
- var bindView = new Ember._SimpleHandlebarsView(null, null, !options.hash.unescaped, options.data);
+/**
+ `template` allows you to render a template from inside another template.
+ This allows you to re-use the same template in multiple places. For example:
- // Override SimpleHandlebarsView's method for generating the view's content.
- bindView.normalizedValue = function() {
- var args = [], boundOption;
+ ```html
+
+ ```
- // Copy over bound hash options.
- for (boundOption in boundOptions) {
- if (!boundOptions.hasOwnProperty(boundOption)) { continue; }
- property = normalizePath(currentContext, boundOptions[boundOption], data);
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- hash[boundOption] = normalizedValue.call(bindView);
- }
+ ```html
+
+ ```
- for (loc = 0; loc < numProperties; ++loc) {
- property = normalizedProperties[loc];
- if (property) {
- bindView.path = property.path;
- bindView.pathRoot = property.root;
- args.push(normalizedValue.call(bindView));
- } else {
- args.push(properties[loc]);
- }
- }
- args.push(options);
+ ```handlebars
+ {{#if isUser}}
+ {{template "user_info"}}
+ {{else}}
+ {{template "unlogged_user_info"}}
+ {{/if}}
+ ```
- // Run the supplied helper function.
- return fn.apply(currentContext, args);
- };
+ This helper looks for templates in the global `Ember.TEMPLATES` hash. If you
+ add `
+ ```
+
+ Take note that `"welcome"` is a string and not an object
+ reference.
+
+ @method loc
+ @for Ember.Handlebars.helpers
+ @param {String} str The string to format
*/
-Ember._SimpleMetamorphView = Ember.CoreView.extend(Ember._Metamorph);
+Ember.Handlebars.registerHelper('loc', function locHelper(str) {
+ return Ember.String.loc(str);
+});
})();
(function() {
-/*globals Handlebars */
-/*jshint newcap:false*/
-/**
-@module ember
-@submodule ember-handlebars
-*/
-var get = Ember.get, set = Ember.set, handlebarsGet = Ember.Handlebars.get;
-var Metamorph = requireModule('metamorph');
-function SimpleHandlebarsView(path, pathRoot, isEscaped, templateData) {
- this.path = path;
- this.pathRoot = pathRoot;
- this.isEscaped = isEscaped;
- this.templateData = templateData;
+})();
- this.morph = Metamorph();
- this.state = 'preRender';
- this.updateId = null;
- this._parentView = null;
- this.buffer = null;
-}
-Ember._SimpleHandlebarsView = SimpleHandlebarsView;
-SimpleHandlebarsView.prototype = {
- isVirtual: true,
- isView: true,
+(function() {
- destroy: function () {
- if (this.updateId) {
- Ember.run.cancel(this.updateId);
- this.updateId = null;
- }
- if (this._parentView) {
- this._parentView.removeChild(this);
- }
- this.morph = null;
- this.state = 'destroyed';
- },
+})();
- propertyWillChange: Ember.K,
- propertyDidChange: Ember.K,
- normalizedValue: function() {
- var path = this.path,
- pathRoot = this.pathRoot,
- result, templateData;
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
- // Use the pathRoot as the result if no path is provided. This
- // happens if the path is `this`, which gets normalized into
- // a `pathRoot` of the current Handlebars context and a path
- // of `''`.
- if (path === '') {
- result = pathRoot;
- } else {
- templateData = this.templateData;
- result = handlebarsGet(pathRoot, path, { data: templateData });
- }
+var set = Ember.set, get = Ember.get;
- return result;
- },
+/**
+ The internal class used to create text inputs when the `{{input}}`
+ helper is used with `type` of `checkbox`.
- renderToBuffer: function(buffer) {
- var string = '';
+ See [handlebars.helpers.input](/api/classes/Ember.Handlebars.helpers.html#method_input) for usage details.
- string += this.morph.startTag();
- string += this.render();
- string += this.morph.endTag();
+ ## Direct manipulation of `checked`
- buffer.push(string);
- },
+ The `checked` attribute of an `Ember.Checkbox` object should always be set
+ through the Ember object or by interacting with its rendered element
+ representation via the mouse, keyboard, or touch. Updating the value of the
+ checkbox via jQuery will result in the checked value of the object and its
+ element losing synchronization.
- render: function() {
- // If not invoked via a triple-mustache ({{{foo}}}), escape
- // the content of the template.
- var escape = this.isEscaped;
- var result = this.normalizedValue();
+ ## Layout and LayoutName properties
- if (result === null || result === undefined) {
- result = "";
- } else if (!(result instanceof Handlebars.SafeString)) {
- result = String(result);
- }
+ Because HTML `input` elements are self closing `layout` and `layoutName`
+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
- if (escape) { result = Handlebars.Utils.escapeExpression(result); }
- return result;
- },
+ @class Checkbox
+ @namespace Ember
+ @extends Ember.View
+*/
+Ember.Checkbox = Ember.View.extend({
+ classNames: ['ember-checkbox'],
- rerender: function() {
- switch(this.state) {
- case 'preRender':
- case 'destroyed':
- break;
- case 'inBuffer':
- throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM.");
- case 'hasElement':
- case 'inDOM':
- this.updateId = Ember.run.scheduleOnce('render', this, 'update');
- break;
- }
+ tagName: 'input',
- return this;
+ attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name',
+ 'autofocus', 'form'],
+
+ type: "checkbox",
+ checked: false,
+ disabled: false,
+ indeterminate: false,
+
+ init: function() {
+ this._super();
+ this.on("change", this, this._updateElementValue);
},
- update: function () {
- this.updateId = null;
- this.morph.html(this.render());
+ didInsertElement: function() {
+ this._super();
+ this.get('element').indeterminate = !!this.get('indeterminate');
},
- transitionTo: function(state) {
- this.state = state;
+ _updateElementValue: function() {
+ set(this, 'checked', this.$().prop('checked'));
}
-};
+});
-var states = Ember.View.cloneStates(Ember.View.states), merge = Ember.merge;
+})();
-merge(states._default, {
- rerenderIfNeeded: Ember.K
-});
-merge(states.inDOM, {
- rerenderIfNeeded: function(view) {
- if (view.normalizedValue() !== view._lastNormalizedValue) {
- view.rerender();
- }
- }
-});
+(function() {
/**
- `Ember._HandlebarsBoundView` is a private view created by the Handlebars
- `{{bind}}` helpers that is used to keep track of bound properties.
+@module ember
+@submodule ember-handlebars
+*/
- Every time a property is bound using a `{{mustache}}`, an anonymous subclass
- of `Ember._HandlebarsBoundView` is created with the appropriate sub-template
- and context set up. When the associated property changes, just the template
- for this view will re-render.
+var get = Ember.get, set = Ember.set;
- @class _HandlebarsBoundView
+/**
+ Shared mixin used by `Ember.TextField` and `Ember.TextArea`.
+
+ @class TextSupport
@namespace Ember
- @extends Ember._MetamorphView
+ @uses Ember.TargetActionSupport
+ @extends Ember.Mixin
@private
*/
-Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
- instrumentName: 'boundHandlebars',
- states: states,
+Ember.TextSupport = Ember.Mixin.create(Ember.TargetActionSupport, {
+ value: "",
+
+ attributeBindings: ['placeholder', 'disabled', 'maxlength', 'tabindex', 'readonly',
+ 'autofocus', 'form', 'selectionDirection', 'spellcheck', 'required'],
+ placeholder: null,
+ disabled: false,
+ maxlength: null,
+
+ init: function() {
+ this._super();
+ this.on("focusOut", this, this._elementValueDidChange);
+ this.on("change", this, this._elementValueDidChange);
+ this.on("paste", this, this._elementValueDidChange);
+ this.on("cut", this, this._elementValueDidChange);
+ this.on("input", this, this._elementValueDidChange);
+ this.on("keyUp", this, this.interpretKeyEvents);
+ },
/**
- The function used to determine if the `displayTemplate` or
- `inverseTemplate` should be rendered. This should be a function that takes
- a value and returns a Boolean.
+ The action to be sent when the user presses the return key.
- @property shouldDisplayFunc
- @type Function
+ This is similar to the `{{action}}` helper, but is fired when
+ the user presses the return key when editing a text field, and sends
+ the value of the field as the context.
+
+ @property action
+ @type String
@default null
*/
- shouldDisplayFunc: null,
+ action: null,
/**
- Whether the template rendered by this view gets passed the context object
- of its parent template, or gets passed the value of retrieving `path`
- from the `pathRoot`.
+ The event that should send the action.
- For example, this is true when using the `{{#if}}` helper, because the
- template inside the helper should look up properties relative to the same
- object as outside the block. This would be `false` when used with `{{#with
- foo}}` because the template should receive the object found by evaluating
- `foo`.
+ Options are:
- @property preserveContext
+ * `enter`: the user pressed enter
+ * `keyPress`: the user pressed a key
+
+ @property onEvent
+ @type String
+ @default enter
+ */
+ onEvent: 'enter',
+
+ /**
+ Whether they `keyUp` event that triggers an `action` to be sent continues
+ propagating to other views.
+
+ By default, when the user presses the return key on their keyboard and
+ the text field has an `action` set, the action will be sent to the view's
+ controller and the key event will stop propagating.
+
+ If you would like parent views to receive the `keyUp` event even after an
+ action has been dispatched, set `bubbles` to true.
+
+ @property bubbles
@type Boolean
@default false
*/
- preserveContext: false,
+ bubbles: false,
+
+ interpretKeyEvents: function(event) {
+ var map = Ember.TextSupport.KEY_EVENTS;
+ var method = map[event.keyCode];
+
+ this._elementValueDidChange();
+ if (method) { return this[method](event); }
+ },
+
+ _elementValueDidChange: function() {
+ set(this, 'value', this.$().val());
+ },
/**
- If `preserveContext` is true, this is the object that will be used
- to render the template.
+ The action to be sent when the user inserts a new line.
- @property previousContext
- @type Object
+ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 13.
+ Uses sendAction to send the `enter` action to the controller.
+
+ @method insertNewline
+ @param {Event} event
*/
- previousContext: null,
+ insertNewline: function(event) {
+ sendAction('enter', this, event);
+ sendAction('insert-newline', this, event);
+ },
/**
- The template to render when `shouldDisplayFunc` evaluates to `true`.
+ Called when the user hits escape.
- @property displayTemplate
- @type Function
- @default null
+ Called by the `Ember.TextSupport` mixin on keyUp if keycode matches 27.
+ Uses sendAction to send the `escape-press` action to the controller.
+
+ @method cancel
+ @param {Event} event
*/
- displayTemplate: null,
+ cancel: function(event) {
+ sendAction('escape-press', this, event);
+ },
/**
- The template to render when `shouldDisplayFunc` evaluates to `false`.
+ Called when the text area is focused.
- @property inverseTemplate
- @type Function
- @default null
+ @method focusIn
+ @param {Event} event
*/
- inverseTemplate: null,
-
+ focusIn: function(event) {
+ sendAction('focus-in', this, event);
+ },
/**
- The path to look up on `pathRoot` that is passed to
- `shouldDisplayFunc` to determine which template to render.
-
- In addition, if `preserveContext` is `false,` the object at this path will
- be passed to the template when rendering.
+ Called when the text area is blurred.
- @property path
- @type String
- @default null
+ @method focusOut
+ @param {Event} event
*/
- path: null,
+ focusOut: function(event) {
+ sendAction('focus-out', this, event);
+ },
/**
- The object from which the `path` will be looked up. Sometimes this is the
- same as the `previousContext`, but in cases where this view has been
- generated for paths that start with a keyword such as `view` or
- `controller`, the path root will be that resolved object.
+ The action to be sent when the user presses a key. Enabled by setting
+ the `onEvent` property to `keyPress`.
- @property pathRoot
- @type Object
+ Uses sendAction to send the `keyPress` action to the controller.
+
+ @method keyPress
+ @param {Event} event
*/
- pathRoot: null,
+ keyPress: function(event) {
+ sendAction('key-press', this, event);
+ }
- normalizedValue: function() {
- var path = get(this, 'path'),
- pathRoot = get(this, 'pathRoot'),
- valueNormalizer = get(this, 'valueNormalizerFunc'),
- result, templateData;
+});
- // Use the pathRoot as the result if no path is provided. This
- // happens if the path is `this`, which gets normalized into
- // a `pathRoot` of the current Handlebars context and a path
- // of `''`.
- if (path === '') {
- result = pathRoot;
- } else {
- templateData = get(this, 'templateData');
- result = handlebarsGet(pathRoot, path, { data: templateData });
+Ember.TextSupport.KEY_EVENTS = {
+ 13: 'insertNewline',
+ 27: 'cancel'
+};
+
+// In principle, this shouldn't be necessary, but the legacy
+// sectionAction semantics for TextField are different from
+// the component semantics so this method normalizes them.
+function sendAction(eventName, view, event) {
+ var action = get(view, eventName),
+ on = get(view, 'onEvent'),
+ value = get(view, 'value');
+
+ // back-compat support for keyPress as an event name even though
+ // it's also a method name that consumes the event (and therefore
+ // incompatible with sendAction semantics).
+ if (on === eventName || (on === 'keyPress' && eventName === 'key-press')) {
+ view.sendAction('action', value);
+ }
+
+ view.sendAction(eventName, value);
+
+ if (action || on === eventName) {
+ if(!get(view, 'bubbles')) {
+ event.stopPropagation();
}
+ }
+}
- return valueNormalizer ? valueNormalizer(result) : result;
- },
+})();
- rerenderIfNeeded: function() {
- this.currentState.rerenderIfNeeded(this);
- },
- /**
- Determines which template to invoke, sets up the correct state based on
- that logic, then invokes the default `Ember.View` `render` implementation.
- This method will first look up the `path` key on `pathRoot`,
- then pass that value to the `shouldDisplayFunc` function. If that returns
- `true,` the `displayTemplate` function will be rendered to DOM. Otherwise,
- `inverseTemplate`, if specified, will be rendered.
+(function() {
+/**
+@module ember
+@submodule ember-handlebars
+*/
- For example, if this `Ember._HandlebarsBoundView` represented the `{{#with
- foo}}` helper, it would look up the `foo` property of its context, and
- `shouldDisplayFunc` would always return true. The object found by looking
- up `foo` would be passed to `displayTemplate`.
+var get = Ember.get, set = Ember.set;
- @method render
- @param {Ember.RenderBuffer} buffer
+/**
+
+ 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.
+
+ ## Layout and LayoutName properties
+
+ Because HTML `input` elements are self closing `layout` and `layoutName`
+ properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
+
+ @class TextField
+ @namespace Ember
+ @extends Ember.Component
+ @uses Ember.TextSupport
+*/
+Ember.TextField = Ember.Component.extend(Ember.TextSupport, {
+
+ classNames: ['ember-text-field'],
+ tagName: "input",
+ attributeBindings: ['type', 'value', 'size', 'pattern', 'name', 'min', 'max',
+ 'accept', 'autocomplete', 'autosave', 'formaction',
+ 'formenctype', 'formmethod', 'formnovalidate', 'formtarget',
+ 'height', 'inputmode', 'list', 'multiple', 'pattern', 'step',
+ 'width'],
+
+ /**
+ The `value` attribute of the input element. As the user inputs text, this
+ property is updated live.
+
+ @property value
+ @type String
+ @default ""
*/
- render: function(buffer) {
- // If not invoked via a triple-mustache ({{{foo}}}), escape
- // the content of the template.
- var escape = get(this, 'isEscaped');
+ value: "",
- var shouldDisplay = get(this, 'shouldDisplayFunc'),
- preserveContext = get(this, 'preserveContext'),
- context = get(this, 'previousContext');
+ /**
+ The `type` attribute of the input element.
- var inverseTemplate = get(this, 'inverseTemplate'),
- displayTemplate = get(this, 'displayTemplate');
+ @property type
+ @type String
+ @default "text"
+ */
+ type: "text",
- var result = this.normalizedValue();
- this._lastNormalizedValue = result;
+ /**
+ The `size` of the text field in characters.
- // First, test the conditional to see if we should
- // render the template or not.
- if (shouldDisplay(result)) {
- set(this, 'template', displayTemplate);
+ @property size
+ @type String
+ @default null
+ */
+ size: null,
- // If we are preserving the context (for example, if this
- // is an #if block, call the template with the same object.
- if (preserveContext) {
- set(this, '_context', context);
- } else {
- // Otherwise, determine if this is a block bind or not.
- // If so, pass the specified object to the template
- if (displayTemplate) {
- set(this, '_context', result);
- } else {
- // This is not a bind block, just push the result of the
- // expression to the render context and return.
- if (result === null || result === undefined) {
- result = "";
- } else if (!(result instanceof Handlebars.SafeString)) {
- result = String(result);
- }
+ /**
+ The `pattern` attribute of input element.
- if (escape) { result = Handlebars.Utils.escapeExpression(result); }
- buffer.push(result);
- return;
- }
- }
- } else if (inverseTemplate) {
- set(this, 'template', inverseTemplate);
+ @property pattern
+ @type String
+ @default null
+ */
+ pattern: null,
- if (preserveContext) {
- set(this, '_context', context);
- } else {
- set(this, '_context', result);
- }
- } else {
- set(this, 'template', function() { return ''; });
- }
+ /**
+ The `min` attribute of input element used with `type="number"` or `type="range"`.
+
+ @property min
+ @type String
+ @default null
+ */
+ min: null,
- return this._super(buffer);
- }
+ /**
+ The `max` attribute of input element used with `type="number"` or `type="range"`.
+
+ @property max
+ @type String
+ @default null
+ */
+ max: null
});
})();
@@ -25142,1252 +31053,1092 @@ Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
@submodule ember-handlebars
*/
-var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
-var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
-var forEach = Ember.ArrayPolyfills.forEach;
-
-var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
-
-function exists(value) {
- return !Ember.isNone(value);
-}
-
-// Binds a property into the DOM. This will create a hook in DOM that the
-// KVO system will look for and update if the property changes.
-function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
- var data = options.data,
- fn = options.fn,
- inverse = options.inverse,
- view = data.view,
- currentContext = this,
- normalized, observer, i;
-
- normalized = normalizePath(currentContext, property, data);
+var get = Ember.get, set = Ember.set;
- // Set up observers for observable objects
- if ('object' === typeof this) {
- if (data.insideGroup) {
- observer = function() {
- Ember.run.once(view, 'rerender');
- };
+/**
+ The internal class used to create textarea element when the `{{textarea}}`
+ helper is used.
- var template, context, result = handlebarsGet(currentContext, property, options);
+ See [handlebars.helpers.textarea](/api/classes/Ember.Handlebars.helpers.html#method_textarea) for usage details.
- result = valueNormalizer ? valueNormalizer(result) : result;
+ ## Layout and LayoutName properties
- context = preserveContext ? currentContext : result;
- if (shouldDisplay(result)) {
- template = fn;
- } else if (inverse) {
- template = inverse;
- }
+ Because HTML `textarea` elements do not contain inner HTML the `layout` and
+ `layoutName` properties will not be applied. See [Ember.View](/api/classes/Ember.View.html)'s
+ layout section for more information.
- template(context, { data: options.data });
- } else {
- // Create the view that will wrap the output of this template/property
- // and add it to the nearest view's childViews array.
- // See the documentation of Ember._HandlebarsBoundView for more.
- var bindView = view.createChildView(Ember._HandlebarsBoundView, {
- preserveContext: preserveContext,
- shouldDisplayFunc: shouldDisplay,
- valueNormalizerFunc: valueNormalizer,
- displayTemplate: fn,
- inverseTemplate: inverse,
- path: property,
- pathRoot: currentContext,
- previousContext: currentContext,
- isEscaped: !options.hash.unescaped,
- templateData: options.data
- });
+ @class TextArea
+ @namespace Ember
+ @extends Ember.Component
+ @uses Ember.TextSupport
+*/
+Ember.TextArea = Ember.Component.extend(Ember.TextSupport, {
+ classNames: ['ember-text-area'],
- view.appendChild(bindView);
+ tagName: "textarea",
+ attributeBindings: ['rows', 'cols', 'name', 'selectionEnd', 'selectionStart', 'wrap'],
+ rows: null,
+ cols: null,
- observer = function() {
- Ember.run.scheduleOnce('render', bindView, 'rerenderIfNeeded');
- };
+ _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);
}
+ }),
- // Observes the given property on the context and
- // tells the Ember._HandlebarsBoundView to re-render. If property
- // is an empty string, we are printing the current context
- // object ({{this}}) so updating it is not our responsibility.
- if (normalized.path !== '') {
- view.registerObserver(normalized.root, normalized.path, observer);
- if (childProperties) {
- for (i=0; i -1;
+ } else {
+ // Primitives get passed through bindings as objects... since
+ // `new Number(4) !== 4`, we use `==` below
+ return content == selection;
+ }
+ }).property('content', 'parentView.selection'),
- if (!options.fn) {
- return simpleBind(context, property, options);
- }
+ labelPathDidChange: Ember.observer('parentView.optionLabelPath', function() {
+ var labelPath = get(this, 'parentView.optionLabelPath');
- return bind.call(context, property, options, false, exists);
-});
+ if (!labelPath) { return; }
-/**
- @private
+ Ember.defineProperty(this, 'label', Ember.computed(function() {
+ return get(this, labelPath);
+ }).property(labelPath));
+ }),
- Use the `boundIf` helper to create a conditional that re-evaluates
- whenever the truthiness of the bound value changes.
+ valuePathDidChange: Ember.observer('parentView.optionValuePath', function() {
+ var valuePath = get(this, 'parentView.optionValuePath');
- ```handlebars
- {{#boundIf "content.shouldDisplayTitle"}}
- {{content.title}}
- {{/boundIf}}
- ```
+ if (!valuePath) { return; }
- @method boundIf
- @for Ember.Handlebars.helpers
- @param {String} property Property to bind
- @param {Function} fn Context to provide for rendering
- @return {String} HTML string
-*/
-EmberHandlebars.registerHelper('boundIf', function(property, fn) {
- var context = (fn.contexts && fn.contexts.length) ? fn.contexts[0] : this;
- var func = function(result) {
- var truthy = result && get(result, 'isTruthy');
- if (typeof truthy === 'boolean') { return truthy; }
+ Ember.defineProperty(this, 'value', Ember.computed(function() {
+ return get(this, valuePath);
+ }).property(valuePath));
+ })
+});
- if (Ember.isArray(result)) {
- return get(result, 'length') !== 0;
- } else {
- return !!result;
- }
- };
+Ember.SelectOptgroup = Ember.CollectionView.extend({
+ tagName: 'optgroup',
+ attributeBindings: ['label'],
+
+ selectionBinding: 'parentView.selection',
+ multipleBinding: 'parentView.multiple',
+ optionLabelPathBinding: 'parentView.optionLabelPath',
+ optionValuePathBinding: 'parentView.optionValuePath',
- return bind.call(context, property, fn, true, func, func, ['isTruthy', 'length']);
+ itemViewClassBinding: 'parentView.optionView'
});
/**
- @method with
- @for Ember.Handlebars.helpers
- @param {Function} context
- @param {Hash} options
- @return {String} HTML string
-*/
-EmberHandlebars.registerHelper('with', function(context, options) {
- if (arguments.length === 4) {
- var keywordName, path, rootPath, normalized;
+ The `Ember.Select` view class renders a
+ [select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
+ allowing the user to choose from a list of options.
- Ember.assert("If you pass more than one argument to the with helper, it must be in the form #with foo as bar", arguments[1] === "as");
- options = arguments[3];
- keywordName = arguments[2];
- path = arguments[0];
+ The text and `value` property of each `