mirror of
https://github.com/jashkenas/backbone.git
synced 2026-04-08 03:00:26 -04:00
Merge pull request #893 from braddunbar/change
Fire `'change:attr'` from `change`
This commit is contained in:
56
backbone.js
56
backbone.js
@@ -163,10 +163,11 @@
|
||||
this.attributes = {};
|
||||
this._escapedAttributes = {};
|
||||
this.cid = _.uniqueId('c');
|
||||
this._changed = {};
|
||||
if (!this.set(attributes, {silent: true})) {
|
||||
throw new Error("Can't create an invalid model");
|
||||
}
|
||||
this._changed = false;
|
||||
this._changed = {};
|
||||
this._previousAttributes = _.clone(this.attributes);
|
||||
this.initialize.apply(this, arguments);
|
||||
};
|
||||
@@ -174,9 +175,6 @@
|
||||
// Attach all inheritable methods to the Model prototype.
|
||||
_.extend(Backbone.Model.prototype, Backbone.Events, {
|
||||
|
||||
// Has the item been changed since the last `"change"` event?
|
||||
_changed: false,
|
||||
|
||||
// The default name for the JSON `id` attribute is `"id"`. MongoDB and
|
||||
// CouchDB users may want to set this to `"_id"`.
|
||||
idAttribute: 'id',
|
||||
@@ -226,7 +224,6 @@
|
||||
if (!attrs) return this;
|
||||
if (attrs instanceof Backbone.Model) attrs = attrs.attributes;
|
||||
if (options.unset) for (var attr in attrs) attrs[attr] = void 0;
|
||||
var now = this.attributes, escaped = this._escapedAttributes;
|
||||
|
||||
// Run validation.
|
||||
if (this.validate && !this._performValidation(attrs, options)) return false;
|
||||
@@ -234,30 +231,26 @@
|
||||
// Check for changes of `id`.
|
||||
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
|
||||
|
||||
// We're about to start triggering change events.
|
||||
var now = this.attributes;
|
||||
var escaped = this._escapedAttributes;
|
||||
var prev = this._previousAttributes || {};
|
||||
var alreadyChanging = this._changing;
|
||||
this._changing = true;
|
||||
|
||||
// Update attributes.
|
||||
var changes = {};
|
||||
for (attr in attrs) {
|
||||
val = attrs[attr];
|
||||
if (!_.isEqual(now[attr], val) || (options.unset && (attr in now))) {
|
||||
delete escaped[attr];
|
||||
this._changed = true;
|
||||
changes[attr] = val;
|
||||
}
|
||||
if (!_.isEqual(now[attr], val)) delete escaped[attr];
|
||||
options.unset ? delete now[attr] : now[attr] = val;
|
||||
delete this._changed[attr];
|
||||
if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
|
||||
this._changed[attr] = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire `change:attribute` events.
|
||||
for (var attr in changes) {
|
||||
if (!options.silent) this.trigger('change:' + attr, this, changes[attr], options);
|
||||
}
|
||||
|
||||
// Fire the `"change"` event, if the model has been changed.
|
||||
// Fire the `"change"` events, if the model has been changed.
|
||||
if (!alreadyChanging) {
|
||||
if (!options.silent && this._changed) this.change(options);
|
||||
if (!options.silent && this.hasChanged()) this.change(options);
|
||||
this._changing = false;
|
||||
}
|
||||
return this;
|
||||
@@ -376,19 +369,23 @@
|
||||
return this.id == null;
|
||||
},
|
||||
|
||||
// Call this method to manually fire a `change` event for this model.
|
||||
// Call this method to manually fire a `"change"` event for this model and
|
||||
// a `"change:attribute"` event for each changed attribute.
|
||||
// Calling this will cause all objects observing the model to update.
|
||||
change: function(options) {
|
||||
for (var attr in this._changed) {
|
||||
this.trigger('change:' + attr, this, this._changed[attr], options);
|
||||
}
|
||||
this.trigger('change', this, options);
|
||||
this._previousAttributes = _.clone(this.attributes);
|
||||
this._changed = false;
|
||||
this._changed = {};
|
||||
},
|
||||
|
||||
// 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) return !_.isEqual(this._previousAttributes[attr], this.attributes[attr]);
|
||||
return this._changed;
|
||||
if (attr) return _.has(this._changed, attr);
|
||||
return !_.isEmpty(this._changed);
|
||||
},
|
||||
|
||||
// Return an object containing all the attributes that have changed, or
|
||||
@@ -396,17 +393,8 @@
|
||||
// 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.
|
||||
changedAttributes: function(now) {
|
||||
if (!this._changed) return false;
|
||||
now || (now = this.attributes);
|
||||
var changed = false, old = this._previousAttributes;
|
||||
for (var attr in now) {
|
||||
if (_.isEqual(old[attr], now[attr])) continue;
|
||||
(changed || (changed = {}))[attr] = now[attr];
|
||||
}
|
||||
for (var attr in old) {
|
||||
if (!(attr in now)) (changed || (changed = {}))[attr] = void 0;
|
||||
}
|
||||
return changed;
|
||||
if (!this.hasChanged()) return false;
|
||||
return _.clone(this._changed);
|
||||
},
|
||||
|
||||
// Get the previous value of an attribute, recorded at the time the last
|
||||
|
||||
29
index.html
29
index.html
@@ -382,7 +382,7 @@
|
||||
|
||||
<p>
|
||||
Backbone.js gives structure to your serious JavaScript web applications
|
||||
by supplying <b>models</b> with key-value binding and custom events,
|
||||
by supplying <b>models</b> with key-value binding and custom events,
|
||||
<b>collections</b> with a rich API of enumerable functions,
|
||||
<b>views</b> with declarative event handling, and connects it all to your
|
||||
existing API over a RESTful JSON interface.
|
||||
@@ -392,7 +392,7 @@
|
||||
The project is <a href="http://github.com/documentcloud/backbone/">hosted on GitHub</a>,
|
||||
and the <a href="docs/backbone.html">annotated source code</a> is available,
|
||||
as well as an online <a href="test/test.html">test suite</a>,
|
||||
an <a href="examples/todos/index.html">example application</a>,
|
||||
an <a href="examples/todos/index.html">example application</a>,
|
||||
a <a href="https://github.com/documentcloud/backbone/wiki/Tutorials%2C-blog-posts-and-example-sites">list of tutorials</a>
|
||||
and a <a href="#examples">long list of real-world projects</a> that use Backbone.
|
||||
</p>
|
||||
@@ -1036,8 +1036,9 @@ ActiveRecord::Base.include_root_in_json = false
|
||||
<p id="Model-change">
|
||||
<b class="header">change</b><code>model.change()</code>
|
||||
<br />
|
||||
Manually trigger the <tt>"change"</tt> event.
|
||||
If you've been passing <tt>{silent: true}</tt> to the <a href="#Model-set">set</a> function in order to
|
||||
Manually trigger the <tt>"change"</tt> event and a <tt>"change:attribute"</tt>
|
||||
event for each attribute that has changed. If you've been passing
|
||||
<tt>{silent: true}</tt> to the <a href="#Model-set">set</a> function in order to
|
||||
aggregate rapid changes to a model, you'll want to call <tt>model.change()</tt>
|
||||
when you're all finished.
|
||||
</p>
|
||||
@@ -2964,28 +2965,28 @@ Inbox.messages.add(newMessage);
|
||||
<p id="FAQ-rails">
|
||||
<b class="header">Working with Rails</b>
|
||||
<br />
|
||||
Backbone.js was originally extracted from
|
||||
Backbone.js was originally extracted from
|
||||
<a href="http://www.documentcloud.org">a Rails application</a>; getting
|
||||
your client-side (Backbone) Models to sync correctly with your server-side
|
||||
(Rails) Models is painless, but there are still a few things to be aware of.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
By default, Rails adds an extra layer of wrapping around the JSON representation
|
||||
of models. You can disable this wrapping by setting:
|
||||
</p>
|
||||
|
||||
|
||||
<pre>
|
||||
ActiveRecord::Base.include_root_in_json = false
|
||||
ActiveRecord::Base.include_root_in_json = false
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
... in your configuration. Otherwise, override
|
||||
<a href="#Model-parse">parse</a> to pull model attributes out of the
|
||||
wrapper. Similarly, Backbone PUTs and POSTs direct JSON representations
|
||||
of models, where by default Rails expcects namespaced attributes. You can
|
||||
have your controllers filter attributes directly from <tt>params</tt>, or
|
||||
you can override <a href="#Model-toJSON">toJSON</a> in Backbone to add
|
||||
... in your configuration. Otherwise, override
|
||||
<a href="#Model-parse">parse</a> to pull model attributes out of the
|
||||
wrapper. Similarly, Backbone PUTs and POSTs direct JSON representations
|
||||
of models, where by default Rails expcects namespaced attributes. You can
|
||||
have your controllers filter attributes directly from <tt>params</tt>, or
|
||||
you can override <a href="#Model-toJSON">toJSON</a> in Backbone to add
|
||||
the extra wrapping Rails expects.
|
||||
</p>
|
||||
|
||||
|
||||
@@ -543,7 +543,7 @@ $(document).ready(function() {
|
||||
|
||||
test("unset fires change for undefined attributes", 1, function() {
|
||||
var model = new Backbone.Model({x: undefined});
|
||||
model.bind('change:x', function(){ ok(true); });
|
||||
model.on('change:x', function(){ ok(true); });
|
||||
model.unset('x');
|
||||
});
|
||||
|
||||
@@ -552,4 +552,28 @@ $(document).ready(function() {
|
||||
ok('x' in model.attributes);
|
||||
});
|
||||
|
||||
test("change fires change:attr", 1, function() {
|
||||
var model = new Backbone.Model({x: 1});
|
||||
model.set({x: 2}, {silent: true});
|
||||
model.on('change:x', function(){ ok(true); });
|
||||
model.change();
|
||||
});
|
||||
|
||||
test("hasChanged is false after original values are set", function() {
|
||||
var model = new Backbone.Model({x: 1});
|
||||
model.on('change:x', function(){ ok(false); });
|
||||
model.set({x: 2}, {silent: true});
|
||||
ok(model.hasChanged());
|
||||
model.set({x: 1}, {silent: true});
|
||||
ok(!model.hasChanged());
|
||||
});
|
||||
|
||||
test("set/hasChanged object prototype props", function() {
|
||||
var model = new Backbone.Model();
|
||||
ok(!model.hasChanged('toString'));
|
||||
model.set({toString: undefined});
|
||||
model.unset('toString', {silent: true});
|
||||
ok(model.hasChanged());
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user