I'm new at knockout and I've been searching for an answer to the following question without finding an answer:
I several lists of radiobuttons and I want to store the selected value of the selected radiobutton in each of the radio button lists in an array (first index in the array should contain the selected value from the first radio button list and so on). Can this be done in any way?
See my code below to see what I'm trying to do. Basically I want to store the selected radio button values in the observableArray selectedProducts.
function Product(id, name, room) {
this.id = id;
this.name = name;
this.room = room;
}
var listOfProds = [
new Product(1, 'Prod1', 1),
new Product(2, 'Prod2', 1),
new Product(3, 'Prod3', 1),
new Product(1, 'Prod1', 2),
new Product(2, 'Prod2', 2),
new Product(3, 'Prod3', 2)
];
var viewModel = {
products: ko.observableArray(listOfProds),
selectedProducts: ko.observableArray()
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: products">
<li>
<input type="radio" data-bind="attr: {name: 'room'+room}, checkedValue: $data, checked: $root.selectedProducts" />[
<span data-bind="text: room"></span>]
<span data-bind="text: name"></span>
</li>
</ul>
<hr/>
<div data-bind="text: ko.toJSON($root)"></div>
You can try to bind onclick event and make selectedProducts manually.
Html:
<input type="radio" data-bind="attr: {name: 'room'+room}, click: $root.addSelectedElement" />
Click binding:
self.addSelectedElement = function(data) {
var productInRoomArr = self.selectedProducts().filter(function(product) {
return product.room == data.room;
});
if (productInRoomArr.length > 0) {
var productInRoom = productInRoomArr[0];
if (productInRoom.id == data.id)
return true;
var index = self.selectedProducts.indexOf(productInRoom);
if (index > -1) {
self.selectedProducts.splice(index, 1);
}
}
self.selectedProducts.push(data);
return true;}
JsFiddle
I think your selectedProducts needs to be an array of observables, rather than an observable array, so that the actual contents are monitored, rather than it's length:
this.selectedProducts=[ko.observable(),ko.observable(),ko.observable() ];
Then you can bind the radioboxes as follows:
<input type="radio" data-bind="
attr: {name: 'room'+room},
checkedValue: $data,
checked: $root.selectedProducts[$data.room-1]"/>
There are probably more flexible ways to map the rooms to the length of selectedProducts (and you may want an observableArray of observables if the number of rooms is not fixed), and the $data.room-1 snippet mightn't generalise if your rooms aren't consecutive integers, but this should give you some direction.
The full code is at http://jsfiddle.net/yq6jx8v7/
Related
I am trying to generate link on the button when multiple Checkbox is clicked based on the value. I have used the below link and it's working fine and I am able to generate link.
Create a dynamic link based on checkbox values
The issue is that when I select the checkbox for the first time it generates a link to /collections/all/blue+green but when I again select/deselect the different value its duplicates and ADDs the values with old Link → to collections/all/blue+green+blue+green
For Live Issue check on mobile View Click on filter on bottom => https://faaya-gifting.myshopify.com/collections/all
$("input[type=checkbox]").on("change", function() {
var arr = []
$(":checkbox").each(function() {
if ($(this).is(":checked")) {
arr.push($(this).val())
}
})
var vals = arr.join(",")
var str = "http://example.com/?subject=Products&checked=" + vals
console.log(str);
if (vals.length > 0) {
$('.link').html($('<a>', {
href: str,
text: str
}));
} else {
$('.link').html('');
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<input type="checkbox" name="selected" value="Blue" class="products"> Blue<br>
<input type="checkbox" name="selected" value="Green" class="products"> Green<br>
<input type="checkbox" name="selected" value="Purple" class="products"> Purple
<br>
<span class="link"></span>
For Live Issue check on mobile View Click on filter on bottom => https://faaya-gifting.myshopify.com/collections/all
I see what's going on now. What's causing the duplicate is actually multiple checkboxes having the checked value.
If you run this code in your console, you should see that you have twice of the item checked as its length:
// Run this in your browser console
$('input[type=checkbox]:checked').length
For example, if I have Wood and Metal checked and clicked Apply, running the code above gives length of 4 instead of just 2. Meaning, you have a duplicate checkbox input for each filter hidden somewhere in your code. I have verified this.
As options, you can:
Try to remove the duplicate checkbox input – Best Option; or
Add class to narrow down your selector to just one of the checkbox input containers.
Here's a screenshare of what's going on: https://www.loom.com/share/2f7880ec3435427a8378050c7bf6a6ea
UPDATED 2020/06/09:
If there's actually no way to modify how your filters are displayed, or add classes to narrow things down, we can opt for an ad hoc solution which is to actually, just remove the duplicates:
// get all your values
var arr = []
$(":checkbox").each(function() {
if ($(this).is(":checked")) {
arr.push($(this).val().toLowerCase())
}
})
// removes duplicates
var set = new Set(arr)
// convert to array and join
var vals = [...set].join(",")
I am bit bad in jquery. Am I doing right?
Should I just add the script which I have written below
$("input[type=checkbox]").on("change", function() {
var arr = []
$(":checkbox").each(function() {
if ($(this).is(":checked")) {
arr.push($(this).val().toLowerCase())
}
})
// removes duplicates
var set = new Set(arr)
// convert to array and join
var vals = [...set].join(",")
var str = "http://example.com/?subject=Products&checked=" + vals
console.log(str);
if (vals.length > 0) {
$('.link').html($('<a>', {
href: str,
text: str
}));
} else {
$('.link').html('');
}
})
Am I right?
Or should I add any values on var set = new Set(arr) or var vals = [...set]
Thank you for you help Algef
I am new in knockoutjs, I saw an example, which moves up and down array values by selecting the dropdown values of the index in the option. But its problem is they dont't move the values correctly. And after changing the array position options in select box will be changed..
var viewModel = function() {
var self = this;
var Item = function(name, pos) {
this.name = ko.observable(name);
this.position = ko.observable(pos);
var oldPosition = pos;
this.position.subscribe(function(newValue) {
self.reposition(this, oldPosition, newValue);
oldPosition = newValue;
}, this);
};
this.items = [
new Item("item Three", "3"),
new Item("item One", "1"),
new Item("item Two", "2"),
new Item("item Five", "5"),
new Item("item Four", "4"),
new Item("item Six", "6")
];
self.orderedItems = ko.computed(function() {
return ko.utils.arrayFilter(this.items, function(item) {
return true;
}).sort(function(a, b) {
return a.position() - b.position();
});
});
self.curName = ko.observable();
self.reposition = function(item, oldPosition, newPosition) {
console.debug("Reposition", item, oldPosition, newPosition);
};
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class='liveExample'>
<ul data-bind="foreach: orderedItems">
<li>
<div> <span data-bind="text: name"> </span> has Position:
<select id="pos" data-bind=" options: orderedItems,
optionsText: 'position',
optionsValue: 'position',
value: position "></select>
</div>
</li>
</ul>
</div>
this is my sample code, i want to show array index position should be show in the dropdown. I want to select the index value in the dropdown the position of the array values should be change but not the opitions. How is it possible with knockout js.
So this one is a little more tricky than normal since you are basing your index on a property of the Item. It's not wrong, it just adds more complexity.
First you have to create the array of "indexes" since you aren't actually changing the index of the items, they are just computed off of the position property.
this.items() has been changed to an observableArray to propagate/bubble changes of the items to other functions. Now you could include an "Add Item" feature, add it to the the items array and everything would updated correctly.
I removed the subscribe function in the Item constructor, it was causing too many issues firing off when it didn't need to. Instead I attached an event handler to the select box that can manage the items and removed the two way binding of the value by getting the value().
Hope this helps and good luck!
var viewModel = function() {
var self = this;
// UPDATED: Removed the subscribe function
var Item = function(name, pos) {
this.name = ko.observable(name);
this.position = ko.observable(pos);
};
// UPDATED: Changed to observable so you can change items here and it will propogate down to the computed functions
this.items = ko.observable([
new Item("item Three", "3"),
new Item("item One", "1"),
new Item("item Two", "2"),
new Item("item Five", "5"),
new Item("item Four", "4"),
new Item("item Six", "6")
]);
// ADDED: Create array of index options based on length
this.positions = ko.computed(function(){
var numArray = [];
for(i = 0; i < self.items().length; i++) {
numArray.push(i + 1)
}
return numArray;
})
self.orderedItems = ko.computed(function() {
return ko.utils.arrayFilter(self.items(), function(item) {
return true;
}).sort(function(a, b) {
return a.position() - b.position();
});
});
self.curName = ko.observable();
/**
* UPDATED: Get item at selected position, change it to the current
* items position, then update current items position to the selected position;
*/
self.reposition = function(item, event) {
var selectedPosition = event.target.value;
var itemAtPosition = ko.utils.arrayFirst(self.items(), function(i){
return i.position() === selectedPosition;
})
itemAtPosition.position(item.position());
item.position(event.target.value)
};
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class='liveExample'>
<ul data-bind="foreach: orderedItems">
<li>
<div> <span data-bind="text: name"> </span> has Position:
<select id="pos" data-bind=" options: positions(),
value: position(), event: { change: reposition} "></select>
</div>
</li>
</ul>
</div>
I'm using knockout.js (v. 3.2.0) to create a series of checkbox lists on a page. For any given list I want checked items to move to the top of the list, and move below the checked items when unchecked.
At first I thought I could use a js function to reorder the list and then call the function after ko.applyBindings which is apparently synchronous (calling it before applyBindingds wouldn't work because the DOM isn't complete yet). Nevertheless, it didn't work. Any idea how to write a function in the view model that will do this?
Here is my markup:
<ul data-bind="foreach: targetingViewModel.filteredTargetingInstances('assets')">
<li>
<label>
<input type="checkbox" data-bind="checked:selected"/>
<span data-bind="text: value"></span>
</label>
</li>
</ul>
And here is the jQuery function I found in another Stack Overflow answer:
addSelectionsToList = function () {
var $list = $(".target-list");
sortItems($list);
function sortItems(list){
var origOrder = list.children();
list.on("click", ":checkbox", function() {
var i,
checked = document.createDocumentFragment(),
unchecked = document.createDocumentFragment();
for (i = 0; i < origOrder.length; i++) {
if (origOrder[i].getElementsByTagName("input")[0].checked) {
checked.appendChild(origOrder[i]);
} else {
unchecked.appendChild(origOrder[i]);
}
}
list.append(checked).append(unchecked);
});
}
};
Thanks.
Check out following Code snippet. hope this is will help you.
Steps I have performed
Created one viewmodel with class Item with subscribe on change of
isChecked property.
There is Obsservable array of list items which
will contain isChecked property.
Finally on change of value it
will call the sorting by selecting value on change in any item.
function SomeViewModel() {
var self = this;
self.chkLists = ko.observableArray([]);
function Item(isChecked, name) {
this.isChecked = ko.observable(isChecked);
this.name = ko.observable(name);
this.isChecked.subscribe(function (newValue) {
self.chkLists.sort(function (l) { return l.isChecked() === false });
})
}
self.chkLists = ko.observableArray([
new Item(false, "Bear"),
new Item(false, "Hippo"),
new Item(false, "Lion"),
new Item(false, "Tiger"),
new Item ( false, "Zebra" )
]);
}
$(document).ready(function () {
var divEle = $("#sortedList")[0];
ko.applyBindings(SomeViewModel, divEle);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="sortedList">
<ul data-bind="foreach: chkLists">
<li>
<label>
<input type="checkbox" data-bind="checked: $data.isChecked" />
<span data-bind="text: $data.name"></span>
</label>
</li>
</ul>
</div>
I have a script that produces an array of forms, with each form affecting the available options for the next form. The awesome martin booth solved the problem of getting the displayed values to update as new forms are added.
however, I have an observable array (defaultSampleRates) that sits outside the forms array, and for the life of me I can't get the form to push items into that array. I've tried declaring it in a dozen different places in a dozen different ways, but it just won't stick.
basically I need the 'Default sample rate' drop-down to show the sample rates that have been selected in the form above (the user must only be able to choose a default sample rate from a displayed one, rather than from the full list).
Any tips much helpo brain pain. fiddle here: http://jsfiddle.net/3lliot/9vsa4hh7/
html:
<body>
<div style="float:left; width:60%">
<div data-bind="foreach: forms">
<div style="float:left; margin-right:20px"> <span>
<!-- This is a *view* - HTML markup that defines the appearance of your UI -->
<p><span style="color:#AB0002">Sample rate element <span data-bind="text: formNum"></span></span>
</p>
<p>Sample rate (Hz):
<select data-bind="options: sampleRates, value: selectedSampleRate"></select>
</p>
</span>
</div>
</div>
<div style="float:left; clear:both; margin-bottom:20px">
<hr/>
<button data-bind="click: addForm">Add <srate> element</button>
<button data-bind="click: removeForm">Remove</button>
<p>Default sample rate:
<select data-bind="options: defaultSampleRates, value: selectedDefaultSampleRate"></select>
</p>
</div>
</div>
<div style="float:right; width:38%; overflow:scroll; border-left:thin; border-left-style:solid; border-left-color:#dfdfdf;padding-left: 1%"> <span class="code"><audio></span>
<ul data-bind="foreach: forms">
<li>
<!-- render the json --> <span class="code"> <srate id="<span data-bind="text: formNum"></span>">
<br/> <sample_rate><span data-bind="text: selectedSampleRate"></span></sample_rate>
<br/> </srate></span>
</li>
</ul> <span class="code"> <default_srate><span data-bind="text: selectedDefaultSampleRate"></span></default_srate></span>
<br/><span class="code"></audio></span>
</div>
</body>
js:
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
//window.onload = startKnockout;
window.onload = startKnockout;
var formNum;
var i = -1;
var selectedSampleRates = [];
function Form(allSampleRates, forms) {
var self = this;
// Declare observables
self.selectedSampleRate = ko.observable();
self.formNum = ko.observable();
self.sampleRates = ko.computed(function () {
var formsValue = forms(),
availableSampleRates = ko.utils.arrayFilter(allSampleRates, function (sampleRate) {
return !ko.utils.arrayFirst(formsValue, function (form) {
if (form != self) {
if (form.selectedSampleRate() === sampleRate) {
if (selectedSampleRates.indexOf(sampleRate) === -1) {
selectedSampleRates.push(sampleRate);
}
}
return form.selectedSampleRate() === sampleRate;
} else {
return form != self;
}
});
});
return availableSampleRates;
});
// count how many srate elements there are
i++;
self.formNum = i;
}
var Vm = function () {
var self = this;
var item = 0,
allSampleRates = ['192000', '176400', '96000', '88200', '48000', '44100'];
// declare observables for options outside the srate elements
self.selectedDefaultSampleRate = ko.observable();
// add remove forms stuff
self.forms = ko.observableArray([]);
self.forms.push(new Form(allSampleRates, self.forms));
item++;
self.addForm = function () {
if (i < 5) {
self.forms.push(new Form(allSampleRates, self.forms));
item++;
} else {
alert("Can't have more than 6 <srate> elements!")
}
};
self.removeForm = function () {
if (item > 1) {
self.forms.splice(item - 1, 1);
item--;
i--;
} else {
alert("Must have at least one <srate> element!")
}
};
// define arrays for options outside srate elements
self.defaultSampleRates = ko.observableArray([]);
return self;
}
// Activates knockout.js
function startKnockout() {
ko.applyBindings(new Vm());
};
You can make use of selectedOptions binding to add defaultSample rate.
I changed select sampleRates code to this
<select data-bind="options: sampleRates, value: selectedSampleRate, selectedOptions: $root.defaultSampleRates"></select>
Notice selectedOptions binding there..
Should work as per your need.
Updated Fiddle Demo here : http://jsfiddle.net/rahulrulez/9vsa4hh7/3/
I hope that's what you wanted.
I have a little knockout form which I am able to duplicate using a button to allow multiple sets of data to be provided.
However, each value for the 'sample rate' select can only be used once. For example, the first form by default is set to 192000. So when I click 'add srate element' the form that is generated should not include 192000 as an option in the Sample rate drop-down.
Obviously if the Sample rate for the first form is set to something else, that value should be removed for the second form, and so on.
How do I remove the already-selected Sample rate from the array when a new form is added, so that it is not available in further copies of the form? It is even possible, given the structure of the view model?
There's a fiddle here: http://jsfiddle.net/3lliot/x3cg131g/
There's a bit of logic included already to prevent more than 6 forms.
Any tips will be appreciated ...
Html code:
<body>
<ul data-bind="foreach: forms">
<li>
<!-- This is a *view* - HTML markup that defines the appearance of your UI -->
<p><span style="color:#AB0002">Sample rate element <span data-bind="text: formNum"></span></span>
</p>
<p>Sample rate (Hz):
<select data-bind="options: $parent.sampleRate, value: selectedSampleRate"></select>
</p>
<p>TDM channels per line:
<select data-bind="options: tdmChans, value: selectedTdmchan"></select>
</p>
</li>
</ul>
<button data-bind="click: addForm">Add <srate> element</button>
<button data-bind="click: removeForm">Remove</button>
<hr/>
<ul data-bind="foreach: forms">
<li>
<!-- render the json -->
<p class="code"><srate id="<span data-bind="text: formNum"></span>">
<br/> <sample_rate><span data-bind="text: selectedSampleRate"></span></sample_rate>
<br/> <tdm_chan><span data-bind="text: selectedTdmchan"></span></tdm_chan>
<br/>
</p>
</li>
</ul>
</body>
JS code:
window.onload = startKnockout;
var formNum;
var i = -1;
function Form() {
var self = this;
// Declare observables
self.selectedSampleRate = ko.observable();
self.selectedTdmchan = ko.observable();
self.formNum = ko.observable();
// Define controls
self.tdmChans = ko.computed(function() {
if (self.selectedSampleRate() == 44100 || self.selectedSampleRate() == 48000) {
return ['2', '4', '8', '16'];
} else if (self.selectedSampleRate() == 88200 || self.selectedSampleRate() == 96000) {
return ['2', '4', '8'];
} else if (self.selectedSampleRate() == 176400 || self.selectedSampleRate() == 192000) {
return ['2', '4'];
} else {
// do nothing
}
}, self);
i++;
self.formNum = i;
}
var Vm = function() {
var self = this;
var item = 0;
self.forms = ko.observableArray([]);
self.forms.push(new Form());
item++;
self.addForm = function() {
if (i < 5) {
self.forms.push(new Form());
item++;
} else {
alert("Can't have more than 6 <srate> elements!")
}
};
self.removeForm = function() {
if (item > 1) {
self.forms.splice(item - 1, 1);
item--;
i--;
} else {
alert("Must have at least one <srate> element!")
}
};
self.sampleRate = ko.observableArray(['192000', '176400', '96000', '88200', '48000', '44100']);
return self;
}
// Activates knockout.js
function startKnockout() {
ko.applyBindings(new Vm());
};
Take a look at this:
http://jsfiddle.net/martinbooth/x3cg131g/1/
importantly, compute the available samples rates based on what has been selected in other forms:
self.sampleRates = ko.computed(function(){
var formsValue = forms(),
availableSampleRates = ko.utils.arrayFilter(allSampleRates, function(sampleRate){
return !ko.utils.arrayFirst(formsValue, function(form){
return form != self && form.selectedSampleRate() === sampleRate;
});
});
return availableSampleRates;
});