How can I programmatically assign knockout js attributes to a view model?
I need some kind of factory structure, but I'm not sure about the best way to go about it...
I'm trying to assign attributes that don't exist yet to a model..
I want to reduce this:
function aViewModel() {
var self = this;
self.attr1Text = ko.observable("");
self.attr1Text.focused = ko.observable(false);
self.attr2Text = ko.observable("");
self.attr2Text.focused = ko.observable(false);
self.attr3Text = ko.observable("");
self.attr3Text.focused = ko.observable(false);
self.attr4Text = ko.observable("");
self.attr4Text.focused = ko.observable(false);
self.attr5Text = ko.observable("");
self.attr5Text.focused = ko.observable(false);
}
vm = new aViewModel();
ko.applyBindings(vm);
to something like this:
function aViewModel(attrs) {
var self = this;
for (var attr of attrs){
self[attr+"Text"] = ko.observable("");
self[attr+"Text"]["focused"] = ko.observable(false);
}
}
attr_list = ["attr1", "attr2", ..., "attrN"]
vm = new aViewModel(attr_list);
ko.applyBindings(vm);
And have it actually work.
This works:
function aViewModel() {
var self = this;
}
vm = new aViewModel();
attr_list = ["attr1", "attr2", ..., "attrN"]
for (var attr of attr_list){
self[attr+"Text"] = ko.observable("");
self[attr+"Text"]["focused"] = ko.observable(false);
}
ko.applyBindings(vm);
But more encapsulation would be nice so I just created a wrapper function,
function createGrid(things, data){
vm = new gridViewModel(things);
for (var thing of things){
vm[thing+"Text"] = ko.observable("");
vm[thing+"Text"]["focused"] = ko.observable(false);
}
\\ other stuff with data
return vm;
}
then used
vm = createGrid(things, data)
ko.applyBindings(vm);
I feel like I'm answering my own question. But is there a better way to do this?
use the knockout mapping plugin
var modelClass = function(data){
ko.mapping.fromJS(data,this);
//additional values or computed
this.full_name = ko.computed(function(){
return ko.unwrap(this.first_name) + ' ' + ko.unwrap(this.last_name);
}
}
var mapping = {
create : function(options){
return new modelClass(options.data);
}
}
var viewModel = function(){
this.myModel = ko.mapping.fromJS(data,mapping);
}
Related
I think I'm having a closure issue, but I'm having a hard time trying to solve it.
I thought that using $.map would prevent any problem.
See the javascript below (require Zepto or jQuery to work) or access the working code https://jsfiddle.net/rowcp07n/4/:
"use strict";
var log = function(){
var output = document.getElementById('output');
for(var i in arguments){
output.append(JSON.stringify(arguments[i]));
output.appendChild(document.createElement('br'));
output.appendChild(document.createElement('hr'));
}
};
function EmptyAction(){
self = this;
self.play = function(){
self.complete();
};
};
function Scene(row, i){
log('Constructing Scene '+i);
var self = this;
self.index = i;
self.steps = $.map(row, function(col, i){
return new SceneStep(row[i], self, i);
});
}
function SceneStep(col, scene, i){
log('Constructing SceneStep '+i+' for Scene '+scene.index);
var self = this;
self.index = i;
self.action = new EmptyAction();
self.action.parentStep = self;
self.action.complete = function(){
log('Complete handler for SceneStep '+ self.index);
};
self.value = col;
}
var data = [
[1,2,3],
[4,5,6],
[7,8,9]
];
function ViewModel(data){
var self = this;
self.scenes = $.map(data, function(row, i){
return new Scene(row, i);
});
}
var vm = new ViewModel(data);
log('Triggering complete of 0-0');
vm.scenes[0].steps[0].action.complete();
log('calling play() of 0-0');
vm.scenes[0].steps[0].action.play();
I expect to see, at the end of the log, "Complete handler for SceneStep 0"
but I'm getting "Complete handler for SceneStep 2"
Can anyone point to me what I'm missing?
As #JJJ pointed, I forgot to declare the var self in class EmptyAction.
I have a view model called ProductsViewModel
This contains an observableArray of ProductViewModel
A ProductViewModel also contains an observableArray - of ProductPriceViewModel
One feature I have is that I can duplicate a ProductViewModel and insert it into the ProductsViewModel array.
When I clone using:
ko.mapping.fromJS(ko.toJS(itemToCopy));
It doesn't appear to copy correctly - the prices observable array, isn't populated with ProductPriceViewModels - just Object
Here's the view models
var ProductsViewModel = function() {
var self = this;
self.products = ko.observableArray([new ProductViewModel()]);
self.addNewProduct = function() {
self.products.push(new ProductViewModel());
};
self.duplicateProduct = function() {
var itemToCopy = ko.utils.arrayFirst(self.products(), function(item) {
return item.visible();
});
//if i look at itemToCopy.prices() it is an array of ProductViewModel
var newItem = ko.mapping.fromJS(ko.toJS(itemToCopy));
//if i look at newItem.prices() it is an array of Object
self.products.push(newItem);
};
};
var ProductViewModel = function() {
var self = this;
self.name = ko.observable();
self.visible = ko.observable(true);
self.prices = ko.observableArray([new ProductPriceViewModel()]);
self.addPrice = function() {
self.prices.push(new ProductPriceViewModel());
};
};
var ProductPriceViewModel = function() {
var self = this;
self.name = ko.observable();
self.price = ko.observable();
};
I solved this by passing in a mapping configuration like this:
var mapping = {
'prices': {
create: function (options) {
return new ServicePriceViewModel(options.data);
}
}
};
on
var newItem = ko.mapping.fromJS(ko.toJS(productToCopy), mapping);
and changing my ProductPriceViewModel to accept data as a parameter:
var ProductPriceViewModel = function (data) {
var self = this;
self.name = ko.observable();
self.description = ko.observable();
self.price = ko.observable();
self.priceIsFrom = ko.observable();
if (data)
ko.mapping.fromJS(data, {}, this);
};
JSFiddle.
I am trying to push objects for an existing array into a knockout observable array but am failing terrible. How do I get this code to work, and since this code sucks what is the 'proper' way of doing this. Thank you so much
<div data-bind="foreach: Bob">
<div data-bind="text: $data">
</div>
</div>
var Test = function(){
var self= this;
self.Init = function(name){
self.Name = ko.observable(name);
};
};
var ArrayOfTests = function() {
var self = this;
self.Init = function(tests){
self.AllTests = ko.observableArray([]);
for(var i = 0; i <=tests.length-1;i++)
{
self.AllTests.push(tests[i]);
};
};
};
var TestViewModule = function(){
self.Bob = ko.observable();
var temp = new ArrayOfTests();
var tempTest = new Test();
tempTest.Init('one2');
temp.Init([tempTest,tempTest]);
self.Bob= temp;
};
ko.applyBindings(new TestViewModule());
console.log(new TestViewModule());
The answer ended up being, I was adding a null item to the array making the array not work.
You pass 'one2' string in Test.Init, but this method don't accept parameters:
var Test = function(){
var self= this;
self.Name = ko.observable();
self.Init = function(data){
self.Name(data);
};
};
Edit
I completly refactored your viewModel, because current implementation is not correct:
Html:
<div data-bind="with: Bob">
<div data-bind="foreach: AllTests">
<div data-bind="text: $data">
</div>
</div>
</div>
ViewModel:
var Test = function(){
var self= this;
self.Name = ko.observable(name);
self.Init = function(name){
self.Name(name);
};
};
var ArrayOfTests = function() {
var self = this;
self.AllTests = ko.observableArray();
self.Init = function(tests) {
for(var i = 0; i < tests.length; i++) {
self.AllTests.push(tests[i].Name());
};
};
};
var TestViewModule = function(){
var temp = new ArrayOfTests();
var tempTest = new Test();
tempTest.Init('one2');
temp.Init([tempTest,tempTest]);
self.Bob = ko.observable(temp);
};
ko.applyBindings(new TestViewModule());
console.log(new TestViewModule());
Let's say I have three objects like this:
var registryEpisode = function() {
var self = this;
self.registry = ko.observable(new registry());
self.episodeType = ko.observable(new episodeType());
};
var episodeType = function() {
var self = this;
self.id = ko.observable(""),
self.name = ko.observable("");
};
var registry = function() {
var self = this;
self.id = ko.observable(""),
self.name = ko.observable("");
};
Then I have a view model like this:
var vm = function() {
var self = this;
self.registryEpisodeTypes = ko.observableArray([new registryEpisode()]);
self.addRegistryEpisodeType = function (episodeType, registry) {
var regEpisode = new registryEpisode();
regEpisode.registry = registry;
regEpisode.episodeType = episodeType;
self.registryEpisodeTypes.push(regEpisode);
} .bind(this);
}
I'm trying to bind the view model to a table of dropdown lists and have the view model updated each time a registry and episode type is selected, but I need to maintain the relationship between episodesTypes and registries. Thoughts?
Here is a simple implemantion of a chain dropDownLists: jsFiddle
var registryEpisode = function() {
var self = this;
self.registry = ko.observable(new registry());
self.episodeType = ko.observable(new episodeType());
};
var episodeType = function(id, name) {
var self = this;
self.id = ko.observable(id), self.name = ko.observable(name);
};
var registry = function() {
var self = this;
self.id = ko.observable(""), self.name = ko.observable("");
};
var vm = function() {
var self = this;
self.selectedRegistry = ko.observable("");
self.registryEpisodeTypes = ko.observableArray([]);
self.addRegistryEpisodeType = function(episodeType, registry) {
var regEpisode = new registryEpisode();
regEpisode.registry = registry;
regEpisode.episodeType = episodeType;
self.registryEpisodeTypes.push(regEpisode);
}.bind(this);
self.getRegistries = function() {
var hashCheck = {};
var result = ko.observableArray([]);
ko.utils.arrayMap(self.registryEpisodeTypes(), function(item) {
if (hashCheck["" + item.registry.id()] === null || hashCheck["" + item.registry.id()] === undefined) {
hashCheck["" + item.registry.id()] = item.registry.name();
result.push({
"name": item.registry.name(),
"value": item.registry.id()
});
}
});
return result;
}
self.getEpisodes = ko.dependentObservable(function() {
var ret = self.registryEpisodeTypes();
var selectedRegistryItem = self.selectedRegistry();
if (selectedRegistryItem === null || selectedRegistryItem === undefined || selectedRegistryItem === "")
return ;
var result = ko.observableArray([]);
ko.utils.arrayMap(ret, function(item) {
if (item.registry.id() == selectedRegistryItem) result.push({
"name": item.episodeType.name(),
"value": item.episodeType.id()
});
});
console.log(ko.toJSON(result));
return result;
});
}
var viewModel = new vm();
var breakingBad = new registry();
breakingBad.id("1000");
breakingBad.name("Breaking Bad");
viewModel.addRegistryEpisodeType(new episodeType("1", "E1-breakingBad"), breakingBad);
viewModel.addRegistryEpisodeType(new episodeType("2", "E2-breakingBad"), breakingBad);
viewModel.addRegistryEpisodeType(new episodeType("3", "E3-breakingBad"), breakingBad);
var trueBlood = new registry();
trueBlood.id("1001");
trueBlood.name("True Blood");
viewModel.addRegistryEpisodeType(new episodeType("1", "E1-trueBlood"), trueBlood);
viewModel.addRegistryEpisodeType(new episodeType("2", "E2-trueBlood"), trueBlood);
viewModel.addRegistryEpisodeType(new episodeType("3", "E3-trueBlood"), trueBlood);
ko.applyBindings(viewModel);
<div>
<span data-bind="text:selectedRegistry"></span>
</div>
<div>
<select data-bind="options:getRegistries(),optionsText:'name',optionsValue:'value',value:selectedRegistry"></select>
</div>
<div>
<select data-bind="options:getEpisodes(),optionsText:'name',optionsValue:'value'"></select>
</div>
I create a dependentObservable called getEpisodes. Whenever selectedRegistry changes this episodeList calculated again.
I'm trying to achieve a degree of inheritance in JavaScript and here is what I have so far:
function Address() {
this.address1 = ko.observable();
this.address2 = ko.observable();
this.country = ko.observableSafe(null, new Country(-1, '', false));
this.city = ko.observable('');
this.state = ko.observable();
this.province = ko.observable('');
this.zipCode = ko.observable();
this.countryLookupID = '';
this.stateLookupID = '';
ko.computed(function () {
var newCountry = this.country();
if (newCountry) {
this.countryLookupID = newCountry.id.toString();
if (newCountry.international) {
this.clearDomestic();
}
else {
this.clearInternational();
}
}
else {
this.countryLookupID = "";
}, this);
ko.computed(function () {
var newState = this.state();
if (newState) {
this.stateLookupID = newState.id.toString();
}
else {
this.stateLookupID = "";
}
}, this);
}
Address.prototype.clearDomestic = function () { return true; };
Address.prototype.clearInternational = function () { return true; };
function Company() {
this.base = Address;
this.base(this);
this.legalEntityID = ko.observable(0);
this.legalEntityName = ko.observable('');
this.isFemaleOwned = ko.observable(false);
this.isMinorityOwned = ko.observable(false);
this.webAddress = ko.observable();
this.businessPhone = ko.observable();
this.faxNumber = ko.observable();
this.locked = ko.observable(false);
}
Company.prototype.constructor = Address;
Company.prototype.clearDomestic = function () {
this.businessPhone('');
this.state(null);
this.zipCode('');
};
Company.prototype.clearInternational = function () {
this.province('');
};
If you are unfamiliar with the Knockout framework, that is OK, as it's probably not pertinent to this discussion. I haven't seen inheritance done exactly like this anywhere that I've looked. As it currently stands, this works exactly how you think it should. When clearDomestic() is called, the correct version of the function is called in the inherited class and this points to a Company object. If I take out the base and call to base(this), it breaks.
Can anyone explain why this is working? If this is a bad practice, can someone tell me how to rewrite it so it functions the same? I don't really want to include another library to achieve this.
UPDATE
If inside Address you invoke this.clearDomestic() outside of the ko.computed, it tries to call the clearDomestic() attached to Company but then this points to an Address object and so businessPhone is no longer defined.
UPDATE 2
I've moved things around again, and I've settled on this method. It's not ideal, but it's the only way that consistently works.
function Address() {
this.address1 = ko.observable();
this.address2 = ko.observable();
this.country = ko.observableSafe(null, new Country(-1, '', false));
this.city = ko.observable('');
this.state = ko.observable();
this.province = ko.observable('');
this.zipCode = ko.observable();
this.countryLookupID = '';
this.stateLookupID = '';
}
Address.prototype.clearDomestic = function () { return true; };
Address.prototype.clearInternational = function () { };
function Company() {
this.legalEntityID = ko.observable(0);
this.legalEntityName = ko.observable('');
this.isFemaleOwned = ko.observable(false);
this.isMinorityOwned = ko.observable(false);
this.webAddress = ko.observable();
this.businessPhone = ko.observable();
this.faxNumber = ko.observable();
this.locked = ko.observable(false);
ko.computed(function () {
var newCountry = this.country();
if (newCountry) {
this.countryLookupID = newCountry.id.toString();
if (newCountry.international) {
this.clearDomestic();
}
else {
this.clearInternational();
}
}
else {
this.countryLookupID = "";
}
}, this);
ko.computed(function () {
var newState = this.state();
if (newState) {
this.stateLookupID = newState.id.toString();
}
else {
this.stateLookupID = "";
}
}, this);
}
Company.prototype = new Address;
Company.prototype.clearDomestic = function () {
// Since we are entering this method via Address, we need a reference back to a real company object with self.
this.businessPhone('');
this.state(null);
this.zipCode('');
};
Company.prototype.clearInternational = function () {
this.province('');
};
I'm going to have to do the logic in the newstate and newcountry in every object that inherits from Address which makes this far from ideal, but until I find a better suggestion, I'm stuck with this.
I'm not sure I understand the problem exactly, but instead of this.base(this);, try calling this.base.call(this);? (in the first version of your code).