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());
Related
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);
}
Why does my click event not work in the second foreach?
My html:
<div class="row" id="menuBody" data-bind="foreach:categoryArray">
<div class="outer col-md-2" data-bind=" attr:{id:id},event:{mouseover :$parent.mouseOverKnockout,mouseout:$parent.mouseOutKnockout }">
<div class="inner col-md-12">
<label class="label" data-bind="text:name"> </label>
<div class="children" data-bind="css:{changeTopChildren: changeTopChildren}">
<ul data-bind="foreach:$parent.items1" class="childrenUl">
<li data-bind=" text: name,attr:{id:id},click: $parent.selectLi" class="childrenLi col-md-12"></li>
</ul>
</div>
</div>
</div>
</div>
My script:
var modelCategory = function (id, name) {
var self = this;
self.changeTopChildren = ko.observable(false);
self.name = ko.observable(name);
self.id = ko.observable(id);
}
var modelProduct = function (id, name) {
var _self = this;
_self.name = ko.observable(name);
_self.id = ko.observable(id);
_self.selectLi = function () {
alert("li");
console.log(" selectLi");
};
}
var viewModelMenuBody = function () {
var self = this;
self.selectLi = function (tag) {
alert("li");
console.log(" selectLi");
};
self.categoryArray = ko.observableArray();
self.items1 = ko.observableArray();
var temp = null;
self.mouseOverKnockout = function (arg, e) {
temp = arg;
for (var i = 0; i < self.categoryArray().length; i++) {
self.categoryArray()[i].changeTopChildren(false);
}
arg.changeTopChildren(true);
$.getJSON("/Home/getChildrenForMenu", { id: arg.id }, function (rowProduct) {
self.items1.removeAll();
for (var total = 0; total < rowProduct.length; total++) {
var temp = new modelProduct();
temp.id(rowProduct[total].id);
temp.name(rowProduct[total].name);
self.items1.push(temp);
}
});
}
self.mouseOutKnockout = function (arg) {
if (arg!=null)
arg.changeTopChildren(false);
//else
// temp.changeTopChildren(false);
};
(function () {
$.getJSON("/Home/getDataForMenu", null, function (rowCategory) {
for (var total = 0; total < rowCategory.length; total++) {
var temp = new modelCategory();
temp.id(rowCategory[total].id);
temp.name(rowCategory[total].name);
self.categoryArray.push(temp);
}
});
})();
};
var viewModel1 = new viewModelMenuBody();
ko.applyBindings(viewModel1, document.getElementById('menuBody'));
All methods are defined within root viewmodel object. So $parent.selectLi call from the nested foreach binding won't work because $parent context refers to current item of the outer foreach.
Use $root.selectLi instead.
More info: http://knockoutjs.com/documentation/binding-context.html
I'm working on a project where I have a search field that filters an observableArray based on what is typed in the search box.
Here is my html code:
<input class="form-control" placeholder="Search for a burger joint around downtown Indianapolis, IN" data-bind="value: searchInput, valueUpdate: 'afterkeydown', event: { keyup: filterResults }">
Here is my js code:
var BurgerJoint = function(data) {
this.id = ko.observable(data.id);
this.name = ko.observable(data.name);
this.long = ko.observable(data.long);
this.lat = ko.observable(data.lat);
this.comments = ko.observable(data.comments);
}
var ViewModel = function() {
var self = this;
self.searchInput = ko.observable('');
this.burgerList = ko.observableArray([]);
initialBurgerJoints.forEach(function(burgerItem){
self.burgerList.push( new BurgerJoint(burgerItem) );
addmarker(burgerItem.lat, burgerItem.long, burgerItem.id, burgerItem.name, burgerItem.comments);
});
self.burgerList.sort(function (l, r) { return l.name() > r.name() ? 1 : -1 });
self.currentFilter = ko.observable();
self.filterResults = function(){
var value = self.searchInput().toLowerCase();
if(value != ''){
self.burgerList(self.burgerList.filter(function(data){
var startsWith = data.name.toLowerCase().startsWith(value);
return burgerList;
}));
}
return true;
};
self.showmap = function(data) {
viewmarker(data.id());
};
}
ko.applyBindings(new ViewModel());
I'm having issues with the filterResults function. Any suggestions?
Maybe you shouldn't set your original burgerList in filter function. Make a copy/clone of the original first. Try something like
self.filterResults = function(){
var value = self.searchInput().toLowerCase();
if(value != ''){
self.burgerList(self.burgerList().filter(function(data){
return data.name.toLowerCase().startsWith(value);
}));
}else{
// reset with the original burgerList contents here
self.burgerList(self.burgerListClone());
}
};
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);
};
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.