├── README.md ├── assets ├── css │ ├── application.css │ ├── bootstrap.css │ ├── images │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ ├── ui-bg_glass_55_fbf9ee_1x400.png │ │ ├── ui-bg_glass_65_ffffff_1x400.png │ │ ├── ui-bg_glass_75_dadada_1x400.png │ │ ├── ui-bg_glass_75_e6e6e6_1x400.png │ │ ├── ui-bg_glass_75_ffffff_1x400.png │ │ ├── ui-bg_highlight-soft_75_cccccc_1x100.png │ │ ├── ui-bg_inset-soft_95_fef1ec_1x100.png │ │ ├── ui-icons_222222_256x240.png │ │ ├── ui-icons_2e83ff_256x240.png │ │ ├── ui-icons_454545_256x240.png │ │ ├── ui-icons_888888_256x240.png │ │ ├── ui-icons_cd0a0a_256x240.png │ │ └── ui-icons_f6cf3b_256x240.png │ └── jquery-ui-1.10.3.custom.css ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png └── js │ ├── app.js │ ├── apps │ ├── about │ │ ├── about_app.js │ │ └── show │ │ │ ├── show_controller.js │ │ │ ├── show_view.js │ │ │ └── templates │ │ │ └── message.tpl │ ├── config │ │ └── storage │ │ │ └── localstorage.js │ ├── contacts │ │ ├── common │ │ │ ├── templates │ │ │ │ └── form.tpl │ │ │ └── views.js │ │ ├── contacts_app.js │ │ ├── contacts_app_router.js │ │ ├── edit │ │ │ ├── edit_controller.js │ │ │ └── edit_view.js │ │ ├── list │ │ │ ├── list_controller.js │ │ │ ├── list_view.js │ │ │ └── templates │ │ │ │ ├── layout.tpl │ │ │ │ ├── list.tpl │ │ │ │ ├── list_item.tpl │ │ │ │ ├── none.tpl │ │ │ │ └── panel.tpl │ │ ├── new │ │ │ └── new_view.js │ │ └── show │ │ │ ├── show_controller.js │ │ │ ├── show_view.js │ │ │ └── templates │ │ │ ├── missing.tpl │ │ │ └── view.tpl │ └── header │ │ ├── header_app.js │ │ └── list │ │ ├── list_controller.js │ │ ├── list_view.js │ │ └── templates │ │ ├── list.tpl │ │ └── list_item.tpl │ ├── build.js │ ├── common │ ├── templates │ │ └── loading.tpl │ └── views.js │ ├── entities │ ├── common.js │ ├── contact.js │ └── header.js │ ├── require_main.built.js │ ├── require_main.js │ └── vendor │ ├── almond.js │ ├── backbone.js │ ├── backbone.localstorage.js │ ├── backbone.marionette.js │ ├── backbone.picky.js │ ├── backbone.syphon.js │ ├── jquery-ui.js │ ├── jquery.js │ ├── json2.js │ ├── require.js │ ├── spin.jquery.js │ ├── spin.js │ ├── text.js │ ├── underscore-tpl.js │ └── underscore.js ├── index.html └── r.js /README.md: -------------------------------------------------------------------------------- 1 | # Structuring Backbone Code with RequireJS and Marionette Modules 2 | 3 | This code repository is the companion to my book on [using RequireJS with Marionette.js](https://leanpub.com/structuring-backbone-with-requirejs-and-marionette). 4 | 5 | Even if you don't have the book, you can learn a lot from looking through the commit history. But if you're serious about wanting to become competent with RequireJS (and save time dealing with errors), I suggest you consider purchasing the book: typical mistakes and various tips, techniques, and explanations are indicated in the book and absent from this repository. -------------------------------------------------------------------------------- /assets/css/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px; /* 60px to make the container make room for the navigation bar */ 3 | } -------------------------------------------------------------------------------- /assets/css/images/ui-bg_flat_0_aaaaaa_40x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_flat_0_aaaaaa_40x100.png -------------------------------------------------------------------------------- /assets/css/images/ui-bg_glass_55_fbf9ee_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_glass_55_fbf9ee_1x400.png -------------------------------------------------------------------------------- /assets/css/images/ui-bg_glass_65_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_glass_65_ffffff_1x400.png -------------------------------------------------------------------------------- /assets/css/images/ui-bg_glass_75_dadada_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_glass_75_dadada_1x400.png -------------------------------------------------------------------------------- /assets/css/images/ui-bg_glass_75_e6e6e6_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_glass_75_e6e6e6_1x400.png -------------------------------------------------------------------------------- /assets/css/images/ui-bg_glass_75_ffffff_1x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_glass_75_ffffff_1x400.png -------------------------------------------------------------------------------- /assets/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png -------------------------------------------------------------------------------- /assets/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png -------------------------------------------------------------------------------- /assets/css/images/ui-icons_222222_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-icons_222222_256x240.png -------------------------------------------------------------------------------- /assets/css/images/ui-icons_2e83ff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-icons_2e83ff_256x240.png -------------------------------------------------------------------------------- /assets/css/images/ui-icons_454545_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-icons_454545_256x240.png -------------------------------------------------------------------------------- /assets/css/images/ui-icons_888888_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-icons_888888_256x240.png -------------------------------------------------------------------------------- /assets/css/images/ui-icons_cd0a0a_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-icons_cd0a0a_256x240.png -------------------------------------------------------------------------------- /assets/css/images/ui-icons_f6cf3b_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/css/images/ui-icons_f6cf3b_256x240.png -------------------------------------------------------------------------------- /assets/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /assets/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davidsulc/structuring-backbone-with-requirejs-and-marionette/5b7fdc52f34992ef3eeedf01324004b765f3edbf/assets/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /assets/js/app.js: -------------------------------------------------------------------------------- 1 | define(["marionette", "jquery-ui"], function(Marionette){ 2 | var ContactManager = new Marionette.Application(); 3 | 4 | ContactManager.navigate = function(route, options){ 5 | options || (options = {}); 6 | Backbone.history.navigate(route, options); 7 | }; 8 | 9 | ContactManager.getCurrentRoute = function(){ 10 | return Backbone.history.fragment 11 | }; 12 | 13 | ContactManager.startSubApp = function(appName, args){ 14 | var currentApp = appName ? ContactManager.module(appName) : null; 15 | if (ContactManager.currentApp === currentApp){ return; } 16 | 17 | if (ContactManager.currentApp){ 18 | ContactManager.currentApp.stop(); 19 | } 20 | 21 | ContactManager.currentApp = currentApp; 22 | if(currentApp){ 23 | currentApp.start(args); 24 | } 25 | }; 26 | 27 | ContactManager.on("before:start", function(){ 28 | var RegionContainer = Marionette.LayoutView.extend({ 29 | el: "#app-container", 30 | 31 | regions: { 32 | header: "#header-region", 33 | main: "#main-region", 34 | dialog: "#dialog-region" 35 | } 36 | }); 37 | 38 | ContactManager.regions = new RegionContainer(); 39 | ContactManager.regions.dialog.onShow = function(view){ 40 | var self = this; 41 | var closeDialog = function(){ 42 | self.stopListening(); 43 | self.empty(); 44 | self.$el.dialog("destroy"); 45 | }; 46 | 47 | this.listenTo(view, "dialog:close", closeDialog); 48 | 49 | this.$el.dialog({ 50 | modal: true, 51 | title: view.title, 52 | width: "auto", 53 | close: function(e, ui){ 54 | closeDialog(); 55 | } 56 | }); 57 | }; 58 | }); 59 | 60 | ContactManager.on("start", function(){ 61 | if(Backbone.history){ 62 | require(["apps/contacts/contacts_app", "apps/about/about_app"], function () { 63 | Backbone.history.start(); 64 | 65 | if(ContactManager.getCurrentRoute() === ""){ 66 | ContactManager.trigger("contacts:list"); 67 | } 68 | }); 69 | } 70 | }); 71 | 72 | return ContactManager; 73 | }); 74 | -------------------------------------------------------------------------------- /assets/js/apps/about/about_app.js: -------------------------------------------------------------------------------- 1 | define(["app", "marionette"], function(ContactManager, Marionette){ 2 | var Router = Marionette.AppRouter.extend({ 3 | appRoutes: { 4 | "about" : "showAbout" 5 | } 6 | }); 7 | 8 | var API = { 9 | showAbout: function(){ 10 | require(["apps/about/show/show_controller"], function(ShowController){ 11 | ContactManager.startSubApp(null); 12 | ShowController.showAbout(); 13 | ContactManager.execute("set:active:header", "about"); 14 | }); 15 | } 16 | }; 17 | 18 | ContactManager.on("about:show", function(){ 19 | ContactManager.navigate("about"); 20 | API.showAbout(); 21 | }); 22 | 23 | ContactManager.on("before:start", function(){ 24 | new Router({ 25 | controller: API 26 | }); 27 | }); 28 | 29 | return Router; 30 | }); 31 | -------------------------------------------------------------------------------- /assets/js/apps/about/show/show_controller.js: -------------------------------------------------------------------------------- 1 | define(["app", "apps/about/show/show_view"], function(ContactManager, View){ 2 | return { 3 | showAbout: function(){ 4 | var view = new View.Message(); 5 | ContactManager.regions.main.show(view); 6 | } 7 | }; 8 | }); 9 | -------------------------------------------------------------------------------- /assets/js/apps/about/show/show_view.js: -------------------------------------------------------------------------------- 1 | define(["marionette", "tpl!apps/about/show/templates/message.tpl"], function(Marionette, messageTpl){ 2 | return { 3 | Message: Marionette.ItemView.extend({ 4 | template: messageTpl 5 | }) 6 | }; 7 | }); 8 | -------------------------------------------------------------------------------- /assets/js/apps/about/show/templates/message.tpl: -------------------------------------------------------------------------------- 1 |
This application was designed to accompany you during your learning.
3 |Hopefully, it has served you well !
4 | -------------------------------------------------------------------------------- /assets/js/apps/config/storage/localstorage.js: -------------------------------------------------------------------------------- 1 | define(["app", "localstorage"], function(ContactManager){ 2 | ContactManager.module("Entities", function(Entities, ContactManager, Backbone, Marionette, $, _){ 3 | var findStorageKey = function(entity){ 4 | // use a model's urlRoot value 5 | if(entity.urlRoot){ 6 | return _.result(entity, "urlRoot"); 7 | } 8 | // use a collection's url value 9 | if(entity.url){ 10 | return _.result(entity, "url"); 11 | } 12 | 13 | throw new Error("Unable to determine storage key"); 14 | }; 15 | 16 | var storageCache = {}; 17 | var getStorage = function(key){ 18 | var storage = storageCache[key]; 19 | if(storage){ 20 | return storage; 21 | } 22 | var newStorage = new Backbone.LocalStorage(key); 23 | storageCache[key] = newStorage; 24 | return newStorage; 25 | } 26 | 27 | var StorageMixin = function(entityPrototype){ 28 | var storageKey = findStorageKey(entityPrototype); 29 | return { localStorage: getStorage(storageKey) }; 30 | }; 31 | 32 | Entities.configureStorage = function(constructorString, constructor){ 33 | var OldConstructor = constructor; 34 | var NewConstructor = function(){ 35 | var obj = new OldConstructor(arguments[0], arguments[1]); 36 | _.extend(obj, new StorageMixin(OldConstructor.prototype)); 37 | return obj; 38 | } 39 | NewConstructor.prototype = OldConstructor.prototype; 40 | 41 | eval(constructorString + " = NewConstructor;"); 42 | }; 43 | }); 44 | 45 | return ContactManager.Entities.configureStorage; 46 | }); 47 | -------------------------------------------------------------------------------- /assets/js/apps/contacts/common/templates/form.tpl: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /assets/js/apps/contacts/common/views.js: -------------------------------------------------------------------------------- 1 | define(["app", "tpl!apps/contacts/common/templates/form.tpl", "backbone.syphon"], function(ContactManager, formTpl){ 2 | ContactManager.module("ContactsApp.Common.Views", function(Views, ContactManager, Backbone, Marionette, $, _){ 3 | Views.Form = Marionette.ItemView.extend({ 4 | template: formTpl, 5 | 6 | events: { 7 | "click button.js-submit": "submitClicked" 8 | }, 9 | 10 | submitClicked: function(e){ 11 | e.preventDefault(); 12 | var data = Backbone.Syphon.serialize(this); 13 | this.trigger("form:submit", data); 14 | }, 15 | 16 | onFormDataInvalid: function(errors){ 17 | var $view = this.$el; 18 | 19 | var clearFormErrors = function(){ 20 | var $form = $view.find("form"); 21 | $form.find(".help-inline.error").each(function(){ 22 | $(this).remove(); 23 | }); 24 | $form.find(".control-group.error").each(function(){ 25 | $(this).removeClass("error"); 26 | }); 27 | } 28 | 29 | var markErrors = function(value, key){ 30 | var $controlGroup = $view.find("#contact-" + key).parent(); 31 | var $errorEl = $("", { class: "help-inline error", text: value }); 32 | $controlGroup.append($errorEl).addClass("error"); 33 | } 34 | 35 | clearFormErrors(); 36 | _.each(errors, markErrors); 37 | } 38 | }); 39 | }); 40 | 41 | return ContactManager.ContactsApp.Common.Views; 42 | }); 43 | -------------------------------------------------------------------------------- /assets/js/apps/contacts/contacts_app.js: -------------------------------------------------------------------------------- 1 | define(["app"], function(ContactManager){ 2 | ContactManager.module("ContactsApp", function(ContactsApp, ContactManager, Backbone, Marionette, $, _){ 3 | ContactsApp.startWithParent = false; 4 | 5 | ContactsApp.onStart = function(){ 6 | console.log("starting ContactsApp"); 7 | }; 8 | 9 | ContactsApp.onStop = function(){ 10 | console.log("stopping ContactsApp"); 11 | }; 12 | }); 13 | 14 | return ContactManager.ContactsApp; 15 | }); 16 | -------------------------------------------------------------------------------- /assets/js/apps/contacts/contacts_app_router.js: -------------------------------------------------------------------------------- 1 | define(["app"], function(ContactManager){ 2 | ContactManager.module("Routers.ContactsApp", function(ContactsAppRouter, ContactManager, Backbone, Marionette, $, _){ 3 | ContactsAppRouter.Router = Marionette.AppRouter.extend({ 4 | appRoutes: { 5 | "contacts(/filter/criterion::criterion)": "listContacts", 6 | "contacts/:id": "showContact", 7 | "contacts/:id/edit": "editContact" 8 | } 9 | }); 10 | 11 | var executeAction = function(action, arg){ 12 | ContactManager.startSubApp("ContactsApp"); 13 | action(arg); 14 | ContactManager.execute("set:active:header", "contacts"); 15 | }; 16 | 17 | var API = { 18 | listContacts: function(criterion){ 19 | require(["apps/contacts/list/list_controller"], function(ListController){ 20 | executeAction(ListController.listContacts, criterion); 21 | }); 22 | }, 23 | 24 | showContact: function(id){ 25 | require(["apps/contacts/show/show_controller"], function(ShowController){ 26 | executeAction(ShowController.showContact, id); 27 | }); 28 | }, 29 | 30 | editContact: function(id){ 31 | require(["apps/contacts/edit/edit_controller"], function(EditController){ 32 | executeAction(EditController.editContact, id); 33 | }); 34 | } 35 | }; 36 | 37 | ContactManager.on("contacts:list", function(){ 38 | ContactManager.navigate("contacts"); 39 | API.listContacts(); 40 | }); 41 | 42 | ContactManager.on("contacts:filter", function(criterion){ 43 | if(criterion){ 44 | ContactManager.navigate("contacts/filter/criterion:" + criterion); 45 | } 46 | else{ 47 | ContactManager.navigate("contacts"); 48 | } 49 | }); 50 | 51 | ContactManager.on("contact:show", function(id){ 52 | ContactManager.navigate("contacts/" + id); 53 | API.showContact(id); 54 | }); 55 | 56 | ContactManager.on("contact:edit", function(id){ 57 | ContactManager.navigate("contacts/" + id + "/edit"); 58 | API.editContact(id); 59 | }); 60 | 61 | ContactManager.Routers.on("start", function(){ 62 | new ContactsAppRouter.Router({ 63 | controller: API 64 | }); 65 | }); 66 | }); 67 | 68 | return ContactManager.ContactsAppRouter; 69 | }); 70 | -------------------------------------------------------------------------------- /assets/js/apps/contacts/edit/edit_controller.js: -------------------------------------------------------------------------------- 1 | define(["app", "apps/contacts/edit/edit_view"], function(ContactManager, View){ 2 | ContactManager.module("ContactsApp.Edit", function(Edit, ContactManager, Backbone, Marionette, $, _){ 3 | Edit.Controller = { 4 | editContact: function(id){ 5 | require(["common/views", "entities/contact"], function(CommonViews){ 6 | var loadingView = new CommonViews.Loading({ 7 | title: "Artificial Loading Delay", 8 | message: "Data loading is delayed to demonstrate using a loading view." 9 | }); 10 | ContactManager.regions.main.show(loadingView); 11 | 12 | var fetchingContact = ContactManager.request("contact:entity", id); 13 | $.when(fetchingContact).done(function(contact){ 14 | var view; 15 | if(contact !== undefined){ 16 | view = new View.Contact({ 17 | model: contact, 18 | generateTitle: true 19 | }); 20 | 21 | view.on("form:submit", function(data){ 22 | if(contact.save(data)){ 23 | ContactManager.trigger("contact:show", contact.get('id')); 24 | } 25 | else{ 26 | view.triggerMethod("form:data:invalid", contact.validationError); 27 | } 28 | }); 29 | } 30 | else{ 31 | view = new ContactManager.ContactsApp.Show.MissingContact(); 32 | } 33 | 34 | ContactManager.regions.main.show(view); 35 | }); 36 | }); 37 | } 38 | }; 39 | }); 40 | 41 | return ContactManager.ContactsApp.Edit.Controller; 42 | }); 43 | -------------------------------------------------------------------------------- /assets/js/apps/contacts/edit/edit_view.js: -------------------------------------------------------------------------------- 1 | define(["app", "apps/contacts/common/views"], function(ContactManager, CommonViews){ 2 | ContactManager.module("ContactsApp.Edit.View", function(View, ContactManager, Backbone, Marionette, $, _){ 3 | View.Contact = CommonViews.Form.extend({ 4 | initialize: function(){ 5 | this.title = "Edit " + this.model.get("firstName") + " " + this.model.get("lastName"); 6 | }, 7 | 8 | onRender: function(){ 9 | if(this.options.generateTitle){ 10 | var $title = $("Phone number: <%- phoneNumber %>
7 | -------------------------------------------------------------------------------- /assets/js/apps/header/header_app.js: -------------------------------------------------------------------------------- 1 | define(["app", "apps/header/list/list_controller"], function(ContactManager, ListController){ 2 | ContactManager.module("HeaderApp", function(Header, ContactManager, Backbone, Marionette, $, _){ 3 | var API = { 4 | listHeader: function(){ 5 | ListController.listHeader(); 6 | } 7 | }; 8 | 9 | ContactManager.commands.setHandler("set:active:header", function(name){ 10 | ListController.setActiveHeader(name); 11 | }); 12 | 13 | Header.on("start", function(){ 14 | API.listHeader(); 15 | }); 16 | }); 17 | 18 | return ContactManager.HeaderApp; 19 | }); 20 | -------------------------------------------------------------------------------- /assets/js/apps/header/list/list_controller.js: -------------------------------------------------------------------------------- 1 | define(["app", "apps/header/list/list_view"], function(ContactManager, View){ 2 | ContactManager.module("HeaderApp.List", function(List, ContactManager, Backbone, Marionette, $, _){ 3 | List.Controller = { 4 | listHeader: function(){ 5 | require(["entities/header"], function(){ 6 | var links = ContactManager.request("header:entities"); 7 | var headers = new View.Headers({collection: links}); 8 | 9 | headers.on("brand:clicked", function(){ 10 | ContactManager.trigger("contacts:list"); 11 | }); 12 | 13 | headers.on("childview:navigate", function(childView, model){ 14 | var trigger = model.get("navigationTrigger"); 15 | ContactManager.trigger(trigger); 16 | }); 17 | 18 | ContactManager.regions.header.show(headers); 19 | }); 20 | }, 21 | 22 | setActiveHeader: function(headerUrl){ 23 | var links = ContactManager.request("header:entities"); 24 | var headerToSelect = links.find(function(header){ return header.get("url") === headerUrl; }); 25 | headerToSelect.select(); 26 | links.trigger("reset"); 27 | } 28 | }; 29 | }); 30 | 31 | return ContactManager.HeaderApp.List.Controller; 32 | }); 33 | -------------------------------------------------------------------------------- /assets/js/apps/header/list/list_view.js: -------------------------------------------------------------------------------- 1 | define(["app", 2 | "tpl!apps/header/list/templates/list.tpl", 3 | "tpl!apps/header/list/templates/list_item.tpl"], 4 | function(ContactManager, listTpl, listItemTpl){ 5 | ContactManager.module("HeaderApp.List.View", function(View, ContactManager, Backbone, Marionette, $, _){ 6 | View.Header = Marionette.ItemView.extend({ 7 | template: listItemTpl, 8 | tagName: "li", 9 | 10 | events: { 11 | "click a": "navigate" 12 | }, 13 | 14 | navigate: function(e){ 15 | e.preventDefault(); 16 | this.trigger("navigate", this.model); 17 | }, 18 | 19 | onRender: function(){ 20 | if(this.model.selected){ 21 | // add class so Bootstrap will highlight the active entry in the navbar 22 | this.$el.addClass("active"); 23 | }; 24 | } 25 | }); 26 | 27 | View.Headers = Marionette.CompositeView.extend({ 28 | template: listTpl, 29 | className: "navbar navbar-inverse navbar-fixed-top", 30 | childView: View.Header, 31 | childViewContainer: "ul", 32 | 33 | events: { 34 | "click a.brand": "brandClicked" 35 | }, 36 | 37 | brandClicked: function(e){ 38 | e.preventDefault(); 39 | this.trigger("brand:clicked"); 40 | } 41 | }); 42 | }); 43 | 44 | return ContactManager.HeaderApp.List.View; 45 | }); 46 | -------------------------------------------------------------------------------- /assets/js/apps/header/list/templates/list.tpl: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /assets/js/apps/header/list/templates/list_item.tpl: -------------------------------------------------------------------------------- 1 | <%- name %> 2 | -------------------------------------------------------------------------------- /assets/js/build.js: -------------------------------------------------------------------------------- 1 | ({ 2 | baseUrl: ".", 3 | name: "vendor/almond", 4 | include: "require_main", 5 | mainConfigFile: "require_main.js", 6 | out: "require_main.built.js", 7 | wrapShim: true, 8 | findNestedDependencies: true 9 | }) 10 | -------------------------------------------------------------------------------- /assets/js/common/templates/loading.tpl: -------------------------------------------------------------------------------- 1 |<%- message %>
3 | 4 | -------------------------------------------------------------------------------- /assets/js/common/views.js: -------------------------------------------------------------------------------- 1 | define(["app", "tpl!common/templates/loading.tpl", "spin.jquery"], function(ContactManager, loadingTpl){ 2 | ContactManager.module("Common.Views", function(Views, ContactManager, Backbone, Marionette, $, _){ 3 | Views.Loading = Marionette.ItemView.extend({ 4 | template: loadingTpl, 5 | 6 | title: "Loading Data", 7 | message: "Please wait, data is loading.", 8 | 9 | serializeData: function(){ 10 | return { 11 | title: Marionette.getOption(this, "title"), 12 | message: Marionette.getOption(this, "message") 13 | } 14 | }, 15 | 16 | onShow: function(){ 17 | var opts = { 18 | lines: 13, // The number of lines to draw 19 | length: 20, // The length of each line 20 | width: 10, // The line thickness 21 | radius: 30, // The radius of the inner circle 22 | corners: 1, // Corner roundness (0..1) 23 | rotate: 0, // The rotation offset 24 | direction: 1, // 1: clockwise, -1: counterclockwise 25 | color: "#000", // #rgb or #rrggbb 26 | speed: 1, // Rounds per second 27 | trail: 60, // Afterglow percentage 28 | shadow: false, // Whether to render a shadow 29 | hwaccel: false, // Whether to use hardware acceleration 30 | className: "spinner", // The CSS class to assign to the spinner 31 | zIndex: 2e9, // The z-index (defaults to 2000000000) 32 | top: "30px", // Top position relative to parent in px 33 | left: "auto" // Left position relative to parent in px 34 | }; 35 | $("#spinner").spin(opts); 36 | } 37 | }); 38 | }); 39 | 40 | return ContactManager.Common.Views; 41 | }); 42 | -------------------------------------------------------------------------------- /assets/js/entities/common.js: -------------------------------------------------------------------------------- 1 | define(["app"], function(ContactManager){ 2 | ContactManager.module("Entities", function(Entities, ContactManager, Backbone, Marionette, $, _){ 3 | Entities.FilteredCollection = function(options){ 4 | var original = options.collection; 5 | var filtered = new original.constructor(); 6 | filtered.add(original.models); 7 | filtered.filterFunction = options.filterFunction; 8 | 9 | var applyFilter = function(filterCriterion, filterStrategy, collection){ 10 | var collection = collection || original; 11 | var criterion; 12 | if(filterStrategy == "filter"){ 13 | criterion = filterCriterion.trim(); 14 | } 15 | else{ 16 | criterion = filterCriterion; 17 | } 18 | 19 | var items = []; 20 | if(criterion){ 21 | if(filterStrategy == "filter"){ 22 | if( ! filtered.filterFunction){ 23 | throw("Attempted to use 'filter' function, but none was defined"); 24 | } 25 | var filterFunction = filtered.filterFunction(criterion); 26 | items = collection.filter(filterFunction); 27 | } 28 | else{ 29 | items = collection.where(criterion); 30 | } 31 | } 32 | else{ 33 | items = collection.models; 34 | } 35 | 36 | // store current criterion 37 | filtered._currentCriterion = criterion; 38 | 39 | return items; 40 | }; 41 | 42 | filtered.filter = function(filterCriterion){ 43 | filtered._currentFilter = "filter"; 44 | var items = applyFilter(filterCriterion, "filter"); 45 | 46 | // reset the filtered collection with the new items 47 | filtered.reset(items); 48 | return filtered; 49 | }; 50 | 51 | filtered.where = function(filterCriterion){ 52 | filtered._currentFilter = "where"; 53 | var items = applyFilter(filterCriterion, "where"); 54 | 55 | // reset the filtered collection with the new items 56 | filtered.reset(items); 57 | return filtered; 58 | }; 59 | 60 | // when the original collection is reset, 61 | // the filtered collection will re-filter itself 62 | // and end up with the new filtered result set 63 | original.on("reset", function(){ 64 | var items = applyFilter(filtered._currentCriterion, filtered._currentFilter); 65 | 66 | // reset the filtered collection with the new items 67 | filtered.reset(items); 68 | }); 69 | 70 | // if the original collection gets models added to it: 71 | // 1. create a new collection 72 | // 2. filter it 73 | // 3. add the filtered models (i.e. the models that were added *and* 74 | // match the filtering criterion) to the `filtered` collection 75 | original.on("add", function(models){ 76 | var coll = new original.constructor(); 77 | coll.add(models); 78 | var items = applyFilter(filtered._currentCriterion, filtered._currentFilter, coll); 79 | filtered.add(items); 80 | }); 81 | 82 | return filtered; 83 | }; 84 | }); 85 | 86 | return ContactManager.Entities.FilteredCollection; 87 | }); 88 | -------------------------------------------------------------------------------- /assets/js/entities/contact.js: -------------------------------------------------------------------------------- 1 | define(["app", "apps/config/storage/localstorage"], function(ContactManager){ 2 | ContactManager.module("Entities", function(Entities, ContactManager, Backbone, Marionette, $, _){ 3 | Entities.Contact = Backbone.Model.extend({ 4 | urlRoot: "contacts", 5 | 6 | defaults: { 7 | firstName: "", 8 | lastName: "", 9 | phoneNumber: "" 10 | }, 11 | 12 | validate: function(attrs, options) { 13 | var errors = {} 14 | if (! attrs.firstName) { 15 | errors.firstName = "can't be blank"; 16 | } 17 | if (! attrs.lastName) { 18 | errors.lastName = "can't be blank"; 19 | } 20 | else{ 21 | if (attrs.lastName.length < 2) { 22 | errors.lastName = "is too short"; 23 | } 24 | } 25 | if( ! _.isEmpty(errors)){ 26 | return errors; 27 | } 28 | } 29 | }); 30 | 31 | Entities.configureStorage("ContactManager.Entities.Contact", Entities.Contact); 32 | 33 | Entities.ContactCollection = Backbone.Collection.extend({ 34 | url: "contacts", 35 | model: Entities.Contact, 36 | comparator: "firstName" 37 | }); 38 | 39 | Entities.configureStorage("ContactManager.Entities.ContactCollection", Entities.ContactCollection); 40 | 41 | var initializeContacts = function(){ 42 | var contacts = new Entities.ContactCollection([ 43 | { id: 1, firstName: "Alice", lastName: "Arten", phoneNumber: "555-0184" }, 44 | { id: 2, firstName: "Bob", lastName: "Brigham", phoneNumber: "555-0163" }, 45 | { id: 3, firstName: "Charlie", lastName: "Campbell", phoneNumber: "555-0129" } 46 | ]); 47 | contacts.forEach(function(contact){ 48 | contact.save(); 49 | }); 50 | return contacts.models; 51 | }; 52 | 53 | var API = { 54 | getContactEntities: function(){ 55 | var contacts = new Entities.ContactCollection(); 56 | var defer = $.Deferred(); 57 | contacts.fetch({ 58 | success: function(data){ 59 | defer.resolve(data); 60 | } 61 | }); 62 | var promise = defer.promise(); 63 | $.when(promise).done(function(fetchedContacts){ 64 | if(fetchedContacts.length === 0){ 65 | // if we don't have any contacts yet, create some for convenience 66 | var models = initializeContacts(); 67 | contacts.reset(models); 68 | } 69 | }); 70 | return promise; 71 | }, 72 | 73 | getContactEntity: function(contactId){ 74 | var contact = new Entities.Contact({id: contactId}); 75 | var defer = $.Deferred(); 76 | setTimeout(function(){ 77 | contact.fetch({ 78 | success: function(data){ 79 | defer.resolve(data); 80 | }, 81 | error: function(data){ 82 | defer.resolve(undefined); 83 | } 84 | }); 85 | }, 2000); 86 | return defer.promise(); 87 | } 88 | }; 89 | 90 | ContactManager.reqres.setHandler("contact:entities", function(){ 91 | return API.getContactEntities(); 92 | }); 93 | 94 | ContactManager.reqres.setHandler("contact:entity", function(id){ 95 | return API.getContactEntity(id); 96 | }); 97 | 98 | ContactManager.reqres.setHandler("contact:entity:new", function(id){ 99 | return new Entities.Contact(); 100 | }); 101 | }); 102 | 103 | return ; 104 | }); 105 | -------------------------------------------------------------------------------- /assets/js/entities/header.js: -------------------------------------------------------------------------------- 1 | define(["app", "backbone.picky"], function(ContactManager){ 2 | ContactManager.module("Entities", function(Entities, ContactManager, Backbone, Marionette, $, _){ 3 | Entities.Header = Backbone.Model.extend({ 4 | initialize: function(){ 5 | var selectable = new Backbone.Picky.Selectable(this); 6 | _.extend(this, selectable); 7 | } 8 | }); 9 | 10 | Entities.HeaderCollection = Backbone.Collection.extend({ 11 | model: Entities.Header, 12 | 13 | initialize: function(){ 14 | var singleSelect = new Backbone.Picky.SingleSelect(this); 15 | _.extend(this, singleSelect); 16 | } 17 | }); 18 | 19 | var initializeHeaders = function(){ 20 | Entities.headers = new Entities.HeaderCollection([ 21 | { name: "Contacts", url: "contacts", navigationTrigger: "contacts:list" }, 22 | { name: "About", url: "about", navigationTrigger: "about:show" } 23 | ]); 24 | }; 25 | 26 | var API = { 27 | getHeaders: function(){ 28 | if(Entities.headers === undefined){ 29 | initializeHeaders(); 30 | } 31 | return Entities.headers; 32 | } 33 | }; 34 | 35 | ContactManager.reqres.setHandler("header:entities", function(){ 36 | return API.getHeaders(); 37 | }); 38 | }); 39 | 40 | return ; 41 | }); 42 | -------------------------------------------------------------------------------- /assets/js/require_main.js: -------------------------------------------------------------------------------- 1 | requirejs.config({ 2 | baseUrl: "assets/js", 3 | paths: { 4 | backbone: "vendor/backbone", 5 | "backbone.picky": "vendor/backbone.picky", 6 | "backbone.syphon": "vendor/backbone.syphon", 7 | jquery: "vendor/jquery", 8 | "jquery-ui": "vendor/jquery-ui", 9 | json2: "vendor/json2", 10 | localstorage: "vendor/backbone.localstorage", 11 | marionette: "vendor/backbone.marionette", 12 | spin: "vendor/spin", 13 | "spin.jquery": "vendor/spin.jquery", 14 | text: "vendor/text", 15 | tpl: "vendor/underscore-tpl", 16 | underscore: "vendor/underscore" 17 | }, 18 | 19 | shim: { 20 | underscore: { 21 | exports: "_" 22 | }, 23 | backbone: { 24 | deps: ["jquery", "underscore", "json2"], 25 | exports: "Backbone" 26 | }, 27 | "backbone.picky": ["backbone"], 28 | "backbone.syphon": ["backbone"], 29 | marionette: { 30 | deps: ["backbone"], 31 | exports: "Marionette" 32 | }, 33 | "jquery-ui": ["jquery"], 34 | localstorage: ["backbone"], 35 | "spin.jquery": ["spin", "jquery"], 36 | tpl: ["text"] 37 | } 38 | }); 39 | 40 | require(["app", "apps/header/header_app", "apps/contacts/contacts_app_router", "apps/about/about_app"], function(ContactManager){ 41 | ContactManager.start(); 42 | }); 43 | -------------------------------------------------------------------------------- /assets/js/vendor/almond.js: -------------------------------------------------------------------------------- 1 | /** 2 | * almond 0.2.6 Copyright (c) 2011-2012, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/jrburke/almond for details 5 | */ 6 | //Going sloppy to avoid 'use strict' string cost, but strict practices should 7 | //be followed. 8 | /*jslint sloppy: true */ 9 | /*global setTimeout: false */ 10 | 11 | var requirejs, require, define; 12 | (function (undef) { 13 | var main, req, makeMap, handlers, 14 | defined = {}, 15 | waiting = {}, 16 | config = {}, 17 | defining = {}, 18 | hasOwn = Object.prototype.hasOwnProperty, 19 | aps = [].slice; 20 | 21 | function hasProp(obj, prop) { 22 | return hasOwn.call(obj, prop); 23 | } 24 | 25 | /** 26 | * Given a relative module name, like ./something, normalize it to 27 | * a real name that can be mapped to a path. 28 | * @param {String} name the relative name 29 | * @param {String} baseName a real name that the name arg is relative 30 | * to. 31 | * @returns {String} normalized name 32 | */ 33 | function normalize(name, baseName) { 34 | var nameParts, nameSegment, mapValue, foundMap, 35 | foundI, foundStarMap, starI, i, j, part, 36 | baseParts = baseName && baseName.split("/"), 37 | map = config.map, 38 | starMap = (map && map['*']) || {}; 39 | 40 | //Adjust any relative paths. 41 | if (name && name.charAt(0) === ".") { 42 | //If have a base name, try to normalize against it, 43 | //otherwise, assume it is a top-level require that will 44 | //be relative to baseUrl in the end. 45 | if (baseName) { 46 | //Convert baseName to array, and lop off the last part, 47 | //so that . matches that "directory" and not name of the baseName's 48 | //module. For instance, baseName of "one/two/three", maps to 49 | //"one/two/three.js", but we want the directory, "one/two" for 50 | //this normalization. 51 | baseParts = baseParts.slice(0, baseParts.length - 1); 52 | 53 | name = baseParts.concat(name.split("/")); 54 | 55 | //start trimDots 56 | for (i = 0; i < name.length; i += 1) { 57 | part = name[i]; 58 | if (part === ".") { 59 | name.splice(i, 1); 60 | i -= 1; 61 | } else if (part === "..") { 62 | if (i === 1 && (name[2] === '..' || name[0] === '..')) { 63 | //End of the line. Keep at least one non-dot 64 | //path segment at the front so it can be mapped 65 | //correctly to disk. Otherwise, there is likely 66 | //no path mapping for a path starting with '..'. 67 | //This can still fail, but catches the most reasonable 68 | //uses of .. 69 | break; 70 | } else if (i > 0) { 71 | name.splice(i - 1, 2); 72 | i -= 2; 73 | } 74 | } 75 | } 76 | //end trimDots 77 | 78 | name = name.join("/"); 79 | } else if (name.indexOf('./') === 0) { 80 | // No baseName, so this is ID is resolved relative 81 | // to baseUrl, pull off the leading dot. 82 | name = name.substring(2); 83 | } 84 | } 85 | 86 | //Apply map config if available. 87 | if ((baseParts || starMap) && map) { 88 | nameParts = name.split('/'); 89 | 90 | for (i = nameParts.length; i > 0; i -= 1) { 91 | nameSegment = nameParts.slice(0, i).join("/"); 92 | 93 | if (baseParts) { 94 | //Find the longest baseName segment match in the config. 95 | //So, do joins on the biggest to smallest lengths of baseParts. 96 | for (j = baseParts.length; j > 0; j -= 1) { 97 | mapValue = map[baseParts.slice(0, j).join('/')]; 98 | 99 | //baseName segment has config, find if it has one for 100 | //this name. 101 | if (mapValue) { 102 | mapValue = mapValue[nameSegment]; 103 | if (mapValue) { 104 | //Match, update name to the new value. 105 | foundMap = mapValue; 106 | foundI = i; 107 | break; 108 | } 109 | } 110 | } 111 | } 112 | 113 | if (foundMap) { 114 | break; 115 | } 116 | 117 | //Check for a star map match, but just hold on to it, 118 | //if there is a shorter segment match later in a matching 119 | //config, then favor over this star map. 120 | if (!foundStarMap && starMap && starMap[nameSegment]) { 121 | foundStarMap = starMap[nameSegment]; 122 | starI = i; 123 | } 124 | } 125 | 126 | if (!foundMap && foundStarMap) { 127 | foundMap = foundStarMap; 128 | foundI = starI; 129 | } 130 | 131 | if (foundMap) { 132 | nameParts.splice(0, foundI, foundMap); 133 | name = nameParts.join('/'); 134 | } 135 | } 136 | 137 | return name; 138 | } 139 | 140 | function makeRequire(relName, forceSync) { 141 | return function () { 142 | //A version of a require function that passes a moduleName 143 | //value for items that may need to 144 | //look up paths relative to the moduleName 145 | return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync])); 146 | }; 147 | } 148 | 149 | function makeNormalize(relName) { 150 | return function (name) { 151 | return normalize(name, relName); 152 | }; 153 | } 154 | 155 | function makeLoad(depName) { 156 | return function (value) { 157 | defined[depName] = value; 158 | }; 159 | } 160 | 161 | function callDep(name) { 162 | if (hasProp(waiting, name)) { 163 | var args = waiting[name]; 164 | delete waiting[name]; 165 | defining[name] = true; 166 | main.apply(undef, args); 167 | } 168 | 169 | if (!hasProp(defined, name) && !hasProp(defining, name)) { 170 | throw new Error('No ' + name); 171 | } 172 | return defined[name]; 173 | } 174 | 175 | //Turns a plugin!resource to [plugin, resource] 176 | //with the plugin being undefined if the name 177 | //did not have a plugin prefix. 178 | function splitPrefix(name) { 179 | var prefix, 180 | index = name ? name.indexOf('!') : -1; 181 | if (index > -1) { 182 | prefix = name.substring(0, index); 183 | name = name.substring(index + 1, name.length); 184 | } 185 | return [prefix, name]; 186 | } 187 | 188 | /** 189 | * Makes a name map, normalizing the name, and using a plugin 190 | * for normalization if necessary. Grabs a ref to plugin 191 | * too, as an optimization. 192 | */ 193 | makeMap = function (name, relName) { 194 | var plugin, 195 | parts = splitPrefix(name), 196 | prefix = parts[0]; 197 | 198 | name = parts[1]; 199 | 200 | if (prefix) { 201 | prefix = normalize(prefix, relName); 202 | plugin = callDep(prefix); 203 | } 204 | 205 | //Normalize according 206 | if (prefix) { 207 | if (plugin && plugin.normalize) { 208 | name = plugin.normalize(name, makeNormalize(relName)); 209 | } else { 210 | name = normalize(name, relName); 211 | } 212 | } else { 213 | name = normalize(name, relName); 214 | parts = splitPrefix(name); 215 | prefix = parts[0]; 216 | name = parts[1]; 217 | if (prefix) { 218 | plugin = callDep(prefix); 219 | } 220 | } 221 | 222 | //Using ridiculous property names for space reasons 223 | return { 224 | f: prefix ? prefix + '!' + name : name, //fullName 225 | n: name, 226 | pr: prefix, 227 | p: plugin 228 | }; 229 | }; 230 | 231 | function makeConfig(name) { 232 | return function () { 233 | return (config && config.config && config.config[name]) || {}; 234 | }; 235 | } 236 | 237 | handlers = { 238 | require: function (name) { 239 | return makeRequire(name); 240 | }, 241 | exports: function (name) { 242 | var e = defined[name]; 243 | if (typeof e !== 'undefined') { 244 | return e; 245 | } else { 246 | return (defined[name] = {}); 247 | } 248 | }, 249 | module: function (name) { 250 | return { 251 | id: name, 252 | uri: '', 253 | exports: defined[name], 254 | config: makeConfig(name) 255 | }; 256 | } 257 | }; 258 | 259 | main = function (name, deps, callback, relName) { 260 | var cjsModule, depName, ret, map, i, 261 | args = [], 262 | usingExports; 263 | 264 | //Use name if no relName 265 | relName = relName || name; 266 | 267 | //Call the callback to define the module, if necessary. 268 | if (typeof callback === 'function') { 269 | 270 | //Pull out the defined dependencies and pass the ordered 271 | //values to the callback. 272 | //Default to [require, exports, module] if no deps 273 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; 274 | for (i = 0; i < deps.length; i += 1) { 275 | map = makeMap(deps[i], relName); 276 | depName = map.f; 277 | 278 | //Fast path CommonJS standard dependencies. 279 | if (depName === "require") { 280 | args[i] = handlers.require(name); 281 | } else if (depName === "exports") { 282 | //CommonJS module spec 1.1 283 | args[i] = handlers.exports(name); 284 | usingExports = true; 285 | } else if (depName === "module") { 286 | //CommonJS module spec 1.1 287 | cjsModule = args[i] = handlers.module(name); 288 | } else if (hasProp(defined, depName) || 289 | hasProp(waiting, depName) || 290 | hasProp(defining, depName)) { 291 | args[i] = callDep(depName); 292 | } else if (map.p) { 293 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); 294 | args[i] = defined[depName]; 295 | } else { 296 | throw new Error(name + ' missing ' + depName); 297 | } 298 | } 299 | 300 | ret = callback.apply(defined[name], args); 301 | 302 | if (name) { 303 | //If setting exports via "module" is in play, 304 | //favor that over return value and exports. After that, 305 | //favor a non-undefined return value over exports use. 306 | if (cjsModule && cjsModule.exports !== undef && 307 | cjsModule.exports !== defined[name]) { 308 | defined[name] = cjsModule.exports; 309 | } else if (ret !== undef || !usingExports) { 310 | //Use the return value from the function. 311 | defined[name] = ret; 312 | } 313 | } 314 | } else if (name) { 315 | //May just be an object definition for the module. Only 316 | //worry about defining if have a module name. 317 | defined[name] = callback; 318 | } 319 | }; 320 | 321 | requirejs = require = req = function (deps, callback, relName, forceSync, alt) { 322 | if (typeof deps === "string") { 323 | if (handlers[deps]) { 324 | //callback in this case is really relName 325 | return handlers[deps](callback); 326 | } 327 | //Just return the module wanted. In this scenario, the 328 | //deps arg is the module name, and second arg (if passed) 329 | //is just the relName. 330 | //Normalize module name, if it contains . or .. 331 | return callDep(makeMap(deps, callback).f); 332 | } else if (!deps.splice) { 333 | //deps is a config object, not an array. 334 | config = deps; 335 | if (callback.splice) { 336 | //callback is an array, which means it is a dependency list. 337 | //Adjust args if there are dependencies 338 | deps = callback; 339 | callback = relName; 340 | relName = null; 341 | } else { 342 | deps = undef; 343 | } 344 | } 345 | 346 | //Support require(['a']) 347 | callback = callback || function () {}; 348 | 349 | //If relName is a function, it is an errback handler, 350 | //so remove it. 351 | if (typeof relName === 'function') { 352 | relName = forceSync; 353 | forceSync = alt; 354 | } 355 | 356 | //Simulate async callback; 357 | if (forceSync) { 358 | main(undef, deps, callback, relName); 359 | } else { 360 | //Using a non-zero value because of concern for what old browsers 361 | //do, and latest browsers "upgrade" to 4 if lower value is used: 362 | //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: 363 | //If want a value immediately, use require('id') instead -- something 364 | //that works in almond on the global level, but not guaranteed and 365 | //unlikely to work in other AMD implementations. 366 | setTimeout(function () { 367 | main(undef, deps, callback, relName); 368 | }, 4); 369 | } 370 | 371 | return req; 372 | }; 373 | 374 | /** 375 | * Just drops the config on the floor, but returns req in case 376 | * the config return value is used. 377 | */ 378 | req.config = function (cfg) { 379 | config = cfg; 380 | if (config.deps) { 381 | req(config.deps, config.callback); 382 | } 383 | return req; 384 | }; 385 | 386 | /** 387 | * Expose module registry for debugging and tooling 388 | */ 389 | requirejs._defined = defined; 390 | 391 | define = function (name, deps, callback) { 392 | 393 | //This module may not have dependencies 394 | if (!deps.splice) { 395 | //deps is not an array, so probably means 396 | //an object literal or factory function for 397 | //the value. Adjust args. 398 | callback = deps; 399 | deps = []; 400 | } 401 | 402 | if (!hasProp(defined, name) && !hasProp(waiting, name)) { 403 | waiting[name] = [name, deps, callback]; 404 | } 405 | }; 406 | 407 | define.amd = { 408 | jQuery: true 409 | }; 410 | }()); 411 | -------------------------------------------------------------------------------- /assets/js/vendor/backbone.localstorage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Backbone localStorage Adapter 3 | * Version 1.1.7 4 | * 5 | * https://github.com/jeromegn/Backbone.localStorage 6 | */ 7 | (function (root, factory) { 8 | if (typeof exports === 'object' && typeof require === 'function') { 9 | module.exports = factory(require("underscore"), require("backbone")); 10 | } else if (typeof define === "function" && define.amd) { 11 | // AMD. Register as an anonymous module. 12 | define(["underscore","backbone"], function(_, Backbone) { 13 | // Use global variables if the locals are undefined. 14 | return factory(_ || root._, Backbone || root.Backbone); 15 | }); 16 | } else { 17 | // RequireJS isn't being used. Assume underscore and backbone are loaded in 22 |