So I have a Select that has its options from a computed. I want to select a default every time the selects options change.
I have tried several different ways of doing it:
subscribe to list - is called before list has returned so changes the value of the observable alright but it dosnt render right because the list changes AFTER.
afterRender - Does not work with this type of binding.
OptionsafterRender - works, as in the fiddle below, HOWEVER its called for every individual item rather then just once on the whole render so strikes me as the Wrong Way to do this.
var rawData = [{
Type: "1",
Color: "Blue",
Name: "Blue Car"
}, {
Type: "2",
Color: "Blue",
Name: "Blue House"
}, {
Type: "1",
Color: "Red",
Name: "Red Car"
}, {
Type: "2",
Color: "Red",
Name: "Red House"
}];
var viewModel = {
FirstSelectedOption: ko.observable(),
SecondSelectOptions: null,
SecondSelectedOption: ko.observable(),
Load: function() {
var self = viewModel;
self.SecondSelectOptions = ko.computed(function() {
var selected = self.FirstSelectedOption();
var returnValue = new Array({
Type: "*",
Color: "All",
Name: "All"
});
var filteredlist = ko.utils.arrayFilter(rawData, function(item) {
return item.Type == selected;
});
returnValue = returnValue.concat(filteredlist);
return returnValue;
}, self);
self.SecondSelectedOption.SetDefault = function() {
// we want the default to always be blue instead 'all', blue might not be the last option
var self = viewModel;
var defaultoption = ko.utils.arrayFirst(self.SecondSelectOptions(), function(item) {
return item.Color == "Blue";
});
self.SecondSelectedOption(defaultoption);
};
}
};
viewModel.Load();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="value: FirstSelectedOption">
<option value="1">Car</option>
<option value="2">House</option>
</select>
<br/>
<select data-bind="options: SecondSelectOptions,
optionsText: 'Name',
value: SecondSelectedOption,
optionsAfterRender: SecondSelectedOption.SetDefault"></select>
http://jsfiddle.net/dt627rkp/
The only way I can think off off the top of my head is a custom binding...and im not even sure that would really be possible without reimplemnting the entire options binding.
I can't be the first one to want this, is there a best practice/way that I'm missing?
The optionsAfterRender callback passes 2 parameters: option (element), and item (data bound to the option). The callback already loops over the options, so no need to reiterate:
self.SecondSelectedOption.SetDefault = function (option, item) {
var self = viewModel;
if (item.Color === 'Blue')
self.SecondSelectedOption(item);
};
Updated fiddle
Ref: from the docs
EDIT: That being said, if you don't want the options to re-evaluate every time,
you could also simply bind the change event with the setDefault method on the first <select>. If I were faced with this code 'issue', I would probably preprocess the data into separate arrays, like in this fiddle
Related
Here is the fiddle:https: https://jsfiddle.net/t5v7fmoq/1/
What I want to achieve:
I want to be able to update checkbox view automatically depending on the recieved state variable (which can have true or false value)
state variables (with initial states) for three checkboxes are:
self.state1 = ko.observable(true);
self.state2 = ko.observable(false);
self.state3 = ko.observable(true);
In the init function I populate observablearray:
self.init = function() {
self.availableItems([
new Item(1, "item1", self.state1(), self.onItemStateChange),
new Item(2, "item2", self.state2(), self.onItemStateChange),
new Item(3, "item3", self.state3(), self.onItemStateChange)
]);
In Item function I set the observable properties and onChnage method:
function Item(id, name, state, onChange) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.state = ko.observable(state);
self.state.subscribe(function(newValue) {
onChange(self, newValue);
});
}
With setTimeout I fake an one-time ajax call, which sets new states:
setTimeout(()=>{
self.state1(false)
self.state2(true)
self.state3(false)
self.availableItems()[0].state(self.state1())
self.availableItems()[1].state(self.state2())
self.availableItems()[2].state(self.state3())
},1000)
But, what I want to achieve, is that I want to avoid typing the following:
self.availableItems()[0].state(self.state1())
self.availableItems()[1].state(self.state2())
self.availableItems()[2].state(self.state3())
I want to code this behaviour and track this statuses using common practice and optimal coding...
I don't have the idea how to approach this problem differently.
I tried using arrays like this (so that later I can use forach and indexing):
setTimeout(()=>{
self.state1(false)
self.state2(true)
self.state3(false)
self.availableItems()[0].state(self.itemStatus()[0])
self.availableItems()[1].state(self.itemStatus()[1])
self.availableItems()[2].state(self.itemStatus()[2])
},1000)
But this does not work as expected.
In Short I would like to learn what coding approach to take to code the behaviour, so that when a new state is recieved from server, the proper state is applied to proper checkbox, and proper checkbox view is updated properly.
General truth: If you create numbered variables (item1, item2, item3), you are doing something wrong. Use arrays.
Depending on how you're getting state updates from the server, the implementation of updateState needs to be changed. My implementation below assumes you're getting an array of Boolean values, e.g. [true, true, false].
It's a good idea to make viewmodels that accept a params object and initialize themselves with it, so that's what the code below does.
function Item(params) {
var self = this;
self.id = ko.observable(params.id);
self.name = ko.observable(params.name);
self.state = ko.observable(params.state);
}
function ItemList(params) {
var self = this;
self.items = ko.observableArray(params.items.map(item => new Item(item)));
self.updateState = function () {
var items = self.items(),
randomStates = items.map(item => Math.random() < 0.5);
randomStates.forEach((state, i) => items[i].state(state));
};
}
var viewModel = new ItemList({
items: [
{id: "item1", name: "Item 1", state: false},
{id: "item2", name: "Item 2", state: false},
{id: "item3", name: "Item 3", state: true},
]
});
ko.applyBindings(viewModel);
.switchName {
font-weight: bold;
}
pre {
position: absolute;
right: 0;
top: 0;
left: 50%;
font-size: smaller;
]
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: items">
<div class="switchBox">
<input type="checkbox" data-bind="checked: state, attr: {id: id}">
<label class="switchName" data-bind="text: name, attr: {for: id}"></label>
</div>
</div>
<button data-bind="click: updateState">Simulate Random Update</button>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
I have been trying to subscribe to when a dropdown value changes. I have the following logic however I cannot seem to get it working.
HTML
<div id="case-pin-#modelItem.CaseID" data-caseid="#modelItem.CaseID" class="row hidden popovercontainer pinBinding">
<select data-bind="options:userPins,
value:selectedPin,
optionsCaption:'-- please select --',
optionsText: 'Name',
optionsValue: 'Id'"></select>
</div>
JS
function UserPinViewModel(caseId) {
var self = this;
self.selectedPin = ko.observable();
self.userPins = ko.observableArray([]);
self.caseId = caseId;
self.selectedPin.subscribe(function (newValue) {
console.log(newValue);
//addCaseToPin(newValue, self.caseId);
});
}
var pinObjs = [];
$(function () {
pinObjs = [];
$(".pinBinding").each(function () {
var caseId = this.getAttribute("data-caseid");
var view = new UserPinViewModel(caseId);
pinObjs.push(view);
ko.cleanNode(this);
ko.applyBindings(view, this);
});
})
The userPins array is populated by an AJAX call to the server as the values in the dropdown are dependent upon another section of the website which can change the values in the dropdown - here the logic I have used to populate the array.
function getPins() {
$.ajax({
type: 'POST',
url: '/Home/GetPins',
success: function (data) {
for (var i = 0; i < pinObjs.length; i++) {
pinObjs[i].userPins(data);
}
},
error: function (request, status, error) {
alert("Oooopppppsss! Something went wrong - " + error);
}
});
}
The actual values in the dropdowns all change to match what is returned from the server however whenever I manually change the dropdown, the subscription event is not fired.
You're using both jQuery and Knockout to manipulate the DOM, which is not a good idea. The whole idea of Knockout is that you don't manipulate the DOM, it does. You manipulate your viewModel.
Using cleanNode is also a code smell, indicating that you're doing things the wrong way. Knockout will handle that if you use the tools Knockout provides.
In this case, I was going to suggest a custom binding handler, but it looks like all you really want is to have a UserPinViewModel object created and applied to each instance of your .pinBinding element in the HTML. You can do that using the with binding, if you expose the UserPinViewModel constructor in your viewModel.
function UserPinViewModel(caseId) {
var self = this;
self.selectedPin = ko.observable();
self.userPins = ko.observableArray([]);
self.caseId = caseId;
self.selectedPin.subscribe(function(newValue) {
console.log(newValue);
//addCaseToPin(newValue, self.caseId);
});
// Pretend Ajax call to set pins
setTimeout(() => {
self.userPins([{
Name: 'option1',
Id: 1
}, {
Name: 'option2',
Id: 2
}, {
Name: 'option3',
Id: 3
}])
}, 800);
// Later, the options change
setTimeout(() => {
self.userPins([{
Name: 'animal1',
Id: 'Elephant'
}, {
Name: 'animal2',
Id: 'Pony'
}, {
Name: 'animal3',
Id: 'Donkey'
}])
}, 4000);
}
ko.bindingHandlers.pin = {
init: () => null,
update: () => null
};
ko.applyBindings({
pinVm: UserPinViewModel
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="case-pin-#modelItem.CaseID" data-bind="with: new pinVm('someCaseId')" class="row hidden popovercontainer pinBinding">
<select data-bind="options:userPins,
value:selectedPin,
optionsCaption:'-- please select --',
optionsText: 'Name',
optionsValue: 'Id'"></select>
</div>
Your getPins function suggests that the .pinBinding elements should correspond to the data being received. In that case, pinObjs should really be a part of your viewModel, and the elements should be generated (perhaps in a foreach) from the data, rather than being hard-coded. I don't know how that works with what I presume is the server-side #modelItem.CaseID, though.
I have a 3 tier cascading select list that I have implemented using knockout.js / jQuery / json.
There may be instances where a select box only has 1 choice within it - in which case I'd like to not force the user to have to manually select it and instead default it to the single value, and cascade down to the next box automatically. Can this be done?
My select lists (the first is currently slightly different because it's generated by MVC Razor view with values supplied direct from view model):
<!--Variants-->
<select class="dim" data-bind="value: selectedDim1" id="Dim1" name="Dim1" onchange="FetchDim2();"><option selected="selected" value="">Select Colour</option>
<option value="Black">Colour:Black</option>
<option value="NAVYBLUE">Colour:Navy Blue</option>
</select>
<select id="Dim2" data-bind="value: selectedDim2, options: ddlDim2, optionsText: 'Text', optionsValue: 'Value', optionsCaption: 'Select Waist Size'" class="dim"></select>
<select id="Dim3" data-bind="value: selectedDim3, options: ddlDim3, optionsText: 'Text', optionsValue: 'Value', optionsCaption: 'Select Leg Length'" class="dim"></select>
My knockout code:
function DDLViewModel() {
this.ddlDim1 = ko.observableArray([]);
this.ddlDim2 = ko.observableArray([]);
this.ddlDim3 = ko.observableArray([]);
this.selectedDim1 = ko.observable();
this.selectedDim1.subscribe(FetchDim2, this);
this.selectedDim2 = ko.observable();
this.selectedDim2.subscribe(FetchDim3, this);
this.selectedDim3 = ko.observable();
this.selectedDim3.subscribe(FetchVariant, this);
}
var objVM = new DDLViewModel();
// Activates knockout.js
ko.applyBindings(objVM);
function FetchDim2() {
$.ajax({
type: 'POST',
url: '/product/getdims/', // we are calling json method
dataType: 'json',
// here we get value of selected dim.
data: { id: 20408,
level: 2,
head: 'Waist Size',
code: $("#Dim1").val()
},
success: function (dims) {
// dims contains the JSON formatted list of dims passed from the controller
objVM.ddlDim2(dims);
objVM.ddlDim3.removeAll();
},
error: function (ex) {
alert('Failed to retrieve dims.' + ex);
}
});
}
function FetchDim3() {
$.ajax({
type: 'POST',
url: '/product/getdims/', // we are calling json method
dataType: 'json',
// here we get value of selected dim.
data: { id: 20408,
level: 3,
head: 'Leg Length',
code: $("#Dim2").val()
},
success: function (dims) {
// dims contains the JSON formatted list of dims passed from the controller
objVM.ddlDim3(dims);
},
error: function (ex) {
alert('Failed to retrieve dims.' + ex);
}
});
}
I guess I (a) need to specify a default value if there is only on choice and (b) force the call of the code that populates the next level down? Not sure how to do either without breaking it all though!
Yikes! Don't mix jQuery and Knockout like that. Don't let jQuery do DOM manipulation (e.g. val(...)) but instead get the selected value from your view model.
This will also greatly simplify your view, as things like those id attributes become irrelevant.
Furthermore, I recommend making the Fetch... methods a dependency for your view model. In my example below I just inline those functions inside the view model constructor function, but you could also wrap them in a service and have that service as a dependency (you'd still have to provide input and success handlers to that service of course).
Another thing, quite needed / useful if you follow the above advice: use the var self = this idiom, instead of reiterating this everywhere / providing the this argument everywhere.
With all those things changed, it becomes trivial to fix your original question. Triggering cascaded updates can be done inside the success functions. Before I show the full snippet, here's the nitty gritty for your actual question:
success: function(dims) {
self.ddlDim3(dims);
if (dims.length === 1) {
self.selectedDim3(dims[0].Value);
}
}
Simply put, this selects the first option if there is only one, and lets Knockout handle updating the DOM (and cascading, if needed).
Here's a full demo based off your original code:
// Fake the Ajax requests:
var $ = {
ajax: function(options) {
if (options.data.level === 2 && options.data.code === "Black") {
options.success([{
Text: "Waist size S",
Value: "S"
}, {
Text: "Waist size M",
Value: "M"
}, {
Text: "Waist size L",
Value: "L"
}]);
}
if (options.data.level === 2 && options.data.code === "NAVYBLUE") {
options.success([{
Text: "Waist size M",
Value: "M"
}]);
}
// Not faking lvl 3 as extensively, but the same would hold as above.
if (options.data.level === 3) {
options.success([{
Text: "Legs 40",
Value: "40"
}]);
}
}
};
function DDLViewModel() {
var self = this;
self.ddlDim1 = ko.observableArray([]);
self.ddlDim2 = ko.observableArray([]);
self.ddlDim3 = ko.observableArray([]);
self.selectedDim1 = ko.observable();
self.selectedDim1.subscribe(FetchDim2);
self.selectedDim2 = ko.observable();
self.selectedDim2.subscribe(FetchDim3);
self.selectedDim3 = ko.observable();
self.selectedDim3.subscribe(FetchVariant);
function FetchDim2() {
console.log(2);
$.ajax({
url: '/product/getdims/',
data: {
id: 20408,
level: 2,
head: 'Waist Size',
code: self.selectedDim1()
},
success: function(dims) {
self.ddlDim2(dims);
self.ddlDim3.removeAll();
if (dims.length === 1) {
self.selectedDim2(dims[0].Value);
} else {
self.selectedDim2(null);
}
}
});
}
function FetchDim3() {
if (!self.selectedDim2()) {
self.ddlDim3.removeAll();
} else {
$.ajax({
data: {
id: 20408,
level: 3,
head: 'Leg Length',
code: self.selectedDim2()
},
success: function(dims) {
self.ddlDim3(dims);
if (dims.length === 1) {
self.selectedDim3(dims[0].Value);
}
}
});
}
}
function FetchVariant() {
// Noop / not provided in question
}
}
ko.applyBindings(new DDLViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="value: selectedDim1">
<option selected="selected" value="">Select Colour</option>
<option value="Black">Colour:Black</option>
<option value="NAVYBLUE">Colour:Navy Blue</option>
</select>
<select data-bind="value: selectedDim2, options: ddlDim2, optionsText: 'Text', optionsValue: 'Value', optionsCaption: 'Select Waist Size'"></select>
<select data-bind="value: selectedDim3, options: ddlDim3, optionsText: 'Text', optionsValue: 'Value', optionsCaption: 'Select Leg Length'"></select>
I'm trying to set up a form on my site, and want to use some dynamic dropdown.
I found Selectize.js, which seems like a good solution, however I'm struggling to find out how to get the ID's from the selected option when I post the form.
As in user selects "Banana" and selectize should return 2 as value for the post
The obvious answer would of course be to change valueField to 'id' however that messes up the createFilter so that's a no go..
I've made a jsfiddle with what I have so far: http://jsfiddle.net/imfpa/Lh3anheq/16/
HTML:
<form>
<select id="item-type" placeholder="Choose type...">
</select>
</form>
javascript:
function hasOwnPropertyCaseInsensitive(obj, property) {
var props = [];
for (var i in obj) if (obj.hasOwnProperty(i)) props.push(i);
var prop;
while (prop = props.pop()) if (prop.toLowerCase() === property.toLowerCase()) return true;
return false;
}
var REGEX = '[a-zA-ZæøåÆØÅ][a-zA-ZæøåÆØÅ ]*[a-zA-ZæøåÆØÅ]';
$('#item-type').selectize({
persist: true,
valueField: 'text',
labelField: 'text',
searchField: ['text'],
options: [
{id: '1', text: 'Apple'},
{id: '2', text: 'Banana'},
{id: '3', text: 'Orange'},
{id: '4', text: 'Cherry'},
],
createFilter: function(input) {
var match, regex;
regex = new RegExp('^' + REGEX + '$', 'i');
match = input.match(regex);
if (match) {
console.log(match[0]);
return !hasOwnPropertyCaseInsensitive(this.options, match[0]);
}
return false;
},
create: true
});
jsFiddle demo (http://jsfiddle.net/json/Lh3anheq/35/)
Okay, based on our discussion in the comments above, you want the selectize.js to return the id of the selected item, and also let users create unique items.
You are right about the id: you just need to replace the valueField: 'text' with valueField: 'id'.
Now we need to fix the decision making in your function hasOwnPropertyCaseInsensitive.
The first argument in this function is an object of objects. If you see the console output, for this.options of your selectize element, you will see roughly the following structure (valueField is already replaced with id here):
{
idOfItem1: {
id: idOfItem1,
text: textOfItem1
},
idOfItem2: ...
}
Here is what the web inspector prints out for console.log(this.options):
So, we can iterate over all objects and still have the display value in the field text, and this is exactly the string that we need to compare against the user input for uniqueness.
function hasOwnPropertyCaseInsensitive(options, userValue) {
var exists = false;
for (var option in options) {
if (options.hasOwnProperty(option)) {
if (options[option].text.toLowerCase() === userValue.toLowerCase()) {
exists = true;
break; // break from the loop when the match is found. return true works as well.
}
}
}
return exists;
}
Note! The id of an element created by a user will be the same as the display value. I.e. if I add a new element to the list, e.g. Test, it will look like this:
{
Test: {
id: "Test",
text: "Test"
}
}
Please see the jsFiddle demo (http://jsfiddle.net/json/Lh3anheq/35/) and let me know if I missed something.
i have a question regarding the dojotoolkit.
I have a DataGrid and want to get a value inside of it. The tricky part is, that the value is a
TextBox.
My structure for the DataGrid looks like this:
grid = new dojox.grid.DataGrid({
store: store,
structure: [
{
name: "Quantity", field: "Quantity", width: "30px",
formatter: function(item)
{
var tb = new dijit.form.TextBox(
{
name: "quantity",
value: "1",
placeHolder: "quantity",
});
return tb;
}
},
{ name: "Value", field: "Value", width: "auto"}
]
}, "bGrid");
I have furthermore a button which can be clicked.
If the button was clicked, this function is executed:
myClass.prototype.Test = function Test(tItem)
{
var item = tItem;
var val = grid.getItem(item.value); //Value in this case is an integer refering to the position of my item in the grid
var quantity;
var name;
if(val!==null){
var store = grid.store;
quantity = store.getValue(val, 'Quantity');
name = store.getValue(val, 'Value');
}
console.log("Quantity "+quantity+ " Name: "+name);
}
the name variable is set correctly but i don't get anything from quantity.
I would guess that i would get the TextBox but i receive nothing.
Does anybody know how to access the field of the store ?
I think because the Textbox is a widget you can get access to its value by setting an id for it and get the element by
dijit.byId("YourTB").get('value');
Regards, Miriam