diff --git a/backbone.js b/backbone.js index 4e1a8b4e..a8fbefef 100644 --- a/backbone.js +++ b/backbone.js @@ -116,6 +116,7 @@ attributes || (attributes = {}); if (this.defaults) attributes = _.extend({}, this.defaults, attributes); this.attributes = {}; + this._escapedAttributes = {}; this.cid = _.uniqueId('c'); this.set(attributes, {silent : true}); this._previousAttributes = _.clone(this.attributes); @@ -147,6 +148,14 @@ return this.attributes[attr]; }, + // Get the HTML-escaped value of an attribute. + escape : function(attr) { + var html; + if (html = this._escapedAttributes[attr]) return html; + var val = this.attributes[attr]; + return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : val); + }, + // Set a hash of model attributes on the object, firing `"change"` unless you // choose to silence it. set : function(attrs, options) { @@ -155,7 +164,7 @@ options || (options = {}); if (!attrs) return this; if (attrs.attributes) attrs = attrs.attributes; - var now = this.attributes; + var now = this.attributes, escaped = this._escapedAttributes; // Run validation. if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false; @@ -168,6 +177,7 @@ var val = attrs[attr]; if (!_.isEqual(now[attr], val)) { now[attr] = val; + delete escaped[attr]; if (!options.silent) { this._changed = true; this.trigger('change:' + attr, this, val); @@ -193,6 +203,7 @@ // Remove the attribute. delete this.attributes[attr]; + delete this._escapedAttributes[attr]; if (!options.silent) { this._changed = true; this.trigger('change:' + attr, this); @@ -213,6 +224,7 @@ if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false; this.attributes = {}; + this._escapedAttributes = {}; if (!options.silent) { this._changed = true; for (attr in old) { @@ -981,4 +993,12 @@ return _.isFunction(object.url) ? object.url() : object.url; }; + // Helper function to escape a string for HTML rendering. + var escapeHTML = function(string) { + return string.replace(/&(?!\w+;)/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + })(); diff --git a/test/model.js b/test/model.js index c103b98c..b37b99a5 100644 --- a/test/model.js +++ b/test/model.js @@ -94,6 +94,16 @@ $(document).ready(function() { equals(doc.get('author'), 'Bill Shakespeare'); }); + test("Model: escape", function() { + equals(doc.escape('title'), 'The Tempest'); + doc.set({audience: 'Bill & Bob'}); + equals(doc.escape('audience'), 'Bill & Bob'); + doc.set({audience: 'Tim > Joan'}); + equals(doc.escape('audience'), 'Tim > Joan'); + doc.unset('audience'); + equals(doc.escape('audience'), ''); + }); + test("Model: set and unset", function() { attrs = { 'foo': 1, 'bar': 2, 'baz': 3}; a = new Backbone.Model(attrs); @@ -151,7 +161,7 @@ $(document).ready(function() { model.change(); equals(model.get('name'), 'Rob'); }); - + test("Model: save within change event", function () { var model = new Backbone.Model({firstName : "Taylor", lastName: "Swift"}); model.bind('change', function () {