I've implemented a javascript-file that, show a loading screen while the shiny is busy. It didn't worked so I've debugged it and I've found that the function
addMessageHandler('progress', function(message) {
$(document.documentElement).addClass('shiny-busy');
for (var i = 0; i < message.length; i++) {
var key = message[i];
var binding = this.$bindings[key];
if (binding && binding.showProgress) {
binding.showProgress(true);
}
}
});
is never called. But the function that removes the class("shiny-busy") it's always called.
The problem seems to be here:
// A function for sending messages to the appropriate handlers.
// - msgObj: the object containing messages, with format {msgObj.foo, msObj.bar
this._sendMessagesToHandlers = function(msgObj, handlers, handlerOrder) {
// Dispatch messages to handlers, if handler is present
for (var i = 0; i < handlerOrder.length; i++) {
var msgType = handlerOrder[i];
if (msgObj[msgType]) { //when msgType = "progress", the if is false and so the function for addClass("shiny-busy") it's not called
// Execute each handler with 'this' referring to the present value of
// 'this'
handlers[msgType].call(this, msgObj[msgType]);
}
}
};
How can/should I solve the problem?
Thank you!
EDIT: PROBLEM SOLVED!
Related
I'm busy trying to dynamically assign functions to certain buttons and I've run into a strange problem that I'm absolutely stumped with.
I have the following simple HTML for demonstration purposes
<div id="butts">
<button>BUTT 01</button>
<button>BUTT 02</button>
</div>
Now I am assigning functions to these buttons using JavaScript with the following loop (including the event)
var butts = $("#butts").find("button");
for(var cnt = 0; cnt < butts.length; cnt ++) {
// get button description just for testing
var buttonDesc = $(butts[cnt]).text();
// EVENT
butts[cnt].addEventListener (
"click", function(event) {
funcEvent(event)
}
);
}
Calling a very simple test function to verify that it is working
function funcEvent(event) {
console.log("funcEvent");
console.log(event);
}
This is working fine but I also need to pass a variable to the function which I would normally do as follows
var butts = $("#butts").find("button");
for(var cnt = 0; cnt < butts.length; cnt ++) {
// get button description just for testing
var buttonDesc = $(butts[cnt]).text();
// BIND
butts[cnt].addEventListener (
"click", funcBind.bind(this, buttonDesc)
);
}
Another very simple test function
function funcBind(buttonDesc) {
console.log("funcBind");
console.log(buttonDesc);
}
Separately they both work just fine but I am struggling to pass the event argument in the bind function
I am trying to combine the two so that I can call a single function that can receive both the event and the argument
UPDATE
This seems to be a possible fix although I do not understand how to be honest
With the same loop
var butts = $("#butts").find("button");
for(var cnt = 0; cnt < butts.length; cnt ++) {
// get button description just for testing
// using var did not work (always last element of array)
// var buttonDesc = $(butts[cnt]).text();
let buttonDesc = $(butts[cnt]).text();
// EVENT
butts[cnt].addEventListener (
"click", function(event) {
funcEventBind(event, buttonDesc);
}
);
}
Calling a very simple test function to verify that it is working
function funcBindEvent(event, buttonDesc) {
console.log("funcEvent");
console.log(event);
console.log(buttonDesc);
}
You need to create a closure so that the even handler callback contain the context. You can do that by using forEach like this
butts.forEach(function(bt) {
var buttonDesc = $(bt).text();
// BIND
bt.addEventListener (
"click", function(event){
funcBind(event, buttonDesc)
}
);
})
Here's the deal:
I populate a local array in a callback like so:
var datasource = datasources[id];
var contexts = [];
datasource.data($selected.parent().data(), function (items) {
var dataarr = items.data;
for (var i = 0; i < dataarr.length; ++i) {
contexts.push(dataarr[i]);
}
});
foo(contexts);
Now, in foo, I run a simple check like:
function foo(contexts) {
if (contexts.length < 2) {
return;
}
}
If I break at the return statement above, contexts.length is in fact, greater than 2. Similarly, if I run the code step by step in a debugger, everything works as expected.
This make me suspect that when not running in a debugger, this code is being executed before the callback has completed.
Fine. But how can I control the execution order? Or, perhaps, is there a better paradigm to go about this if the only way I know can acquire items.data is from within that callback?
Thanks!
I'm not sure about the context here a little bit, but if you want your method foo to be called after the array has been populated, why not call it after the for loop? like so:
datasource.data($selected.parent().data(), function (items) {
var dataarr = items.data;
for (var i = 0; i < dataarr.length; ++i) {
contexts.push(dataarr[i]);
}
foo(contexts);
});
if you're uncomfortable with that, look at PubSubJS which allows you to emit events and handle them asynchronously. I wrote an article about it's usage here.
Edit: An example:
datasource.data($selected.parent().data(), function (items) {
var dataarr = items.data;
for (var i = 0; i < dataarr.length; ++i) {
contexts.push(dataarr[i]);
}
PubSub.publish('contextsPopulated', contexts);
});
and then modify foo as:
function foo(message, contexts) {
if (contexts.length < 2) {
return;
}
}
now register foo to be called whenever 'contextsPopulated' is signalled.
PubSub.subscribe('contextsPopulated', foo);
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
I add event handlers to multiple hrefs on my website with JS like this:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++)
{
button.addEventListener('click',function() { addTosel(i); },true);
}
}
}
But unfortunately to addTosel is passed the last i not the i from the loop. How to pass i accordingly to the object being processed in this moment?
You need to create a closure:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', function(index) {
return function () {
addTosel(index);
};
}(i), true);
}
}
This way the scope of the handler is bound to the proper context of i.
See this article for more information on this subject.
You need to bind the i variable to the function when its declared. like so
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click',(function() { addTosel(this); }).bind(i) ,true);
}
Note: I just wrote the code from memory so it may not be perfect, but it is the sulution you're needing, for reference as to the proper way, ie with cross browser shims etc look at:
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
If you're going to take the .bind approach, do it like this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', addTosel.bind(null, i), true);
}
This makes a new function with null bound as the this value since your function doesn't seem to need it, and the current i bound as the first argument.
Or make your own binder function
var _slice = Array.prototype.slice;
function _binder(func, ctx /*, arg1, argn */) {
var bound_args = _slice.call(arguments, 2);
return function() {
return func.apply(ctx, bound_args.concat(_slice.call(arguments)));
}
}
And then do this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', _binder(addTosel, null, i), true);
}
function createTextFields(obj) {
for (var i = 0; i < obj.length; i++) {
var dataDump = {};
for (var key in obj[i]) {
var textField = Ti.UI.createTextField(pm.combine($$.labelBrown, {
left: 200,
height:35,
value:obj[i][key],
width:550,
keyboardType:Ti.UI.KEYBOARD_NUMBER_PAD,
layout:'horizontal',
backgroundColor:'transparent',
id:i
}));
dataDump[key] = textField.value;
var callback = function (vbKey) {
return function (e) {
dataDump[vbKey] = e.source.value;
};
}(key);
}
globalData.push(dataDump);
}
}
I am using the simlar code for Adding the data and it works fine. I posted the problem yesterday and it got resolved...
Last Object is always getting updated?
Now when i go to edit page, it shows me four text fields or number of text fields added... now when i edit something and click on save... the value get's updated on the fourth or the last TextFields Object...
Don't define functions inside loops. Computationally expensive and leads to problems, like this one. Here's a fix that should solve it:
function createTextFields(obj) {
var callback = function (vbKey, localDump) {
return function (e) {
localDump[vbKey] = e.source.value;
};
}
var i;
var max = obj.length;
for (i = 0; i < max; i++) {
var dataDump = {};
for (var key in obj[i]) {
dataDump[key] = textField.value;
var callBackInstance = function(keyn, dataDump);
}
globalData.push(dataDump);
}
}
JavaScript does not have block level scope, so your variables dataDump and callback, though "declared" inside for-loops actually belong to the function. As in, you're saving a value to dataDump, then you're overwriting it, each time you go through the loop. Which is why finally only the code that operated on the last value remains.
Take a look at What is the scope of variables in JavaScript? too.
I am trying to open a window from a php script. But when I click I get this error
Line: 389
Error: 'surgExpressBuildDefaultButton' is null or not an object
Line 389 code is as follow
function setupLayout(i)
{
document.body.onselectstart = function()
{
if (event.srcElement.tagName.search(/input|textarea/i)) return false;
}
setupButtons();
if(window.parent.opener.parent.frames[0].surgExpressBuildDefaultButton)
{
buttonClick(window.parent.opener.parent.frames[0].surgExpressBuildDefaultButton);
}
else
{
layout.buttons.commonButton.fixSelected();
}
for(i = 0; i < da.imgPlus.length; i++)
{
da.imgPlus[i].onclick = clickPlus;
da.imgMinus[i].onclick = clickMinus;
}
for(i = 0; i < da.spnName.length; i++)
{
da.spnName[i].selected = 0;
da.spnName[i].title = da.spnName[i].innerText;
da.spnName[i].onclick = function(){selectCommonProcedure(this);}
da.spnName[i].ondblclick = function(){addCommonProcedure(this);}
da.spnName[i].onmouseout = function(){this.className = (this.selected ? "nSelected" : "nOut");}
da.spnName[i].onmousedown = function(){this.className = "nDown";}
da.spnName[i].onmouseover = da.spnName[i].onmouseup = function(){this.className = "nOver";}
}
da.inpSearch.onkeydown = function(){if(event.keyCode == 13) updateProcedureList();}
da.btnSearch.onclick = da.selSpecialty.onchange = updateProcedureList;
da.btnClose.onclick = function(){window.close();}
da.btnAdd.disable = da.btnSave.disable = CC.Disable;
da.btnAdd.disable(1);
da.btnAdd.onclick = addCommonProcedure;
da.btnSave.disable(1);
da.btnSave.onclick = saveExpress;
}
what could be the problem. Any idea?
It's hard to tell without knowing which of those four dozen lines is line 489, but this jumped out at me:
function setupLayout(i)
{
document.body.onselectstart = function()
{
if (event.srcElement.tagName.search(/input|textarea/i)) return false;
^-- here
You're using event without having declared it (at least, not in the code you quoted). On IE, that'll work because IE makes the event object a property of window (and unless they're shadowed by other declarations, you can access window properties without explicitly qualifying them), but on most other browsers the event object is an argument to the event handler function – and so that code tries to use an undefined value as an object reference, which triggers exactly the error message you're seeing.
The usual idiom for dealing with this discrepancy in browsers (other than using a library like jQuery or Prototype that handle it for you) is this:
document.body.onselectstart = function(event)
{
event = event || window.event;
// ...
};
That idiom declares event it as an argument to the function, but then has it fall back to looking on the window (to support IE).