I read data into my viewmodel from JSON (from my server).
My user changes the viewmodel from some input fields, but I need to be able to undo it and bring back the original value from my original JSON.
Is it possible to store "attributes" on a viewmodel - an attribute like original value? So I can read it back?
Thanks
UPDATE...
Tried making a jsfiddle (which obviously doesn't work) to show what I would like to do:
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
};
ViewModel.firstName.attribute("fieldtype", "string");
ViewModel.firstName.attribute("fieldlength", "30");
ViewModel.firstName.attribute("org-value", "Jane");
ko.applyBindings(new ViewModel("John", "Doe"));
$("#cmd").clicked(function() {
ViewModel.firstName(ViewModel.firstName.attribute("org-value"));
});
http://jsfiddle.net/MojoDK/kaymX/
I like to attach "attributes" to observables by adding properties to it (it's just a JavaScript function after all). Each property is attached to the observable it belongs to rather than stored in another structure elsewhere. Another benefit is that the properties on observables are also not serialized if you call ko.toJSON() on the view model.
function VM (value) {
var self = this;
self.foo = ko.observable(value);
self.foo.original = value;
self.revert = function () {
self.foo(self.foo.original);
};
}
JsBin: http://jsbin.com/biguvoqe/1/edit?html,js,output
Adapting what you put in your fiddle, you could do something like this:
http://jsfiddle.net/kaymX/2/
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.firstName.attributes = {};
};
var myVM = new ViewModel("John", "Doe");
myVM.firstName.attributes["fieldtype"] = "string";
myVM.firstName.attributes["fieldlength"] = "30";
myVM.firstName.attributes["org-value"] = "Jane";
ko.applyBindings(myVM);
$("#cmd").click(function() {
myVM.firstName(myVM.firstName.attributes["org-value"]);
});
Although it might be easier to move setting of attributes inside the constructor for your view model. Also you could use the dot notation rather than the bracket notation, but I left it with accessing properties by strings since I assume you had a reason for having it that way in the first place.
Related
I have a constructor in a class that sets properties based on the arguments passed when creating the function as well as a call to another function based on those arguments but I am running into an issue. I have solved in with a work around but there must be a more elegant way to write this (no pun intended).
Here is my constructor:
constructor(name,model,version){
// create optional parameters to set basic values
this.name = name;
this.model = model;
this.version = version;
if(model === undefined || version === undefined){
this.make = "";
this.model = "";
this.version = "";
this.prop3 = 0
this.prop4 = 0;
this.prop5 = 0;
}
else{
this = getVersionAttrs(model,version); }
This code does not work, if I replace this with this.what then the code functions but sets all of the properties as properties of OBJECT.what and not directly of the OBJECT.
I've replaced the else statement with the following that works:
else{
temp = getVersionAttrs(model,version);
for(var x in temp){ this[x] = temp[x]; }
}
Does anyone know how to assign this object I am getting back from the function getVersionAttrs() to the object that is being created? Aka properties of the returned object should become properties of the object being created.
Thanks in advance, I've been scratching my head over this one.
Don't try to re-assign a this. If getVersionAttrs returns an object with properties you want to add to this, you should use Object.assign instead:
else {
Object.assign(this, getVersionAttrs(model, version));
}
class MyClass {
constructor() {
Object.assign(this, { foo: 'foo' });
}
}
console.log(new MyClass());
I'm not exactly sure what you're trying accomplish here, but from what I can see, I believe you should be set with something as simple as this:
constructor(name, model=null, version=null){
if (!model || !version) {
var {model, version} = getVersionAttrs(model, version);
}
this.name = name;
this.model = model;
this.version = version;
}
Assuming getVersionAttrs(model, version) returns something like {model: 'model', version: 'version' }
Hahaha, that is typical wrong. Let us review your code
this = getVersionAttrs(model,version);
I guess you want to Object.assign this value but you re assignment this value.
So, there is a simple solution below, about Object.assign
this = Object.assign({}, this, getVersionAttrs(model,version))
Also you can use lodash or other same APIs.
I have nested view models like below. I am trying to access value in container view model from the contained view model (child). I got undefined error when the modelA.prop1 trying to get mainVM.prop1 value. Thanks for your help.
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA();
self.modelB = new modelB();
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(){
var self = this;
self.prop1 = ko.observable(mainVM.prop1); //I'd like to get value in containing view model above
}
function modelB(){....}
$(function () {
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
If you want to make sub-ViewModels dependent/aware of their parent you'll have to pass it to them. E.g.:
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA(self);
self.modelB = new modelB(self);
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(parent){
var self = this;
self.prop1 = ko.observable(parent.prop1); //I'd like to get value in containing view model above
}
function modelB(parent){....}
$(function () {
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
Think carefully though if this dependency is something you want in your design.
An alternative (though arguably worse from a design standpoint) solution would be to give them access through the scope, e.g.:
$(function () {
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA();
self.modelB = new modelB();
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(){
var self = this;
self.prop1 = ko.observable(viewModel.prop1); //I'd like to get value in containing view model above
}
function modelB(){....}
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
Some additional thoughts to #Jeroen answer
Having dependencies to parent from children is not only bad design it can create hard to find memory leaks
If you use the parent from a computed in the child KO will hook up a dependency, if you remove the child it's computed will still fire when the parent change state.
My general way of solving dependencies between models is to use a EventAggregator pattern, I have made one for this library
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy
Its a signalR library, if you do not need singalR you can extract the event aggregation part
Demo
http://jsfiddle.net/jh8JV/
ViewModel = function() {
this.events = ko.observableArray();
this.subModel = new SubViewModel();
signalR.eventAggregator.subscribe(Event, this.onEvent, this);
};
ViewModel.prototype = {
onEvent: function(e) {
this.events.push(e);
}
};
I think you've got an "XY problem" here: you want to accomplish task X (which you haven't named here) and you think that implementation Y (in this case, a child VM having a dependency on its parent) is the way to do it, even though Y might not be the best (or even a good) way to do it.
What's the actual problem you're trying to solve? If you need to access the parent property from within a child binding, Knockout's binding context ($root, $parent, $parents[], etc.) will let you do it, e.g.
<div data-bind="with:modelA">
<p>prop2 is <span data-bind="text:prop2"></span>
and prop1 from the main model is
<span data-bind="text:$root.prop1"></span>
</p>
</div>
In this case you could use $parent in place of $root since there's only one level of nesting.
In knockout js I see View Models declared as either:
var viewModel = {
firstname: ko.observable("Bob")
};
ko.applyBindings(viewModel );
or:
var viewModel = function() {
this.firstname= ko.observable("Bob");
};
ko.applyBindings(new viewModel ());
What's the difference between the two, if any?
I did find this discussion on the knockoutjs google group but it didn't really give me a satisfactory answer.
I can see a reason if I wanted to initialise the model with some data, for example:
var viewModel = function(person) {
this.firstname= ko.observable(person.firstname);
};
var person = ... ;
ko.applyBindings(new viewModel(person));
But if I'm not doing that does it matter which style I choose?
There are a couple of advantages to using a function to define your view model.
The main advantage is that you have immediate access to a value of this that equals the instance being created. This means that you can do:
var ViewModel = function(first, last) {
this.first = ko.observable(first);
this.last = ko.observable(last);
this.full = ko.computed(function() {
return this.first() + " " + this.last();
}, this);
};
So, your computed observable can be bound to the appropriate value of this, even if called from a different scope.
With an object literal, you would have to do:
var viewModel = {
first: ko.observable("Bob"),
last: ko.observable("Smith"),
};
viewModel.full = ko.computed(function() {
return this.first() + " " + this.last();
}, viewModel);
In that case, you could use viewModel directly in the computed observable, but it does get evaluated immediate (by default) so you could not define it within the object literal, as viewModel is not defined until after the object literal closed. Many people don't like that the creation of your view model is not encapsulated into one call.
Another pattern that you can use to ensure that this is always appropriate is to set a variable in the function equal to the appropriate value of this and use it instead. This would be like:
var ViewModel = function() {
var self = this;
this.items = ko.observableArray();
this.removeItem = function(item) {
self.items.remove(item);
}
};
Now, if you are in the scope of an individual item and call $root.removeItem, the value of this will actually be the data being bound at that level (which would be the item). By using self in this case, you can ensure that it is being removed from the overall view model.
Another option is using bind, which is supported by modern browsers and added by KO, if it is not supported. In that case, it would look like:
var ViewModel = function() {
this.items = ko.observableArray();
this.removeItem = function(item) {
this.items.remove(item);
}.bind(this);
};
There is much more that could be said on this topic and many patterns that you could explore (like module pattern and revealing module pattern), but basically using a function gives you more flexibility and control over how the object gets created and the ability to reference variables that are private to the instance.
I use a different method, though similar:
var viewModel = (function () {
var obj = {};
obj.myVariable = ko.observable();
obj.myComputed = ko.computed(function () { return "hello" + obj.myVariable() });
ko.applyBindings(obj);
return obj;
})();
Couple of reasons:
Not using this, which can confusion when used within ko.computeds etc
My viewModel is a singleton, I don't need to create multiple instances (i.e. new viewModel())
I'm using CoffeeScript and KnockoutJS and have a problem getting the values of my view model from within a function.
I have a view model:
window.Application || = {}
class Application.ViewModel
thisRef = this
searchTerm: ko.observable("")
search: ->
alert #searchTerm
Which compiles to:
window.Application || (window.Application = {});
Application.ViewModel = (function() {
var thisRef;
function ViewModel() {}
thisRef = ViewModel;
ViewModel.prototype.searchTerm = ko.observable("");
ViewModel.prototype.search = function() {
return alert(this.searchTerm);
};
return ViewModel;
})();
This view model is part of a parent view model which exposes it as field. The problem is that I can't get a reference to the child view model. In the search function 'this' is a instance of the parent, which I don't want.
In the search function 'this' is a instance of the parent...
That depends on how you call it. If you do
m = new Application.ViewModel
m.search()
then this will be m; if you write
obj = {search: m.search}
obj.search()
then this will be obj.
Anyway, just use CoffeeScript's => operator:
search: =>
alert #searchTerm
That way, this/# within search will point to the ViewModel instance.
thisRef will, as Travis says, just point to the class, not the instance.
You already have a thisRef object hanging around, use thisRef.searchTerm instead of #searchTerm. I often have that happen when using jQuery...
doSomething = ->
target = $(#)
$("#blah").click ->
target.doSomethingElse()
Since #doSomethingElse() would be bound to the DOM element the click was executed for. Not what I want.
If you have an array of product objects created from JSON, how would you add a prototype method to the product objects so that they all point to the same method? How would you train JavaScript to recognize all product objects in an array are instances of the same class without recreating them?
If I pull down a JSON array of Products for example, and want each product in the array to have a prototype method, how would I add the single prototype method to each copy of Product?
I first thought to have a Product constructor that takes product JSON data as a parameter and returns a new Product with prototypes, etc. which would replace the data send from the server. I would think this would be impractical because you are recreating the objects. We just want to add functions common to all objects.
Is it possible to $.extend an object's prototype properties to the JSON object so that each JSON object would refer to exactly the same functions (not a copy of)?
For example:
var Products = [];
Products[0] = {};
Products[0].ID = 7;
Products[0].prototype.GetID = function() { return this.ID; };
Products[1].ID = 8;
Products[1].prototype = Products[0].prototype; // ??
I know that looks bad, but what if you JQuery $.extend the methods to each Product object prototype: create an object loaded with prototypes then $.extend that object over the existing Product objects? How would you code that? What are the better possibilities?
For one, you're not modifying the Products[0].prototype, you're modifying Object.prototype, which will put that function on the prototype of all objects, as well as making it enumerable in every for loop that touches an Object.
Also, that isn't the proper way to modify a prototype, and ({}).prototype.something will throw a TypeError as .prototype isn't defined. You want to set it with ({}).__proto__.something.
If you want it to be a certain instance you need to create that instance, otherwise it will be an instance of Object.
You probably want something like:
var Product = function(ID) {
if (!this instanceof Product)
return new Product(ID);
this.ID = ID;
return this;
};
Product.prototype.GetID = function() {
return this.ID;
};
Then, fill the array by calling new Product(7) or whatever the ID is.
First, one problem is that prototype methods are associated when the object is created, so assigning to an object's prototype will not work:
var Products = [];
Products[0] = {};
Products[0].prototype.foo = function () { return 'hello' } // ***
Products[0].foo(); // call to undefined function
(*** Actually, the code fails here, because prototype is undefined.)
So in order to attach objects, you'll need to assign actual functions to the object:
Products[0].foo = function () { return 'hello'; };
You can create a helper function to do so:
var attachFoo = (function () { // Create a new variable scope, so foo and
// bar is not part of the global namespace
function foo() { return this.name; }
function bar() { return 'hello'; }
return function (obj) {
obj.foo = foo;
obj.bar = bar;
return obj; // This line is actually optional,
// as the function /modifies/ the current
// object rather than creating a new one
};
}());
attachFoo(Products[0]);
attachFoo(Products[1]);
// - OR -
Products.forEach(attachFoo);
By doing it this way, your obj.foos and obj.bars will all be referencing the same foo() and bar().
So, if I'm getting this all correctly, this is a more complete example of KOGI's idea:
// Create a person class
function Person( firstName, lastName ) {
var aPerson = {
firstName: firstName,
lastName: lastName
}
// Adds methods to an object to make it of type "person"
aPerson = addPersonMethods( aPerson );
return aPerson;
}
function addPersonMethods( obj ) {
obj.nameFirstLast = personNameFirstLast;
obj.nameLastFirst = personNameLastFirst;
return obj;
}
function personNameFirstLast() {
return this.firstName + ' ' + this.lastName;
}
function personNameLastFirst() {
return this.lastName + ', ' + this.firstName;
}
So, with this structure, you are defining the methods to be added in the addPersonMethods function. This way, the methods of an object are defined in a single place and you can then do something like this:
// Given a variable "json" with the person json data
var personWithNoMethods = JSON.parse( json ); // Use whatever parser you want
var person = addPersonMethods( personWithNoMethods );
You could do this...
function product( )
{
this.getId = product_getId;
// -- create a new product object
}
function product_getId( )
{
return this.id;
}
This way, although you will have several instances of the product class, they all point to the instance of the function.
Could try doing something like this (without jquery)
Basic prototypal object:
function Product(id){
this.id = id;
}
Product.prototype.getId() = function(){return this.id;};
var Products = [];
Products[0] = new Product(7);
Products[1] = new Product(8);
Products[2] = new Product(9);
alert(Products[2].getId());
IMO I found a pretty good answer right here:
Return String from Cross-domain AJAX Request
...I could serialize my
data in the service as a JSON string
and then further wrap that in JSONP
format? I guess when it comes over to
the client it would give the JSON
string to the callback function.
That's not a bad idea. I guess I would
also have the option of sending a
non-JSON string which might allow me
to just use eval in the callback
function to create new Person objects.
I'm thinking this would be a more
efficient solution in both speed and
memory usage client-side.