setAttribute, onClick and cross browser compatibility - ajax

I have read a number of posts about this but none with any solid answer. Here is my code:
// button creation
onew = document.createElement('input');
onew.setAttribute("type", "button");
onew.setAttribute("value", "hosts");
onew.onclick = function(){fnDisplay_Computers("'" + alines[i] + "'"); }; // ie
onew.setAttribute("onclick", "fnDisplay_Computers('" + alines[i] + "')"); // mozilla
odiv.appendChild(onew);
Now, the setAttribute() method (with the mozilla comment) works fine in mozilla but only if it comes AFTER the line above it. So in other words it seems to just default to whichever gets set last. The .onclick method (with the ie comment) does not work in either case, am I using it incorrectly?
Either way I can't find a way to make this work at all in IE, let alone in both. I did change the function call when using the .onclick method and it worked fine using just a simple call to an alert function which is why I believe my syntax is incorrect.
Long story short, I can't get the onclick parameter to work consistently between IE/Mozilla.
-- Nicholas

onew.setAttribute("type", "button");
Never use setAttribute on HTML documents. IE gets it badly wrong in many cases, and the DOM-HTML properties are shorter, faster and easier to read:
onew.type= 'button';
onew.onclick = function(){fnDisplay_Computers("'" + alines[i] + "'"); }; // ie
What is ‘alines’? Why are you converting it to a string and surrounding it with single quotes? It looks like you are trying to do something heinous involving evaluating code in a string (which is what you're doing below in the ‘onew.setAttribute’ version). Evaluating JavaScript code in strings is almost always the Wrong Thing; avoid it like the plague. In the above case, IE should do the same as Firefox: it shouldn't work.
If ‘alines[i]’ is a string, I guess what you're trying to do is make it remember that string by constructing a code string that will evaluate in JavaScript to the original string. But:
"'" + alines[i] + "'"
is insufficient. What happens if ‘alines[i]’ has an apostrophe in, or a backslash?
'O'Reilly'
you've got a syntax error and possible security hole. Now, you could do something laborious and annoying like:
"'" + alines[i].split('\\').join('\\\\').split("'").join("\\'") + "'"
to try to escape the string, but it's ugly and won't work for other datatypes. You could even ask JavaScript to do it for you:
uneval(alines[i])
But not all objects can even be converted to evaluatable JavaScript source strings; basically the entire approach is doomed to failure.
The normal thing to do if you just want to have the onclick callback call a function with a parameter is to write the code in the straightforward way:
onew.onclick= function() {
fnDisplay_Computers(alines[i]);
};
Generally this will work and is what you want. There is, however, a slight wrinkle which you may have hit here, which could be what is confusing you into considering the wacky approach with the strings.
Namely, if ‘i’ in this case is the variable of an enclosing ‘for’ loop, the reference to ‘alines[i]’ won't do what you think it does. The ‘i’ will be accessed by the callback function when the click happens — which is after the loop has finished. At this point the ‘i’ variable will be left with whatever value it had at the end of the loop, so ‘alines[i]’ will always be the last element of ‘alines’, regardless of which ‘onew’ was clicked.
(See eg. How to fix closure problem in ActionScript 3 (AS3) for some discussion of this. It's one of the biggest causes of confusion with closures in both JavaScript and Python, and should really be fixed at a language level some day.)
You can get around the loop problem by encapsulating the closure in its own function, like this:
function callbackWithArgs(f, args) {
return function() { f.apply(window, args); }
}
// ...
onew.onclick= callbackWithArgs(fnDisplay_Computers, [alines[i]]);
And in a later version of JavaScript, you'll be able to say simply:
onew.onclick= fnDisplay_Computers.bind(window, alines[i]);
If you would like to be able to use ‘Function.bind()’ in browsers today, you can get an implementation from the Prototype framework, or just use:
if (!('bind' in Function.prototype)) {
Function.prototype.bind= function(owner) {
var that= this;
var args= Array.prototype.slice.call(arguments, 1);
return function() {
return that.apply(owner,
args.length===0? arguments : arguments.length===0? args :
args.concat(Array.prototype.slice.call(arguments, 0))
);
};
};
}

I usually use something like:
onew.onclick = new Function("fnDisplay_Computers('" + alines[i] + "')");
this should work both in IE e Firefox.

Use the addEventListener() function with "click" for the type argument for Mozilla-based browsers, and attachEvent() function with "onclick" as the sEvent argument for IE; I find it best to use a try/catch statement, for example:
try {
onew.attachEvent("onclick", //For IE
function(){fnDisplay_Computers("'" + alines[i] + "'"); });
}
catch(e) {
onew.addEventListener("click", //For Mozilla-based browsers
function(){fnDisplay_Computers("'" + alines[i] + "'"); },
false);
}

I think #3 protesteth too much. In a lot of situations I'm building a table dynamically and need to pass parameters to the callback function. It isn't a typesafe issue since my variable parm is an integer index to the table row in question. Here's code with both a variable and fixed parameter that seems to be cross-browser compliant:
for (i = 0; i < arrTableData.length; i++) {
eleTR = objTable.insertRow(i + 1);
cell = eleTR.insertCell(0);
cell.width = "21";
var myElement = document.createElement('img');
myElement.setAttribute('src', 'images/button_down.gif');
myElement.setAttribute('alt', 'Move item down');
myElement.onclick = new Function('moveItem(' + i + ',0)');
cell.appendChild(myElement);
}

Related

TestComplete object sometimes not found

I have some trouble with TestComplete because sometimes it won't find my objects, sometimes it just doesn't and I get an error because the object is null.
For instance in this small function
function SelectCountry(country){
var page = Sys.Browser("*").Page("*");
var panel = page.Form("ID1");
select = panel.FindChildByXPath("//select[#id='ID2']");
select.ClickItem(country);
link = page.FindChildByXPath("//a[#id='ID3']");
link.Click();
page.Wait();
}
I get an error for 4 out of 5 runs telling me that select has not been found, but then on the one lucky run, everything passes fine.
Can anyone tell me what I have to check for?
Try searching for your object in a loop. Use the Exists property of the object to determine if the object exists after each search of the page. Another option would be to use the Wait methods https://support.smartbear.com/viewarticle/73657/
I would suggest avoiding hard coded delays for the reasons you have discovered. They way I search for page objects in my project is to do the search in a loop and log an error if not found.
var stopTime = Win32API.GetTickCount() + 60000;
var currentUpTime = Win32API.GetTickCount();
while (currentUpTime < stopTime) { //repeat search for element for n milliseconds
currentUpTime = Win32API.GetTickCount();
for (i = 0; i < attributes.length; i++) {
var element = eval('Sys.Browser("iexplore").Page("*").' + tcMethod + '(' + '"' + attributes[i] + '"' + ',' + "'" + attributeValue + "'" + ',20000' + ')');
if (element.Exists) {
return element;
}
}
}
I found a working solution. It's evident that the source of the problem is that the page is not properly loaded. So I put some hard coded stops before every stap that loades a new page.
aqUtils.Delay(2000);
Sometimes I have even to go for 5 seconds.
This is still not very stable since for some reason delays could be higher sometimes.
Is there some way of telling TestComplete it should try to find an element during 30 seconds and only then raise an error?
you can always put up a delay in the test with the test complete code
aqUtils.Delay(2000);
as mentioned.But this can also occur in case you are doing something very fast in the tests because of which the test reaches the point before the object is visible. That is reason why we use the delay in test to wait for object to load.
Try putting the breakpoint at the object and check for after waiting for 10 seconds.if the test passes in all the cases in this method it should be due to delay in object load.Or use the wait process mentioned in
https://support.smartbear.com/testcomplete/docs/app-objects/common-tasks/waiting-process-or-window-activation.html

Backbone navigate triggers twice in Firefox

Trying to use Backbone's navigate property.
this.navigate("week/" + companyName + "/" + employeeNo + "/" + weekEnd, { trigger: true, replace: false });
The code above is executed once.
It hits this:
routes: {
"week/:companyName/:employeeNo/:weekEnd": "getWeek"
},
And then this function gets hit twice:
getWeek: function (companyName, employeeNo, weekEnd) {
console.log('getWeek:', companyName, employeeNo, weekEnd);
}
It is logged twice in Firefox, only once in IE and Chrome.
What's the issue here? I originally didn't even have trigger set to true, and Firefox ignored that and still triggered the URL.
I had a similar issue recently with Firefox doing two server calls after a Backbone.navigate. In my case it was because we had not encoded the string. Does your company name have any characters which should be encoded?
You could try:
this.navigate("week/" + escape(companyName) + "/" + employeeNo + "/" + weekEnd, { trigger: true, replace: false });
Stepping in as I've run into the same issue and got to the underlying problem here.
As everyone mentioned before, the problem comes from URL encoding. Now as to why the issue only appears in Firefox...
Let's start by summarizing quickly how the routes are called when the hash changes. There are 3 key functions here:
loadUrl: this function is the one that will call your route handler.
navigate: this is the function used to change the route manually. If the trigger flag is set to true, the function will call loadUrl.
checkUrl: this function is set as callback for the onhashchange event on the window object (when it's available of course). It also runs loadUrl on certain conditions.
Now, we're getting to the interesting part.
When you run navigate, Backbone will cache the fragment you navigated to. The hash changing, checkUrl will also be called. This function will then check if the cached hash equals the current one, so as not to execute loadUrl if you called navigate before, because it would mean it has already been called. To make that comparison, checkUrl gets the current hash with the function getFragment, which uses getHash. Here is getHash's code:
getHash: function(window) {
var match = (window || this).location.href.match(/#(.*)$/);
return match ? match[1] : '';
},
And you got your problem. location.href is URI-encoded in firefox, but is not in chrome. So if you navigated to another hash (with or without the trigger flag), in firefox, Backbone will cache the unencoded version of your hash, and then compare it with the encoded version. If your hash contained a should-be-encoded character, the result of the comparison will be negative, and Backbone will execute the route handler it should not execute.
As per the solution, well, folks said it before, your URIs should be encoded.
Question may be old, but for me this was still relevant. Encoding the url wasn't enough in my case. I replaced the GetHash() function in Backbone with:
getHash: function (t) {
var e = (t || this).location.href.match(/#(.*)$/);
return match ? this.decodeFragment(match[1]) : '';
}

How to prevent CKEditor replacing spaces with ?

I'm facing an issue with CKEditor 4, I need to have an output without any html entity so I added config.entities = false; in my config, but some appear when
an inline tag is inserted: the space before is replaced with
text is pasted: every space is replaced with even with config.forcePasteAsPlainText = true;
You can check that on any demo by typing
test test
eg.
Do you know how I can prevent this behaviour?
Thanks!
Based on Reinmars accepted answer and the Entities plugin I created a small plugin with an HTML filter which removes redundant entities. The regular expression could be improved to suit other situations, so please edit this answer.
/*
* Remove entities which were inserted ie. when removing a space and
* immediately inputting a space.
*
* NB: We could also set config.basicEntities to false, but this is stongly
* adviced against since this also does not turn ie. < into <.
* #link http://stackoverflow.com/a/16468264/328272
*
* Based on StackOverflow answer.
* #link http://stackoverflow.com/a/14549010/328272
*/
CKEDITOR.plugins.add('removeRedundantNBSP', {
afterInit: function(editor) {
var config = editor.config,
dataProcessor = editor.dataProcessor,
htmlFilter = dataProcessor && dataProcessor.htmlFilter;
if (htmlFilter) {
htmlFilter.addRules({
text: function(text) {
return text.replace(/(\w) /g, '$1 ');
}
}, {
applyToAll: true,
excludeNestedEditable: true
});
}
}
});
These entities:
// Base HTML entities.
var htmlbase = 'nbsp,gt,lt,amp';
Are an exception. To get rid of them you can set basicEntities: false. But as docs mention this is an insecure setting. So if you only want to remove , then I should just use regexp on output data (e.g. by adding listener for #getData) or, if you want to be more precise, add your own rule to htmlFilter just like entities plugin does here.
Remove all but not <tag> </tag> with Javascript Regexp
This is especially helpful with CKEditor as it creates lines like <p> </p>, which you might want to keep.
Background: I first tried to make a one-liner Javascript using lookaround assertions. It seems you can't chain them, at least not yet. My first approach was unsuccesful:
return text.replace(/(?<!\>) (?!<\/)/gi, " ")
// Removes but not <p> </p>
// It works, but does not remove `<p> blah </p>`.
Here is my updated working one-liner code:
return text.replace(/(?<!\>\s.)( (?!<\/)|(?<!\>) <\/p>)/gi, " ")
This works as intended. You can test it here.
However, this is a shady practise as lookarounds are not fully supported by some browsers.
Read more about Assertions.
What I ended up using in my production code:
I ended up doing a bit hacky approach with multiple replace(). This should work on all browsers.
.trim() // Remove whitespaces
.replace(/\u00a0/g, " ") // Remove unicode non-breaking space
.replace(/((<\w+>)\s*( )\s*(<\/\w+>))/gi, "$2<!--BOOM-->$4") // Replace empty nbsp tags with BOOM
.replace(/ /gi, " ") // remove all
.replace(/((<\w+>)\s*(<!--BOOM-->)\s*(<\/\w+>))/gi, "$2 $4") // Replace BOOM back to empty tags
If you have a better suggestion, I would be happy to hear 😊.
I needed to change the regular expression Imeus sent, in my case, I use TYPO3 and needed to edit the backend editor. This one didn't work. Maybe it can help another one that has the same problem :)
return text.replace(/ /g, ' ');

Firefox & AJAX oddity: DOM replacements not working again after just working

these are the two functions (externally loaded):
function replaceText(element, text) {
if (element != null) {
clearText(element);
var newNode = document.createTextNode(text);
element.appendChild(newNode);
}
}
function replaceImage(element, maker, imageState) {
replaceText(element, "replacing image " + maker + " with " + imageState + " version");
var imagePath = "_img/coffeeMaker_";
if (maker != null)
{
document.getElementById("coffeeMakerImg"+ maker).src = imagePath + imageState + ".png";
}
}
now here's the part that calls these functions. *notice that the replaceText() is called from within replaceImage()
replaceText(cmStatus_01, "BREWING " + name + "'s " + size + " " + beverage);
replaceImage("feedback", "01", "full");
document.forms[0].reset();
okay. now here's the kicker: the FIRST replaceText() works fine in ALL browsers. the replaceImage() fails ONLY in Firefox which CONTAINS A CALL TO replaceText() that only JUST worked as advertised!! i could see how i might have screwed up the image replacement (even though i copy/pasted it from another working project that DOES replace the image in FF...so weird...), but i do NOT see how the replaceText() can fail: it just worked!
so: whaaaaat!? i'm thinking its some kind of scope issue, but i'm stumped as to why.
totally stumped. forehead really sore...
thank for your time and help. i'm praying this isn't something really retarded...
WR!
PS: i'm also confused why, if i remove the quotes from the element name in the replaceImage() call, it breaks; but it works in the replaceText() call without brackets just fine...
okay. i figured it out. the problem was actually what i was passing into the functions:
cmStatus_01 was NOT the actual ID of the div. it was evaluated earlier like so:
var cmStatus_01 = document.getElementById('divName');
but i WAS passing the divName into the replaceImage() function and it was expecting the evaluted version of it, like cmStatus_01. so it broke.
so when i actually retooled the function so i was ONLY passing divName, it obviously worked. this is the retool:
function replaceNodeText(id, newText)
{
var node = document.getElementById(id);
while (node.firstChild)
{
node.removeChild(node.firstChild);
}
node.appendChild(document.createTextNode(newText));
}
project deadline too tight! it's making my brain fail. :P
also: apologies for not posting where the variables came from. that would have helped enormously, i'm sure and i don't know why i didn't think to post them as well.
thank you for your patience and your time.
WR!

Prototype: how to dynamically construct selector?

I am having a little bit of difficulty passing a variable into a selector in prototype. I would like to be able to pass a variable into the select string, so that one function can work for many of the same kind.
At the moment, this is what I would basically like to do:
function myFunct(var)
{
$(var + 'add_form').hide() //so inde the brackets would be ('#product1 #add_form') for example.
}
Be able to pass 'var' into the function that would pass it to the selector, so that I can hide a pattern that is the same for many on the page.
Any ideas for a path to follow would be greatly appreciated.
You're on the right track! Couple things:
var is a JavaScript keyword (source), don't use it to name a variable
if you're querying an element by id (such as #add_form) you don't need to add any container element as you're doing
If you're querying an element by class, you need to use the $$ function, not the $ function
You need to iterate over the wrapped set to call your method
whitespace is significant in css selectors, so make sure to include those in your selector construction to tell Prototype to search within your parent container:
function myFunct(parent) {
$$(parent + ' .add_form').invoke('hide')
}
myFunct('#someparent'); // hides .add_form inside #someparent
That should work... just rename var to something else.
function myFunct(yourVar)
{
$$('#' + yourVar + ' .add_form').each(function(s){ s.hide(); }); // yourVar being the id of the container element
}
I've put a '.' in front of add_form because you can't use multiple elements with same ID, make it a class.

Resources