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 22a0e89a..b48d1d00 100644 --- a/class/class.js +++ b/class/class.js @@ -2,7 +2,7 @@ // This is a modified version of John Resig's class // http://ejohn.org/blog/simple-javascript-inheritance/ // It provides class level inheritance and callbacks. -//@steal-clean +//!steal-clean steal("jquery","jquery/lang/string",function( $ ) { // =============== HELPERS ================= @@ -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); } }; @@ -425,13 +425,13 @@ steal("jquery","jquery/lang/string",function( $ ) { // keep a reference to us in self self = this; - //@steal-remove-start + //!steal-remove-start for( var i =0; i< funcs.length;i++ ) { if(typeof funcs[i] == "string" && !isFunction(this[funcs[i]])){ throw ("class.js "+( this.fullName || this.Class.fullName)+" does not have a "+funcs[i]+"method!"); } } - //@steal-remove-end + //!steal-remove-end return function class_cb() { // add the arguments after the curried args var cur = concatArgs(args, arguments), @@ -612,14 +612,14 @@ steal("jquery","jquery/lang/string",function( $ ) { current = getObject(parts.join('.'), window, true), namespace = current; - //@steal-remove-start + //!steal-remove-start if (!Class.nameOk ) { //steal.dev.isHappyName(fullName) } if(current[shortName]){ steal.dev.warn("class.js There's already something called "+fullName) } - //@steal-remove-end + //!steal-remove-end current[shortName] = Class; } @@ -782,4 +782,4 @@ steal("jquery","jquery/lang/string",function( $ ) { proxy = clss.proxy; -})(); \ No newline at end of file +})(); diff --git a/controller/controller.js b/controller/controller.js index fbf40739..f3bd8476 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -394,11 +394,11 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( } // make sure listensTo is an array - //@steal-remove-start + //!steal-remove-start if (!isArray(this.listensTo) ) { throw "listensTo is not an array in " + this.fullName; } - //@steal-remove-end + //!steal-remove-end // calculate and cache actions this.actions = {}; @@ -1086,4 +1086,4 @@ steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( }); -}); \ No newline at end of file +}); diff --git a/controller/view/view.js b/controller/view/view.js index 5c05345f..c248fa5c 100644 --- a/controller/view/view.js +++ b/controller/view/view.js @@ -1,4 +1,6 @@ steal('jquery/controller', 'jquery/view').then(function( $ ) { + var URI = steal.URI || steal.File; + jQuery.Controller.getFolder = function() { return jQuery.String.underscore(this.fullName.replace(/\./g, "/")).replace("/Controllers", ""); }; @@ -19,10 +21,10 @@ steal('jquery/controller', 'jquery/view').then(function( $ ) { if ( typeof view == "string" ) { if ( view.substr(0, 2) == "//" ) { //leave where it is } else { - view = "//" + new steal.File('views/' + (view.indexOf('/') !== -1 ? view : (hasControllers ? controller_name + '/' : "") + view)).joinFrom(path) + suffix; + view = "//" + URI(path).join( 'views/' + (view.indexOf('/') !== -1 ? view : (hasControllers ? controller_name + '/' : "") + view)) + suffix; } } else if (!view ) { - view = "//" + new steal.File('views/' + (hasControllers ? controller_name + '/' : "") + action_name.replace(/\.|#/g, '').replace(/ /g, '_')).joinFrom(path) + suffix; + view = "//" + URI(path).join('views/' + (hasControllers ? controller_name + '/' : "") + action_name.replace(/\.|#/g, '').replace(/ /g, '_'))+ suffix; } return view; }; @@ -45,12 +47,14 @@ steal('jquery/controller', 'jquery/view').then(function( $ ) { var current = window; var parts = this.constructor.fullName.split(/\./); for ( var i = 0; i < parts.length; i++ ) { - if ( typeof current.Helpers == 'object' ) { - jQuery.extend(helpers, current.Helpers); + if(current){ + if ( typeof current.Helpers == 'object' ) { + jQuery.extend(helpers, current.Helpers); + } + current = current[parts[i]]; } - current = current[parts[i]]; } - if ( typeof current.Helpers == 'object' ) { + if (current && typeof current.Helpers == 'object' ) { jQuery.extend(helpers, current.Helpers); } this._default_helpers = helpers; diff --git a/dom/fixture/fixture.js b/dom/fixture/fixture.js index f2a020dc..672b5a7b 100644 --- a/dom/fixture/fixture.js +++ b/dom/fixture/fixture.js @@ -35,11 +35,14 @@ 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-remove-start steal.dev.log("looking for fixture in " + url); - //@steal-remove-end + //!steal-remove-end settings.url = url; settings.data = null; settings.type = "GET"; @@ -50,9 +53,9 @@ steal('jquery/dom', } }else { - //@steal-remove-start + //!steal-remove-start steal.dev.log("using a dynamic fixture for " +settings.type+" "+ settings.url); - //@steal-remove-end + //!steal-remove-end //it's a function ... add the fixture datatype so our fixture transport handles it // TODO: make everything go here for timing and other fun stuff @@ -898,4 +901,6 @@ steal('jquery/dom', * } * */ -}); \ No newline at end of file + //Expose this for fixture debugging + $.fixture.overwrites = overwrites; +}); diff --git a/dom/fixture/fixture_test.js b/dom/fixture/fixture_test.js index 55320696..7559d1ba 100644 --- a/dom/fixture/fixture_test.js +++ b/dom/fixture/fixture_test.js @@ -41,7 +41,7 @@ test("dynamic fixtures",function(){ test("fixture function", 3, function(){ stop(); - var url = steal.root.join("jquery/dom/fixture/fixtures/foo.json"); + var url = steal.root.join("jquery/dom/fixture/fixtures/foo.json")+''; $.fixture(url,"//jquery/dom/fixture/fixtures/foobar.json" ); $.get(url,function(data){ @@ -77,7 +77,7 @@ test("fixtures with converters", function(){ stop(); $.ajax( { - url : steal.root.join("jquery/dom/fixture/fixtures/foobar.json"), + url : steal.root.join("jquery/dom/fixture/fixtures/foobar.json")+'', dataType: "json fooBar", converters: { "json fooBar": function( data ) { @@ -250,7 +250,7 @@ test("fixture function gets id", function(){ }); test("replacing and removing a fixture", function(){ - var url = steal.root.join("jquery/dom/fixture/fixtures/remove.json") + var url = steal.root.join("jquery/dom/fixture/fixtures/remove.json")+'' $.fixture("GET "+url, function(){ return {weird: "ness!"} }) diff --git a/dom/form_params/form_params.js b/dom/form_params/form_params.js index cbfc7f6c..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 ) { @@ -77,7 +114,7 @@ steal("jquery/dom").then(function( $ ) { $this; // Don't do all this work if there's no value - if ( value ) { + if ( value !== undefined ) { $this = $(this); // Nested these if statements for performance @@ -96,67 +133,45 @@ steal("jquery/dom").then(function( $ ) { $this.val( value ); } } - }); + }); }, 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/dom/route/route.js b/dom/route/route.js index 44084c78..4159adec 100644 --- a/dom/route/route.js +++ b/dom/route/route.js @@ -212,6 +212,7 @@ function( $ ) { var names = [], test = url.replace(matcher, function( whole, name ) { names.push(name) + // TODO: I think this should have a + return "([^\\/\\&]*)" // The '\\' is for string-escaping giving single '\' for regEx escaping }); @@ -220,7 +221,7 @@ function( $ ) { // A regular expression that will match the route when variable values // are present; i.e. for :page/:type the regEx is /([\w\.]*)/([\w\.]*)/ which // will match for any value of :page and :type (word chars or period). - test: new RegExp("^" + test), + test: new RegExp("^" + test+"($|&)"), // The original URL, same as the index for this entry in routes. route: url, // An array of all the variable names in this route @@ -316,7 +317,7 @@ function( $ ) { // start will contain the full matched string; parts contain the variable values. start = parts.shift(), // The remainder will be the &key=value list at the end of the URL. - remainder = url.substr(start.length), + remainder = url.substr(start.length - (parts[parts.length-1] === "&" ? 1 : 0) ), // If there is a remainder and it contains a &key=value list deparam it. obj = (remainder && paramsMatcher.test(remainder)) ? $.String.deparam( remainder.slice(1) ) : {}; @@ -324,7 +325,7 @@ function( $ ) { obj = extend(true, {}, route.defaults, obj); // Overwrite each of the default values in obj with those in parts if that part is not empty. each(parts,function(i, part){ - if ( part ) { + if ( part && part !== '&') { obj[route.names[i]] = decode( part ); } }); diff --git a/dom/route/route_test.js b/dom/route/route_test.js index ed7e7f09..f487da94 100644 --- a/dom/route/route_test.js +++ b/dom/route/route_test.js @@ -169,8 +169,8 @@ test("param-deparam", function(){ var res = $.route.param(data); var obj = $.route.deparam(res); delete obj.route - same(data, obj) - + same(obj,data ) + return; data = {page: "jQuery.Controller", type: "foo", bar: "baz", where: "there"}; res = $.route.param(data); obj = $.route.deparam(res); @@ -254,6 +254,14 @@ test("param with route defined", function(){ equal(res, "foo&foo=abc") }) - +test("route endings", function(){ + $.route.routes = {}; + $.route("foo",{foo: true}); + $.route("food",{food: true}) + + var res = $.route.deparam("food") + ok(res.food, "we get food back") + +}) }) 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/drop/drop.js b/event/drop/drop.js index df624632..792c860a 100644 --- a/event/drop/drop.js +++ b/event/drop/drop.js @@ -1,8 +1,5 @@ -steal('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then(function($){ - var event = $.event, - callHanders = function(){ - - }; +steal('jquery/event/drag','jquery/dom/within','jquery/dom/compare',function($){ + var event = $.event; //somehow need to keep track of elements with selectors on them. When element is removed, somehow we need to know that // /** @@ -368,7 +365,9 @@ steal('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then(functio } }, end: function( event, moveable ) { - var responder, la, endName = this.lowerName+'end'; + var responder, la, + endName = this.lowerName+'end', + dropData; // call dropon //go through the actives ... if you are over one, call dropped on it @@ -380,7 +379,8 @@ steal('jquery/event/drag','jquery/dom/within','jquery/dom/compare').then(functio } // call dropend for(var r =0; r

HoverLeave

Leave and don't return for a half second
+ src='../../../steal/steal.js'> \ No newline at end of file diff --git a/event/pause/pause.js b/event/pause/pause.js index 74bc0b3b..45d28946 100644 --- a/event/pause/pause.js +++ b/event/pause/pause.js @@ -105,7 +105,7 @@ $.Event.prototype.pause = function(){ isDefaultPrevented : this.isDefaultPrevented() ? returnTrue : returnFalse, isPropagationStopped : this.isPropagationStopped() ? - returnTrue : returnFalse, + returnTrue : returnFalse }; this.stopImmediatePropagation(); 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/lang/observe/observe.js b/lang/observe/observe.js index 0d664065..e018cc07 100644 --- a/lang/observe/observe.js +++ b/lang/observe/observe.js @@ -638,7 +638,7 @@ steal('jquery/class').then(function() { if(this.comparator && /^\d+./.test(attr) ) { // get the index - var index = +/^\d+/.exec(attr)[0], + var index = +(/^\d+/.exec(attr)[0]), // and item item = this[index], // and the new item diff --git a/lang/openajax/openajax.js b/lang/openajax/openajax.js index 37b3a4ee..cc48f88f 100644 --- a/lang/openajax/openajax.js +++ b/lang/openajax/openajax.js @@ -1,4 +1,4 @@ -//@steal-clean +//!steal-clean /******************************************************************************* * OpenAjax.js * 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/model.js b/model/model.js index f6a29bf4..9e0e589d 100644 --- a/model/model.js +++ b/model/model.js @@ -761,11 +761,11 @@ steal('jquery/class', 'jquery/lang/string', function() { if ( this.listType ) { this.list = new this.listType([]); } - //@steal-remove-start + //!steal-remove-start if (!proto ) { steal.dev.warn("model.js " + fullName + " has no static properties. You probably need ,{} ") } - //@steal-remove-end + //!steal-remove-end each(ajaxMethods, function(name, method){ var prop = self[name]; if ( typeof prop !== 'function' ) { @@ -1010,11 +1010,11 @@ steal('jquery/class', 'jquery/lang/string', function() { length = raw ? raw.length : null, i = 0; - //@steal-remove-start + //!steal-remove-start if (!length ) { steal.dev.warn("model.js models has no data. If you have one item, use model") } - //@steal-remove-end + //!steal-remove-end for (; i < length; i++ ) { res.push(this.model(raw[i])); } @@ -1573,7 +1573,7 @@ steal('jquery/class', 'jquery/lang/string', function() { type = attrs[attr]; // the attribute's converter or the default converter for the class converter = Class.serialize[type] || Class.serialize['default']; - data[attr] = converter(this[attr], type); + data[attr] = converter.call(Class, this[attr], type); } } return data; @@ -1624,8 +1624,8 @@ steal('jquery/class', 'jquery/lang/string', function() { * }, error); * * - * @param {Function} [success(instance,data)] called if a successful save. - * @param {Function} [error(jqXHR)] error handler function called if the + * @param {Function} [success] called with (instance,data) if a successful save. + * @param {Function} [error] error handler function called with (jqXHR) if the * save was not successful. It is passed the ajax request's jQXHR object. * @return {$.Deferred} a jQuery deferred that resolves to the instance, but * after it has been created or updated. @@ -1747,9 +1747,9 @@ steal('jquery/class', 'jquery/lang/string', function() { // call event on the instance trigger(this,funcName); - //@steal-remove-start + //!steal-remove-start steal.dev.log("Model.js - "+ constructor.shortName+" "+ funcName); - //@steal-remove-end + //!steal-remove-end // call event on the instance's Class trigger(constructor,funcName, this); @@ -1822,4 +1822,4 @@ steal('jquery/class', 'jquery/lang/string', function() { } }; -}); \ No newline at end of file +}); 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 b9ffac43..0719d4e3 100644 --- a/model/test/qunit/model_test.js +++ b/model/test/qunit/model_test.js @@ -318,7 +318,7 @@ test("isNew", function(){ test("findAll string", function(){ $.fixture.on = false; $.Model("Test.Thing",{ - findAll : steal.root.join("jquery/model/test/qunit/findAll.json") + findAll : steal.root.join("jquery/model/test/qunit/findAll.json")+'' },{}); stop(); Test.Thing.findAll({},function(things){ @@ -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" @@ -501,8 +502,8 @@ test("hookup and elements", function(){ var res = esc.elements(ul); - equals(res.length,1) - equals(res[0], li[0]) + equals(res.length,1, "1 item") + ok(res[0] === li[0], "items are equal") }) test('aborting create update and destroy', function(){ diff --git a/model/test/qunit/qunit.js b/model/test/qunit/qunit.js index bf3583db..e1117ddb 100644 --- a/model/test/qunit/qunit.js +++ b/model/test/qunit/qunit.js @@ -1,7 +1,7 @@ //we probably have to have this only describing where the tests are steal("jquery/model","jquery/dom/fixture") //load your app .then('funcunit/qunit') //load qunit - .then("./model_test.js","./associations_test.js") + .then("./model_test.js")//,"./associations_test.js") .then( "jquery/model/backup/qunit", "jquery/model/list/list_test.js" 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; }; diff --git a/view/ejs/ejs_test.js b/view/ejs/ejs_test.js index 57cb9b12..195625d1 100644 --- a/view/ejs/ejs_test.js +++ b/view/ejs/ejs_test.js @@ -103,4 +103,27 @@ test("easy hookup", function(){ ok( div.find('div').hasClass('yes'), "has yes" ) }); +test("helpers", function() { + $.EJS.Helpers.prototype.simpleHelper = function() + { + return 'Simple'; + } + + $.EJS.Helpers.prototype.elementHelper = function() + { + return function(el) { + el.innerHTML = 'Simple'; + } + } + + var text = "
<%= simpleHelper() %>
"; + var compiled = new $.EJS({text: text}).render() ; + equals(compiled, "
Simple
"); + + text = "
<%= elementHelper() %>>
"; + compiled = new $.EJS({text: text}).render() ; + $('#qunit-test-area').append($(compiled)); + equals($('#hookup').html(), "Simple"); +}); + }) diff --git a/view/helpers/helpers.js b/view/helpers/helpers.js index 75b3f91b..478046eb 100644 --- a/view/helpers/helpers.js +++ b/view/helpers/helpers.js @@ -315,7 +315,7 @@ $.extend($.EJS.Helpers.prototype, { */ img_tag: function( image_location, options ) { options = options || {}; - options.src = steal.root.join("resources/images/"+image_location); + options.src = steal.root.join("resources/images/"+image_location)+''; return this.single_tag_for('img', options); } diff --git a/view/view.js b/view/view.js index 93e23a69..069e8899 100644 --- a/view/view.js +++ b/view/view.js @@ -407,7 +407,7 @@ steal("jquery").then(function( $ ) { var sub = url.substr(2); url = typeof steal === "undefined" ? url = "/" + sub : - steal.root.mapJoin(sub); + steal.root.mapJoin(sub) +''; } //set the template engine type @@ -539,7 +539,7 @@ steal("jquery").then(function( $ ) { if ( window.steal ) { steal.type(info.suffix + " view js", function( options, success, error ) { var type = $view.types["." + options.type], - id = toId(options.rootSrc); + id = toId(options.rootSrc+''); options.text = type.script(id, options.text) success(); @@ -580,7 +580,7 @@ steal("jquery").then(function( $ ) { if ( window.steal ) { steal.type("view js", function( options, success, error ) { var type = $view.types["." + options.type], - id = toId(options.rootSrc); + id = toId(options.rootSrc+''); options.text = "steal('" + (type.plugin || "jquery/view/" + options.type) + "').then(function($){" + "$.View.preload('" + id + "'," + options.text + ");\n})"; success();