Knockout check box binding - javascript

I am trying to have a checkbox update the value of an observable. This partially works, but the checkbox does not "check" itself after. To solve this I tried to add the checked: binding, looking for the value that I had just set in the click event, but this also does not work.
My Observable
appViewModel.test = ko.observable(1);
The checkbox
<input type="checkbox" data-bind="checked: test() == 4, click: test.bind($data, 4)"/>

You can write a click handler that checks to see if the value is 4 (or whatever arbitrary value you want) and then can act accordingly, like this:
HTML:
<input type="checkbox" data-bind="checked: checkBoxValue() === 4,
click: handleCheckBoxClick">
<br/>
<div>
<span>Debug:</span>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
</div>
JavaScript:
var ViewModel = function () {
var self = this;
self.checkBoxValue = ko.observable(0);
self.handleCheckBoxClick = function () {
if (self.checkBoxValue() !== 4) {
self.checkBoxValue(4);
} else {
self.checkBoxValue(0);
}
return true;
};
};
ko.applyBindings(new ViewModel());
Note: I added the debug output so you could see the underlying checkBoxValue value in the view model as you interact with the checkbox.
See jsFiddle here

You can use a computed to catch observable changes:
var ViewModel = function(first, last) {
var self = this;
this.checked = ko.observable(false);
this.test = ko.observable();
this.isChecked = ko.computed(function(){
var test = self.test();
if(test === '4')
{
self.checked(true);
return;
}
self.checked(false);
});
};
Here is a jsfiddle

The checked binding wants to be two-way, writable as well as readable. It can't write to a test, though, when you click it. Instead of having a click binding, you should have the test set to 4 by the write function of the computed you use as the checked binding.
var vm = {
test: ko.observable(1)
};
vm.checked = ko.computed({
read: function () { return vm.test() == 4; },
write: function (newValue) {
vm.test(newValue ? 4 : 1);
}
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="checkbox" data-bind="checked: checked"/>
<div data-bind="text:test"></div>

Related

Multiple submit in knockout

We have a submit binding in knockout, however we can use it only on whole form, so in a case of multiple submit buttons, they all trigger same binding. I'd like differ action for each one, but idk how to differ which one has been clicked, eg.:
HTML:
<form data-bind="submit: save">
<input type=submit name=save value=Save>
<input type=submit name=saveAndClose value="Save & close">
</form>
VM:
var ViewModel = function () {
this.save = function (form) {
var clicked = 'how to find out?';
if (clicked === 'save') {
// save
} else if (clicked === 'saveAndClose') {
// save
// close
}
};
};
Yea, I can use click binding on each submit, but then there's no form element available, yea - I can obtain it by different way, but maybe you know better solution.
Do you?
Well, what you can do is to use a click binding on each input and pass a $element variable as a parameter. As such:
<form>
<input type=submit name=save data-bind="click: save.bind(null, $element)" value=Save>
<input type=submit name=saveAndClose data-bind="click: save.bind(null, $element)" value="Save & close">
</form>
bind in javascript creates a new function with a specified this (null) and parameter ($element) in this case. Therefore, it is easy to obtain a form element, and determine which input was clicked:
var ViewModel = function () {
this.save = function (el) {
var clicked = el.getAttribute('name');
var form = el.parentElement;
console.log(clicked, form)
if (clicked === 'save') {
// save
} else if (clicked === 'saveAndClose') {
// save
// close
}
};
};
Working fiddle
Note however, that this method is markup dependent, if the inputs are not direct children of the form element, you may need to find another method to get the form element itself instead of parentElement
Overload submit binding to allow use it on any element.
JSFiddle: Knockout submit binding applicable onto any button (uses jQuery)
Binding overload:
var overridden = ko.bindingHandlers.submit.init;
var $clickedButton;
ko.bindingHandlers.submit.init = function (element, valueAccessor) {
var $form = $(element);
if ($form.prop('tagName') !== 'FORM') {
var $button = $form;
$form = $form.closest('form');
if ($form.length === 0) {
throw new Error('Submit binding can be used only in forms');
}
$button.on('click', function () {
$clickedButton = $button;
});
var formHandler = function () {
return function () {
if ($clickedButton === $button) {
return valueAccessor().apply(this, arguments);
}
return false;
};
};
overridden.apply(this, [$form[0], formHandler].concat([].splice.call(arguments, 2)));
} else {
overridden.apply(this, arguments);
}
};
ViewModel:
var ViewModel = {
submitA: function () {
alert('A');
},
submitB: function () {
alert('B');
}
};
ko.applyBindings(ViewModel);
HTML:
<form method=post>
<input type=submit data-bind="submit: submitA" value=A>
<input type=submit data-bind="submit: submitB" value=B>
</form>

JavaScript event, not updating knockout observable/model value

I have text field which is wired up with Keyup & change event, to trim the field length.
#Html.TextBoxFor(m => m.zipCode, new {data_bind = "textInput: zipcode, event: { keyup: trimField, change: trimField }", maxlength = "5"})
Trim function,
function trimField(data, event) {
var obj = event.target;
var maxlength = parseInt(obj.getAttribute('maxlength'), 10);
obj.value = obj.value.substring(0, maxlength);
obj.focus();
return true;
}
If I type "123456", on the UI it shows "12345", but the model has "123456".
How to get model updated after the keyup event?
You are not updating the observable variable which is bound to your element. It is better to make it generic as an observable extend so it can be used everywhere based on your max-length and to make sure it follows your rule for an initial value.
Example : https://jsfiddle.net/kyr6w2x3/55/
HTML:
<input data-bind='textInput: zipCode' />
<div>
zip code in Model:<span data-bind="text:zipCode"></span>
</div>
JS:
function AppViewModel(input) {
this.zipCode = ko.observable(input).extend({ maxLength:5});
this.phone = ko.observable(input).extend({ maxLength:11});
}
ko.extenders.maxLenght = function(target, characters) {
//you can use this to show an error message on view
// target.validationMessage = ko.observable();
//define a function to do validation for maxLength
function validate(newValue) {
var maxlength = parseInt(characters, 10);
if(newValue){
target(newValue.substring(0, maxlength) );
}
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
ko.applyBindings(new AppViewModel("12345678910"));
change maxlength from 5 to 6:
#Html.TextBoxFor(m => m.zipCode, new {data_bind = "textInput: zipcode, event: { keyup: trimField, change: trimField }", maxlength = "6"})
Stop modifying the DOM. That's Knockout's job. You just modify the data item and Knockout will ensure that the UI is right.
function trimField(data, event) {
var obj = event.target;
var maxlength = parseInt(obj.getAttribute('maxlength'), 10);
data.zipcode(data.zipcode().substr(0, maxlength));
return true;
}

Detect input value change with MutationObserver

I want to detect when text/value change in input field. Even if I change the value with js, I want to detect that changes.
Here's what I've tried so far in demo in fiddle.
HTML:
<input type="text" id="exNumber"/>
JavaScript:
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// console.log('Mutation type: ' + mutation.type);
if ( mutation.type == 'childList' ) {
if (mutation.addedNodes.length >= 1) {
if (mutation.addedNodes[0].nodeName != '#text') {
// console.log('Added ' + mutation.addedNodes[0].tagName + ' tag.');
}
}
else if (mutation.removedNodes.length >= 1) {
// console.log('Removed ' + mutation.removedNodes[0].tagName + ' tag.')
}
}
if (mutation.type == 'attributes') {
console.log('Modified ' + mutation.attributeName + ' attribute.')
}
});
});
var observerConfig = {
attributes: true,
childList: false,
characterData: false
};
// Listen to all changes to body and child nodes
var targetNode = document.getElementById("exNumber");
observer.observe(targetNode, observerConfig);
To understand what is going on is necessary to clear up the difference between attribute (content attribute) and property (IDL attribute). I won't expand on this as in SO there are already excellent answers covering the topic:
Properties and Attributes in HTML
.prop() vs .attr()
What is happening behind .setAttribute vs .attribute=?
When you change the content of a input element, by typing in or by JS:
targetNode.value="foo";
the browser updates the value property but not the value attribute (which reflects the defaultValue property instead).
Then, if we look at the spec of MutationObserver, we will see that attributes is one of the object members that can be used. So if you explicitly set the value attribute:
targetNode.setAttribute("value", "foo");
MutationObserver will notify an attribute modification. But there is nothing like properties in the list of the spec: the value property can not be observed.
If you want to detect when an user alters the content of your input element, the input event is the most straightforward way. If you need to catch JS modifications, go for setInterval and compare the new value with the old one.
Check this SO question to know about different alternatives and its limitations.
I've modified Shawn's method a little and wanted to share it. Can't believe there's actually a solution to this.
Type into the input box to see the default behavior. Now, open the DevTools and select the input element, then change its value, e.g. $0.value = "hello". Examine the UI vs. API difference. It seems UI interactions do not modify value property directly. If it were, it would also log "...changed via API...".
let inputBox = document.querySelector("#inputBox");
inputBox.addEventListener("input", function () {
console.log("Input value changed via UI. New value: '%s'", this.value);
});
observeElement(inputBox, "value", function (oldValue, newValue) {
console.log("Input value changed via API. Value changed from '%s' to '%s'", oldValue, newValue);
});
function observeElement(element, property, callback, delay = 0) {
let elementPrototype = Object.getPrototypeOf(element);
if (elementPrototype.hasOwnProperty(property)) {
let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
Object.defineProperty(element, property, {
get: function() {
return descriptor.get.apply(this, arguments);
},
set: function () {
let oldValue = this[property];
descriptor.set.apply(this, arguments);
let newValue = this[property];
if (typeof callback == "function") {
setTimeout(callback.bind(this, oldValue, newValue), delay);
}
return newValue;
}
});
}
}
<input type="text" id="inputBox" placeholder="Enter something" />
the value property can be observed, Don't waste your time.
function changeValue (event, target) {
document.querySelector("#" + target).value = new Date().getTime();
}
function changeContentValue () {
document.querySelector("#content").value = new Date().getTime();
}
Object.defineProperty(document.querySelector("#content"), "value", {
set: function (t) {
alert('#changed content value');
var caller = arguments.callee
? (arguments.callee.caller ? arguments.callee.caller : arguments.callee)
: ''
console.log('this =>', this);
console.log('event => ', event || window.event);
console.log('caller => ', caller);
return this.textContent = t;
}
});
<form id="form" name="form" action="test.php" method="post">
<input id="writer" type="text" name="writer" value="" placeholder="writer" /> <br />
<textarea id="content" name="content" placeholder="content" ></textarea> <br />
<button type="button" >Submit (no action)</button>
</form>
<button type="button" onClick="changeValue(this, 'content')">Change Content</button>
This works and preserves and chains the original setter and getter so everything else about your field still works.
var registered = [];
var setDetectChangeHandler = function(field) {
if (!registered.includes(field)) {
var superProps = Object.getPrototypeOf(field);
var superSet = Object.getOwnPropertyDescriptor(superProps, "value").set;
var superGet = Object.getOwnPropertyDescriptor(superProps, "value").get;
var newProps = {
get: function() {
return superGet.apply(this, arguments);
},
set: function (t) {
var _this = this;
setTimeout( function() { _this.dispatchEvent(new Event("change")); }, 50);
return superSet.apply(this, arguments);
}
};
Object.defineProperty(field, "value", newProps);
registered.push(field);
}
}

How to create a set of dropdowns where the list reduces as you select?

I have a HTML form like this:
All of the dropdowns contain the same list: Option 1, Option 2, Option 3 and the user needs to select a value for each key. This works as expected with no worries:
However, I want to enhance as it. Both the Keys and Options List can become relatively large (say 20). There is expected to be a one-to-one mapping and you can't select a value in two places. But when the list is large, it becomes easy to make a mistake and select the same value in two places. We do some client-side validation to check for duplicates but I would prefer a user experience that works by removing the selected option from other dropdowns such that it cannot be selected again. Like this:
How can I go about this?
FINAL SOLUTION
I initially selected the Knockout solution but on second thought, I preferred Rick Hitchcock's plain JQuery solution because I can easily plug it in anywhere without any additional setup. Here's how I modified Rick's solution to be more reusable:
function reducingDropdowns(dropDownSelector){
var $dropdowns = $(dropDownSelector);
$dropdowns.change(function() {
// First enable all options.
$dropdowns.find('option').prop('disabled', false);
// Then for each dropdown, get its current value and
// disable that option in other dropdowns.
$dropdowns.each(function() {
var $currDropdown= $(this);
var currDropdownValue= $currDropdown.val();
if(currDropdownValue !== ''){
var $otherDropdowns = $dropdowns.not($currDropdown);
$otherDropdowns.find('option').each(function() {
var $option = $(this);
var optionIsAlreadySelected = $option.val() === currDropdownValue;
if(optionIsAlreadySelected)
$option.prop('disabled', true);
});
}
});
});
}
now you can just give all your related dropdowns a common class and call something like this anywhere you need it:
reducingDropdowns('.myDropdownClass');
Thank you all for the help.
PS: I also realized that for my application, I preferred to disable the options that were already used instead of removing them from the list completely.
Here's a very simple way of doing it and this can be made more efficient, but here's the basic idea:
Html
<select data-bind="value: value1, options: options1, optionsCaption: ''"></select>
<select data-bind="value: value2, options: options2, optionsCaption: ''"></select>
<select data-bind="value: value3, options: options3, optionsCaption: ''"></select>
View Model
var self = this;
this.options = ko.observableArray(['Option 1', 'Option 2', 'Option 3']);
this.value1 = ko.observable();
this.value2 = ko.observable();
this.value3 = ko.observable();
this.options1 = ko.computed(function() {
return ko.utils.arrayFilter(this.options(), function(f) {
return f != self.value2() && f != self.value3();
});
}, this);
this.options2 = ko.computed(function() {
return ko.utils.arrayFilter(this.options(), function(f) {
return f != self.value1() && f != self.value3();
});
}, this);
this.options3 = ko.computed(function() {
return ko.utils.arrayFilter(this.options(), function(f) {
return f != self.value1() && f != self.value2();
});
}, this);
JSFiddle
You can hide the used options like this:
$('select').change(function() {
$('option').show();
$('select').each(function() {
var val= $(this).val();
$(this).siblings('select')
.find('option')
.filter(function() {
return $(this).val() === val && $(this).val() !== '';
})
.hide();
});
});
Working Fiddle #1
An alternative to removing the items is to disable them:
$('select').change(function() {
$('option').prop('disabled', false);
$('select').each(function() {
var val= $(this).val();
$(this).siblings('select')
.find('option')
.filter(function() {
return $(this).val() === val && $(this).val() !== '';
})
.prop('disabled', true);
});
});
Working Fiddle #2
There is even a cleaner and easier way of doing this: http://jsfiddle.net/ejs1d3zb/5/
$(function () {
$('select').change(function (){
var val = $(this).val();
$('select option[value='+val+']').not(this.children).remove();
});
});
There is an OnChange event should have some kind of taken and available list and check against each for each comobbox

knockout chaning binding events

Currently I am unable to fire off a binding event that is dependent on the results of another binding event in knockout.
In the example below, provide a value in the 'available' input, when the 'condition1' input is filled with a value such as 22 the 'available' input should be cleared and disabled, all of this is done in the skip logic binding. This is happening properly.
However, the problem lies with the execution of the skiplogic binding on the chain1 input element. This isn't even being fired after the 'available' input is cleared of its value. How can I get it so the results of one binding fires off another binding?
Here is the js fiddle version of the code below: http://jsfiddle.net/gYNb8/2/
Here is the form I am using to test the concept out on:
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<span>Condition 1</span>
<input id="condition1" data-bind="value: condition1" />
<br/>
<span>Condition 2</span>
<input id="condition2" data-bind="value: condition2" />
<br/>
<span>Available?</span>
<input id="available" data-bind="value: available, skipLogic: condition1, skipLogic: condition2" />
<br/>
<span>Chain1</span>
<input id="chain1" data-bind="value: chain1, skiplogic: available" />
Here is the javascript:
// Overall viewmodel for this screen, along with initial state
function ReservationsViewModel() {
var self = this;
self.condition1 = ko.observable();
self.condition2 = ko.observable();
self.available = ko.observable();
self.chain1 = ko.observable();
}
//Here are the conditions which govern whether an element should be enabled or not
var elementConditions = {
'available': [{
'Condition': 'condition1() > 0',
'Type': 'evaluation'
}, {
'Condition': 'condition2() > 0',
'Type': 'evaluation'
}],
'chain1': [{
'Condition': 'available',
'Type': 'empty'
}]
};
ko.bindingHandlers.skipLogic = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var id = $(element).attr("id");
var conditions = elementConditions[id];
var isSkipped = false;
var conditionMet = false;
for (var i = 0; i < conditions.length; i++) {
conditionMet = false;
if (conditions[i].Type == "evaluation") {
conditionMet = eval('viewModel.' + conditions[i].Condition);
} else if (conditions[i].Type == "empty") {
if ($('#' + conditions[i].Condition).val().length == 0) {
conditionMet = true;
}
} else if (conditions[i].Type == "notempty") {
if ($('#' + conditions[i].Condition).val().length > 0) {
conditionMet = true;
}
}
if (conditionMet == true) {
isSkipped = true;
}
}
if (isSkipped) {
eval("viewModel." + id + "('');");
$(element).attr("disabled", "disabled");
} else {
if (elementSkipped[id] > 0) {
$(element).attr("disabled", "disabled");
} else {
$(element).removeAttr("disabled");
}
}
}
};
ko.applyBindings(new ReservationsViewModel());
Instead of trying to keep the conditions separately, can you use Boolean logic to string them together in the binding? this way you don't need to keep track of each bindings state. I have built the following binding:
ko.bindingHandlers.skipLogic = {
init: function(element, valueAccessor) {
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var valueOfCondition = ko.unwrap(valueAccessor());
var jqElement = $(element);
//update if the field is disabled if more than one condition is met
if(valueOfCondition){
jqElement.prop('disabled', true);
}
else{
jqElement.prop('disabled', false);
}
}
};
a working example is here: http://jsfiddle.net/M7vUV/3/
The update function for a binding will be executed when the element is first bound (after the init function) and then will run again whenever any of its dependencies change. You create dependencies by accessing observables within the function (like inside of a computed, because a computed is actually used to facilitate the binding updates).
So, you would want to ensure that you are accessing the retrieving whatever you passed to the binding by calling valueAccessor(), then if the value is observable you would want to call it as a function to retrieve the value. Otherwise, if you unsure whether you have been passed an observable you can call ko.unwrap (before 2.3 this was ko.utils.unwrapObservable - post 2.3 either can be used).
Additionally, you could access values passed to other bindings by using the allBindingsAccessor argument (3rd argument) or accessing values directly off of the data (4th arg) or context (5th arg).
It will not work to pass multiple bindings with the same name of the same element. You might want to consider structuring it differently like passing an array data-bind="skipLogic: [one, two]" and then accessing the values off of each.

Categories