Object Not Defined with Knockout Data Mapping Plugin - javascript

I'm trying to use the mapping plugin to make children objects' properties observable. I have the following:
// setData defined here
var mapping = {
create: function(options) {
//customize at the root level.
var innerModel = ko.mapping.fromJS(options.data);
innerModel.cardCount = ko.computed(function () {
debugger;
return this.cards().length; // cards not defined - "this" is Window for some reason
});
innerModel.deleteCard = function (card) {
// Pending UI
// call API here
// On success, complete
this.cards.remove(card);
}.bind(this);
innerModel.addCard = function () {
//debugger;
// Pending UI
// Call API here
// On success, complete
this.cards.push(dummyCard);
//this.cardToAdd("");
}.bind(this);
return innerModel;
}
};
var SetViewModel = ko.mapping.fromJS(setData, mapping);
ko.applyBindings(SetViewModel);
When I run this in chrome debugger, I get "Object [Object global] has no method cards". Cards should be an observable array. What am I doing wrong?

innerModel.cardCount = ko.computed(function () {
debugger;
return this.cards().length; // cards not defined - "this" is Window for some reason
});
this is inside the anonymous function you're creating and is therefore bound to the global object. if you want to reference innermodel you'll have to do so directly, or bind innermodel to the function.
innerModel.cardCount = ko.computed(function () {
return innerModel.cards().length;
});
or
var computedFunction = function () {
return this.cards().length;
};
innerModel.cardCount = ko.computed(computedFunction.apply(innerModel));

Related

Access Computed Value in subscribable function

When using my view model:
function SummaryViewModel (arrayString) {
//------- Attributes -------
var self = this;
self.Claims = ko.observableArray(namesapce.Helpers.subnamespace.ToCollection(arrayString));
self.ShowTable = ko.computed(function() {
var collection = ko.unwrap(self.Claims());
return collection.length > 0;
}, this);
self.showWarningPanel = self.ShowTable.Not();
}
I am trying to invert the computed value. So either the table will be displayed or a warning message.
I have created the following subscribable function:
ko.subscribable.fn.Not = function () {
return ko.pureComputed(function() {
var bool = this();
return !(ko.unwrap(bool));
});
};
However, the value for this() does not provide the value of the computed attribute. It instead returns all the objects in the current scope.
Originally I marked the method as ko.computed.fn and this also did not return the computed value.
I have been referring to the documentation on the KO website to help build the function.
http://knockoutjs.com/documentation/fn.html
You'll have to pass this to the pureComputed you're creating inside:
ko.subscribable.fn.Not = function () {
return ko.pureComputed(function() {
return !this();
}, this);
// ^^^^ Here, you tell knockout to execute the function with `this`
// context. Alternatively, you could use the var `self = this`
// pattern.
};
var myObs = ko.observable(true);
var invertedObs = myObs.Not();
myObs(false);
console.log("observable: " + myObs());
console.log("inverted: " + invertedObs());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

Store instance for usage in nested map/object

I have a really annoying scope issue for my JQuery Widget. Essentially I need to access the widget instance (this) inside of my map/object options.
Is it possible to do this? Any advice how I can achieve this?
$.widget( "my.myWidget", {
// Below 'this.defCallback' will be undefined
// How can I store 'this' (the widget instance) in a variable??
options: {
callback: this.defCallback // allow user to overwrite/provide custom callback
},
....
defCallback: function() {
console.log('defCallback');
}
});
If I had a nested function I know I can easily solve this but I have a nested object/map which makes things difficult.
function foo {
var _this = this;
...
var bar = function() {
// easily access this
_this.defCallback();
...
}
}
Usage:
$('<div></div>')
.myWidget(); // use defCallback
$('<div></div>')
.myWidget({
callback: function() {
...
}
}); // use custom callback
Edit: How the callback function is 'bound' and called:
_create: function() {
this.element.click( this.options.callback );
}
.click(value.callback(_this)
In javascript you could dynamically change the context of a function with the apply() and with call() methods.
On es5 you could use bind().
So your code:
_create: function() {
this.element.click( this.options.callback );
}
Became with apply():
_create: function() {
var el = this.element;
var callback = this.options.callback;
el.click(function() {
callback.apply(el);
// If you have parameters:
// callback.apply(el, arguments || array);
});
}
With call():
_create: function() {
var el = this.element;
var callback = this.options.callback;
el.click(function() {
callback.call(el);
// If you have parameters:
// callback.call(el, arg0, arg1, ...);
});
}
With bind():
_create: function() {
this.element.click(this.options.callback.bind(this));
}
UPDATE
As your issue is to have the this reference binded inside the object definition you need to change your code.
The quick way is is to emend it like this (from your fiddle):
var mw = {
defCallback: function () {
alert("abc");
},
_create: function () {
//this.populateOptions();
alert("Is undefined: " + this.options.isUndefined); // outputs 'true'
this.element.click(this.options.callback.bind(this));
},
populateOptions: function() {
if (this.options.callback === undefined)
this.options.callback = this.defCallback;
}
};
So you first define your object with the parent attributes and functions.
mw.options = {
//accessObjectParent: this.instantiator,
isUndefined: (mw.defCallback === undefined), // this refers to the map/object
// Can I access the maps 'parent'/instantiator?
// this.instantiator.defCallback ???
callback: mw.defCallback
};
Than you attach the options object and you could refer on the parent object instead of using this.
$.widget( "my.myWidget", mw );
And now you pass the object on your widget declaration.

ko.utils.extend Doesn't Override Property From Model

Why ko.utils.extend doesn't override this.selectItem ? Here it always shows alert of 1.
var ViewModel = function () {
this.selectItem = function () { alert(1) };
};
}
ko.utils.extend(ViewModel.prototype, {
selectItem: function () {
alert("from extend");
}
The reason extend doesn't override it is that it's not a prototype property. It's a property added to the instance by the constructor function, which shadows (hides) the prototype property.
Here's the order of what occurs (note - I've fixed the syntax errors in the code in the question, and added an instantiation of the view model):
// 1 - the `ViewModel` function is created, along with `ViewModel.prototype`
var ViewModel = function () {
// 4 - the instance gets its own `selectItem` property
this.selectItem = function () { alert(1) };
};
// 2 - `selectItem` is added to `ViewModel.prototype`
ko.utils.extend(ViewModel.prototype, {
selectItem: function () {
alert("from extend");
}
});
// 3 - an instance is created; its underlying prototype is set from `ViewModel.prototype`
var vm = new ViewModel();

Javascript: Modify array directly only within its own function

I have a very simple function:
var errorsViewModel = function () {
var self = this;
var _errors = ko.observableArray([]);
self.get = function () {
return _errors;
};
self.insert = function ( error ) {
_errors.push(error);
};
}
What I want to acomplish is make _errors array modifiable directly only within its own function. That is users from outside can get the array for reading through the get method and insert itsert items only through the insert method.
But not to be able to do something like this:
var err = new errorsViewModel();
var array = err.get();
array.push('item');
Instead use the errorsViewModel interface :
err.insert('some error');
Is that possible?
Just copy the returned array:
self.get = function () {
return _errors.slice(0);
};
That way, when get is called, the caller can make changes to it if they want - but it won't modify the original.
To make sure that your array isn't accessible from outside your scope I would suggest that you expose the array via a ko.computed and then notify it's listeners on an insert.
var errorsViewModel = function () {
var self = this;
var _errors = [];
self.errors = ko.computed(function () {
return self.get();
});
self.get = function () {
return _errors.splice(0);
};
self.insert = function ( error ) {
_errors.push(error);
self.errors.valueHasMutated();
};
}

init function and the object in Javascript

I have the following (simplified) code:
var Foo = (function () {
var data = {},
settings = {
// default settings here
};
function bar(callback) { // bar is an asynchronous function
var result = null;
// fiddle around until you get a result
if (callback) {
callback(result);
}
}
return {
init: function (options, callback) {
var kallback = callback;
$.extend(settings, options);
bar(function () {
if (kallback) {
kallback(WHAT_GOES_HERE);
}
});
},
debug: function () {
return {
settings: settings,
data: data
};
},
set: function (k, v) {
settings[k] = v;
},
get: function (k) {
return settings[k];
}
};
}());
The code above is in a js file, then in the footer of the page in question:
<script type="text/javascript">
Foo.init({ option1: "value", option2: "value" }, function (obj) {
console.log("The object was ", obj);
});
</script>
Basically, here is what I want to be able to do:
Create an object (with a set of optional params, but not important for this question)
During the creation phase of the object, have it call an asynchronous function
When the asynchronous function is done, I should be able to trigger a callback, and the argument for the callback should be the intialized object
I thought that this would work for WHAT_GOES_HERE above, but turns out, at least when I've tested it, that this is the DOM Window object.
First of all, am I constructing this object correctly? Or is there a better way to create it?
Secondly, assuming I am doing this right, what should go in the WHAT_GOES_HERE so that when console.log("The object was ", foo); runs, the value for obj is the created Foo object?
Yes, in an anonymous function called this way, this will refer to the window object. To refer to the this reference from the init method you have to store the reference in another variable:
var kallback = callback, self = this;
$.extend(settings, options);
bar(function () {
if (kallback) {
kallback(self);
}
});
Your way of constructing the object works, as long as you only want to have one single foo object.

Categories

Resources