I'm using cascading dowpdowns in a list.
The user interface is working fine, but the underlying viewmodel is not up to date with the user selections.
I have the following html :
<ul data-bind="foreach: selectedExams">
<li>
<select data-bind="options: $parent.availableExams, optionsCaption: 'Choisir un type...', optionsText: 'examTypeName', value: examtype"></select>
<select data-bind="options: exams, optionsCaption: 'Choisir un examen...' , optionsText: 'examName',value: exam, enable:exams().length"></select>
Remove
</li>
</ul>
<button data-bind="click: add">Add</button>
<pre data-bind="text: ko.toJSON($root.selectedExams, null, 2)"></pre>
js file:
function AppViewModel() {
var self = this;
self.availableExams = [
{
examTypeId: "SCAN", examTypeName: "Scanner", exams: [
{ examId: "SCOEUR", examName: "SCOEUR" },
{ examId: "SANGIO", examName: "SANGIO abdominopelvien" },
{ examId: "SSINUS", examName: "SSINUS sans inj" }
]
},
{
examTypeId: "RX", examTypeName: "Radio", exams: [
{ examId: "RBRAS", examName: "RBRAS" },
{ examId: "RAVBRAS", examName: "RAVBRAS" },
{ examId: "RBASSIN", examName: "RBASSIN 1 inc + rx bilat COXO FEMO 1/2 inc" }
]
},
{
examTypeId: "IRM", examTypeName: "IRM", exams: [
{ examId: "ITETE", examTypeId: "IRM", examName: "ITETE angio IRM enceph" },
{ examId: "IRACHIS", examTypeId: "IRM", examName: "IRACHIS 1/2 segt avec INJ" },
{ examId: "ITHORAX", examTypeId: "IRM", examName: "ITHORAX sans inj" }
]
}
];
self.selectedExams = ko.observableArray([new selectedExam()]);
self.add = function () {
self.selectedExams.push(new selectedExam());
};
self.remove = function (exam) { self.selectedExams.remove(exam) }
}
var selectedExam = function () {
self.examtype = ko.observable(undefined);
self.exam = ko.observable(undefined);
self.exams = ko.computed(function () {
if (self.examtype() == undefined || self.examtype().exams == undefined)
return [];
return self.examtype().exams;
});
}
ko.applyBindings(new AppViewModel());
The result, for 3 lines of various selections is the following :
[
{},
{},
{}
]
I'm expecting to see this for instance :
[
{"SCAN","SCOEUR"},
{"RX", "RBRAS"},
{"IRM", "ITETE"}
]
This is probably a data binding issue, but I don't know where to start to debug this kind of problem.
Please note that I'm using this code in a bootstrap grid.
Any Help appreciated.
Thanks in advance.
The problem is that you're missing the definition of self in your selectedExam constructor function. Currently, your self is actually referencing window (the global context) hence you're ending-up with empty objects being returned.
Try this:
var selectedExam = function () {
var self = this; // <-- add this
self.examtype = ko.observable(undefined);
self.exam = ko.observable(undefined);
self.exams = ko.computed(function () {
if (self.examtype() == undefined || self.examtype().exams == undefined)
return [];
return self.examtype().exams;
});
}
Related
I want to update the observable array in knockout binding handler. But it is not updating. The following code i tried but nothing worked out.
this.DropdownValues = ko.observableArray([
{ id: 0, type: "Arc",Checked:false },
{ id: 1, type: "Eve",Checked:false },
{ id: 2, type: "Ca",Checked:false },
{ id: 3, type: "test",Checked:false },
]);
Code I have written inside binding handler.
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
console.log("true");
valueUnwrapped.map(function(item){
item[Checked]= true; return item;
});
ko.utils.unwrapObservable(value(valueUnwrapped));
But my view still not detecting the values. foreach not refeshing in view.
You were missing quotes around the word Checked. It should be item['Checked'] or item.Checked rather than item[Checked].
ko.bindingHandlers.updateArray = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.unwrap(value);
$("#before").text(JSON.stringify(valueUnwrapped));
console.log("true");
valueUnwrapped.map(function(item){
item['Checked']= true; return item;
});
ko.utils.unwrapObservable(value(valueUnwrapped));
}
}
var viewModel = function(){
var self = this;
self.DropdownValues = ko.observableArray([
{ id: 0, type: "Arc",Checked:false },
{ id: 1, type: "Eve",Checked:false },
{ id: 2, type: "Ca",Checked:false },
{ id: 3, type: "test",Checked:false },
]);
};
ko.applyBindings(new viewModel());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Before:<br>
<span id="before"></span>
<div data-bind="updateArray: DropdownValues">
</div>
<br><br>
After:<br>
<span data-bind="text: ko.toJSON(DropdownValues)"></span>
I have a linked list of objects of the same type:
GET /cards.json
[{
"id": 1,
"lesserCardId": null,
"greaterCardId": 2,
}, {
"id": 2,
"lesserCardId": 1,
"greaterCardId": 3
}, {
"id": 3,
"lesserCardId": 2,
"greaterCardId": null
}]
Resource definition:
DS.defineResource({
name: 'card',
relations: {
belongsTo: {
card: [{
localField: 'lesserCard',
localKey: 'lesserCardId'
}, {
localField: 'greaterCard',
localKey: 'greaterCardId'
}]
}
}
});
js-data correctly reads json and creates object hierarchy. What I want next is to automatically update linked card properties like this:
var card1 = DS.get('card', 1);
var card4 = DS.inject('card', {'id': 4});
card1.greaterCard = card4;
// corresponding liked object properties should be updated automatically
expect(card4.lesserCard).toBe(card1);
expect(card2.lesserCard).toBeUndefined();
I have achieved this without js-data making custom setter like this:
Object.defineProperty(Card.prototype, 'greaterCard', {
set: function (card) {
// custom logic for updating linked list structure
if (card === this.$greaterCard) {
return;
}
this.$greaterCard.lesserCard = this.$lesserCard;
this.$greaterCard = card;
if (card) {
card.lesserCard = this;
}
// updating depending properties
this.$updateCardLevel();
this.$updateCardLevelMax();
},
get: function () {
return this.$greaterCard;
}
});
But js-data introduces it's own property descriptors for relation objects. So this approach cannot be applied. I haven't found a way to hook into js-data relation property setters. I could make a separate service with method like cardService.setGreaterCard(card, greaterCard); which would update list structure, but this wouldn't be so convenient. Is there better way for updating linked objects on property change?
js-data version: 2.9.0
In JSData 2.x you would do this:
var Card = DS.defineResource({
name: 'card',
relations: {
belongsTo: {
card: [
{
localField: 'lesserCard',
localKey: 'lesserCardId',
link: false
},
{
localField: 'greaterCard',
localKey: 'greaterCardId',
link: false
}
]
}
}
});
Object.defineProperties(Card[Card.class].prototype, {
lesserCard: {
set: function (lesserCard) {
this.lesserCardId = lesserCard.id;
lesserCard.greaterCardId = this.id;
},
get: function () {
return Card.get(this.lesserCardId);
}
},
greaterCard: {
set: function (greaterCard) {
this.greaterCardId = greaterCard.id;
greaterCard.lesserCardId = this.id;
},
get: function () {
return Card.get(this.greaterCardId);
}
}
});
In JSData 3.x you would just do this:
store.defineMapper('card', {
relations: {
belongsTo: {
card: [
{
localField: 'lesserCard',
foreignKey: 'lesserCardId',
set: function (Relation, card, lesserCard, originalSet) {
lesserCard.greaterCardId = card.id;
originalSet(lesserCard);
}
},
{
localField: 'greaterCard',
foreignKey: 'greaterCardId',
set: function (Relation, card, greaterCard, originalSet) {
greaterCard.lesserCardId = card.id;
originalSet(lesserCard);
}
}
]
}
}
});
Functionality allows you to add/delete description, title and time for the event.
I can not deal with the duplication(cloning) of the object which is created through v-model = (event.name, event.description and event.date)
All works fine with the removing selected object, it works like this:
deleteEvent: function(index){
if(confirm('Are you sure?')) {
this.events.$remove(index);
}
}
Here's an example of my code for a application to adding and changing events.
var vm = new Vue({
el: '#events',
data:{
event: { name:'', description:'', date:'' },
events: []
},
ready: function(){
this.fetchEvents();
},
methods: {
fetchEvents: function() {
var events = [
{
id: 1,
name: 'TIFF',
description: 'Toronto International Film Festival',
date: '2015-09-10'
},
{
id: 2,
name: 'The Martian Premiere',
description: 'The Martian comes to theatres.',
date: '2015-10-02'
},
{
id: 3,
name: 'SXSW',
description: 'Music, film and interactive festival in Austin, TX.',
date: '2016-03-11'
}
];
this.$set('events', events);
},
addEvent: function() {
if(this.event.name) {
this.events.push(this.event);
this.event = { name: '', description: '', date: '' };
}
},
deleteEvent($index)"
deleteEvent: function(index){
if(confirm('Вы точно хотите удалить данную запись?')) {
this.events.$%remove(index);
}
},
cloneItem: function(index) {
}
}
});
there full code
http://codepen.io/Monocle/pen/ojLYGx
I found undocumented built it extend function Vue.util.extend that is equivalent to jQuery's extend.
In this case, you can avoid the enumerating the object properties
cloneItem: function(index) {
this.events.push(Vue.util.extend({},this.events[index]));
}
Access the cloned object via this.events[index], and then use it's properties to create new object and push it into events array:
cloneItem: function(index) {
this.events.push({ name: this.events[index].name,
description: this.events[index].description,
date: this.events[index].date });
}
Demo: http://codepen.io/anon/pen/MajKZO
I am currently having problems binding data to an observable array in knockoutJS. What I am trying to do is display new values based on the user's selection from a select box.
The fiddle is available at http://jsfiddle.net/jwayne2978/k0coh1fz/3/
My HTML looks like the following.
<select data-bind="options: categories,
optionsText: 'name',
optionsValue: 'id',
value: selectedCategory,
optionsCaption: 'Choose...',
event: { change: categoryChanged }
">
<div data-bind="foreach: values">
<div data-bind="text: name"></div>
</div>
<div data-bind="foreach: categories">
<div data-bind="text: name"></div>
</div>
My JavaScript looks like the following.
var categories = [
{ "name" : "color", "id": "1" },
{ "name" : "names", "id": "2" }
];
var values0 = [ { "name" : "empty1" }, { "name" : "empty2" } ];
var values1 = [ { "name" : "white" }, { "name" : "black" } ];
var values2 = [ { "name" : "john" }, { "name" : "name" } ];
var Category = function(data) {
this.name = ko.observable(data.name);
this.id = ko.observable(data.id);
};
var Value = function(data) {
this.name = ko.observable(data.name);
}
var ViewModel = function(categories, values) {
var self = this;
self.categories = ko.observableArray(ko.utils.arrayMap(categories, function(category) {
return new Category(category);
}));
self.selectedCategory = ko.observable();
self.values = ko.observableArray(ko.utils.arrayMap(values, function(value) {
return new Value(value);
}));
self.categoryChanged = function(obj, event) {
if(self.selectedCategory()) {
console.log(self.selectedCategory());
if("1" == self.selectedCategory()) {
//console.log(values1);
self.values.push(new Value({"name":"test1"}));
} else if("2" == self.selectedCategory()) {
//console.log(values2);
self.values.push(new Value({"name":"test2"}));
}
}
};
};
var viewModel;
$(document).ready(function() {
viewModel = new ViewModel(categories, values0);
ko.applyBindings(viewModel);
});
When a category is changed, what I really want to do is something like this.
self.values.removeAll();
for(var v in values1) {
self.values.push(new Value(v));
}
But that doesn't work and so I simply have the line to push a new value into the observable array.
Also, my iterations on the div for the values and categories are not showing and I am unsure why.
Any idea on what I am doing wrong?
your <select> element is missing a closing tag and causing issues further down in the view.
<select data-bind="options: categories,
optionsText: 'name',
optionsValue: 'id',
value: selectedCategory,
optionsCaption: 'Choose...',
event: { change: categoryChanged }"></select>
updated fiddle: http://jsfiddle.net/ragnarok56/69q8xmrp/
I'm trying to chenge the css class of a li tag when I click on it.
I have this:
Model:
var businessUnitsModel = {
businessUnitsList: ko.observableArray([
{ siteID: "a", title: "business1" },
{ siteID: "b", title: "business2" },
{ siteID: "c", title: "business3" },
{ siteID: "d", title: "business4" }]),
currentSelected: ko.observable(),
selectItem: function (site) { this.currentSelected(site.siteID); }
}
//overall viewModel
var viewModel = {
businessUnits: businessUnitsModel,
};
HTML
<ul class="modal-list" data-bind="'foreach': businessUnits.businessUnitsList">
<li class="filterItem" data-bind="'text': title,
css: { 'filterItemSelect': siteID === $parent.currentSelected },
'click': $parent.selectItem">
</li>
</ul>
CSS
.filterItemSelect {
color:#0069ab;
}
and I can't understand why it is not working.
This is what you are looking for :
JS:
var businessUnitsModel = {
businessUnitsList: ko.observableArray([{
siteID: "a",
title: "business1"
}, {
siteID: "b",
title: "business2"
}, {
siteID: "c",
title: "business3"
}, {
siteID: "d",
title: "business4"
}]),
currentSelected: ko.observable(),
selectItem: function (that, site) {
that.currentSelected(site.siteID);
}
}
//overall viewModel
var viewModel = {
businessUnits: businessUnitsModel,
};
ko.applyBindings(viewModel);
View :
<div data-bind="with :businessUnits">
<ul class="modal-list" data-bind="'foreach': businessUnitsList">
<li class="filterItem" data-bind="'text': title,
css: { 'filterItemSelect': siteID === $parent.currentSelected() },
'click': function(){$parent.selectItem($parent, $data);}"></li>
</ul>
</div>
See fiddle
I hope it helps.
You should use currentSelected function value (i.e. add parentheses) when applying css:
<ul class="modal-list" data-bind="foreach: businessUnitsList">
<li class="filterItem" data-bind="text: title,
css: { 'filterItemSelect': siteID === $parent.currentSelected() },
click: $parent.selectItem">
</li>
</ul>
And script:
var businessUnitsModel = function() {
var self = this;
self.businessUnitsList = ko.observableArray([
{ siteID: "a", title: "business1" },
{ siteID: "b", title: "business2" },
{ siteID: "c", title: "business3" },
{ siteID: "d", title: "business4" }]);
self.currentSelected = ko.observable();
self.selectItem = function (site) {
self.currentSelected(site.siteID);
}
}
ko.applyBindings(new businessUnitsModel());
Fiddle
UPDATE here is update of your markup and view model. You should provide full path to currentSelected() property:
<ul class="modal-list" data-bind="'foreach': businessUnits.businessUnitsList">
<li class="filterItem" data-bind="'text': title,
css: { 'filterItemSelect':
siteID === $parent.businessUnits.currentSelected() },
'click': $parent.businessUnits.selectItem">
</li>
</ul>
And here is fixed problem with model - inside selectItem function this was equal to item which you clicked. Thus you don't want to use self alias to model, you need to specify its name:
var businessUnitsModel = {
businessUnitsList: ko.observableArray([
{ siteID: "a", title: "business1" },
{ siteID: "b", title: "business2" },
{ siteID: "c", title: "business3" },
{ siteID: "d", title: "business4" }]),
currentSelected: ko.observable(),
selectItem: function (site) {
businessUnitsModel.currentSelected(site.siteID);
}
}
//overall viewModel
var viewModel = {
businessUnits: businessUnitsModel,
};
ko.applyBindings(viewModel);
Fiddle