Skip to content
Browse files

alpha release

  • Loading branch information...
1 parent 8506efe commit ecb0a731f5bd63b5fefb3252933752d6df5a5eda @rhysbrettbowen committed
View
113 .svn/text-base/all_tests.html.svn-base
@@ -1,113 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-Copyright 2009 The Closure Library Authors. All Rights Reserved.
-
-Use of this source code is governed by the Apache License, Version 2.0.
-See the COPYING file for details.
--->
-<head>
-<title>Closure - All JsUnit Tests</title>
-<script src="closure-library/closure/goog/base.js"></script>
-<script src="alltests.js"></script>
-<script>
-goog.require('goog.userAgent.product');
-goog.require('goog.testing.MultiTestRunner');
-</script>
-<link rel="stylesheet" href="closure-library/closure/goog/css/multitestrunner.css" type="text/css">
-<style>
-h1 {
- font: normal x-large arial, helvetica, sans-serif;
- margin: 0;
-}
-p, form {
- font: normal small sans-serif;
- margin: 0;
-}
-#header {
- position: absolute;
- right: 10px;
- top: 13px;
-}
-#footer {
- margin-top: 8px;
-}
-a {
- text-decoration: none;
-}
-a:hover {
- text-decoration: underline;
-}
-.warning {
- font-size: 14px;
- font-weight: bold;
- width: 80%;
-}
-</style>
-</head>
-<body>
-
-<script>
- if (goog.userAgent.product.CHROME &&
- window.location.toString().indexOf('file:') == 0) {
- document.write(
- '<div class="warning">' +
- 'WARNING: Due to Chrome\'s security restrictions ' +
- 'this test will not be able to load files off your local disk ' +
- 'unless you start Chrome with:<br>' +
- '<code>--allow-file-access-from-files</code></div><br>');
- }
-</script>
-
-<h1>Closure - All JsUnit Tests</h1>
-<p id="header">
- <a href="http://wiki/Main/ClosureUnitTests">Closure JS Testing HOWTO</a>
-</p>
-<div id="runner"></div>
-<!-- Use a form so browser persists input values -->
-<form id="footer" onsubmit="return false">
- Settings:<br>
- <input type="checkbox" name="hidepasses" id="hidepasses" checked>
- <label for="hidepasses">Hide passes</label><br>
- <input type="checkbox" name="parallel" id="parallel" checked>
- <label for="parallel">Run in parallel</label>
- <small>(timing stats not available if enabled)</small><br>
- <input type="text" name="filter" id="filter" value="">
- <label for="filter">Run only tests for path</label>
-</form>
-<script>
- var hidePassesInput = document.getElementById('hidepasses');
- var parallelInput = document.getElementById('parallel');
- var filterInput = document.getElementById('filter');
-
- function setFilterFunction() {
- var matchValue = filterInput.value || '';
- testRunner.setFilterFunction(function(testPath) {
- return testPath.indexOf(matchValue) > -1;
- });
- }
-
- // Create a test runner and render it.
- var testRunner = new goog.testing.MultiTestRunner()
- .setName(document.title)
- .setBasePath('./')
- .setPoolSize(parallelInput.checked ? 8 : 1)
- .setStatsBucketSizes(5, 500)
- .setHidePasses(hidePassesInput.checked)
- //.setVerbosePasses(true)
- .addTests(_allTests);
- testRunner.render(document.getElementById('runner'));
-
- goog.events.listen(hidePassesInput, 'click', function(e) {
- testRunner.setHidePasses(e.target.checked);
- });
-
- goog.events.listen(parallelInput, 'click', function(e) {
- testRunner.setPoolSize(e.target.checked ? 8 : 1);
- });
-
- goog.events.listen(filterInput, 'keyup', setFilterFunction);
- setFilterFunction();
-</script>
-</body>
-</html>
View
22 .svn/text-base/alltests.js.svn-base
@@ -1,22 +0,0 @@
-// Copyright 2009 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-var _allTests = [
- "collection_test.html",
- "control_test.html",
- "model_test.html",
- 'router_test.html',
- 'store_test.html',
- 'mediator_test.html'
-];
View
11 .svn/text-base/collection_test.html.svn-base
@@ -1,11 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>Test for mvc.Collection</title>
-</head>
-<body>
- <script src="closure-library/closure/goog/base.js"></script>
- <script src="test_deps.js"></script>
- <script src="collection_test.js"></script>
-</body>
-</html>
View
51 .svn/text-base/collection_test.js.svn-base
@@ -1,51 +0,0 @@
-goog.require('mvc.Collection');
-goog.require('mvc.Model');
-
-goog.require('goog.testing.PropertyReplacer');
-goog.require('goog.testing.jsunit');
-
-/** @type {mvc.Model} */
-var model1, model2, model3;
-
-var setUp = function() {
- model1 = new mvc.Model({'sort':3});
- model2 = new mvc.Model({'sort':1});
- model3 = new mvc.Model({'sort':2});
-};
-
-var testUnsortedCollection = function() {
- var test = new mvc.Collection({'models':[model1, model2, model3]});
- assertEquals('first object should be mock 1', test.at(0), model1);
- assertEquals('second object should be mock 2', test.at(1), model2);
- assertEquals('third object should be mock 3', test.at(2), model3);
-};
-
-var testSortedCollection = function() {
- var sort = function(a, b) {return a.get('sort')-b.get('sort');};
- var test = new mvc.Collection();
- test.setComparator(sort);
- test.add(model1);
- assertEquals('first object should be mock 1', test.at(0), model1);
- test.add(model2);
- assertEquals('first object should be mock 2', test.at(0), model2);
- assertEquals('second object should be mock 1', test.at(1), model1);
- test.add(model3);
- assertEquals('first object should be mock 2', test.at(0), model2);
- assertEquals('second object should be mock 3', test.at(1), model3);
- assertEquals('third object should be mock 1', test.at(2), model1);
-};
-
-var testNewSortedCollection = function() {
- var sort = function(a, b) {return a.get('sort')-b.get('sort');};
- var test = new mvc.Collection({'models':[model1,model2,model3]});
- test.setComparator(sort);
- assertEquals('first object should be mock 2', test.at(0), model2);
- assertEquals('second object should be mock 3', test.at(1), model3);
- assertEquals('third object should be mock 1', test.at(2), model1);
-};
-
-var testAsModel = function() {
- var test = new mvc.Collection();
- test.set('a', 1);
- assertEquals('should have attribute a as 1', test.get('a'), 1);
-};
View
12 .svn/text-base/control_test.html.svn-base
@@ -1,12 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>Test for mvc.Control</title>
-</head>
-<body>
- <div id="control"><div id="test1" class="class1"><div id="test2" class="class2"><span id="test3" class="class1"></span></div></div></div>
- <script src="closure-library/closure/goog/base.js"></script>
- <script src="test_deps.js"></script>
- <script src="control_test.js"></script>
-</body>
-</html>
View
49 .svn/text-base/control_test.js.svn-base
@@ -1,49 +0,0 @@
-goog.require('mvc.Control');
-goog.require('mvc.Model');
-
-goog.require('goog.dom');
-goog.require('goog.testing.jsunit');
-
-var simpleControl;
-
-var setUp = function() {
- simpleModel = new mvc.Model();
- simpleControl = new mvc.Control(simpleModel);
- simpleControl.decorate(goog.dom.getElement("control"));
-};
-
-var testSimpleControl = function() {
- assertEquals("should come back with one element", 1, simpleControl.getEls(".class2").length);
- assertEquals("should come back with 2 elements", 2, simpleControl.getEls(".class1").length);
-};
-
-var testControlListener = function () {
- var toggle = false;
- var handle = function(){toggle = !toggle;};
- var uid = simpleControl.click(handle);
- var evt = document.createEvent("MouseEvents");
- evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- simpleControl.getElement().dispatchEvent(evt);
- assert("true, click should be handled", toggle);
- simpleControl.off(uid);
- evt = document.createEvent("MouseEvents");
- evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- simpleControl.getElement().dispatchEvent(evt);
- assert("true, click listener should be removed", toggle);
-};
-
-var testControlOnce = function () {
- var toggle = false;
- var handle = function(){
- toggle = !toggle;
- };
- simpleControl.once(goog.events.EventType.CLICK, handle);
- var evt = document.createEvent("MouseEvents");
- evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- simpleControl.getElement().dispatchEvent(evt);
- assert("true, click should be handled", toggle);
- evt = document.createEvent("MouseEvents");
- evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
- simpleControl.getElement().dispatchEvent(evt);
- assert("true, click listener should be removed", toggle);
-};
View
11 .svn/text-base/mediator_test.html.svn-base
@@ -1,11 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>Test for mvc.Mediator</title>
-</head>
-<body>
- <script src="closure-library/closure/goog/base.js"></script>
- <script src="test_deps.js"></script>
- <script src="mediator_test.js"></script>
-</body>
-</html>
View
32 .svn/text-base/mediator_test.js.svn-base
@@ -1,32 +0,0 @@
-goog.require('mvc.Mediator');
-
-goog.require('goog.testing.jsunit');
-
-var med;
-var listen;
-
-var setUp = function() {
- med = new mvc.Mediator();
-};
-
-testRegister = function(){
- var a = {};
- med.register(a, ['test']);
- assertEquals(a, med.available_['test'][0]);
-};
-
-testUnregister = function() {
- var a = {};
- med.unregister(a, ['test']);
- assertUndefined(med.available_['test']);
-};
-
-testListen = function() {
- listen = med.on('test', function(){});
- assert(med.isListened('test'));
-};
-
-testUnlisten = function() {
- med.off(listen);
- assert(!med.isListened('test'));
-};
View
11 .svn/text-base/model_test.html.svn-base
@@ -1,11 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>Test for mvc.Model</title>
-</head>
-<body>
- <script src="closure-library/closure/goog/base.js"></script>
- <script src="test_deps.js"></script>
- <script src="model_test.js"></script>
-</body>
-</html>
View
51 .svn/text-base/model_test.js.svn-base
@@ -1,51 +0,0 @@
-goog.require('mvc.Model');
-
-goog.require('goog.testing.jsunit');
-
-var simpleModel;
-var emptyModel;
-
-var setUp = function() {
- simpleModel = new mvc.Model({attr:
- {'a':'exists'}});
- emptyModel = new mvc.Model();
-};
-
-var testSimpleModel = function() {
- assertNotNullNorUndefined("New model created", simpleModel);
- assertEquals("Should be able to get 'a'", simpleModel.get('a'), 'exists');
- assertUndefined("Should return undefined", simpleModel.get('b'));
- simpleModel.set('a', 'changed');
- assertEquals("Should be able to change 'a'", simpleModel.get('a'), 'changed');
- simpleModel.set('b', 'new');
- assertEquals("Should be able to add new attribute 'b'", simpleModel.get('b'), 'new');
- simpleModel.unset('b');
- assertUndefined("Should be able to remove attribute 'b'", simpleModel.get('b'));
-};
-
-var testEmptyModel = function() {
- assertNotNull(emptyModel);
-};
-
-var testAlias = function() {
- simpleModel.set('date', {day:1,month:1});
- simpleModel.alias('1jan2010', 'date');
- assertEquals(simpleModel.get('1jan2010'), simpleModel.get('date'));
-};
-
-var testFormat = function() {
- simpleModel.set('date', {day:1,month:1});
- simpleModel.format('date', function(date) {
- return date.day+"/"+date.month;
- });
- assertEquals(simpleModel.get('date'), "1/1");
-};
-
-var testMeta = function() {
- simpleModel.set('day',1);
- simpleModel.set('month',1);
- simpleModel.meta('jan1', ['day', 'month'], function(day, month) {
- return day+"/"+month;
- });
- assertEquals(simpleModel.get('jan1'),"1/1");
-};
View
15 .svn/text-base/router_test.html.svn-base
@@ -1,15 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>Test for mvc.Router</title>
-</head>
-<body>
- <script src="closure-library/closure/goog/base.js"></script>
- <script src="test_deps.js"></script>
- <script>
- goog.require('goog.testing.ContinuationTestCase');
- goog.require('goog.testing.jsunit');
- </script>
- <script src="router_test.js"></script>
-</body>
-</html>
View
33 .svn/text-base/router_test.js.svn-base
@@ -1,33 +0,0 @@
-goog.require('mvc.Router');
-
-goog.require('goog.testing.ContinuationTestCase');
-goog.require('goog.testing.jsunit');
-
-var router;
-
-var setUp = function() {
- router = new mvc.Router();
-};
-
-var testNavigation = function() {
- router.navigate("testing");
- loc = document.location.toString();
- assertEquals(loc.replace(/.*#/,''), "testing");
-};
-
-var testRoute = function() {
- var reached = false;
- var a = function(){reached = true;};
-
- waitForEvent(router.history_, goog.history.EventType.NAVIGATE,
- function() {
- assert(reached);
- });
- router.route("test", a);
- router.navigate("test");
-};
-
-
-testCase = new goog.testing.ContinuationTestCase();
-testCase.autoDiscoverTests();
-G_testRunner.initialize(testCase);
View
11 .svn/text-base/store_test.html.svn-base
@@ -1,11 +0,0 @@
-<!doctype html>
-<html>
-<head>
- <title>Test for mvc.Router</title>
-</head>
-<body>
- <script src="closure-library/closure/goog/base.js"></script>
- <script src="test_deps.js"></script>
- <script src="store_test.js"></script>
-</body>
-</html>
View
34 .svn/text-base/store_test.js.svn-base
@@ -1,34 +0,0 @@
-goog.require('mvc.Model');
-goog.require('mvc.Collection');
-goog.require('mvc.Store');
-
-goog.require('goog.testing.jsunit');
-
-var simpleModel;
-var store;
-
-var setUp = function() {
- simpleModel = new mvc.Model({
- 'id':'abc123',
- 'text': 'test model 1'
- });
- store = new mvc.Store();
-};
-
-
-var testGetStoredObject = function(){
- store.set(simpleModel);
- assertEquals(store.get('abc123'), simpleModel);
-};
-
-var testGetUnstoredObject = function(){
- assertEquals(store.get('xyz789').constructor, mvc.Model);
-};
-
-var testGetNewObject = function(){
- assertEquals(store.get().constructor, mvc.Model);
-};
-
-var testGetObjectOfType = function(){
- assertEquals(store.get('qwerty',mvc.Collection).constructor, mvc.Collection);
-};
View
803 .svn/text-base/test_deps.js.svn-base
0 additions, 803 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
304 README.md
@@ -1,18 +1,37 @@
-# goog.mvc #
+# PlastronJS #
-An MVC library which uses the Google Closure library for use with the Closure Compiler
+PlastronJS is an MVC library which uses the Google Closure library for use with the Closure Compiler. I've decided to call it Plastron after the name of the underside of the turtle to mimic names of frameworks like Backbone and Spine but also because it's a good base to build upon.
-The folder should be put in your project directory and the path passed to the closure compiler
+PlastronJS though is not just a MVC framework, it's the start of an application framework. I've decided to include a mediator and a store with the package which will hopefully help in the construction of medium to large size applications.
-to use a module, use
+The source code should be well documented and I plan on including tutorials on my blog at http://rhysbrettbowen.com. Feel free to contact me at rhysbrettbowen@gmail.com if there are any questions
+
+The folder should be put in your project directory and the path passed to the closure compiler. I have my project folder setup like this:
+
+myApp/
+ js/ <- your main app code here
+ lib/ <- libraries you use here
+ mvc/ <- PlastronJS here
+ styles/ <- CSS/GSS/SASS etc.
+ template/ <- Soy files here
+
+to use a module you'll need to include it
```javascript
-goog.require('packageName');
+goog.require('mvc.Model');
```
+I decided to leave the namespace that the project is under at mvc for two reasons. The first is that it's simpler to write than PlastronJS and the second is that it makes more sense in the code.
+
+I'd like to point out that I've spent a lot of time reading the source code of other MVC frameworks, most notably Backbone.js so hopefully it will seem fairly familiar. There are however some notable differences. The closure compiler does not mix dot notation and square bracket notation so a lot of the nice linking to functions based on names was out of the question. I also want this framework to work for larger applications. A lot of people find trouble with the current mvc frameworks because they don't scale well or provide functionality for more complex systems. I'm hoping that I have addressed this. Lastly I developed this MVC to use on the webclient at Catch.com so some of the decisions I made were based on what I needed - but I have also included extra functionality I am not using and this means that some parts are not tested as rigorously. If you do spot any bugs or any features that need fine tuning please let me know or fork the project and make some changes.
+
+If you are new to closure then you need this book http://www.amazon.com/Closure-Definitive-Guide-Michael-Bolin/dp/1449381871
+
## mvc.Model ##
-The model is the main component. It allows you to store data and sends out change events when it changes (it extends goog.events.EventTarget). It also communicates with the data store through mvc.Sync
+The model is the main component. It allows you to store data and send out change events when it changes (it extends goog.events.EventTarget). It also communicates with the data store through mvc.Sync
+
+### make a Model ###
You can instantiate a new model directly, or you may wish to create a new type of model that inherits from mvc.Model like so:
@@ -35,13 +54,59 @@ var Person = function(firstName, lastName) {
goog.inherits(Person, mvc.Model);
```
+Another option is to just create a model and differentiate it by a schema:
+
+```javascript
+var Rhys = mvc.Model.create({
+ 'schema': myApp.schema.Person
+ });
+```
+
+we'll go in to schemas later.
+
Any setup would go in the constructor function (in the above we used the meta function to create a meta attribute). You can override methods and add new methods of your own.
-When creating a new model instance, the constructor takes an options object. The options object takes the special keys 'schema', 'sync' and 'attr'. The schema should inherit from mvc.models.Schema, the sync should implement mvc.Sync. The attr should contain a map of all the initial values you want for the model. Other key value pairs given in the options object will be moved under the attr key.
+### new Model options ###
+
+When creating a new model instance, the constructor takes an options object. The options object takes the special keys 'schema', 'sync' and 'attr'. You can put any attributes directly in the object except for the reserved keywords of schema, sync and attr. If you do wish to use these in your model then you can place them under the attr keyword. To be safe it's best to put them all under the attr keyword. So an options object might look a little something like this:
+
+```javascript
+var attr = {
+ 'firstName': 'Rhys',
+ 'lastName': 'Brett-Bowen'
+};
+var options = {
+ 'attr': attr,
+ 'sync': new mvc.AjaxSync()
+};
+```
+
+or
+
+```
+var options = {
+ 'firstName': 'Rhys',
+ 'lastName': 'Brett-Bowen'
+};
+```
+
+in the second example I put the attributes right in the object. I can do this and mix them with 'schema' and 'sync'. Just remember that 'attr', 'sync' and 'schema' are reserved words, 'attr' is a safe place for attributes and 'sync' and 'schema' are reserved.
+
+You might also noticed that I put quotes around the keys in the object. This is necessary as closure would rename the keys after compilation and that means that shema could become 'c' which would be put in as an attribute. I will point out where you need to use quotes and where they are optional as I go on.
+
+### get and set key/values ###
-You should always use the get() and set() functions when dealing with the model's data. These functions take care of saving old data and publishing change events
+You should always use the get() and set() functions when dealing with the model's data. These functions take care of saving old data and publishing change events. To get and set data you can do the below:
-There are also three functions that allow you to manipulate how you can get the data.
+```javascript
+model.get('firstName'); // returns 'Rhys'
+model.set('firstName', 'Bruce');
+model.get('firstName'); //returns 'Bruce'
+```
+
+### customizing get and set ###
+
+You can also manipulate how you get and set the data using these functions by either specifying a schema object (which we'll go in to later) or by using the below functions to modify the model's default schema
The alias function allows you to get a member using a different name
@@ -59,7 +124,7 @@ model.format('now', function(now) {return now.toDateString()});
model.get('now'); // returns the set date as a string
```
-The meta function is passed the new variable name, an array or attributes to base the data off and the formatting function.
+The meta function is passed the new variable name, an array or attributes to base the data off and the formatting function. To do this you need to pass the new attribute name, the required attributes and the function.
```javascript
model.set({
@@ -72,36 +137,87 @@ model.meta('name', ['firstName', 'lastName'], function(firstName, lastName) {
model.get('name'); // returns "Brett-Bowen, Rhys"
```
-the real power in a model comes in binding. You can bind keys, or computed keys to functions or attributes. The bind function takes three arguments. The first is the attribute name or an array of attribute names whose changes you want to listen to. The second is a function which will be passed the value of each of the items you are listening to and the model. If you want to bind it between two models, we give you a helper function:
+### setting and validation ###
+You can also change how you set and validate data by using the setter function. you can do any checks on the data in the function and throw and error if it doesn't pass. The errors are handled by the model's error handling function. By default it just ignores the error and the value isn't set, but you can override it by:
+
+```javascript
+ var errorFn = function(err) {
+ alert(err.getMessage());
+ };
+ model.errorHandler(errorFn);
```
-// married couple living together
-var husband = new mvc.Model({'city':'San Francisco'});
-var wife = new mvc.Model({'city':'San Francisco'});
-husband.bind('city', wife.setBinder('city'));
+
+We can then put in a formatting and validating function for our name
+
+```javascript
+model.setter('firstName', function(name) {
+ name = goog.string.trim(name);
+ if (!name.length)
+ throw new Error("name can not be blank");
+ return name;
+})
```
-The final parameter is the object to bind the function to. So you could do this:
+the above will check to see that a name isn't blank and trim the input.
+
+### Binding & unbinding data ###
+
+the real power in a model comes in binding. You can bind keys, or computed keys to functions. The bind function takes three arguments. The first is the attribute name or an array of attribute names whose changes you want to listen to. The second is a function which will be passed the value of each of the items you are listening to and the model. The final parameter is the object to bind the function to. So you could do this:
```javascript
var task = new mvc.Model({'done':false,'important':false});
var element = document.getElementById('myTask');
-task.bind(['done','important'], function(done, important) {
+var bound = task.bind(['done','important'], function(done, important) {
this.checked = done;
this.setProperty('important', important);
-}, element);
+ }, element);
+// changes to done or important will change the element
+task.unbind(bound);
+//changes will no longer change the element
```
-if you want to listen to any change you can use bindAll and just pass in a function.
+in the example above we saved a uid in bound that we can use to unbind a listener. All bind functions return a uid that can be used with unbind.
+
+### binding keys to a function ###
+
+the model class comes with a function getBinder that can be used to bind a function to a key. For instance you can use it for convenience methods to get and set keys:
+
+```javascript
+var lat = model.getBinder('location:latitude');
+lat(3) // sets the location:latitude to 3
+lat() // returns 3
+```
+
+you can even use this with the bind method above to bind a key with a key from another model
+
+```javascript
+// married couple living together
+var husband = new mvc.Model({'city':'San Francisco'});
+var wife = new mvc.Model({'city':'San Francisco'});
+husband.bind('city', wife.getBinder('city'));
+husband.set('city', 'New York');
+wife.get('city') // returns New York
+```
+
+### listening to all changes ###
+
+if you want to listen to any change you can use bindAll and just pass in a function. and an optional handler.
-The bindAll and bind functions return an id that can be passed to unbind to remove the binding.
+### unload ###
+
+there is also a bindUnload that takes in a function and an optional handler to be fired when the model's dispose() method is called.
+
+### other functions ###
There are also other functions that can take parameters such as the boolean "silent" to suppress change events. The other functions available on a model are:
+
- toJson: used to return a json model that can be used by mvc.Sync or other objects. You should generally override this method with your own implementation
- setSync(mvc.Sync): will set the sync used by the object
- reset(silent): clears the attributes
- isNew: returns a boolean if an id has been set (rather than just a cid)
-- setSchema(mvc.model.Schema): sets the schema
+- setSchema(Obj): sets the schema
+- addSchemaRules(Obj): will extend the schema object with the object passed in
- has(string): whether the model has the key
- unset(string): deletes a key (sets to undefined)
- change: manually fire a change event - handy if you've been using silent for bulk operations
@@ -110,6 +226,9 @@ There are also other functions that can take parameters such as the boolean "sil
- revert: rolls back to the previous attributes
- fetch(callback, silent): updates the model using it's sync
- save: tells the sync to create/update
+- dispose(bool): disposes of the model and all it's listeners. Pass true if you want the model to call the model's sync's delete function
+
+## Schema ##
You can also set a schema for a model. A schema is an object which has a set of keys for which you want getters and setters. Under the key you can put an object with the keys of get, set and require. The get function works much like the meta function above (in fact the meta function is just a nice way to add things to the schema). The function will accept in the values for any attributes you put in the require array. The set function will take the value and should return the data to get set on the model. You can also throw errors to stop the value saving and fire the models handleErr_ function which you should set. an example of a schema:
@@ -135,7 +254,13 @@ the require should be put in for any user defined attributes as the model goes u
## mvc.Collection ##
-A mvc.Collection extends mvc.Model and so has all of it's properties. Also a collection can contain an array of models that belong to it. A collection can keep these models in an order if given a comparator function which works the same as the javascript array.sort function - taking two models and returning -1, 0 or 1 and will also listen to changes and emit a change if any model it contains fires a change event.
+A mvc.Collection extends mvc.Model and so has all of it's properties. Also a collection can contain an array of models that belong to it. A collection can keep these models in an order if given a comparator function.
+
+### Sorting ###
+
+A comparator function works the same as the javascript array.sort function - taking two models and returning -1, 0 or 1 and will also listen to changes and emit a change if any model it contains fires a change event.
+
+### children ###
The mvc.Collection can also take a modelType which works with the newModel method that takes an options map and will create a new model of the type passed in. So you can create new students is a class like this:
@@ -152,6 +277,12 @@ class.addModel({'name':'Fred'});
// class now has a student called fred
```
+### options ###
+
+the options object for a collection is slightly different to that of a model. It has three extra reserved words. 'comparator' which is the comparison function, 'modelType' which is the base model to create when putting in a new child and 'models' which is an array of mvc.Models that should be added to the collection.
+
+### other functions ###
+
A collection also offers these aditional methods:
- pluck([keys]): returns an array of json models with the keys and values of each model
@@ -159,34 +290,135 @@ A collection also offers these aditional methods:
- getLength: number of models contained
- sort(silent): used internally to sort the models
- add(mvc.Model, index, silent): adds a model to the end unless an index is given
-- remove(mvc.Model): removes the model
+- remove(mvc.Model, silent): removes the model
- getById(id): returns a model by it's id
- getModels(Function): returns an array of the models optionally filtered by a function that takes the model and the index and returns true if it should be returned in the filter otherwise false
- at(index): return the model at an index
- clear: clears all the models
+- modelChange(function, handler): like bind for model but is bound only on changes to the collections children being sorted or added/removed
## mvc.Store ##
This can be used as a factory and cache for models. Use the get to retrieve models by their ID and if they don't exist they will be created. This is good to make sure your models are unique. If no ID is passed then a new model will be created and you can get the model using it's CID until an ID is set for the model.
+```javascript
+var store = new mvc.Store();
+var a = store.get(111); // store creates a new object with id 12345
+ // it then calls sync to load it
+var b = store.get(111);
+a == b; //true
+
+a = store.get(); // a is a new model
+a.set('id', 12345)
+b = store.get(12345);
+a == b; // true, store listens to changes on id and sets it when one is set
+```
+
## mvc.Control ##
-the closure library already provides goog.ui.Component which is a great controller. If you use Backbone.js you'll probably recognise it as the view. mvc.Control adds in two methods, delegateEvents and getEls. These are convenience functions. The getEls allows you to use simple string selectors to get a handle for the elements under the component and delegateEvents gives an easier interface for listening to events in the component. If you want to use a different class in the library that already extends goog.ui.Component you can still use these functions by adding them to your classes prototype like so:
+The mvc.Control inherits from goog.ui.Component and so works in a similar way. You create it's DOM in the createDom method and can make changes to it's dom by setting up listeners in the enterDocument method.
+
+### making a new control ###
+
+adding in a control is the same as adding in a goog.ui.component. The only thing is that you have to pass it a model or collection that it is to be associated with. You'll then be able to access it through the controls getModel() method.
```javascript
-goog.require('mvc.Control');
+var model = new mvc.Model();
+var control = new mvc.Control(model);
+control.getModel() == model; // true
+```
+
+### getting a child element ###
+
+a control has the function getEls(selector) which can find elements in it's DOM for you. All you need to do is pass it a valid selector that can be in the form of:
+
+- ".className"
+- "#elementId"
+- "tagName[ .className]"
+
+where the square brackets show that it is optional.
+
+### handling events ###
-// your class and protoype declarations go here
-// you will also want to make sure that you set the model for the component
-// preferably in the constructor with:
-// this.setModel(model);
+handling events are done slightly different to what you may be used to. A control only has one listener per event type and manually decides which handlers to fire. To listen for an event you just need to do this:
-goog.object.extend(myClass.prototype, mvc.Control.prototype);
+```javascript
+control.prototype.enterDocument = function() {
+ goog.base(this, 'enterDocument');
+ this.on(goog.events.EventType.CLICK, function() {
+ alert('clicked');
+ });
+};
```
-to delegate events use the "on" method which takes the event name, function to run, an optional className or array of classnames to check the event target against, an optional handler for the function and an optional priority. The priority could be important if you want to use Event.stopPropagation as the control will run the functions by their priority and then in the order they are registered, rather than their position in the DOM. This give you fine grain control over when to run things. There is also a click method which puts in the click event name for you, and a once method which will only call the function once.
+for clicks there is even a convenience function called .click()
+
+But what if you want to listen for a click on a particular element?
+
+### delegating events ###
-Both the on and the click method pass back a uid that can be passed to "off" to stop the listener.
+You can decide whether or not to fire a handler by either the className of the element (or an array of classNames if it can fire or any of them) or a function that takes the event and returns true if it should be handled. for instance:
+
+```javascript
+this.click(function(){alert('click')}, ["star", "unstar"]);
+```
+
+will fire for any element that has the class star or unstar. If you want to fine tune it more you could use a function:
+
+```javascript
+this.click(function(){alert("click");}, function(e) {return e.clientX<100;});
+```
+
+this will fire for clicks in the left 100px of the screen that are on the control.
+
+### handling priorities ###
+
+Because of all this we need finer control over what should fire first. because of this the .on() method can also take a priority. The default priority is 50 and items with the same priority will be executed in the order they are given. You can change this by giving a lower number, which is a higher priority.
+
+```javascript
+var a = this.click(function(){alert('a')});
+var b = this.click(function(){alert('b')});
+// click will alert a the alert b
+this.off(a);
+this.off(b);
+a = this.click(function(){alert('a')});
+b = this.click(function(){alert('b')}, undefined, this, 40);
+// click will alert b then alert a
+```
+
+above the arguments in the last click call are the function, the decider function/classname filter (undefined for no filter so fire on all clicks), the optional handler to bind the function to (it binds to this by default, but putting in this is shorted than undefined) and then the last parameter is the priority which should be a number.
+
+### unhandling ###
+
+you can use the off method like I have above to unbind any handlers just like unbind in models.
+
+### handling notes ###
+
+you can create other convenience functions like click easily using the goog.bind. All you need to do is this:
+
+```javascript
+ this.mouseOver = goog.bind(this.on, this, goog.events.EventType.MOUSE_OVER);
+```
+
+you can also handle an event only once by using the .once() method
+
+### binding to a model ###
+
+The best place to setup bindings is in the controls enterDocument method. for instance:
+
+```javascript
+this.click(function(e) {
+ this.getModel().set('star', !this.getModel().get('star'));
+ }, "starButton");
+this.getModel().bind('star', function(star) {
+ if(star)
+ goog.dom.classes.add(this.getEls(".starButton")[0], 'star');
+ else
+ goog.dom.classes.remove(this.getEls(".starButton")[0], 'star');
+}, this);
+```
+
+now clicking on the star button will change the star attribute and that will in turn change the class on the star Button. There are lots of ways to match up the bindings.
## mvc.Sync ##
@@ -240,6 +472,13 @@ you can then register your object with the mediator and the messages that you ma
### changelog ###
+### v1.0a ###
+
+- alpha release
+- put tests in their own folder (they'll be broken - that's why this is alpha)
+- redo the README
+- new name
+
#### v0.9 ####
- reworked bind for performance
@@ -249,6 +488,7 @@ you can then register your object with the mediator and the messages that you ma
- files pass closure linter in strict mode
- can unbind listeners on collection model change
- getBinder reworked so can use as getter and setters
+- control.on accepts a function as well as classname to define see if a handler should run
#### v0.8 ####

0 comments on commit ecb0a73

Please sign in to comment.
Something went wrong with that request. Please try again.