Skip to content

Commit

Permalink
first draft of 'simple'
Browse files Browse the repository at this point in the history
  • Loading branch information
jashkenas committed Dec 19, 2012
1 parent 5a3ec24 commit 9c5d02e
Show file tree
Hide file tree
Showing 3 changed files with 843 additions and 888 deletions.
195 changes: 75 additions & 120 deletions backbone.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,12 @@
this.attributes = {};
this._changes = [];
if (options && options.collection) this.collection = options.collection;
if (options && options.parse) attrs = this.parse(attrs, options);
if (options && options.parse) attrs = this.parse(attrs, options) || {};
if (defaults = _.result(this, 'defaults')) {
attrs = _.defaults({}, attrs, defaults);
}
this.set(attrs, _.extend({silent: true}, options));
this._currentAttributes = _.clone(this.attributes);
this._previousAttributes = _.clone(this.attributes);
this.attributes = _.clone(attrs);
this.id = attrs[this.idAttribute];
this.initialize.apply(this, arguments);
};

Expand Down Expand Up @@ -289,10 +288,12 @@
return this.get(attr) != null;
},

// ----------------------------------------------------------------------

// Set a hash of model attributes on the object, firing `"change"` unless
// you choose to silence it.
set: function(key, val, options) {
var attr, attrs;
var attr, attrs, unset, changes, changing, prev, current;
if (key == null) return this;

// Handle both `"key", value` and `{key: value}` -style arguments.
Expand All @@ -304,32 +305,50 @@
}

// Extract attributes and options.
var silent = options && options.silent;
var unset = options && options.unset;
unset = options && options.unset;
changes = [];
changing = this._changing;
this._changing = true;

if (!changing) {
this._previousAttributes = _.clone(this.attributes);
this.changed = {};
}
current = this.attributes, prev = this._previousAttributes;

// Run validation.
if (!this._validate(attrs, options)) return false;

// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];

var now = this.attributes;

// For each `set` attribute...
// For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(prev[attr], val)) {
changes.push(attr, val);
this.changed[attr] = val;
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}

// Update or delete the current value, and track the change.
unset ? delete now[attr] : now[attr] = val;
this._changes.push(attr, val);
// Trigger all relevant attribute changes.
this._pending = !!changes.length;
for (var i = 0, l = changes.length; i < l; i += 2) {
this.trigger('change:' + changes[i], this, changes[i + 1], options);
}

// Signal that the model's state has potentially changed, and we need
// to recompute the actual changes.
this._hasComputed = false;
if (changing) return this;

// Trigger a `change` while there have been changes.
while (this._pending) {
this._pending = false;
this.trigger('change', this, options);
}

// Fire the `"change"` events.
if (!silent) this.change(options);
this._changing = false;
return this;
},

Expand All @@ -347,6 +366,44 @@
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, old = this._previousAttributes;
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 overriden,
// triggering a `"change"` event.
Expand Down Expand Up @@ -471,108 +528,6 @@
return this.id == null;
},

// 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) {
var changing = this._changing;
this._changing = true;

// Generate the changes to be triggered on the model.
var triggers = this._computeChanges(true);

var pending = this._pending = !!triggers.length;

for (var i = triggers.length - 2; i >= 0; i -= 2) {
this.trigger('change:' + triggers[i], this, triggers[i + 1], options);
}

if (changing) return this;

// Ensure the original `change` event is fired regardless of interim changes
if (pending) this._pending = true;

// Trigger a `change` while there have been changes.
while (this._pending) {
this._pending = false;
this.trigger('change', this, options);
this._previousAttributes = _.clone(this.attributes);
}

this._changing = false;
return this;
},

// 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 (!this._hasComputed) this._computeChanges();
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, old = this._previousAttributes;
for (var attr in diff) {
if (_.isEqual(old[attr], (val = diff[attr]))) continue;
(changed || (changed = {}))[attr] = val;
}
return changed;
},

// Looking at the built up list of `set` attribute changes, compute how
// many of the attributes have actually changed. If `loud`, return a
// boiled-down list of only the real changes.
_computeChanges: function(loud) {
this.changed = {};
var already = {};
var triggers = [];
var current = this._currentAttributes;
var changes = this._changes;

// Loop through the current queue of potential model changes.
for (var i = changes.length - 2; i >= 0; i -= 2) {
var key = changes[i], val = changes[i + 1];
if (already[key]) continue;
already[key] = true;

// Check if the attribute has been modified since the last change,
// and update `this.changed` accordingly. If we're inside of a `change`
// call, also add a trigger to the list.
if (!_.isEqual(current[key], val)) {
this.changed[key] = val;
if (!loud) continue;
triggers.push(key, val);
current[key] = val;
}
}
if (loud) this._changes = [];

// Signals `this.changed` is current to prevent duplicate calls from `this.hasChanged`.
this._hasComputed = true;
return triggers;
},

// 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);
},

// Check if the model is currently in a valid state. It's only possible to
// get into an *invalid* state if you're using silent changes.
isValid: function(options) {
Expand Down
4 changes: 2 additions & 2 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
<script src="noconflict.js"></script>
<script src="events.js"></script>
<script src="model.js"></script>
<script src="collection.js"></script>
<!-- <script src="collection.js"></script>
<script src="router.js"></script>
<script src="view.js"></script>
<script src="sync.js"></script>
<script src="speed.js"></script>
<script src="speed.js"></script> -->
</head>
<body>
<div id="qunit"></div>
Expand Down
Loading

0 comments on commit 9c5d02e

Please sign in to comment.