diff options
Diffstat (limited to '')
7 files changed, 479 insertions, 0 deletions
diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/app.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/app.js new file mode 100644 index 0000000000..4c4bde363a --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/app.js @@ -0,0 +1,16 @@ +/*global $ */ +/*jshint unused:false */ +var app = app || {}; +var ENTER_KEY = 13; +var ESC_KEY = 27; + +$(function () { + 'use strict'; + + // kick things off by creating the `App` + window.appView = new app.AppView(); + + var dummyNodeToNotifyAppIsReady = document.createElement('div'); + dummyNodeToNotifyAppIsReady.id = 'appIsReady'; + document.body.appendChild(dummyNodeToNotifyAppIsReady); +}); diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/backbone.sync.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/backbone.sync.js new file mode 100644 index 0000000000..f8e3c67edd --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/backbone.sync.js @@ -0,0 +1,118 @@ +Backbone.sync = function(method, model, options) { + + // we need to make sure we initialize a store, in this case + // we will just use a JS object. + var cache = {}; + + // The size will be primarily used to assign ids to newly + // created models. Each time a new model is created, the size + // will be incremented. + var size = 0; + + // since we need to create a store for the models/collections + // we are actually going to invoke the outer function which will + // return this function below as the Backbone.sync that will + // actually be used. + return function(method, model, options) { + + // create a new deferred object. standard sync returns the $.ajax + // request, which internally returns a deferred. It's important to + // do this so that people can chain on fetch using the standard .then/.fail + // syntax, rather than just the success/error callbacks. + var deferred = $.Deferred(); + + // when creating a new model... + if (method === "create") { + + // first assign it an id. The server would traditionally do this. + model.id = size; + // store it in our cache + cache[model.id] = model; + // make sure we increment the number of models we now have. + size += 1; + + // if a success callback was provided, execute it. + if (options.success) { + options.success(model, model.toJSON(), options); + } + + // resolve the deferred. + deferred.resolve(model); + + // we are updating a model + } else if (method === "update") { + + // as long as the model actually exists in our store + if (cache[model.id]) { + + // overwrite what is currently in the store with this model. + cache[model.id] = model; + + // if a success callback was provided, execute it. + if (options.success) { + options.success(model, model.toJSON(), options); + } + + deferred.resolve(model); + + // if this model doesn't exist yet, we can't update it + } else { + + if (options.error) { + options.error(model, "Model not found"); + } + deferred.reject(model); + } + + // if we're trying to read a model... + } else if (method === "read") { + + // as long as it exists + if (cache[model.id]) { + + // if a success callback was provided, execute it. + if (options.success) { + options.success(model, cache[model.id].toJSON(), options); + } + + // resolve + deferred.resolve(model); + } else { + if (options.error) { + options.error(model, "Model not found"); + } + deferred.reject(model); + } + + // if we're deleting a model... + } else if (method === "delete") { + + // first make sure it exists in the cache + if (cache[model.id]) { + + // then remove it from the cache + delete cache[model.id]; + + // and trigger the success callback. Note we're passing an + // empty object as the second argument, because a deletion + // would result in an empty return from the server. + if (options.success) { + options.success(model, {}, options); + } + + // resolve the deferred + deferred.resolve(model); + + // otherwise, error that the model doesn't exist. + } else { + if (options.error) { + options.error(model, "Model not found"); + } + deferred.reject(model); + } + } + + return deferred.promise(); + } + +}();
\ No newline at end of file diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/collections/todos.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/collections/todos.js new file mode 100644 index 0000000000..f8f5d31113 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/collections/todos.js @@ -0,0 +1,39 @@ +/*global Backbone */ +var app = app || {}; + +(function () { + 'use strict'; + + // Todo Collection + // --------------- + + + var Todos = Backbone.Collection.extend({ + // Reference to this collection's model. + model: app.Todo, + + // Save all of the todo items under this example's namespace. + + // Filter down the list of all todo items that are finished. + completed: function () { + return this.where({completed: true}); + }, + + // Filter down the list to only todo items that are still not finished. + remaining: function () { + return this.where({completed: false}); + }, + + // We keep the Todos in sequential order, despite being saved by unordered + // GUID in the database. This generates the next order number for new items. + nextOrder: function () { + return this.length ? this.last().get('order') + 1 : 1; + }, + + // Todos are sorted by their original insertion order. + comparator: 'order' + }); + + // Create our global collection of **Todos**. + app.todos = new Todos(); +})(); diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/models/todo.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/models/todo.js new file mode 100644 index 0000000000..4f3fde3e26 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/models/todo.js @@ -0,0 +1,26 @@ +/*global Backbone */ +var app = app || {}; + +(function () { + 'use strict'; + + // Todo Model + // ---------- + + // Our basic **Todo** model has `title`, `order`, and `completed` attributes. + app.Todo = Backbone.Model.extend({ + // Default attributes for the todo + // and ensure that each todo created has `title` and `completed` keys. + defaults: { + title: '', + completed: false + }, + + // Toggle the `completed` state of this todo item. + toggle: function () { + this.save({ + completed: !this.get('completed') + }); + } + }); +})(); diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/routers/router.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/routers/router.js new file mode 100644 index 0000000000..5937e9ff03 --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/routers/router.js @@ -0,0 +1,26 @@ +/*global Backbone */ +var app = app || {}; + +(function () { + 'use strict'; + + // Todo Router + // ---------- + var TodoRouter = Backbone.Router.extend({ + routes: { + '*filter': 'setFilter' + }, + + setFilter: function (param) { + // Set the current filter to be used + app.TodoFilter = param || ''; + + // Trigger a collection filter event, causing hiding/unhiding + // of Todo view items + app.todos.trigger('filter'); + } + }); + + app.TodoRouter = new TodoRouter(); + Backbone.history.start(); +})(); diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/views/app-view.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/views/app-view.js new file mode 100644 index 0000000000..34252fc9cb --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/views/app-view.js @@ -0,0 +1,131 @@ +/*global Backbone, jQuery, _, ENTER_KEY */ +var app = app || {}; + +(function ($) { + 'use strict'; + + // The Application + // --------------- + + // Our overall **AppView** is the top-level piece of UI. + app.AppView = Backbone.View.extend({ + + // Instead of generating a new element, bind to the existing skeleton of + // the App already present in the HTML. + el: '.todoapp', + + // Our template for the line of statistics at the bottom of the app. + statsTemplate: _.template($('#stats-template').html()), + + // Delegated events for creating new items, and clearing completed ones. + events: { + 'keypress .new-todo': 'createOnEnter', + 'click .clear-completed': 'clearCompleted', + 'click .toggle-all': 'toggleAllComplete' + }, + + // At initialization we bind to the relevant events on the `Todos` + // collection, when items are added or changed. Kick things off by + // loading any preexisting todos that might be saved in *localStorage*. + initialize: function () { + this.allCheckbox = this.$('.toggle-all')[0]; + this.$input = this.$('.new-todo'); + this.$footer = this.$('.footer'); + this.$main = this.$('.main'); + this.$list = $('.todo-list'); + + this.listenTo(app.todos, 'add', this.addOne); + this.listenTo(app.todos, 'reset', this.addAll); + this.listenTo(app.todos, 'change:completed', this.filterOne); + this.listenTo(app.todos, 'filter', this.filterAll); + this.listenTo(app.todos, 'all', _.debounce(this.render, 0)); + + // Suppresses 'add' events with {reset: true} and prevents the app view + // from being re-rendered for every model. Only renders when the 'reset' + // event is triggered at the end of the fetch. + app.todos.fetch({reset: true}); + }, + + // Re-rendering the App just means refreshing the statistics -- the rest + // of the app doesn't change. + render: function () { + var completed = app.todos.completed().length; + var remaining = app.todos.remaining().length; + + if (app.todos.length) { + this.$main.show(); + this.$footer.show(); + + this.$footer.html(this.statsTemplate({ + completed: completed, + remaining: remaining + })); + + this.$('.filters li a') + .removeClass('selected') + .filter('[href="#/' + (app.TodoFilter || '') + '"]') + .addClass('selected'); + } else { + this.$main.hide(); + this.$footer.hide(); + } + + this.allCheckbox.checked = !remaining; + }, + + // Add a single todo item to the list by creating a view for it, and + // appending its element to the `<ul>`. + addOne: function (todo) { + var view = new app.TodoView({ model: todo }); + this.$list.append(view.render().el); + }, + + // Add all items in the **Todos** collection at once. + addAll: function () { + this.$list.html(''); + app.todos.each(this.addOne, this); + }, + + filterOne: function (todo) { + todo.trigger('visible'); + }, + + filterAll: function () { + app.todos.each(this.filterOne, this); + }, + + // Generate the attributes for a new Todo item. + newAttributes: function () { + return { + title: this.$input.val().trim(), + order: app.todos.nextOrder(), + completed: false + }; + }, + + // If you hit return in the main input field, create new **Todo** model, + // persisting it to *memory*. + createOnEnter: function (e) { + if (e.which === ENTER_KEY && this.$input.val().trim()) { + app.todos.create(this.newAttributes()); + this.$input.val(''); + } + }, + + // Clear all completed todo items, destroying their models. + clearCompleted: function () { + _.invoke(app.todos.completed(), 'destroy'); + return false; + }, + + toggleAllComplete: function () { + var completed = this.allCheckbox.checked; + + app.todos.each(function (todo) { + todo.save({ + completed: completed + }); + }); + } + }); +})(jQuery); diff --git a/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/views/todo-view.js b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/views/todo-view.js new file mode 100644 index 0000000000..bb3b4043ff --- /dev/null +++ b/third_party/webkit/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/backbone/js/views/todo-view.js @@ -0,0 +1,123 @@ +/*global Backbone, jQuery, _, ENTER_KEY, ESC_KEY */ +var app = app || {}; + +(function ($) { + 'use strict'; + + // Todo Item View + // -------------- + + // The DOM element for a todo item... + app.TodoView = Backbone.View.extend({ + //... is a list tag. + tagName: 'li', + + // Cache the template function for a single item. + template: _.template($('#item-template').html()), + + // The DOM events specific to an item. + events: { + 'click .toggle': 'toggleCompleted', + 'dblclick label': 'edit', + 'click .destroy': 'clear', + 'keypress .edit': 'updateOnEnter', + 'keydown .edit': 'revertOnEscape', + 'blur .edit': 'close' + }, + + // The TodoView listens for changes to its model, re-rendering. Since + // there's a one-to-one correspondence between a **Todo** and a + // **TodoView** in this app, we set a direct reference on the model for + // convenience. + initialize: function () { + this.listenTo(this.model, 'change', this.render); + this.listenTo(this.model, 'destroy', this.remove); + this.listenTo(this.model, 'visible', this.toggleVisible); + }, + + // Re-render the titles of the todo item. + render: function () { + // Backbone LocalStorage is adding `id` attribute instantly after + // creating a model. This causes our TodoView to render twice. Once + // after creating a model and once on `id` change. We want to + // filter out the second redundant render, which is caused by this + // `id` change. It's known Backbone LocalStorage bug, therefore + // we've to create a workaround. + // https://github.com/tastejs/todomvc/issues/469 + if (this.model.changed.id !== undefined) { + return; + } + + this.$el.html(this.template(this.model.toJSON())); + this.$el.toggleClass('completed', this.model.get('completed')); + this.toggleVisible(); + this.$input = this.$('.edit'); + return this; + }, + + toggleVisible: function () { + this.$el.toggleClass('hidden', this.isHidden()); + }, + + isHidden: function () { + return this.model.get('completed') ? + app.TodoFilter === 'active' : + app.TodoFilter === 'completed'; + }, + + // Toggle the `"completed"` state of the model. + toggleCompleted: function () { + this.model.toggle(); + }, + + // Switch this view into `"editing"` mode, displaying the input field. + edit: function () { + this.$el.addClass('editing'); + this.$input.focus(); + }, + + // Close the `"editing"` mode, saving changes to the todo. + close: function () { + var value = this.$input.val(); + var trimmedValue = value.trim(); + + // We don't want to handle blur events from an item that is no + // longer being edited. Relying on the CSS class here has the + // benefit of us not having to maintain state in the DOM and the + // JavaScript logic. + if (!this.$el.hasClass('editing')) { + return; + } + + if (trimmedValue) { + this.model.save({ title: trimmedValue }); + } else { + this.clear(); + } + + this.$el.removeClass('editing'); + }, + + // If you hit `enter`, we're through editing the item. + updateOnEnter: function (e) { + if (e.which === ENTER_KEY) { + this.close(); + } + }, + + // If you're pressing `escape` we revert your change by simply leaving + // the `editing` state. + revertOnEscape: function (e) { + if (e.which === ESC_KEY) { + this.$el.removeClass('editing'); + // Also reset the hidden input back to the original value. + this.$input.val(this.model.get('title')); + } + }, + + // Remove the item, destroy the model from *localStorage* and delete its view. + clear: function () { + this.model.destroy(); + } + }); +})(jQuery); |