I am working on a drop down control that is powered by Boolean values. I have a viewmodel that consists of the lookup dataset array (values and text attributes) and also a dataset that contains a Boolean value that needs to be aligned with that value chosen in the array. So data in the viewmodel represents an actual data item, while lookupdata represents essentially a mapping of value pairs. These two items really need to stay independent of eachother for the purposes of each data item containing only information relevant to it.
I have been able to get a sample working where I brought IsActive outside of the data object as it exists in viewmodel and placed it in viewmodel, however I can't get it to work while it is inside of viewmodel. I have created a JSFiddle that demonstrates functionally what I want the user to see, as well as one where I have the data where it belongs, but the drop down does not work as expected.
Active maps to true and Inactive maps to false. Switching between those should also affect the word true/false on the screen.
http://jsfiddle.net/rodearly/xF78A/11/
<div data-bind="with: data">
<select data-bind="options: $root.lookupData.status, optionsText: 'text', optionsValue: 'value', value: IsActive"></select>
<label>Value: </label>
<span data-bind="text: IsActive"></span>
</div>
<div>Alternative</div>
<div>
<select data-bind="options: lookupData.status, optionsText: 'text', optionsValue: 'value', value: IsActive"></select>
<label>Value: </label>
<span data-bind="text: IsActive"></span>
</div>
function Item(id, name) {
this.id = ko.observable(id);
this.name = ko.observable(name);
}
function getLookupData() {
var lookupData = {};
lookupData.status = [{
text: "Active",
value: true
}, {
text: "Inactive",
value: false
}];
return lookupData;
}
CreateImplantEditViewModel = function (data, lookupData) {
var vm = {};
vm.data = ko.observable(data);
vm.IsActive = ko.observable(false);
vm.lookupData = {};
vm.lookupData.status = ko.observableArray(lookupData.status);
return vm;
};
debugger;
var editImplantVm = CreateImplantEditViewModel({
IsActive: false
},
getLookupData());
ko.applyBindings(editImplantVm);
Thanks for any help,
~David
If I understand what you are attempting to do, your problem lies in the data that you are passing in to the 'CreateImplantEditViewModel' constructor. You need to create IsActive as an observable, otherwise it will never update:
var editImplantVm = CreateImplantEditViewModel({
IsActive: ko.observable(false)
},
getLookupData());
I've updated your jsfiddle, and both dropdowns now update their associated text binding when you change the selected option.
The problem here actually is that the Boolean values are being converted into string values. To get this to work, value needs to be either a string or a number.
Related
I've got a question regarding checkedValue of checked binding in Knockout (http://knockoutjs.com/documentation/checked-binding.html)
Basically, look this fiddle http://jsfiddle.net/jo9dfykt/1/.
Why if I click the button "Add Item" the right choise is selected but if I click the second button "Add Item Two" this not succed?
What are the differences between addItem and addItem2 method in viewModel?
And then, why the with selectedValue shows nothing?
My goal is to bind a list of radio button to an observableArray and save the entire object into an observable. But I want to set the inital value of radio button without search it on observableArray.
Javascript
var viewModel = {
items: ko.observableArray([
{ itemName: 'Choice 1' },
{ itemName: 'Choice 2' }
]),
chosenItem: ko.observable(),
addItem: function() {
this.chosenItem(this.items()[0]);
},
addItem2: function() {
this.chosenItem({itemName: 'Choice 2'});
}
};
viewModel.chosenItem.subscribe(function(newValue){
console.log(newValue.itemName);
}),
ko.applyBindings(viewModel);
HTML
<!-- ko foreach: items -->
<input type="radio" data-bind="checkedValue: $data, checked: $root.chosenItem" />
<span data-bind="text: itemName"></span>
<!-- /ko -->
<input type="button" data-bind="click: addItem" value="Add Item"/>
<input type="button" data-bind="click: addItem2" value="Add Item Two"/>
<div>Selected Value: <span data-bind="text: chosenItem.itemName"></span></div>
Knockout computes the checked state by doing a reference check. I.e.:
checkedValue = $data; // from checkedValue: $data
checked = checkedValue === chosenItem() // from checked: $root.chosenItem
You'll probably know that, in javascript, two objects that look similar are not equal:
{ a: 1 } === { a: 1 } // returns false
That's why you need to rewrite your second button to:
this.chosenItem({itemName: this.items()[0]});
to make sure the reference check passes.
Also, to correctly show the current state, use:
<div data-bind="with: chosenItem">
Selected Value: <span data-bind="text: itemName"></span>
</div>
If you want to circumvent having to point to the elements in your items array, you can use checkedValue: itemName and addItem() { this.chosenItem("Choice 1"); } but this forces you to use unique names.
A better option would be to make viewmodels for your items that have their own add method:
var Item = function(name, selection) {
var self = this;
this.itemName = name;
this.add = function() {
selection(self);
};
};
var items = ["Choice 1", "Choice 2"].map(function(name) {
return new Item(name, vm.chosenItem);
});
In the first example you're providing a reference to the actual item you want selected. In the second you're providing an object which has the same property & value, but its not the actual item in the list merely one which looks like it.
But I want to set the inital value of radio button without search it on observableArray.
Perhaps make chosentItem a computed observable, which relies on a string observable with just the key of the item you want selected. This would allow you to say something like:
self.chosenItemName('Choice2');
I have some problems with knockoutjs writables computes observables I think.
I created a fiddle.
What I need is actually not so hard:
I have the nullable WeightInGramms and VolumeInMilliliters values.
These values should be bound to two input fields (only one of them should be visible).
At the top, the user can choose which of these values he want to use with the radio buttons.
At initialisation, when both of them are null, the "g" radio button should be checked, also when WeightInGramms is not null. When VolumeInMilliliters have some value, the "ml" radio button should be checked.
I used a knockoutjs writable computes observable for this, please correct me if there is a better way to do this!
So, the read function seems to work, when I change the value in the input which are bind to WeightInGramms or VolumeInMilliliters. But when I change the radio buttons nothing happens...
var ViewModel = function (data) {
var self = this;
this.VolumeInMilliliters = ko.observable(data.VolumeInMilliliters);
this.WeightInGramms = ko.observable(data.WeightInGramms);
this.GrammIsSelected = ko.computed({
read: function() {
return (!self.WeightInGramms() && !self.VolumeInMilliliters()) || !self.VolumeInMilliliters();
},
write: function (newValue) {
console.log(newValue);
return newValue;
},
owner: this
});
};
When I change the radio buttons, the corresponding input field should be visible:
<div data-bind="visible: GrammIsSelected">g is active</div>
<div data-bind="visible: !GrammIsSelected()">ml is active</div>
Edit:
When the Form is loaded for the first Time both values will be null -> the "g" button should be checked.
The observables can be initialized with:
null, null
33, null
null, 33
Both can be null, but only one of them can have a value.
If the user types in a value, and then clicks the other radio the value can be applied to the other value.
I hop it is a bit clearer
Some tips:
Make your viewModel (JS) resemble the view (HTML) as much as possible. Additionally, this avoids having to repeat too much markup. In this case, radio buttons are always lists, and so it is most convenient to store the options in an array.
Instead of testing whether GrammIsselected, you should define a selected observable that holds the selected metric. This way if you ever add more options, the code will still work without refactoring.
When to use a computed property? A computed property adds readonly value by calculating a result based on multiple observables/ variables. A writeable computed property does the same, except you can write back changes. This makes it especially useful for 'Select all' style checkboxes (see example 2 in the docs), data validation & transformations.
The absolutely clearest setup for what you want to achieve would be the following:
var ViewModel = function (data) {
this.metrics = [
{ name: 'g', value: ko.observable(data.WeightInGramms) },
{ name: 'ml', value: ko.observable(data.VolumeInMilliliters) }
];
this.selectedMetric = ko.observable(this.metrics[0]);
};
By setting an object as observable (selectedMetric), you can furthermore simplify the markup for the volume/weight input:
<div class="control-group">
<label class="control-label">choose</label>
<div class="controls" data-bind="with: selectedMetric">
<input type="text" data-bind="value: value">
<span class="help-inline" data-bind="text: '(' + name + ')'"></span>
</div>
</div>
Getting the 'final value' of your app would be as easy as retrieving selectedMetric().value().
A computed property isn't super useful here, but for example, if you wanted to provide a way for the user to both set the g/ml with radio buttons and text, you could add the following method to your viewModel:
this.selectedMetricByText = ko.computed({
read: function() {
return this.selectedMetric().name;
},
write: function(value) {
var newMetric = ko.utils.arrayFirst(this.metrics, function(metric) {
return metric.name === value;
}) || false;
this.selectedMetric(newMetric || this.metrics[0]);
}
}, this);
Fiddle
Your write function doesn't write anything, it seems?
Contrary to this other answer, based on my experience I'll give you the advice not to avoid writeable computeds: used wisely they can be very effective!
Note: in my answer I try to remain close to the original design from the question, but if you're able (have resources available) I recommend redesigning things even more based on the answer by #Tyblitz.
Here's the way you could approach this utilizing a computed:
var ViewModel = function (data) {
var self = this;
self.VolumeInMilliliters = ko.observable(data.VolumeInMilliliters);
self.WeightInGramms = ko.observable(data.WeightInGramms);
var _measurementType = ko.observable("volume");
self.MeasurementType = ko.computed({
read: function() {
return _measurementType();
},
write: function (newValue) {
_measurementType(newValue);
self.VolumeInMilliliters(newValue === "volume" ? 0 : null);
self.WeightInGramms(newValue === "mass" ? 0 : null);
}
});
};
ko.applyBindings(new ViewModel({ VolumeInMilliliters: 12 }));
label { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<label>
<input type="radio" name="measurementType" value="volume" data-bind="checked: MeasurementType">
Volume
</label>
<input data-bind="value: VolumeInMilliliters, enable: MeasurementType() === 'volume'">
<label>
<input type="radio" name="measurementType" value="mass" data-bind="checked: MeasurementType">
Weight in gramms
</label>
<input data-bind="value: WeightInGramms, enable: MeasurementType() === 'mass'">
For radio buttons, you need to use the "checked" binding.
http://knockoutjs.com/documentation/checked-binding.html
And for my personal experience (as a KO nija) I have to give you the advice: avoid writeable ko computed.
<input type="radio" name="unitSelector" value="g" data-bind="checked: unit" /> Grams</br>
<input type="radio" name="unitSelector" value="ml" data-bind="checked: unit" /> Millis</br>
Now the view model
var ViewModel = function (data) {
var self = this;
self.unit = ko.observable('g');
self.userValue = ko.observable(data.WeightInGramms);
};
Now the binding should only care about the value entered by the user, you don't need computed here and you don't need two fields...
<input type="text" data-bind="textInput: userValue ">
<span data-bind="text: unit"> </span>
It looks really too simple but that's what you need, as #Jotabe mentioned, you should take measurement and the unit as two separate things... what you do with this thing later, could be done with computed observables.
If this thing doesn't solve your problem then you should tell what you really want...
I am building an HTML page which sends and gets data from a REST Api (as part of an SPA).
For a specific entity, it shall be possible to create its content in several languages (number of languages is variable by user).
Let's imagine an entity with one field: "Field1". I want to be able, in the GUI, to enter the value of Field1 for different languages and then send back to the REST Api a JSON array of those entities.
The idea is to have a language selector and one form that is reused for each language. Field1 of this form shall be binded to an observable object which contains the language and Field1 value. An array would hold those objects.
The following Fiddle shows what I have done and it works as expected (the array is updated accordingly). But being a beginner in Knockout I have the feeling that my solution is really not the best one....
http://jsfiddle.net/rtacsltng/sb4ws0dj/ (fiddle code is also added below)
In particular, in order to update the Array (elementArray), I've subscribed to Field1 but I have the feeling that there might be a more elegant/efficient way to do that.
Also, another question: I haven't declared elementArray as observable, since I am only interested in "observing" its elements. Is it right?
Finally, to increase the complexity, note that in the final version, the field will be declared through a custom binding (see Material Design Lite: How to programatically reset a floating label input text where Roy J nicely explained me how to do so). Do you think its possible to integrate this custom binding in the overall "mechanism"?
Thanks in advance for your help!
HTML
Please choose a language first (en = English, sp = Spanish)
<br>
<br>
<form>
<select data-bind="
options: availableLangs,
optionsCaption: 'Choose Language',
value: chosenLang;
"></select>
<input type="text" id="field1" data-bind="value: field1, valueUpdate: 'keypress'" />
</form>
<br>Value of Field in English: <span data-bind="text: elementArray[0].elementField1"></span>
<br>Value of Field in Spanish: <span data-bind="text: elementArray[1].elementField1"></span>
JS
function EditableElement(lang) {
var self = this;
self.elementLang = lang;
self.elementField1 = ko.observable();
}
function MyViewModel() {
var self = this;
self.availableLangs = [ //Available languages
"en",
"sp"];
self.field1 = ko.observable("");
self.field1.subscribe(function (newValue) {
self.elementArray[self.chosenLangIndex()].elementField1(newValue);
});
self.elementArray = [];
for (var i = 0, arrLength = self.availableLangs.length; i < arrLength; i++) {
self.elementArray.push(new EditableElement(self.availableLangs[i]));
}
self.chosenLang = ko.observable("");
self.chosenLangIndex = ko.observable("");
self.chosenLang.subscribe(function (newValue) {
self.chosenLangIndex(self.availableLangs.indexOf(newValue));
self.field1("");
});
}
var vm = new MyViewModel();
ko.applyBindings(vm);
Entirely updated
Now that I understand what you're trying to do, let's do this completely differently! The input field is now the dependent field: depending on which language is chosen, it reads/writes one of the values you want to save.
I fleshed out the availableLanguages structure and then made a dictionary of savedValues indexed by the language code. The input field is a writable computed that picks one of the savedValues entries based on the chosen language. No clearing the field when you change languages, it is automatically set to whatever is saved in the appropriate place.
I also hid the field when no language is selected, because it doesn't map to anything then.
function MyViewModel() {
var self = this;
//Available languages
self.availableLangs = [{
code: "en",
name: 'English'
}, {
code: "sp",
name: 'Spanish'
}];
self.chosenLang = ko.observable("");
self.savedValues = {};
ko.utils.arrayForEach(self.availableLangs, function(langInfo) {
self.savedValues[langInfo.code] = ko.observable();
});
self.saveValue = ko.computed({
read: function() {
var lang = self.chosenLang();
return lang ? self.savedValues[lang]() : '';
},
write: function(newValue) {
var lang = self.chosenLang();
if (lang) {
self.savedValues[lang](newValue);
}
}
});
}
var vm = new MyViewModel();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Please choose a language first (en = English, sp = Spanish)
<br>
<br>
<form>
<select data-bind="options: availableLangs,
optionsCaption: 'Choose Language',
optionsText: 'name',
optionsValue: 'code',
value: chosenLang;
"></select>
<input type="text" id="field1" data-bind="value: saveValue, valueUpdate: 'input', visible: chosenLang" />
</form>
<div data-bind="foreach: availableLangs">
<br>Value of Field in <span data-bind="text:name"></span>: <span data-bind="text: $parent.savedValues[code]()"></span>
</div>
I have an array of texts that I display in a <select>.
The texts may have different version no, and I want to filter the <select> based upon the latest version only.
I guess there are more elegant ways to do it (suggestions welcome), but I´ve chosen to use 2 <select>s set to alternate visibility depending on the checkbox.
The code is a hack, but the result looks pretty good. Unfortunately there´s a bug.
I have two observables indicating the selected option in their respective arrays:
self.SelectedText = ko.observable();
self.SelectedUnique = ko.observable();
Both have subscriptions, but I cannot link them together in both subscription, so I have chosen one to be indipendant on the other like this:
self.SelectedUnique.subscribe(function (text) {
if (text) {
self.SelectedText(text);
}
});
However, the get out of sync.
Scenario 1: select text 1,2,3. [OK]
Scenario 2: select text 2; check "Latest versions only"
This causes no options ("Choose…") to be displayed. Not what I want.
It gets worse.
Scenario 3: uncheck; select text 3; Then check "Latest versions only" again.
Now the select option chosen is set to select option no 2 of the unfiltered.
There´s probably a simple issue. I just can´t make it work probably. Here´s the fiddle: Fiddle: http://jsfiddle.net/h5mt51gv/6/
All help and suggestions appreciated!
I have streamlined your approach:
the <select> binds to a computed list of options (visibleTextBatches)
this computed list depends on the state of the checkbox (latestOnly), effectively toggling between the full and the filtered list
the filtered list (latestTextBatches) is another computed that holds the latest version for each group
the <select> stores the actual selected TextBatch object in an observable (selectedTextBatch)
there is a subscription to visibleTextBatches that causes the latest selectable TextBatch to become the current one when the list is filtered. When the list is unfiltered, it does nothing.
function TextBatch(data) {
this.textbatchId = data.textbatchId;
this.parentId = data.parentId;
this.version = data.version;
this.title = ko.observable(data.title);
}
function ViewModel() {
var self = this;
// read up on the mapping plugin, too
self.textBatches = ko.observableArray([
new TextBatch({textbatchId: 1, parentId: 1, version: 1, title: "TB1.1"}),
new TextBatch({textbatchId: 2, parentId: 1, version: 2, title: "TB1.2"}),
new TextBatch({textbatchId: 3, parentId: 3, version: 1, title: "TB2.1"})
]);
self.selectedTextBatch = ko.observable();
self.latestOnly = ko.observable(false);
self.latestTextBatchGroups = ko.computed(function () {
var latest = {};
ko.utils.arrayForEach(self.textBatches(), function (batch) {
if (!latest.hasOwnProperty(batch.parentId) ||
batch.version > latest[batch.parentId].version
) latest[batch.parentId] = batch;
});
return latest;
});
self.latestTextBatches = ko.computed(function () {
return ko.utils.arrayFilter(self.textBatches(), function (batch) {
return batch === self.latestTextBatchGroups()[batch.parentId];
});
});
self.visibleTextBatches = ko.computed(function () {
return self.latestOnly() ? self.latestTextBatches() : self.textBatches();
});
self.visibleTextBatches.subscribe(function () {
var selectedBatch = self.selectedTextBatch();
if (selectedBatch && self.latestOnly()) {
self.selectedTextBatch(
self.latestTextBatchGroups()[selectedBatch.parentId]
);
}
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
<select data-bind="
options: visibleTextBatches,
optionsText: 'title',
optionsCaption: 'Select...',
value: selectedTextBatch
" />
</div>
<div>
<input type="checkbox" id="chkLatestOnly" data-bind="checked: latestOnly" />
<label for="chkLatestOnly">Latest only</label>
</div>
<hr />
<pre data-bind="text: ko.toJSON($root, null,2)"></pre>
This puts a checkbox next to each item of a list where changing the checked status adds/removes that value from the SelectedItems array:
<script type="text/x-jquery-tmpl" id="tmpl">
<input name="cSelect"
type="checkbox"
value="${ ID }"
data-bind="checked: VM.SelectedItems" />
<!-- Add other item content here -->
</script>
VM.SelectedItems = ko.observeableArray([]);
At any point, SelectedItems contains the ids of the checked items.
What if I wanted the checkbox to add and remove an object to SelectedItems? For example, I want to store an actual object of { id : 3, checked : true } instead of serializing it for the value attribute?
When using the checked binding against an array, it will only work with an array of primitives.
One option is to create a computed observable that builds your array of objects from the selected ids.
var ViewModel = function() {
var self = this;
this.items = [{id: 1, name: "one"}, {id: 2, name: "two"}, {id: 3, name: "three"}];
this.selectedIds = ko.observableArray();
this.selectedItems = ko.computed(function() {
return ko.utils.arrayMap(self.selectedIds(), function(id) {
return ko.utils.arrayFirst(self.items, function(item) {
return item.id == id; //selected id will be a string
});
});
});
};
ko.applyBindings(new ViewModel());
If you are dealing with a large number of items, then you might want to build an object that is an index of the items by key, so that you can loop through selectedIds and directly grab each object to reduce the amount of looping.
Here is a sample: http://jsfiddle.net/rniemeyer/pQQsY/
With KnockoutJS 3.0.0 you can use the checkedValue parameter:
<input name="cSelect" type="checkbox" value="${ ID }" data-bind="checkedValue: $data, checked: VM.SelectedItems" />
If your binding also includes checkedValue, this defines the value
used by the checked binding instead of the element’s value attribute.
This is useful if you want the value to be something other than a
string (such as an integer or object), or you want the value set
dynamically.
More details in the documentation
We can use like
<input type="checkbox" data-bind="attr: {value: JSON.parse($data).Id}, checked: $root.selectedIds"/>
and write a click event in the checkbox to get a selected data or subscripe method for selectedIds and get the selected id entire details as a JSON and we have to use JSON.parse to get the data.
but I don't how to store entire object with out JSON.