Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions model/validations/qunit/validations_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module("jquery/model/validations",{
})

test("models can validate, events, callbacks", 11,function(){

Person.validate("age", {message : "it's a date type"},function(val){
return ! ( this.date instanceof Date )
})
Expand Down Expand Up @@ -61,6 +62,23 @@ test("validatesFormatOf", function(){
});

test("validatesInclusionOf", function(){

Person.validateInclusionOf("thing",["value1","value2","value3"]);

ok(!new Person({thing: "value2"}).errors(),"no errors");

var errors = new Person({thing: "foobar"}).errors();

ok(errors, "there are errors")
equals(errors.thing.length,1,"one error on thing");

equals(errors.thing[0],"is not a valid option (perhaps out of range)","basic message");

Person.validateInclusionOf("otherThing",["value1","value2","value3"],{message: "not in array"})

var errors2 = new Person({thing: "1-2", otherThing: "a"}).errors();

equals(errors2.otherThing[0],"not in array", "can supply a custom message")


})
Expand Down Expand Up @@ -98,9 +116,34 @@ test("validatesPresenceOf", function(){
errors = task.errors();;

ok(!errors, "no errors "+typeof errors);

})

test("validatesRangeOf", function(){

jQuery.Model.extend("ValidatesRangeOfMock1",{},{});

ValidatesRangeOfMock1.validateRangeOf("thing",2,3);

ok(!new ValidatesRangeOfMock1({thing: 2.5}).errors(),"no errors");

var errors = new ValidatesRangeOfMock1({thing: 4}).errors();

ok(errors, "there are errors")

equals(errors.thing.length,1,"one error on thing");

equals(errors.thing[0],"is out of range [2,3]","basic message");

jQuery.Model.extend("ValidatesRangeOfMock2",{},{});

ValidatesRangeOfMock2.validateRangeOf("otherThing",-100,-10,{message: "not in range"})

ok(!new ValidatesRangeOfMock2({otherThing: -50}).errors(),"no errors, with custom message");

var errors2 = new ValidatesRangeOfMock2({thing: 2.5, otherThing: 3}).errors();

equals(errors2.otherThing[0],"not in range", "can supply a custom message")

})

Expand Down
59 changes: 56 additions & 3 deletions tie/test/qunit/qunit.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
steal
.plugins("funcunit/qunit", "jquery/tie",'jquery/model')
.then("tie_test").then(function(){
.then(function(){


module("jquery/tie",{
Expand Down Expand Up @@ -37,6 +37,27 @@ steal
equals(inp2.val(), "6", "nothing set");


});

test("sets age of standard element on tie", function(){

var person1 = new Person({age: 5});
var inp = $("<div/>").appendTo( $("#qunit-test-area") );

inp.tie(person1, 'age');

equals(inp.text(), "5", "sets age");

var person2 = new Person();
var inp2 = $("<div/>").appendTo( $("#qunit-test-area") );
inp2.tie(person2, 'age');
equals(inp2.val(), "", "nothing set");

person2.attr("age",6);

equals(inp2.html(), "6", "nothing set");


});

test("removing the controller, removes the tie ", 3, function(){
Expand Down Expand Up @@ -93,15 +114,47 @@ steal
});

test("input error recovery", function(){

var person1 = new Person({age: 5});
var inp = $("<input/>").appendTo( $("#qunit-test-area") );

var error_called = false;

inp.tie(person1, 'age');
inp.tie(person1, 'age',function() {}, function() { error_called = true; });

inp.val(100).trigger('change');

equals(inp.val(), "5", "input value stays the same");
equals(person1.attr('age'), "5", "persons age stays the same");
})
equals(error_called,true, "error function called");

});

test("input success calls callback", function() {
var person1 = new Person({age: 5});
var inp = $("<input/>").appendTo( $("#qunit-test-area") );

var success_called = false;

inp.tie(person1, 'age',function() {success_called=true});

equals(success_called, true, "success called for init");

success_called = false;

person1.attr('age',4);

equals(success_called, true, "success called for model change");

success_called = false;

inp.val(3).trigger('change');

equals(success_called, true, "success called for input change");




});

});
148 changes: 141 additions & 7 deletions tie/tie.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,145 @@ steal.plugins('jquery/controller').then(function($){

/**
* @core
* @tag core
* @class jQuery.Tie
*
* The $.fn.tie plugin binds form elements and controllers with
* @page jquery.tie Tie
* @plugin jquery/tie
* @test jquery/tie/qunit.html

* The $.fn.tie plugin binds form elements, dom elements and controllers with
* models and vice versa. The result is that a change in
* a model will automatically update the form element or controller
* AND a change event on the element will update the model.
* a model will automatically update the form element, dom elemnt, or controller
* AND a change event on the element will update the model.
*
* ## Setup
*
* @codestart
* $.Model("Person")
*
* var person = new Person({age: 5});
* @codeend
*
* ## Form elements
*
* @codestart
* $('input:first').tie(person,'age');
* @codeend
*
* When this code is run, it will automatically set the input's value to 5.
* If I do the following ...
*
* @codestart
* person.attr('age',7);
* @codeend
*
* ... It will update the input element.
*
* If I change the input element manually, it will effectively do a:
*
* @codestart
* person.attr('age',$('input:first').val());
* @codeend
*
* ## Non-form elements
*
* Tie will also call html on elements allowing you to link any html elements.
*
* @codestart
* $('p.age').tie(person,'age');
* @codeend
*
* This will link all paragraphs with a class of age. Initially it will set the
* text to 7 (as we have already changed the persons age to 7).
*
* If I do the following ...
*
* @codestart
* person.attr('age',2);
* @codeend
*
* ... It will update all the paragraph elements.
*
* ## Controllers
*
* For form elements, tie uses $(el).val() to get and set values and listens
* for change events to know when the input element has changed.
*
* For controller, it's basically the same way. Your controller only has to
* do 2 things:
*
* 1. implement a val function that take an optional value.
* If a value is provided, it should update the UI appropriately;
* otherwise, it should return the value:
*
* @codestart
* $.Controller('Rating',{
* val : function(value){
* if(value !== undefined){
* //update the UI
* }else{
* //return the value
* }
* }
* })
* @codeend
*
* 2. When the model should be updated, trigger a change event
* with the new value:
*
* @codestart
* this.element.trigger('change',7);
* @codeend
*
* Here's a slider widget implemented this way:
* https://github.com/jupiterjs/mxui/blob/master/slider/slider.js
* Notice in dropend, it triggers a change with the value of the slider.
*
* You could tie a slider to a person's age like:
*
* @codestart
* $('#slider').mxui_slider().tie(person,'age');
* @codeend
*
* Reads pretty well doesn't it!
*
* ## Validation
*
* Here's how we could setup our model to validate ages:
*
* @codestart
* $.Model.extend("Person",{
* setAge : function(age, success, error){
* age = +(age);
* if(isNaN(age) || !isFinite(age) || age < 1 || age > 10){
* error()
* }else{
* return age;
* }
* }
* });
* @codeend
*
* This checks that age is a number between 1 and 10. You could also
* use the validations plugin for this.
*
* If setAge made an Ajax request to the server, you would call
* success(finalAge) instead of returning the correct value.
*
*/
$.Controller.extend("jQuery.Tie",{
init : function(el, inst, attr, type){
/**
* @function jQuery.Tie
* @parent jquery.tie
* Initiate a link between an html element or controller and model.
* @param {Object} inst A model instance to bind with.
* @param {String} attr The model attribute to trigger changes on.
* @param {Function} success (optional) A callback function if the element, controller, or model is successfully updated
* @param {Function} failure (optional) A callback function if the element, controller, or model cannot be updated, for example if it fails the models validation.
* @param {Object} type (optional) TODO I'm not entirely clear on what this does, something to do with you being able to give it a different controller to call value on I think.
*
*/
init : function(el, inst, attr, success, failure, type){
// if there's a controller
if(!type){
//find the first one that implements val
Expand All @@ -32,6 +157,8 @@ $.Controller.extend("jQuery.Tie",{
this.type = type;
this.attr = attr;
this.inst = inst;
this.success = success;
this.failure = failure;
this.bind(inst, attr, "attrChanged");

//destroy this controller if the model instance is destroyed
Expand All @@ -47,12 +174,17 @@ $.Controller.extend("jQuery.Tie",{
this.element[type]("val",value);

}else{
this.element.val(value)
this.element.val(value);
this.element.html(value);
}
if (typeof this.success == "function")
this.success(this.element);
},
attrChanged : function(inst, ev, val){
if (val !== this.lastValue) {
this.setVal(val);
if (typeof this.success == "function")
this.success(this.element);
this.lastValue = val;
}
},
Expand All @@ -61,7 +193,8 @@ $.Controller.extend("jQuery.Tie",{
this.element[this.type]("val", val)
}
else {
this.element.val(val)
this.element.val(val);
this.element.html(val);
}
},
change : function(el, ev, val){
Expand All @@ -73,6 +206,7 @@ $.Controller.extend("jQuery.Tie",{

},
setBack : function(){
this.failure(this.element);
this.setVal(this.lastValue);
},
destroy : function(){
Expand Down