Adds backbone-nested
authordsc <dsc@wikimedia.org>
Wed, 14 Mar 2012 08:39:36 +0000 (01:39 -0700)
committerdsc <dsc@wikimedia.org>
Wed, 14 Mar 2012 08:39:36 +0000 (01:39 -0700)
static/vendor/backbone.nested-1.1.0.js [new file with mode: 0644]
static/vendor/backbone.nested-1.1.0.min.js [new file with mode: 0644]
static/vendor/backbone.nested.js [new symlink]
static/vendor/backbone.nested.min.js [new symlink]
static/vendor/require.min.js

diff --git a/static/vendor/backbone.nested-1.1.0.js b/static/vendor/backbone.nested-1.1.0.js
new file mode 100644 (file)
index 0000000..3002834
--- /dev/null
@@ -0,0 +1,228 @@
+/**
+ * Backbone-Nested 1.1.0 - An extension of Backbone.js that keeps track of nested attributes
+ *
+ * http://afeld.github.com/backbone-nested/
+ *
+ * Copyright (c) 2011-2012 Aidan Feldman
+ * MIT Licensed (LICENSE)
+ */
+/*global Backbone, _, $ */
+(function(){
+  'use strict';
+
+  Backbone.NestedModel = Backbone.Model.extend({
+
+    get: function(attrStrOrPath, opts){
+      opts = opts || {};
+
+      var attrPath = Backbone.NestedModel.attrPath(attrStrOrPath),
+        childAttr = attrPath[0],
+        result = Backbone.NestedModel.__super__.get.call(this, childAttr);
+      
+      // walk through the child attributes
+      for (var i = 1; i < attrPath.length; i++){
+        if (!result){
+          // value not present
+          break;
+        }
+        childAttr = attrPath[i];
+        result = result[childAttr];
+      }
+
+      // check if the result is an Object, Array, etc.
+      if (!opts.silent && _.isObject(result) && window.console){
+        window.console.log("Backbone-Nested syntax is preferred for accesing values of attribute '" + attrStrOrPath + "'.");
+      }
+      // else it's a leaf
+
+      return result;
+    },
+
+    has: function(attr){
+      // for some reason this is not how Backbone.Model is implemented - it accesses the attributes object directly
+      var result = this.get(attr, {silent: true});
+      return !(result === null || _.isUndefined(result));
+    },
+
+    set: function(key, value, opts){
+      var attrs;
+      if (_.isObject(key) || key == null) {
+        attrs = key;
+        opts = value;
+      } else {
+        attrs = {};
+        attrs[key] = value;
+      }
+      opts = opts || {};
+
+      var newAttrs = Backbone.NestedModel.deepClone(this.attributes),
+        attrVal, attrPath, attrObj;
+      
+      for (var attrStr in attrs){
+        attrPath = Backbone.NestedModel.attrPath(attrStr);
+        attrObj = Backbone.NestedModel.createAttrObj(attrPath, attrs[attrStr]);
+
+        this._mergeAttrs(newAttrs, attrObj, opts);
+      }
+
+      return Backbone.NestedModel.__super__.set.call(this, newAttrs, opts);
+    },
+
+    unset: function(attrStr, opts){
+      opts = _.extend({}, opts, {unset: true});
+      this.set(attrStr, null, opts);
+
+      return this;
+    },
+
+    add: function(attrStr, value, opts){
+      var current = this.get(attrStr, {silent: true});
+      this.set(attrStr + '[' + current.length + ']', value, opts);
+    },
+
+    remove: function(attrStr, opts){
+      opts = opts || {};
+
+      var attrPath = Backbone.NestedModel.attrPath(attrStr),
+        aryPath = _.initial(attrPath),
+        val = this.get(aryPath, {silent: true}),
+        i = _.last(attrPath);
+
+      if (!_.isArray(val)){
+        throw new Error("remove() must be called on a nested array");
+      }
+
+      // only trigger if an element is actually being removed
+      var trigger = !opts.silent && (val.length > i + 1),
+        oldEl = val[i];
+
+      // remove the element from the array
+      val.splice(i, 1);
+      this.set(attrStr, val, opts);
+
+      if (trigger){
+        this.trigger('remove:' + Backbone.NestedModel.createAttrStr(aryPath), this, oldEl);
+      }
+
+      return this;
+    },
+
+    toJSON: function(){
+      var json = Backbone.NestedModel.__super__.toJSON.apply(this);
+      return Backbone.NestedModel.deepClone(json);
+    },
+
+
+    // private
+
+    _mergeAttrs: function(dest, source, opts, stack){
+      stack = stack || [];
+
+      _.each(source, function(sourceVal, prop){
+        if (prop === '-1'){
+          prop = dest.length;
+        }
+
+        var destVal = dest[prop],
+          newStack = stack.concat([prop]),
+          attrStr;
+
+        var isChildAry = _.isObject(sourceVal) && _.any(sourceVal, function(val, attr){
+          return attr === '-1' || _.isNumber(attr);
+        });
+
+        if (isChildAry && !_.isArray(destVal)){
+          destVal = dest[prop] = [];
+        }
+
+        if (prop in dest && _.isObject(sourceVal) && _.isObject(destVal)){
+          destVal = dest[prop] = this._mergeAttrs(destVal, sourceVal, opts, newStack);
+        } else {
+          var oldVal = destVal;
+
+          destVal = dest[prop] = sourceVal;
+
+          if (_.isArray(dest) && !opts.silent){
+            attrStr = Backbone.NestedModel.createAttrStr(stack);
+
+            if (!oldVal && destVal){
+              this.trigger('add:' + attrStr, this, destVal);
+            } else if (oldVal && !destVal){
+              this.trigger('remove:' + attrStr, this, oldVal);
+            }
+          }
+        }
+        
+        // let the superclass handle change events for top-level attributes
+        if (!opts.silent && newStack.length > 1){
+          attrStr = Backbone.NestedModel.createAttrStr(newStack);
+          this.trigger('change:' + attrStr, this, destVal);
+        }
+      }, this);
+
+      return dest;
+    }
+
+  }, {
+    // class methods
+
+    attrPath: function(attrStrOrPath){
+      var path;
+      
+      if (_.isString(attrStrOrPath)){
+        // change all appends to '-1'
+        attrStrOrPath = attrStrOrPath.replace(/\[\]/g, '[-1]');
+        // TODO this parsing can probably be more efficient
+        path = (attrStrOrPath === '') ? [''] : attrStrOrPath.match(/[^\.\[\]]+/g);
+        path = _.map(path, function(val){
+          // convert array accessors to numbers
+          return val.match(/^\d+$/) ? parseInt(val, 10) : val;
+        });
+      } else {
+        path = attrStrOrPath;
+      }
+
+      return path;
+    },
+
+    createAttrObj: function(attrStrOrPath, val){
+      var attrPath = this.attrPath(attrStrOrPath),
+        newVal;
+
+      switch (attrPath.length){
+        case 0:
+          throw "no valid attributes: '" + attrStrOrPath + "'";
+        
+        case 1: // leaf
+          newVal = val;
+          break;
+        
+        default: // nested attributes
+          var otherAttrs = _.rest(attrPath);
+          newVal = this.createAttrObj(otherAttrs, val);
+          break;
+      }
+
+      var childAttr = attrPath[0],
+        result = _.isNumber(childAttr) ? [] : {};
+      
+      result[childAttr] = newVal;
+      return result;
+    },
+
+    createAttrStr: function(attrPath){
+      var attrStr = attrPath[0];
+      _.each(_.rest(attrPath), function(attr){
+        attrStr += _.isNumber(attr) ? ('[' + attr + ']') : ('.' + attr);
+      });
+
+      return attrStr;
+    },
+
+    deepClone: function(obj){
+      return $.extend(true, {}, obj);
+    }
+
+  });
+
+})();
diff --git a/static/vendor/backbone.nested-1.1.0.min.js b/static/vendor/backbone.nested-1.1.0.min.js
new file mode 100644 (file)
index 0000000..c1f8a53
--- /dev/null
@@ -0,0 +1,8 @@
+/**
+ * Backbone-Nested 1.1.0 - An extension of Backbone.js that keeps track of nested attributes
+ *
+ * http://afeld.github.com/backbone-nested/
+ *
+ * Copyright (c) 2011-2012 Aidan Feldman
+ * MIT Licensed (LICENSE)
+ *//*global Backbone, _, $ */(function(){"use strict",Backbone.NestedModel=Backbone.Model.extend({get:function(a,b){b=b||{};var c=Backbone.NestedModel.attrPath(a),d=c[0],e=Backbone.NestedModel.__super__.get.call(this,d);for(var f=1;f<c.length;f++){if(!e)break;d=c[f],e=e[d]}return!b.silent&&_.isObject(e)&&window.console&&window.console.log("Backbone-Nested syntax is preferred for accesing values of attribute '"+a+"'."),e},has:function(a){var b=this.get(a,{silent:!0});return b!==null&&!_.isUndefined(b)},set:function(a,b,c){var d;_.isObject(a)||a==null?(d=a,c=b):(d={},d[a]=b),c=c||{};var e=Backbone.NestedModel.deepClone(this.attributes),f,g,h;for(var i in d)g=Backbone.NestedModel.attrPath(i),h=Backbone.NestedModel.createAttrObj(g,d[i]),this._mergeAttrs(e,h,c);return Backbone.NestedModel.__super__.set.call(this,e,c)},unset:function(a,b){return b=_.extend({},b,{unset:!0}),this.set(a,null,b),this},add:function(a,b,c){var d=this.get(a,{silent:!0});this.set(a+"["+d.length+"]",b,c)},remove:function(a,b){b=b||{};var c=Backbone.NestedModel.attrPath(a),d=_.initial(c),e=this.get(d,{silent:!0}),f=_.last(c);if(!_.isArray(e))throw new Error("remove() must be called on a nested array");var g=!b.silent&&e.length>f+1,h=e[f];return e.splice(f,1),this.set(a,e,b),g&&this.trigger("remove:"+Backbone.NestedModel.createAttrStr(d),this,h),this},toJSON:function(){var a=Backbone.NestedModel.__super__.toJSON.apply(this);return Backbone.NestedModel.deepClone(a)},_mergeAttrs:function(a,b,c,d){return d=d||[],_.each(b,function(b,e){e==="-1"&&(e=a.length);var f=a[e],g=d.concat([e]),h,i=_.isObject(b)&&_.any(b,function(a,b){return b==="-1"||_.isNumber(b)});i&&!_.isArray(f)&&(f=a[e]=[]);if(e in a&&_.isObject(b)&&_.isObject(f))f=a[e]=this._mergeAttrs(f,b,c,g);else{var j=f;f=a[e]=b,_.isArray(a)&&!c.silent&&(h=Backbone.NestedModel.createAttrStr(d),!j&&f?this.trigger("add:"+h,this,f):j&&!f&&this.trigger("remove:"+h,this,j))}!c.silent&&g.length>1&&(h=Backbone.NestedModel.createAttrStr(g),this.trigger("change:"+h,this,f))},this),a}},{attrPath:function(a){var b;return _.isString(a)?(a=a.replace(/\[\]/g,"[-1]"),b=a===""?[""]:a.match(/[^\.\[\]]+/g),b=_.map(b,function(a){return a.match(/^\d+$/)?parseInt(a,10):a})):b=a,b},createAttrObj:function(a,b){var c=this.attrPath(a),d;switch(c.length){case 0:throw"no valid attributes: '"+a+"'";case 1:d=b;break;default:var e=_.rest(c);d=this.createAttrObj(e,b)}var f=c[0],g=_.isNumber(f)?[]:{};return g[f]=d,g},createAttrStr:function(a){var b=a[0];return _.each(_.rest(a),function(a){b+=_.isNumber(a)?"["+a+"]":"."+a}),b},deepClone:function(a){return $.extend(!0,{},a)}})})();
\ No newline at end of file
diff --git a/static/vendor/backbone.nested.js b/static/vendor/backbone.nested.js
new file mode 120000 (symlink)
index 0000000..84461d0
--- /dev/null
@@ -0,0 +1 @@
+backbone.nested-1.1.0.js
\ No newline at end of file
diff --git a/static/vendor/backbone.nested.min.js b/static/vendor/backbone.nested.min.js
new file mode 120000 (symlink)
index 0000000..0437046
--- /dev/null
@@ -0,0 +1 @@
+backbone.nested-1.1.0.min.js
\ No newline at end of file
index 3a47453..7d397bd 100644 (file)
@@ -1 +1 @@
-require=function(){function d(){}function c(d){if(b.hasOwnProperty(d))return b[d];var e=arguments.callee.caller;if(!a.hasOwnProperty(d))throw new Error('No such module "'+d+'"'+(e?" (requested by "+(e.name||e.displayName||e)+")":"")+"!");var f=a[d],g=f.exports=b[d]={};f.setup.call(g,c,g,f),g=b[d]=f.exports;return g}var a=c.modules={},b=c.cache={};c.install=function(b,c,e){if(!b)throw new Error("No module ID specified: id="+b+"!");if(a.hasOwnProperty(b))throw new Error('Module "'+b+'" already exists!');c instanceof Function&&(e=c,c={id:b}),c.id=b,c.setup=e||d,a[b]=c;return c};return c}()
\ No newline at end of file
+require=function(){var a=this,b=a.require,c=a.require||{};b=function(a,b){var c=require.resolve?require.resolve(a,b||"/"):a,d=require.modules[c];if(!d)throw new Error("Failed to resolve module "+a+", tried "+c);d.exports||d();var e=(d.module||{}).exports||d._cached||d.exports;return e};for(var d in c)b[d]=c[d];return b.modules=c.modules||{},b.cache=c.cache||{},b}(),require.install=function(a,b,c){if(!a)throw new Error("No module ID specified: id="+a+"!");a=""+a;if(require.modules.hasOwnProperty(a))throw new Error('Module "'+a+'" already exists!');typeof b=="function"&&(c=b),b={id:a};var d=b.exports={},e;return e=function(){return e.exports=d,c&&c.call(d,require,d,b),e.exports=e._cached=require.cache[a]=b.exports,b.exports},e.id=a,e.module=b,e.setup=c,require.modules[a]=e,b};
\ No newline at end of file