Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add UMD support, bump version to 0.1.1, add 0.1.1 dist folder #23

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions backbone.trackit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
(function() {
(function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('backbone'), require('underscore'));
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['backbone', 'underscore'], function (Backbone, _) {
return factory(Backbone, _);
});
} else {
// Global Variables
factory(Backbone, _);
}
}(this, function (Backbone, _) {
if (!Backbone) throw new Error('backbone.trackit requires Backbone.');
if (!_) throw new Error('backbone.trackit requires underscore.');

// Unsaved Record Keeping
// ----------------------
Expand Down Expand Up @@ -189,4 +204,4 @@
return oldSync(method, model, options);
});

})();
}));
212 changes: 212 additions & 0 deletions dist/0.1.1/backbone.trackit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//
// backbone.trackit - 0.1.1
// The MIT License
// Copyright (c) 2013 The New York Times, CMS Group, Matthew DeLambo <[email protected]>
//
(function (root, factory) {
if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('backbone'), require('underscore'));
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['backbone', 'underscore'], function (Backbone, _) {
return factory(Backbone, _);
});
} else {
// Global Variables
factory(Backbone, _);
}
}(this, function (Backbone, _) {
if (!Backbone) throw new Error('backbone.trackit requires Backbone.');
if (!_) throw new Error('backbone.trackit requires underscore.');

// Unsaved Record Keeping
// ----------------------

// Collection of all models in an app that have unsaved changes.
var unsavedModels = [];

// If the given model has unsaved changes then add it to
// the `unsavedModels` collection, otherwise remove it.
var updateUnsavedModels = function(model) {
if (!_.isEmpty(model._unsavedChanges)) {
if (!_.findWhere(unsavedModels, {cid:model.cid})) unsavedModels.push(model);
} else {
unsavedModels = _.filter(unsavedModels, function(m) { return model.cid != m.cid; });
}
};

// Unload Handlers
// ---------------

// Helper which returns a prompt message for an unload handler.
// Uses the given function name (one of the callback names
// from the `model.unsaved` configuration hash) to evaluate
// whether a prompt is needed/returned.
var getPrompt = function(fnName) {
var prompt, args = _.rest(arguments);
// Evaluate and return a boolean result. The given `fn` may be a
// boolean value, a function, or the name of a function on the model.
var evaluateModelFn = function(model, fn) {
if (_.isBoolean(fn)) return fn;
return (_.isString(fn) ? model[fn] : fn).apply(model, args);
};
_.each(unsavedModels, function(model) {
if (!prompt && evaluateModelFn(model, model._unsavedConfig[fnName]))
prompt = model._unsavedConfig.prompt;
});
return prompt;
};

// Wrap Backbone.History.navigate so that in-app routing
// (`router.navigate('/path')`) can be intercepted with a
// confirmation if there are any unsaved models.
Backbone.History.prototype.navigate = _.wrap(Backbone.History.prototype.navigate, function(oldNav, fragment, options) {
var prompt = getPrompt('unloadRouterPrompt', fragment, options);
if (prompt) {
if (confirm(prompt + ' \n\nAre you sure you want to leave this page?')) {
oldNav.call(this, fragment, options);
}
} else {
oldNav.call(this, fragment, options);
}
});

// Create a browser unload handler which is triggered
// on the refresh, back, or forward button.
window.onbeforeunload = function(e) {
return getPrompt('unloadWindowPrompt', e);
};

// Backbone.Model API
// ------------------

_.extend(Backbone.Model.prototype, {

unsaved: {},
_trackingChanges: false,
_originalAttrs: {},
_unsavedChanges: {},

// Opt in to tracking attribute changes
// between saves.
startTracking: function() {
this._unsavedConfig = _.extend({}, {
prompt: 'You have unsaved changes!',
unloadRouterPrompt: false,
unloadWindowPrompt: false
}, this.unsaved || {});
this._trackingChanges = true;
this._resetTracking();
this._triggerUnsavedChanges();
return this;
},

// Resets the default tracking values
// and stops tracking attribute changes.
stopTracking: function() {
this._trackingChanges = false;
this._originalAttrs = {};
this._unsavedChanges = {};
this._triggerUnsavedChanges();
return this;
},

// Gets rid of accrued changes and
// resets state.
restartTracking: function() {
this._resetTracking();
this._triggerUnsavedChanges();
return this;
},

// Restores this model's attributes to
// their original values since tracking
// started, the last save, or last restart.
resetAttributes: function() {
if (!this._trackingChanges) return;
this.attributes = this._originalAttrs;
this._resetTracking();
this._triggerUnsavedChanges();
return this;
},

// Symmetric to Backbone's `model.changedAttributes()`,
// except that this returns a hash of the model's attributes that
// have changed since the last save, or `false` if there are none.
// Like `changedAttributes`, an external attributes hash can be
// passed in, returning the attributes in that hash which differ
// from the model.
unsavedAttributes: function(attrs) {
if (!attrs) return _.isEmpty(this._unsavedChanges) ? false : _.clone(this._unsavedChanges);
var val, changed = false, old = this._unsavedChanges;
for (var attr in attrs) {
if (_.isEqual(old[attr], (val = attrs[attr]))) continue;
(changed || (changed = {}))[attr] = val;
}
return changed;
},

_resetTracking: function() {
this._originalAttrs = _.clone(this.attributes);
this._unsavedChanges = {};
},

// Trigger an `unsavedChanges` event on this model,
// supplying the result of whether there are unsaved
// changes and a changed attributes hash.
_triggerUnsavedChanges: function() {
this.trigger('unsavedChanges', !_.isEmpty(this._unsavedChanges), _.clone(this._unsavedChanges), this);
if (this.unsaved) updateUnsavedModels(this);
}
});

// Wrap `model.set()` and update the internal
// unsaved changes record keeping.
Backbone.Model.prototype.set = _.wrap(Backbone.Model.prototype.set, function(oldSet, key, val, options) {
var attrs, ret;
if (key == null) return this;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options || (options = {});

// Delegate to Backbone's set.
ret = oldSet.call(this, attrs, options);

if (this._trackingChanges && !options.silent && !options.trackit_silent) {
_.each(attrs, _.bind(function(val, key) {
if (_.isEqual(this._originalAttrs[key], val))
delete this._unsavedChanges[key];
else
this._unsavedChanges[key] = val;
}, this));
this._triggerUnsavedChanges();
}
return ret;
});

// Intercept `model.save()` and reset tracking/unsaved
// changes if it was successful.
Backbone.sync = _.wrap(Backbone.sync, function(oldSync, method, model, options) {
options || (options = {});

if (method == 'update' || method == 'create' || method == 'patch') {
options.success = _.wrap(options.success, _.bind(function(oldSuccess, data, textStatus, jqXHR) {
var ret;
if (oldSuccess) ret = oldSuccess.call(this, data, textStatus, jqXHR);
if (model._trackingChanges) {
model._resetTracking();
model._triggerUnsavedChanges();
}
return ret;
}, this));
}
return oldSync(method, model, options);
});

}));
6 changes: 6 additions & 0 deletions dist/0.1.1/backbone.trackit.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added dist/0.1.1/backbone.trackit.min.js.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "backbone.trackit",
"description": "",
"version": "0.1.0",
"version": "0.1.1",
"author": "Matthew DeLambo <[email protected]>",
"repository": {
"type": "git",
Expand Down