· 7 years ago · Jul 24, 2018, 08:56 PM
1## Introduction
2
3JavaScriptMVC is an open-source jQuery-based JavaScript framework. It is nearly a comprehensive (holistic) front-end development framework; packaging utilities for:
4
5 - testing
6 - dependency management
7 - error reporting
8 - package management
9 - code cleaning
10 - custom events
11 - jQuery extensions
12 - documentation
13
14The library is broken into 4 mostly independent sub-projects:
15
16
17
18
19
20essentially everything but UI widgets. However, JMVC's MVC parts are only 7k gzipped.
21
22
23### Everything is a plugin
24
25JavaScriptMVC is broken down into 4 independent sub-projects:
26
27 - StealJS - Dependency Management, Code Generators, Production builds, Code cleaning.
28 - FuncUnit - Web testing framework
29 - DocumentJS - JS documentation framework
30 - jQueryMX - jQuery MVC extentions
31
32In the download, these are arranged into the following folders
33
34 funcunit
35 documentjs
36 jquery
37 steal
38
39Within each of these folders, are numerous sub-plugins. For example, JavaScriptMVC's controller is found in jquery/controller/controller.js.
40
41JavaScriptMVC encourages you to use a similar folder structure. We organized the todo app at the end of the application like:
42
43 funcunit
44 documentjs
45 jquery
46 steal
47 todo/
48 todo.js
49 todo.html
50
51## StealJS - Dependency Management
52
53JavaScriptMVC uses StealJS for dependency management and production builds. To use steal, you just have to load the steal script in your page and point it to a script file that loads your other files. For example, putting the following in todo.html loads steal.js and tells it to load todo/todo.js.
54
55 <script type='text/javascript'
56 src='../steal/steal.js?todo/todo.js'>
57 </script>
58
59The todo.js file can then use steal to load any dependencies it needs. JavaScriptMVC comes with the jQuery library in 'jquery/jquery.js'.
60The following loads jQuery and uses it to write Todo List:
61
62 steal('../jquery/jquery').then(function(){
63 $(document.body).append("<h1>Todo List</h1>")
64 })
65
66Because loading from JavaScriptMVC's root folder is extremely common, steal provides a plugins helper method. We can write the above as:
67
68 steal.plugins('jquery').then(function(){
69 $(document.body).append("<h1>Todo List</h1>")
70 })
71
72### Loading Non JavaScript resources
73
74Steal can load and build other resource types as well: client side templates, css, LessCSS and CoffeeScript. The following uses an jQuery.tmpl template in todos/views/list.tmpl to write todo list:
75
76 steal.plugins('jquery','jquery/view/tmpl')
77 .views("list.tmpl")
78 .then(function(){
79
80 $(document.body).append("//todo/views/list.tmpl",{message: "Todo List"} )
81 })
82
83
84
85### Compression
86
87Having lots of files is very slow for page loading times. StealJS makes building your app into a single JS and CSS file very easy. To generate the files, run:
88
89 js steal/buildjs todo/todo.html
90
91For our mini todo app, this produces:
92
93 todo/production.js
94
95To use production.js, we just have to load the production version of steal. We do this by changing our steal script tag to:
96
97 <script type='text/javascript'
98 src='../steal/steal.js?todo/todo.js'>
99 </script>
100
101## Class
102
103JavaScriptMVC's $.Controller and $.Model are based on it's Class helper - $.Class. $.Class is based on John Resig's simple class. It adds several important features, namely:
104
105 - static methods and properties
106 - introspection
107 - namespaces
108 - callback creation
109
110Creating a $.Class and extending it with a new class is straightforward:
111
112 $.Class("Animal",{
113 breath : function(){
114 console.log('breath');
115 }
116 });
117
118 Animal("Dog",{
119 wag : function(){
120 console.log('wag');
121 }
122 })
123
124 var dog = new Dog;
125 dog.wag();
126 dog.breath();
127
128When a new $.Class is created, it calls the class's init method with the arguments passed to the constructor function:
129
130 $.Class('Person',{
131 init : function(name, age){
132 this.name = name;
133 this.age = age;
134 },
135 speak : function(){
136 return "I am "+this.name+".";
137 }
138 });
139
140 var justin = new Person("Justin",28);
141 justin.speak(); //-> 'I am Justin.'
142
143$.Class lets you call base functions with this._super. Lets make a 'classier' person:
144
145 Person("ClassyPerson", {
146 speak : function(){
147 return "Salutations, "+this._super();
148 }
149 });
150
151 var fancypants = new ClassyPerson("Mr. Fancy",42);
152 fancypants.speak(); //-> 'Salutations, I am Mr. Fancy.'
153
154Class provides a callback method that can be used to return a function that has 'this' set appropriately (similar to proxy):
155
156 $.Class("Clicky",{
157 init : function(){
158 this.clickCount = 0;
159 },
160 wasClicked : function(){
161 this.clickCount++;
162 },
163 addListeners : function(el){
164 el.click(this.callback('wasClicked');
165 }
166 })
167
168Callback also lets you curry arguments and chain methods together.
169
170Class lets you define inheritable static properties and methods:
171
172 $.Class("Person",{
173 findOne : function(id, success){
174 $.get('/person/'+id, function(attrs){
175 success( new Person( attrs ) );
176 },'json')
177 }
178 },{
179 init : function(attrs){
180 $.extend(this, attrs)
181 },
182 speak : function(){
183 return "I am "+this.name+".";
184 }
185 })
186
187 Person.findOne(5, function(person){
188 alert( person.speak() );
189 })
190
191Class also provides namespacing and access to the name of the class and namespace object:
192
193 $.Class("Jupiter.Person");
194
195 Jupiter.Person.shortName; //-> 'Person'
196 Jupiter.Person.fullName; //-> 'Jupiter.Person'
197 Jupiter.Person.namespace; //-> Jupiter
198
199Putting this all together, we can make a basic ORM-style model layer:
200
201 $.Class("ORM",{
202 findOne : function(id, success){
203 $.get('/'+this.fullName.toLowerCase()+'/'+id,
204 this.callback(function(attrs){
205 success( new this( attrs ) );
206 })
207 },'json')
208 }
209 },{
210 init : function(attrs){
211 $.extend(this, attrs)
212 }
213 })
214
215 ORM("Person",{
216 speak : function(){
217 return "I am "+this.name+".";
218 }
219 });
220
221 Person.findOne(5, function(person){
222 alert( person.speak() );
223 });
224
225 ORM("Task")
226
227 Task.findOne(7,function(task){
228 alert(task.name);
229 })
230
231
232This is similar to how JavaScriptMVC's model layer works.
233
234## Models
235
236
237
238JavaScriptMVC's model and it associated plugins provide lots of tools around organizing model data such as validations, associations, events, lists and more. But the core functionality is centered around service encapsulation and type conversion.
239
240### Service Encapsulation
241
242Model makes it crazy easy to connect to JSON REST services and add helper methods to the resulting data. For example, take a todos service that allowed you to create, retrieve, update and delete todos like:
243
244 POST /todos name=laundry dueDate=1300001272986 -> {'id': 8}
245 GET /todos -> [{'id': 5, 'name': "take out trash", 'dueDate' : 1299916158482},
246 {'id': 7, 'name': "wash dishes", 'dueDate' : 1299996222986},
247 ... ]
248 GET /todos/5 -> {'id': 5, 'name': "take out trash", 'dueDate' : 1299916158482}
249 PUT /todos/5 name=take out recycling -> {}
250 DELETE /todos/5 -> {}
251
252Making a Model that can connect to these services and add helper functions is shockingly easy:
253
254 $.Model("Todo",{
255 findAll : "GET /todos",
256 findOne : "GET /todos/{id}",
257 create : "POST /todos",
258 update : "PUT /todos/{id}",
259 destroy : "DELETE /todos/{id}"
260 },{
261 daysRemaining : function(){
262 return ( new Date(this.dueDate) - new Date() ) / 86400000
263 }
264 });
265
266This allows you to
267
268 // List all todos
269 Todo.findAll({}, function(todos){
270 var html = [];
271 for(var i =0; i < todos.length; i++){
272 html.push(todos[i].name+" is due in "+
273 todos[i].daysRemaining()+
274 "days")
275 }
276 $('#todos').html("<li>"+todos.join("</li><li>")+"</li>")
277 })
278
279 //Create a todo
280 new Todo({
281 name: "vacuum stairs",
282 dueDate: new Date()+86400000
283 }).save(function(todo){
284 alert('you have to '+todo.name+".")
285 });
286
287 //update a todo
288 todo.update({name: "vacuum all carpets"}, function(todo){
289 alert('updated todo to '+todo.name+'.')
290 });
291
292 //destroy a todo
293 todo.destroy(function(todo){
294 alert('you no longer have to '+todo.name+'.')
295 });
296
297Of course, you can supply your own functions.
298
299### Events
300
301Although encapsulating ajax requests in a model is valuable, there's something even more important about models to an MVC architecture - events. $.Model lets you listen model events. You can listen to models being updated, destroyed, or even just having single attributes changed.
302
303$.Model produces two types of events:
304
305 - OpenAjax.hub events
306 - jQuery events
307
308Each has advantages and disadvantages for particular situations. For now we'll deal with jQuery events. Lets say we wanted to know when a todo is created and add it to the page. And after it's been added to the page, we'll listen for updates on that todo to make sure we are showing its name correctly. We can do that like:
309
310 $(Todo).bind('created', function(todo){
311 var el = $('<li>').html(todo.name);
312 el.appendTo($('#todos'));
313 todo.bind('updated', function(todo){
314 el.html(todo.name)
315 }).bind('destroyed', function(){
316 el.remove()
317 })
318 })
319
320### Getter / Setters ?
321
322
323
324### Model.Lists
325
326Often, in complex JS apps, you're dealing with discrete lists of lots of items. For example, you might have two people's todo lists on the page at once.
327
328Model has the model list plugin to help with this.
329
330 $.Model.List("Todo.List")
331
332 $.Class('ListWidget',{
333 init : function(element, username){
334 this.element = element;
335 this.username = username;
336 this.list = new Todo.List();
337 this.list.bind("add", this.callback('addTodos') );
338 this.list.findAll({username: username});
339 this.element.delegate('.create','submit',this.callback('create'))
340 },
341 addTodos : function(todos){
342 // TODO: gets called with multiple todos
343 var el = $('<li>').html(todo.name);
344 el.appendTo(this.element.find('ul'));
345 todo.bind('updated', function(todo){
346 el.html(todo.name)
347 })
348 },
349 create : function(ev){
350 var self = this;
351 new Todo({name: ev.target.name.value,
352 username: this.username}).save(function(todo){
353 self.list.push(todo);
354 })
355 }
356 });
357
358 new ListWidget($("#briansList"), "brian" );
359 new ListWidget($("#justinsList"), "justin" );
360
361## $.Controller - jQuery plugin factory
362
363JavaScriptMVC's controllers are really a jQuery plugin factory. They can be used as a traditional view, for example, making a slider widget, or a traditional controller, creating view-controllers and binding them to models.
364
365 - jQuery helper
366 - auto bind / unbind
367 - parameterized actions
368 - defaults
369 - pub / sub
370
371
372## $.View - Client Side Templates
373
374JavaScriptMVC's views are really just client side templates. jQuery.View is a templating interface that takes care of complexities using templates:
375
376 - Convenient and uniform syntax
377 - Template loading from html elements and external files.
378 - Synchronous and asynchronous template loading.
379 - Template preloading.
380 - Caching of processed templates.
381 - Bundling of processed templates in production builds.
382
383JavaScriptMVC comes pre-packaged with 4 different templates:
384
385 - EJS
386 - JAML
387 - Micro
388 - Tmpl
389
390And there are 3rd party plugins for Mustache and Dust.
391
392### Use
393
394When using views, you almost always want to insert the results of a rendered template into the page. jQuery.View overwrites the jQuery modifiers so using a view is as easy as:
395
396 $("#foo").html('mytemplate.ejs',{message: 'hello world'})
397
398This code:
399
400 1. Loads the template a 'mytemplate.ejs'. It might look like:
401
402 <h2><%= message %></h2>
403
404 2. Renders it with {message: 'hello world'}, resulting in:
405
406 <h2>hello world</h2>
407
408 3. Inserts the result into the foo element. Foo might look like:
409
410 <div id='foo'><h2>hello world</h2></div>
411
412
413### jQuery Modifiers
414
415You can use a template with the following jQuery modifier methods:
416
417 $('#bar').after('temp.jaml',{});
418 $('#bar').append('temp.jaml',{});
419 $('#bar').before('temp.jaml',{});
420 $('#bar').html('temp.jaml',{});
421 $('#bar').prepend('temp.jaml',{});
422 $('#bar').replaceWidth('temp.jaml',{});
423 $('#bar').text('temp.jaml',{});
424
425### Loading from a script tag
426
427View can load from script tags or from files. To load from a script tag, create a script tag with your template and an id like:
428
429 <script type='text/ejs' id='recipes'>
430 <% for(var i=0; i < recipes.length; i++){ %>
431 <li><%=recipes[i].name %></li>
432 <%} %>
433 </script>
434
435Render with this template like:
436
437$("#foo").html('recipes',recipeData)
438
439Notice we passed the id of the element we want to render.
440
441### Asynchronous loading
442
443By default, retrieving requests is done synchronously. This is fine because StealJS packages view templates with your JS download.
444
445However, some people might not be using StealJS or want to delay loading templates until necessary. If you have the need, you can provide a callback paramter like:
446
447 $("#foo").html('recipes',recipeData, function(result){
448 this.fadeIn()
449 });
450
451The callback function will be called with the result of the rendered template and 'this' will be set to the original jQuery object.
452
453## Special Events and Dom Extensions
454
455JavaScriptMVC is packed with jQuery helpers that make building a jQuery app easier and fast. Here's the some of the most useful plugins:
456
457### CurStyles
458
459Rapidly retrieve multiple css styles on a single element:
460
461 $('#foo').curStyles('paddingTop',
462 'paddingBottom',
463 'marginTop',
464 'marginBottom');
465
466### Fixtures
467
468Often you need to start building JS functionality before the server code is ready. Fixtures simulate Ajax responses. They let you make Ajax requests and get data back. Use them by mapping request from one url to another url:
469
470 $.fixture("/todos.json","/fixtures/todos.json")
471
472And then make a request like normal:
473
474 $.get("/todos.json",{}, function(){},'json')
475
476### Drag Drop Events
477
478### Hover
479
480### Default
481
482### Destroyed
483
484### Hashchange
485
486
487
488
489## Building a todo list