Combining checked binding with click binding on containing element [duplicate] - javascript

This question already has an answer here:
Prevent event bubbling when using the checked binding in knockoutjs
(1 answer)
Closed 8 years ago.
In my view model I have a boolean property that's visualized by a checkbox in the view. I want the user to be able to click on the containing element as well to toggle the property, but that poses problems when the user clicks the checkbox: the change is not registered then.
Consider this view:
<div class="my-option" data-bind="click: toggleOption1">
<input type="checkbox" data-bind="checked: isOption1Checked" />
Entire div is clickable to select this option.
</div>
With this view model:
var ViewModel = function() {
var self = this;
self.isOption1Checked = ko.observable(false);
self.toggleOption1 = function(){
self.isOption1Checked(!self.isOption1Checked());
};
};
As you can see in this corresponding jsfiddle, this will not allow you to click on the checkbox to change the boolean observable. This kinda makes sense to me, probably the click handler changes the value, but the checked binding also handles the change and reverts it.
The general solution I felt I needed was a one way checked binding or something of the sort, so I tried using the attr binding:
<div class="my-option" data-bind="click: toggleOption1">
<input type="checkbox"
data-bind="attr: { checked: isOption1Checked() ? 'checked' : '???'" />
Entire div is clickable to select this option.
</div>
However, this will not work: there is no checked="false" option in html. You just omit the checked attribute altogether. I don't think the attr binding can do that however.
One other workaround I thought of was to create my own binding (possibly based on / delegating the read bit to the default checked binding), but it feels like overkill. Am I missing an obvious, elegant Knockout solution?
My current workaround (trying to evade creating such a custom binding) involves some elaborate view logic:
<div class="my-option" data-bind="click: toggleOption1">
<!-- ko if: isOption1Checked -->
<input type="checkbox" checked="checked" />
<!-- /ko -->
<!-- ko if: !isOption1Checked() -->
<input type="checkbox" />
<!-- /ko -->
Entire div is clickable to select this option.
</div>
This works, but is very verbose to my taste.
Any other elegant, concise way to handle this?

Right, just after posting and searching some more I found the answer in another question. Here's the specifics for the scenario from my question:
<div class="my-option" data-bind="click: toggleOption1">
<input type="checkbox"
data-bind="checked: isOption1Checked,
click: function() { return true; },
clickBubble: false" />
Entire div is clickable to select this option.
</div>
I'll leave this answer and question here as a duplicate, should it help a random Googling internet user that words his question similar to mine as opposed to similar to the other one.

Related

Knockout.js: Initial value of observable doesn't update RadioButton state

Here is a very simple story that I fail to make it work:
We have an array of questions. Each question has an array of answer. Each answer has a text and value. Each question has the property selectedAnswer that represents the selected answer's value.
Here is jsfiddle: https://jsfiddle.net/votsevfd/8/
Implementation:
<div data-bind="foreach : {data: questions}">
Question: <span data-bind="text: descr"></span>
<div data-bind="foreach: {data: answers}">
<div>
<label data-bind="text: text"></label>
<input name="something" type="radio" data-bind="checked: $parent.selectedAnswer, value: value"></input>
</div>
</div>
<div>
The selected answer is:<span data-bind="text: selectedAnswer"></span>
</div>
</div>
And here is the related JavaScript:
var model = {
questions: ko.observableArray(),
};
var q1 = {
descr: 'Do you like JS?',
selectedAnswer: ko.observable('200'), // Initially, select No (200)
answers: [
{text:'Yes', value:'100'},
{text:'No', value:'200'}
],
}
model.questions.push(q1);
ko.applyBindings(model);
Problem:
I want the appropriate radio button become selected according to initial value of selectedAnswer (in the above case, radio button No). But it doesn't work.
What am I missing?
This is a weird issue. I was able to fix it by changing the order of the checked and value bindings like this:
<input name="something" type="radio" data-bind="value: value, checked: $parent.selectedAnswer" />
Updated fiddle
There's another fix for this. If you switch to knockout's latest version, this issues goes away. So, I'm assuming they fixed it somewhere between version 2 and 3.0.
Updated fiddle
(Also, you can simplify your foreach binding to this:data-bind="foreach : questions")
Update:
It is indeed a bug. You can go through #user3297291's detailed answer for more information regarding this.

How to prevent checking checkbox when added dynamically to a div?

so I've been struggling with this issue.
I want to add a checkbox into a div dynamically by clicking a button. Let's say I already have 2 checkboxes in the div, then I uncheck those 2. When I click the button, the checkboxes become 3 (which is what I want), but all those 3 will be checked. What I want is when I add a checkbox, the other checkbox(s)' checked state remain the same as before.
Here is my code (http://jsfiddle.net/gr2o47wt/4/):
HTML:
<div id="chkbox_container">
<input type="checkbox" checked>Check<br />
</div>
<input type="button" value="Add CheckBox" onClick="addCheckBox();">
JavaScript:
function addCheckBox() {
txt = "<input type=\"checkbox\" checked>Check<br />";
document.getElementById('chkbox_container').innerHTML += txt;
}
Thanks in advance for your answers! :)
You can use insertAdjacentHTML() rather than manipulating the innerHTML:
<input type="button" value="Add CheckBox"
onClick="document.getElementById('chkbox_container').insertAdjacentHTML('beforeend','<input type=\'checkbox\' checked=\'checked\' />Check<br />');">
JS Fiddle demo.
The problem you had was that the original HTML (as returned by innerHTML) is from the source of the page, not the DOM; and therefore the checked/unchecked nature of the checkbox originally in place was restored.
insertAdjacentHTML() simply adds the HTML string in the specified place ('beforeend' in this case).
More or less as an aside, it's worth trying, where possible, to keep your event-handling outside of your HTML elements; and binding those event-handlers in the JavaScript itself. This makes for somewhat easier maintainability, and would lead to code like the following:
// note that I gave the button an 'id' for simplicity:
var button = document.getElementById('addCheckboxes');
button.addEventListener('click', function () {
document.getElementById('chkbox_container').insertAdjacentHTML('beforeend', '<input type=\'checkbox\' checked=\'checked\' />Check<br />');
});
JS Fiddle demo.
Finally, some of your HTML is invalid (or at least erroneous), an <input /> is a void element, it can have no descendants; therefore it either has no closing tag (just: <input>) or self-closes (<input />).
Further, the text beside the checkboxes is a little misleading, usually with an HTML form the text beside the <input /> will focus that input; that's achieved by using a <label> element to associate the text with the control, for example:
<label><input type="checkbox" /> click</label>
JS Fiddle demo.
Or:
<input type="checkbox" id="inputElementID" />
<label for="inputElementID">click</label>
But this latter form does require the dynamic generation of ids (which is a little beyond the scope of this question).
References:
insertAdjacentHTML().

Using angularjs to create horizontal radio buttons

I am building a set of radio buttons using the ngRepeat directive and I need to make it horizontal. I'm not sure it's possible to do that with ngRepeat, since each instance gets its own scope. The below structure creates a new div for each item in the options array and they're displayed vertically.
<div ng-repeat="option in options">
<input type="radio" role="radio" />
<span>label</span>
</div>
Does anyone know any tricks for creating horizontal radio buttons?
Angular doesn't really affect style in this way. Give your div float:left or display:inline-block in its style.
It should be noted that AngularJS from version 1.1.6 up allows one to do this much more cleanly:
Assume
$scope.data = [{val:0,txt:'Foo'},{val:1,txt:'Bar'},{val:2,txt:'Baz'}];
then you can use repeat-start and repeat-end like this:
<input ng-repeat-start="item in data" type="radio" value="{{item.val}}">
<span ng-repeat-end>{{item.txt}}</span>
Demo here: http://jsfiddle.net/rWLfZ/

knockout event handling

I have a code (you can play it at http://learn.knockoutjs.com/#/?tutorial=intro , click Run in output window before playing ):
HTML:
<div class="btn" style="margin-left: 15px;" data-bind="click: includeMyNumber">
<input data-bind="checked: isIncludeMyNumber" data-val="true" id="IncludeMe" name="IncludeMe" style="margin: 0" type="checkbox" value="true" />
Include my number (+<span>11111111111</span>)
</div>
Javascript:
// This is a simple *viewmodel* - JavaScript that defines the data and behavior of your UI
function AppViewModel() {
this.isIncludeMyNumber = ko.observable(false);
this.includeMyNumber = function(){
this.isIncludeMyNumber(!this.isIncludeMyNumber());
}
}
// Activates knockout.js
ko.applyBindings(new AppViewModel());
The problem is that checkbox click event handling does not work properly. When I click the space inside [div class="btn"...] ...[/div] area, checkbox behaviour is OK, but when I click the checkbox itself, it is not checked. How can I make it checkable in any case?
Thank you.
You´re use case is to make the checkbox checked when clicking the text?
I made a binding for this in my custom binding collection
https://github.com/AndersMalmgren/Knockout.Bindings
http://jsfiddle.net/5nqw4/
<input data-bind="checked: checked, label: { caption: 'Label with reference to input' }" type="checkbox" />
edit: You can also fix this by using the standard hack of wrapping the checkbox in a label element like http://jsfiddle.net/7dTfM/

Protect unspecified Javascript event handlers from being garbage collected

Recently I posed a question, asking why some of my javascript code was misbehaving. However, the accepted answer didn't fully solve my problem, so here I am once again.
Problem Description
I have a <div> which has a collection of radio buttons in it.
I use jquery ui to style that collection with a buttonset(). It looks resonably pretty.
Then I empty the <div> with jquery by doing something to the effect of $("#mydiv").html("")
Then I once again restore exact contents that were removed.
Finally the buttonset is no longer working properly, because its events got unhinged in the process.
So my question is how to protect such bound events from being garbage collected, when I temporarily tinker with the DOM?
NB! I can't do display:none to hide the <div> instead, because the whole business with deleting html content and restoring it later is handled by an unnamed jquery plugin. Nor can I call buttonset() again, because a) the graphic style gets messed up, and b) there are other controls in my real problem that don't have this handy functionality. So what I really need is some way to protect all those handlers while the elements which behavior they are supposed to govern are temporarily missing from the DOM.
Sample Code
HTML
<div id="container">
<div id="buttonset">
<input type="radio" id="radio1" name="option" />
<label for="radio1">X</label>
<input type="radio" id="radio2" name="option" />
<label for="radio2">Y</label>
<input type="radio" id="radio3" name="option" />
<label for="radio3">Z</label>
</div>
</div>
<div id="control">
<input id="toggle" type="checkbox"/>
<label for="toggle">Toggle</label>
</div>
Javascript
$(function(){
$("#buttonset").buttonset();
$("#toggle").click(
function(){
if($(this).is(":checked"))
{
backup = $("#container").html();
$("#container").html("");
} else $("#container").html(backup);
}
)
});
Playable Version
See this jsFiddle
Solution
I used the idea in the accepted answer to save html contents before applying buttonset(), then reapply buttonset() each time as needed on that saved data.
Update:
Here's an updated fiddle that's pretty close to what your OP is. The basic idea is it destroys the buttonset to get the original html back
$(function() {
//Turn the radio buttons into a jquery ui buttonset
$("#buttonset").buttonset();
//Use the toggle button to hide/show the #container div.
//NB! It's not possible to do css display:none instead,
//due to some other limitations, to wit, I'm using a
//library that I can not modify.
$("#toggle").button();
var backup; // added this to prevent it from leaking onto global scope.
$("#toggle").click(function() {
if ($(this).is(":checked")) {
// restore the html back
$("#buttonset").buttonset("destroy");
backup = $("#container").html();
$("#container").html("");
}
else {
$("#container").html(backup);
$("#buttonset").buttonset();
}
})
});

Categories