├── api ├── deleteContact.json.txt ├── getContact.json.txt ├── saveContact.json.txt └── getContacts.json.txt ├── linked ├── css │ ├── images │ │ ├── site_mvc_breakdown.jpg │ │ ├── site_cormvc_billboard.jpg │ │ ├── site_footer_background.jpg │ │ └── site_header_background.jpg │ ├── standard.css │ ├── reset.css │ ├── content.css │ ├── data_form.css │ ├── contacts.css │ └── structure.css └── js │ ├── model │ ├── contact.js │ ├── contact_service_mock.js │ └── contact_service.js │ ├── controller │ ├── contacts.js │ └── content.js │ ├── view │ ├── contact_form.js │ └── contact_list.js │ └── app │ └── corMVC.js ├── ie_back_button.htm ├── LICENSE.md ├── README.md └── index.htm /api/deleteContact.json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "errors": [], 4 | "data": 1 5 | } -------------------------------------------------------------------------------- /linked/css/images/site_mvc_breakdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennadel/CorMVC/HEAD/linked/css/images/site_mvc_breakdown.jpg -------------------------------------------------------------------------------- /linked/css/images/site_cormvc_billboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennadel/CorMVC/HEAD/linked/css/images/site_cormvc_billboard.jpg -------------------------------------------------------------------------------- /linked/css/images/site_footer_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennadel/CorMVC/HEAD/linked/css/images/site_footer_background.jpg -------------------------------------------------------------------------------- /linked/css/images/site_header_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennadel/CorMVC/HEAD/linked/css/images/site_header_background.jpg -------------------------------------------------------------------------------- /api/getContact.json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "errors": [], 4 | "data": { "id": 1, "name": "Ben Nadel", "email": "ben@corMVC.com", "phone": "212-555-1111" } 5 | } -------------------------------------------------------------------------------- /api/saveContact.json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "errors": [], 4 | "data": { "id": 1, "name": "Ben Nadel", "email": "ben@corMVC.com", "phone": "212-555-1111" } 5 | } -------------------------------------------------------------------------------- /linked/css/standard.css: -------------------------------------------------------------------------------- 1 | 2 | @import url( "./reset.css" ); 3 | @import url( "./content.css" ); 4 | @import url( "./structure.css" ); 5 | @import url( "./data_form.css" ); 6 | @import url( "./contacts.css" ); 7 | -------------------------------------------------------------------------------- /linked/css/reset.css: -------------------------------------------------------------------------------- 1 | 2 | h1, 3 | h2, 4 | h3, 5 | h4, 6 | h5, 7 | p, 8 | ul, 9 | ol, 10 | li, 11 | table { 12 | line-height: 1.6em ; 13 | margin: 0px 0px 1.6em 0px ; 14 | padding: 0px 0px 0px 0px ; 15 | } 16 | 17 | form { 18 | margin: 0px 0px 0px 0px ; 19 | padding: 0px 0px 0px 0px ; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /ie_back_button.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /linked/css/content.css: -------------------------------------------------------------------------------- 1 | 2 | /* From jQuery UI CSS Framework. */ 3 | .clear:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } 4 | .clear { display: inline-block; } 5 | /* required comment for clearfix to work in Opera \*/ 6 | * html .clear { height:1%; } 7 | .clear { display:block; } 8 | 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5 { 14 | font-family: arial, helvetica ; 15 | font-weight: 400 ; 16 | } 17 | 18 | h1 { 19 | font-size: 180% ; 20 | margin-bottom: .5em ; 21 | } 22 | 23 | h2 { 24 | font-size: 150% ; 25 | margin-bottom: .5em ; 26 | } 27 | 28 | ul, 29 | ol { 30 | margin-left: 30px ; 31 | } 32 | 33 | li { 34 | margin-bottom: 5px ; 35 | } 36 | 37 | a { 38 | color: #9E0B0F ; 39 | } -------------------------------------------------------------------------------- /linked/js/model/contact.js: -------------------------------------------------------------------------------- 1 | 2 | // I represent a contact object. 3 | 4 | // Add model to the application. 5 | window.application.addModel((function( $, application ){ 6 | 7 | // I am the contact class. 8 | function Contact( id, name, phone, email ){ 9 | this.id = (id || 0); 10 | this.name = (name || ""); 11 | this.phone = (phone || ""); 12 | this.email = (email || ""); 13 | }; 14 | 15 | // I validate the contact instance. 16 | Contact.prototype.validate = function(){ 17 | // Not using this right now. 18 | return( [] ); 19 | }; 20 | 21 | 22 | // ----------------------------------------------------------------------- // 23 | // ----------------------------------------------------------------------- // 24 | 25 | // Return a new model class. 26 | return( Contact ); 27 | 28 | })( jQuery, window.application )); 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | # The MIT License (MIT) 3 | 4 | Copyright (c) 2013 [Ben Nadel][1] 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | [1]: http://www.bennadel.com -------------------------------------------------------------------------------- /linked/css/data_form.css: -------------------------------------------------------------------------------- 1 | 2 | div.form-errors { 3 | background-color: #FEF1F1 ; 4 | border: 1px dotted #9E0B0F ; 5 | color: #9E0B0F ; 6 | margin-bottom: 1.5em ; 7 | padding: 10px 10px 10px 10px ; 8 | } 9 | 10 | div.form-errors h3 { 11 | font-size: 100% ; 12 | font-weight: bold ; 13 | margin-bottom: .6em ; 14 | } 15 | 16 | div.form-errors ul { 17 | margin-bottom: 0px ; 18 | } 19 | 20 | div.form-errors li { 21 | margin-bottom: 3px ; 22 | } 23 | 24 | input.branded-input { 25 | border: 1px solid #333333 ; 26 | border-bottom-color: #999999 ; 27 | border-right-color: #999999 ; 28 | font-size: 120% ; 29 | padding: 3px 6px 3px 6px ; 30 | } 31 | 32 | input.large { 33 | width: 300px ; 34 | } 35 | 36 | input.xlarge { 37 | width: 600px ; 38 | } 39 | 40 | form.data-form { 41 | margin-bottom: 1.5em ; 42 | } 43 | 44 | form.data-form fieldset { 45 | border-width: 0px 0px 0px 0px ; 46 | margin: 0px 0px 0px 0px ; 47 | padding: 0px 0px 0px 0px ; 48 | } 49 | 50 | form.data-form div.entry { 51 | margin-bottom: 15px ; 52 | } 53 | 54 | form.data-form div.entry label { 55 | color: #666666 ; 56 | display: block ; 57 | font-weight: bold ; 58 | margin-bottom: 2px ; 59 | } -------------------------------------------------------------------------------- /api/getContacts.json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "errors": [], 4 | "data": [ 5 | { "id": 1, "name": "Ben Nadel", "phone": "212-555-1234", "email": "ben.nadel@corMVC.com" }, 6 | { "id": 2, "name": "Rey Bango", "phone": "212-555-2345", "email": "rey.bango@corMVC.com" }, 7 | { "id": 3, "name": "Cody Lindley", "phone": "212-555-0393", "email": "cody.lindley@corMVC.com" }, 8 | { "id": 4, "name": "Scott Gonzalez", "phone": "212-555-0908", "email": "scott.gonzalez@corMVC.com" }, 9 | { "id": 5, "name": "Rebecca Murphey", "phone": "212-555-1433", "email": "rebecca.murphey@corMVC.com" }, 10 | { "id": 6, "name": "Adam Sontag", "phone": "212-555-3948", "email": "adam.sontag@corMVC.com" }, 11 | { "id": 7, "name": "Karl Swedberg", "phone": "212-555-9983", "email": "karl.swedberg@corMVC.com" }, 12 | { "id": 8, "name": "Dan Wellman", "phone": "212-555-3434", "email": "dan.wellman@corMVC.com" }, 13 | { "id": 9, "name": "John Resig", "phone": "212-555-7890", "email": "john.resig@corMVC.com" }, 14 | { "id": 10, "name": "Paul Irish", "phone": "212-555-4311", "email": "paul.irish@corMVC.com" }, 15 | { "id": 11, "name": "Ben Alman", "phone": "212-555-7829", "email": "ben.alman@corMVC.com" }, 16 | { "id": 12, "name": "Bob Bonifield", "phone": "212-555-1248", "email": "bob.bonifield@corMVC.com" }, 17 | { "id": 13, "name": "Vlad Filippov", "phone": "212-555-8282", "email": "vlad.filippov@corMVC.com" }, 18 | { "id": 14, "name": "Jon Snook", "phone": "212-555-7894", "email": "jon.snook@corMVC.com" }, 19 | { "id": 15, "name": "Jon Clark", "phone": "212-555-4561", "email": "jon.clark@corMVC.com" }, 20 | { "id": 16, "name": "Elijah Manor", "phone": "212-555-4566", "email": "elijah.manor@corMVC.com" }, 21 | { "id": 17, "name": "James Padolsey", "phone": "212-555-1591", "email": "james.padolsey@corMVC.com" }, 22 | { "id": 18, "name": "Alex Sexton", "phone": "212-555-1236", "email": "alex.sexton@corMVC.com" } 23 | ] 24 | } -------------------------------------------------------------------------------- /linked/css/contacts.css: -------------------------------------------------------------------------------- 1 | 2 | #contact-list-header { 3 | background-color: #2B1812 ; 4 | color: #F5BE44 ; 5 | font-size: 150% ; 6 | height: 50px ; 7 | line-height: 48px ; 8 | margin-bottom: 1em ; 9 | padding: 0px 20px 0px 20px ; 10 | } 11 | 12 | #contact-list-header form { 13 | float: left ; 14 | } 15 | 16 | #contact-list-header input { 17 | background-color: #482C21 ; 18 | border: 1px solid #663E2F ; 19 | color: #FFFFFF ; 20 | font-size: 100% ; 21 | margin-top: -2px ; 22 | padding: 0px 0px 1px 5px ; 23 | width: 440px ; 24 | } 25 | 26 | #contact-list-header a { 27 | color: #F5BE44 ; 28 | float: right ; 29 | text-decoration: none ; 30 | } 31 | 32 | #contact-list-header span.contrast { 33 | color: #FFFFFF ; 34 | } 35 | 36 | #contact-list { 37 | color: #333333 ; 38 | font-size: 110% ; 39 | list-style-type: none ; 40 | margin: 0px 0px 0px 0px ; 41 | padding: 0px 0px 0px 0px ; 42 | } 43 | 44 | #contact-list li { 45 | margin: 0px 0px 15px 0px ; 46 | padding: 0px 0px 0px 0px ; 47 | } 48 | 49 | #contact-list li.contact { 50 | background-color: #DBDAC1 ; 51 | padding: 7px 10px 7px 10px ; 52 | width: 712px ; 53 | } 54 | 55 | #contact-list li.last-contact { 56 | margin-bottom: 0px ; 57 | } 58 | 59 | #contact-list li.contact a { 60 | color: #333333 ; 61 | } 62 | 63 | #contact-list div.summary { 64 | float: left ; 65 | font-weight: bold ; 66 | } 67 | 68 | #contact-list div.summary a.name { 69 | text-decoration: none ; 70 | } 71 | 72 | #contact-list div.actions { 73 | float: right ; 74 | font-weight: bold ; 75 | } 76 | 77 | #contact-list dl.details { 78 | clear: both ; 79 | display: none ; 80 | margin: 0px 0px 0px 0px ; 81 | padding: 10px 0px 5px 0px ; 82 | width: 712px ; 83 | } 84 | 85 | #contact-list dl.details dt { 86 | clear: both ; 87 | color: #797746 ; 88 | float: left ; 89 | font-weight: bold ; 90 | margin: 0px 0px 0px 0px ; 91 | padding: 0px 0px 0px 0px ; 92 | text-align: right ; 93 | width: 70px ; 94 | } 95 | 96 | #contact-list dl.details dd { 97 | float: left ; 98 | margin: 0px 0px 0px 0px ; 99 | padding: 0px 0px 0px 10px ; 100 | width: 620px ; 101 | } 102 | 103 | #contact-form {} 104 | 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # CorMVC - Client-Side jQuery And JavaScript Framework 3 | 4 | by [Ben Nadel][1] (on [Google+][2]) 5 | 6 | __*NOTE: This is a really old project that has not been updated in years. I 7 | have personally started to use Google's AngularJS and highly recommend it.*__ 8 | 9 | __Demo:__ [View the online demo][4] 10 | 11 | CorMVC is a jQuery-powered Model-View-Controller (MVC) framework that can 12 | aide in the development of single-page jQuery applications. It evolved out of 13 | my recent presentation, [Building Single-Page Applications Using jQuery And ColdFusion][3], 14 | and will continue to evolve as I think more deeply about this type of 15 | application architecture. 16 | 17 | CorMVC stands for: Client-Only-Required Model-View-Controller, and is my 18 | laboratory experiment in application architecture held completely seperate 19 | from server-side technologies. 20 | 21 | ## CorMVC Philosophy 22 | 23 | Building frameworks in jQuery (or any other language for that matter) is very 24 | new to me; I don't claim to be any good at it. In fact, when I started looking 25 | into jQuery-based frameworks, I had no intention of creating my own. As I 26 | started to do my research, however, I quickly encountered two major problems 27 | with what was avilable: 28 | 29 | * Most examples were so small that I could not see how they might be applied 30 | to the kind of software I build. 31 | * Most frameworks were enormous and required command line utilities and some 32 | additional server-side technology (like Ruby On Rails) just to experiment with. 33 | 34 | I didn't even know how to begin learning. So, rather than wade through what was 35 | available, I decided to try and create something from scratch. What I came up 36 | with is corMVC. The philosophies that I put into the corMVC framework are those 37 | that were hopefully a remedy to the problems I encoutered above: 38 | 39 | * __A large sample application__. This whole demo site (including the contacts 40 | section) runs off of corMVC as a single-page application. 41 | * __No server required__. This demo application does not require any additional 42 | server-side technologies. If you have a web browser, you can download and run 43 | this application immediately. 44 | * __No building required__. This framework does not require you to build the 45 | application using scaffolding or any other command-line executables. You just 46 | download it and open it up in a browser. 47 | * __Small Framework__. This framework is very small (and excessively 48 | commented). It doesn't do anything more than it is supposed to. 49 | 50 | While I want to keep the corMVC framework as small as possible, I am sure that 51 | as I begin to more fully understand the various needs of single-page 52 | applications, the framework will have to evolve as necessary. In the end 53 | though, I want the corMVC framework to be an aide and not a constraint- 54 | affording the programmer the freedom to pile their own jQuery magic on top of 55 | this foundation. 56 | 57 | 58 | [1]: http://www.bennadel.com 59 | [2]: https://plus.google.com/108976367067760160494?rel=author 60 | [3]: http://www.bennadel.com/blog/1730-Building-Single-Page-Applications-Using-jQuery-And-ColdFusion-With-Ben-Nadel-Video-Presentation-.htm 61 | [4]: http://bennadel.github.io/CorMVC -------------------------------------------------------------------------------- /linked/js/controller/contacts.js: -------------------------------------------------------------------------------- 1 | 2 | // I control the events within the Contacts section of the application. 3 | 4 | // Add a controller to the application. 5 | window.application.addController((function( $, application ){ 6 | 7 | // I am the controller class. 8 | function Controller(){ 9 | // Route URL events to the controller's event handlers. 10 | this.route( "/contacts/", this.index ); 11 | this.route( "/contacts/add/", this.addContact ); 12 | this.route( "/contacts/edit/:id", this.editContact ); 13 | this.route( "/contacts/delete/:id", this.deleteContact ); 14 | 15 | // Set default properties. 16 | this.currentView = null; 17 | this.contactListView = null; 18 | this.contactFormView = null; 19 | }; 20 | 21 | // Extend the core application controller (REQUIRED). 22 | Controller.prototype = new application.Controller(); 23 | 24 | 25 | // I initialize the controller. I get called once the application starts 26 | // running (or when the controller is registered - if the application is 27 | // already running). At that point, the DOM is available and all the other 28 | // model and view classes will have been added to the system. 29 | Controller.prototype.init = function(){ 30 | this.contactListView = application.getView( "ContactList" ); 31 | this.contactFormView = application.getView( "ContactForm" ); 32 | }; 33 | 34 | 35 | // ----------------------------------------------------------------------- // 36 | // ----------------------------------------------------------------------- // 37 | 38 | 39 | // I am the add event for this controller. 40 | Controller.prototype.addContact = function( event ){ 41 | // Show the form view. 42 | this.showView( this.contactFormView, event ); 43 | }; 44 | 45 | 46 | // I am the edit event for this controller. 47 | Controller.prototype.editContact = function( event, id ){ 48 | // Show the form view. 49 | this.showView( this.contactFormView, event ); 50 | }; 51 | 52 | 53 | // I am the delete event for this controller. 54 | Controller.prototype.deleteContact = function( event, id ){ 55 | // Delete the contact. 56 | application.getModel( "ContactService" ).deleteContact( 57 | id, 58 | function(){ 59 | application.relocateTo( "contacts" ); 60 | } 61 | ); 62 | }; 63 | 64 | 65 | // I am the default event for this controller. 66 | Controller.prototype.index = function( event ){ 67 | // Show the list view. 68 | this.showView( this.contactListView, event ); 69 | }; 70 | 71 | 72 | // I show the given view; but first, I hide any existing view. 73 | Controller.prototype.showView = function( view, event ){ 74 | // Check to see if there is a current view. If so, then hide it. 75 | if (this.currentView && this.currentView.hideView){ 76 | this.currentView.hideView(); 77 | } 78 | 79 | // Show the given view. 80 | view.showView( event.parameters ); 81 | 82 | // Store the given view as the current view. 83 | this.currentView = view; 84 | }; 85 | 86 | 87 | // ----------------------------------------------------------------------- // 88 | // ----------------------------------------------------------------------- // 89 | 90 | // Return a new contoller singleton instance. 91 | return( new Controller() ); 92 | 93 | })( jQuery, window.application )); 94 | -------------------------------------------------------------------------------- /linked/css/structure.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #1F1F1F ; 4 | font-family: verdana, arial ; 5 | font-size: 62.5% ; 6 | margin: 0px 0px 0px 0px ; 7 | padding: 0px 0px 0px 0px ; 8 | } 9 | 10 | 11 | #site-wrapper {} 12 | 13 | #site-header { 14 | background-color: #2B1812 ; 15 | background-image: url( "./images/site_header_background.jpg" ) ; 16 | background-position: left top ; 17 | background-repeat: repeat-x ; 18 | height: 471px ; 19 | overflow: hidden ; 20 | } 21 | 22 | #site-header-content { 23 | margin: 0px auto 0px auto ; 24 | width: 775px ; 25 | } 26 | 27 | #site-navigation { 28 | border-bottom: 4px solid #482C21 ; 29 | font-size: 14px ; 30 | height: 38px ; 31 | list-style-type: none ; 32 | margin: 0px 0px 8px 0px ; 33 | padding: 29px 0px 0px 0px ; 34 | } 35 | 36 | #site-navigation li { 37 | float: left ; 38 | height: 30px ; 39 | line-height: 30px ; 40 | margin: 0px 15px 0px 0px ; 41 | } 42 | 43 | #site-navigation a { 44 | color: #FFFFFF ; 45 | float: left ; 46 | padding: 0px 12px 0px 12px ; 47 | text-decoration: none ; 48 | text-transform: uppercase ; 49 | zoom: 1 ; 50 | } 51 | 52 | #site-navigation a:active, 53 | #site-navigation a:focus { 54 | outline-width: 0px 0px 0px 0px ; 55 | outline: none ; 56 | } 57 | 58 | #site-navigation a:hover { 59 | text-decoration: underline ; 60 | } 61 | 62 | #site-navigation li.bennadel { 63 | float: right ; 64 | margin-right: 0px ; 65 | } 66 | 67 | #site-navigation li.on a { 68 | background-color: #9E0B0F ; 69 | } 70 | 71 | #site-navigation li.on a:hover { 72 | text-decoration: none ; 73 | } 74 | 75 | #site-cormvc-billboard { 76 | background-color: #FFFFFF ; 77 | background-image: url( "./images/site_cormvc_billboard.jpg" ) ; 78 | background-position: center center ; 79 | background-repeat: no-repeat ; 80 | height: 260px ; 81 | overflow: hidden ; 82 | padding: 11px 11px 11px 11px ; 83 | text-indent: 9999px ; 84 | width: 753px ; 85 | white-space: nowrap ; 86 | } 87 | 88 | #site-mvc-breakdown { 89 | background-image: url( "./images/site_mvc_breakdown.jpg" ) ; 90 | background-position: center center ; 91 | background-repeat: no-repeat ; 92 | height: 110px ; 93 | overflow: hidden ; 94 | padding: 0px 11px 0px 11px ; 95 | text-indent: 9999px ; 96 | width: 753px ; 97 | white-space: nowrap ; 98 | } 99 | 100 | #site-body { 101 | background-color: #FFFFFF ; 102 | } 103 | 104 | #site-body-content { 105 | font-size: 1.2em ; 106 | margin: 0px auto 0px auto ; 107 | padding: 0px 0px 50px 0px ; 108 | width: 732px ; 109 | } 110 | 111 | #primary-content-stages { 112 | list-style-type: none ; 113 | margin: 0px 0px 0px 0px ; 114 | padding: 0px 0px 0px 0px ; 115 | } 116 | 117 | #primary-content-stages li.primary-content-stage { 118 | display: none ; 119 | margin: 0px 0px 0px 0px ; 120 | padding: 0px 0px 0px 0px ; 121 | } 122 | 123 | #primary-content-stages li.current-primary-content-stage { 124 | display: block ; 125 | } 126 | 127 | ul.primary-content-views { 128 | list-style-type: none ; 129 | margin: 0px 0px 0px 0px ; 130 | padding: 0px 0px 0px 0px ; 131 | } 132 | 133 | ul.primary-content-views li.primary-content-view { 134 | display: none ; 135 | margin: 0px 0px 0px 0px ; 136 | padding: 0px 0px 0px 0px ; 137 | } 138 | 139 | ul.primary-content-views li.current-primary-content-view { 140 | display: block ; 141 | } 142 | 143 | #site-footer { 144 | background-image: url( "./images/site_footer_background.jpg" ) ; 145 | background-position: left top ; 146 | background-repeat: repeat-x ; 147 | } 148 | 149 | #site-footer-content { 150 | color: #666666 ; 151 | font-size: 1.1em ; 152 | margin: 0px auto 0px auto ; 153 | padding: 15px 10px 100px 10px ; 154 | width: 755px ; 155 | } 156 | 157 | #site-footer a { 158 | color: #666666 ; 159 | } 160 | -------------------------------------------------------------------------------- /linked/js/controller/content.js: -------------------------------------------------------------------------------- 1 | 2 | // I control the primary navigation and the corresponding view of content that 3 | // is displayed on the page. I do not control the content that is displayed within 4 | // the primary content view (that is delegated to the other controllers). 5 | 6 | // Add a controller to the application. 7 | window.application.addController((function( $, application ){ 8 | 9 | // I am the controller class. 10 | function Controller(){ 11 | // Route URL events to the controller's event handlers. 12 | this.route( "/", this.index ); 13 | this.route( "/home", this.index ); 14 | this.route( "/contacts.*", this.contacts ); 15 | this.route( "/documentation", this.documentation ); 16 | this.route( "/resources", this.resources ); 17 | this.route( "/404", this.pageNotFound ); 18 | 19 | // Set default properties. 20 | this.navigation = null; 21 | this.navigationItems = null; 22 | this.views = null; 23 | this.homeView = null; 24 | this.contactsView = null; 25 | this.documentationView = null; 26 | this.resourcesView = null; 27 | this.pageNotFoundView = null; 28 | }; 29 | 30 | // Extend the core application controller (REQUIRED). 31 | Controller.prototype = new application.Controller(); 32 | 33 | 34 | // I initialize the controller. I get called once the application starts 35 | // running (or when the controller is registered - if the application is 36 | // already running). At that point, the DOM is available and all the other 37 | // model and view classes will have been added to the system. 38 | Controller.prototype.init = function(){ 39 | this.navigation = $( "#site-navigation" ); 40 | this.navigationItems = this.navigation.find( "> li:has(a[rel])" ); 41 | this.views = $( "#primary-content-stages > li" ); 42 | this.homeView = this.views.filter( "[ rel = 'home' ]" ); 43 | this.contactsView = this.views.filter( "[ rel = 'contacts' ]" ); 44 | this.documentationView = this.views.filter( "[ rel = 'documentation' ]" ); 45 | this.resourcesView = this.views.filter( "[ rel = 'resources' ]" ); 46 | this.pageNotFoundView = this.views.filter( "[ rel = '404' ]" ); 47 | 48 | // Update the copyright year. 49 | $( "#site-copyright span.copyright-year" ).text( 50 | (new Date()).getFullYear() 51 | ); 52 | }; 53 | 54 | 55 | // ----------------------------------------------------------------------- // 56 | // ----------------------------------------------------------------------- // 57 | 58 | 59 | // I show the contacts view. 60 | Controller.prototype.contacts = function( event ){ 61 | this.showView( this.contactsView ); 62 | }; 63 | 64 | 65 | // I show the documentation view. 66 | Controller.prototype.documentation = function( event ){ 67 | this.showView( this.documentationView ); 68 | }; 69 | 70 | 71 | // I am the default event for this controller. 72 | Controller.prototype.index = function( event ){ 73 | this.showView( this.homeView ); 74 | }; 75 | 76 | 77 | // I show the page not found view. 78 | Controller.prototype.pageNotFound = function( event ){ 79 | this.showView( this.pageNotFoundView ); 80 | }; 81 | 82 | 83 | // I show the resources view. 84 | Controller.prototype.resources = function( event ){ 85 | this.showView( this.resourcesView ); 86 | }; 87 | 88 | 89 | // I show the given view; but first, I hide any existing view. 90 | Controller.prototype.showView = function( view ){ 91 | // Turn off the primary navigation. 92 | this.navigationItems.removeClass( "on" ); 93 | 94 | // Remove the current view class. 95 | this.views.removeClass( "current-primary-content-stage" ); 96 | 97 | // Turn on the correct navigation. Match the REL attribute of the given 98 | // view to the REL attribute of the anchor inside the navigation item. 99 | if (view.attr( "rel" )){ 100 | this.navigationItems.filter( ":has(a[rel = '" + view.attr( "rel" ) + "' ])" ).addClass( "on" ); 101 | } 102 | 103 | // Add the primary content view class. 104 | view.addClass( "current-primary-content-stage" ); 105 | }; 106 | 107 | 108 | // ----------------------------------------------------------------------- // 109 | // ----------------------------------------------------------------------- // 110 | 111 | // Return a new contoller singleton instance. 112 | return( new Controller() ); 113 | 114 | })( jQuery, window.application )); 115 | -------------------------------------------------------------------------------- /linked/js/model/contact_service_mock.js: -------------------------------------------------------------------------------- 1 | 2 | // I am the gateway to the contacts collection within the system. For this 3 | // demo, there is communication with the "server", but only to invoke hard-coded 4 | // JSON files in attempt to show you how client-server communication might happen. 5 | 6 | // Add model to the application. 7 | window.application.addModel((function( $, application ){ 8 | 9 | // I am the contacts service class. 10 | function ContactService(){ 11 | // ... Nothing to do here ... 12 | }; 13 | 14 | 15 | // I initialize the model. I get called once the application starts 16 | // running (or when the model is registered - if the application is 17 | // already running). At that point, the DOM is available and all the other 18 | // model and view classes will have been added to the system. 19 | ContactService.prototype.init = function(){ 20 | // ... nothing needed here ... 21 | }; 22 | 23 | 24 | // ----------------------------------------------------------------------- // 25 | // ----------------------------------------------------------------------- // 26 | 27 | 28 | // I delete the contact at the given ID. 29 | ContactService.prototype.deleteContact = function( id, onSuccess, onError ){ 30 | var self = this; 31 | 32 | // Delete the contact on the server and return any error messages. 33 | application.ajax({ 34 | url: "api/deleteContact.json.txt", 35 | data: { 36 | method: "deleteContact", 37 | id: id 38 | }, 39 | normalizeJSON: true, 40 | success: function( response ){ 41 | // Check to see if the request was successful. 42 | if (response.success){ 43 | // The delete was successful - pass back the ID to the callback. 44 | onSuccess( id ); 45 | } else if (onError){ 46 | // The call was not successful - call the error function. 47 | onError( respsonse.errors ); 48 | } 49 | } 50 | }); 51 | }; 52 | 53 | 54 | // I get the contact at the given ID. 55 | ContactService.prototype.getContact = function( id, onSuccess, onError ){ 56 | var self = this; 57 | 58 | // Get the contacts from the server. 59 | application.ajax({ 60 | url: "api/getContact.json.txt", 61 | data: { 62 | method: "getContact", 63 | id: id 64 | }, 65 | normalizeJSON: true, 66 | success: function( response ){ 67 | // Check to see if the request was successful. 68 | if (response.success){ 69 | // Create contacts based on the response data and pass the contact 70 | // off to the callback. 71 | onSuccess( self.populateContactsFromResponse( response.data ) ); 72 | } else if (onError){ 73 | // The call was not successful - call the error function. 74 | onError( respsonse.errors ); 75 | } 76 | } 77 | }); 78 | }; 79 | 80 | 81 | // I get the contacts. 82 | ContactService.prototype.getContacts = function( onSuccess, onError ){ 83 | var self = this; 84 | 85 | // Get the contacts from the server. 86 | application.ajax({ 87 | url: "api/getContacts.json.txt", 88 | data: { 89 | method: "getContacts" 90 | }, 91 | normalizeJSON: true, 92 | success: function( response ){ 93 | // Check to see if the request was successful. 94 | if (response.success){ 95 | // Create contacts based on the response data and pass the contacts 96 | // collection off to the callback. 97 | onSuccess( self.populateContactsFromResponse( response.data ) ); 98 | } else if (onError){ 99 | // The call was not successful - call the error function. 100 | onError( respsonse.errors ); 101 | } 102 | } 103 | }); 104 | }; 105 | 106 | 107 | // I populate real contact objects based on the raw respons data. 108 | ContactService.prototype.populateContactsFromResponse = function( responseData ){ 109 | // Check to see if the given response object is an array. If so, 110 | // then we will populate an array and return it. If not, then we will 111 | // populate a single object and return it. 112 | if ($.isArray( responseData )){ 113 | 114 | // We are populating an array of contacts. 115 | var contacts = []; 116 | 117 | // Loop over each response item and conver it to a real contact. 118 | $.each( 119 | responseData, 120 | function( index, contactData ){ 121 | contacts.push( 122 | application.getModel( "Contact", [ contactData.id, contactData.name, contactData.phone, contactData.email ] ) 123 | ); 124 | } 125 | ); 126 | 127 | // Return the populated contacts collection. 128 | return( contacts ); 129 | 130 | } else { 131 | 132 | // We are populating a single contact. 133 | return( 134 | application.getModel( "Contact", [ responseData.id, responseData.name, responseData.phone, responseData.email ] ) 135 | ); 136 | 137 | } 138 | }; 139 | 140 | 141 | // I save the given contact. 142 | ContactService.prototype.saveContact = function( id, name, phone, email, onSuccess, onError ){ 143 | var self = this; 144 | 145 | // Save the contact on the server and return any error messages. 146 | application.ajax({ 147 | url: "api/saveContact.json.txt", 148 | data: { 149 | method: "saveContact", 150 | id: id, 151 | name: name, 152 | phone: phone, 153 | email: email 154 | }, 155 | normalizeJSON: true, 156 | success: function( response ){ 157 | // Check to see if the request was successful. 158 | if (response.success){ 159 | // The save was successful - return the saved contact ID. 160 | onSuccess( response.data ); 161 | } else { 162 | // The save was not successful - return the errors. 163 | onError( response.errors ); 164 | } 165 | } 166 | }); 167 | }; 168 | 169 | 170 | // ----------------------------------------------------------------------- // 171 | // ----------------------------------------------------------------------- // 172 | 173 | // Return a new model class singleton instance. 174 | return( new ContactService() ); 175 | 176 | })( jQuery, window.application )); 177 | -------------------------------------------------------------------------------- /linked/js/model/contact_service.js: -------------------------------------------------------------------------------- 1 | 2 | // I am the gateway to the contacts collection within the system. For this 3 | // demo, there is no communication with the server - all contacts are stored 4 | // locally and internally to this service object. 5 | 6 | // Add model to the application. 7 | window.application.addModel((function( $, application ){ 8 | 9 | // I am the contacts service class. 10 | function ContactService(){ 11 | this.contacts = []; 12 | this.contactsIndex = {}; 13 | this.uuid = 0; 14 | }; 15 | 16 | 17 | // I initialize the model. I get called once the application starts 18 | // running (or when the model is registered - if the application is 19 | // already running). At that point, the DOM is available and all the other 20 | // model and view classes will have been added to the system. 21 | ContactService.prototype.init = function(){ 22 | // ... nothing needed here ... 23 | }; 24 | 25 | 26 | // ----------------------------------------------------------------------- // 27 | // ----------------------------------------------------------------------- // 28 | 29 | 30 | // I delete the contact at the given ID. 31 | ContactService.prototype.deleteContact = function( id, onSuccess, onError ){ 32 | // Loop over the array looking for the given contact. 33 | for (var i = 0 ; i < this.contacts.length ; i++){ 34 | 35 | // Check to see if this is the target contact. 36 | if (this.contacts[ i ].id == id){ 37 | 38 | // Delete this contact from the collection. 39 | this.contacts.splice( i, 1 ); 40 | 41 | // Delete this contact from the index. 42 | this.contactsIndex[ id ] = null; 43 | 44 | // Break out of the loop. 45 | break; 46 | 47 | } 48 | 49 | } 50 | 51 | // Call the callback with the given id. 52 | onSuccess( id ); 53 | }; 54 | 55 | 56 | // I get the contact at the given ID. 57 | ContactService.prototype.getContact = function( id, onSuccess, onError ){ 58 | // Check to see if the contact exists. 59 | if (this.contactsIndex[ id ]){ 60 | 61 | // Return the given contact. 62 | onSuccess( this.contactsIndex[ id ] ); 63 | 64 | } else { 65 | 66 | // The contact could not be found - just return a new contact. 67 | onSuccess( 68 | application.getModel( "Contact", [ 0 ] ) 69 | ); 70 | 71 | } 72 | }; 73 | 74 | 75 | // I get the contacts. 76 | ContactService.prototype.getContacts = function( onSuccess, onError ){ 77 | // Return the contacts collection. 78 | onSuccess( this.contacts ); 79 | }; 80 | 81 | 82 | // I save the given contact. 83 | ContactService.prototype.saveContact = function( id, name, phone, email, onSuccess, onError ){ 84 | // Create an array to hold the validation errors. 85 | var errors = []; 86 | 87 | // Validate the name. 88 | if ($.trim( name ).length == 0){ 89 | 90 | // Add a new error. 91 | errors.push({ 92 | type: "MissingData", 93 | message: "[NAME] is required", 94 | parameter: "name" 95 | }); 96 | 97 | } 98 | 99 | // Valide the email. 100 | if ( 101 | email.length && 102 | !(new RegExp( "^[^@]+@([^.]+\\.)+[^.]{2,}$", "i" )).test( email ) 103 | ){ 104 | 105 | // Add a new error. 106 | errors.push({ 107 | type: "InvalidValue", 108 | message: "[EMAIL] is invalid", 109 | parameter: "email" 110 | }); 111 | 112 | } 113 | 114 | // Check to see if there any errors. 115 | if (errors.length){ 116 | 117 | // Return errors. 118 | onError( errors ); 119 | 120 | } else { 121 | 122 | // The contact validated, save it. 123 | 124 | // Check to see if this contact exists. 125 | if (this.contactsIndex[ id ]){ 126 | 127 | // Simply update the contact information. 128 | this.contactsIndex[ id ].name = name; 129 | this.contactsIndex[ id ].phone = phone; 130 | this.contactsIndex[ id ].email = email; 131 | 132 | } else { 133 | 134 | // Create a new contact. 135 | var contact = application.getModel( "Contact", [ ++this.uuid, name, phone, email ] ); 136 | 137 | // Add the contact to the collection. 138 | this.contacts.push( contact ); 139 | 140 | // Add the contact to the index. 141 | this.contactsIndex[ contact.id ] = contact; 142 | 143 | } 144 | 145 | // Sort the contacts collection. 146 | this.contacts.sort( 147 | function( a, b ){ 148 | if (a.name < b.name){ 149 | return( -1 ); 150 | } else { 151 | return( 1 ); 152 | } 153 | } 154 | ); 155 | 156 | // Return the saved contact id. 157 | onSuccess( id ); 158 | 159 | } 160 | }; 161 | 162 | 163 | // ----------------------------------------------------------------------- // 164 | // ----------------------------------------------------------------------- // 165 | 166 | // Return a new model class singleton instance. 167 | return( new ContactService() ); 168 | 169 | })( jQuery, window.application )); 170 | 171 | 172 | 173 | // ----------------------------------------------------------------------- // 174 | // FOR THE DEMO ONLY!!! // 175 | // ----------------------------------------------------------------------- // 176 | 177 | // When the DOM is ready, pre-populate some sample data. 178 | jQuery(function( $ ){ 179 | 180 | // Get the contact service. 181 | var contactService = window.application.getModel( "ContactService" ); 182 | 183 | var onSuccess = function(){}; 184 | 185 | // Add some test data to the database. 186 | contactService.saveContact( 0, "Ben Nadel", "212-555-1234", "ben.nadel@corMVC.com", onSuccess ); 187 | contactService.saveContact( 0, "Rey Bango", "212-555-2345", "rey.bango@corMVC.com", onSuccess ); 188 | contactService.saveContact( 0, "Cody Lindley", "212-555-0393", "cody.lindley@corMVC.com", onSuccess ); 189 | contactService.saveContact( 0, "Scott Gonzalez", "212-555-0908", "scott.gonzalez@corMVC.com", onSuccess ); 190 | contactService.saveContact( 0, "Rebecca Murphey", "212-555-1433", "rebecca.murphey@corMVC.com", onSuccess ); 191 | contactService.saveContact( 0, "Adam Sontag", "212-555-3948", "adam.sontag@corMVC.com", onSuccess ); 192 | contactService.saveContact( 0, "Karl Swedberg", "212-555-9983", "karl.swedberg@corMVC.com", onSuccess ); 193 | contactService.saveContact( 0, "Dan Wellman", "212-555-3434", "dan.wellman@corMVC.com", onSuccess ); 194 | contactService.saveContact( 0, "John Resig", "212-555-7890", "john.resig@corMVC.com", onSuccess ); 195 | contactService.saveContact( 0, "Paul Irish", "212-555-4311", "paul.irish@corMVC.com", onSuccess ); 196 | contactService.saveContact( 0, "Ben Alman", "212-555-7829", "ben.alman@corMVC.com", onSuccess ); 197 | contactService.saveContact( 0, "Bob Bonifield", "212-555-1248", "bob.bonifield@corMVC.com", onSuccess ); 198 | contactService.saveContact( 0, "Vlad Filippov", "212-555-8282", "vlad.filippov@corMVC.com", onSuccess ); 199 | contactService.saveContact( 0, "Jon Snook", "212-555-7894", "jon.snook@corMVC.com", onSuccess ); 200 | contactService.saveContact( 0, "Jon Clark", "212-555-4561", "jon.clark@corMVC.com", onSuccess ); 201 | contactService.saveContact( 0, "Elijah Manor", "212-555-4566", "elijah.manor@corMVC.com", onSuccess ); 202 | contactService.saveContact( 0, "James Padolsey", "212-555-1591", "james.padolsey@corMVC.com", onSuccess ); 203 | contactService.saveContact( 0, "Alex Sexton", "212-555-1236", "alex.sexton@corMVC.com", onSuccess ); 204 | 205 | }); 206 | -------------------------------------------------------------------------------- /linked/js/view/contact_form.js: -------------------------------------------------------------------------------- 1 | 2 | // I am the view helper for the Contact Add / Edit form. I bind the 3 | // appropriate event handlers and translate the UI events into actions 4 | // within the application. 5 | 6 | // Add view to the application. 7 | window.application.addView((function( $, application ){ 8 | 9 | // I am the contact form view class. 10 | function ContactForm(){ 11 | this.view = null; 12 | this.backLink = null; 13 | this.form = null; 14 | this.errors = null; 15 | this.fields = { 16 | id: null, 17 | name: null, 18 | phone: null, 19 | email: null 20 | }; 21 | this.cancelLink = null; 22 | }; 23 | 24 | 25 | // I initialize the view. I get called once the application starts 26 | // running (or when the view is registered - if the application is 27 | // already running). At that point, the DOM is available and all the other 28 | // model and view classes will have been added to the system. 29 | ContactForm.prototype.init = function(){ 30 | var self = this; 31 | 32 | // Initialize properties. 33 | this.view = $( "#contact-edit-view" ); 34 | this.form = $( "#contact-form" ); 35 | this.errors = this.form.find( "div.form-errors" ); 36 | this.fields.id = this.form.find( ":input[ name = 'id' ]" ); 37 | this.fields.name = this.form.find( ":input[ name = 'name' ]" ); 38 | this.fields.phone = this.form.find( ":input[ name = 'phone' ]" ); 39 | this.fields.email = this.form.find( ":input[ name = 'email' ]" ); 40 | this.cancelLink = this.form.find( "div.actions a.cancel" ); 41 | 42 | // Bind the submit handler. 43 | this.form.submit( 44 | function( event ){ 45 | // Submit the form. 46 | self.submitForm(); 47 | 48 | // Cancel default event. 49 | return( false ); 50 | } 51 | ); 52 | 53 | // Bind the cancel link. 54 | this.cancelLink.click( 55 | function( event ){ 56 | // Confirm the cancel link. 57 | return( confirm( "Are you sure you want to cancel?" ) ); 58 | } 59 | ); 60 | }; 61 | 62 | 63 | // ----------------------------------------------------------------------- // 64 | // ----------------------------------------------------------------------- // 65 | 66 | 67 | // I apply the given submission errors to the form. This involves translating the 68 | // paramters-based errors into user-friendly errors messages. 69 | ContactForm.prototype.applyErrors = function( errors ){ 70 | var self = this; 71 | 72 | // Clear any existing errors. 73 | this.clearErrors(); 74 | 75 | // Get the list of error messages. 76 | var errorList = this.errors.find( "> ul" ); 77 | 78 | // Loop over the errors to translate them. 79 | $.each( 80 | errors, 81 | function( index, error ){ 82 | var message = ""; 83 | 84 | // Check to see which type of error this is. 85 | switch (error.parameter){ 86 | case "name": 87 | message = "Please enter a name."; 88 | break; 89 | case "phone": 90 | message = "Please enter a valid phone number."; 91 | break; 92 | case "email": 93 | message = "Please enter a valid email."; 94 | break; 95 | default: 96 | message = ("An unknown error occurred: " + error.message + "."); 97 | break; 98 | }; 99 | 100 | // Add the error to the form. 101 | errorList.append( "
  • " + message + "
  • " ); 102 | } 103 | ); 104 | 105 | // Show the errors. 106 | this.errors.show(); 107 | }; 108 | 109 | 110 | // I clear the errors from the field. 111 | ContactForm.prototype.clearErrors = function(){ 112 | this.errors 113 | .hide() 114 | .find( "> ul" ) 115 | .empty() 116 | ; 117 | }; 118 | 119 | 120 | // I diable the form. 121 | ContactForm.prototype.disableForm = function(){ 122 | // Disable the fields. 123 | this.fields.name.attr( "disabled", true ); 124 | this.fields.phone.attr( "disabled", true ); 125 | this.fields.email.attr( "disabled", true ); 126 | }; 127 | 128 | 129 | // I enable the form. 130 | ContactForm.prototype.enableForm = function(){ 131 | // Enable the fields. 132 | this.fields.name.removeAttr( "disabled" ); 133 | this.fields.phone.removeAttr( "disabled" ); 134 | this.fields.email.removeAttr( "disabled" ); 135 | }; 136 | 137 | 138 | // I get called when the view needs to be hidden. 139 | ContactForm.prototype.hideView = function(){ 140 | this.view.removeClass( "current-primary-content-view" ); 141 | }; 142 | 143 | 144 | // I populate the form with the given contact informations. 145 | ContactForm.prototype.populateForm = function( id, name, phone, email ){ 146 | this.fields.id.val( id ); 147 | this.fields.name.val( name ); 148 | this.fields.phone.val( phone ); 149 | this.fields.email.val( email ); 150 | }; 151 | 152 | 153 | // I reset the contact form. 154 | ContactForm.prototype.resetForm = function(){ 155 | // Clear the errors. 156 | this.clearErrors(); 157 | 158 | // Reset the form fields. 159 | this.form.get( 0 ).reset(); 160 | }; 161 | 162 | 163 | // I get called when the view needs to be shown. 164 | ContactForm.prototype.showView = function( parameters ){ 165 | var self = this; 166 | 167 | // Reset the form. 168 | this.resetForm(); 169 | 170 | // Show the view. 171 | this.view.addClass( "current-primary-content-view" ); 172 | 173 | // Check to see if a contact ID was passed in. If so, we need to 174 | // populate the form with the given contact data. 175 | if (parameters.id){ 176 | 177 | // Diable the form while the model loads. 178 | this.disableForm(); 179 | 180 | // Get the contact and then use it to populate the form. 181 | application.getModel( "ContactService" ).getContact( 182 | parameters.id, 183 | 184 | // Success callback. 185 | function( contact ){ 186 | // Now that we have the form data, enable it. 187 | self.enableForm(); 188 | 189 | // Populate the form fields. 190 | self.populateForm( 191 | contact.id, 192 | contact.name, 193 | contact.phone, 194 | contact.email 195 | ); 196 | 197 | // Focus the first field. 198 | self.fields.name[ 0 ].focus(); 199 | } 200 | ); 201 | 202 | // If there was no ID, then this is an ADD form. Check to see if a default 203 | // value was passed for the name. 204 | } else if ("name" in parameters){ 205 | 206 | // Populate the name field with the given data. 207 | this.fields.name.val( parameters.name ); 208 | 209 | } 210 | 211 | // Focus the first field. 212 | this.fields.name[ 0 ].focus(); 213 | }; 214 | 215 | 216 | // I submit the form. 217 | ContactForm.prototype.submitForm = function(){ 218 | var self = this; 219 | 220 | // Try to save the contact using the contact service. 221 | application.getModel( "ContactService" ).saveContact( 222 | this.fields.id.val(), 223 | this.fields.name.val(), 224 | this.fields.phone.val(), 225 | this.fields.email.val(), 226 | 227 | // Success callback. 228 | function( id ){ 229 | application.relocateTo( "contacts" ); 230 | }, 231 | 232 | // Error callback. 233 | function( errors ){ 234 | // Apply the errors to the form. 235 | self.applyErrors( errors ); 236 | 237 | // Focus the name field. 238 | self.fields.name[ 0 ].focus(); 239 | } 240 | ); 241 | }; 242 | 243 | 244 | // ----------------------------------------------------------------------- // 245 | // ----------------------------------------------------------------------- // 246 | 247 | // Return a new view class singleton instance. 248 | return( new ContactForm() ); 249 | 250 | })( jQuery, window.application )); 251 | -------------------------------------------------------------------------------- /linked/js/view/contact_list.js: -------------------------------------------------------------------------------- 1 | 2 | // I am a view helper for the Contact List. I bind the appropriate 3 | // event handlers and translate the UI events into actions within 4 | // the application. 5 | 6 | // Add view to the application. 7 | window.application.addView((function( $, application ){ 8 | 9 | // I am the contact list view class. 10 | function ContactList(){ 11 | this.view = null; 12 | this.searchForm = null; 13 | this.searchCriteria = null; 14 | this.addLink = null; 15 | this.contactList = null; 16 | this.contactTemplate = null; 17 | }; 18 | 19 | 20 | // I initialize the view. I get called once the application starts 21 | // running (or when the view is registered - if the application is 22 | // already running). At that point, the DOM is available and all the other 23 | // model and view classes will have been added to the system. 24 | ContactList.prototype.init = function(){ 25 | var self = this; 26 | 27 | // Initialize properties. 28 | this.view = $( "#contact-list-view" ); 29 | this.searchForm = $( "#contact-list-header form" ); 30 | this.searchCriteria = this.searchForm.find( "input" ); 31 | this.addLink = $( "#contact-list-header a" ); 32 | this.contactList = $( "#contact-list" ); 33 | this.contactTemplate = $( "#contact-list-item-template" ); 34 | 35 | // Bind the search form submit. 36 | this.searchForm.submit( 37 | function( event ){ 38 | // Hand of to the search form handler. 39 | self.searchFormHandler(); 40 | 41 | // Cancel the default event. 42 | return( false ); 43 | } 44 | ); 45 | 46 | // Bind the keypress event on the search criteria. 47 | this.searchCriteria.keyup( 48 | function( event ){ 49 | // Filter the contact list. 50 | self.filterList( this.value ); 51 | } 52 | ); 53 | 54 | // Bind the keypress event to the search criteria so we can track 55 | // the use of the special keys presses. 56 | this.searchCriteria.keypress( 57 | function( event ){ 58 | // Store the SHIFT and ALT key status of the current click. 59 | self.searchCriteria.data( "shift", event.shiftKey ); 60 | self.searchCriteria.data( "alt", event.altKey ); 61 | } 62 | ); 63 | 64 | // Bind the list-level clicking (to avoid setting to many 65 | // event handlers). 66 | this.contactList.click( 67 | function( event ){ 68 | var target = $( event.target ); 69 | 70 | // Check to see if the target is the "more" link. 71 | if (target.is( "a.more" )){ 72 | 73 | // Toggle the list item details. 74 | self.toggleDetails( target.parents( "li" ) ); 75 | 76 | // Blur the current link. 77 | target.blur(); 78 | 79 | // Prevent default event. 80 | return( false ); 81 | 82 | // Check to see if the target is a the "delete" link. 83 | } else if (target.is( "a.delete" )){ 84 | 85 | // Blur the current link. 86 | target.blur(); 87 | 88 | // Confirm that the user wants to delete the contact. 89 | if (confirm( "Delete this contact?" )){ 90 | 91 | // This is a *hack* that we have to do since jQuery click() 92 | // event won't trigger the default action of the link. 93 | application.setLocation( target.attr( "href" ) ); 94 | 95 | } 96 | 97 | // Return false to cancel default event. 98 | return( false ); 99 | 100 | } 101 | } 102 | ); 103 | }; 104 | 105 | 106 | // ----------------------------------------------------------------------- // 107 | // ----------------------------------------------------------------------- // 108 | 109 | 110 | // I clear the contact list. 111 | ContactList.prototype.clearList = function(){ 112 | this.contactList.empty(); 113 | }; 114 | 115 | 116 | // I clear the search form. 117 | ContactList.prototype.clearSearchForm = function(){ 118 | this.searchForm[ 0 ].reset(); 119 | }; 120 | 121 | 122 | // I filter the contact list based on the given value. 123 | ContactList.prototype.filterList = function( criteria ){ 124 | // Convert criteria to lower case. 125 | criteria = criteria.toLowerCase(); 126 | 127 | // Get all the list items that do NOT match the criteria and hide them. 128 | this.contactList.find( "> li" ) 129 | // Show all the items. 130 | .show() 131 | 132 | // Filter down to the ones that don't match. 133 | .filter( 134 | function( index ){ 135 | // Get the model associated with this template. 136 | var contact = $( this ).data( "model" ); 137 | 138 | // Check the domain values. 139 | var matchesCriteria = ( 140 | (contact.name.toLowerCase().indexOf( criteria ) >= 0) || 141 | (contact.phone.toLowerCase().indexOf( criteria ) >= 0) || 142 | (contact.email.toLowerCase().indexOf( criteria ) >= 0) 143 | ); 144 | 145 | return( !matchesCriteria ); 146 | } 147 | ) 148 | 149 | // Hide the ones that don't match. 150 | .hide() 151 | ; 152 | }; 153 | 154 | 155 | // I get called when the view needs to be hidden. 156 | ContactList.prototype.hideView = function(){ 157 | this.view.removeClass( "current-primary-content-view" ); 158 | }; 159 | 160 | 161 | // I populate the list of contacts. 162 | ContactList.prototype.populateList = function(){ 163 | var self = this; 164 | 165 | // Clear the list. 166 | this.clearList(); 167 | 168 | // Get the contacts. 169 | application.getModel( "ContactService" ).getContacts( 170 | function( contacts ){ 171 | // Loop over the contacts and create templates. 172 | $.each( 173 | contacts, 174 | function( index, contact ){ 175 | // Append the contact to the list display. 176 | self.contactList.append( 177 | application.getFromTemplate( self.contactTemplate, contact ) 178 | ); 179 | } 180 | ); 181 | } 182 | ); 183 | }; 184 | 185 | 186 | // I handle any submit on the search form. 187 | ContactList.prototype.searchFormHandler = function(){ 188 | var visibleContacts = this.contactList.find( "> li:visible" ) 189 | 190 | // Check to see if there are any visible contacts. 191 | if (visibleContacts.size()){ 192 | 193 | // Check to see if the SHIFT button was down when form was submitted. 194 | // If so, then we are going to go to the edit page for the first contact. 195 | if (this.searchCriteria.data( "shift" )){ 196 | 197 | // Form was submitted with special action button - forward to 198 | // edit page for given contact. 199 | application.relocateTo( "contacts/edit/" + visibleContacts.eq( 0 ).data( "model" ).id ); 200 | 201 | // Check to see if the ALT button was down when the form was submitted. 202 | // If so, then we are going to trigger the delete of the first contact. 203 | } else if (this.searchCriteria.data( "alt" )){ 204 | 205 | // Trigger delete. 206 | setTimeout( 207 | function(){ 208 | visibleContacts.eq( 0 ).find( "a.delete" ).click(); 209 | }, 210 | 20 211 | ); 212 | 213 | } else { 214 | 215 | // There are visible items and no special action buttons, 216 | // so just show details. For performance reasons (and usability), 217 | // only show the first 3 visible items. To do this, we are just 218 | // going to simulate the click of the "more" link. 219 | visibleContacts.filter( ":lt(3)" ).find( "a.more" ).click(); 220 | 221 | } 222 | 223 | } else { 224 | 225 | // Since there are visible items, send to add form. Pass the search 226 | // form data through to the next location. 227 | application.relocateTo( 228 | "contacts/add", 229 | { 230 | name: this.searchCriteria.val() 231 | } 232 | ); 233 | 234 | } 235 | }; 236 | 237 | 238 | // I get called when the view needs to be shown. 239 | ContactList.prototype.showView = function(){ 240 | // Clear the search form. 241 | this.clearSearchForm(); 242 | 243 | // Show the view. 244 | this.view.addClass( "current-primary-content-view" ); 245 | 246 | // Populate the contacts. 247 | this.populateList(); 248 | 249 | // Focust the search form. 250 | this.searchCriteria[ 0 ].focus(); 251 | }; 252 | 253 | 254 | // I toggle the details of a given contact list item. 255 | ContactList.prototype.toggleDetails = function( contactListItem ){ 256 | // Toggle the details. 257 | contactListItem 258 | .find( "> dl.details" ) 259 | .slideToggle( 250 ) 260 | ; 261 | }; 262 | 263 | 264 | // ----------------------------------------------------------------------- // 265 | // ----------------------------------------------------------------------- // 266 | 267 | // Return a new view class singleton instance. 268 | return( new ContactList() ); 269 | 270 | })( jQuery, window.application )); 271 | -------------------------------------------------------------------------------- /index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | corMVC - jQuery-Powered Client Model-View-Controller (MVC) Framework 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    34 | 35 | 36 | 76 | 77 | 78 | 79 | 80 |
    81 | 82 | 83 |
    84 | 85 | 86 | 87 |
      88 | 89 | 90 |
    • 91 | 92 | 93 |

      94 | Welcome to corMVC - My jQuery-powered Client MVC Framework. 95 |

      96 | 97 |

      98 | CorMVC is a jQuery-powered Model-View-Controller (MVC) framework that 99 | can aide in the development of single-page jQuery applications. It evolved out of 100 | my recent presentation, 101 | Building Single-Page Applications Using jQuery And ColdFusion, 102 | and will continue to evolve as I think more deeply about this type of application 103 | architecture. 104 |

      105 | 106 |

      107 | CorMVC stands for: 108 | Client-Only-Required 109 | Model-View-Controller, 110 | and is my laboratory experiment in application architecture held completely 111 | seperate from server-side technologies. 112 |

      113 | 114 |

      115 | corMVC Philosophy 116 |

      117 | 118 |

      119 | Building frameworks in jQuery (or any other language for that matter) is 120 | very new to me; I don't claim to be any good at it. In fact, when I started 121 | looking into jQuery-based frameworks, I had no intention of creating my own. 122 | As I started to do my research, however, I quickly encountered two major problems 123 | with what was avilable: 124 |

      125 | 126 |
        127 |
      1. 128 | Most examples were so small that I could not see how they might be applied 129 | to the kind of software I build. 130 |
      2. 131 |
      3. 132 | Most frameworks were enormous and required command line utilities and 133 | some additional server-side technology (like Ruby On Rails) just to experiment with. 134 |
      4. 135 |
      136 | 137 |

      138 | I didn't even know how to begin learning. So, rather than wade through what was 139 | available, I decided to try and create something from scratch. What I came up with 140 | is corMVC. The philosophies that I put into the corMVC framework 141 | are those that were hopefully a remedy to the problems I encoutered above: 142 |

      143 | 144 |
        145 |
      1. 146 | A large sample application. This whole demo site (including the 147 | contacts section) runs off of corMVC as a single-page application. 148 |
      2. 149 |
      3. 150 | No server required. This demo application does not require any additional 151 | server-side technologies. If you have a web browser, you can 152 | download and run this application immediately. 153 |
      4. 154 |
      5. 155 | No building required. This framework does not require you to 156 | build the application using scaffolding or any other command-line executables. 157 | You just download it and open it up in a browser. 158 |
      6. 159 |
      7. 160 | Small Framework. This framework is very small (and excessively 161 | commented). It doesn't do anything more than it is supposed to. 162 |
      8. 163 |
      164 | 165 |

      166 | While I want to keep the corMVC framework as small as possible, I am sure that as I begin 167 | to more fully understand the various needs of single-page applications, the framework 168 | will have to evolve as necessary. In the end though, I want the corMVC framework to be 169 | an aide and not a constraint- affording the programmer the freedom to pile their 170 | own jQuery magic on top of this foundation. 171 |

      172 | 173 |

      174 | What If Javascript Is Not Enabled? 175 |

      176 | 177 |

      178 | When dealing with Javascript and "progressive enhancement enthusiast," someone always asks 179 | how this should work / degrade if the end user does not have Javascript enabled. 180 | While this might be a contentious answer, if your target user does not have Javascript 181 | enabled, then I don't think they should be using a single-page application. That's like 182 | asking how a user should navigate a FLEX application if they don't have Flash installed; 183 | it doesn't make sense. I'm not talking about building "pamphlet" sites here - 184 | I'm talking about building complex, user-experience-oriented applications. 185 |

      186 | 187 | 188 |
    • 189 | 190 | 191 | 192 |
    • 193 | 194 | 195 | 196 |
        197 | 198 | 199 |
      • 200 | 201 | 202 |

        203 | Contacts 204 |

        205 | 206 |

        207 | The contacts section is meant to demonstrate more complex interactions that 208 | involve the application's service layer (part of the Model) and potentially 209 | (thought not required in this demo) communication with the server. 210 |

        211 | 212 | 213 |
        214 | 215 |
        216 | 217 | 218 |
        219 | 220 | 221 | CreateContact » 222 | 223 | 224 |
        225 | 226 |
        227 | 228 |
          229 | 230 |
        231 | 232 |
        233 | 234 | 235 | 236 | 274 | 275 | 276 | 277 |
      • 278 | 279 | 280 | 281 |
      • 282 | 283 | 284 |

        285 | Add / Edit Contact 286 |

        287 | 288 |

        289 | Please fill out the contact information below. 290 |

        291 | 292 |
        293 | 294 | 295 | 296 | 307 | 308 |
        309 | 310 |
        311 | 314 | 315 |
        316 | 317 |
        318 | 321 | 322 |
        323 | 324 |
        325 | 328 | 329 |
        330 | 331 |
        332 | 333 |
        334 | 335 | ( Cancel ) 336 |
        337 | 338 |
        339 | 340 | 341 |
      • 342 | 343 | 344 |
      345 | 346 | 347 | 348 |
    • 349 | 350 | 351 | 352 |
    • 353 | 354 | 355 |

      356 | Documentation 357 |

      358 | 359 |

      360 | This is the documentation page. 361 |

      362 | 363 |

      364 | Coming soon.... 365 |

      366 | 367 | 368 |
    • 369 | 370 | 371 | 372 |
    • 373 | 374 | 375 |

      376 | Resources 377 |

      378 | 379 |

      380 | This is the resources page. 381 |

      382 | 383 |

      384 | Coming soon.... 385 |

      386 | 387 | 388 |
    • 389 | 390 | 391 | 392 |
    • 393 | 394 | 395 |

      396 | 404 - Page Not Found 397 |

      398 | 399 |

      400 | Sorry, but the page you requested could not be found. 401 |

      402 | 403 |

      404 | Try going back to the home page. 405 |

      406 | 407 | 408 |
    • 409 | 410 | 411 |
    412 | 413 | 414 | 415 |
    416 | 417 | 418 |
    419 | 420 | 421 | 422 | 423 | 437 | 438 | 439 |
    440 | 441 | 442 | 443 | 444 | -------------------------------------------------------------------------------- /linked/js/app/corMVC.js: -------------------------------------------------------------------------------- 1 | 2 | // Create a closed memory space for the application definition and instantiation. 3 | // This way, we keep the global name space clean. 4 | (function( $ ){ 5 | 6 | // I am the class that controls the Javascript framework. 7 | function Application(){ 8 | // Set default properties. 9 | 10 | // I am a unique ID of this application instance. NOTE: not currently used. 11 | this.id = ("app-" + (new Date()).getTime()); 12 | 13 | // I am the locaion (URL) monitor timer interval and handle. 14 | this.locationMonitorInterval = null; 15 | this.locationMonitorDelay = 150; 16 | 17 | // I am the location frame that is use for IE6 browsers that do not properly 18 | // listen to the back button on the browser. NOTE: I am only added to the DOM 19 | // for IE6. 20 | this.locationFrame = null; 21 | 22 | // I am the current location value of the application. 23 | this.currentLocation = null; 24 | 25 | // I am the collection of route mappings that map URL patterns to event 26 | // handlers within the cached controllers. 27 | this.routeMappings = []; 28 | 29 | // I am the collection of controllers. All controllers are intended to be 30 | // singleton instances. 31 | this.controllers = []; 32 | 33 | // I am the collection of models. I can contain either cached singleton 34 | // instances or class definitions (to be instantiated at request). 35 | this.models = { 36 | cache: {}, 37 | classes: {} 38 | }; 39 | 40 | // I am the collection of views. I can contain either cached singleton 41 | // instances of class definitions (to be instantiated at request). 42 | this.views = { 43 | cache: {}, 44 | classes: {} 45 | }; 46 | 47 | 48 | 49 | 50 | // ????????????????????????????????????????????????????????????????? 51 | // ????????????????????????????????????????????????????????????????? 52 | // ????????????????????????????????????????????????????????????????? 53 | // ????????????????????????????????????????????????????????????????? 54 | this.locationEvent = null; 55 | 56 | 57 | 58 | 59 | // I bind myself (THIS) to the change event on the hash. 60 | $( this ).bind( "locationchange", this.proxyCallback( this.onLocationChange ) ); 61 | 62 | // I am a flag to determine if the application has already started running. 63 | // I will determine if action is delayed or executed immediately when controllers 64 | // are registered. 65 | this.isRunning = false; 66 | }; 67 | 68 | 69 | // I add a given class to the given cache or class repository. 70 | Application.prototype.addClass = function( target, value ){ 71 | // Get the constructor of our value class. 72 | var constructor = value.constructor; 73 | 74 | // Check to see if this constructor is the Function object. If it is, 75 | // then this is just a class, not an instance. 76 | if (constructor == Function){ 77 | 78 | // This value object is a class, not an instance. Therefore, we need 79 | // to get the name of the class from the function itself. 80 | var className = value.toString().match( new RegExp( "^function\\s+([^\\s\\(]+)", "i" ) )[ 1 ]; 81 | 82 | // Cache the class constructor. 83 | target.classes[ className ] = value; 84 | 85 | } else { 86 | 87 | // This value object is an actual instance of the given class. Therefore, 88 | // we need to get the name of the class from its constructor function. 89 | var className = value.constructor.toString().match( new RegExp( "^function\\s+([^\\s\\(]+)", "i" ) )[ 1 ]; 90 | 91 | // Cache the class constructor. 92 | target.classes[ className ] = value.constructor; 93 | 94 | // In addition to caching the class constructor, let's cache this instance 95 | // of the given class itself (as it will act as a singleton). 96 | target.cache[ className ] = value; 97 | 98 | // Check to see if the application is running. If it is, then we need to initialize 99 | // the singleton instance. 100 | if (this.isRunning){ 101 | this.initClass( value ); 102 | } 103 | 104 | } 105 | }; 106 | 107 | 108 | // I add the given controller to the collection of controllers. 109 | Application.prototype.addController = function( controller ){ 110 | // Add the controller. 111 | this.controllers.push( controller ); 112 | 113 | // Check to see if the application is running. If it is, then we need to initialize 114 | // the controller instance. 115 | if (this.isRunning){ 116 | this.initClass( controller ); 117 | } 118 | }; 119 | 120 | 121 | // I add the given model class or instance to the model class library. Any classes 122 | // that are passed in AS instances will be cached and act as singletons. 123 | Application.prototype.addModel = function( model ){ 124 | this.addClass( this.models, model ); 125 | }; 126 | 127 | 128 | // I add the given view class or instance to the view class library. Any classes 129 | // that are passed in AS instances will be cached and act as singletons. 130 | Application.prototype.addView = function( view ){ 131 | this.addClass( this.views, view ); 132 | }; 133 | 134 | 135 | // I provide an AJAX gateway to the server. 136 | Application.prototype.ajax = function( options ){ 137 | var self = this; 138 | 139 | // Get the full range of settings. 140 | var ajaxOptions = $.extend( 141 | { 142 | type: "get", 143 | dataType: "json", 144 | normalizeJSON: false, 145 | cache: false, 146 | timeout: (10 * 1000), 147 | success: function( response, statusText ){}, 148 | error: function( request, statusText, error ){ 149 | alert( "There was a critical error communicating with the server" ); 150 | }, 151 | complete: function( request, statusText ){} 152 | }, 153 | options 154 | ); 155 | 156 | // If the data type is JSON, override the success method with one that 157 | // will normalize the response first. 158 | if ( 159 | ajaxOptions.normalizeJSON && 160 | (ajaxOptions.dataType == "json") && 161 | options.success 162 | ){ 163 | 164 | var targetSuccess = options.success; 165 | 166 | // Proxy the success callback, normalizing the response. 167 | ajaxOptions.success = function( response, statusText ){ 168 | targetSuccess( self.normalizeJSON( response ) ); 169 | }; 170 | } 171 | 172 | // Make the AJAX call. 173 | $.ajax( ajaxOptions ); 174 | }; 175 | 176 | 177 | // I check the location for a change. 178 | Application.prototype.checkLocationForChange = function(){ 179 | // Grab the live location and clean it up. Remove any hash marks 180 | // as well as any leading or trailing slashes. 181 | var liveLocation = this.normalizeHash( window.location.hash ); 182 | 183 | // Check to see if the location is not set or that it has changed 184 | // from the previous value. 185 | if ( 186 | (this.currentLocation == null) || 187 | (this.currentLocation != liveLocation) 188 | ){ 189 | 190 | // Update the iFrame location. This is needed to enable back button 191 | // functionality in Internet Explorer (IE). 192 | if (this.locationFrame){ 193 | this.locationFrame.attr( "src", "ie_back_button.htm?_=" + (new Date()).getTime() + "&hash=" + liveLocation ); 194 | } 195 | 196 | // Set the current location. 197 | this.setLocation( liveLocation ); 198 | 199 | } 200 | }; 201 | 202 | 203 | // I return an instance of the class with the given name from the given target. 204 | Application.prototype.getClass = function( target, className, initArguments ){ 205 | // Check to see if the instance is a cached singleton. 206 | if (target.cache[ className ]){ 207 | 208 | // This is a cached class - return the singleton. 209 | return( target.cache[ className ] ); 210 | 211 | } else { 212 | 213 | // This is not a cached class - return a new instance. In order to 214 | // do that, we will have to create an instance of it and then 215 | // initialize it with the given arguments. 216 | var newInstance = new (target.classes[ className ])(); 217 | 218 | // Initialize the class, this time calling the constructor in the 219 | // context of the class instance. 220 | target.classes[ className ].apply( newInstance, initArguments ); 221 | 222 | // Return the new instance. 223 | return( newInstance ); 224 | 225 | } 226 | }; 227 | 228 | 229 | // I am utility method used to create new DOM elements from templates. 230 | Application.prototype.getFromTemplate = function( template, model ){ 231 | // Get the raw HTML from the template. 232 | var templateData = template.html(); 233 | 234 | // Replace any data place holders with model data. 235 | templateData = templateData.replace( 236 | new RegExp( "\\$\\{([^\\}]+)\\}", "gi" ), 237 | function( $0, $1 ){ 238 | // Check to see if the place holder corresponds to a model property. 239 | // If it does, replace it in. 240 | if ($1 in model){ 241 | // Replace with model property. 242 | return( model[ $1 ] ); 243 | } else { 244 | // Model property not found - just return what was already there. 245 | return( $0 ) 246 | } 247 | } 248 | ); 249 | 250 | // Create the new node, store the model data internall, and return 251 | // the new node. 252 | return( $( templateData ).data( "model", model ) ); 253 | }; 254 | 255 | 256 | // I return an instance of the class with the given name. 257 | Application.prototype.getModel = function( className, initArguments ){ 258 | return( this.getClass( this.models, className, initArguments ) ); 259 | }; 260 | 261 | 262 | // I return an instance of the class with the given name. 263 | Application.prototype.getView = function( className, initArguments ){ 264 | return( this.getClass( this.views, className, initArguments ) ); 265 | }; 266 | 267 | 268 | // I initialize the given class instance. 269 | Application.prototype.initClass = function( instance ){ 270 | // Check to see if the target instance has an init method. 271 | if (instance.init){ 272 | // Invoke the init method. 273 | instance.init(); 274 | } 275 | }; 276 | 277 | 278 | // I intialize the given collection of class singletons. 279 | Application.prototype.initClasses = function( classes ){ 280 | var self = this; 281 | 282 | // Loop over the given class collection - our singletons - and init them. 283 | $.each( 284 | classes, 285 | function( index, instance ){ 286 | self.initClass( instance ); 287 | } 288 | ); 289 | }; 290 | 291 | 292 | // I intialize the controllers. Once the application starts running and the 293 | // DOM can be interacted with, I need to give the controllers a chance to 294 | // get ready. 295 | Application.prototype.initControllers = function(){ 296 | this.initClasses( this.controllers ); 297 | }; 298 | 299 | 300 | // I set up the location monitor - preparing it for running. I should only 301 | // be called once. 302 | Application.prototype.initLocationMonitor = function(){ 303 | // We only want to do this for IE, to help fix the back button. 304 | if (document.all){ 305 | this.locationFrame = $( "