Selecting default option value with knockout - javascript

I'm trying to select a default select option based on one of the property with which I'm populating my select option.
This code is copied straight from #rneimeyer's fiddle. I did tweak it to do what I wanted to do.
So, I have choices as my observableArray.
var choices = [
{ id: 1, name: "one", choice: false },
{ id: 2, name: "two", choice: true },
{ id: 3, name: "three", choice: false }
];
function ViewModel(choices, choice) {
this.choices = ko.observableArray(choices);
};
The difference between rneimeyer's fiddle and mine is that I have choice property added on my object inside the observableArray instead of having a separate observable for the option that we want to be default.
Here's the fiddle on my attempt.
Now I'm checking in my select element tag whether the choice attribute is true or not. And if it is then I want to set the name to the value attribute so that it becomes the default.
<select data-bind="options: choices, optionsText: 'name', value: choice"></select>
I've tested this with simple data model in my fiddle here as well which is working just as I wanted.
I guess what my real query is how to check choice property in the data-bind. I see that optionText is being able to access the name property just fine. Not sure why it isn't same for choice property in value attribute.

I might have misdirected to some people. Also, I apologize for not mentioning the version that I'm using. I'm currently using Knockout 3.0.0 (you'll see why this is important later)
Also, just to note that I'm not saying #XGreen's method is wrong but that wasn't exactly what I was looking for and this might be due to my poor explanation.
Let me first try to clarify what I was trying to accomplish.
First of all, I will be having an array of object with the information for the options.
[
{ id: 1, name: "one", choice: false },
{ id: 2, name: "two", choice: true },
{ id: 3, name: "three", choice: false }
]
Now, what I wanted to do was to data-bind select option to that array with choice true being the default selected one.
I'm not intending to create any extra observable except the array itself which is going to be an observableArray.
After much research I finally found optionsAfterRender attribute for options property in Knockout's Docs.
<select data-bind="options: choices,
optionsValue: 'name',
optionsAfterRender: $root.selectDefault">
</select>
So what optionsAfterRender really does is, on each array element it calls custom function which I've set to check if the choice is true or not and make the value of select option that which has the true.
Do note that ko.applyBindingsToNode does not work on version 2.2.0 which I had in my original fiddle.
function ViewModel(choices) {
this.choices = ko.observableArray(choices);
this.selectDefault = function(option,item){
if(item.choice){
ko.applyBindingsToNode(option.parentElement, {value: item.name}, item);
}
};
};
ko.applyBindings(new ViewModel(choices));
And here's the fiddle for it.

Ok If I understand you want to set the true choice as your default selected value.
First you need to involve id in your drop down so it becomes the value of the options as we will filter our collection based on that unique id
<select data-bind="options: choices, optionsText: 'name', optionsValue: 'id', value: selectedChoice"></select>
As you see now you need to create a new observable called selectedChoice and we are going to populate that observable with the choice that is true using a computed.
var choices = [
{ id: 1, name: "one", choice: false },
{ id: 2, name: "two", choice: true },
{ id: 3, name: "three", choice: false }
];
function ViewModel(choices) {
var self = this;
self.choices = ko.observableArray(choices);
self.trueChoice = ko.computed(function(){
return ko.utils.arrayFirst(self.choices(), function(item){
return item.choice === true;
});
});
self.selectedChoice = ko.observable(self.trueChoice().id);
};
ko.applyBindings(new ViewModel(choices));
the new computed property trueChoice uses the arrayFirst method in order to return the first item in your choices collection that has its choice property set to true.
Now that we have our true choice all we have to do is to set the selected value of the dropdown aka selectedChoice to be the id of that true choice so the item becomes selected in the drop down.
Here is also a working fiddle for this

Added a Gist that disabled the first option in a select drop down list, and work nicely with KO's optionsCaption binding, using a optionsDisableDefault binding:
https://gist.github.com/garrypas/d2e72a54162787aca345e9ce35713f1f
HTML:
<select data-bind="value: MyValueField,
options:OptionsList,
optionsText: 'name',
optionsValue: 'value',
optionsCaption: 'Select an option',
optionsDisableDefault: true">
</select>

You could create a computed that holds the selected items
self.selected_options = ko.computed({
read: function() {
return self.choices.filter(function(item){ return item.choice });
},
write: function(value) {
self.choices.forEach(function(item) { item.choice = value.indexOf(item) > 0;});
}
})
Then bind to that as the selected options.

Related

How to prevent alpaca from generating radio field when the search select has 3 or less options

I have a div and a following javascript:
let usersNotContributingIds = [ 19, 20, 21 ];
let usersNotContributingNames = [ "Flavius K.", "Pogchamp", "Lazy Gopnik" ];
let contributorToBeAddedId; // the value that will be used for further actions
$("#alpaca-search-contributing-users").alpaca({
data: null,
schema: {
type: "object",
enum: usersNotContributingIds,
},
options: {
name: "pls",
label: 'Contributor Fullname',
optionLabels: usersNotContributingNames,
helper: "Select user sou want to add as a contributor",
id: "select2-search",
focus: false,
events: {
change: function() {
console.log(this.getValue().value);
contributorToBeAddedId = this.getValue().value
},
focus: function() {
console.log(this.name);
},
blur: function() {
console.log(this.name + ": blur");
},
ready: function() {
console.log(this.name);
}
},
},
postRender: function(control) {
$('#select2-search').select2();
}
});
Obviously, I want to get the newly set value, or anyhow access the selected value and use it. For example with AJAX and a button.
The problem is, that when I have 3 or less options, Alpaca render the field not as a search, but as a radio-something and the this.getValue() is null.
Is there a way to force Alpaca to NOT USE THE RADIO BUTTONS? I dont want to use them, even if I had only 1 option. Documentation just promtly states, that if there are 3 or less options, it will generate radio buttons instead of select, but it says nothing about the fact, that it breaks everything and that I would not be able to retrieve the value the same way as with select field.
If I am doing something inefficiently or wrong, please tell me, I am new with Alpaca and I just want a neat select dropdown with search, that I can use to pick users from a list with any length. Also, I would like the "null" or "none" option to not be there.
To have your select component rendered you should use the option type and set it to "select".
The issue with the value is because you're using it wrong, to get the value in alpaca you only do this.getValue() and there's no need to add .value.
FYI: If you see the error "This field should have one of the values in Flavius K., Lazy Gopnik, Pogchamp. Current value is: 19" you should update your enum array to have strings instead of ints let usersNotContributingIds = [ "19", "20", "21" ];.
Here's a working fiddle for this.

Dynamically change dropdown select defaults depending on existing values in array

I am new to web development and AngularJS and I have been struggling with how to go about this. Sorry for the bad English.
I use an ng-repeat that creates the correct number of dropdowns I need as this needs to be dynamic. The dropdowns have a label like:
Test1: <dropdown here>
Test2: <dropdown here> ...etc.
I have a HTTP request that returns an array. If the array has "Test1 State1" in it, I would like the dropdown for Test1: to change to State1 on default. (continues with all the Tests)
How can I go about this?
HTML
<div ng-repeat="o in options track by $index">
<label for="{{::$o}}" class="col-xs-3">{{o}}:</label>
<select id="{{::$o}}" ng-model="stateModel"
ng-options="state.changeToState for state in states"
ng-change="onStateSelect(stateModel.platformReleaseNotes, o)">
{{state}}
</select>
</div>
$scope.states = [
{
changeToState: 'State1',
notes: 'Hello World'
},
{
changeToState: 'State2',
notes: 'Goodbye'
},
{
changeToState: 'State3',
notes: ' is State3'
},
{
changeToState: 'State4',
notes: ' is State4'
}
];
You cannot share model if you want to have different values for all drop downs.
ng-model should be different for all drop downs and this can be achieved by having array of drop downs as below.
$scope.dropDowns = [{
dropDownName: 'Test1:',
id: 'test1',
selectedOption: ''
}, {
dropDownName: 'Test2:',
id: 'test2',
selectedOption: ''
}];
see the running example in
http://plnkr.co/edit/jsAn1jwGkQfxXK5I9G6J?p=preview

Meteor Autoform - disable select options in an array of Object fields

I'm wanting to disable an option if it has already been selected in one of the object groups.
So, if I selected "2013" then added another sample, "2013" would not be available in that group, unless that option is changed in the original group.
Is there an easy way to do this that I'm missing? Do I need to reactively update the schema when a selection is made?
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: Object,
optional: true
},
"samples.$.sample":{
type:[String],
autoform: {
type: "select",
options: function () {
return [
{
optgroup: "Group",
options: [
{label: "2013", value: 2013},
{label: "2014", value: 2014},
{label: "2015", value: 2015}
]
}
];
}
}
},
Proof of Concept
I know this post is about 3 years old. However, I came across the same issue and want to provide an answer for all those who also stumbled over this post.
This answer is only a proof of concept and does not provide a full generic and performant solution, that could be used on production apps.
A fully generic solution would require a deep change in the code of how select field options are generated and updated in AutoForm.
Some prior notes.
I am using Autoform >=6 which provides a good API to instantly obtain field and form values in your SimpleSchema without greater trouble. SimpleSchema is included as npm package and Tracker has to be passed to it in order to ensure Meteor reactivity.
Functions like AutoForm.getFieldValue are reactive, which is a real great improvement. However, reactively changing the select options based on a reactive value causes a lot of update cycles and slows the performance (as we will see later).
Using AutoForm.getFormValues is not working, when using it within options of an Object field. While working within Array field, it will not behave reactively in Object fields, thus not update the filtering on them.
Manipulating Options for Arrays of Select Inputs (failing)
You can't use it with array types of fields. It's because if you change the select options, it applies for all your select instances in the array. It will therefore also apply to your already selected values and strips them away, too. This makes your select looks like it is always "not selected"
You can test that yourself with the following example code:
new SimpleSchema({
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: String,
autoform: {
type: "select",
options: function () {
const values = AutoForm.getFormValues('sampleSchemaForm') || {};
const samples = values && values.insertDoc && values.insertDoc.samples
? values.insertDoc.samples
: [];
const mappedSamples = samples.map(x => x.sample);
const filteredOpts = [
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(y => mappedSamples.indexOf(y.value) === -1);
return [
{
optgroup: "Group",
options:filteredOpts,
}
];
}
}
},
}, {tracker: Tracker});
Using fixed values on an Object Field
when taking a closer look at the schema, I saw the maxCount property. This made me think, that if you anyway have a list of max options, you could solve this by having fixed properties on a samples object (by the way: maxCount: 5 makes no sense, when there are only three select options).
This causes each select to have it's own update, that does not interfere the others. It requires an external function, that keeps track of all selected values but that came out be very easy.
Consider the following code:
export const SampleSchema = new SimpleSchema({
samples:{
type: Object,
optional: true,
},
"samples.a":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'a');
}
}
},
"samples.b":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'b');
}
}
},
"samples.c":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'c');
}
}
},
}, {tracker: Tracker});
The code above has three sample entries (a, b and c) which will let their options be computed by an external function.
This function needs to fulfill certain requirements:
filter no options if nothin is selected
filter not the option, that is selected by the current samples select
filter all other options, if they are selected by another select
The code for this function is the following:
function getOptions(samples={}, prop) {
// get keys of selections to
// determine, for which one
// we will filter options
const sampleKeys = Object.keys(samples);
// get sample values to
// determine which values
// to filter here
const sampleValues = Object.values(samples);
const filteredOptiond = [
// note that values are stored as strings anyway
// so instead of parsing let's make them strings
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(option => {
// case 1: nothing is selected yet
if (sampleKeys.length === 0) return true;
// case2: this selection has a
// selected option and current option
// is the selected -> keep this option
if (sampleKeys.indexOf(prop) > -1 && option.value === samples[prop])
return true;
// case 3: this selection has no value
// but others may have selected this option
return sampleValues.indexOf(option.value) === -1;
});
return [
{
optgroup: "Group",
options: filteredOptiond,
}
]
};
Some Notes on this Concept
Good:
-it works
-you can basically extend and scale it to your desired complexity (optgroups, more fields on samples, checking against other fields with other fields etc.)
Bad:
- performance
- bound to a given (or the nearest) form context (see here)
- much more code to write, than for an array.

knockout-kendo issues binding through computed observable

I am attempting to use knockout-kendo.js to declare a kendo dropdownlist control in a knockout forEach template, so that as new items are added to the knockout observable array, new kendo dropdownlists are rendered in the UI.
Initially, I come to realize that I can no longer bind the dropdownlist's selected value to an entire entry object in my dropdownlist's specified 'data' array.
To overcome this issue, I followed the RP Niemeyer's suggestion in the following thread:
Set the binding of a dropdown in knockout to an object
Now, this all works. Great.
My issue is when attempting to add second drop down list to the template, who's data is bound to an array property on the object being returned from the computed observable... (I need to chain the drop down lists so that the first displays all Students, second displays all classes for the student that is currently selected in the first drop down list, third displays all test grades for the class that is currently selected in the second drop down list, etc....)
I created a fiddle based on RP Niemeyer's original fiddle to demonstrate my issue:
Original Fiddle (RP Niemeyer's)
My Fiddle With Issues
I added the below lines to the fiddle:
HTML:
<input data-bind="kendoDropDownList: { dataTextField: 'caption', dataValueField: 'id', data: selectedChoice().shapes, value: selectedShapeId }" />
JS:
this.choices = ko.observableArray([
{ id: "1", name: "apple", shapes: ko.observableArray([ { id: "5", caption: "circle" }, { id: "6", caption: "square" }]) },
{ id: "2", name: "orange", shapes: ko.observableArray([ { id: "5", caption: "circle" }]) },
{ id: "3", name: "banana", shapes: ko.observableArray([ { id: "5", caption: "circle" }, { id: "6", caption: "square" }, { id: "7", caption: "triangle" }]) }
]);
Again, I was expecting that upon the selection changing in the first drop down list (causing selectedId to change, causing selectedChoice to change) would also cause any UI elements bound to 'selectedChoice' or any of selectedChoices' properties, to have their bindings re-evaluated and UI respectively updated.
Am I missing something? Or is there a better way to achieve this 'chaining of drop down list' behavior (while still utilizing a knockout template and kendo drop down list control)?
Let me offer you some advice. Try to avoid accessing properties of an observable's value, as you can see, the dependency detection will not always be able to detect the dependency. You should create a computed observable which does the accessing for you.
var ViewModel = function () {
// ...
this.selectedChoice = ko.computed(function () {
var id = this.selectedId();
return ko.utils.arrayFirst(this.choices(), function(choice) {
return choice.id === id;
});
}, this);
this.selectedChoiceShapes = ko.computed(function () {
var selectedChoice = this.selectedChoice();
return selectedChoice && selectedChoice.shapes;
}, this);
}
Then your bindings becomes:
<input data-bind="kendoDropDownList: {
dataTextField: 'name',
dataValueField: 'id',
data: choices,
value: selectedId }" />
<input data-bind="kendoDropDownList: {
dataTextField: 'caption',
dataValueField: 'id',
data: selectedChoiceShapes,
value: selectedShapeId }" />
updated fiddle
This appears to be a shortcoming of Kendo using Knockout. When Kendo evaluates selectedChoice().shapes it holds onto the array it finds, instead of keeping the entire expression. If you update that specific array with options, you can see them in the second dropdown. The problem is that when you update selectedChoice Kendo does not reevaluate the data to the new shapes array. You can see this behavior in this fiddle.
Open the JS console, set the context to the fiddle (it defaults to the top frame in Chrome`, and run this:
window.vm.choices()[1].shapes.push({"id": "6", "caption" : "Thing"})
And you will see the second dropdown update. Changing the first dropdown doesn't have an effect. You can see that in this fiddle Knockout without kendo reevaluates the entire expression, properly updating the second select options.

dijit.form.filteringselect dynamically change options

I have a dijit.form.FilteringSelect component and I want to change the options dynamically. But I get the store from the dijit.form.FilteringSelectwith its store property; there is no setter function in the store. (It may be a dojo.store.Reader)
So how can I change the option of dijit.form.FilteringSelect? Should I change it directly with DOM? Is there any way to update the store behind dijit.form.FilteringSelect?
there is two type of data store in dojo:
dojo.data.ItemFileReadStore - readonly datastore
dojo.data.ItemFileWriteStore - extension of ItemFileReadStore that adds on the dojo.data.api.Write
In your case, you should use ItemFileWriteStore - it provides functions for modifying data in store.
E.g.:
You have array of countries and you want to use it in filtering select:
[{
abbr: 'ec',
name: 'Ecuador',
capital: 'Quito'
},
{
abbr: 'eg',
name: 'Egypt',
capital: 'Cairo'
},
{
abbr: 'et',
name: 'Ethiopia',
capital: 'Addis Ababa'
}]
First of all you will need to create data store js-variable for ItemFileWriteStore.
<script>
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.form.FilteringSelect");
var storeData = {
identifier: 'abbr',
label: 'name',
items: //YOUR COUTRIES ARRAY
}
</script>
Next step - declare filtering select and itemFileWriteStore in html markup:
<div dojotype="dojo.data.ItemFileWriteStore" data="storeData" jsid="countryStore"></div>
<div dojotype="dijit.form.FilteringSelect" store="countryStore" searchattr="name" id="filtSelect"></div>
And finally create special functions for add/delete/modify items in filtering select:
Add New Item:
function addItem() {
var usa = countryStore.newItem({ abbr: 'us', name: 'United States', capital: 'Washington DC' });
}
I hope here is all clear. Only small note: "identifier" field ("abbr" in our case) must be unique in store
Delete Items - e.g. removing all items with name "United States of America"
function removeItem() {
var gotNames = function (items, request) {
for (var i = 0; i < items.length; i++) {
countryStore.deleteItem(items[i]);
}
}
countryStore.fetch({ query: { name: "United States of America" }, queryOptions: { ignoreCase: true }, onComplete: gotNames });
}
As you can see i have created query, that finds items with name == "United States of America" in data store. After the query is executed, function "gotNames" will be called.
function gotNames removes all items that query return.
And last function - Edit Item
it is similar to the delete function. only one difference:
you should use setValue() method of itemFileWriteStore for changing item property:
countryStore.setValue(item, "name", newValue);
Here - page with working example
I solved the same problem with this sentences, hope it helps someone.
For Dojo version < 1.7
dijit.byId('myId').store.root[{index of select}].innerText='New text';
dijit.byId('myId').store.root[{index of select}].value='New Value';
For Dojo version >= 1.7
dijit.byId('myId').store.data[{index of select}].name='New Text';
dijit.byId('myId').store.data[{index of select}].value='New Value';
To change displayed text (current selected)
dijit.byId('myId').textbox.value='New text';
You can see this properties using Firebug or another debug console.
the properties 'urlPreventCache:true, clearOnClose:true' will force the store to be reloaded
<div data-dojo-type="eco/dojo/data/ItemFileReadStore" data-dojo-props='url:"../json/GetClients", urlPreventCache:true, clearOnClose:true' data-dojo-id="clientStore" ></div>
<div id=proposalClient data-dojo-type="dijit/form/FilteringSelect" data-dojo-props="store:clientStore, searchAttr:'clientName', required:'true', pageSize:'15'" ></div>
and then, on event/callback/handler where you need/want to reset the values just do this
function func-name() {
clientStore.url = "../json/GetClients?param=<your-new-search-conditions>";
clientStore.close();
}

Categories