%PDF- %PDF-
| Direktori : /proc/self/root/data/old/home/stash/atlassian-stash/static/bower/backbone-brace/ |
| Current File : //proc/self/root/data/old/home/stash/atlassian-stash/static/bower/backbone-brace/backbone.brace.js |
(function () {
// node / browser imports, copied from here
var root = this;
var Brace;
if (typeof exports !== 'undefined') {
Brace = exports;
} else {
Brace = root.Brace = {};
}
var _ = root._;
if (!_ && (typeof require !== 'undefined')){ _ = require('underscore'); }
var Backbone = root.Backbone;
if (!Backbone && (typeof require !== 'undefined')) { Backbone = require('backbone'); }
// ## Helper functions
// Given an array, this function will return an object where each
// property name is an element of the original array. The value
// of each property will be null.
// Given anything else, this function will return the object untouched.
/**
* This function is used to ensure a consistent shape for the namedAttributes property.
* namedAttributes accepts two formats:
* - An array like ['attr1', 'attr2'] if you don't care about the type of any attribute.
* - An object like { attr1 : type1, attr2 : type2 } to specify an expected type.
*
* @param maybeArray {*} thing to transform into an object
*/
function asObject(maybeArray) {
if (_.isArray(maybeArray)) {
return _.reduce(maybeArray, function(obj, prop) {
obj[prop] = null;
return obj;
}, {});
}
return maybeArray;
}
// Return a value of the specified type, generated from the value parameter.
// If type conversion is necessary, this function will generate a new object using
// `new type(value)`
//
// `value` Any value. null and undefined values will be untouched.
//
// `type` When given:
//
// * a falsy value: this function will do no type conversion.
// * a string: this function will throw if `typeof value !== type`, and return value otherwise.
// * an Array: this function will be recursively called for each element in value using
// type's first element as the type. E.g.,
// `ensureType([ Number ], [ 1 ])` will recursively call `ensureType(Number, 1)`
// It will return a new array consisting of the result of each recursive call.
// * a Backbone.Collection constructor: this function may be recursively called for each element in
// value using type.model as the type. E.g.,
// `ensureType({ model : Number, __proto__ : Backbone.Collection.prototype }, [ 1 ])` will
// recursively call `ensureType(Number, 1)`
// It will return a Backbone.Collection via new type({array of recursive results})
// * a function: This will check value instanceof type, and if false, will return `new type(value)`
// Otherwise it will return value directly
/**
* @param value {*}
* @param type {Array|function(new:*, *)|string|false|null|undefined}
*/
function ensureType(type, value) {
/*jshint newcap: false */
if (!type || value == null) {
return value;
}
if (typeof type === 'string' || type instanceof String) {
if (typeof value !== ""+type) {
throw "The typeof " + value + " is " + typeof value + " but expected it to be " + type;
}
return value;
}
if (_.isArray(type) || type === Array) {
if (!isArrayLike(value)) {
throw "Array type expected, but nonnull, non-Array value provided.";
}
return type === Array || !type[0] ?
value :
_.map(value, _.bind(ensureType, null, type[0]));
}
if (typeof type !== 'function') {
throw "Invalid expected type " + type +
'. Should be falsy, String, Array, Backbone.Collection constructor, or function.';
}
if (value instanceof type) {
return value;
}
if (isCollectionConstructor(type)) {
return new type(ensureType([ type.model ], value));
}
return new type(value);
}
// Returns true if obj is extend()'ed from Backbone.Collection (or from another collection)
/**
* @param obj {*} object to check
* @param rootConstructor {?function(new:Backbone.Collection)} optional constructor that inherits from
* Backbone.Collection. Will check that obj is extend()'ed from this instead of Backbone.Collection.
*/
function isCollectionConstructor(obj, rootConstructor) {
return obj && (
// obj *distantly* extends Backbone.Collection (most likely case)
obj.__super__ instanceof (rootConstructor || Backbone.Collection) ||
// obj *directly* extends Backbone.Collection (e.g., Brace.Collection)
// !(fn.prototype instanceof fn), so the above check doesn't catch this case.
obj.__super__ === (rootConstructor || Backbone.Collection).prototype ||
// obj *is* Backbone.Collection
obj === (rootConstructor || Backbone.Collection)
);
}
// array-like is currently defined by "has a length property, and is not a string or
// function or Backbone.Collection."
// Backbone.Collections are excluded because you can't do collection[0] to access models.
function isArrayLike(value) {
return _.has(value,'length') && !(
value instanceof String ||
_.has({'string':1, 'function':1}, typeof value) ||
value instanceof Backbone.Collection
);
}
// With namedAttributes, we want to allow both the mixin, and the extender to define types.
// We want to take the stricter of the two types where possible.
// Where the types conflict, throw an error.
function nonConflictedTypes(obj, refObj) {
var newObj = {};
_.each(obj, function(val, key) {
if (!refObj[key] || assumes(val, refObj[key])) {
newObj[key] = val;
} else if (!val || assumes(refObj[key], val)) {
return;
} else {
throw key + " has conflicted type descriptors.";
}
});
return newObj;
}
// One type *assumes* another when the conditions for meeting its
// type-check are a super set of the assumed type's conditions.
// E.g., `[ 'string' ]` assumes `Array` because you can't have an array of strings without an array.
function assumes(assumer, assumed) {
if (!assumed || assumed === assumer) {
return true;
}
// if it's a string, only the previous strict equality check would have sufficed.
if (!assumer || typeof assumer === 'string') {
return false;
}
if (assumer instanceof Array) {
return assumed === Array ||
(assumed instanceof Array && assumes(assumer[0], assumed[0]));
}
if (typeof assumed !== 'function') {
return false;
}
if (isCollectionConstructor(assumed)) {
return isCollectionConstructor(assumer, assumed);
}
return assumer.prototype instanceof assumed;
}
/**
* @param {Object?} object
* @return {Object} plain object
*/
function nestedToJSON(object) {
if (_.isObject(object)) {
return _.reduce(object, function(memo, value, key) {
if (value && _.isFunction(value.toJSON)) {
memo[key] = value.toJSON();
} else if (_.isArray(value)) {
memo[key] = _.map(value, function(el) {
if (el && _.isFunction(el.toJSON)) {
return el.toJSON();
} else {
return el;
}
});
}
return memo;
}, object);
} else {
return object;
}
}
function createToJSON(previousToJSON) {
return function toJSON() {
var json = previousToJSON.call(this);
return nestedToJSON(json);
};
}
// ## Brace.Mixins ##
// Mixin utilities
Brace.Mixins = {
// Creates a camelCased method name
createMethodName: function(prefix, suffix) {
return prefix + suffix.charAt(0).toUpperCase() + suffix.substr(1);
},
// Applies a mixin to the given constructor's prototype.
applyMixin: function(ctor, mixin) {
_.forEach(_.keys(mixin), function(key) {
var proto = ctor.prototype;
// `initialize` is not mixed in - we compose the mixin's initialize with the existing initialize method (if it exists).
if ("initialize" === key) {
var oldInitialize = proto.initialize;
proto.initialize = function() {
if (oldInitialize) {
oldInitialize.apply(this, arguments);
}
mixin.initialize.apply(this, arguments);
};
return;
}
// `validate` is not mixed in - we compose the mixin's validate with the existing validate method (if it exists).
if ("validate" === key) {
var oldValidate = proto.validate;
proto.validate = function() {
if (oldValidate) {
var errors = oldValidate.apply(this, arguments);
if (errors) {
return errors;
}
}
return mixin.validate.apply(this, arguments);
};
return;
}
// `defaults` are not mixed in - we compose the mixin's defaults with existing defaults if they exist
if ("defaults" === key) {
var defaults = proto.defaults || (proto.defaults = {});
var mixinDefaults = mixin[key];
for (var id in mixinDefaults) {
if (defaults.hasOwnProperty(id)) {
throw "Mixin error: class already has default '" + id + "' defined";
}
defaults[id] = mixinDefaults[id];
}
return;
}
// `namedAttributes` are added to the mixin, and we mixin in getters and setters for each attribute.
if ("namedAttributes" === key) {
var protoAttrs = asObject(proto.namedAttributes) || {};
var mixinAttrs = asObject(mixin[key]);
proto.namedAttributes = _.extend(protoAttrs, nonConflictedTypes(mixinAttrs, protoAttrs));
return;
}
// `namedEvents` are added to the mixin, and we mix in on and trigger methods for each event.
if ("namedEvents" === key) {
// `events` must be an array
if (!_.isArray(mixin[key])) {
throw "Expects events member on mixin to be an array";
}
if (!proto.namedEvents) {
proto.namedEvents = [];
}
proto.namedEvents = _.uniq(proto.namedEvents.concat(mixin[key]));
return;
}
// Name collisions with other mixins or or the object we're mixing into result in violent and forceful disapproval.
if (proto.hasOwnProperty(key)) {
throw "Mixin error: class already has property '" + key + "' defined";
}
proto[key] = mixin[key];
}, this);
}
};
// ## Brace.AttributesMixinCreator ##
Brace.AttributesMixinCreator = {
// Creates a mixin of getter and setter methods for each item in the given attribute list.
// A getter and setter for `id` is always generated.
create: function(attributes) {
var methods = {};
if (!attributes) {
attributes = {};
}
if (!_.has(attributes, "id")) {
attributes.id = null;
}
_.each(attributes, function (expectedType, attrName) {
// TODO: has, escape, unset
var setter = Brace.Mixins.createMethodName("set", attrName);
methods[setter] = function (val,options) {
return this.set(attrName, val, options);
};
var getter = Brace.Mixins.createMethodName("get", attrName);
methods[getter] = function () {
return this.get(attrName);
};
});
return methods;
},
/**
* See JSDoc for this function in the backbone.brace file.
*
* Expose the ensureType function for people with custom constructors that don't call
* .set() to initialize attributes. Hopefully this is rarely used, as I would
* consider not calling .set() to be a code smell.
*
* @param type
* @param value
*/
ensureType : ensureType
};
// ## Brace.EventsMixinCreator ##
Brace.EventsMixinCreator = {
// Creates a mixin of on and trigger methods for each item in the given list of events.
create: function(events) {
var eventMethods = {};
var createEvent = function(eventName) {
// TODO: off
var binder = Brace.Mixins.createMethodName("on", eventName);
eventMethods[binder] = function() {
return this.on.apply(this, [eventName].concat(_.toArray(arguments)));
};
var trigger = Brace.Mixins.createMethodName("trigger", eventName);
eventMethods[trigger] = function() {
return this.trigger.apply(this, [eventName].concat(_.toArray(arguments)));
};
};
_.each(events, _.bind(createEvent,this));
return eventMethods;
}
};
// Generates an `extend` method that overrides Backbone's default `extend`. The new extend calls Backbone's `extend`, then:
//
// * Adds all mixins specified in the `mixins` array
// * Adds a `Brace.EventsMixinCreator` to mix in on and trigger methods for events specified in the `namedEvents` array
// * Adds a `Brace.AttributesMixinCreator` to mix in get and set methods for attributes specified in the `attributes` array
function generateMixinExtend(oldExtend) {
return function newExtend(protoProps, classProps) {
var child;
var cleanProtoProps = _.extend({}, protoProps);
// Remove `mixins` - we don't want to see them on the created prototype. Note that we do want to see `namedAttributes` and `namedEvents` for debugging
var mixins;
if (protoProps && protoProps.mixins) {
mixins = protoProps.mixins;
delete cleanProtoProps.mixins;
}
child = oldExtend.call(this, cleanProtoProps, classProps);
if (this.prototype.namedEvents) {
Brace.Mixins.applyMixin(child, { namedEvents : this.prototype.namedEvents });
}
if (this.prototype.namedAttributes) {
Brace.Mixins.applyMixin(child, { namedAttributes : this.prototype.namedAttributes });
}
if (mixins) {
_.each(protoProps.mixins, function(mixin) {
Brace.Mixins.applyMixin(child, mixin);
});
}
if (child.prototype.namedEvents) {
Brace.Mixins.applyMixin(child, Brace.EventsMixinCreator.create(child.prototype.namedEvents));
}
if (child.prototype.namedAttributes) {
child.prototype.namedAttributes = asObject(child.prototype.namedAttributes);
Brace.Mixins.applyMixin(child, Brace.AttributesMixinCreator.create(child.prototype.namedAttributes));
}
if (child.prototype.toJSON) {
child.prototype.toJSON = createToJSON(child.prototype.toJSON);
}
child.extend = newExtend;
return child;
};
}
// Overrides Backbone's `get` and `set` methods to validate that the attribute being get / set is a namedAttribute.
function overrideSetGet(ctor, childCtor) {
var proto = ctor.prototype;
var childProto = childCtor.prototype;
var oldSet = proto.set;
childProto.set = function(key, value, options) {
// TODO: has, escape, unset
var attrs,
attributes = this.namedAttributes;
if (!attributes || key == null) {
return oldSet.apply(this, arguments);
}
if (_.isObject(key)) {
attrs = _.clone(key);
options = value;
} else {
attrs = {};
attrs[key] = value;
}
for (var attr in attrs) {
if (!_.has(attrs, attr)) {
continue;
}
if (!_.has(attributes, attr)) {
throw "Attribute '" + attr + "' does not exist";
}
attrs[attr] = ensureType(attributes[attr], attrs[attr]);
}
return oldSet.call(this, attrs, options);
};
var oldGet = proto.get;
childProto.get = function(attr) {
if (this.namedAttributes && !_.has(this.namedAttributes, attr)) {
throw "Attribute '" + attr + "' does not exist";
}
return oldGet.apply(this, arguments);
};
}
// Applies extensions to the given constructor function. Sets `extend` to a method generated by `generateMixinExtend`
function applyExtensions(ctor) {
var child = ctor.extend();
var oldExtend = ctor.extend;
child.extend = generateMixinExtend(oldExtend);
return child;
}
// Applies extensions to the given constructor function. Sets `extend` to a method generated by `generateMixinExtend`
function applyModelExtensions(ctor) {
var child = applyExtensions(ctor);
overrideSetGet(ctor, child);
return child;
}
// Extend base Backbone classes
Brace.Model = applyModelExtensions(Backbone.Model);
Brace.Collection = applyExtensions(Backbone.Collection);
Brace.View = applyExtensions(Backbone.View);
Brace.Router = applyExtensions(Backbone.Router);
}());