/*global exports:true _ log4javascript JSON:true*/ (function () {// Backbone.js 1.1.0 // (c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc. // (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org (function(root, factory) { // Set up Backbone appropriately for the environment. if (typeof exports !== 'undefined') { // Node/CommonJS, no need for jQuery in that case. factory(root, exports, require('underscore')); } else if (typeof define === 'function' && define.amd) { // AMD define('backbone',['underscore', 'jquery', 'exports'], function(_, $, exports) { // Export global even in AMD case in case this script is loaded with // others that may still expect a global Backbone. root.Backbone = factory(root, exports, _, $); }); } else { // Browser globals root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); } }(this, function(root, Backbone, _, $) { // Initial Setup // ------------- // Save the previous value of the `Backbone` variable, so that it can be // restored later on, if `noConflict` is used. var previousBackbone = root.Backbone; // Create local references to array methods we'll want to use later. var array = []; var push = array.push; var slice = array.slice; var splice = array.splice; // Current version of the library. Keep in sync with `package.json`. Backbone.VERSION = '1.1.0'; // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable. Backbone.$ = $; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. Backbone.noConflict = function() { root.Backbone = previousBackbone; return this; }; // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and // set a `X-Http-Method-Override` header. Backbone.emulateHTTP = false; // Turn on `emulateJSON` to support legacy servers that can't deal with direct // `application/json` requests ... will encode the body as // `application/x-www-form-urlencoded` instead and will send the model in a // form param named `model`. Backbone.emulateJSON = false; // Backbone.Events // --------------- // A module that can be mixed in to *any object* in order to provide it with // custom events. You may bind with `on` or remove with `off` callback // functions to an event; `trigger`-ing an event fires all callbacks in // succession. // // var object = {}; // _.extend(object, Backbone.Events); // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // var Events = Backbone.Events = { // Bind an event to a `callback` function. Passing `"all"` will bind // the callback to all events fired. on: function(name, callback, context) { if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; this._events || (this._events = {}); var events = this._events[name] || (this._events[name] = []); events.push({callback: callback, context: context, ctx: context || this}); return this; }, // Bind an event to only be triggered a single time. After the first time // the callback is invoked, it will be removed. once: function(name, callback, context) { if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; var self = this; var once = _.once(function() { self.off(name, once); callback.apply(this, arguments); }); once._callback = callback; return this.on(name, once, context); }, // Remove one or many callbacks. If `context` is null, removes all // callbacks with that function. If `callback` is null, removes all // callbacks for the event. If `name` is null, removes all bound // callbacks for all events. off: function(name, callback, context) { var retain, ev, events, names, i, l, j, k; if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; if (!name && !callback && !context) { this._events = {}; return this; } names = name ? [name] : _.keys(this._events); for (i = 0, l = names.length; i < l; i++) { name = names[i]; if (this._events[name]) { events = this._events[name]; this._events[name] = retain = []; if (callback || context) { for (j = 0, k = events.length; j < k; j++) { ev = events[j]; if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || (context && context !== ev.context)) { retain.push(ev); } } } if (!retain.length) delete this._events[name]; } } return this; }, // Trigger one or many events, firing all bound callbacks. Callbacks are // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). trigger: function(name) { if (!this._events) return this; var args = slice.call(arguments, 1); if (!eventsApi(this, 'trigger', name, args)) return this; var events = this._events[name]; var allEvents = this._events.all; if (events) triggerEvents(events, args); if (allEvents) triggerEvents(allEvents, arguments); return this; }, // Tell this object to stop listening to either specific events ... or // to every object it's currently listening to. stopListening: function(obj, name, callback) { var listeningTo = this._listeningTo; if (!listeningTo) return this; var remove = !name && !callback; if (!callback && typeof name === 'object') callback = this; if (obj) (listeningTo = {})[obj._listenId] = obj; for (var id in listeningTo) { obj = listeningTo[id]; obj.off(name, callback, this); if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id]; } return this; } }; // Regular expression used to split event strings. var eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event // names `"change blur"` and jQuery-style event maps `{change: action}` // in terms of the existing API. var eventsApi = function(obj, action, name, rest) { if (!name) return true; // Handle event maps. if (typeof name === 'object') { for (var key in name) { obj[action].apply(obj, [key, name[key]].concat(rest)); } return false; } // Handle space separated event names. if (eventSplitter.test(name)) { var names = name.split(eventSplitter); for (var i = 0, l = names.length; i < l; i++) { obj[action].apply(obj, [names[i]].concat(rest)); } return false; } return true; }; // A difficult-to-believe, but optimized internal dispatch function for // triggering events. Tries to keep the usual cases speedy (most internal // Backbone events have 3 arguments). var triggerEvents = function(events, args) { var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; switch (args.length) { case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); } }; var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; // Inversion-of-control versions of `on` and `once`. Tell *this* object to // listen to an event in another object ... keeping track of what it's // listening to. _.each(listenMethods, function(implementation, method) { Events[method] = function(obj, name, callback) { var listeningTo = this._listeningTo || (this._listeningTo = {}); var id = obj._listenId || (obj._listenId = _.uniqueId('l')); listeningTo[id] = obj; if (!callback && typeof name === 'object') callback = this; obj[implementation](name, callback, this); return this; }; }); // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; // Allow the `Backbone` object to serve as a global event bus, for folks who // want global "pubsub" in a convenient place. _.extend(Backbone, Events); // Backbone.Model // -------------- // Backbone **Models** are the basic data object in the framework -- // frequently representing a row in a table in a database on your server. // A discrete chunk of data and a bunch of useful, related methods for // performing computations and transformations on that data. // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var attrs = attributes || {}; options || (options = {}); this.cid = _.uniqueId('c'); this.attributes = {}; if (options.collection) this.collection = options.collection; if (options.parse) attrs = this.parse(attrs, options) || {}; attrs = _.defaults({}, attrs, _.result(this, 'defaults')); this.set(attrs, options); this.changed = {}; this.initialize.apply(this, arguments); }; // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, // The value returned during the last failed validation. validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. idAttribute: 'id', // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function() {}, // Return a copy of the model's `attributes` object. toJSON: function(options) { return _.clone(this.attributes); }, // Proxy `Backbone.sync` by default -- but override this if you need // custom syncing semantics for *this* particular model. sync: function() { return Backbone.sync.apply(this, arguments); }, // Get the value of an attribute. get: function(attr) { return this.attributes[attr]; }, // Get the HTML-escaped value of an attribute. escape: function(attr) { return _.escape(this.get(attr)); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has: function(attr) { return this.get(attr) != null; }, // Set a hash of model attributes on the object, firing `"change"`. This is // the core primitive operation of a model, updating the data and notifying // anyone who needs to know about the change in state. The heart of the beast. set: function(key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. if (typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options || (options = {}); // Run validation. if (!this._validate(attrs, options)) return false; // Extract attributes and options. unset = options.unset; silent = options.silent; changes = []; changing = this._changing; this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes); this.changed = {}; } current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; // For each `set` attribute, update or delete the current value. for (attr in attrs) { val = attrs[attr]; if (!_.isEqual(current[attr], val)) changes.push(attr); if (!_.isEqual(prev[attr], val)) { this.changed[attr] = val; } else { delete this.changed[attr]; } unset ? delete current[attr] : current[attr] = val; } // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = true; for (var i = 0, l = changes.length; i < l; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } // You might be wondering why there's a `while` loop here. Changes can // be recursively nested within `"change"` events. if (changing) return this; if (!silent) { while (this._pending) { this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }, // Remove an attribute from the model, firing `"change"`. `unset` is a noop // if the attribute doesn't exist. unset: function(attr, options) { return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, // Clear all attributes on the model, firing `"change"`. clear: function(options) { var attrs = {}; for (var key in this.attributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged: function(attr) { if (attr == null) return !_.isEmpty(this.changed); return _.has(this.changed, attr); }, // Return an object containing all the attributes that have changed, or // false if there are no changed attributes. Useful for determining what // parts of a view need to be updated and/or what attributes need to be // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. changedAttributes: function(diff) { if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false; var old = this._changing ? this._previousAttributes : this.attributes; for (var attr in diff) { if (_.isEqual(old[attr], (val = diff[attr]))) continue; (changed || (changed = {}))[attr] = val; } return changed; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. previous: function(attr) { if (attr == null || !this._previousAttributes) return null; return this._previousAttributes[attr]; }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes: function() { return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. fetch: function(options) { options = options ? _.clone(options) : {}; // Set the Authorization header for IE8 here so that it // is included in every API call options = this.setIEAuthHeader(options); if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { if (!model.set(model.parse(resp, options), options)) return false; if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, setIEAuthHeader: function (options) { var authHeader, browser = require('chiropractor/browser'); if (browser.isIE8or9) { if ($.cookie('arnold_user_auth_token')) { authHeader = $.cookie('arnold_user_auth_token'); } else if ($.cookie('anon_user_auth_token')) { authHeader = $.cookie('anon_user_auth_token'); } else { authHeader = null; } // Only set the auth header if one does not already exist if (!('headers' in options) || ('headers' in options && !('Authorization' in options.headers) && !('authorization' in options.headers))) { if (authHeader) { options['headers'] = {'Authorization': authHeader}; } } } return options; }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. save: function(key, val, options) { var attrs, method, xhr, attributes = this.attributes; // Handle both `"key", value` and `{key: value}` -style arguments. if (key == null || typeof key === 'object') { attrs = key; options = val; } else { (attrs = {})[key] = val; } options = _.extend({validate: true}, options); // If we're not waiting and attributes exist, save acts as // `set(attr).save(null, opts)` with validation. Otherwise, check if // the model will be valid when the attributes, if any, are set. if (attrs && !options.wait) { if (!this.set(attrs, options)) return false; } else { if (!this._validate(attrs, options)) return false; } // Set temporary attributes if `{wait: true}`. if (attrs && options.wait) { this.attributes = _.extend({}, attributes, attrs); } // After a successful server-side save, the client is (optionally) // updated with the server-side state. if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; options.success = function(resp) { // Ensure attributes are restored during synchronous saves. model.attributes = attributes; var serverAttrs = model.parse(resp, options); if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { return false; } if (success) success(model, resp, options); model.trigger('sync', model, resp, options); }; wrapError(this, options); method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); if (method === 'patch') options.attrs = attrs; xhr = this.sync(method, this, options); // Restore attributes. if (attrs && options.wait) this.attributes = attributes; return xhr; }, // Destroy this model on the server if it was already persisted. // Optimistically removes the model from its collection, if it has one. // If `wait: true` is passed, waits for the server to respond before removal. destroy: function(options) { options = options ? _.clone(options) : {}; var model = this; var success = options.success; var destroy = function() { model.trigger('destroy', model, model.collection, options); }; options.success = function(resp) { if (options.wait || model.isNew()) destroy(); if (success) success(model, resp, options); if (!model.isNew()) model.trigger('sync', model, resp, options); }; if (this.isNew()) { options.success(); return false; } wrapError(this, options); var xhr = this.sync('delete', this, options); if (!options.wait) destroy(); return xhr; }, // Default URL for the model's representation on the server -- if you're // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. parse: function(resp, options) { return resp; }, // Create a new model with identical attributes to this one. clone: function() { return new this.constructor(this.attributes); }, // A model is new if it has never been saved to the server, and lacks an id. isNew: function() { return this.id == null; }, // Check if the model is currently in a valid state. isValid: function(options) { return this._validate({}, _.extend(options || {}, { validate: true })); }, // Run validation against the next complete set of model attributes, // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; this.trigger('invalid', this, error, _.extend(options, {validationError: error})); return false; } }); // Underscore methods that we want to implement on the Model. var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; // Mix in each Underscore method as a proxy to `Model#attributes`. _.each(modelMethods, function(method) { Model.prototype[method] = function() { var args = slice.call(arguments); args.unshift(this.attributes); return _[method].apply(_, args); }; }); // Backbone.Collection // ------------------- // If models tend to represent a single row of data, a Backbone Collection is // more analagous to a table full of data ... or a small slice or page of that // table, or a collection of rows that belong together for a particular reason // -- all of the messages in this particular folder, all of the documents // belonging to this particular author, and so on. Collections maintain // indexes of their models, both in order, and for lookup by `id`. // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); if (options.model) this.model = options.model; if (options.comparator !== void 0) this.comparator = options.comparator; this._reset(); this.initialize.apply(this, arguments); if (models) this.reset(models, _.extend({silent: true}, options)); }; // Default options for `Collection#set`. var setOptions = {add: true, remove: true, merge: true}; var addOptions = {add: true, remove: false}; // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { // The default model for a collection is just a **Backbone.Model**. // This should be overridden in most cases. model: Model, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function() {}, // The JSON representation of a Collection is an array of the // models' attributes. toJSON: function(options) { return this.map(function(model) { return model.toJSON(options); }); }, // Proxy `Backbone.sync` by default. sync: function() { return Backbone.sync.apply(this, arguments); }, // Add a model, or list of models to the set. add: function(models, options) { return this.set(models, _.extend({merge: false}, options, addOptions)); }, // Remove a model, or a list of models from the set. remove: function(models, options) { var singular = !_.isArray(models); models = singular ? [models] : _.clone(models); options || (options = {}); var i, l, index, model; for (i = 0, l = models.length; i < l; i++) { model = models[i] = this.get(models[i]); if (!model) continue; delete this._byId[model.id]; delete this._byId[model.cid]; index = this.indexOf(model); this.models.splice(index, 1); this.length--; if (!options.silent) { options.index = index; model.trigger('remove', model, this, options); } this._removeReference(model); } return singular ? models[0] : models; }, // Update a collection by `set`-ing a new list of models, adding new ones, // removing models that are no longer present, and merging models that // already exist in the collection, as necessary. Similar to **Model#set**, // the core operation for updating the data contained by the collection. set: function(models, options) { options = _.defaults({}, options, setOptions); if (options.parse) models = this.parse(models, options); var singular = !_.isArray(models); models = singular ? (models ? [models] : []) : _.clone(models); var i, l, id, model, attrs, existing, sort; var at = options.at; var targetModel = this.model; var sortable = this.comparator && (at == null) && options.sort !== false; var sortAttr = _.isString(this.comparator) ? this.comparator : null; var toAdd = [], toRemove = [], modelMap = {}; var add = options.add, merge = options.merge, remove = options.remove; var order = !sortable && add && remove ? [] : false; // Turn bare objects into model references, and prevent invalid models // from being added. for (i = 0, l = models.length; i < l; i++) { attrs = models[i]; if (attrs instanceof Model) { id = model = attrs; } else { id = attrs[targetModel.prototype.idAttribute]; } // If a duplicate is found, prevent it from being added and // optionally merge it into the existing model. if (this.get(id)) { existing = this.get(id); if (remove) modelMap[existing.cid] = true; if (merge) { attrs = attrs === model ? model.attributes : attrs; if (options.parse) attrs = existing.parse(attrs, options); existing.set(attrs, options); if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; } models[i] = existing; // If this is a new, valid model, push it to the `toAdd` list. } else if (add) { model = models[i] = this._prepareModel(attrs, options); if (!model) continue; toAdd.push(model); // Listen to added models' events, and index models for lookup by // `id` and by `cid`. model.on('all', this._onModelEvent, this); this._byId[model.cid] = model; if (model.id != null) this._byId[model.id] = model; } if (order) order.push(existing || model); } // Remove nonexistent models if appropriate. if (remove) { for (i = 0, l = this.length; i < l; ++i) { if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); } if (toRemove.length) this.remove(toRemove, options); } // See if sorting is needed, update `length` and splice in new models. if (toAdd.length || (order && order.length)) { if (sortable) sort = true; this.length += toAdd.length; if (at != null) { for (i = 0, l = toAdd.length; i < l; i++) { this.models.splice(at + i, 0, toAdd[i]); } } else { if (order) this.models.length = 0; var orderedModels = order || toAdd; for (i = 0, l = orderedModels.length; i < l; i++) { this.models.push(orderedModels[i]); } } } // Silently sort the collection if appropriate. if (sort) this.sort({silent: true}); // Unless silenced, it's time to fire all appropriate add/sort events. if (!options.silent) { for (i = 0, l = toAdd.length; i < l; i++) { (model = toAdd[i]).trigger('add', model, this, options); } if (sort || (order && order.length)) this.trigger('sort', this, options); } // Return the added (or merged) model (or models). return singular ? models[0] : models; }, // When you have more items than you want to add or remove individually, // you can reset the entire set with a new list of models, without firing // any granular `add` or `remove` events. Fires `reset` when finished. // Useful for bulk operations and optimizations. reset: function(models, options) { options || (options = {}); for (var i = 0, l = this.models.length; i < l; i++) { this._removeReference(this.models[i]); } options.previousModels = this.models; this._reset(); models = this.add(models, _.extend({silent: true}, options)); if (!options.silent) this.trigger('reset', this, options); return models; }, // Add a model to the end of the collection. push: function(model, options) { return this.add(model, _.extend({at: this.length}, options)); }, // Remove a model from the end of the collection. pop: function(options) { var model = this.at(this.length - 1); this.remove(model, options); return model; }, // Add a model to the beginning of the collection. unshift: function(model, options) { return this.add(model, _.extend({at: 0}, options)); }, // Remove a model from the beginning of the collection. shift: function(options) { var model = this.at(0); this.remove(model, options); return model; }, // Slice out a sub-array of models from the collection. slice: function() { return slice.apply(this.models, arguments); }, // Get a model from the set by id. get: function(obj) { if (obj == null) return void 0; return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj]; }, // Get the model at the given index. at: function(index) { return this.models[index]; }, // Return models with matching attributes. Useful for simple cases of // `filter`. where: function(attrs, first) { if (_.isEmpty(attrs)) return first ? void 0 : []; return this[first ? 'find' : 'filter'](function(model) { for (var key in attrs) { if (attrs[key] !== model.get(key)) return false; } return true; }); }, // Return the first model with matching attributes. Useful for simple cases // of `find`. findWhere: function(attrs) { return this.where(attrs, true); }, // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. sort: function(options) { if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); options || (options = {}); // Run sort based on type of `comparator`. if (_.isString(this.comparator) || this.comparator.length === 1) { this.models = this.sortBy(this.comparator, this); } else { this.models.sort(_.bind(this.comparator, this)); } if (!options.silent) this.trigger('sort', this, options); return this; }, // Pluck an attribute from each model in the collection. pluck: function(attr) { return _.invoke(this.models, 'get', attr); }, // Fetch the default set of models for this collection, resetting the // collection when they arrive. If `reset: true` is passed, the response // data will be passed through the `reset` method instead of `set`. fetch: function(options) { var authHeader, browser = require('chiropractor/browser'); options = options ? _.clone(options) : {}; // Set the Authorization header for IE8 here so that it // is included in every API call if (browser.isIE8or9) { if ($.cookie('arnold_user_auth_token')) { authHeader = $.cookie('arnold_user_auth_token'); } else if ($.cookie('anon_user_auth_token')) { authHeader = $.cookie('anon_user_auth_token'); } else { authHeader = null; } // Only set the auth header if one does not already exist if (!('headers' in options) || ('headers' in options && !('Authorization' in options.headers) && !('authorization' in options.headers))) { if (authHeader) { options['headers'] = {'Authorization': authHeader}; } } } if (options.parse === void 0) options.parse = true; var success = options.success; var collection = this; options.success = function(resp) { var method = options.reset ? 'reset' : 'set'; collection[method](resp, options); if (success) success(collection, resp, options); collection.trigger('sync', collection, resp, options); }; wrapError(this, options); return this.sync('read', this, options); }, // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. create: function(model, options) { options = options ? _.clone(options) : {}; if (!(model = this._prepareModel(model, options))) return false; if (!options.wait) this.add(model, options); var collection = this; var success = options.success; options.success = function(model, resp, options) { if (options.wait) collection.add(model, options); if (success) success(model, resp, options); }; model.save(null, options); return model; }, // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. parse: function(resp, options) { return resp; }, // Create a new collection with an identical list of models as this one. clone: function() { return new this.constructor(this.models); }, // Private method to reset all internal state. Called when the collection // is first initialized or reset. _reset: function() { this.length = 0; this.models = []; this._byId = {}; }, // Prepare a hash of attributes (or other model) to be added to this // collection. _prepareModel: function(attrs, options) { if (attrs instanceof Model) { if (!attrs.collection) attrs.collection = this; return attrs; } options = options ? _.clone(options) : {}; options.collection = this; var model = new this.model(attrs, options); if (!model.validationError) return model; this.trigger('invalid', this, model.validationError, options); return false; }, // Internal method to sever a model's ties to a collection. _removeReference: function(model) { if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, // Internal method called every time a model in the set fires an event. // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. _onModelEvent: function(event, model, collection, options) { if ((event === 'add' || event === 'remove') && collection !== this) return; if (event === 'destroy') this.remove(model, options); if (model && event === 'change:' + model.idAttribute) { delete this._byId[model.previous(model.idAttribute)]; if (model.id != null) this._byId[model.id] = model; } this.trigger.apply(this, arguments); } }); // Underscore methods that we want to implement on the Collection. // 90% of the core usefulness of Backbone Collections is actually implemented // right here: var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', 'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle', 'lastIndexOf', 'isEmpty', 'chain']; // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function(method) { Collection.prototype[method] = function() { var args = slice.call(arguments); args.unshift(this.models); return _[method].apply(_, args); }; }); // Underscore methods that take a property name as an argument. var attributeMethods = ['groupBy', 'countBy', 'sortBy']; // Use attributes instead of properties. _.each(attributeMethods, function(method) { Collection.prototype[method] = function(value, context) { var iterator = _.isFunction(value) ? value : function(model) { return model.get(value); }; return _[method](this.models, iterator, context); }; }); // Backbone.View // ------------- // Backbone Views are almost more convention than they are actual code. A View // is simply a JavaScript object that represents a logical chunk of UI in the // DOM. This might be a single item, an entire list, a sidebar or panel, or // even the surrounding frame which wraps your whole app. Defining a chunk of // UI as a **View** allows you to define your DOM events declaratively, without // having to worry about render order ... and makes it easy for the view to // react to specific changes in the state of your models. // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... var View = Backbone.View = function(options) { this.cid = _.uniqueId('view'); options || (options = {}); _.extend(this, _.pick(options, viewOptions)); this._ensureElement(); this.initialize.apply(this, arguments); this.delegateEvents(); }; // Cached regex to split keys for `delegate`. var delegateEventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; // Set up all inheritable **Backbone.View** properties and methods. _.extend(View.prototype, Events, { // The default `tagName` of a View's element is `"div"`. tagName: 'div', // jQuery delegate for element lookup, scoped to DOM elements within the // current view. This should be preferred to global lookups where possible. $: function(selector) { return this.$el.find(selector); }, // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function() {}, // **render** is the core function that your view should override, in order // to populate its element (`this.el`), with the appropriate HTML. The // convention is for **render** to always return `this`. render: function() { return this; }, // Remove this view by taking the element out of the DOM, and removing any // applicable Backbone.Events listeners. remove: function() { this.$el.remove(); this.stopListening(); return this; }, // Change the view's element (`this.el` property), including event // re-delegation. setElement: function(element, delegate) { if (this.$el) this.undelegateEvents(); this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); this.el = this.$el[0]; if (delegate !== false) this.delegateEvents(); return this; }, // Set callbacks, where `this.events` is a hash of // // *{"event selector": "callback"}* // // { // 'mousedown .title': 'edit', // 'click .button': 'save', // 'click .open': function(e) { ... } // } // // pairs. Callbacks will be bound to the view, with `this` set properly. // Uses event delegation for efficiency. // Omitting the selector binds the event to `this.el`. // This only works for delegate-able events: not `focus`, `blur`, and // not `change`, `submit`, and `reset` in Internet Explorer. delegateEvents: function(events) { if (!(events || (events = _.result(this, 'events')))) return this; this.undelegateEvents(); for (var key in events) { var method = events[key]; if (!_.isFunction(method)) method = this[events[key]]; if (!method) continue; var match = key.match(delegateEventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateEvents' + this.cid; if (selector === '') { this.$el.on(eventName, method); } else { this.$el.on(eventName, selector, method); } } return this; }, // Clears all callbacks previously bound to the view with `delegateEvents`. // You usually don't need to use this, but may wish to if you have multiple // Backbone views attached to the same DOM element. undelegateEvents: function() { this.$el.off('.delegateEvents' + this.cid); return this; }, // Ensure that the View has a DOM element to render into. // If `this.el` is a string, pass it through `$()`, take the first // matching element, and re-assign it to `el`. Otherwise, create // an element from the `id`, `className` and `tagName` properties. _ensureElement: function() { if (!this.el) { var attrs = _.extend({}, _.result(this, 'attributes')); if (this.id) attrs.id = _.result(this, 'id'); if (this.className) attrs['class'] = _.result(this, 'className'); var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); this.setElement($el, false); } else { this.setElement(_.result(this, 'el'), false); } } }); // Backbone.sync // ------------- // Override this function to change the manner in which Backbone persists // models to the server. You will be passed the type of request, and the // model in question. By default, makes a RESTful Ajax request // to the model's `url()`. Some possible customizations could be: // // * Use `setTimeout` to batch rapid-fire updates into a single request. // * Send up the models as XML instead of JSON. // * Persist models via WebSockets instead of Ajax. // // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests // as `POST`, with a `_method` parameter containing the true HTTP method, // as well as all requests with the body as `application/x-www-form-urlencoded` // instead of `application/json` with the model in a param named `model`. // Useful when interfacing with server-side languages like **PHP** that make // it difficult to read the body of `PUT` requests. Backbone.sync = function(method, model, options) { var type = methodMap[method]; // Default options, unless specified. _.defaults(options || (options = {}), { emulateHTTP: Backbone.emulateHTTP, emulateJSON: Backbone.emulateJSON }); // Default JSON-request options. var params = {type: type, dataType: 'json'}; // Ensure that we have a URL. if (!options.url) { params.url = _.result(model, 'url') || urlError(); } // Ensure that we have the appropriate request data. if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { params.contentType = 'application/json'; params.data = JSON.stringify(options.attrs || model.toJSON(options)); } // For older servers, emulate JSON by encoding the request into an HTML-form. if (options.emulateJSON) { params.contentType = 'application/x-www-form-urlencoded'; params.data = params.data ? {model: params.data} : {}; } // For older servers, emulate HTTP by mimicking the HTTP method with `_method` // And an `X-HTTP-Method-Override` header. if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { params.type = 'POST'; if (options.emulateJSON) params.data._method = type; var beforeSend = options.beforeSend; options.beforeSend = function(xhr) { xhr.setRequestHeader('X-HTTP-Method-Override', type); if (beforeSend) return beforeSend.apply(this, arguments); }; } // Don't process data on a non-GET request. if (params.type !== 'GET' && !options.emulateJSON) { params.processData = false; } // If we're sending a `PATCH` request, and we're in an old Internet Explorer // that still has ActiveX enabled by default, override jQuery to use that // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. if (params.type === 'PATCH' && noXhrPatch) { params.xhr = function() { return new ActiveXObject("Microsoft.XMLHTTP"); }; } // Make the request, allowing the user to override any Ajax options. var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); model.trigger('request', model, xhr, options); return xhr; }; var noXhrPatch = typeof window !== 'undefined' && !!window.ActiveXObject && !(window.XMLHttpRequest && (new XMLHttpRequest).dispatchEvent); // Map from CRUD to HTTP for our default `Backbone.sync` implementation. var methodMap = { 'create': 'POST', 'update': 'PUT', 'patch': 'PATCH', 'delete': 'DELETE', 'read': 'GET' }; // Set the default implementation of `Backbone.ajax` to proxy through to `$`. // Override this if you'd like to use a different library. Backbone.ajax = function() { return Backbone.$.ajax.apply(Backbone.$, arguments); }; // Backbone.Router // --------------- // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. var Router = Backbone.Router = function(options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); this.initialize.apply(this, arguments); }; // Cached regular expressions for matching named param parts and splatted // parts of route strings. var optionalParam = /\((.*?)\)/g; var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. _.extend(Router.prototype, Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. initialize: function() {}, // Manually bind a single named route to a callback. For example: // // this.route('search/:query/p:num', 'search', function(query, num) { // ... // }); // route: function(route, name, callback) { if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; Backbone.history.route(route, function(fragment) { var args = router._extractParameters(route, fragment); callback && callback.apply(router, args); router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); Backbone.history.trigger('route', router, name, args); }); return this; }, // Simple proxy to `Backbone.history` to save a fragment into the history. navigate: function(fragment, options) { Backbone.history.navigate(fragment, options); return this; }, // Bind all defined routes to `Backbone.history`. We have to reverse the // order of the routes here to support behavior where the most general // routes can be defined at the bottom of the route map. _bindRoutes: function() { if (!this.routes) return; this.routes = _.result(this, 'routes'); var route, routes = _.keys(this.routes); while ((route = routes.pop()) != null) { this.route(route, this.routes[route]); } }, // Convert a route string into a regular expression, suitable for matching // against the current location hash. _routeToRegExp: function(route) { route = route.replace(escapeRegExp, '\\$&') .replace(optionalParam, '(?:$1)?') .replace(namedParam, function(match, optional) { return optional ? match : '([^\/]+)'; }) .replace(splatParam, '(.*?)'); return new RegExp('^' + route + '$'); }, // Given a route, and a URL fragment that it matches, return the array of // extracted decoded parameters. Empty or unmatched parameters will be // treated as `null` to normalize cross-browser behavior. _extractParameters: function(route, fragment) { var params = route.exec(fragment).slice(1); return _.map(params, function(param) { return param ? decodeURIComponent(param) : null; }); } }); // Backbone.History // ---------------- // Handles cross-browser history management, based on either // [pushState](http://diveintohtml5.info/history.html) and real URLs, or // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) // and URL fragments. If the browser supports neither (old IE, natch), // falls back to polling. var History = Backbone.History = function() { this.handlers = []; _.bindAll(this, 'checkUrl'); // Ensure that `History` can be used outside of the browser. if (typeof window !== 'undefined') { this.location = window.location; this.history = window.history; } }; // Cached regex for stripping a leading hash/slash and trailing space. var routeStripper = /^[#\/]|\s+$/g; // Cached regex for stripping leading and trailing slashes. var rootStripper = /^\/+|\/+$/g; // Cached regex for detecting MSIE. var isExplorer = /msie [\w.]+/; // Cached regex for removing a trailing slash. var trailingSlash = /\/$/; // Cached regex for stripping urls of hash and query. var pathStripper = /[?#].*$/; // Has the history handling already been started? History.started = false; // Set up all inheritable **Backbone.History** properties and methods. _.extend(History.prototype, Events, { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. getHash: function(window) { var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, // Get the cross-browser normalized URL fragment, either from the URL, // the hash, or the override. getFragment: function(fragment, forcePushState) { if (fragment == null) { if (this._hasPushState || !this._wantsHashChange || forcePushState) { fragment = this.location.pathname; var root = this.root.replace(trailingSlash, ''); if (!fragment.indexOf(root)) fragment = fragment.slice(root.length); } else { fragment = this.getHash(); } } return fragment.replace(routeStripper, ''); }, // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. start: function(options) { if (History.started) throw new Error("Backbone.history has already been started"); History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? this.options = _.extend({root: '/'}, this.options, options); this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; this._wantsPushState = !!this.options.pushState; this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); // Normalize root to always include a leading and trailing slash. this.root = ('/' + this.root + '/').replace(rootStripper, '/'); if (oldIE && this._wantsHashChange) { this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow; this.navigate(fragment); } // Depending on whether we're using pushState or hashes, and whether // 'onhashchange' is supported, determine how we check the URL state. if (this._hasPushState) { Backbone.$(window).on('popstate', this.checkUrl); } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) { Backbone.$(window).on('hashchange', this.checkUrl); } else if (this._wantsHashChange) { this._checkUrlInterval = setInterval(this.checkUrl, this.interval); } // Determine if we need to change the base url, for a pushState link // opened by a non-pushState browser. this.fragment = fragment; var loc = this.location; var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root; // Transition from hashChange to pushState or vice versa if both are // requested. if (this._wantsHashChange && this._wantsPushState) { // If we've started off with a route from a `pushState`-enabled // browser, but we're currently in a browser that doesn't support it... if (!this._hasPushState && !atRoot) { this.fragment = this.getFragment(null, true); this.location.replace(this.root + this.location.search + '#' + this.fragment); // Return immediately as browser will do redirect to new url return true; // Or if we've started out with a hash-based route, but we're currently // in a browser where it could be `pushState`-based instead... } else if (this._hasPushState && atRoot && loc.hash) { this.fragment = this.getHash().replace(routeStripper, ''); this.history.replaceState({}, document.title, this.root + this.fragment + loc.search); } } if (!this.options.silent) return this.loadUrl(); }, // Disable Backbone.history, perhaps temporarily. Not useful in a real app, // but possibly useful for unit testing Routers. stop: function() { Backbone.$(window).off('popstate', this.checkUrl).off('hashchange', this.checkUrl); clearInterval(this._checkUrlInterval); History.started = false; }, // Add a route to be tested when the fragment changes. Routes added later // may override previous routes. route: function(route, callback) { this.handlers.unshift({route: route, callback: callback}); }, // Checks the current URL to see if it has changed, and if it has, // calls `loadUrl`, normalizing across the hidden iframe. checkUrl: function(e) { var current = this.getFragment(); if (current === this.fragment && this.iframe) { current = this.getFragment(this.getHash(this.iframe)); } if (current === this.fragment) return false; if (this.iframe) this.navigate(current); this.loadUrl(); }, // Attempt to load the current URL fragment. If a route succeeds with a // match, returns `true`. If no defined routes matches the fragment, // returns `false`. loadUrl: function(fragment) { fragment = this.fragment = this.getFragment(fragment); return _.any(this.handlers, function(handler) { if (handler.route.test(fragment)) { handler.callback(fragment); return true; } }); }, // Save a fragment into the hash history, or replace the URL state if the // 'replace' option is passed. You are responsible for properly URL-encoding // the fragment in advance. // // The options object can contain `trigger: true` if you wish to have the // route callback be fired (not usually desirable), or `replace: true`, if // you wish to modify the current URL without adding an entry to the history. navigate: function(fragment, options) { if (!History.started) return false; if (!options || options === true) options = {trigger: !!options}; var url = this.root + (fragment = this.getFragment(fragment || '')); // Strip the fragment of the query and hash for matching. fragment = fragment.replace(pathStripper, ''); if (this.fragment === fragment) return; this.fragment = fragment; // Don't include a trailing slash on the root. if (fragment === '' && url !== '/') url = url.slice(0, -1); // If pushState is available, we use it to set the fragment as a real URL. if (this._hasPushState) { this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); // If hash changes haven't been explicitly disabled, update the hash // fragment to store history. } else if (this._wantsHashChange) { this._updateHash(this.location, fragment, options.replace); if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) { // Opening and closing the iframe tricks IE7 and earlier to push a // history entry on hash-tag change. When replace is true, we don't // want this. if (!options.replace) this.iframe.document.open().close(); this._updateHash(this.iframe.location, fragment, options.replace); } // If you've told us that you explicitly don't want fallback hashchange- // based history, then `navigate` becomes a page refresh. } else { return this.location.assign(url); } if (options.trigger) return this.loadUrl(fragment); }, // Update the hash location, either replacing the current entry, or adding // a new one to the browser history. _updateHash: function(location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); location.replace(href + '#' + fragment); } else { // Some browsers require that `hash` contains a leading #. location.hash = '#' + fragment; } } }); // Create the default Backbone.history. Backbone.history = new History; // Helpers // ------- // Helper function to correctly set up the prototype chain, for subclasses. // Similar to `goog.inherits`, but uses a hash of prototype properties and // class properties to be extended. var extend = function(protoProps, staticProps) { var parent = this; var child; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. if (protoProps && _.has(protoProps, 'constructor')) { child = protoProps.constructor; } else { child = function() { return parent.apply(this, arguments); }; } // Add static properties to the constructor function, if supplied. _.extend(child, parent, staticProps); // Set the prototype chain to inherit from `parent`, without calling // `parent`'s constructor function. var Surrogate = function() { this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate; // Add prototype properties (instance properties) to the subclass, // if supplied. if (protoProps) _.extend(child.prototype, protoProps); // Set a convenience property in case the parent's prototype is needed // later. child.__super__ = parent.prototype; return child; }; // Set up inheritance for the model, collection, router, view and history. Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; // Throw an error when a URL is needed, and none is supplied. var urlError = function() { throw new Error('A "url" property or function must be specified'); }; // Wrap an optional error callback with a fallback error event. var wrapError = function(model, options) { var error = options.error; options.error = function(resp) { if (error) error(model, resp, options); model.trigger('error', model, resp, options); }; }; return Backbone; })); // backbone-subroute.js v0.4.2 // // Copyright (C) 2012 Dave Cadwallader, Model N, Inc. // Distributed under the MIT License // // Documentation and full license available at: // https://github.com/ModelN/backbone.subroute (function(factory) { if (typeof define === 'function' && define.amd) { // Register as an AMD module if available... define('backbone.subroute',['underscore', 'backbone'], factory); } else if (typeof exports === 'object') { // Next for Node.js, CommonJS, browserify... factory(require('underscore'), require('backbone')); } else { // Browser globals for the unenlightened... factory(_, Backbone); } }(function(_, Backbone) { Backbone.SubRoute = Backbone.Router.extend({ constructor: function(prefix, options) { // each subroute instance should have its own routes hash this.routes = _.clone(this.routes) || {}; // Prefix is optional, set to empty string if not passed this.prefix = prefix = prefix || ""; // SubRoute instances may be instantiated using a prefix with or without a trailing slash. // If the prefix does *not* have a trailing slash, we need to insert a slash as a separator // between the prefix and the sub-route path for each route that we register with Backbone. this.separator = (prefix.slice(-1) === "/") ? "" : "/"; // if you want to match "books" and "books/" without creating separate routes, set this // option to "true" and the sub-router will automatically create those routes for you. this.createTrailingSlashRoutes = options && options.createTrailingSlashRoutes; // Required to have Backbone set up routes Backbone.Router.prototype.constructor.call(this, options); // grab the full URL var hash; if (Backbone.history.fragment) { hash = Backbone.history.getFragment(); } else { hash = Backbone.history.getHash(); } // Trigger the subroute immediately. this supports the case where // a user directly navigates to a URL with a subroute on the first page load. // Check every element, if one matches, break. Prevent multiple matches _.every(this.routes, function(key, route) { // Use the Backbone parser to turn route into regex for matching if (hash.match(Backbone.Router.prototype._routeToRegExp(route))) { Backbone.history.loadUrl(hash); return false; } return true; }, this); if (this.postInitialize) { this.postInitialize(options); } }, navigate: function(route, options) { if (route.substr(0, 1) != '/' && route.indexOf(this.prefix.substr(0, this.prefix.length - 1)) !== 0) { route = this.prefix + (route ? this.separator : "") + route; } Backbone.Router.prototype.navigate.call(this, route, options); }, route: function(route, name, callback) { // strip off any leading slashes in the sub-route path, // since we already handle inserting them when needed. if (route.substr(0) === "/") { route = route.substr(1, route.length); } var _route = this.prefix; if (route && route.length > 0) { if (this.prefix.length > 0) _route += this.separator; _route += route; } if (this.createTrailingSlashRoutes) { this.routes[_route + '/'] = name; Backbone.Router.prototype.route.call(this, _route + '/', name, callback); } // remove the un-prefixed route from our routes hash delete this.routes[route]; // add the prefixed-route. note that this routes hash is just provided // for informational and debugging purposes and is not used by the actual routing code. this.routes[_route] = name; // delegate the creation of the properly-prefixed route to Backbone return Backbone.Router.prototype.route.call(this, _route, name, callback); } }); return Backbone.SubRoute; })); /* json2.js 2013-05-26 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See http://www.JSON.org/js.html This code should be minified before deployment. See http://javascript.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. This file creates a global JSON object containing two methods: stringify and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object values are stringified for objects. It can be a function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each level. If it is a string (such as '\t' or ' '), it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value For example, this would serialize Dates as ISO strings. Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing object. The value that is returned from your method will be serialized. If your method returns undefined, then the member will be excluded from the serialization. If the replacer parameter is an array of strings, then it will be used to select the members to be serialized. It filters the results such that only members with keys listed in the replacer array are stringified. Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. The optional space parameter produces a stringification of the value that is filled with line breaks and indentation to make it easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then the indentation will be that many spaces. Example: text = JSON.stringify(['e', {pluribus: 'unum'}]); // text is '["e",{"pluribus":"unum"}]' text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value; }); // text is '["Date(---current time---)"]' JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. Example: // Parse the text. Values that look like ISO date strings will // be converted to Date objects. myData = JSON.parse(text, function (key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }); myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { var d; if (typeof value === 'string' && value.slice(0, 5) === 'Date(' && value.slice(-1) === ')') { d = new Date(value.slice(5, -1)); if (d) { return d; } } return value; }); This is a reference implementation. You are free to copy, modify, or redistribute. */ /*jslint evil: true, regexp: true */ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace, slice, stringify, test, toJSON, toString, valueOf */ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. if (typeof JSON !== 'object') { // eslint-disable-next-line no-global-assign JSON = {}; } /*global define*/ define('json-ie7',['require'],function(require) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function () { return this.valueOf(); }; } // eslint-disable-next-line no-control-regex var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, // eslint-disable-next-line no-control-regex escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { if (typeof rep[i] === 'string') { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; } // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; } }); /*global define*/ define('chiropractor/views/base',['require','underscore','json-ie7','jquery','backbone','handlebars'],function(require) { var _ = require('underscore'), JSON = require('json-ie7'), $ = require('jquery'), Backbone = require('backbone'), Handlebars = require('handlebars'), removeHandlerTest = $('<span></span>'), removeHandlerExists = false, placeholderId; removeHandlerTest.on('remove', function() { removeHandlerExists = true; }); removeHandlerTest.remove(); if (!removeHandlerExists) { $.event.special.remove = { remove: function(e) { if (e.handler) { var $el = $(this); // Since this event gets fired on calling $el.off('remove') // as well as when the $el.remove() gets called, we need to // allow the Backbone View to unregister this without // firing it. if (!$el.hasClass('removedEventFired')) { $el.addClass('removedEventFired'); e.handler.call(this, new $.Event('remove', {target: this})); } } } }; } placeholderId = function(view) { return 'chiropractorId' + view.cid; }; return Backbone.View.extend({ initialize: function(options) { options = options || {}; Backbone.View.prototype.initialize.call(this, options); _.bindAll(this, 'remove'); this._childViews = []; this._context = options.context || {}; this.$el.on('remove', this.remove); }, _addChild: function(view) { this._childViews.push(view); return '<' + view.el.tagName + ' id="' + placeholderId(view) + '"></div>'; }, context: function() { return { model: this.model, collection: this.collection }; }, render: function() { var template = typeof(this.template) === 'string' ? Handlebars.compile(this.template) : this.template, context = typeof(this.context) === 'function' ? this.context() : this.context; context.declaringView = this; _.defaults(context, this._context); if (template) { this.$el.html(template(context)); } _(this._childViews).each(function(view) { this.$('#' + placeholderId(view)).replaceWith(view.el); }, this); return this; }, remove: function() { this.$el.addClass('removedEventFired'); this.$el.off('remove', this.remove); _(this._childViews).each(function(view) { view.remove(); }); this._childViews = []; Backbone.View.prototype.remove.apply(this, arguments); } }); }); /*global define*/ define('chiropractor/views/form',['require','underscore','json-ie7','jquery','backbone','./base'],function(require) { var _ = require('underscore'), JSON = require('json-ie7'), $ = require('jquery'), Backbone = require('backbone'), Base = require('./base'); return Base.extend({ initialize: function() { Base.prototype.initialize.apply(this, arguments); this.listenForErrors(this.model); this.listenForErrors(this.collection); }, listenForErrors: function(obj) { if (obj) { if (obj instanceof Backbone.Model) { this.listenTo(obj, 'invalid', this.renderFormErrors); this.listenTo(obj, 'change', this.clearFormErrors); } else if (obj instanceof Backbone.Collection) { obj.each(function(model) { this.listenTo(model, 'invalid', this.renderFormErrors); this.listenTo(model, 'change', this.clearFormErrors); }, this); } else { throw new Error('Invalid object to associate errors with.'); } } }, clearFormErrors: function(model) { _(model.changed).each(function(value, field) { this.$('#container-' + model.fieldId(field)) .find('.help-inline').html(''); }, this); }, renderFormErrors: function(model, errors) { _(errors).each(function(errorMessages, field) { var help; errorMessages = typeof(errorMessages) === 'string' ? [errorMessages] : errorMessages; if (field === '__all__') { help = $('<div class="errors"></div>'); this.$el.prepend(help); } else { help = this.$('#container-' + model.fieldId(field)) .find('.help-inline'); } help.html(errorMessages.join(', ')); }, this); } }); }); /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/fields/label',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression; foundHelper = helpers.value; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.value; stack1 = typeof stack1 === functionType ? stack1() : stack1; } return escapeExpression(stack1); }); Handlebars.registerPartial('chiropractor_views_templates_fields_label', t); return t; }); /* END_TEMPLATE */ /*global define*/ define('chiropractor/views/field',['require','json-ie7','jquery','underscore','handlebars','hbs!./templates/fields/label'],function(require) { var JSON = require('json-ie7'), $ = require('jquery'), _ = require('underscore'), Handlebars = require('handlebars'), fieldTemplates = {}, label = require('hbs!./templates/fields/label'); fieldTemplates = { 'defaults': label }; Handlebars.registerHelper('field', function(type, model, fieldName) { // template helper in the form of: // // {{ field 'text' model 'fieldname' [attrName="attrValue"]*}} var template = fieldTemplates[type], options = arguments[arguments.length - 1], opts = options.hash || {}, field = opts.field, id = '', value = ''; if (!template) { template = fieldTemplates.defaults; } if (model) { id = model.fieldId(fieldName); //console.log(model); if (field) { value = model.get(field.id); } } _.defaults(opts, { id: id, label: fieldName, name: fieldName, options: [], blank: false, value: value, help: '', model: model }); return new Handlebars.SafeString(template(opts)); }); return { Templates: fieldTemplates, addTemplate: function(name,template) { fieldTemplates[name] = template; } }; }); /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/row/row',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = "", stack1, foundHelper, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this; function program1(depth0,data,depth1) { var buffer = "", stack1, stack2, stack3, foundHelper; buffer += "\r\n <td>"; stack1 = depth1.model; stack2 = depth0.fieldtype; stack3 = {}; stack3['field'] = depth0; foundHelper = helpers.field; stack1 = foundHelper ? foundHelper.call(depth0, stack2, stack1, {hash:stack3}) : helperMissing.call(depth0, "field", stack2, stack1, {hash:stack3}); buffer += escapeExpression(stack1) + "</td>\r\n "; return buffer; } buffer += "<tr class=\""; foundHelper = helpers.rowclass; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.rowclass; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">\r\n "; stack1 = depth0.options; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.fields; stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.programWithDepth(program1, data, depth0)}); if (stack1 || stack1 === 0) { buffer += stack1; } buffer += "\r\n</tr>"; return buffer; }); Handlebars.registerPartial('chiropractor_views_templates_row_row', t); return t; }); /* END_TEMPLATE */ /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/row/error_messagebox',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression; buffer += "<div id=\"chiropractor-error-box\" style=\"color: #a94442;background-color: #f2dede;border-color: #ebccd1;padding: 15px;border: 1px solid transparent;border-radius: 4px;\">\r\n<div aria-hidden=\"true\" style=\"float:right;\" onClick=\"javascript:var element = document.getElementById('chiropractor-error-box');element.parentNode.removeChild(element);\">×</div>\r\n<div style=\"font-weight:bold\">"; stack1 = depth0.model; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.errorMessage; stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "</div>\r\n<div>Message: "; stack1 = depth0.model; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.response; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.responseText; stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "</div>\r\n<div>Status: "; stack1 = depth0.model; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.response; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.statusText; stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "</div>\r\n<div>StatusCode: "; stack1 = depth0.model; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.response; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.status; stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "</div>\r\n<div>Url: <a href=\""; stack1 = depth0.model; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.url; stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "\">"; stack1 = depth0.model; stack1 = stack1 == null || stack1 === false ? stack1 : stack1.url; stack1 = typeof stack1 === functionType ? stack1() : stack1; buffer += escapeExpression(stack1) + "</a></div>\r\n</div>"; return buffer; }); Handlebars.registerPartial('chiropractor_views_templates_row_error_messagebox', t); return t; }); /* END_TEMPLATE */ /*global define*/ define('chiropractor/views/row',['require','json-ie7','jquery','underscore','handlebars','hbs!./templates/row/row','hbs!./templates/row/error_messagebox'],function(require) { var JSON = require('json-ie7'), $ = require('jquery'), _ = require('underscore'), Handlebars = require('handlebars'), RowTemplates = {}, Row = require('hbs!./templates/row/row'), ErrorTemplate = require('hbs!./templates/row/error_messagebox'); RowTemplates = { 'row': Row, 'error': ErrorTemplate }; Handlebars.registerHelper('row', function(type, model, fieldName) { // template helper in the form of: // // {{ field 'text' model 'fieldname' [attrName="attrValue"]*}} var current = RowTemplates[type], options = arguments[arguments.length - 1], opts = options.hash || {}, id = '', value = ''; if (!current) { throw new Error('Unregistered field template: ' + type); } //console.log(opt); return new Handlebars.SafeString(current({ model: model, options: opts })); }); return { Templates: RowTemplates, addTemplate: function(name,template) { RowTemplates[name] = template; } }; }); /*global define*/ define('chiropractor/hbs/view',['require','underscore','backbone','handlebars'],function(require) { var _ = require('underscore'), Backbone = require('backbone'), Handlebars = require('handlebars'), view; view = function() { // template helper in the form of: // // {{ view "path/to/require/module[|ViewName]" [viewOptions] [context] [viewOption1=val viewOption2=val]}} var View, view, options, requirePath, viewName, attrs, requireBits, placeholder, context = {}; options = arguments[arguments.length - 1]; attrs = arguments[1] || {}; _.defaults(attrs, options.hash || {}); if (arguments.length === 4) { context = arguments[2]; } _.defaults(this, context); if (typeof(arguments[0]) === 'string') { requireBits = arguments[0].split('|'); requirePath = requireBits[0]; viewName = requireBits[1]; View = require(requirePath); if (typeof(viewName) === 'string') { View = View[viewName]; } } else { View = arguments[0]; } if (options.fn) { View = View.extend({ template: options.fn }); } view = new View(attrs).render(); placeholder = this.declaringView._addChild(view); // Return a placeholder that the Chiropractor.View can replace with // the child view appended above. // If this is called as a block hbs helper, then we do not need to // use safe string, while as a hbs statement it needs to be declared // safe. if (options.fn) { return placeholder; } else { return new Handlebars.SafeString(placeholder); } }; Handlebars.registerHelper('view', view); return view; }); /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/formfield/text',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression; buffer += "<div class=\"control-group\" id=\"container-"; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">\r\n <label class=\"control-label "; foundHelper = helpers.labelclass; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.labelclass; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" for=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</label>\r\n <div class=\"controls\">\r\n <input type=\"text\" placeholder=\""; foundHelper = helpers.placeholder; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.placeholder; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" id=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" name=\""; foundHelper = helpers.name; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" value=\""; foundHelper = helpers.value; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.value; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" class=\""; foundHelper = helpers['class']; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0['class']; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" />\r\n <span class=\"description\">"; foundHelper = helpers.description; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.description; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</span>\r\n <span class=\"help-inline\">"; foundHelper = helpers.help; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.help; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</span>\r\n </div>\r\n</div>\r\n"; return buffer; }); Handlebars.registerPartial('chiropractor_views_templates_formfield_text', t); return t; }); /* END_TEMPLATE */ /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/formfield/textarea',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression; buffer += "<div class=\"control-group\" id=\"container-"; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">\r\n <label class=\"control-label\" for=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</label>\r\n <div class=\"controls\">\r\n <textarea id=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" name=\""; foundHelper = helpers.name; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.value; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.value; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</textarea>\r\n <span class=\"help-inline\">"; foundHelper = helpers.help; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.help; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</span>\r\n </div>\r\n</div>\r\n"; return buffer; }); Handlebars.registerPartial('chiropractor_views_templates_formfield_textarea', t); return t; }); /* END_TEMPLATE */ /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/formfield/select',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing; function program1(depth0,data) { var buffer = "", stack1, foundHelper; buffer += "\r\n <option value=\"\">"; foundHelper = helpers.blank; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.blank; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</option>\r\n "; return buffer; } function program3(depth0,data,depth1) { var buffer = "", stack1, stack2, foundHelper; buffer += "\r\n <option value=\""; foundHelper = helpers.value; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.value; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\""; stack1 = depth1.value; stack2 = depth0.value; foundHelper = helpers.ifequal; stack1 = foundHelper ? foundHelper.call(depth0, stack2, stack1, {hash:{},inverse:self.noop,fn:self.program(4, program4, data)}) : helperMissing.call(depth0, "ifequal", stack2, stack1, {hash:{},inverse:self.noop,fn:self.program(4, program4, data)}); if (stack1 || stack1 === 0) { buffer += stack1; } buffer += ">"; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</option>\r\n "; return buffer; } function program4(depth0,data) { return " selected=\"selected\""; } buffer += "<div class=\"control-group\" id=\"container-"; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">\r\n <label class=\"control-label "; foundHelper = helpers.labelclass; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.labelclass; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" for=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</label>\r\n <div class=\"controls\">\r\n <select id=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" name=\""; foundHelper = helpers.name; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">\r\n "; stack1 = depth0.blank; stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)}); if (stack1 || stack1 === 0) { buffer += stack1; } buffer += "\r\n "; stack1 = depth0.options; stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.programWithDepth(program3, data, depth0)}); if (stack1 || stack1 === 0) { buffer += stack1; } buffer += "\r\n </select>\r\n <span class=\"description\">"; foundHelper = helpers.description; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.description; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</span>\r\n <span class=\"help-inline\">"; foundHelper = helpers.help; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.help; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</span>\r\n </div>\r\n</div>\r\n"; return buffer; }); Handlebars.registerPartial('chiropractor_views_templates_formfield_select', t); return t; }); /* END_TEMPLATE */ /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/formfield/checkbox',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this; function program1(depth0,data) { var buffer = "", stack1, foundHelper; buffer += " \r\n <input type=\"checkbox\" id=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" name=\""; foundHelper = helpers.name; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" value=\""; foundHelper = helpers.value; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.value; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" /> "; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\r\n "; return buffer; } buffer += "<div class=\"control-group\" id=\"container-"; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">\r\n <label class=\"control-label\" for=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</label>\r\n <div class=\"controls\">\r\n "; stack1 = depth0.options; stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)}); if (stack1 || stack1 === 0) { buffer += stack1; } buffer += "\r\n <span class=\"help-inline\">"; foundHelper = helpers.help; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.help; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</span>\r\n </div>\r\n</div>\r\n"; return buffer; }); Handlebars.registerPartial('chiropractor_views_templates_formfield_checkbox', t); return t; }); /* END_TEMPLATE */ /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/formfield/radio',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression, self=this; function program1(depth0,data) { var buffer = "", stack1, foundHelper; buffer += " \r\n <input type=\"radio\" id=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" name=\""; foundHelper = helpers.name; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.name; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" value=\""; foundHelper = helpers.value; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.value; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\" /> "; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\r\n "; return buffer; } buffer += "<div class=\"control-group\" id=\"container-"; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">\r\n <label class=\"control-label\" for=\""; foundHelper = helpers.id; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "\">"; foundHelper = helpers.label; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.label; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</label>\r\n <div class=\"controls\">\r\n "; stack1 = depth0.options; stack1 = helpers.each.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)}); if (stack1 || stack1 === 0) { buffer += stack1; } buffer += "\r\n <span class=\"help-inline\">"; foundHelper = helpers.help; if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } else { stack1 = depth0.help; stack1 = typeof stack1 === functionType ? stack1() : stack1; } buffer += escapeExpression(stack1) + "</span>\r\n </div>\r\n</div>\r\n"; return buffer; }); Handlebars.registerPartial('chiropractor_views_templates_formfield_radio', t); return t; }); /* END_TEMPLATE */ /*global define*/ define('chiropractor/views/formfield',['require','json-ie7','jquery','underscore','handlebars','../hbs/view','./base','hbs!./templates/formfield/text','hbs!./templates/formfield/textarea','hbs!./templates/formfield/select','hbs!./templates/formfield/checkbox','hbs!./templates/formfield/radio'],function(require) { var JSON = require('json-ie7'), $ = require('jquery'), _ = require('underscore'), Handlebars = require('handlebars'), viewHelper = require('../hbs/view'), Base = require('./base'), fieldTemplates = {}, View, register; View = Base.extend({ events: { 'change input': 'inputChanged', 'blur input': 'inputChanged', 'change select': 'inputChanged', 'blur select': 'inputChanged', 'change textarea': 'inputChanged', 'blur textarea': 'inputChanged' }, initialize: function(options) { Base.prototype.initialize.apply(this, arguments); this.config = options.context; this.field = options.field; }, inputChanged: function() { var val = this.$('[name=' + this.field + ']').val(); this.model.set(this.field, val, {validate: true}); // We want to ensure that the model value is really updated even // if validation fails in the previous step. However, it should be // silent and not trigger any change events since the previous step // will take care of that in the case of success. this.model.set(this.field, val, {silent: true}); } }); View.register = register = function(type, def) { var SubClass = fieldTemplates[type] = this.extend(def); SubClass.register = register; return SubClass; }; View.unregister = function(type) { if (fieldTemplates[type]) { delete fieldTemplates[type]; } }; View.register('text', { template: require('hbs!./templates/formfield/text') }); View.register('textarea', { template: require('hbs!./templates/formfield/textarea') }); View.register('select', { template: require('hbs!./templates/formfield/select') }); View.register('checkbox', { template: require('hbs!./templates/formfield/checkbox') }); View.register('radio', { template: require('hbs!./templates/formfield/radio') }); Handlebars.registerHelper('formfield', function(type, model, fieldName) { // template helper in the form of: // // {{ formfield 'text' model 'fieldname' [attrName="attrValue"]*}} var FormField = fieldTemplates[type], options = arguments[arguments.length - 1], opts = options.hash || {}; if (!FormField) { throw new Error('Unregistered formfield template: ' + type); } _.defaults(opts, { id: model.fieldId(fieldName), label: fieldName, name: fieldName, options: [], blank: false, value: model.get(fieldName) || '', help: '' }); return viewHelper.call(this, FormField, { field: fieldName, model: model, context: opts }, options); }); return View; }); /*global define*/ define('chiropractor/views',['require','./views/base','./views/form','./views/field','./views/row','./views/formfield'],function(require) { var Base = require('./views/base'), Form = require('./views/form'), Field = require('./views/field'), Row = require('./views/row'), FormField = require('./views/formfield'); return { Base: Base, Form: Form, Row: Row, Field: Field, FormField: FormField }; }); /** * @preserve console-shim 1.0.3 * https://github.com/kayahr/console-shim * Copyright (C) 2011 Klaus Reimer <k@ailis.de> * Licensed under the MIT license * (See http://www.opensource.org/licenses/mit-license) */ (function() { /** * Returns a function which calls the specified function in the specified * scope. * * @param {Function} func * The function to call. * @param {Object} scope * The scope to call the function in. * @param {...*} args * Additional arguments to pass to the bound function. * @returns {function(...[*]): undefined} * The bound function. */ var bind = function(func, scope, args) { var fixedArgs = Array.prototype.slice.call(arguments, 2); return function() { var args = fixedArgs.concat(Array.prototype.slice.call(arguments, 0)); (/** @type {Function} */ func).apply(scope, args); }; }; // Create console if not present if (!window["console"]) window.console = /** @type {Console} */ ({}); var console = (/** @type {Object} */ window.console); // Implement console log if needed if (!console["log"]) { // Use log4javascript if present if (window["log4javascript"]) { var log = log4javascript.getDefaultLogger(); console.log = bind(log.info, log); console.debug = bind(log.debug, log); console.info = bind(log.info, log); console.warn = bind(log.warn, log); console.error = bind(log.error, log); } else { // Use empty dummy implementation to ignore logging console.log = (/** @param {...*} args */ function(args) {}); } } // Implement other log levels to console.log if missing if (!console["debug"]) console.debug = console.log; if (!console["info"]) console.info = console.log; if (!console["warn"]) console.warn = console.log; if (!console["error"]) console.error = console.log; // Wrap the log methods in IE (<=9) because their argument handling is wrong // This wrapping is also done if the __consoleShimTest__ symbol is set. This // is needed for unit testing. if (window["__consoleShimTest__"] != null || eval("/*@cc_on @_jscript_version <= 9@*/")) { /** * Wraps the call to a real IE logging method. Modifies the arguments so * parameters which are not represented by a placeholder are properly * printed with a space character as separator. * * @param {...*} args * The function arguments. First argument is the log function * to call, the other arguments are the log arguments. */ var wrap = function(args) { var i, max, match, log; // Convert argument list to real array args = Array.prototype.slice.call(arguments, 0); // First argument is the log method to call log = args.shift(); max = args.length; if (max > 1 && window["__consoleShimTest__"] !== false) { // When first parameter is not a string then add a format string to // the argument list so we are able to modify it in the next stop if (typeof(args[0]) != "string") { args.unshift("%o"); max += 1; } // For each additional parameter which has no placeholder in the // format string we add another placeholder separated with a // space character. match = args[0].match(/%[a-z]/g); for (i = match ? match.length + 1 : 1; i < max; i += 1) { args[0] += " %o"; } } Function.apply.call(log, console, args); }; // Wrap the native log methods of IE to fix argument output problems console.log = bind(wrap, window, console.log); console.debug = bind(wrap, window, console.debug); console.info = bind(wrap, window, console.info); console.warn = bind(wrap, window, console.warn); console.error = bind(wrap, window, console.error); } // Implement console.assert if missing if (!console["assert"]) { console["assert"] = function() { var args = Array.prototype.slice.call(arguments, 0); var expr = args.shift(); if (!expr) { args[0] = "Assertion failed: " + args[0]; console.error.apply(console, args); } }; } // Linking console.dir and console.dirxml to the console.log method if // missing. Hopefully the browser already logs objects and DOM nodes as a // tree. if (!console["dir"]) console["dir"] = console.log; if (!console["dirxml"]) console["dirxml"] = console.log; // Linking console.exception to console.error. This is not the same but // at least some error message is displayed. if (!console["exception"]) console["exception"] = console.error; // Implement console.time and console.timeEnd if one of them is missing if (!console["time"] || !console["timeEnd"]) { var timers = {}; console["time"] = function(id) { timers[id] = new Date().getTime(); }; console["timeEnd"] = function(id) { var start = timers[id]; if (start) { console.log(id + ": " + (new Date().getTime() - start) + "ms"); delete timers[id]; } }; } // Implement console.table if missing if (!console["table"]) { console["table"] = function(data, columns) { var i, iMax, row, j, jMax, k; // Do nothing if data has wrong type or no data was specified if (!data || !(data instanceof Array) || !data.length) return; // Auto-calculate columns array if not set if (!columns || !(columns instanceof Array)) { columns = []; for (k in data[0]) { if (!data[0].hasOwnProperty(k)) continue; columns.push(k); } } for (i = 0, iMax = data.length; i < iMax; i += 1) { row = []; for (j = 0, jMax = columns.length; j < jMax; j += 1) { row.push(data[i][columns[j]]); } Function.apply.call(console.log, console, row); } }; } // Dummy implementations of other console features to prevent error messages // in browsers not supporting it. if (!console["clear"]) console["clear"] = function() {}; if (!console["trace"]) console["trace"] = function() {}; if (!console["group"]) console["group"] = function() {}; if (!console["groupCollapsed"]) console["groupCollapsed"] = function() {}; if (!console["groupEnd"]) console["groupEnd"] = function() {}; if (!console["timeStamp"]) console["timeStamp"] = function() {}; if (!console["profile"]) console["profile"] = function() {}; if (!console["profileEnd"]) console["profileEnd"] = function() {}; if (!console["count"]) console["count"] = function() {}; })(); define("console-shim", (function (global) { return function () { var ret, fn; return ret || global.console; }; }(this))); /** * easyXDM * http://easyxdm.net/ * Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * 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. */ define('jquery.cors/easyxdm/easyxdm',['require','json2'],function(require) { var JSON = require('json2'); var global = this; var channelId = Math.floor(Math.random() * 10000); // randomize the initial id in case of multiple closures loaded var emptyFn = Function.prototype; var reURI = /^((http.?:)\/\/([^:\/\s]+)(:\d+)*)/; // returns groups for protocol (2), domain (3) and port (4) var reParent = /[\-\w]+\/\.\.\//; // matches a foo/../ expression var reDoubleSlash = /([^:])\/\//g; // matches // anywhere but in the protocol var namespace = ""; // stores namespace under which easyXDM object is stored on the page (empty if object is global) var easyXDM = {}; var _easyXDM = window.easyXDM; // map over global easyXDM in case of overwrite var IFRAME_PREFIX = "easyXDM_"; var HAS_NAME_PROPERTY_BUG; var useHash = false; // whether to use the hash over the query var flashVersion; // will be set if using flash var HAS_FLASH_THROTTLED_BUG; //(function(window, document, location, setTimeout, decodeURIComponent, encodeURIComponent) { /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global JSON, XMLHttpRequest, window, escape, unescape, ActiveXObject */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting function isHostMethod(object, property) { var t = typeof object[property]; return t == 'function' || ( !! (t == 'object' && object[property])) || t == 'unknown'; } function isHostObject(object, property) { return !!(typeof(object[property]) == 'object' && object[property]); } // end // http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/ function isArray(o) { return Object.prototype.toString.call(o) === '[object Array]'; } // end function hasFlash() { var name = "Shockwave Flash", mimeType = "application/x-shockwave-flash"; if (!undef(navigator.plugins) && typeof navigator.plugins[name] == "object") { // adapted from the swfobject code var description = navigator.plugins[name].description; if (description && !undef(navigator.mimeTypes) && navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin) { flashVersion = description.match(/\d+/g); } } if (!flashVersion) { var flash; try { flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); flashVersion = Array.prototype.slice.call(flash.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1); flash = null; } catch (notSupportedException) { // do nothing } } if (!flashVersion) { return false; } var major = parseInt(flashVersion[0], 10), minor = parseInt(flashVersion[1], 10); HAS_FLASH_THROTTLED_BUG = major > 9 && minor > 0; return true; } /* * Cross Browser implementation for adding and removing event listeners. */ var on, un; if (isHostMethod(window, "addEventListener")) { on = function(target, type, listener) { target.addEventListener(type, listener, false); }; un = function(target, type, listener) { target.removeEventListener(type, listener, false); }; } else if (isHostMethod(window, "attachEvent")) { on = function(object, sEvent, fpNotify) { object.attachEvent("on" + sEvent, fpNotify); }; un = function(object, sEvent, fpNotify) { object.detachEvent("on" + sEvent, fpNotify); }; } else { throw new Error("Browser not supported"); } /* * Cross Browser implementation of DOMContentLoaded. */ var domIsReady = false, domReadyQueue = [], readyState; if ("readyState" in document) { // If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and // 'interactive' (HTML5 specs, recent WebKit builds) states. // https://bugs.webkit.org/show_bug.cgi?id=45119 readyState = document.readyState; domIsReady = readyState == "complete" || (~navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive")); } else { // If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately // when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not. // We only need a body to add elements to, so the existence of document.body is enough for us. domIsReady = !! document.body; } function dom_onReady() { if (domIsReady) { return; } domIsReady = true; for (var i = 0; i < domReadyQueue.length; i++) { domReadyQueue[i](); } domReadyQueue.length = 0; } if (!domIsReady) { if (isHostMethod(window, "addEventListener")) { on(document, "DOMContentLoaded", dom_onReady); } else { on(document, "readystatechange", function() { if (document.readyState == "complete") { dom_onReady(); } }); if (document.documentElement.doScroll && window === top) { var doScrollCheck = function() { if (domIsReady) { return; } // http://javascript.nwbox.com/IEContentLoaded/ try { document.documentElement.doScroll("left"); } catch (e) { setTimeout(doScrollCheck, 1); return; } dom_onReady(); }; doScrollCheck(); } } // A fallback to window.onload, that will always work on(window, "load", dom_onReady); } /** * This will add a function to the queue of functions to be run once the DOM reaches a ready state. * If functions are added after this event then they will be executed immediately. * @param {function} fn The function to add * @param {Object} scope An optional scope for the function to be called with. */ function whenReady(fn, scope) { if (domIsReady) { fn.call(scope); return; } domReadyQueue.push(function() { fn.call(scope); }); } /** * Returns an instance of easyXDM from the parent window with * respect to the namespace. * * @return An instance of easyXDM (in the parent window) */ function getParentObject() { var obj = parent; if (namespace !== "") { for (var i = 0, ii = namespace.split("."); i < ii.length; i++) { obj = obj[ii[i]]; } } return obj.easyXDM; } /** * Removes easyXDM variable from the global scope. It also returns control * of the easyXDM variable to whatever code used it before. * * @param {String} ns A string representation of an object that will hold * an instance of easyXDM. * @return An instance of easyXDM */ function noConflict(ns) { window.easyXDM = _easyXDM; namespace = ns; if (namespace) { IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_"; } return easyXDM; } /* * Methods for working with URLs */ /** * Get the domain name from a url. * @param {String} url The url to extract the domain from. * @return The domain part of the url. * @type {String} */ function getDomainName(url) { return url.match(reURI)[3]; } /** * Get the port for a given URL, or "" if none * @param {String} url The url to extract the port from. * @return The port part of the url. * @type {String} */ function getPort(url) { return url.match(reURI)[4] || ""; } /** * Returns a string containing the schema, domain and if present the port * @param {String} url The url to extract the location from * @return {String} The location part of the url */ function getLocation(url) { var m = url.toLowerCase().match(reURI); var proto = m[2], domain = m[3], port = m[4] || ""; if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) { port = ""; } return proto + "//" + domain + port; } /** * Resolves a relative url into an absolute one. * @param {String} url The path to resolve. * @return {String} The resolved url. */ function resolveUrl(url) { // replace all // except the one in proto with / url = url.replace(reDoubleSlash, "$1/"); // If the url is a valid url we do nothing if (!url.match(/^(http||https):\/\//)) { // If this is a relative path var path = (url.substring(0, 1) === "/") ? "" : location.pathname; if (path.substring(path.length - 1) !== "/") { path = path.substring(0, path.lastIndexOf("/") + 1); } url = location.protocol + "//" + location.host + path + url; } // reduce all 'xyz/../' to just '' while (reParent.test(url)) { url = url.replace(reParent, ""); } return url; } /** * Appends the parameters to the given url.<br/> * The base url can contain existing query parameters. * @param {String} url The base url. * @param {Object} parameters The parameters to add. * @return {String} A new valid url with the parameters appended. */ function appendQueryParameters(url, parameters) { var hash = "", indexOf = url.indexOf("#"); if (indexOf !== -1) { hash = url.substring(indexOf); url = url.substring(0, indexOf); } var q = []; for (var key in parameters) { if (parameters.hasOwnProperty(key)) { q.push(key + "=" + encodeURIComponent(parameters[key])); } } return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash; } // build the query object either from location.query, if it contains the xdm_e argument, or from location.hash var query = (function(input) { input = input.substring(1).split("&"); var data = {}, pair, i = input.length; while (i--) { pair = input[i].split("="); data[pair[0]] = decodeURIComponent(pair[1]); } return data; }(/xdm_e=/.test(location.search) ? location.search : location.hash)); /* * Helper methods */ /** * Helper for checking if a variable/property is undefined * @param {Object} v The variable to test * @return {Boolean} True if the passed variable is undefined */ function undef(v) { return typeof v === "undefined"; } /** * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works. * @return {JSON} A valid JSON conforming object, or null if not found. */ var getJSON = function() { var cached = {}; var obj = { a: [1, 2, 3] }, json = "{\"a\":[1,2,3]}"; if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) { // this is a working JSON instance return JSON; } if (Object.toJSON) { if (Object.toJSON(obj).replace((/\s/g), "") === json) { // this is a working stringify method cached.stringify = Object.toJSON; } } if (typeof String.prototype.evalJSON === "function") { obj = json.evalJSON(); if (obj.a && obj.a.length === 3 && obj.a[2] === 3) { // this is a working parse method cached.parse = function(str) { return str.evalJSON(); }; } } if (cached.stringify && cached.parse) { // Only memoize the result if we have valid instance getJSON = function() { return cached; }; return cached; } return null; }; /** * Applies properties from the source object to the target object.<br/> * @param {Object} target The target of the properties. * @param {Object} source The source of the properties. * @param {Boolean} noOverwrite Set to True to only set non-existing properties. */ function apply(destination, source, noOverwrite) { var member; for (var prop in source) { if (source.hasOwnProperty(prop)) { if (prop in destination) { member = source[prop]; if (typeof member === "object") { apply(destination[prop], member, noOverwrite); } else if (!noOverwrite) { destination[prop] = source[prop]; } } else { destination[prop] = source[prop]; } } } return destination; } // This tests for the bug in IE where setting the [name] property using javascript causes the value to be redirected into [submitName]. function testForNamePropertyBug() { var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input")); input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name]; document.body.removeChild(form); } /** * Creates a frame and appends it to the DOM. * @param config {object} This object can have the following properties * <ul> * <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li> * <li> {object} attr The attributes that should be set on the frame.</li> * <li> {DOMElement} container Its parent element (Optional).</li> * <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li> * </ul> * @return The frames DOMElement * @type DOMElement */ function createFrame(config) { if (undef(HAS_NAME_PROPERTY_BUG)) { testForNamePropertyBug(); } var frame; // This is to work around the problems in IE6/7 with setting the name property. // Internally this is set as 'submitName' instead when using 'iframe.name = ...' // This is not required by easyXDM itself, but is to facilitate other use cases if (HAS_NAME_PROPERTY_BUG) { frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>"); } else { frame = document.createElement("IFRAME"); frame.name = config.props.name; } frame.id = frame.name = config.props.name; delete config.props.name; if (typeof config.container == "string") { config.container = document.getElementById(config.container); } if (!config.container) { // This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers. apply(frame.style, { position: "absolute", top: "-2000px", // Avoid potential horizontal scrollbar left: "0px" }); config.container = document.body; } // HACK: IE cannot have the src attribute set when the frame is appended // into the container, so we set it to "javascript:false" as a // placeholder for now. If we left the src undefined, it would // instead default to "about:blank", which causes SSL mixed-content // warnings in IE6 when on an SSL parent page. var src = config.props.src; config.props.src = "javascript:false"; // transfer properties to the frame apply(frame, config.props); frame.border = frame.frameBorder = 0; frame.allowTransparency = true; config.container.appendChild(frame); if (config.onLoad) { on(frame, "load", config.onLoad); } // set the frame URL to the proper value (we previously set it to // "javascript:false" to work around the IE issue mentioned above) if (config.usePost) { var form = config.container.appendChild(document.createElement('form')), input; form.target = frame.name; form.action = src; form.method = 'POST'; if (typeof(config.usePost) === 'object') { for (var i in config.usePost) { if (config.usePost.hasOwnProperty(i)) { if (HAS_NAME_PROPERTY_BUG) { input = document.createElement('<input name="' + i + '"/>'); } else { input = document.createElement("INPUT"); input.name = i; } input.value = config.usePost[i]; form.appendChild(input); } } } form.submit(); form.parentNode.removeChild(form); } else { frame.src = src; } config.props.src = src; return frame; } /** * Check whether a domain is allowed using an Access Control List. * The ACL can contain * and ? as wildcards, or can be regular expressions. * If regular expressions they need to begin with ^ and end with $. * @param {Array/String} acl The list of allowed domains * @param {String} domain The domain to test. * @return {Boolean} True if the domain is allowed, false if not. */ function checkAcl(acl, domain) { // normalize into an array if (typeof acl == "string") { acl = [acl]; } var re, i = acl.length; while (i--) { re = acl[i]; re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "$")); if (re.test(domain)) { return true; } } return false; } /* * Functions related to stacks */ /** * Prepares an array of stack-elements suitable for the current configuration * @param {Object} config The Transports configuration. See easyXDM.Socket for more. * @return {Array} An array of stack-elements with the TransportElement at index 0. */ function prepareTransportStack(config) { var protocol = config.protocol, stackEls; config.isHost = config.isHost || undef(query.xdm_p); useHash = config.hash || false; if (!config.props) { config.props = {}; } if (!config.isHost) { config.channel = query.xdm_c.replace(/["'<>\\]/g, ""); config.secret = query.xdm_s; config.remote = query.xdm_e.replace(/["'<>\\]/g, ""); protocol = query.xdm_p; if (config.acl && !checkAcl(config.acl, config.remote)) { throw new Error("Access denied for " + config.remote); } } else { config.remote = resolveUrl(config.remote); config.channel = config.channel || "default" + channelId++; config.secret = Math.random().toString(16).substring(2); if (undef(protocol)) { if (getLocation(location.href) == getLocation(config.remote)) { /* * Both documents has the same origin, lets use direct access. */ protocol = "4"; } else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) { /* * This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+ */ protocol = "1"; } else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) { /* * The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS */ protocol = "6"; } else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) { /* * This is supported in Gecko (Firefox 1+) */ protocol = "5"; } else if (config.remoteHelper) { /* * This is supported in all browsers that retains the value of window.name when * navigating from one domain to another, and where parent.frames[foo] can be used * to get access to a frame from the same domain */ protocol = "2"; } else { /* * This is supported in all browsers where [window].location is writable for all * The resize event will be used if resize is supported and the iframe is not put * into a container, else polling will be used. */ protocol = "0"; } } } config.protocol = protocol; // for conditional branching switch (protocol) { case "0": // 0 = HashTransport apply(config, { interval: 100, delay: 2000, useResize: true, useParent: false, usePolling: false }, true); if (config.isHost) { if (!config.local) { // If no local is set then we need to find an image hosted on the current domain var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image; var i = images.length; while (i--) { image = images[i]; if (image.src.substring(0, domain.length) === domain) { config.local = image.src; break; } } if (!config.local) { // If no local was set, and we are unable to find a suitable file, then we resort to using the current window config.local = window; } } var parameters = { xdm_c: config.channel, xdm_p: 0 }; if (config.local === window) { // We are using the current window to listen to config.usePolling = true; config.useParent = true; config.local = location.protocol + "//" + location.host + location.pathname + location.search; parameters.xdm_e = config.local; parameters.xdm_pa = 1; // use parent } else { parameters.xdm_e = resolveUrl(config.local); } if (config.container) { config.useResize = false; parameters.xdm_po = 1; // use polling } config.remote = appendQueryParameters(config.remote, parameters); } else { apply(config, { channel: query.xdm_c, remote: query.xdm_e, useParent: !undef(query.xdm_pa), usePolling: !undef(query.xdm_po), useResize: config.useParent ? false : config.useResize }); } stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({ encode: true, maxLength: 4000 - config.remote.length }), new easyXDM.stack.VerifyBehavior({ initiate: config.isHost })]; break; case "1": stackEls = [new easyXDM.stack.PostMessageTransport(config)]; break; case "2": config.remoteHelper = resolveUrl(config.remoteHelper); stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({ initiate: config.isHost })]; break; case "3": stackEls = [new easyXDM.stack.NixTransport(config)]; break; case "4": stackEls = [new easyXDM.stack.SameOriginTransport(config)]; break; case "5": stackEls = [new easyXDM.stack.FrameElementTransport(config)]; break; case "6": if (!flashVersion) { hasFlash(); } stackEls = [new easyXDM.stack.FlashTransport(config)]; break; } // this behavior is responsible for buffering outgoing messages, and for performing lazy initialization stackEls.push(new easyXDM.stack.QueueBehavior({ lazy: config.lazy, remove: true })); return stackEls; } /** * Chains all the separate stack elements into a single usable stack.<br/> * If an element is missing a necessary method then it will have a pass-through method applied. * @param {Array} stackElements An array of stack elements to be linked. * @return {easyXDM.stack.StackElement} The last element in the chain. */ function chainStack(stackElements) { var stackEl, defaults = { incoming: function(message, origin) { this.up.incoming(message, origin); }, outgoing: function(message, recipient) { this.down.outgoing(message, recipient); }, callback: function(success) { this.up.callback(success); }, init: function() { this.down.init(); }, destroy: function() { this.down.destroy(); } }; for (var i = 0, len = stackElements.length; i < len; i++) { stackEl = stackElements[i]; apply(stackEl, defaults, true); if (i !== 0) { stackEl.down = stackElements[i - 1]; } if (i !== len - 1) { stackEl.up = stackElements[i + 1]; } } return stackEl; } /** * This will remove a stackelement from its stack while leaving the stack functional. * @param {Object} element The elment to remove from the stack. */ function removeFromStack(element) { element.up.down = element.down; element.down.up = element.up; element.up = element.down = null; } /* * Export the main object and any other methods applicable */ /** * @class easyXDM * A javascript library providing cross-browser, cross-domain messaging/RPC. * @version 2.4.17.1 * @singleton */ apply(easyXDM, { /** * The version of the library * @type {string} */ version: "2.4.17.1", /** * This is a map containing all the query parameters passed to the document. * All the values has been decoded using decodeURIComponent. * @type {object} */ query: query, /** * @private */ stack: {}, /** * Applies properties from the source object to the target object.<br/> * @param {object} target The target of the properties. * @param {object} source The source of the properties. * @param {boolean} noOverwrite Set to True to only set non-existing properties. */ apply: apply, /** * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works. * @return {JSON} A valid JSON conforming object, or null if not found. */ getJSONObject: getJSON, /** * This will add a function to the queue of functions to be run once the DOM reaches a ready state. * If functions are added after this event then they will be executed immediately. * @param {function} fn The function to add * @param {object} scope An optional scope for the function to be called with. */ whenReady: whenReady, /** * Removes easyXDM variable from the global scope. It also returns control * of the easyXDM variable to whatever code used it before. * * @param {String} ns A string representation of an object that will hold * an instance of easyXDM. * @return An instance of easyXDM */ noConflict: noConflict }); /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global console, _FirebugCommandLine, easyXDM, window, escape, unescape, isHostObject, undef, _trace, domIsReady, emptyFn, namespace */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, isHostObject, isHostMethod, un, on, createFrame, debug */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.DomHelper * Contains methods for dealing with the DOM * @singleton */ easyXDM.DomHelper = { /** * Provides a consistent interface for adding eventhandlers * @param {Object} target The target to add the event to * @param {String} type The name of the event * @param {Function} listener The listener */ on: on, /** * Provides a consistent interface for removing eventhandlers * @param {Object} target The target to remove the event from * @param {String} type The name of the event * @param {Function} listener The listener */ un: un, /** * Checks for the presence of the JSON object. * If it is not present it will use the supplied path to load the JSON2 library. * This should be called in the documents head right after the easyXDM script tag. * http://json.org/json2.js * @param {String} path A valid path to json2.js */ requiresJSON: function(path) { if (!isHostObject(window, "JSON")) { // we need to encode the < in order to avoid an illegal token error // when the script is inlined in a document. document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>'); } } }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, debug */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // (function() { // The map containing the stored functions var _map = {}; /** * @class easyXDM.Fn * This contains methods related to function handling, such as storing callbacks. * @singleton * @namespace easyXDM */ easyXDM.Fn = { /** * Stores a function using the given name for reference * @param {String} name The name that the function should be referred by * @param {Function} fn The function to store * @namespace easyXDM.fn */ set: function(name, fn) { _map[name] = fn; }, /** * Retrieves the function referred to by the given name * @param {String} name The name of the function to retrieve * @param {Boolean} del If the function should be deleted after retrieval * @return {Function} The stored function * @namespace easyXDM.fn */ get: function(name, del) { var fn = _map[name]; if (del) { delete _map[name]; } return fn; } }; }()); /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, chainStack, prepareTransportStack, getLocation, debug */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.Socket * This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/> * The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/> * Internally different stacks will be used depending on the browsers features and the available parameters. * <h2>How to set up</h2> * Setting up the provider: * <pre><code> * var socket = new easyXDM.Socket({ * local: "name.html", * onReady: function(){ * // you need to wait for the onReady callback before using the socket * socket.postMessage("foo-message"); * }, * onMessage: function(message, origin) { * alert("received " + message + " from " + origin); * } * }); * </code></pre> * Setting up the consumer: * <pre><code> * var socket = new easyXDM.Socket({ * remote: "http://remotedomain/page.html", * remoteHelper: "http://remotedomain/name.html", * onReady: function(){ * // you need to wait for the onReady callback before using the socket * socket.postMessage("foo-message"); * }, * onMessage: function(message, origin) { * alert("received " + message + " from " + origin); * } * }); * </code></pre> * If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property * and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports. * @namespace easyXDM * @constructor * @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window. * @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed. * @cfg {String} remote (Consumer only) The url to the providers document. * @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional. * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000. * @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300. * @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional. * @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional. * @cfg {Function} onReady A method that should be called when the transport is ready. Optional. * @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional. * @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/> * This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $. * If none of the patterns match an Error will be thrown. * @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>. * Properties such as 'name' and 'src' will be overrided. Optional. */ easyXDM.Socket = function(config) { // create the stack var stack = chainStack(prepareTransportStack(config).concat([{ incoming: function(message, origin) { config.onMessage(message, origin); }, callback: function(success) { if (config.onReady) { config.onReady(success); } } }])), recipient = getLocation(config.remote); // set the origin this.origin = getLocation(config.remote); /** * Initiates the destruction of the stack. */ this.destroy = function() { stack.destroy(); }; /** * Posts a message to the remote end of the channel * @param {String} message The message to send */ this.postMessage = function(message) { stack.outgoing(message, recipient); }; stack.init(); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, undef,, chainStack, prepareTransportStack, debug, getLocation */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.Rpc * Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation * of methods to be called from the remote end.<br/> * The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/> * This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods. * <h2>How to set up</h2> * <pre><code> * var rpc = new easyXDM.Rpc({ * // this configuration is equal to that used by the Socket. * remote: "http://remotedomain/...", * onReady: function(){ * // you need to wait for the onReady callback before using the proxy * rpc.foo(... * } * },{ * local: {..}, * remote: {..} * }); * </code></pre> * * <h2>Exposing functions (procedures)</h2> * <pre><code> * var rpc = new easyXDM.Rpc({ * ... * },{ * local: { * nameOfMethod: { * method: function(arg1, arg2, success, error){ * ... * } * }, * // with shorthand notation * nameOfAnotherMethod: function(arg1, arg2, success, error){ * } * }, * remote: {...} * }); * </code></pre> * The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/> * To send a successfull result back you can use * <pre><code> * return foo; * </pre></code> * or * <pre><code> * success(foo); * </pre></code> * To return an error you can use * <pre><code> * throw new Error("foo error"); * </code></pre> * or * <pre><code> * error("foo error"); * </code></pre> * * <h2>Defining remotely exposed methods (procedures/notifications)</h2> * The definition of the remote end is quite similar: * <pre><code> * var rpc = new easyXDM.Rpc({ * ... * },{ * local: {...}, * remote: { * nameOfMethod: {} * } * }); * </code></pre> * To call a remote method use * <pre><code> * rpc.nameOfMethod("arg1", "arg2", function(value) { * alert("success: " + value); * }, function(message) { * alert("error: " + message + ); * }); * </code></pre> * Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/> * When called with no callback a JSON-RPC 2.0 notification will be executed. * Be aware that you will not be notified of any errors with this method. * <br/> * <h2>Specifying a custom serializer</h2> * If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library * then you can specify a custom serializer using <code>serializer: foo</code> * <pre><code> * var rpc = new easyXDM.Rpc({ * ... * },{ * local: {...}, * remote: {...}, * serializer : { * parse: function(string){ ... }, * stringify: function(object) {...} * } * }); * </code></pre> * If <code>serializer</code> is set then the class will not attempt to use the native implementation. * @namespace easyXDM * @constructor * @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters. * @param {Object} jsonRpcConfig The description of the interface to implement. */ easyXDM.Rpc = function(config, jsonRpcConfig) { // expand shorthand notation if (jsonRpcConfig.local) { for (var method in jsonRpcConfig.local) { if (jsonRpcConfig.local.hasOwnProperty(method)) { var member = jsonRpcConfig.local[method]; if (typeof member === "function") { jsonRpcConfig.local[method] = { method: member }; } } } } // create the stack var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), { callback: function(success) { if (config.onReady) { config.onReady(success); } } }])); // set the origin this.origin = getLocation(config.remote); /** * Initiates the destruction of the stack. */ this.destroy = function() { stack.destroy(); }; stack.init(); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, un, on, apply, whenReady, getParentObject, IFRAME_PREFIX*/ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.SameOriginTransport * SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/> * This can be useful for testing and for when the main application supports both internal and external sources. * @namespace easyXDM.stack * @constructor * @param {Object} config The transports configuration. * @cfg {String} remote The remote document to communicate with. */ easyXDM.stack.SameOriginTransport = function(config) { var pub, frame, send, targetOrigin; return (pub = { outgoing: function(message, domain, fn) { send(message); if (fn) { fn(); } }, destroy: function() { if (frame) { frame.parentNode.removeChild(frame); frame = null; } }, onDOMReady: function() { targetOrigin = getLocation(config.remote); if (config.isHost) { // set up the iframe apply(config.props, { src: appendQueryParameters(config.remote, { xdm_e: location.protocol + "//" + location.host + location.pathname, xdm_c: config.channel, xdm_p: 4 // 4 = SameOriginTransport }), name: IFRAME_PREFIX + config.channel + "_provider" }); frame = createFrame(config); easyXDM.Fn.set(config.channel, function(sendFn) { send = sendFn; setTimeout(function() { pub.up.callback(true); }, 0); return function(msg) { pub.up.incoming(msg, targetOrigin); }; }); } else { send = getParentObject().Fn.get(config.channel, true)(function(msg) { pub.up.incoming(msg, targetOrigin); }); setTimeout(function() { pub.up.callback(true); }, 0); } }, init: function() { whenReady(pub.onDOMReady, pub); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global global, easyXDM, window, getLocation, appendQueryParameters, createFrame, debug, apply, whenReady, IFRAME_PREFIX, namespace, resolveUrl, getDomainName, HAS_FLASH_THROTTLED_BUG, getPort, query*/ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.FlashTransport * FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth. * @namespace easyXDM.stack * @constructor * @param {Object} config The transports configuration. * @cfg {String} remote The remote domain to communicate with. * @cfg {String} secret the pre-shared secret used to secure the communication. * @cfg {String} swf The path to the swf file * @cfg {Boolean} swfNoThrottle Set this to true if you want to take steps to avoid beeing throttled when hidden. * @cfg {String || DOMElement} swfContainer Set this if you want to control where the swf is placed */ easyXDM.stack.FlashTransport = function(config) { var pub, // the public interface frame, send, targetOrigin, swf, swfContainer; function onMessage(message, origin) { setTimeout(function() { pub.up.incoming(message, targetOrigin); }, 0); } /** * This method adds the SWF to the DOM and prepares the initialization of the channel */ function addSwf(domain) { // the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error. var url = config.swf + "?host=" + config.isHost; var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000); // prepare the init function that will fire once the swf is ready easyXDM.Fn.set("flash_loaded" + domain.replace(/[\-.]/g, "_"), function() { easyXDM.stack.FlashTransport[domain].swf = swf = swfContainer.firstChild; var queue = easyXDM.stack.FlashTransport[domain].queue; for (var i = 0; i < queue.length; i++) { queue[i](); } queue.length = 0; }); if (config.swfContainer) { swfContainer = (typeof config.swfContainer == "string") ? document.getElementById(config.swfContainer) : config.swfContainer; } else { // create the container that will hold the swf swfContainer = document.createElement('div'); // http://bugs.adobe.com/jira/browse/FP-4796 // http://tech.groups.yahoo.com/group/flexcoders/message/162365 // https://groups.google.com/forum/#!topic/easyxdm/mJZJhWagoLc apply(swfContainer.style, HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle ? { height: "20px", width: "20px", position: "fixed", right: 0, top: 0 } : { height: "1px", width: "1px", position: "absolute", overflow: "hidden", right: 0, top: 0 }); document.body.appendChild(swfContainer); } // create the object/embed var flashVars = "callback=flash_loaded" + domain.replace(/[\-.]/g, "_") + "&proto=" + global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" + getPort(global.location.href) + "&ns=" + namespace; swfContainer.innerHTML = "<object height='20' width='20' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" + "<param name='allowScriptAccess' value='always'></param>" + "<param name='wmode' value='transparent'>" + "<param name='movie' value='" + url + "'></param>" + "<param name='flashvars' value='" + flashVars + "'></param>" + "<embed type='application/x-shockwave-flash' FlashVars='" + flashVars + "' allowScriptAccess='always' wmode='transparent' src='" + url + "' height='1' width='1'></embed>" + "</object>"; } return (pub = { outgoing: function(message, domain, fn) { swf.postMessage(config.channel, message.toString()); if (fn) { fn(); } }, destroy: function() { try { swf.destroyChannel(config.channel); } catch (e) { // do nothing } swf = null; if (frame) { frame.parentNode.removeChild(frame); frame = null; } }, onDOMReady: function() { targetOrigin = config.remote; // Prepare the code that will be run after the swf has been intialized easyXDM.Fn.set("flash_" + config.channel + "_init", function() { setTimeout(function() { pub.up.callback(true); }); }); // set up the omMessage handler easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage); config.swf = resolveUrl(config.swf); // reports have been made of requests gone rogue when using relative paths var swfdomain = getDomainName(config.swf); var fn = function() { // set init to true in case the fn was called was invoked from a separate instance easyXDM.stack.FlashTransport[swfdomain].init = true; swf = easyXDM.stack.FlashTransport[swfdomain].swf; // create the channel swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost); if (config.isHost) { // if Flash is going to be throttled and we want to avoid this if (HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle) { apply(config.props, { position: "fixed", right: 0, top: 0, height: "20px", width: "20px" }); } // set up the iframe apply(config.props, { src: appendQueryParameters(config.remote, { xdm_e: getLocation(location.href), xdm_c: config.channel, xdm_p: 6, // 6 = FlashTransport xdm_s: config.secret }), name: IFRAME_PREFIX + config.channel + "_provider" }); frame = createFrame(config); } }; if (easyXDM.stack.FlashTransport[swfdomain] && easyXDM.stack.FlashTransport[swfdomain].init) { // if the swf is in place and we are the consumer fn(); } else { // if the swf does not yet exist if (!easyXDM.stack.FlashTransport[swfdomain]) { // add the queue to hold the init fn's easyXDM.stack.FlashTransport[swfdomain] = { queue: [fn] }; addSwf(swfdomain); } else { easyXDM.stack.FlashTransport[swfdomain].queue.push(fn); } } }, init: function() { whenReady(pub.onDOMReady, pub); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, un, on, apply, whenReady, IFRAME_PREFIX*/ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.PostMessageTransport * PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/> * <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/> * <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a> * @namespace easyXDM.stack * @constructor * @param {Object} config The transports configuration. * @cfg {String} remote The remote domain to communicate with. */ easyXDM.stack.PostMessageTransport = function(config) { var pub, // the public interface frame, // the remote frame, if any callerWindow, // the window that we will call with targetOrigin; // the domain to communicate with /** * Resolves the origin from the event object * @private * @param {Object} event The messageevent * @return {String} The scheme, host and port of the origin */ function _getOrigin(event) { if (event.origin) { // This is the HTML5 property return getLocation(event.origin); } if (event.uri) { // From earlier implementations return getLocation(event.uri); } if (event.domain) { // This is the last option and will fail if the // origin is not using the same schema as we are return location.protocol + "//" + event.domain; } throw "Unable to retrieve the origin of the event"; } /** * This is the main implementation for the onMessage event.<br/> * It checks the validity of the origin and passes the message on if appropriate. * @private * @param {Object} event The messageevent */ function _window_onMessage(event) { var origin = _getOrigin(event); if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") { pub.up.incoming(event.data.substring(config.channel.length + 1), origin); } } return (pub = { outgoing: function(message, domain, fn) { callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin); if (fn) { fn(); } }, destroy: function() { un(window, "message", _window_onMessage); if (frame) { callerWindow = null; frame.parentNode.removeChild(frame); frame = null; } }, onDOMReady: function() { targetOrigin = getLocation(config.remote); if (config.isHost) { // add the event handler for listening var waitForReady = function(event) { if (event.data == config.channel + "-ready") { // replace the eventlistener callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document; un(window, "message", waitForReady); on(window, "message", _window_onMessage); setTimeout(function() { pub.up.callback(true); }, 0); } }; on(window, "message", waitForReady); // set up the iframe apply(config.props, { src: appendQueryParameters(config.remote, { xdm_e: getLocation(location.href), xdm_c: config.channel, xdm_p: 1 // 1 = PostMessage }), name: IFRAME_PREFIX + config.channel + "_provider" }); frame = createFrame(config); } else { // add the event handler for listening on(window, "message", _window_onMessage); callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document; callerWindow.postMessage(config.channel + "-ready", targetOrigin); setTimeout(function() { pub.up.callback(true); }, 0); } }, init: function() { whenReady(pub.onDOMReady, pub); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, getLocation, appendQueryParameters, createFrame, debug, apply, query, whenReady, IFRAME_PREFIX*/ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.FrameElementTransport * FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/> * Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running. * @namespace easyXDM.stack * @constructor * @param {Object} config The transports configuration. * @cfg {String} remote The remote document to communicate with. */ easyXDM.stack.FrameElementTransport = function(config) { var pub, frame, send, targetOrigin; return (pub = { outgoing: function(message, domain, fn) { send.call(this, message); if (fn) { fn(); } }, destroy: function() { if (frame) { frame.parentNode.removeChild(frame); frame = null; } }, onDOMReady: function() { targetOrigin = getLocation(config.remote); if (config.isHost) { // set up the iframe apply(config.props, { src: appendQueryParameters(config.remote, { xdm_e: getLocation(location.href), xdm_c: config.channel, xdm_p: 5 // 5 = FrameElementTransport }), name: IFRAME_PREFIX + config.channel + "_provider" }); frame = createFrame(config); frame.fn = function(sendFn) { delete frame.fn; send = sendFn; setTimeout(function() { pub.up.callback(true); }, 0); // remove the function so that it cannot be used to overwrite the send function later on return function(msg) { pub.up.incoming(msg, targetOrigin); }; }; } else { // This is to mitigate origin-spoofing if (document.referrer && getLocation(document.referrer) != query.xdm_e) { window.top.location = query.xdm_e; } send = window.frameElement.fn(function(msg) { pub.up.incoming(msg, targetOrigin); }); pub.up.callback(true); } }, init: function() { whenReady(pub.onDOMReady, pub); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, undef, getLocation, appendQueryParameters, resolveUrl, createFrame, debug, un, apply, whenReady, IFRAME_PREFIX*/ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.NameTransport * NameTransport uses the window.name property to relay data. * The <code>local</code> parameter needs to be set on both the consumer and provider,<br/> * and the <code>remoteHelper</code> parameter needs to be set on the consumer. * @constructor * @param {Object} config The transports configuration. * @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host. * @namespace easyXDM.stack */ easyXDM.stack.NameTransport = function(config) { var pub; // the public interface var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl; function _sendMessage(message) { var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel; callerWindow.contentWindow.sendMessage(message, url); } function _onReady() { if (isHost) { if (++readyCount === 2 || !isHost) { pub.up.callback(true); } } else { _sendMessage("ready"); pub.up.callback(true); } } function _onMessage(message) { pub.up.incoming(message, remoteOrigin); } function _onLoad() { if (callback) { setTimeout(function() { callback(true); }, 0); } } return (pub = { outgoing: function(message, domain, fn) { callback = fn; _sendMessage(message); }, destroy: function() { callerWindow.parentNode.removeChild(callerWindow); callerWindow = null; if (isHost) { remoteWindow.parentNode.removeChild(remoteWindow); remoteWindow = null; } }, onDOMReady: function() { isHost = config.isHost; readyCount = 0; remoteOrigin = getLocation(config.remote); config.local = resolveUrl(config.local); if (isHost) { // Register the callback easyXDM.Fn.set(config.channel, function(message) { if (isHost && message === "ready") { // Replace the handler easyXDM.Fn.set(config.channel, _onMessage); _onReady(); } }); // Set up the frame that points to the remote instance remoteUrl = appendQueryParameters(config.remote, { xdm_e: config.local, xdm_c: config.channel, xdm_p: 2 }); apply(config.props, { src: remoteUrl + '#' + config.channel, name: IFRAME_PREFIX + config.channel + "_provider" }); remoteWindow = createFrame(config); } else { config.remoteHelper = config.remote; easyXDM.Fn.set(config.channel, _onMessage); } // Set up the iframe that will be used for the transport var onLoad = function() { // Remove the handler var w = callerWindow || this; un(w, "load", onLoad); easyXDM.Fn.set(config.channel + "_load", _onLoad); (function test() { if (typeof w.contentWindow.sendMessage == "function") { _onReady(); } else { setTimeout(test, 50); } }()); }; callerWindow = createFrame({ props: { src: config.local + "#_4" + config.channel }, onLoad: onLoad }); }, init: function() { whenReady(pub.onDOMReady, pub); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, getLocation, createFrame, debug, un, on, apply, whenReady, IFRAME_PREFIX*/ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.HashTransport * HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/> * <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/> * @namespace easyXDM.stack * @constructor * @param {Object} config The transports configuration. * @cfg {String/Window} local The url to the local file used for proxying messages, or the local window. * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. * @cfg {Number} interval The interval used when polling for messages. */ easyXDM.stack.HashTransport = function(config) { var pub; var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow; var useParent, _remoteOrigin; function _sendMessage(message) { if (!_callerWindow) { return; } var url = config.remote + "#" + (_msgNr++) + "_" + message; ((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url; } function _handleHash(hash) { _lastMsg = hash; pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin); } /** * Checks location.hash for a new message and relays this to the receiver. * @private */ function _pollHash() { if (!_listenerWindow) { return; } var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#"); if (indexOf != -1) { hash = href.substring(indexOf); } if (hash && hash != _lastMsg) { _handleHash(hash); } } function _attachListeners() { _timer = setInterval(_pollHash, pollInterval); } return (pub = { outgoing: function(message, domain) { _sendMessage(message); }, destroy: function() { window.clearInterval(_timer); if (isHost || !useParent) { _callerWindow.parentNode.removeChild(_callerWindow); } _callerWindow = null; }, onDOMReady: function() { isHost = config.isHost; pollInterval = config.interval; _lastMsg = "#" + config.channel; _msgNr = 0; useParent = config.useParent; _remoteOrigin = getLocation(config.remote); if (isHost) { apply(config.props, { src: config.remote, name: IFRAME_PREFIX + config.channel + "_provider" }); if (useParent) { config.onLoad = function() { _listenerWindow = window; _attachListeners(); pub.up.callback(true); }; } else { var tries = 0, max = config.delay / 50; (function getRef() { if (++tries > max) { throw new Error("Unable to reference listenerwindow"); } try { _listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"]; } catch (ex) { // do nothing } if (_listenerWindow) { _attachListeners(); pub.up.callback(true); } else { setTimeout(getRef, 50); } }()); } _callerWindow = createFrame(config); } else { _listenerWindow = window; _attachListeners(); if (useParent) { _callerWindow = parent; pub.up.callback(true); } else { apply(config, { props: { src: config.remote + "#" + config.channel + new Date(), name: IFRAME_PREFIX + config.channel + "_consumer" }, onLoad: function() { pub.up.callback(true); } }); _callerWindow = createFrame(config); } } }, init: function() { whenReady(pub.onDOMReady, pub); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, debug */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.ReliableBehavior * This is a behavior that tries to make the underlying transport reliable by using acknowledgements. * @namespace easyXDM.stack * @constructor * @param {Object} config The behaviors configuration. */ easyXDM.stack.ReliableBehavior = function(config) { var pub, // the public interface callback; // the callback to execute when we have a confirmed success/failure var idOut = 0, idIn = 0, currentMessage = ""; return (pub = { incoming: function(message, origin) { var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(","); message = message.substring(indexOf + 1); if (ack[0] == idOut) { currentMessage = ""; if (callback) { callback(true); callback = null; } } if (message.length > 0) { pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin); if (idIn != ack[1]) { idIn = ack[1]; pub.up.incoming(message, origin); } } }, outgoing: function(message, origin, fn) { currentMessage = message; callback = fn; pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, debug, undef, removeFromStack*/ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.QueueBehavior * This is a behavior that enables queueing of messages. <br/> * It will buffer incoming messages and dispach these as fast as the underlying transport allows. * This will also fragment/defragment messages so that the outgoing message is never bigger than the * set length. * @namespace easyXDM.stack * @constructor * @param {Object} config The behaviors configuration. Optional. * @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation. */ easyXDM.stack.QueueBehavior = function(config) { var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false; function dispatch() { if (config.remove && queue.length === 0) { removeFromStack(pub); return; } if (waiting || queue.length === 0 || destroying) { return; } waiting = true; var message = queue.shift(); pub.down.outgoing(message.data, message.origin, function(success) { waiting = false; if (message.callback) { setTimeout(function() { message.callback(success); }, 0); } dispatch(); }); } return (pub = { init: function() { if (undef(config)) { config = {}; } if (config.maxLength) { maxLength = config.maxLength; doFragment = true; } if (config.lazy) { lazy = true; } else { pub.down.init(); } }, callback: function(success) { waiting = false; var up = pub.up; // in case dispatch calls removeFromStack dispatch(); up.callback(success); }, incoming: function(message, origin) { if (doFragment) { var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10); incoming += message.substring(indexOf + 1); if (seq === 0) { if (config.encode) { incoming = decodeURIComponent(incoming); } pub.up.incoming(incoming, origin); incoming = ""; } } else { pub.up.incoming(message, origin); } }, outgoing: function(message, origin, fn) { if (config.encode) { message = encodeURIComponent(message); } var fragments = [], fragment; if (doFragment) { // fragment into chunks while (message.length !== 0) { fragment = message.substring(0, maxLength); message = message.substring(fragment.length); fragments.push(fragment); } // enqueue the chunks while ((fragment = fragments.shift())) { queue.push({ data: fragments.length + "_" + fragment, origin: origin, callback: fragments.length === 0 ? fn : null }); } } else { queue.push({ data: message, origin: origin, callback: fn }); } if (lazy) { pub.down.init(); } else { dispatch(); } }, destroy: function() { destroying = true; pub.down.destroy(); } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, undef, debug */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.VerifyBehavior * This behavior will verify that communication with the remote end is possible, and will also sign all outgoing, * and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages. * @namespace easyXDM.stack * @constructor * @param {Object} config The behaviors configuration. * @cfg {Boolean} initiate If the verification should be initiated from this end. */ easyXDM.stack.VerifyBehavior = function(config) { var pub, mySecret, theirSecret, verified = false; function startVerification() { mySecret = Math.random().toString(16).substring(2); pub.down.outgoing(mySecret); } return (pub = { incoming: function(message, origin) { var indexOf = message.indexOf("_"); if (indexOf === -1) { if (message === mySecret) { pub.up.callback(true); } else if (!theirSecret) { theirSecret = message; if (!config.initiate) { startVerification(); } pub.down.outgoing(message); } } else { if (message.substring(0, indexOf) === theirSecret) { pub.up.incoming(message.substring(indexOf + 1), origin); } } }, outgoing: function(message, origin, fn) { pub.down.outgoing(mySecret + "_" + message, origin, fn); }, callback: function(success) { if (config.initiate) { startVerification(); } } }); }; /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global easyXDM, window, escape, unescape, undef, getJSON, debug, emptyFn, isArray */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // 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. // /** * @class easyXDM.stack.RpcBehavior * This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/> * Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything. * @namespace easyXDM.stack * @constructor * @param {Object} proxy The object to apply the methods to. * @param {Object} config The definition of the local and remote interface to implement. * @cfg {Object} local The local interface to expose. * @cfg {Object} remote The remote methods to expose through the proxy. * @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON. */ easyXDM.stack.RpcBehavior = function(proxy, config) { var pub, serializer = config.serializer || getJSON(); var _callbackCounter = 0, _callbacks = {}; /** * Serializes and sends the message * @private * @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added. */ function _send(data) { data.jsonrpc = "2.0"; pub.down.outgoing(serializer.stringify(data)); } /** * Creates a method that implements the given definition * @private * @param {Object} The method configuration * @param {String} method The name of the method * @return {Function} A stub capable of proxying the requested method call */ function _createMethod(definition, method) { var slice = Array.prototype.slice; return function() { var l = arguments.length, callback, message = { method: method }; if (l > 0 && typeof arguments[l - 1] === "function") { //with callback, procedure if (l > 1 && typeof arguments[l - 2] === "function") { // two callbacks, success and error callback = { success: arguments[l - 2], error: arguments[l - 1] }; message.params = slice.call(arguments, 0, l - 2); } else { // single callback, success callback = { success: arguments[l - 1] }; message.params = slice.call(arguments, 0, l - 1); } _callbacks["" + (++_callbackCounter)] = callback; message.id = _callbackCounter; } else { // no callbacks, a notification message.params = slice.call(arguments, 0); } if (definition.namedParams && message.params.length === 1) { message.params = message.params[0]; } // Send the method request _send(message); }; } /** * Executes the exposed method * @private * @param {String} method The name of the method * @param {Number} id The callback id to use * @param {Function} method The exposed implementation * @param {Array} params The parameters supplied by the remote end */ function _executeMethod(method, id, fn, params) { if (!fn) { if (id) { _send({ id: id, error: { code: -32601, message: "Procedure not found." } }); } return; } var success, error; if (id) { success = function(result) { success = emptyFn; _send({ id: id, result: result }); }; error = function(message, data) { error = emptyFn; var msg = { id: id, error: { code: -32099, message: message } }; if (data) { msg.error.data = data; } _send(msg); }; } else { success = error = emptyFn; } // Call local method if (!isArray(params)) { params = [params]; } try { var result = fn.method.apply(fn.scope, params.concat([success, error])); if (!undef(result)) { success(result); } } catch (ex1) { error(ex1.message); } } return (pub = { incoming: function(message, origin) { var data = serializer.parse(message); if (data.method) { // A method call from the remote end if (config.handle) { config.handle(data, _send); } else { _executeMethod(data.method, data.id, config.local[data.method], data.params); } } else { // A method response from the other end var callback = _callbacks[data.id]; if (data.error) { if (callback.error) { callback.error(data.error); } } else if (callback.success) { callback.success(data.result); } delete _callbacks[data.id]; } }, init: function() { if (config.remote) { // Implement the remote sides exposed methods for (var method in config.remote) { if (config.remote.hasOwnProperty(method)) { proxy[method] = _createMethod(config.remote[method], method); } } } pub.down.init(); }, destroy: function() { for (var method in config.remote) { if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) { delete proxy[method]; } } pub.down.destroy(); } }); }; window.easyXDM = easyXDM; //returning the module instance return easyXDM; //})(window, document, location, window.setTimeout, decodeURIComponent, encodeURIComponent); }); /** * jquery.cors * * Implements CORS functionality for browsers supporting it natively with an automatic * fallback to easyXDM which implements cors via an embedded iframe or swf. To use it, * you must do two things: * * 1. Include the jquery plugin and use jquery.ajax calls as usual. * 2. Make jquery.cors available via require.js: * shim: { * 'jquery.cors/easyxdm/easyxdm': { exports: 'easyXDM' }, * } * paths: { * 'jquery.cors': 'lib/jquery.cors/0.1', // shim all paths * } * 3. Adjust jquery_cors_path to match the location where jquery.easyxdm.provider.js * 4. Adjust jquery.easyxdm.provider.js to allow calls from the domains needing CORS * 5. Adjust server to apply CORS headers for domains that should be able to. For * Allow-Credentials to work properly, the server must dynamically set the Allow-Origin * to match the Origin: from the request, you cannot simply use * * * Access-Control-Allow-Credentials: true * Access-Control-Allow-Origin: https://localhost:2443 * Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS * Access-Control-Allow-Headers: x-requested-with, content-type, accept, origin, authorization * */ define('jquery.cors/jquery.cors',['require','underscore','jquery','json2','console-shim','jquery.cors/easyxdm/easyxdm'],function (require) { var _ = require('underscore'), $ = require('jquery'), JSON = require('json2'), console = require('console-shim'), easyXDM = require('jquery.cors/easyxdm/easyxdm'), jquery_cors_path = '/static-optimized/js/lib/jquery.cors/0.1/', easyXDM_path = jquery_cors_path + '/easyxdm/', easyXDM_connections = {}, jquery_cors = {}; // $.support.cors = false; /** * Configure jQuery for CrossDomain CORS requests */ $.ajaxSetup({ xhrFields: { withCredentials: true } }); //console.log('using jquery cors'); /** * register a custom ajaxTransport, per * http://api.jquery.com/jQuery.ajaxTransport/ */ $.ajaxTransport("+*", function (options, originalOptions, jqXHR) { // Uncomment these two lines if want to test easyXDM // $.support.cors = false; // options.crossDomain = true; if (options.crossDomain && !$.support.cors) { var provider_base_url = "", regexp_results = options.url.match(/^(https?:\/\/[^\/]*)\/?.*/); if (regexp_results) { provider_base_url = regexp_results[1]; } if (!easyXDM_connections.hasOwnProperty(provider_base_url)) { console.log('creating easyXDM pool entry for ' + provider_base_url); easyXDM_connections[provider_base_url] = { callbackQueue: [], connection: undefined, error: undefined, state: "before" }; } console.log('jquery.cors easyXDM request for ' + options.url + ' using ' + provider_base_url); return provider_base_url === "" ? null : { send: function (headers, completeCallback) { jquery_cors.getConnection(provider_base_url, { 'success': function (easyXDM_connection) { function continuation_proxy(results) { console.log('easyXDM connection for ' + options.url + ' via ' + provider_base_url + ' continuation proxy excecuting'); completeCallback(results.status, results.statusText, results.responses, results.headers ); } originalOptions.context = null; easyXDM_connection.jquery_proxy(originalOptions, continuation_proxy); console.log('easyXDM connection for ' + options.url + ' via ' + provider_base_url + ' initialized'); }, 'error': function () { console.log('easyXDM connection for ' + provider_base_url + ' failed to initialize'); } }); }, 'abort': function () { console.log('easyXDM connection for ' + provider_base_url + ' aborted'); } }; } }); jquery_cors.doRequests = function (provider_base_url, callbacks) { var scoped_easyXDM = easyXDM.noConflict("jquery_easyXDM" + provider_base_url), remote_url = provider_base_url + jquery_cors_path + "jquery.easyxdm.provider.html"; console.log('doRequests for ' + provider_base_url); jquery_cors.easyXDM = scoped_easyXDM; easyXDM_connections[provider_base_url]['connection'] = new jquery_cors.easyXDM.Rpc( { channel: provider_base_url, remote: remote_url, swf: provider_base_url + easyXDM_path + "easyxdm.swf", onReady: function () { callbacks.success(easyXDM_connections[provider_base_url]['connection']); } }, { remote: { jquery_proxy: function() { console.log('jquery_proxy'); }}} ); }; jquery_cors.getConnection = function (provider_base_url, callbacks) { var i; switch (easyXDM_connections[provider_base_url]['state']) { case "before": console.log('before: ' + provider_base_url); easyXDM_connections[provider_base_url]['state'] = "working"; easyXDM_connections[provider_base_url]['callbackQueue'].push(callbacks); jquery_cors.doRequests(provider_base_url,{ 'success': function (singletonInstance) { console.log('doRequests.success for ' + provider_base_url); easyXDM_connections[provider_base_url]['state'] = "success"; easyXDM_connections[provider_base_url]['connection'] = singletonInstance; for (i = 0; i < easyXDM_connections[provider_base_url]['callbackQueue'].length; i++) { easyXDM_connections[provider_base_url]['callbackQueue'][i].success(easyXDM_connections[provider_base_url]['connection']); } easyXDM_connections[provider_base_url]['callbackQueue'] = []; }, 'failure': function (errorObj) { console.log('doRequests.failure for ' + provider_base_url); easyXDM_connections[provider_base_url]['state'] = "failure"; easyXDM_connections[provider_base_url]['error'] = errorObj; for (i = 0; i < easyXDM_connections[provider_base_url]['callbackQueue'].length; i++) { easyXDM_connections[provider_base_url]['callbackQueue'][i].failure(errorObj); } easyXDM_connections[provider_base_url]['callbackQueue'] = []; } }); break; case "working": console.log('working: ' + provider_base_url); easyXDM_connections[provider_base_url]['callbackQueue'].push(callbacks); break; case "success": console.log('success: ' + provider_base_url); callbacks.success(easyXDM_connections[provider_base_url]['connection']); break; case "failure": console.log('failure: ' + provider_base_url); callbacks.failure(easyXDM_connections[provider_base_url]['error']); break; default: console.log('invalid state: ' + provider_base_url); throw new Error("Invalid state: " + easyXDM_connections[provider_base_url]['state']); } }; window.$ = $; window.jquery_cors = jquery_cors; return jquery_cors; }); /*global define,setTimeout,clearTimeout*/ define('chiropractor/models/auth',['require','backbone','jquery','json-ie7','underscore','jquery.cookie'],function(require) { var Backbone = require('backbone'), $ = require('jquery'), JSON = require('json-ie7'), _ = require('underscore'), tokenCookie = 'wttoken', expirationWarningMinutes = 2, expirationWarningActive = false, expirationTimeoutId, expirationWarning, activeToken, getToken, setToken, clearToken; require('jquery.cookie'); expirationWarning = function() { Backbone.Events.trigger('authentication:expiration'); expirationWarningActive = true; expirationTimeoutId = setTimeout( function() { Backbone.Events.trigger('authentication:failure'); }, expirationWarningMinutes * 60 * 1000 ); }; getToken = function() { if (typeof(activeToken) === 'undefined') { activeToken = $.cookie(tokenCookie); } return activeToken; }; setToken = function(token) { var tokenComponents = token.split('::'), serverTime = tokenComponents[1], expireTime = tokenComponents[2], // We want an expiration alert to happen two minutes before the // token is going to expire. expirationTimeout = Math.max( 0, expireTime - serverTime - (expirationWarningMinutes * 60) ) * 1000; activeToken = token; $.cookie(tokenCookie, token); if (expirationTimeoutId) { clearTimeout(expirationTimeoutId); } if (expirationWarningActive) { Backbone.Events.trigger('authentication:renewal'); expirationWarningActive = false; } expirationTimeoutId = setTimeout(expirationWarning, expirationTimeout); }; clearToken = function() { activeToken = undefined; $.removeCookie(tokenCookie); if (expirationTimeoutId) { clearTimeout(expirationTimeoutId); } }; Backbone.Events.on( 'authentication:logout authentication:failure', clearToken ); return { sync: function(method, model, options) { var beforeSend = options.beforeSend, onError = options.error, onSuccess = options.success, self = this, opts = _(options).clone(); options.success = function(model, data, xhr) { var token = xhr.getResponseHeader('Authorization'); if (token) { setToken(token); } return onSuccess.apply(self, arguments); }; // This is a jQuery error handler. options.error = function(xhr, statusText, error) { if (xhr.status === 400) { // TODO: add logic to only trigger unauthenticated if the // bad request is due to malformed token Backbone.Events.trigger('authentication:failure', self, xhr); } if (xhr.status === 401) { Backbone.Events.trigger('authentication:failure', self, xhr); self.listenToOnce( Backbone.Events, 'authentication:success', function() { self.sync(method, model, opts); } ); } // Call the original onError handler. if (onError) { return onError.apply(self, arguments); } }; options.beforeSend = function(xhr) { // var token = getToken(); // if (!self.disableAuthToken && token) { // xhr.setRequestHeader( // 'Authorization', // token // ); // } if (beforeSend) { return beforeSend.apply(this, arguments); } }; }, cleanup: clearToken }; }); /** * Underscore mixins for deep objects * * Based on https://gist.github.com/echong/3861963 */ //(function() { /*global define*/ define('underscore.mixin.deepextend',['require','underscore'],function (require) { var _ = require('underscore'), arrays, basicObjects, deepClone, deepExtend, deepExtendCouple, isBasicObject, __slice = [].slice; deepClone = function (obj) { var func, isArr; if (!_.isObject(obj) || _.isFunction(obj)) { return obj; } if (obj instanceof Backbone.Collection || obj instanceof Backbone.Model) { return obj; } if (_.isDate(obj)) { return new Date(obj.getTime()); } if (_.isRegExp(obj)) { return new RegExp(obj.source, obj.toString().replace(/.*\//, "")); } isArr = _.isArray(obj || _.isArguments(obj)); func = function (memo, value, key) { if (isArr) { memo.push(deepClone(value)); } else { memo[key] = deepClone(value); } return memo; }; return _.reduce(obj, func, isArr ? [] : {}); }; isBasicObject = function (object) { if (object === null || object === undefined) { return false; } return (object.prototype === {}.prototype || object.prototype === Object.prototype) && _.isObject(object) && !_.isArray(object) && !_.isFunction(object) && !_.isDate(object) && !_.isRegExp(object) && !_.isArguments(object); }; basicObjects = function (object) { return _.filter(_.keys(object), function (key) { return isBasicObject(object[key]); }); }; arrays = function (object) { return _.filter(_.keys(object), function (key) { return _.isArray(object[key]); }); }; deepExtendCouple = function (destination, source, maxDepth) { var combine, recurse, sharedArrayKey, sharedArrayKeys, sharedObjectKey, sharedObjectKeys, _i, _j, _len, _len1; if (maxDepth === null) { maxDepth = 20; } if (maxDepth <= 0) { console.warn('_.deepExtend(): Maximum depth of recursion hit.'); return _.extend(destination, source); } sharedObjectKeys = _.intersection(basicObjects(destination), basicObjects(source)); recurse = function (key) { source[key] = deepExtendCouple(destination[key], source[key], maxDepth - 1); return source[key]; }; for (_i = 0, _len = sharedObjectKeys.length; _i < _len; _i += 1) { sharedObjectKey = sharedObjectKeys[_i]; recurse(sharedObjectKey); } sharedArrayKeys = _.intersection(arrays(destination), arrays(source)); combine = function (key) { source[key] = _.union(destination[key], source[key]); return source[key]; }; for (_j = 0, _len1 = sharedArrayKeys.length; _j < _len1; _j += 1) { sharedArrayKey = sharedArrayKeys[_j]; combine(sharedArrayKey); } return _.extend(destination, source); }; deepExtend = function () { var finalObj, maxDepth, objects, _i; objects = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), maxDepth = arguments[_i++]; if (!_.isNumber(maxDepth)) { objects.push(maxDepth); maxDepth = 20; } if (objects.length <= 1) { return objects[0]; } if (maxDepth <= 0) { return _.extend.apply(this, objects); } finalObj = objects.shift(); while (objects.length > 0) { finalObj = deepExtendCouple(finalObj, deepClone(objects.shift()), maxDepth); } return finalObj; }; _.mixin({ deepClone: deepClone, isBasicObject: isBasicObject, basicObjects: basicObjects, arrays: arrays, deepExtend: deepExtend }); }); /** * Main source */ (function(factory) { if (typeof define === 'function' && define.amd) { // AMD define('backbone.deep.model',['underscore', 'backbone'], factory); } else { // globals factory(_, Backbone); } }(function(_, Backbone) { /** * Takes a nested object and returns a shallow object keyed with the path names * e.g. { "level1.level2": "value" } * * @param {Object} Nested object e.g. { level1: { level2: 'value' } } * @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' } */ function objToPaths(obj) { var ret = {}, separator = DeepModel.keyPathSeparator; for (var key in obj) { var val = obj[key]; if (val && val.constructor === Object && !_.isEmpty(val)) { //Recursion for embedded objects var obj2 = objToPaths(val); for (var key2 in obj2) { var val2 = obj2[key2]; ret[key + separator + key2] = val2; } } else { ret[key] = val; } } return ret; } /** * @param {Object} Object to fetch attribute from * @param {String} Object path e.g. 'user.name' * @return {Mixed} */ function getNested(obj, path, return_exists) { var separator = DeepModel.keyPathSeparator; var fields = path.split(separator); var result = obj; return_exists || (return_exists === false); for (var i = 0, n = fields.length; i < n; i++) { if (return_exists && !_.has(result, fields[i])) { return false; } result = result[fields[i]]; if (result == null && i < n - 1) { result = {}; } if (typeof result === 'undefined') { if (return_exists) { return true; } return result; } } if (return_exists) { return true; } return result; } /** * @param {Object} obj Object to fetch attribute from * @param {String} path Object path e.g. 'user.name' * @param {Object} [options] Options * @param {Boolean} [options.unset] Whether to delete the value * @param {Mixed} Value to set */ function setNested(obj, path, val, options) { options = options || {}; var separator = DeepModel.keyPathSeparator; var fields = path.split(separator); var result = obj; for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) { var field = fields[i]; //If the last in the path, set the value if (i === n - 1) { options.unset ? delete result[field] : result[field] = val; } else { //Create the child object if it doesn't exist, or isn't an object if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) { result[field] = {}; } //Move onto the next part of the path result = result[field]; } } } function deleteNested(obj, path) { setNested(obj, path, null, { unset: true }); } var DeepModel = Backbone.Model.extend({ // Override constructor // Support having nested defaults by using _.deepExtend instead of _.extend constructor: function(attributes, options) { var defaults; var attrs = attributes || {}; this.cid = _.uniqueId('c'); this.attributes = {}; if (options && options.collection) this.collection = options.collection; if (options && options.parse) attrs = this.parse(attrs, options) || {}; if (_.result(this, 'defaults')) { defaults = _.result(this, 'defaults'); //<custom code> // Replaced the call to _.defaults with _.deepExtend. attrs = _.deepExtend({}, defaults, attrs); //</custom code> } this.set(attrs, options); this.changed = {}; this.initialize.apply(this, arguments); }, // Return a copy of the model's `attributes` object. toJSON: function(options) { return _.clone(this.attributes, true); }, // Override get // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name' get: function(attr) { return getNested(this.attributes, attr); }, // Override set // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name' set: function(key, val, options) { var attr, attrs, unset, changes, silent, changing, prev, current; if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. if (typeof key === 'object') { attrs = key; options = val || {}; } else { (attrs = {})[key] = val; } options || (options = {}); // Run validation. if (!this._validate(attrs, options)) return false; // Extract attributes and options. unset = options.unset; silent = options.silent; changes = []; changing = this._changing; this._changing = true; if (!changing) { this._previousAttributes = _.clone(this.attributes, true); this.changed = {}; } current = this.attributes, prev = this._previousAttributes; // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; //<custom code> attrs = objToPaths(attrs); //</custom code> // For each `set` attribute, update or delete the current value. for (attr in attrs) { val = attrs[attr]; //<custom code>: Using getNested, setNested and deleteNested if (!_.isEqual(getNested(current, attr), val)) changes.push(attr); if (!_.isEqual(getNested(prev, attr), val)) { setNested(this.changed, attr, val); } else { deleteNested(this.changed, attr); } unset ? deleteNested(current, attr) : setNested(current, attr, val); //</custom code> } // Trigger all relevant attribute changes. if (!silent) { if (changes.length) this._pending = true; //<custom code> var separator = DeepModel.keyPathSeparator; var alreadyTriggered = {}; // * @restorer for (var i = 0, l = changes.length; i < l; i++) { key = changes[i]; if (!alreadyTriggered.hasOwnProperty(key) || !alreadyTriggered[key]) { // * @restorer alreadyTriggered[key] = true; // * @restorer this.trigger('change:' + key, this, getNested(current, key), options); } // * @restorer var fields = key.split(separator); //Trigger change events for parent keys with wildcard (*) notation for (var n = fields.length - 1; n > 0; n--) { var parentKey = _.first(fields, n).join(separator), wildcardKey = parentKey + separator + '*'; if (!alreadyTriggered.hasOwnProperty(wildcardKey) || !alreadyTriggered[wildcardKey]) { // * @restorer alreadyTriggered[wildcardKey] = true; // * @restorer this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options); } // * @restorer // + @restorer if (!alreadyTriggered.hasOwnProperty(parentKey) || !alreadyTriggered[parentKey]) { alreadyTriggered[parentKey] = true; this.trigger('change:' + parentKey, this, getNested(current, parentKey), options); } // - @restorer } //</custom code> } } if (changing) return this; if (!silent) { while (this._pending) { this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; }, // Clear all attributes on the model, firing `"change"` unless you choose // to silence it. clear: function(options) { var attrs = {}; var shallowAttributes = objToPaths(this.attributes); for (var key in shallowAttributes) attrs[key] = void 0; return this.set(attrs, _.extend({}, options, {unset: true})); }, // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged: function(attr) { if (attr == null) return !_.isEmpty(this.changed); return getNested(this.changed, attr) !== undefined; }, // Return an object containing all the attributes that have changed, or // false if there are no changed attributes. Useful for determining what // parts of a view need to be updated and/or what attributes need to be // persisted to the server. Unset attributes will be set to undefined. // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. changedAttributes: function(diff) { //<custom code>: objToPaths if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false; //</custom code> var old = this._changing ? this._previousAttributes : this.attributes; //<custom code> diff = objToPaths(diff); old = objToPaths(old); //</custom code> var val, changed = false; for (var attr in diff) { if (_.isEqual(old[attr], (val = diff[attr]))) continue; (changed || (changed = {}))[attr] = val; } return changed; }, // Get the previous value of an attribute, recorded at the time the last // `"change"` event was fired. previous: function(attr) { if (attr == null || !this._previousAttributes) return null; //<custom code> return getNested(this._previousAttributes, attr); //</custom code> }, // Get all of the attributes of the model at the time of the previous // `"change"` event. previousAttributes: function() { //<custom code> return _.clone(this._previousAttributes, true); //</custom code> } }); //Config; override in your app to customise DeepModel.keyPathSeparator = '.'; //Exports Backbone.DeepModel = DeepModel; //For use in NodeJS if (typeof module != 'undefined') module.exports = DeepModel; return Backbone; })); // Backbone.Validation v0.8.2 // // Copyright (c) 2011-2013 Thomas Pedersen // Distributed under MIT License // // Documentation and full license available at: // http://thedersen.com/projects/backbone-validation (function (factory) { if (typeof exports === 'object') { module.exports = factory(require('backbone'), require('underscore')); } else if (typeof define === 'function' && define.amd) { define('backbone.validation',['backbone', 'underscore'], factory); } }(function (Backbone, _) { Backbone.Validation = (function(_) { // Default options // --------------- var defaultOptions = { forceUpdate: false, selector: 'name', labelFormatter: 'sentenceCase', valid: Function.prototype, invalid: Function.prototype }; // Helper functions // ---------------- // Formatting functions used for formatting error messages var formatFunctions = { // Uses the configured label formatter to format the attribute name // to make it more readable for the user formatLabel: function(attrName, model) { return defaultLabelFormatters[defaultOptions.labelFormatter](attrName, model); }, // Replaces nummeric placeholders like {0} in a string with arguments // passed to the function format: function() { var args = Array.prototype.slice.call(arguments), text = args.shift(); return text.replace(/\{(\d+)\}/g, function(match, number) { return typeof args[number] !== 'undefined' ? args[number] : match; }); } }; // Flattens an object // eg: // // var o = { // address: { // street: 'Street', // zip: 1234 // } // }; // // becomes: // // var o = { // 'address.street': 'Street', // 'address.zip': 1234 // }; var flatten = function (obj, into, prefix) { into = into || {}; prefix = prefix || ''; _.each(obj, function(val, key) { if (obj.hasOwnProperty(key)) { if (val && typeof val === 'object' && !( val instanceof Array || val instanceof Date || val instanceof RegExp || val instanceof Backbone.Model || val instanceof Backbone.Collection) ) { flatten(val, into, prefix + key + '.'); } else { into[prefix + key] = val; } } }); return into; }; // Validation // ---------- var Validation = (function() { // Returns an object with undefined properties for all // attributes on the model that has defined one or more // validation rules. var getValidatedAttrs = function(model) { return _.reduce(_.keys(_.result(model, 'validation') || {}), function(memo, key) { memo[key] = void 0; return memo; }, {}); }; // Looks on the model for validations for a specified // attribute. Returns an array of any validators defined, // or an empty array if none is defined. var getValidators = function(model, attr) { var attrValidationSet = model.validation ? _.result(model, 'validation')[attr] || {} : {}; // If the validator is a function or a string, wrap it in a function validator if (_.isFunction(attrValidationSet) || _.isString(attrValidationSet)) { attrValidationSet = { fn: attrValidationSet }; } // Stick the validator object into an array if (!_.isArray(attrValidationSet)) { attrValidationSet = [attrValidationSet]; } // Reduces the array of validators into a new array with objects // with a validation method to call, the value to validate against // and the specified error message, if any return _.reduce(attrValidationSet, function(memo, attrValidation) { _.each(_.without(_.keys(attrValidation), 'msg'), function(validator) { memo.push({ fn: defaultValidators[validator], val: attrValidation[validator], msg: attrValidation.msg }); }); return memo; }, []); }; // Validates an attribute against all validators defined // for that attribute. If one or more errors are found, // the first error message is returned. // If the attribute is valid, an empty string is returned. var validateAttr = function(model, attr, value, computed) { // Reduces the array of validators to an error message by // applying all the validators and returning the first error // message, if any. return _.reduce(getValidators(model, attr), function(memo, validator) { // Pass the format functions plus the default // validators as the context to the validator var ctx = _.extend({}, formatFunctions, defaultValidators), result = validator.fn.call(ctx, value, attr, validator.val, model, computed); if (result === false || memo === false) { return false; } if (result && !memo) { return _.result(validator, 'msg') || result; } return memo; }, ''); }; // Loops through the model's attributes and validates them all. // Returns and object containing names of invalid attributes // as well as error messages. var validateModel = function(model, attrs) { var error, invalidAttrs = {}, isValid = true, computed = _.clone(attrs), flattened = flatten(attrs); _.each(flattened, function(val, attr) { error = validateAttr(model, attr, val, computed); if (error) { invalidAttrs[attr] = error; isValid = false; } }); return { invalidAttrs: invalidAttrs, isValid: isValid }; }; // Contains the methods that are mixed in on the model when binding var mixin = function(view, options) { return { // Check whether or not a value, or a hash of values // passes validation without updating the model preValidate: function(attr, value) { var self = this, result = {}, error; if (_.isObject(attr)) { _.each(attr, function(value, key) { error = self.preValidate(key, value); if (error) { result[key] = error; } }); return _.isEmpty(result) ? undefined : result; } else { return validateAttr(this, attr, value, _.extend({}, this.attributes)); } }, // Check to see if an attribute, an array of attributes or the // entire model is valid. Passing true will force a validation // of the model. isValid: function(option) { var flattened = flatten(this.attributes); if (_.isString(option)) { return !validateAttr(this, option, flattened[option], _.extend({}, this.attributes)); } if (_.isArray(option)) { return _.reduce(option, function(memo, attr) { return memo && !validateAttr(this, attr, flattened[attr], _.extend({}, this.attributes)); }, true, this); } if (option === true) { this.validate(); } return this.validation ? this._isValid : true; }, // This is called by Backbone when it needs to perform validation. // You can call it manually without any parameters to validate the // entire model. validate: function(attrs, setOptions) { var model = this, validateAll = !attrs, opt = _.extend({}, options, setOptions), validatedAttrs = getValidatedAttrs(model), allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs), changedAttrs = flatten(attrs || allAttrs), result = validateModel(model, allAttrs); model._isValid = result.isValid; // After validation is performed, loop through all changed attributes // and call the valid callbacks so the view is updated. _.each(validatedAttrs, function(val, attr) { var invalid = result.invalidAttrs.hasOwnProperty(attr); if (!invalid) { opt.valid(view, attr, opt.selector); } }); // After validation is performed, loop through all changed attributes // and call the invalid callback so the view is updated. _.each(validatedAttrs, function(val, attr) { var invalid = result.invalidAttrs.hasOwnProperty(attr), changed = changedAttrs.hasOwnProperty(attr); if (invalid && (changed || validateAll)) { opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector); } }); // Trigger validated events. // Need to defer this so the model is actually updated before // the event is triggered. _.defer(function() { model.trigger('validated', model._isValid, model, result.invalidAttrs); model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs); }); // Return any error messages to Backbone, unless the forceUpdate flag is set. // Then we do not return anything and fools Backbone to believe the validation was // a success. That way Backbone will update the model regardless. if (!opt.forceUpdate && _.intersection(_.keys(result.invalidAttrs), _.keys(changedAttrs)).length > 0) { return result.invalidAttrs; } } }; }; // Helper to mix in validation on a model var bindModel = function(view, model, options) { _.extend(model, mixin(view, options)); }; // Removes the methods added to a model var unbindModel = function(model) { delete model.validate; delete model.preValidate; delete model.isValid; }; // Mix in validation on a model whenever a model is // added to a collection var collectionAdd = function(model) { bindModel(this.view, model, this.options); }; // Remove validation from a model whenever a model is // removed from a collection var collectionRemove = function(model) { unbindModel(model); }; // Returns the public methods on Backbone.Validation return { // Current version of the library version: '0.8.2', // Called to configure the default options configure: function(options) { _.extend(defaultOptions, options); }, // Hooks up validation on a view with a model // or collection bind: function(view, options) { options = _.extend({}, defaultOptions, defaultCallbacks, options); var model = options.model || view.model, collection = options.collection || view.collection; if (typeof model === 'undefined' && typeof collection === 'undefined') { throw 'Before you execute the binding your view must have a model or a collection.\n' + 'See http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.'; } if (model) { bindModel(view, model, options); } else if (collection) { collection.each(function(model) { bindModel(view, model, options); }); collection.bind('add', collectionAdd, {view: view, options: options}); collection.bind('remove', collectionRemove); } }, // Removes validation from a view with a model // or collection unbind: function(view, options) { options = _.extend({}, options); var model = options.model || view.model, collection = options.collection || view.collection; if (model) { unbindModel(model); } if (collection) { collection.each(function(model) { unbindModel(model); }); collection.unbind('add', collectionAdd); collection.unbind('remove', collectionRemove); } }, // Used to extend the Backbone.Model.prototype // with validation mixin: mixin(null, defaultOptions) }; }()); // Callbacks // --------- var defaultCallbacks = Validation.callbacks = { // Gets called when a previously invalid field in the // view becomes valid. Removes any error message. // Should be overridden with custom functionality. valid: function(view, attr, selector) { view.$('[' + selector + '~="' + attr + '"]') .removeClass('invalid') .removeAttr('data-error'); }, // Gets called when a field in the view becomes invalid. // Adds a error message. // Should be overridden with custom functionality. invalid: function(view, attr, error, selector) { view.$('[' + selector + '~="' + attr + '"]') .addClass('invalid') .attr('data-error', error); } }; // Patterns // -------- var defaultPatterns = Validation.patterns = { // Matches any digit(s) (i.e. 0-9) digits: /^\d+$/, // Matched any number (e.g. 100.000) number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/, // Matches a valid email address (e.g. mail@example.com) // eslint-disable-next-line no-control-regex email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i, // Mathes any valid url (e.g. http://www.xample.com) // eslint-disable-next-line no-control-regex url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i }; // Error messages // -------------- // Error message for the build in validators. // {x} gets swapped out with arguments form the validator. var defaultMessages = Validation.messages = { required: '{0} is required', acceptance: '{0} must be accepted', min: '{0} must be greater than or equal to {1}', max: '{0} must be less than or equal to {1}', range: '{0} must be between {1} and {2}', length: '{0} must be {1} characters', minLength: '{0} must be at least {1} characters', maxLength: '{0} must be at most {1} characters', rangeLength: '{0} must be between {1} and {2} characters', oneOf: '{0} must be one of: {1}', equalTo: '{0} must be the same as {1}', pattern: '{0} must be a valid {1}' }; // Label formatters // ---------------- // Label formatters are used to convert the attribute name // to a more human friendly label when using the built in // error messages. // Configure which one to use with a call to // // Backbone.Validation.configure({ // labelFormatter: 'label' // }); var defaultLabelFormatters = Validation.labelFormatters = { // Returns the attribute name with applying any formatting none: function(attrName) { return attrName; }, // Converts attributeName or attribute_name to Attribute name sentenceCase: function(attrName) { return attrName.replace(/(?:^\w|[A-Z]|\b\w)/g, function(match, index) { return index === 0 ? match.toUpperCase() : ' ' + match.toLowerCase(); }).replace(/_/g, ' '); }, // Looks for a label configured on the model and returns it // // var Model = Backbone.Model.extend({ // validation: { // someAttribute: { // required: true // } // }, // // labels: { // someAttribute: 'Custom label' // } // }); label: function(attrName, model) { return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model); } }; // Built in validators // ------------------- var defaultValidators = Validation.validators = (function() { // Use native trim when defined var trim = String.prototype.trim ? function(text) { return text === null ? '' : String.prototype.trim.call(text); } : function(text) { var trimLeft = /^\s+/, trimRight = /\s+$/; return text === null ? '' : text.toString().replace(trimLeft, '').replace(trimRight, ''); }; // Determines whether or not a value is a number var isNumber = function(value) { return _.isNumber(value) || (_.isString(value) && value.match(defaultPatterns.number)); }; // Determines whether or not a value is empty var hasValue = function(value) { return !(_.isNull(value) || _.isUndefined(value) || (_.isString(value) && trim(value) === '') || (_.isArray(value) && _.isEmpty(value))); }; return { // Function validator // Lets you implement a custom function used for validation fn: function(value, attr, fn, model, computed) { if (_.isString(fn)) { fn = model[fn]; } return fn.call(model, value, attr, computed); }, // Required validator // Validates if the attribute is required or not required: function(value, attr, required, model, computed) { var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required; if (!isRequired && !hasValue(value)) { return false; // overrides all other validators } if (isRequired && !hasValue(value)) { return this.format(defaultMessages.required, this.formatLabel(attr, model)); } }, // Acceptance validator // Validates that something has to be accepted, e.g. terms of use // `true` or 'true' are valid acceptance: function(value, attr, accept, model) { if (value !== 'true' && (!_.isBoolean(value) || value === false)) { return this.format(defaultMessages.acceptance, this.formatLabel(attr, model)); } }, // Min validator // Validates that the value has to be a number and equal to or greater than // the min value specified min: function(value, attr, minValue, model) { if (!isNumber(value) || value < minValue) { return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue); } }, // Max validator // Validates that the value has to be a number and equal to or less than // the max value specified max: function(value, attr, maxValue, model) { if (!isNumber(value) || value > maxValue) { return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue); } }, // Range validator // Validates that the value has to be a number and equal to or between // the two numbers specified range: function(value, attr, range, model) { if (!isNumber(value) || value < range[0] || value > range[1]) { return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]); } }, // Length validator // Validates that the value has to be a string with length equal to // the length value specified length: function(value, attr, length, model) { if (!hasValue(value) || trim(value).length !== length) { return this.format(defaultMessages.length, this.formatLabel(attr, model), length); } }, // Min length validator // Validates that the value has to be a string with length equal to or greater than // the min length value specified minLength: function(value, attr, minLength, model) { if (!hasValue(value) || trim(value).length < minLength) { return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength); } }, // Max length validator // Validates that the value has to be a string with length equal to or less than // the max length value specified maxLength: function(value, attr, maxLength, model) { if (!hasValue(value) || trim(value).length > maxLength) { return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength); } }, // Range length validator // Validates that the value has to be a string and equal to or between // the two numbers specified rangeLength: function(value, attr, range, model) { if (!hasValue(value) || trim(value).length < range[0] || trim(value).length > range[1]) { return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]); } }, // One of validator // Validates that the value has to be equal to one of the elements in // the specified array. Case sensitive matching oneOf: function(value, attr, values, model) { if (!_.include(values, value)) { return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', ')); } }, // Equal to validator // Validates that the value has to be equal to the value of the attribute // with the name specified equalTo: function(value, attr, equalTo, model, computed) { if (value !== computed[equalTo]) { return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model)); } }, // Pattern validator // Validates that the value has to match the pattern specified. // Can be a regular expression or the name of one of the built in patterns pattern: function(value, attr, pattern, model) { if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) { return this.format(defaultMessages.pattern, this.formatLabel(attr, model), pattern); } } }; }()); return Validation; }(_)); return Backbone.Validation; })); /* START_TEMPLATE */ define('hbs!chiropractor/views/templates/error/modelfetch',['hbs','handlebars'], function( hbs, Handlebars ) { var t = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) { helpers = helpers || Handlebars.helpers; var stack1, stack2, foundHelper, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression; stack1 = {}; stack2 = depth0.response; stack1['response'] = stack2; stack2 = depth0.url; stack1['url'] = stack2; foundHelper = helpers.row; stack1 = foundHelper ? foundHelper.call(depth0, "error", depth0, {hash:stack1}) : helperMissing.call(depth0, "row", "error", depth0, {hash:stack1}); return escapeExpression(stack1); }); Handlebars.registerPartial('chiropractor_views_templates_error_modelfetch', t); return t; }); /* END_TEMPLATE */ /*global define,setTimeout,clearTimeout*/ define('chiropractor/models',['require','jquery.cors/jquery.cors','backbone','underscore','json-ie7','jquery','./models/auth','backbone.deep.model','backbone.validation','hbs!./views/templates/error/modelfetch','underscore.mixin.deepextend'],function (require) { require('jquery.cors/jquery.cors'); var Backbone = require('backbone'), _ = require('underscore'), JSON = require('json-ie7'), $ = require('jquery'), auth = require('./models/auth'), BackboneDeepModel = require('backbone.deep.model'), Validation = require('backbone.validation'), TemplateError = require('hbs!./views/templates/error/modelfetch'), Base, Revision, UserAgent, RegExpression, ASSETS_BASE_URL; // Add Wiser specific settings Issue #31 if (window && window.Wiser && window.Wiser.ASSETS_BASE_URL) { ASSETS_BASE_URL = window.Wiser.ASSETS_BASE_URL; } else { ASSETS_BASE_URL = ''; } require('underscore.mixin.deepextend'); Base = BackboneDeepModel.DeepModel.extend({ errorHandler: function(response) { var errorMessage; switch (response.status) { case 0: errorMessage = "The API was unreachable"; break; case 503: errorMessage = "There was an Error Communicating with the API"; break; default: } $('body').before(TemplateError({ url: this.url , errorMessage: errorMessage, response: response })); }, successHandler: function(model, response, options) { }, sync: function (method, model, options) { // Setup the authentication handlers for the BaseModel // auth.sync.call(this, method, model, options); switch (method) { case 'read': //Empty the error message box for other fetches $('#chiropractor-error-box').empty(); if (this.enableErrorHandler) { options.error = this.errorHandler; // Timeout set to 30 seconds. options.timeout = 30000; } break; case 'update': // Remove Wiser specific settings Issue #31 model.unset('Wiser'); break; default: } return Backbone.Model.prototype.sync.call( this, method, model, options ); }, parse: function (resp, options) { options = options || {}; // We need to unwrap the old WiserTogether API envelop format. if (resp.data && resp.meta) { if (parseInt(resp.meta.status, 10) >= 400) { options.legacyError = true; if (resp.meta.errors && resp.meta.errors.form) { this.validationError = resp.meta.errors.form; this.trigger( 'invalid', this, this.validationError, _.extend(options || {}, { validationError: this.validationError }) ); } else { this.trigger('error', this, resp.data, options); if (options.error) { options.error(this, resp.data, options); } } // We do not want an error response to update the model // attributes (returning an empty object leaves the model // state as it was return {}; } // Add Wiser specific settings Issue #31 resp.Wiser = {}; resp.Wiser.ASSETS_BASE_URL = ASSETS_BASE_URL; return resp.data; } // Add Wiser specific settings Issue #31 resp.Wiser = {}; resp.Wiser.ASSETS_BASE_URL = ASSETS_BASE_URL; return Backbone.Model.prototype.parse.apply(this, arguments); }, fieldId: function (field, prefix) { prefix = prefix || 'formfield'; return [prefix, field, this.cid].join('-'); }, set: function (attrs, options) { // We need to allow the legacy errors to short circuit the Backbone // success handler in the case of a legacy server error. if (options && options.legacyError) { delete options.legacyError; return false; } return BackboneDeepModel.DeepModel.prototype.set.apply(this, arguments); } }); _.extend(Base.prototype, Validation.mixin); return { Base: Base, cleanup: auth.cleanup }; }); /*global define*/ define('chiropractor/collections',['require','jquery.cors/jquery.cors','backbone','underscore','jquery','hbs!./views/templates/error/modelfetch','underscore.mixin.deepextend'],function (require) { require('jquery.cors/jquery.cors'); var Backbone = require('backbone'), _ = require('underscore'), Base, $ = require('jquery'), TemplateError = require('hbs!./views/templates/error/modelfetch'), ASSETS_BASE_URL; // Add Wiser specific settings Issue #31 if (window && window.Wiser && window.Wiser.ASSETS_BASE_URL) { ASSETS_BASE_URL = window.Wiser.ASSETS_BASE_URL; } else { ASSETS_BASE_URL = ''; } require('underscore.mixin.deepextend'); Base = Backbone.Collection.extend({ errorHandler: function (response) { var errorMessage; switch (response.status) { case 0: errorMessage = "The API was unreachable"; break; case 503: errorMessage = "There was an Error Communicating with the API"; break; default: } $('body').before(TemplateError({ url: this.url, errorMessage: errorMessage, response: response })); }, sync: function (method, model, options) { switch (method) { case 'read': //Empty the error message box for other fetches $('#chiropractor-error-box').empty(); if (this.enableErrorHandler) { options.error = this.errorHandler; // Timeout set to 30 seconds. options.timeout = 30000; } break; case 'update': // Remove Wiser specific settings Issue #31 model.unset('Wiser'); break; default: } return Backbone.Collection.prototype.sync.call( this, method, model, options ); }, parse: function (resp, options) { // Add Wiser specific settings Issue #31 resp.Wiser = {}; resp.Wiser.ASSETS_BASE_URL = ASSETS_BASE_URL; return Backbone.Collection.prototype.parse.apply(this, arguments); } }); return { Base: Base }; }); /*global define*/ define('chiropractor/routers',['require','backbone'],function(require) { var Backbone = require('backbone'), Base; Base = Backbone.Router.extend({ }); return { Base: Base }; }); /*global define*/ (function(window) { define('chiropractor/browser',['require'],function(require) { var ieVersion = (function() { var rv = -1; // Return value assumes failure. if (window.navigator.appName === 'Microsoft Internet Explorer') { var ua = window.navigator.userAgent; var re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})"); if (re.exec(ua) !== null) { rv = parseFloat(RegExp.$1); } } return rv; }()); return { isOldIE: ieVersion !== -1 && ieVersion < 9, isIE8or9: ieVersion !== -1 && ieVersion < 10, window: window, navigator: window.navigator, document: window.document }; }); }(this)); /*global define,alert*/ define('chiropractor/debug',['require','exports','module','chiropractor/browser'],function(require, exports, module) { var window = require('chiropractor/browser').window, console = window.console; if (require.specified('console')) { require(['console'], function(mod) { console = mod; }); } function isInspectorOpen() { if (console.firebug) { return true; } else if (console.profile) { console.profile(); console.profileEnd(); if (console.clear) { console.clear(); } if (console.profiles && console.profiles.length > 0) { return true; } } if ((window.outerHeight - window.innerHeight) > 100) { return true; } return false; } if (module.config().enabled) { window.onerror = function(message, url, linenumber) { alert("JavaScript error: " + message + " on line " + linenumber + " for " + url); }; } }); /*global define*/ define('chiropractor/hbs/ifequal',['require','handlebars'],function(require) { var Handlebars = require('handlebars'); Handlebars.registerHelper('ifequal', function(val1, val2, options) { if (val1 === val2) { return options.fn(this); } else { return options.inverse(this); } }); }); /*global define*/ define('chiropractor/hbs/log',['require','handlebars','json-ie7'],function(require) { var Handlebars = require('handlebars'), JSON = require('json-ie7'); function log(context, options) { console.log(JSON.stringify(context)); } Handlebars.registerHelper('log', log); return log; }); /*global define*/ define('chiropractor/hbs',['require','./hbs/view','./hbs/ifequal','./hbs/log'],function(require) { require('./hbs/view'); require('./hbs/ifequal'); require('./hbs/log'); }); /*global define*/ define('chiropractor/main',['require','backbone','underscore','backbone.subroute','./views','./models','./collections','./routers','./debug','./hbs'],function(require) { var Backbone = require('backbone'), _ = require('underscore'), SubRoute = require('backbone.subroute'), Views = require('./views'), Models = require('./models'), Collections = require('./collections'), Routers = require('./routers'), Channels = {}, Subscribe, Publish; require('./debug'); require('./hbs'); Subscribe = function(channel,object) { //We want to be able to push new listeners into Channels[channel] if (!Channels[channel]) { Channels[channel] = []; } Channels[channel].push(object); }; Publish = function(channel,options) { if (Channels[channel]) { var listeners = Channels[channel]; _.each(listeners, function(listener) { listener.trigger(channel,options); }); } }; return { // Channel events Subscribe: Subscribe, Publish: Publish, Channels: Channels, Collection: Collections.Base, Collections: Collections, Events: Backbone.Events, history: Backbone.history, Model: Models.Base, Models: Models, Router: Routers.Base, SubRoute: SubRoute, View: Views.Base, Views: Views }; }); define('chiropractor', ['chiropractor/main'], function (main) { return main; }); }());