Knockout click binding to checkbox prevents jQuery control of value - javascript

If you've got a checkbox on a form, to which you bind a function on click using Knockout, it appears to override programming control of the checkbox status.
There's a fiddle demonstrating the problem here:
http://jsfiddle.net/Y5Zk8/
Code:
<input type="checkbox" id="thisFails" data-bind="click: $root.Fails" />
<label for="thisFails">This Fails</label>
var SimpleModel = function() {
this.Fails = function() {
alert('clicked');
$('#thisFails').attr('checked', true);
}
};
ko.applyBindings(new SimpleModel());
Now, I'm well aware that if I return true or false from my function, it'll work. But imagine for a moment that I can't do that (there's reasons for this - it's complicated). Why can't I control the value of the box in JQuery?

As per this other stackoverflow answer:
knockout event binding
and summarised as:
Allowing the default action
By default, Knockout will prevent the event from taking any default
action. For example if you use the event binding to capture the
keypress event of an input tag, the browser will only call your
handler function and will not add the value of the key to the input
element’s value. A more common example is using the click binding,
which internally uses this binding, where your handler function will
be called, but the browser will not navigate to the link’s href. This
is a useful default because when you use the click binding, it’s
normally because you’re using the link as part of a UI that
manipulates your view model, not as a regular hyperlink to another web
page.
However, if you do want to let the default action proceed, just return
true from your event handler function.
So the default is that unless you return true, the default event will be prevented.
If it's checked value you want to control you could the following, it's a bit long winded mind you and a definite hack, but it works.
Setup an observable on your viewmodel to hold whether the checkbox is checked or not
self.isChecked = ko.observable(false);
Setup a computed observable on your vm that will be used to bind to the checkbox checked value;
self.isCheckedCp = ko.computed(function(){
return self.isChecked();
});
Bind the computed to the checked attribute in the html:
<input type="checkbox" id="thisFails" data-bind="click: $root.Fails,checked:isCheckedCp" />
Change the Fails function to incorporate a timeout that will run immediately after the fails function completes, and in that function, set the value of the underlying isChecked observable (in the example it just toggles the current value)
self.Fails = function(e) {
console.log('e',e.isChecked());
alert('arse');
console.log( $('#thisFails'));
setTimeout(function(){
console.log('set now');
//this works
self.isChecked(!self.isChecked());
//still doesn't work with the set timeout
$('#thisFails').attr('checked', true);
console.log('e',e.isChecked());
}, 0);
}
If you step through the execution of it, you will see that the when it gets to the setTimeout console.log('set now'); that the checkbox has reverted to what it was when you clicked it.
Setting the self.isChecked then updates the observable and the computed is accessed and the checkbox display is updated.
Now the reason that works is quite a bit beyond my knowledge of browsers and their execution paths, but I think the setTimeout with the zero timeout value effectively adds this bit of code to run immediately after the current (click in this case) function, this link goes into some detail:
Why is setTimeout(fn, 0) sometimes useful?
I can't work out why setting checked attribute via your original jquery doesn't work when tried in that timeout function though.
I hope this actually helps you somehow!

Related

onChange event firing incorrectly after being loaded programmatically

I have an APEX application where there are many drop down items. I've bound change event handlers to them using the bind function of jQuery.
Now when I load the content of a drop-down programmatically using $('#ELEMENT').trigger('apexrefresh'), the drop-down reloads but the change event handler fires automatically.
How do I prevent this from happening? I tried avoiding binding the event handler using bind and instead adding the onChange attribute to the element. The incorrect behaviour was still present.
Here is the skeletal code:
$(document).ready(function()
{
$('#P7021_MSG_DEF').bind('change', function(e)
{
console.log('bound function onChange() msg_def');
updateStartWord();
}
);
});
function updateMsgDef()
{
console.log('function updateMsgDef() ');
$('#P7021_MSG_DEF').one('apexafterrefresh', function()
{
if( $x('P7021_RESTORE_CHK').value == 'Y')
{
setdefault('P7021_MSG_DEF', vJson.msg_def);
}
updateStartWord();
}
).trigger('apexrefresh');
}
In the above code, when the updateMsgDef is called from another function the function updateStartWord() gets called twice - once by updateMsgDef() itself and again by the onChange handler that was bound to P7021_MSG_DEF item.
If anyone could help on this?
Calling $('#ELEMENT').trigger('apexrefresh') is going to trigger the change event. Short of going back to the drawing board altogether, the solution is going to be a hack whatever you do. You could poke about in (and quite possibly break) Oracle's javascript. You could write your own AJAX to populate the select list.
The easiest way might be to check in your onChange event which element currently has focus, eg:
onChange = "if($( document.activeElement).attr('id')=='YOUR_PAGE_ELEMENT')
{ $( document.activeElement).trigger('apexrefresh'); };"
If the user has changed the select list, it should still have focus. There's no guarantee that will work in all browsers, but I think it should be ok in current Chrome and IE versions.
I've been in a similar situation to yours, and have come to accept that if the page logic is too complicated to implement using DAs, maintaining it is likely going to be a nightmare whatever happens. Much as I like "proper" programming, Apex is really all about the declarative controls.

Firing change event when knockout view model property is updated

Here's a jsfiddle to show what the issue is:
http://jsfiddle.net/boblauer/BgvV4/
I'm trying to fire the change event after a text field is updated. Unfortunately, inside the subscribe method, the text box's value hasn't been updated yet, so when I figure the change event, it's fired too soon.
I need to fire the change event because I have 3rd party code that is out of my control that relies on the change event.
Any suggestions?
A simple solution is to wrap your call to $("#text1").change() in a setTimeout with a timeout of 0. That's enough to let knockout do the (synchronous) update to the textbox value before the jquery change handler gets invoked.
I forked your fiddle to demonstrate:
http://jsfiddle.net/SuRYa/1//
If this is something you need to do a lot, a better solution is probably to wrap this behavior in a custom binding where the "update" callback of the binding would fire the jquery change event on the updated element.
bmode is right, a custom binding will do it. Although this answer is a bit late, here is the binding in case it helps anyone subsequently reading this post. It updates the value of the textbox using jQuery - the DOM is now updated in order for Bob's 3rd party code to work - so then it fires the change event.
ko.bindingHandlers.valueAndFireChange = {
update: function(element, valueAccessor) {
var val = ko.unwrap(valueAccessor());
if (val == undefined) return;
$(element).val(val);
$(element).change();
}
};
Here's an updated version of Bob's fiddle showing this in action:
http://jsfiddle.net/BgvV4/17/
I changed the alerts to console.log, so you'll need the console open to see the useful info.

Javascript for when AjaxControlToolkit TabContainer is ready?

I have a page with a TabContainer control (from the Ajax Control Toolkit), and I toggle the visibility of some elements on the page depending on the currently-selected tab. I'd been doing this in an event handler for OnClientActiveTabChanged (which works fine), but I discovered that it leaves the page in the wrong state after a postback. I tried adding some code to the document.ready event handler to get the index, but when I do the following:
$(document).ready(function () {
var index = $('#<%= TabContainer1.ClientID %>').[0].control.get_activeTabIndex();
// Do some stuff with the index
});
...I get a null reference exception on the .control property. Is there a way to hook a client-side "ready" event for the TabContainer?
I’m not familiar with the event lifecycle with normal DOM elements (it seems like there ought to be a general onload event, but I don’t see one). If there isn’t an event that can be easily handled, it seemed like it might work to add an UpdatePanel with UpdateMode=Conditional and an AsyncPostBackTrigger that pointed to a hidden button with an onclick event handler that would get the active tab index – but that seems like a lot of moving pieces for something that I’d expect the DOM to expose already.
Too late to be helpful, but I've had the same issue and found a workaround.
Change you code
$(document).ready(function () {
var index = $('#<%= TabContainer1.ClientID %>').[0].control.get_activeTabIndex();
// Do some stuff with the index
});
to
function pageLoad() {
var index = $('#<%= TabContainer1.ClientID %>').[0].control.get_activeTabIndex();
// Do some stuff with the index
};
Explanation here:
http://encosia.com/document-ready-and-pageload-are-not-the-same/
Basically, jQuery's ready event is "a bit early" and the TabContainer is not initialized yet, whereas the client side ASP.Net's pageLoad is late enough and the TabContainer has been initialized by then.
Check this post to save tab selection during postbacks. It works for normal post backs by saving the active tab index in hidden variable. Though its written for a JQuery plugin in the posted link, concept should be the same i.e., persisting the index of the selected tab.

jQuery: Is there a tag like change() that also runs on page load?

I have a set of radio buttons that triggers a jquery function if a specific value is chosen (via .change())
However, in some cases that value will be chosen by default when the page loads. At the moment I have two separate functions - one inside the .change() event, and a conditional that runs on pageload. I'd like to clean this up, so: is there an event which runs both when a value is changed, and at pageload?
Cheers...
You can trigger your change event on pageload like this -
$(document).ready(function()
{
$("#myradioButton").trigger('change');
});
No, but you can use the same handler.
$(document).ready(function() {
radioChangeHandler(); // Call it when the DOM is ready
$('...').change(radioChangeHandler); // Attach it to the radio buttons
});
You can send the deselect the default value on the server (or on client-side), and after binding the change handler, you can call:
$('#radio').change();
In other words, the whole process is:
$(function(){
// deselect the default value
$('#radio').removeAttr('checked');
// binding radios
$('#radios').change(function(){});
// Changing the value of the default radio
$('#radio').attr('checked', 'checked');
});
The normal approach would be to make the call in a separate function, and call this from both places. There is nothing that you can call as you seem to want to.
Yes it does feel slightly untidy to have the same calls in two places, but they have different approaches. The change method is reacting to a control event, whereas the pageload is responding to a page being loaded ( duh ).
You can use something like $(".radio").triggerHandler("change") on page load to trigger event handler without changing state.

Why is onchange on a checkbox not fired when the checkbox is changed indirectly

I'm using Prototype to monitor checkboxes, so I can add javascript checks to them. When the tr or td in which the checkbox is located is clicked, the checkbox should be checked.
When you click directly on a checkbox, the onchange event is fired, so you'll get an alert. When the checkbox' value is changed by javascript (when you click on the tr or td), onchange is not fired. Why is onchange not fired when the checkbox is changed indirectly?
This is the javascript I'm using.
Element.observe(window, 'load', function() {
/* If a tr or td is clicked, change the value of the checkbox. */
$$('#results tr').each(function(el) {
el.observe('click', function(e) {
if(!e.target) { e.target = e.srcElement; }
if(e.target.nodeName == 'TD' || e.target.nodeName == 'TR') {
$('compare-product'+this.id).checked = ($('compare-product'+this.id).checked === false) ? true : false;
}
});
});
/* Monitor if the status of a checkbox has changed. */
$$('#results tr td input').each(function(el) {
el.observe('change', function(e) {
alert('!');
}
);
}
);
}
);
I've tested it in Firefox and IE7, both are not working. I'm not looking for a workaround, I'm just curious to know why this doesn't work.
This isn't uncommon in UI frameworks in general. If you change the state of a control programmatically, it's assumed that you're also capable of programmatically triggering whatever side-effects it's supposed to have. It gives programmers more flexibility, and it avoids bugs where the state is in flux during initialization or teardown. (For example, during initialization, you might set the state of one control before setting the state of several dependent ones. If the change handler for the first control fires immediately, it will execute while the other controls are in an inconsistent state.)
The real reason you can't do this is because it's security issue in the programming model. Events that were not user initiated are usually not chained. So although setting a value is fine, it's not fine to go ahead and fire any events that were set on that control.
What jamesdlin has said makes no sense.
Jamesdlin:
"For example, during initialization,
you might set the state of one control
before setting the state of several
dependent ones. If the change handler
for the first control fires
immediately, it will execute while the
other controls are in an inconsistent
state."
This is true no matter if you set it programmatically change a value or whether you click on the control. In either case you may have dependent other controls.

Categories