Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support init or setup returning something other than new instance #30

Merged
merged 5 commits into from
Jan 26, 2017
Merged
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
86 changes: 84 additions & 2 deletions can-construct.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ assign(Construct, {
writable: true
});
args = inst.setup.apply(inst, arguments);
if (args instanceof Construct.ReturnValue){
return args.value;
}
inst.__inSetup = false;
}
// Call `init` if there is an `init`
Expand Down Expand Up @@ -526,6 +529,46 @@ assign(Construct, {
* dog.speak(); // 'woof'
* blackMamba.speak(); // 'ssssss'
* ```
*
* ## Alternative value for a new instance
*
* Sometimes you may want to return some custom value instead of a new object when creating an instance of your class.
* For example, you want your class to act as a singleton, or check whether an item with the given id was already
* created and return an existing one from your cache store (e.g. using [can-connect/constructor/store/store]).
*
* To achieve this you can return [can-construct.ReturnValue] from `setup` method of your class.
*
* Lets say you have `myStore` to cache all newly created instances. And if an item already exists you want to merge
* the new data into the existing instance and return the updated instance.
*
* ```
* var myStore = {};
*
* var Item = Construct.extend({
* setup: function(params){
* if (myStore[params.id]){
* var item = myStore[params.id];
*
* // Merge new data to the existing instance:
* Object.assign(item, params);
*
* // Return the updated item:
* return new Construct.ReturnValue( item );
* } else {
* // Save to cache store:
* myStore[this.id] = this;
*
* return [params];
* }
* },
* init: function(params){
* Object.assign(this, params);
* }
* });
*
* var item_1 = new Item( {id: 1, name: "One"} );
* var item_1a = new Item( {id: 1, name: "OnePlus"} )
* ```
*/
extend: function (name, staticProperties, instanceProperties) {
var shortName = name,
Expand Down Expand Up @@ -663,6 +706,44 @@ assign(Construct, {
* console.log(Counter.count); // 1
* ```
*/
},
/**
* @function can-construct.ReturnValue ReturnValue
* @parent can-construct.static
*
* Use to overwrite the return value of new Construct(...).
*
* @signature `new Construct.ReturnValue( value )`
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature should talk about how this works. Also, the example code below should be put in the signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

* This constructor function can be used for creating a return value of the `setup` method.
* [can-construct] will check if the return value is an instance of `Construct.ReturnValue`.
* If it is then its `value` will be used as the new instance.
*
* @param {Object} value A value to be used for a new instance instead of a new object.
*
* ```
* var Student = function( name, school ){
* this.name = name;
* this.school = school;
* }
*
* var Person = Construct.extend({
* setup: function( options ){
* if (options.school){
* return new Constructor.ReturnValue( new Student( options.name, options.school ) );
* } else {
* return [options];
* }
* }
* });
*
* var myPerson = new Person( {name: "Ilya", school: "PetrSU"} );
*
* myPerson instanceof Student // => true
* ```
*/
ReturnValue: function(value){
this.value = value;
}
});
/**
Expand All @@ -675,8 +756,9 @@ assign(Construct, {
*
* @param {*} args The arguments passed to the constructor.
*
* @return {Array|undefined} If an array is returned, the array's items are passed as
* arguments to [can-construct::init init]. The following example always makes
* @return {Array|undefined|can-construct.ReturnValue} If an array is returned, the array's items are passed as
* arguments to [can-construct::init init]. If a [can-construct.ReturnValue] instance is returned, the ReturnValue
* instance's value will be returned as the result of calling new Construct(). The following example always makes
* sure that init is called with a jQuery wrapped element:
*
* ```js
Expand Down
35 changes: 35 additions & 0 deletions can-construct_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,38 @@ QUnit.test("setters not invoked on extension (#28)", function(){
extending = false;
new Base().something = "foo";
});

QUnit.test("return alternative value simple", function(){
var Alternative = function(){};
var Base = Construct.extend({
setup: function(){
return new Construct.ReturnValue( new Alternative() );
}
});
QUnit.ok(new Base() instanceof Alternative, "Should create an instance of Alternative");
});

QUnit.test("return alternative value on setup (full case)", function(){
var Student = function(name, school){
this.name = name;
this.school = school;
this.isStudent = true;
};
var Person = Construct.extend({
setup: function(opts){
if (opts.age >= 16){
return new Construct.ReturnValue( new Student(opts.name, opts.school) );
}
opts.isStudent = false;
return [opts];
},
init: function(params){
this.age = params.age;
this.name = params.name;
this.isStudent = params.isStudent;
}
});
QUnit.equal(new Person({age: 12}).isStudent, false, "Age 12 cannot be a student");
QUnit.equal(new Person({age: 30}).isStudent, true, "Age 20 can be a student");
QUnit.ok(new Person({age: 30}) instanceof Student, "Should return an instance of Student");
});