Knockout Binding not updating with foreach select options - javascript

I have two selections, the seconds options options are dependent on the first selection.
Both of the selection boxes NEED to have a class attached to them, so I can not use the options attribute. The only way I have found to do this is using the foreach method.
I need to track both of the selected values at any time, but the second selection does not update its value when repopulated with new data. Please see the fiddle.
Another requirement is if the original option passed in through the viewmodel creation for the second selection is there, I would like that to be the default when populated. e.g in the fiddle when changing from WPA-PSK to none and then back I would like the default selection to be MIX(passed in) rather than AES.
Fiddle: Link to Fiddle
function ViewModel(security) {
var self = this;
self.authenticationMode = ko.observable(security.authenticationMode);
self.encryptionType = ko.observable(security.encryptionType);
self.authenticationModes = ko.observableArray([{translationText: "None", mode: "NONE", translationClass: "T_WPA-PSK"},
{translationText: "Open", mode: "OPEN", translationClass: "T_WPA-PSK"},
{translationText: "WPA-PSK", mode: "WPA-PSK", translationClass: "T_WPA-PSK"}]);
self.encryptionTypes = ko.computed(function () {
console.log(self.authenticationMode());
if (self.authenticationMode() === 'OPEN')
return [{translationText: "WEP", type: 'WEP', translationClass: "T_WEP"}];
if (self.authenticationMode() === 'WPA-PSK')
return [{translationText: "AES", type: 'AES', translationClass: "T_AES"},
{translationText: "MIX", type: 'MIX', translationClass: "T_MIX"}];
return [];
});
}
ko.applyBindings(new ViewModel({authenticationMode: 'WPA-PSK', encryptionType: 'MIX'}));
<select data-bind="value: authenticationMode, foreach: authenticationModes">
<option data-bind="text: translationText, value: mode, attr: { class: translationClass }"></option>
</select>
<select data-bind="value: encryptionType, foreach: encryptionTypes">
<option data-bind="text: translationText, value: type, attr: { class: translationClass }"></option>
</select>
<p>Selected Authentication: <b data-bind="text:authenticationMode"></b></p>
<p>Selected Encryption Type: <b data-bind="text:encryptionType"></b></p>
Fiddle Using Options - Another way I found using options to set the class with optionsAfterRender method.

Updated your fiddle:
http://jsfiddle.net/fS8qp/7/
Both requirements are met by adding the following in your viewmodel:
self.defaultEncryptionType = security.encryptionType;
self.authenticationMode.subscribe(function () {
self.encryptionType(self.defaultEncryptionType); // This will select the passed in encryptionType, if it exists in the current list of options
self.encryptionType.valueHasMutated(); // This will trigger an update of the observable
});

Related

How to set default value on an input box with select2 initialized on it?

How do I set default value on an input box with select2? Here is my HTML:
<input type="text" id="itemId0" value="Item no. 1">
and my javascript:
$("#itemId0").select2({
placeholder: 'Select a product',
formatResult: productFormatResult,
formatSelection: productFormatSelection,
dropdownClass: 'bigdrop',
escapeMarkup: function(m) { return m; },
minimumInputLength:1,
ajax: {
url: '/api/productSearch',
dataType: 'json',
data: function(term, page) {
return {
q: term
};
},
results: function(data, page) {
return {results:data};
}
}
});
function productFormatResult(product) {
var html = "<table><tr>";
html += "<td>";
html += product.itemName ;
html += "</td></tr></table>";
return html;
}
function productFormatSelection(product) {
var selected = "<input type='hidden' name='itemId' value='"+product.id+"'/>";
return selected + product.itemName;
}
Here is the issue:
If I won't initialize my input box into a select2 box, I can display the default value of my input box which is "Item no. 1":
but when I initialize it with select2 eg. $("#itemId0").select2({code here}); I can't then display the default value of my text box:
Anyone knows how can I display the default value please?
You need to utilize the initSelection method as described in Select2's documentation.
From the documentation:
Called when Select2 is created to allow the user to initialize the selection based on the value of the element select2 is attached to.
In your case, take a look at the Loading Remote Data example as it shows how to incorporate it along with AJAX requests.
I hope this helps.
Old initial selections with initSelection
In the past, Select2 required an option called initSelection that was
defined whenever a custom data source was being used, allowing for the
initial selection for the component to be determined. This has been
replaced by the current method on the data adapter.
{
initSelection : function (element, callback) {
var data = [];
$(element.val()).each(function () {
data.push({id: this, text: this});
});
callback(data);
}
}
You can use the set value too.
You should directly call .val on the underlying element
instead. If you needed the second parameter (triggerChange), you
should also call .trigger("change") on the element.
$("select").val("1").trigger("change"); // instead of $("select").select2("val", "1");
Refs:
https://select2.github.io/announcements-4.0.html
https://github.com/select2/select2/issues/2086
http://jsfiddle.net/F46NA/7/
If you have an input element, declare it as follows. Remember to populate the value field as well.
<input type="hidden" name="player" id="player" data-init-text="bla bla" value="bla bla" >
Write an initSelection function
initSelection : function (element, callback) {
var elementText = $(element).attr('data-init-text');
callback({"text":elementText,"id":elementText});
}
make sure that this call back has same key value pair as the one return by the ajax call.
callback({"text":elementText,"id":elementText});
I have noticed that keeping the value field empty will keep the input empty by default so remember to populate it as well.
The method initSelection can not have an empty value attribute to work properly.
That was my problem.
Hopefully this will help someone.
You can easily change the value of your select input by just putting the id's of your selected option in data attribute of your select input. And then in javascript
var select = $('.select');
var data = $(select).data('val');
$(select).val(data);
And after that initialize select2 on your select input
$(select).select2();
Here is the fiddle https://jsfiddle.net/zeeshanu/ozxo0wye/
you dont need ajax or json, you just need to put SELETED tag into your OPTION and SELECT2 will display the init value.
This works for me: 'Add default value to the head of array'
data.unshift({'id':-1, 'name':'xxx'});
$('#id_xxx).select2({
placeholder: '--- SELECT ---',
data: data,
allowClear: true,
width: '100%'
});
However, official doc says:
You can set default options by calling $.fn.select2.defaults.set("key", "value").
There is no example though.
I found the extremly simple solution.
You have to pass the ID and the Name of the default option and insert it like HTML inside
var html = ' <option value="defaultID">defaultName</option>';
$(".js-example-basic-single").html(html);
How to get defaultID or defaultName depends on your code.
For instance in ASP .Net MVC you can do it like
<select id="PersonalID" class="js-example-basic-single form-control form-control-sm" name="PersonalID">
<option value="#Model.PersonalID">#ViewBag.PersonalInfo</option>
</select>
I use another approach in this specific configuration:
- multiple="multiple"
- populating from AJAX on user's search
$("#UserID").select2({
placeholder: 'Input user name',
"language": {
"noResults": function () {
return "Sorry, bro!";
}
},
dropdownParent: $("#UserID").parent(),
ajax: {
delay: 200,
url: '#Url.Action("GetUserAsJSON", "AppEmail")',
cache: true,
dataType: 'json',
data: function (params) {
var query = {
search: params.term,
page: params.page || 1
};
// Query parameters will be ?search=[term]&page=[page]
return query;
}
}
});
There are few steps to get working my solution
1) Keep each added value in a global array.
var selectedIDs = new Array();
$("#UserID").on('change', function (e) {
//this returns all the selected item
selectedIDs = $(this).val();
});
2) So when you save data you always have selectedIDs array.
3) When you refresh/load webpage just populate selectedIDs with saved data for later resaving/editing from one hand and from another hand populate select2 object
In my case of ASP MVC it looks like this but you can use JQuery to insert <option> to <select>.
<select id="UserID" class="js-example-basic-multiple form-control border" name="UserID" style="width:100%!important;" multiple="multiple">
foreach (var item in Model.ToUsers)
{
<option selected="selected" id="#item.ID" value="#item.ID">#item.Value</option>
}
</select>

Knockout.js - select value not set

I am using knockout.js, and it's not setting the value of an empty option (Four):
<select data-bind="value: item.widgetValue, attr: {id: item.widgetName, name: item.widgetName}, options: item.options, optionsText: ‘label’, optionsValue: ‘value’” id=”fld-“ name=”fld0”>
<option value=”one”>One</option>
<option value=”two”>Two</option>
<option value=”three”>Three</option>
<option value>Four</option>
...
</select>
This is creating a problem: when you're on any option and try to select Four, it selects One; it will only select Four the second time you try to select it.
I have tried changing the knockout data-bind to fix it:
value: $.trim(item.widgetValue)
This allows you to select Four immediately, but incorrectly shows One as being selected after you submit the form with Four selected.
Any ideas as to what could be causing this, or how to fix it?
You shouldn't be manually setting options if you are using the options binding on your select element. If those are being dynamically created by the binding (ie. you are actually using item.options for your source) then check the objects you are binding the select element to -
item.options probably looks like this (missing a value or is somehow not like the other options) -
item.options = [
{ label: 'someLabel1', value: 'someValue1' },
{ label: 'someLabel2', value: 'someValue2' },
{ label: 'someLabel3', 'someValue3' }
];
but should be a more uniform object like this (well defined model) -
function optionModel(label, value) {
var self = this;
self.label = ko.observable(label);
self.value = ko.observable(value);
}
item.options = [
new optionModel('someLabel1', 'someValue1'),
new optionModel('someLabel2', 'someValue2'),
new optionModel('someLabel3', 'someValue3')
];

Knockout won't handle numeric array for multiple select list

I am trying to get a numeric array to bind to my select element. I am using a numeric array because that is what the server is returning. I have a jsFiddle setup demonstrating the problem.
HTML
<select data-bind="selectedOptions: EnvironmentIds" id="EnvironmentIds" multiple="multiple" name="EnvironmentIds">
<option selected="selected" value="1">Hosting</option>
<option value="2">Internal</option>
</select>
Script
function IncidentViewModel() {
var self = this;
//Properties
self.EnvironmentIds = ko.observableArray([1]);
}
var incidentViewModel = new IncidentViewModel();
ko.applyBindings(incidentViewModel);
If you simply switch to a string array like so:
ko.observableArray(["1"])
Then the code works as expected. However, I don't want to use a string array since that isn't the type of the data. Is there a way around this or have I overlooked something simple?
Knockout's options binding uses ko.selectExtensions to give option elements a non-string value. A simple custom binding can do this to the existing options.
ko.bindingHandlers.makeOptionsNumeric = {
init: function(element) {
ko.utils.arrayForEach(element.options, function(option) {
ko.selectExtensions.writeValue(option, +option.value);
});
}
};
Add this to the bindings for your select element.
data-bind="makeOptionsNumeric, selectedOptions: EnvironmentIds"
The order is important. makeOptionsNumeric must run before selectedOptions. If you're not yet using Knockout 3.0, you'll need to give some dummy value to the binding (for example makeOptionsNumeric: {}).
jsFiddle: http://jsfiddle.net/mbest/gUdPq/
JS:
function IncidentViewModel() {
var self = this;
//Properties
self.Environments = [{id: 1, name: 'Hosting'}, {id: 2, name: 'Internal'}]
self.EnvironmentIds = ko.observableArray([1]);
}
var incidentViewModel = new IncidentViewModel();
ko.applyBindings(incidentViewModel);
HTML:
<select data-bind="options: Environments,
optionsText: 'name',
optionsValue: 'id',
selectedOptions: EnvironmentIds" id="EnvironmentIds" multiple="multiple" name="EnvironmentIds">
</select>

Knockout Foreach Javascript function

Is it possible to declare a javascript function in your knockout foreach binding? I want to generate a list from a javascript function outside of my view model.
<select class="form-control" data-bind="foreach: { data: function() { // return list values } }">
<option data-bind="text: Value, attr: { value: Value }"></option>
</select>
No quite sure what you are trying to achieve with <option data-bind="text: Value, attr: { value: Value }"></option>.
Have a look at my fiddle for a few different setups. It contains usages of both options and foreach bindings on a select element.
To answer the question, you provide a function in your viewModel to provide the desired data. As long as your viewModel can "access" the data, the function will provide it to the binding. If the data were to be a simple list of literals, then all that is required :
Javascript:
var listOfLiterals = function() { return ["One", "Two", "Three"] };
var viewModel = {
selectedLiteral: ko.observable(),
getLiterals: function() {
return listOfLiterals();
}
}
HTML:
<select data-bind="options: getLiterals(), value: selectedLiteral"></select>

Set initial dropdown value to viewmodel

I'm having some issues with a dropdown list where I need to pass the initial value to the viewmodel. Just to clarify: I'm working on an edit-form, so the dropdownlist will be populated with an already-selected value.
What I have so far is:
Razor:
<select data-bind="selectedOptions: selectedLength">
// razor code omitted
foreach(var preValue in lengthPreValues)
{
if(lengthPreValues.Contains(preValue.value))
{
<option selected="selected" value='#preValue'>#preValue</ option>
}
else
{
<option value='#preValue'>#preValue</option>
}
}
And my viewmodel looks like this:
var editOfferViewModel = {
// Properties omitted
selectedLength: ko.observable("")
};
ko.applyBindings(editOfferViewModel);
While this definately works when selecting a new value, I'm a bit stuck when it comes to setting the initial value. I was fortunate enough to get some great help from Ryan Niemeyer here on stackoverflow.com with checkboxes and creating custom bindinghandlers, but I'm still
having a hard time to figure it out to be honest.
So, any help and/or hint on this is greatly appreciated!
A common and easy way to do this is to serialize your model values to the page. This would be something like:
var viewModel = {
choices: ko.observableArray(#Html.Raw(Json.Encode(Options))),
selectedChoices: ko.observableArray(#Html.Raw(Json.Encode(SelectedOptions)))
};
Then, just use a standard data-bind on your select like:
data-bind="options: choices, selectedOptions: selectedChoices"
You then don't even need to populate the option elements in Razor.
If your viewModel is built in an external file, then you can just set the value of the observables in your view (after your external script has been loaded)
My data is something like :
dataList = [ {name:'length1',id:1},{name:'length2',id:2},{name:'length3',id:3},{name:'length4',id:4},{name:'length5',id:5} ]
And I have been using that data with dropdown like this :
<select name="xxx" id="xxxid" data-bind="options: dataList, value: selectedLength , optionsText: 'name', optionsValue: 'id', optionsCaption: 'Please Select...'"></select>
<select name="xxx2" id="xxxid2" data-bind="options: dataList, selectedOptions: multiSelectedLength , optionsText: 'name', optionsValue: 'id', optionsCaption: 'Please Select...'" size="5" multiple="true"></select>
var editOfferViewModel = {
selectedLength: ko.observable(),
multiSelectedLength: ko.observableArray()
};
ko.applyBindings(editOfferViewModel);
$(document).ready(function() {
// Set initial value
editOfferViewModel.selectedLength(2);
// Set inital multi value
editOfferViewModel.multiSelectedLength(['2','3']);
});
You can use value property to set initial value.
Here is the working example.

Categories