I believe this question is similar to this one but as far as could see in the rules, if there is no answer and it is not the same scenario, I'm allowed to ask.
I've simplified my real scenario with the following, basically, the checkbox is getting checked through some unaccessible code which doesn't get the view model of knockout.js to detect. Is there a work around?
HMTL:
<input id="myCheckbox" type="checkbox" data-bind="checked: myValue" />
<div data-bind="text: myValue"></div>
javascript:
var viewModel = {
myValue: ko.observable(false)
};
ko.applyBindings(viewModel);
setTimeout(function() {
$("#myCheckbox").attr("checked", "checked");
}, 1000);
When a checkbox is modified using the setAttribute function or the checked property, as jqGrid does, it doesn't trigger the click event that Knockout's checked binding uses; neither does it trigger a change event. To be able to detect these changes, you have different options depending on the browser/version: MutationObserver, DOMAttrModified, and/or onpropertychange.
But I'd suggest avoiding those solutions and using what jqGrid gives you: either the jqGridSelectRow event or the onSelectRow callback. You might want to check out the Knockout-jqGridBinding plugin that should give you a good starting point. It includes a selectedItems option that lets you bind an observable array to jqGrid's selected items (using the onSelectRow callback internally).
EDIT:
To re-iterate, I suggest you don't try to solve the problem by watching the checkboxes. But if that's the way you want to go, there's a jQuery plugin, attrchange that provides cross-browser support for this.
Resources:
Knockout-jqGridBinding: https://github.com/CraigCav/Knockout-jqGridBinding
attrchange: http://meetselva.github.io/attrchange/
The answer to which you linked explained that Knockout needs to be alerted of the change through a usual event, such as "click". Here is the idea posted there by Rustam:
function update(){
var $cb = $(':checkbox');
var cb = $cb[0];
// change value directly on element
cb.checked = !cb.checked;
// propagate changes to KO
$cb.triggerHandler('click');
}
setTimeout(update, 1000);
Of course the method more native to KO would be to change the observable on the model, like so:
var update = function() {
viewModel.myValue(!viewModel.myValue)
};
I was not able to fix this on my code in which Foundation was taking control of the checkbox. I ended up binding a click event which then checked on the checkbox to see if it was checked (we only had 1 checkbox that we were trying to keep track of). I then updated the observable from that click event based on whether it was checked or not.
No way around this that I know of.
Related
I'm trying to use jQuery geocomplete along with Vue.js to populate a form with geo data.
My code contains this form:
<form>
<label for="get-offer-location">location: </label><input id="get-offer-location" v-model="newoffer.location" type="text"/>
<div style="visibility:hidden">
<input name="lat" type="text" value=""/>
<input name="lng" type="text" value=""/>
</div>
</form>
After I click on a suggested location from the get-offer-location input, it looks like the field is filled out -- but then when I start typing in another field, the location input field reverts to just the letters I typed in for the location search. Try it out here by clicking "post", then "news" or "offers":
https://jsfiddle.net/dvdgdnjsph/157w6tk8/
Can anyone tell me what's wrong?
The problem you are having is that v-model binds on input, since the geolocation dropdown is a plugin that changes the value programatically the input event is not fired, so v-model is not updated. As a case, try typing a space after selecting a location, you will see that it sticks.
Fortunately, v-model is nothing more than syntactic sugar for v-on:input, so you can use v-on to fire your event instead. Considering that you are going to need to unfocus to get out of the box, the blur event is likely to be your best option, so you can simply do:
v-on:blur="newarticle.location = $event.target.value"
Unfortunately, JSFiddle won't let me save or update your Fiddle, but I did get it working with the code above.
For completeness, in case you want to use this behavior extensively, and because the Vue docs are fairly limited in this regard, you may write a custom directive to encapsulate the code:
Vue.directive('model-blur', {
bind: function(el, binding, vnode) {
el.addEventListener('blur', function() {
vnode.context[binding.expression] = el.value;
});
}
});
Then you can use like so:
<input v-model-blur="myVar" />
Here's the JSFiddle: https://jsfiddle.net/4vp6Lvmc/
Can't tell for sure. But it looks like jQuery plugin just changes input#get-article-location value, but not the Vue model. So when you trigger model update (e.g. editing headline) it overwrites complete location with whatever you typed in.
I have something like this for catch the geocomplete event and try to set the vueJS value :
$("#get-article-location")
.geocomplete({details: ".details"})
.bind("geocode:result", function (event, result) {
vm.newoffer.location = result.formatted_address;
console.log('done');
});
But something still appears wrong, I think you should really change the name of your vueJS instance (var vm) it may be use by another script and make troubles.
This is because v-model, as two-way binding, on the receiving-user-input way, listens to the input event on the input element, while js plugins (like jquery-geocomplete) obviously set input values via js, which leads to the view model's data not changing as we discussed in other answers.
To fix this, just listen to the geocode:result event of the plugin and manually change the data with code (there seems to be something wrong with jsfiddle so I'm posting it here):
var vueVM = this;
$("#get-offer-location").geocomplete({ details: ".details" });
$("#get-article-location")
.geocomplete({ details: ".details" })
/***** highlight start *****/
.bind("geocode:result", function(event, result){
console.log(result);
//tried result.something but couldn't find the the same thing as `this.value`
vueVM.newarticle.location = this.value;
});
/***** highlight end *****/
extra knowledge: the mechanism of v-model stated above is usually used in making reusable components, by receiving a value prop from the parent, and emitting an input event with the user-input value when detecting change on the input element in the component. And then we can use <my-component v-model='parentData'></my-component> as the child behaves exactly like a simple input element from the parent's perspective. example
I am using the following code to programmatically uncheck a checkbox:
$('#someid').removeAttr('checked');
Here is the checkbox that is bound to an Angular model:
<input id="someid" ng-model="model.property" type="checkbox" value="true">
I can see that the checkbox is indeed unchecking. Why is the Angular model property not also updating (changing from true to false) and how can I obtain this desired behavior? I can update the model and have the checkbox update no problem.
If you are using Angular, it's expected that you don't manipulate the DOM this way.
You have to change the angular model variable, and let Angular make the DOM changes.
Study the ToDo List example at
angularjs.org
Tip: I think you really don't need jQuery anymore!
The Angular code you need:
$scope.model.property = false;
Your use of jQuery is breaking anglers binding to the DOM. If you need to uncheck something change the value on the model that is bound to the checkbox:
$scope.model = { isChecked: true };
bound to:
<input type="checkbox" ng-model="model.isChecked">
to "uncheck":
$scope.model.isChecked = false;
No need for jQuery.
I am using Dojo labels and checkboxes in one of my app inside smarty file. I want to add a certain behavior to uncheck a checkbox, if any other checkbox is checked. I also check if that checkbox is originally checked, it will uncheck the same. (I do not want to use radio button)
Here is my code for one CheckBox:
<input id="form.cs"
data-dojo-type='dijit.form.CheckBox'
type="checkbox"
data-dojo-props="value:'true', type:'checkbox', name:'cs', style:'vertical-align: top'"
onChange="if(dijit.byId('form.cs"').checked)
dijit.byId('form.cs"').set('checked', false);
else
dijit.byId(' form.nl"').set('checked', false);"
/>
The problem with code is when i add curly braces, this is not rendered by smarty engine and throws error.
For example :
onChange="if(dijit.byId('form.cs"').checked) {
dijit.byId('form.cs"').set('checked', false); }
else {
dijit.byId(' form.nl"').set('checked', false);"}
The above code snippet will create a breakdown in the smarty.
I recommend writing your event handler in JavaScript. If you're going to write all your event handlers as attributes you're going to have a lot of problems like code validation, ... .
You could write a loop that actually loops over all checkboxes, setting the value to the opposite of the changed value (so if one checkbox becomes true, the other ones must become false).
To do this you could write a simple function like this:
var toggleCheckboxes = function(myNode, value) {
query("input[type=checkbox]").forEach(function(node) {
if (node !== myNode) {
registry.getEnclosingWidget(node).set("value", !value, false);
}
});
};
The dojo/query module allows you to get a list of all nodes matching the given selector. With the dijit/registry module you can retrieve the actual widget behind the DOM node and then you just set the value using:
registry.getEnclosingWidget(node).set("value", !value, false);
The third parameter (set to false) is actually very important. This parameter will prevent further event invocations. If you don't put that parameter there it will actually trigger another onChange, causing an infinite loop.
Now the only thing you need to do is bind an onChange event handler to each checkbox that calls this function, you can also to that with the dojo/query and dijit/registry module, for example:
query("input[type=checkbox]").forEach(function(node) {
registry.getEnclosingWidget(node).on("change", function(value) {
toggleCheckboxes(node, value);
});
});
A complete example can be found on JSFiddle.
But I still recommend using a radiobutton. I think you can actually say this is a bad UI design, a checkbox and a radiobutton have different goals, use them for what they're meant to.
I have a situation where i have a dropdown list bound using knockout:
<select id="RoleGroups" class="tableDropDown" data-bind="options: userGroups,
optionsText: 'group_nm',
optionsValue: 'group_cd',
value: selectedRoleGroup">
</select>
I have a change handler set on the dropdown:
self.roleGroupChanged = function() {
//do stuff
return true;
};
When i update the observable array of the dropdown i reset the current selectedRoleGroup observable to a default. I want to prevent the roleGroupChanged handler from firing in this case so i do the following:
$('.tableDropDown').off('change');
//do stuff...set the dropdown value
$('#RoleGroups').on('change', self.roleGroupChanged);
The problem i am running into is that the observable value selectedRoleGroup is now not being updated when the dropdown changes. I can use jquery to get the current value but that defeats the purpose of using knockout. If i remove the code that uses jquery's off and on methods everything works as expected but the handler fires as a result which is not what i want. Looking for ideas what i am doing wrong.
Instead of using the jQuery events directly, you could tap into the change event in your data-binding. If you use a simple true/false observable for tracking when you are updating your observable array, you can use preventDefault() on the event args for the change event. See updated fiddle
Figured it out so thought i would post....apparently just doing a .off('change') removes more than just my roleGroupChanged handler. If i do a .off('change', self.roleGroupChanged) or if i had namespaced my handler when i ran .on('change.mystuff', self.roleGroupChanged) and followed it up with a .off('change.mystuff') or .off('.mystuff') everything works as expected.
I have a page that displays a list of records. The user can select the record status using radio buttons, e.g.:
<div id="record_653">
<label><input type="radio" name="status_653" value="new" checked/>new</label>
<label><input type="radio" name="status_653" value="skipped" />skipped</label>
<label><input type="radio" name="status_653" value="downloaded" />downloaded</label>
</div>
I am using JQuery to send the changes made by the user back to the server, where I use them to update the database. This is a simplified version of what I do:
$("#record_653").click(
function(event) {
var url = ...,
params = ...;
post(url,params);
});
The problem is that this code will create requests even if the user clicks the same button that was previously checked. What I actually want is the "on change" event, except its behavior in Internet Explorer is not very useful (e.g. here).
So I figure I somehow have to identify if the click event changed the value.
Is the old value stored somewhere (in the DOM? in the event?) so I could compare against it?
If not, how should I store the old value?
The old value is not stored someplace where you can query it, no. You will need to store the value yourself. You could use a javascript variable, a hidden input element, or jQuery's data() function.
EDIT
The jQuery data function provides access to a key-value-pair data structure as a way to store arbitrary data for a given element. The api looks like:
// store original value for an element
$(selector).data('key', value);
// retrieve original value for an element
var value = $(selector).data('key');
A more developed thought:
$(document).ready(function() {
// store original values on document ready
$(selector).each(function() {
var value = $(this).val();
$(this).data('original-value', value);
})
// later on, you might attach a click handler to the the option
// and want to determine if the value has actually changed or not.
$(selector).click(function() {
var currentValue = $(this).val();
var originalValue = $(this).data('original-value');
if (currentValue != originalValue) {
// do stuff.
// you might want to update the original value so future changes
// can be detected:
$(this).data('original-value', currentValue);
}
});
});
$('#record_653 input:radio').each(function() {
$(this).data('isChecked', $(this).is(':checked'));
$(this).click(function() {
if ( $(this).is(':checked') !== $(this).data('isChecked') ) {
// do changed action
} else {
$(this).data('isChecked', !$(this).data('isChecked') );
}
})
});
This was complicated to do in my head but I think you want something like this.
As was suggested by meder and Ken Browning, I ended up using JQuery's data() to store the previous value and check against it on every click.
Storing an "is checked" boolean for each input radio is one solution. However you need to maintain this value. So in the click event handler, in addition to changing the "is checked" of the current input, you need to find the input that was previously checked and change its "is checked" data to false.
What I chose to do instead was to store, in the parent element, the currently checked object. So my code looks something like:
$(document).ready(
function() {
// find the checked input and store it as "currChecked" for the record
$("#record_653").data("currChecked",
$(this).find("input:radio:checked")[0]);
// add the click event
$("#record_653").click( function(event) {
if ($(event.target).is("input:radio") &&
event.target !== $(this).data("currChecked"))
{
$(this).data("currChecked", event.target);
handleChangeEvent(event);
}
});
});
}
);
Thanks
I had the same problem, but with FF I managed to deal with it using the onchange event rather than the onclick.
This is exactly what I was looking for to deal with IE7. Works like a charm!
Thanks for the detailed solution!