diff --git a/README b/README index 19b1dfba..f5535b09 100644 --- a/README +++ b/README @@ -22,3 +22,4 @@ A. How to get (and contribute) JMVC 5. Make changes in steal or jquerymx, and push them back to your fork. 6. Make a pull request to your fork. + diff --git a/class/class.js b/class/class.js index 18854c74..b48d1d00 100644 --- a/class/class.js +++ b/class/class.js @@ -61,7 +61,7 @@ steal("jquery","jquery/lang/string",function( $ ) { * @test jquery/class/qunit.html * @description Easy inheritance in JavaScript. * - * Class provides simulated inheritance in JavaScript. Use clss to bridge the gap between + * Class provides simulated inheritance in JavaScript. Use Class to bridge the gap between * jQuery's functional programming style and Object Oriented Programming. It * is based off John Resig's [http://ejohn.org/blog/simple-javascript-inheritance/|Simple Class] * Inheritance library. Besides prototypal inheritance, it includes a few important features: @@ -349,7 +349,7 @@ steal("jquery","jquery/lang/string",function( $ ) { clss = $.Class = function() { if (arguments.length) { - clss.extend.apply(clss, arguments); + return clss.extend.apply(clss, arguments); } }; diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js index d2355033..672b5a7b 100644 --- a/dom/fixture/fixture.js +++ b/dom/fixture/fixture.js @@ -35,7 +35,10 @@ steal('jquery/dom', var url = settings.fixture; if (/^\/\//.test(url) ) { - url = steal.root.mapJoin(settings.fixture.substr(2))+''; + var sub = settings.fixture.substr(2) + ''; + url = typeof steal === "undefined" ? + url = "/" + sub : + steal.root.mapJoin(sub) +''; } //!steal-remove-start steal.dev.log("looking for fixture in " + url); diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index 69f56ac2..0234c4d4 100644 --- a/dom/form_params/form_params.js +++ b/dom/form_params/form_params.js @@ -2,22 +2,58 @@ * @add jQuery.fn */ steal("jquery/dom").then(function( $ ) { - var radioCheck = /radio|checkbox/i, - keyBreaker = /[^\[\]]+/g, - numberMatcher = /^[\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?$/; + var keyBreaker = /[^\[\]]+/g, + convertValue = function( value ) { + if ( $.isNumeric( value )) { + return parseFloat( value ); + } else if ( value === 'true') { + return true; + } else if ( value === 'false' ) { + return false; + } else if ( value === '' ) { + return undefined; + } + return value; + }, + nestData = function( elem, type, data, parts, value, seen ) { + var name = parts.shift(); + + if ( parts.length ) { + if ( ! data[ name ] ) { + data[ name ] = {}; + } + // Recursive call + nestData( elem, type, data[ name ], parts, value, seen ); + } else { + + // Handle same name case, as well as "last checkbox checked" + // case + if ( name in seen && type != "radio" && ! $.isArray( data[ name ] )) { + if ( name in data ) { + data[ name ] = [ data[name] ]; + } else { + data[ name ] = []; + } + } else { + seen[ name ] = true; + } - var isNumber = function( value ) { - if ( typeof value == 'number' ) { - return true; - } + // Finally, assign data + if ( ( type == "radio" || type == "checkbox" ) && ! elem.is(":checked") ) { + return + } - if ( typeof value != 'string' ) { - return false; - } + if ( ! data[ name ] ) { + data[ name ] = value; + } else { + data[ name ].push( value ); + } + - return value.match(numberMatcher); - }; + } + }; + $.fn.extend({ /** * @parent dom @@ -53,7 +89,9 @@ steal("jquery/dom").then(function( $ ) { * to the result. Defaults to false. * @return {Object} An object of name-value pairs. */ - formParams: function( params, convert ) { + formParams: function( params ) { + + var convert; // Quick way to determine if something is a boolean if ( !! params === params ) { @@ -63,10 +101,9 @@ steal("jquery/dom").then(function( $ ) { if ( params ) { return this.setParams( params ); - } else if ( this[0].nodeName.toLowerCase() == 'form' && this[0].elements ) { - return jQuery(jQuery.makeArray(this[0].elements)).getParams(convert); + } else { + return this.getParams( convert ); } - return jQuery("input[name], textarea[name], select[name]", this[0]).getParams(convert); }, setParams: function( params ) { @@ -100,63 +137,41 @@ steal("jquery/dom").then(function( $ ) { }, getParams: function( convert ) { var data = {}, + // This is used to keep track of the checkbox names that we've + // already seen, so we know that we should return an array if + // we see it multiple times. Fixes last checkbox checked bug. + seen = {}, current; - convert = convert === undefined ? false : convert; - this.each(function() { - var el = this, - type = el.type && el.type.toLowerCase(); - //if we are submit, ignore - if ((type == 'submit') || !el.name ) { + this.find("[name]").each(function() { + var $this = $(this), + type = $this.attr("type"), + name = $this.attr("name"), + value = $this.val(), + parts; + + // Don't accumulate submit buttons and nameless elements + if ( type == "submit" || ! name ) { return; } - var key = el.name, - value = $.data(el, "value") || $.fn.val.call([el]), - isRadioCheck = radioCheck.test(el.type), - parts = key.match(keyBreaker), - write = !isRadioCheck || !! el.checked, - //make an array of values - lastPart; - - if ( convert ) { - if ( isNumber(value) ) { - value = parseFloat(value); - } else if ( value === 'true') { - value = true; - } else if ( value === 'false' ) { - value = false; - } - if(value === '') { - value = undefined; - } + // Figure out name parts + parts = name.match( keyBreaker ); + if ( ! parts.length ) { + parts = [name]; } - // go through and create nested objects - current = data; - for ( var i = 0; i < parts.length - 1; i++ ) { - if (!current[parts[i]] ) { - current[parts[i]] = {}; - } - current = current[parts[i]]; + // Convert the value + if ( convert ) { + value = convertValue( value ); } - lastPart = parts[parts.length - 1]; - - //now we are on the last part, set the value - if (current[lastPart]) { - if (!$.isArray(current[lastPart]) ) { - current[lastPart] = current[lastPart] === undefined ? [] : [current[lastPart]]; - } - if ( write ) { - current[lastPart].push(value); - } - } else if ( write || !current[lastPart] ) { - current[lastPart] = write ? value : undefined; - } + // Assign data recursively + nestData( $this, type, data, parts, value, seen ); }); + return data; } }); diff --git a/dom/form_params/form_params_test.js b/dom/form_params/form_params_test.js index 93d50fd1..2a9e2f3c 100644 --- a/dom/form_params/form_params_test.js +++ b/dom/form_params/form_params_test.js @@ -10,6 +10,7 @@ module("jquery/dom/form_params") test("with a form", function(){ $("#qunit-test-area").html("//jquery/dom/form_params/test/basics.micro",{}) + var formParams = $("#qunit-test-area form").formParams() ; ok(formParams.params.one === "1","one is right"); @@ -17,12 +18,26 @@ test("with a form", function(){ ok(formParams.params.three === "3","three is right"); same(formParams.params.four,["4","1"],"four is right"); same(formParams.params.five,["2","3"],"five is right"); - equal(typeof formParams.id , 'string', "Id value is empty"); + equal( typeof formParams.singleRadio, "string", "Type of single named radio is string" ); + equal( formParams.singleRadio, "2", "Value of single named radio is right" ); + + ok( $.isArray(formParams.lastOneChecked), "Type of checkbox with last option checked is array" ); + equal( formParams.lastOneChecked, "4", "Value of checkbox with the last option checked is 4" ); }); +test("With a non-form element", function() { + + $("#qunit-test-area").html("//jquery/dom/form_params/test/non-form.micro",{}) + + var formParams = $("#divform").formParams() ; + + equal( formParams.id , "foo-bar-baz", "ID input read correctly" ); + +}); + test("with true false", function(){ $("#qunit-test-area").html("//jquery/dom/form_params/test/truthy.micro",{}); diff --git a/dom/form_params/test/basics.micro b/dom/form_params/test/basics.micro index dfc247ff..14e76957 100644 --- a/dom/form_params/test/basics.micro +++ b/dom/form_params/test/basics.micro @@ -11,6 +11,10 @@ + + + + + + + + + + diff --git a/dom/form_params/test/non-form.micro b/dom/form_params/test/non-form.micro new file mode 100644 index 00000000..aa81d984 --- /dev/null +++ b/dom/form_params/test/non-form.micro @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/event/drop/drop.html b/event/drop/drop.html index 5d4af6c6..439f11f1 100644 --- a/event/drop/drop.html +++ b/event/drop/drop.html @@ -41,33 +41,35 @@

Dropmove/Dropon

Drop Count 0 + \ No newline at end of file diff --git a/event/hover/hover.html b/event/hover/hover.html index 29f2fdaa..d98a8894 100644 --- a/event/hover/hover.html +++ b/event/hover/hover.html @@ -32,11 +32,12 @@

HoverLeave

Leave and don't return for a half second
+ src='../../../steal/steal.js'> \ No newline at end of file diff --git a/event/tap/tap.js b/event/tap/tap.js index 91228123..356cfd30 100644 --- a/event/tap/tap.js +++ b/event/tap/tap.js @@ -32,7 +32,7 @@ $.event.setupHelper( ["tap"], touchStartEvent, function(ev){ function upHandler(event){ stop = data(event); - if ((Math.abs( start.coords[0] - stop.coords[0] ) < 10) || + if ((Math.abs( start.coords[0] - stop.coords[0] ) < 10) && ( Math.abs( start.coords[1] - stop.coords[1] ) < 10) ){ $.each($.event.find(delegate, ["tap"], selector), function(){ this.call(entered, ev, {start : start, end: stop}) diff --git a/lang/observe/delegate/delegate.js b/lang/observe/delegate/delegate.js index 583802b1..385ac26e 100644 --- a/lang/observe/delegate/delegate.js +++ b/lang/observe/delegate/delegate.js @@ -51,26 +51,23 @@ steal('jquery/lang/observe',function(){ delegate = function(event, prop, how, newVal, oldVal){ // pre-split properties to save some regexp time var props = prop.split("."), - delegates = $.data(this,"_observe_delegates") || [], + delegates = ($.data(this,"_observe_delegates") || []).slice(0), delegate, - len = delegates.length, attr, matchedAttr, hasMatch, valuesEqual; event.attr = prop; event.lastAttr = props[props.length -1 ]; - // for each delegate - for(var i =0; i < len; i++){ + for(var i =0; delegate = delegates[i++];){ - delegate = delegates[i]; // if there is a batchNum, this means that this // event is part of a series of events caused by a single // attrs call. We don't want to issue the same event // multiple times // setting the batchNum happens later - if(event.batchNum && delegate.batchNum === event.batchNum){ + if((event.batchNum && delegate.batchNum === event.batchNum) || delegate.undelegated ){ continue; } @@ -294,6 +291,7 @@ steal('jquery/lang/observe',function(){ delegateOb = delegates[i]; if( delegateOb.callback === cb || (!cb && delegateOb.selector === selector) ){ + delegateOb.undelegated = true; delegates.splice(i,1) } else { i++; diff --git a/lang/observe/delegate/delegate_test.js b/lang/observe/delegate/delegate_test.js index ec61a9c4..c938e285 100644 --- a/lang/observe/delegate/delegate_test.js +++ b/lang/observe/delegate/delegate_test.js @@ -221,4 +221,33 @@ test("compound sets", function(){ equals(count, 3, "setting person does not fire anything"); }) +test("undelegate within event loop",1, function(){ + + var state = new $.Observe({ + type : "person", + id: "5" + }); + var f1 = function(){ + state.undelegate("type","add",f2); + }, + f2 = function(){ + ok(false,"I am removed, how am I called") + }, + f3 = function(){ + state.undelegate("type","add",f1); + }, + f4 = function(){ + ok(true,"f4 called") + }; + state.delegate("type", "set", f1); + state.delegate("type","set",f2); + state.delegate("type","set",f3); + state.delegate("type","set",f4); + state.attr("type","other"); + +}) + + + + }); \ No newline at end of file diff --git a/model/demo-dom.html b/model/demo-dom.html index 8f818ee0..f0958e6d 100644 --- a/model/demo-dom.html +++ b/model/demo-dom.html @@ -25,9 +25,12 @@

Model DOM Helpers Demo

steal('jquery/model','jquery/dom/fixture').then(function(){ $.fixture("GET /contacts.json", function(){ - return [[{id: 1, name: 'Justin Meyer', birthday: 403938000000}, - {id: 2, name: 'Brian Moschel', birthday: 437205600000}, - {id: 3, name: 'Mihael Konjevic', birthday: 483771600000}]]; + return [[{id: 1, name: 'Justin Meyer', + birthday: 403938000000}, + {id: 2, name: 'Brian Moschel', + birthday: 437205600000}, + {id: 3, name: 'Mihael Konjevic', + birthday: 483771600000}]]; }); $.fixture("DELETE /contacts.json", function(){ diff --git a/model/demo-setter.html b/model/demo-setter.html index 3fca9a5d..6d0d58e8 100644 --- a/model/demo-setter.html +++ b/model/demo-setter.html @@ -30,9 +30,12 @@

Model Setter Demo

function(){ $.fixture("/contacts.json", function(orig, settings, headers){ - return [[{'id': 1,'name' : 'Justin Meyer','birthday': '1982-10-20'}, - {'id': 2,'name' : 'Brian Moschel','birthday': '1983-11-10'}, - {'id': 3,'name' : 'Alex Gomes','birthday': '1980-2-10'}]]; + return [[{'id': 1,'name' : 'Justin Meyer', + 'birthday': '1982-10-20'}, + {'id': 2,'name' : 'Brian Moschel', + 'birthday': '1983-11-10'}, + {'id': 3,'name' : 'Alex Gomes', + 'birthday': '1980-2-10'}]]; }); // A contact model diff --git a/model/list/list.js b/model/list/list.js index 42ce9acf..6996b8db 100644 --- a/model/list/list.js +++ b/model/list/list.js @@ -760,6 +760,14 @@ steal('jquery/model').then(function( $ ) { * */ splice: [].splice, + /** + * @function indexOf + * Finds the index of the item in the list. Returns -1 if not found. + * + * list.indexOf(item) + * + */ + indexOf: [].indexOf, /** * @function sort * Sorts the instances in the list. diff --git a/model/list/local/local.js b/model/list/local/local.js index 568a63cb..eb9bfb0f 100644 --- a/model/list/local/local.js +++ b/model/list/local/local.js @@ -26,7 +26,7 @@ $.Model.List("jQuery.Model.List.Local", // go through and listen to instance updating var ids = [], days = this.days; this.each(function(i, inst){ - window.localStorage[inst.identity()] = instance.attrs(); + window.localStorage[inst.identity()] = inst.attrs(); ids.push(inst.identity()); }); window.localStorage[name] = { diff --git a/model/modelBinder.html b/model/modelBinder.html index 5da061ba..cba4a743 100644 --- a/model/modelBinder.html +++ b/model/modelBinder.html @@ -46,7 +46,7 @@ -steal('jquery/model','mxui/slider').then(function(){ +steal('jquery/model','mxui/nav/slider').then(function(){ $.fn.hookup = function(inst, attr, type){ @@ -79,9 +79,9 @@ var person = new Person({age: 1}) - $("#slider").mxui_slider({interval: 1, min: 1, max: 10}) + $("#slider").mxui_nav_slider({interval: 1, min: 1, max: 10}) - .hookup(person,"age","mxui_slider"); + .hookup(person,"age","mxui_nav_slider"); $('#value, #foo, #bar').hookup(person,"age"); diff --git a/model/store/store.js b/model/store/store.js index cfc4f078..ab73370a 100644 --- a/model/store/store.js +++ b/model/store/store.js @@ -1,30 +1,74 @@ -steal('jquery/model/list','jquery/lang/object',function($){ +steal('jquery/model/list','jquery/lang/object', function($){ -var same = $.Object.same; +var same = $.Object.same, + trigger = function(obj, event, args){ + $.event.trigger(event, args, obj, true) + }, + $method = function( name ) { + return function( eventType, handler ) { + return $.fn[name].apply($([this]), arguments); + } + }, + bind = $method('bind'), + unbind = $method('unbind'); $.Class('jQuery.Model.Store', { + id: "id", + bind: bind, + unbind: unbind, + compare : {}, + init : function(){ if(this.fullName === 'jQuery.Model.Store'){ return; } - /** - * which sets are represented in this store ... - */ + this.sets = []; this.data = {}; - // listen on create and add ... listen on destroy and remove - this.namespace.bind('destroyed', this.callback('remove')) - this.namespace.bind('updated', this.callback('updated')) + // listen on create and add ... listen on destroy and remove + this.namespace.bind('destroyed', this.proxy('destroyed')); + this.namespace.bind('updated', this.proxy('updated')); + this.namespace.bind("created", this.proxy('created')); }, - updated : function(ev, item){ + + /** + * Internal compare method. + * + * @param {Object} prop + * @param {Object} itemData + * @param {Object} paramData + */ + _compare : function(prop, itemData, paramData){ + return same(itemData, paramData, this.compare[prop]); + }, + + /** + * Creates an item in the sets. Triggered from a model + * event indicating an item was created. + * + * @param {Object} event + * @param {Object} item + */ + created: function(ev,item){ + this.add([item]); + }, + + /** + * Updates an item in the sets. Triggered from a model + * event indicating an item was updated. + * + * @param {Object} event + * @param {Object} item + */ + updated: function(ev, item){ // go through lists and remove this guy if he is in the list and should not be ... var sets = this.sets.slice(0), report = ["Store - updating "]; - for(var i=0; i < sets.length; i++){ + for(var i =0, length = this.sets.length; i < length; i++){ var set = sets[i], inSet = this.filter(item, set.params) !== false, inList = set.list.get(item)[0]; @@ -37,52 +81,73 @@ $.Class('jQuery.Model.Store', set.list.remove(item.id) } } - /*if(report.length > 1) { - console.log.apply(console, report); - } else { - console.log("Store - Updated, but no changes") - }*/ }, - // this is mostly unnecessary - remove : function(ev,id){ + + /** + * Destroy triggered by model event. + * Calls remove function to remove item from lists. + * + * @param {Object} event + * @param {Object} id + */ + destroyed : function(ev,id){ + this.remove(id); + }, + + /** + * @function remove + * + * Removes an item from the sets. + * + * @param {Object} id + */ + remove:function(id){ var idProp = this.id; + if(id[idProp] !== undefined){ id = id[idProp]; } + var item = this.data[id]; + if(!item){ return; } - // need to unbind? Of course lists should cause this to happen + delete this.data[id]; - // go through sets ... + }, + + /** + * @function removeSet + * + * Removes a set given a parms object and + * removes each one of the items from the data. + * + * @param {Object} params + */ + removeSet: function(params){ + var matchIdx; - /*var sets = this.sets.slice(0), - report = ["Store - removing from "]; - for(var i=0; i < sets.length; i++){ - var set = sets[i], - removed; - - if(set.list){ - removed = set.list.remove(item) - } - - if(removed.length) { - report.push(set.params, "; "); + $.each(this.sets, this.proxy(function(i,set){ + if($.Object.same(params, set.params, this.compare)){ + set.list.each(this.proxy(function(i,item){ + delete this.data[item[this.id]]; + })); + matchIdx = i; + return false; } - } - if(report.length > 1) { - console.log.apply(console, report); - } else { - console.log("Store - Items to remove, but no matches") - }*/ + })); + + matchIdx != undefined && this.sets.splice(matchIdx, 1); }, - id: "id", + /** - * Adds items ... this essentially creates / updates them ... - * or looks - * @param {Array} items - * @param {Object} [params] will only add to matching sets + * @function add + * + * Adds items to the set(s) given the matching params. + * + * @param {Object} items + * @param {Object} params */ add : function(items, params){ // need to check the filter rules, if we can even add this ... @@ -93,71 +158,80 @@ $.Class('jQuery.Model.Store', idProp = this.id, id, added = []; + for(; i< len; i++){ - item = items[i] - id = item[idProp] + item = items[i]; + id = item[idProp]; + if( this.data[id] ){ + // if there is something there ... take care of it .. this.update(this.data[id], item); + + // if the item already exists from say a 'findOne' call + // the item will already exist in 'data' but not the 'list' + added.push(item) } else { - added.push(this.data[id] = this.create(item)) - } - + added.push(this.data[id] = item); + } } + // go through sets and add to them ... // slice so that if in callback, the number of sets increases, you are ok var sets = this.sets.slice(0), report = ["Store - adding "]; - for(var i=0; i < sets.length; i++){ + + for(var i=0, iLength = sets.length; i < iLength; i++){ var set = sets[i], itemsForSet = []; - for(var j =0; j< added.length; j++){ + for(var j =0, jLength = added.length; j< jLength; j++){ item = added[j] if( this.filter(item, set.params) !== false) { itemsForSet.push(item) } } + if(itemsForSet.length) { report.push(itemsForSet.length,"to", set.params, "; "); set.list.push(itemsForSet); } } - - /*if(report.length > 1) { - console.log.apply(console, report); - } else { - console.log("Store - Got new items, but no matches") - }*/ - - // check if item would be added to set - - // make sure item isn't already in set? }, + /** - * updates the properties of currentItem + * @function update + * + * Updates the properties of currentItem + * + * @param {Object} currentItem + * @param {Object} newItem */ update : function(currentItem, newItem){ currentItem.attrs(newItem.serialize()); }, + /** + * @function sort + * + * Returns if a set contains the parameters. * - * @param {Object} newItem - */ - create : function(newItem){ - return newItem; - }, + * @param {Object} params + **/ has : function(params){ // check if it has an evil param ... - return $.Object.subsets(params, this.sets).length }, + /** + * @function filter + * * Called with the item and the current params. * Should return __false__ if the item should be filtered out of the result. * * By default this goes through each param in params and see if it matches the * same property in item (if item has the property defined). + * * @param {Object} item * @param {Object} params */ @@ -170,20 +244,24 @@ $.Class('jQuery.Model.Store', // in fixtures we ignore null, I don't want to now if ( paramValue !== undefined && item[param] !== undefined - && !this._compare(param, item[param] ,paramValue) ) { + && !this._compare(param, item[param], paramValue) ) { return false; } } }, - compare : {}, - _compare : function(prop, itemData, paramData){ - return same(itemData, paramData, this.compare[prop]) //this.compare[prop] ? this.compare[prop](itemData, paramData) : itemData == paramData; - }, + /** - * Sorts the object in place - * - * By default uses an order property in the param + * @function sort + * + * Sorts the object in place. By default uses an order + * property in the param of the class. + * + * @codestart + * var models = $.Model.Store.sort(myModelListInstance); + * @codeend + * * @param {Object} items + * @param {Object} params */ sort : function(items, params){ $.each((params.order || []).slice(0).reverse(), function( i, name ) { @@ -211,41 +289,136 @@ $.Class('jQuery.Model.Store', }); return items }, + + /** + * @function pagination + * + * Paginates the item in place. By default uses an order + * property in the param of the class. + * + * @codestart + * var models = $.Model.Store.pagination(myModelListInstance); + * @codeend + * + * @param {Object} items + * @param {Object} params + */ pagination : function(items, params){ var offset = parseInt(params.offset, 10) || 0, limit = parseInt(params.limit, 10) || (items.length - offset); return items.slice(offset, offset + limit); }, + + /** + * @function get + * + * Retrieves an item(s) given an id or array of ids from the global data + * set of the model store. If the item is not returned yet, it will return + * the deffered. + * + * @codestart + * var model = $.Model.Store.get(222); + * @codeend + * + * @codestart + * var models = $.Model.Store.get([222, 223, 224]); + * @codeend + * + * @param {Object} id int or array of ints + */ get : function(id){ - return this.data[id]; + if($.isArray(id)) { + var returnArr = []; + + $.each(id, this.proxy(function(i,d){ + var m = this.data[d]; + m && returnArr.push(m); + })); + + return returnArr; + } else { + return this.data[id]; + } }, + + /** + * @function findOne + * + * FindOne attempts to retrieve an individual model + * from the sets of currently fetched data. If the model + * was not previously fetched, it will then execute a request on the + * static 'findOne' method of the model. It returns + * the deffered object. + * + * @codestart + * $.Model.Store.findOne(222).done(success); + * @codeend + * + * + * You can listen for 'findOne' to be triggered by + * binding to the 'findOne' event on the class. + * + * @codestart + * $.Model.Store.bind('findOne', function(id){ ... }); + * @codeend + * + * + * @param {Object} id of item + * @param {Function} success handler + * @param {Function} error handler + **/ findOne : function(id, success, error){ - //console.log("findOne ", id) - if(this.data[id]){ - // check if it is a deferred or not - if(this.data[id].isRejected){ - return this.data[id] + var data = this.data[id], + def; + + if(data){ + if(data.isRejected){ + return data; } else { - var def = $.Deferred() - def.resolve(this.data[id]) + def = $.Deferred(); + def.resolve(data); } } else { - var def = this.namespace.findOne({id: id}), - self = this; - def.done(function(item){ - self[id] = item; - }) + this.data[id] = def = this.namespace.findOne({ id: id }); + + def.done(this.proxy(function(item){ + this.data[id] = item; + })); } - def.done(success) + + def.done(success); + trigger(this, 'findOne', id); + return def; }, + /** - * Returns a list that interacts with the store + * @function findAll + * + * FindAll attempts to retrieve a list of model(s) + * from the sets of currently fetched data. If the model(s) + * were not previously fetched, it will then execute a request on the + * static 'findAll' method of the model. It returns + * the deffered object. + * + * @codestart + * $.Model.Store.findAll({ parentId: 2222 }).done(success); + * @codeend + * + * + * You can listen for 'findAll' to be triggered by + * binding to the 'findAll' event on the class. + * + * @codestart + * $.Model.Store.bind('findAll', function(params){ ... }); + * @codeend + * + * * @param {Object} params - * @param {Boolean} register registers this list as owning some content, but does not - * actually do the request ... - */ + * @param {Boolean} register registers this list as owning some content, but does not + * @param {Boolean} ready + **/ findAll : function(params, register, ready){ // find the first set that is the same // or is a subset with a def @@ -262,40 +435,34 @@ $.Class('jQuery.Model.Store', } ready = ready || function(){}; - for(var i =0; i < this.sets.length; i++){ + for(var i =0, length = this.sets.length; i < length; i++){ var set = this.sets[i]; - if( $.Object.subset(params, set.params, this.compare) ){ + if( $.Object.subset(params, set.params, this.compare) ){ parentLoadedSet = set; - //console.log($.Object.same( set.params, params), set.params, params ); - if( $.Object.same( set.params, params, this.compare) ){ - + + if( $.Object.same(set.params, params, this.compare) ){ // what if it's not loaded if(!set.def){ - //console.log("Store - a listening list, but not loaded", params, ready); var def = this.namespace.findAll(params); set.def = def; def.done(function(items){ - //console.log("adding items from findALL", params, items.length) list = items; self.add(items, params) - cb();; + cb && cb(); }) } else { - //console.log("Store - already loaded exact match",params, ready); list = set.list; if(set.def.isResolved()){ setTimeout(cb, 1); } else { set.def.done(cb); } - //ready && ready(set.list); } return set.list; } } } - // create a list, a set and add the set to our list of sets list = new this.namespace.List(); @@ -306,7 +473,6 @@ $.Class('jQuery.Model.Store', this.sets.push(sameSet); - // we have loaded or are loading what we need if( parentLoadedSet ) { // find the first set with a deferred @@ -316,58 +482,44 @@ $.Class('jQuery.Model.Store', } else if( parentLoadedSet.def.isResolved() ){ // add right away - //console.log("Store - already loaded parent set",params); var items = self.findAllCached(params); - //list.reset(); list.push(items); setTimeout(cb, 1);; } else { // this will be filled when add is called ... parentLoadedSet.def.done(function(){ - //console.log("Store - already loading parent set, waiting for it to return",params, ready); var items = self.findAllCached(params); - //list.reset(); list.push(items); - cb(); + cb && cb(); }) } } else { - if( register ) { - // do nothing ... - - - } else { + if( !register ) { // we need to load it - //console.log("Store - loading data for the first time", params, ready); - var def = this.namespace.findAll(params); - sameSet.def = def; + var def = sameSet.def = this.namespace.findAll(params); def.done(function(items){ self.add(items, params); - cb();//ready && ready(sameSet.list); - }) - + cb && cb(); + }); } - } + trigger(this, 'findAll', params); - - // wait until the items are loaded, do the reset and pushing ... - - // check later if no one is listening ... - setTimeout(function(){ - //console.log('unbinding ...?') - /*if(!list.bound){ - this.sets = $.grep(this.sets, function(set){ set !== sameSet}); - // we need to remove these items too ... (unless we are a superset) - }*/ - },10); return list; - }, + + /** + * @function findAllCached + * + * FindAll attempts to retrieve a list of model(s) + * only from the cache. + * + * @param {Object} params + **/ findAllCached : function(params){ // remove anything not filtering .... // - sorting, grouping, limit, and offset @@ -375,6 +527,7 @@ $.Class('jQuery.Model.Store', var list = [], data = this.data, item; + for(var id in data){ item = data[id]; if( this.filter(item, params) !== false) { @@ -384,10 +537,10 @@ $.Class('jQuery.Model.Store', // do sorting / grouping list = this.pagination(this.sort(list, params), params); + // take limit and offset ... return list; } -},{}); - +},{ }); -}); +}); \ No newline at end of file diff --git a/model/test/qunit/model_test.js b/model/test/qunit/model_test.js index a9077d93..0719d4e3 100644 --- a/model/test/qunit/model_test.js +++ b/model/test/qunit/model_test.js @@ -422,7 +422,8 @@ test("converters and serializes", function(){ } },{}); var d = new Date(); - d.setMonth(1) + d.setDate(1); + d.setMonth(1); var task1=new Task1({ createdAt: d, name:"Task1" diff --git a/view/ejs/ejs.js b/view/ejs/ejs.js index 5805f71d..e5132159 100644 --- a/view/ejs/ejs.js +++ b/view/ejs/ejs.js @@ -528,7 +528,7 @@ steal('jquery/view', 'jquery/lang/string/rsplit').then(function( $ ) { out: 'try { with(_VIEW) { with (_CONTEXT) {' + template + " "+finishTxt+"}}}catch(e){e.lineNumber=null;throw e;}" }; //use eval instead of creating a function, b/c it is easier to debug - myEval.call(out, 'this.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL=' + name + ".js"); + myEval.call(out, 'this.fn = (function(_CONTEXT,_VIEW){' + out.out + '});\r\n//@ sourceURL="' + name + '.js"'); return out; };