backbone_extensions 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -13,5 +13,6 @@ Installing
13
13
  3. In your application.js //= require backbone
14
14
  4. In your application.js //= require backbone_extensions/include
15
15
  5. In your application.js //= require backbone_extensions/decorator
16
+ 6. In your application.js //= require backbone_extensions/associations
16
17
 
17
- Copyright (c) 2012 Ryan Dy, released under the MIT license
18
+ Copyright (c) 2012-3 Ryan Dy, Thomas Bukowski, released under the MIT license
data/config/jshint.yml CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  paths:
10
10
  - lib/assets/javascripts/**/*.js
11
+ - spec/javascripts/**/*.js
11
12
 
12
13
  exclude_paths:
13
14
 
@@ -20,7 +21,7 @@ exclude_paths:
20
21
  adsafe: false # true if ADsafe rules should be enforced. See http://www.ADsafe.org/
21
22
  bitwise: true # true if bitwise operators should not be allowed
22
23
  newcap: true # true if Initial Caps must be used with constructor functions
23
- eqeqeq: false # true if === should be required (for ALL equality comparisons)
24
+ eqeqeq: true # true if === should be required (for ALL equality comparisons)
24
25
  immed: false # true if immediate function invocations must be wrapped in parens
25
26
  nomen: false # true if initial or trailing underscore in identifiers should be forbidden
26
27
  onevar: false # true if only one var statement per function should be allowed
@@ -43,6 +44,7 @@ fragment: false # true if HTML fragments should be allowed
43
44
  laxbreak: false # true if statement breaks should not be checked
44
45
  on: false # true if HTML event handlers (e.g. onclick="...") should be allowed
45
46
  sub: false # true if subscript notation may be used for expressions better expressed in dot notation
47
+ validthis: true # true if suppresses warnings about possible strict violations when the code is running in strict mode and you use this in a non-constructor function
46
48
 
47
49
  # other options
48
50
 
@@ -55,6 +57,14 @@ predef: # Names of predefined global variables - comma-separated strin
55
57
  - _
56
58
  - Backbone
57
59
  - jQuery
60
+ - $
61
+ - describe
62
+ - it
63
+ - beforeEach
64
+ - afterEach
65
+ - expect
66
+ - jasmine
67
+ - spyOn
58
68
 
59
69
  browser: true # true if the standard browser globals should be predefined
60
70
  rhino: false # true if the Rhino environment globals should be predefined
@@ -64,12 +74,12 @@ devel: false # true if functions like alert, confirm, console, prompt etc
64
74
 
65
75
  # jshint options
66
76
  loopfunc: true # true if functions should be allowed to be defined within loops
67
- asi: true # true if automatic semicolon insertion should be tolerated
68
- boss: true # true if advanced usage of assignments and == should be allowed
69
- couch: true # true if CouchDB globals should be predefined
77
+ asi: false # true if automatic semicolon insertion should be tolerated
78
+ boss: false # true if advanced usage of assignments and == should be allowed
79
+ couch: false # true if CouchDB globals should be predefined
70
80
  curly: false # true if curly braces around blocks should be required (even in if/for/while)
71
81
  noarg: true # true if arguments.caller and arguments.callee should be disallowed
72
- node: true # true if the Node.js environment globals should be predefined
82
+ node: false # true if the Node.js environment globals should be predefined
73
83
  noempty: true # true if empty blocks should be disallowed
74
84
  nonew: true # true if using `new` for side-effects should be disallowed
75
85
 
@@ -0,0 +1,166 @@
1
+ //= require backbone_extensions/include
2
+ //= require underscore.string
3
+ (function(_, Backbone) {
4
+ 'use strict';
5
+ function mixin(namespace) {
6
+ namespace = namespace || {};
7
+
8
+ function mergeAssociationOptions() {
9
+ return _(arguments).chain().toArray().reduce(function(result, options) { return _(result).extend(options); }, {})
10
+ .omit('class', 'className', 'inverseOf', 'parseName', 'through').value();
11
+ }
12
+
13
+ function buildAssociation(associationType, associationName, options) {
14
+ function through() {
15
+ function association() {
16
+ var t = (_(options.through).isFunction() && options.through.call(this)) || _.str.camelize(options.through);
17
+ return this[t] && this[t]() && this[t]()[associationName] && this[t]()[associationName]();
18
+ }
19
+ return options.through && association.call(this);
20
+ }
21
+
22
+ function throughCollection() {
23
+ return (this.collection && this.collection[associationName] && this.collection[associationName]()) ||
24
+ (this._options && this._options.collection && this._options.collection[associationName] && this._options.collection[associationName]());
25
+ }
26
+
27
+ function createAssociation() {
28
+ var collectionName = _.str.classify(associationName), className = options.className && _.str.classify(options.className),
29
+ newOptions = mergeAssociationOptions(options, this._options);
30
+
31
+ if (options.inverseOf) {
32
+ newOptions[_.str.camelize(options.inverseOf)] = _(function() { return this; }).bind(this);
33
+ }
34
+
35
+ return _((options['class'] && new options['class'](null, newOptions)) ||
36
+ (className && namespace[className] && new namespace[className](null, newOptions)) ||
37
+ (namespace[collectionName] && new namespace[collectionName](null, newOptions))).tap(_(function(association) {
38
+ through.call(this, association);
39
+ }).bind(this));
40
+ }
41
+
42
+ var associations = {
43
+ hasMany: createAssociation,
44
+ hasOne: function() { return throughCollection.call(this) || createAssociation.call(this); },
45
+ belongsTo: function() { return throughCollection.call(this) || through.call(this); }
46
+ };
47
+
48
+ this.prototype[associationName] = function() {
49
+ return (this._associations || (this._associations = {})) && this._associations[associationName] ||
50
+ (this._associations[associationName] = (this._options && _(this._options).result(associationName)) || associations[associationType].call(this));
51
+ };
52
+ }
53
+
54
+ function parseAssociation(associationType, associationName, options) {
55
+ function parseResponseWith(key, response) {
56
+ var camelized = _.str.camelize(key), underscored = _.str.underscored(key);
57
+ if (response[camelized]) {
58
+ return {key: camelized, response: response[camelized]};
59
+ }
60
+ else if (response[underscored]) {
61
+ return {key: underscored, response: response[underscored]};
62
+ }
63
+ else {
64
+ return {response: null};
65
+ }
66
+ }
67
+
68
+ function through(response) {
69
+ var t = parseResponseWith(_(options).result('through'), response).response,
70
+ singularAssociationName = _.singularize && _(associationName).singularize(),
71
+ p = options.parseName || singularAssociationName;
72
+ return {response: t && p && _(t)[associationType === 'hasOne' ? 'result' : 'pluck'](p)};
73
+ }
74
+
75
+ if (options.parse) {
76
+ var associations = {
77
+ hasMany: function(assocResponse, association, newOptions) {
78
+ association.add(assocResponse, newOptions);
79
+ },
80
+ hasOne: function(assocResponse, association, newOptions) {
81
+ association.clear({silent: true}).set(assocResponse, newOptions);
82
+ }
83
+ };
84
+
85
+ if (associations[associationType]) {
86
+ var parseFunc = _(options.parse).isFunction() &&
87
+ function(response) { return {response: options.parse.call(this, response) }; } ||
88
+ function(response) {
89
+ return (options.through && through.call(this, response)) ||
90
+ (options.parseName && parseResponseWith(options.parseName, response)) ||
91
+ (options.className && parseResponseWith(options.className, response)) ||
92
+ parseResponseWith(associationName, response);
93
+ };
94
+
95
+ if(!this._parsers) {
96
+ var originalParse = this.prototype.parse,
97
+ parsers = this._parsers = [];
98
+ this.prototype.parse = function(response) {
99
+ return _(originalParse.call(this, response)).tap(_(function(parsedResponse) {
100
+ _(parsers)
101
+ .chain()
102
+ .map(function(parser) {
103
+ return _(parser.parseFn.call(this, parsedResponse)).tap(_(function(result) {
104
+ parser.associationFn.call(this, result.response);
105
+ }).bind(this)).key;
106
+ }, this)
107
+ .each(function(key) {
108
+ return key && delete parsedResponse[key];
109
+ });
110
+ }).bind(this));
111
+ };
112
+ }
113
+
114
+ this._parsers.push({
115
+ parseFn: parseFunc,
116
+ associationFn: function(assocResponse) {
117
+ associations[associationType].call(this, assocResponse, this[associationName](), mergeAssociationOptions(options, this.options));
118
+ }
119
+ });
120
+ }
121
+ }
122
+ }
123
+
124
+ return {
125
+ included: function(source) {
126
+ var associations = _({
127
+ belongsTo: {}, hasMany: {parse: true}, hasOne: {parse: true}
128
+ }).reduce(function(associations, defaultOptions, associationType) {
129
+ associations[associationType] = function(name, options) {
130
+ var associationName = _.str.camelize(name);
131
+ options = _({}).extend(defaultOptions, options);
132
+ buildAssociation.call(this, associationType, associationName, options);
133
+ parseAssociation.call(this, associationType, associationName, options);
134
+ return this;
135
+ };
136
+ return associations;
137
+ }, {});
138
+
139
+ _(source).extend(associations, {
140
+ associations: function() {
141
+ _(arguments).chain().toArray().each(function(options) {
142
+ _(associations).chain().keys().each(function(associationType) {
143
+ if (options[associationType]) {
144
+ associations[associationType].call(source, options[associationType], _(options).omit(associationType));
145
+ }
146
+ });
147
+ });
148
+ },
149
+
150
+ extend: _(source.extend).wrap(function(oldExtend, protoProps, classProps) {
151
+ return _(oldExtend.call(this, protoProps, classProps)).tap(function() {
152
+ source.associations((protoProps || {}).associations);
153
+ });
154
+ })
155
+ });
156
+
157
+ source.prototype.initialize = _(source.prototype.initialize).wrap(function(oldInitialize, attrsOrModels, options) {
158
+ this._options = _(options).clone();
159
+ oldInitialize.call(this, attrsOrModels, options);
160
+ });
161
+ }
162
+ };
163
+ }
164
+
165
+ Backbone.extensions = _(Backbone.extensions || {}).extend({associations: mixin});
166
+ })(_, Backbone);
@@ -1,59 +1,45 @@
1
- (function(Backbone, $) {
2
- function decorateModels(self, models) {
3
- var decoratee = models instanceof Backbone.Collection ? models.models : models,
4
- proto = _(self)
5
- .chain()
6
- .functions()
7
- .without('collection', 'constructor', 'initialize', 'model')
8
- .inject(function(result, method) { result[method] = self[method]; return result; }, {})
9
- .value();
10
-
11
- if (decoratee instanceof Array) {
12
- _(proto).each(function(fn, name) {
13
- self[name] = function() {
14
- var args = arguments;
15
- return _(decoratee).map(function(model) {
16
- return fn.apply(model, args);
17
- });
18
- };
19
- });
20
- } else {
21
- _(proto).each(function(fn, name) {
22
- self[name] = function() {
23
- return fn.apply(decoratee, arguments);
24
- };
25
- });
26
- }
1
+ (function(_, Backbone) {
2
+ 'use strict';
3
+ function Decorator(models, options) {
4
+ this._decoratee = models instanceof Backbone.Collection ? models.models : models;
5
+ this.initialize.call(this, models, options);
27
6
  }
28
7
 
29
- function Decorator(models, options) {
30
- decorateModels(this, models);
31
- this.initialize.apply(this, arguments);
8
+ function wrapDecorator(fn) {
9
+ return function() {
10
+ var args = arguments;
11
+ if (_(this._decoratee).isArray()) {
12
+ return _(this._decoratee).map(function(model) {
13
+ return fn.apply(model, args);
14
+ });
15
+ } else {
16
+ return fn.apply(this._decoratee, args);
17
+ }
18
+ };
32
19
  }
33
20
 
34
21
  _(Decorator).extend({
35
22
  extend: function(protoProps, classProps) {
36
- var Klass = Backbone.Model.extend.apply(this, arguments);
37
-
38
- if (protoProps.model) {
39
- protoProps.model.prototype.decorator = function() {
40
- return new Klass(this);
41
- };
42
- }
43
-
44
- if (protoProps.collection) {
45
- protoProps.collection.prototype.decorator = function() {
46
- return new Klass(this);
47
- };
48
- }
49
-
50
- return Klass;
23
+ var proto = _(protoProps).chain().omit('collection', 'constructor', 'initialize', 'model').reduce(function(proto, fn, name) {
24
+ proto[name] = wrapDecorator(fn);
25
+ return proto;
26
+ }, {}).value();
27
+
28
+ return _(Backbone.Model.extend.call(this, _(protoProps).extend(proto), classProps)).tap(function(Klass) {
29
+ _(['model', 'collection']).each(function(type) {
30
+ if (protoProps[type]) {
31
+ protoProps[type].prototype.decorator = function() {
32
+ return new Klass(this);
33
+ };
34
+ }
35
+ });
36
+ });
51
37
  }
52
- }, Backbone.include ? Backbone.include : {});
38
+ }, Backbone.extensions && Backbone.extensions.include || {});
53
39
 
54
40
  _(Decorator.prototype).extend({
55
- initialize: $.noop
41
+ initialize: function(models, options) {}
56
42
  });
57
43
 
58
- Backbone.Decorator = Decorator;
59
- })(Backbone, jQuery);
44
+ Backbone.extensions = _(Backbone.extensions || {}).extend({Decorator: Decorator});
45
+ })(_, Backbone);
@@ -0,0 +1,94 @@
1
+ (function(_, Backbone) {
2
+ 'use strict';
3
+ function bindModelEvents(tuple) {
4
+ var self = this, subject = tuple[0], eventNames = tuple[1], modelEvents = self._modelEvents;
5
+ modelEvents.push(tuple);
6
+
7
+ _(subject && eventNames).each(function(callback, event) {
8
+ _(event.split(' ')).each(function(e) {
9
+ _([callback]).chain().flatten().each(function(c) {
10
+ if (_(c).isFunction()) {
11
+ subject.on(e, c, self);
12
+ } else {
13
+ subject.on(e, self[c], self);
14
+ }
15
+ });
16
+ });
17
+ });
18
+ }
19
+
20
+ function unbindModelEvents() {
21
+ var self = this, modelEvents = self._modelEvents;
22
+ _(modelEvents).each(function(tuple) {
23
+ var subject = tuple[0], events = tuple[1];
24
+
25
+ _(subject && events).each(function(callback, event) {
26
+ _(event.split(' ')).each(function(e) {
27
+ _([callback]).chain().flatten().each(function(c) {
28
+ if (_(c).isFunction()) {
29
+ subject.off(e, c);
30
+ } else {
31
+ subject.off(e, self[c]);
32
+ }
33
+ });
34
+ });
35
+ });
36
+ });
37
+ }
38
+
39
+ function wrapUndelegateEvents(callback) {
40
+ var oldUndelegateEvents = this.undelegateEvents;
41
+ this.undelegateEvents();
42
+ this.undelegateEvents = function() {};
43
+ callback.call(this);
44
+ this.undelegateEvents = oldUndelegateEvents;
45
+ }
46
+
47
+ var delegateEvents = {
48
+ included: function(source) {
49
+ _(source.prototype).extend({
50
+ initialize: _(source.prototype.initialize).wrap(function(oldInitialize, attrsOrModels, options) {
51
+ this._modelEvents = [];
52
+ oldInitialize.call(this, attrsOrModels, options);
53
+ }),
54
+
55
+ delegateEvents: _(source.prototype.delegateEvents).wrap(function(oldDelegateEvents) {
56
+ var self = this, args = _(arguments).rest(1);
57
+
58
+ if (!args.length) {
59
+ return oldDelegateEvents.call(this);
60
+ }
61
+
62
+ wrapUndelegateEvents.call(this, function() {
63
+ _(args).chain().toArray().compact().each(function(obj) {
64
+ var arg = _(obj);
65
+ if (arg.isArray()) {
66
+ bindModelEvents.call(self, obj);
67
+ } else {
68
+ arg.each(function(callbacks, event) {
69
+ _([callbacks]).chain().flatten().each(function(callback) {
70
+ oldDelegateEvents.call(self, _({}).tap(function(obj) {
71
+ obj[event] = callback;
72
+ }));
73
+ });
74
+ });
75
+ }
76
+ });
77
+ });
78
+ }),
79
+
80
+ undelegateEvents: _(source.prototype.undelegateEvents).wrap(function(oldUndelegateEvents) {
81
+ unbindModelEvents.call(this);
82
+ return oldUndelegateEvents.call(this);
83
+ }),
84
+
85
+ remove: _(source.prototype.remove).wrap(function(oldRemove) {
86
+ this.undelegateEvents();
87
+ return oldRemove.call(this);
88
+ })
89
+ });
90
+ }
91
+ };
92
+
93
+ Backbone.extensions = _(Backbone.extensions || {}).extend({delegateEvents: delegateEvents});
94
+ })(_, Backbone);
@@ -1,4 +1,5 @@
1
- (function(Backbone) {
1
+ (function(_, Backbone) {
2
+ 'use strict';
2
3
  var include = {
3
4
  include: function() {
4
5
  var self = this;
@@ -11,5 +12,5 @@
11
12
  }
12
13
  };
13
14
 
14
- Backbone.include = include;
15
- })(Backbone);
15
+ Backbone.extensions = _(Backbone.extensions || {}).extend({include: include});
16
+ })(_, Backbone);
@@ -1,3 +1,3 @@
1
1
  module BackboneExtensions
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backbone_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ryan Dy
9
+ - Thomas Bukowski
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2012-11-02 00:00:00.000000000 Z
13
+ date: 2013-01-07 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: fuubar
@@ -96,12 +97,15 @@ description: Adds extensions to the backbone javascript library. It adds the jav
96
97
  underscore, backbone and the extensions you need.
97
98
  email:
98
99
  - ryan.dy@gmail.com
100
+ - me@neodude.net
99
101
  executables: []
100
102
  extensions: []
101
103
  extra_rdoc_files: []
102
104
  files:
103
105
  - config/jshint.yml
106
+ - lib/assets/javascripts/backbone_extensions/associations.js
104
107
  - lib/assets/javascripts/backbone_extensions/decorator.js
108
+ - lib/assets/javascripts/backbone_extensions/delegate_events.js
105
109
  - lib/assets/javascripts/backbone_extensions/include.js
106
110
  - lib/backbone_extensions/version.rb
107
111
  - lib/backbone_extensions.rb
@@ -122,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
126
  version: '0'
123
127
  segments:
124
128
  - 0
125
- hash: -446599863856035963
129
+ hash: 1650966973307354674
126
130
  required_rubygems_version: !ruby/object:Gem::Requirement
127
131
  none: false
128
132
  requirements:
@@ -131,10 +135,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
135
  version: '0'
132
136
  segments:
133
137
  - 0
134
- hash: -446599863856035963
138
+ hash: 1650966973307354674
135
139
  requirements: []
136
140
  rubyforge_project:
137
- rubygems_version: 1.8.19
141
+ rubygems_version: 1.8.24
138
142
  signing_key:
139
143
  specification_version: 3
140
144
  summary: Extensions to backbone javascript library as a rails engine