This is a little hard to describe but here goes. I'm building a page that looks something like the back end to Azure where the user chooses from a menu on the left that loads an item, call it AllUsers for now, to the right of it. From AllUsers there are options for that item and some of those might load another to the left of it, we will call that User. From User you could open more items to the right.
I have it setup nicely with a JS object something like
var AdminPage=function(){
this.con="../controllers/admin/admin_con.php";
this.pageName=null;
this.childPage=null;
this.page=null;
this.deleteItem=function(){
//Recursively deletes pages to the right
if(this.childPage){
this.childPage.delete();
this.childPage=null;
}
if(this.page){
this.page.remove();
}
return this;
}
this.add=function(pageData){
if(this.childPage){
this.deleteItem();
}
this.childPage=new AdminPage();
this.childPage.getPage(pageData);
return this;
};
this.getPage=function(pageData){
var that=this;
this.pageName=pageData.page;
$.post(this.con, pageData,
function(data,status){
if(status==='success'){
$("#rightCol").append(data);
var num=$("#rightCol").children().length;
that.page=$("#rightCol").children()[num-1];
eval(pageData.page+"=that");
}
}
);
return this;
};
}
So each time a page is added it creates a new instance of the object for that item to work with. If that item is removed it recursively removes all items to the right of it as well.
The line "eval(pageData.page+"=that");" and yes I now that eval is evil and have read all about it, creates a new variable name from the name of the item that has been added. That item say the User item then uses that variable when even it needs to call on its instance of the script. This all works mostly perfectly.
Where it fails is if the User item opens another, call it Connections that shows all the connections for that user. I then want to be able to click on a connection and have it open another User item, this works as it should how ever the problem lies when the second User item is opened and creates a new instance of the AdminPage because the same script has just loaded and has the same name it over writes the JS variable from the previous one. Then then affects several things. If you close one of them it closes both for instance and if you close the first that was loaded it should close what is to the right of it however the childPage property is now empty so that doesn't work.
So now for the question..
Is there a better way to handle creating the variables or is there an all around better way all together that will not cause this problem at all?
Can I get around using the Evil eval and make everything right with the world?
Thanks for any suggestions that you may have.
So I have found an answer to the problem but I still don't like it much.
I have generated a unique id and appended it to the end of the name of the script that I was using as the variable.
Issues with this method is it is still using eval and now I have to pass the variable round trip when getting the new item so that I can use php to inject the variable name into the page.
Not perfect and there has to be a better way.
Here is the updated add function
this.add=function(pageData){
if(this.childPage){
this.deleteChildren();
}
this.childPage=new AdminPage();
this.childPage.id=pageData.page+"_"+(Math.floor(Math.random() * (999999999 - 100000000 + 1)) + 100000000);
eval(this.childPage.id+"=this.childPage");
this.childPage.getPage(pageData);
return this;
};
Related
I am importing a set of notes into my webpage, this is to read a JSON file locally in a loop and append the read data into the main div. No problem till now. But then I'm producing a ckeditor instance beside each note for the client to become able to easily add comments to his note of interest. The comments are initially generated as several indexed empty div's in another HTML file, loaded into the ckeditor instances. However, all these happen in a really large for loop (I have almost 6000 notes to be loaded in a segmented manner using if conditions), and so now I'm engaged with the classic closure-loop problem. Have read several previous questions and answers foo this and other websites and tested a number of them to get rid of the closure-loop problem, but no success so far.
The related segment of my java script has the structure:
var q;
$.when(
$.ajax( ... loads the json file that contains the notes and set q=$.parseJSON(data) on success)
).then(function() {
for(var i in q) {
if(i is in a specific range){
... several lines of code for properly importing the notes ...
... and generating a place for the comments to appear as:
... +'<div id="CKEditor'+i+'" contenteditable="true" placeholder="Put your comment here!"></div>'
... which is appended to the main div of the webpage
... Now the main problematic part begins:
$('#temporary').empty(); // a hidden div defined somewhere in the page
var func = (function() {
var ilocal=i, tmp;
return function() {
tmp=document.getElementById('temporary').innerHTML;
alert(tmp);
CKEDITOR.instances['CKEditor'+ilocal].setData(tmp);
}
})();
$.when(
$('#temporary').load("NewComments.htm #verse-"+i)
).then(func);
};
};
CKEDITOR.disableAutoInline = true;
CKEDITOR.inlineAll();
})
maybe the problem is not for the loop but for the nested $.when().then(), any suggestion to resolve the issue?
The problem is that there is only a single $('#temporary') div in your page, which will be re-used and overwritten by every iteration. In particular, in your callback
document.getElementById('temporary').innerHTML;
…
CKEDITOR.instances['CKEditor'+ilocal]
the ilocal (and tmp) variables are indeed local to the IIFE and that particular iteration, but document.getElementById is global. It will return the same element every time.
A quick fix is to create a new element for every request, and assign it to tmp during the iteration (like you assign i to ilocal) instead of when the func is called.
A much better practice however would be not to use $('#temporary').load("NewComments.htm #verse-"+i) multiple times, and instead load the NewComments.htm only once per Ajax and process the result as you need.
I am trying to favorite tweets using javascript selenium webdriver. What I want to do is search a keyword, go to live tab, favorite the last 50 tweets and follow those people. My favorite tweets part of code fails and I get a
StaleElementReferenceError: Element not found in the cache - perhaps the page has changed since it was looked up
error. Here is my code, can you help me how to click to the favorite buttons?
var button = driver.findElement(By.className('HeartAnimation'));
function buttoninthearray(driver, i) {
var buttons = driver.findElements(By.className('HeartAnimation'));
return webdriver.promise.filter(buttons, function(button) {
return button.isDisplayed();
}).then(function(visiblebuttons) {
return visiblebuttons[i];
});
}
for( i = 0; i <limit; i++){
buttoninthearray(driver, i).then(function(button){
button.click();
});
driver.sleep(1000);
}
This is a snippet from the selenium docs on Stale Element Reference Exception:
A common technique used for simulating a tabbed UI in a web app is to prepare DIVs for each tab, but only attach one at a time, storing the rest in variables. In this case, it's entirely possible that your code might have a reference to an element that is no longer attached to the DOM (that is, that has an ancestor which is "document.documentElement").
Here is a link to the docs:
http://docs.seleniumhq.org/exceptions/stale_element_reference.jsp
Since you are dealing with a tabbed UI, this might be the issue.
Like the page says, "If WebDriver throws a stale element exception in this case, even though the element still exists, the reference is lost. You should discard the current reference you hold and replace it, possibly by locating the element again once it is attached to the DOM."
I'm writing a Firebase web app (Javascript). If I write a reference to child_added like this:
dataRef.child('bids').child(auction).child(lotno).on('child_added', function(data){...}
"auction" and "lotno" are variables and will change as the app runs. I am writing the script to close each old reference (using OFF) whenever this happens, and then I open a new reference. My OFF looks like this:
dataRef.child('bids').child(auction).child(lotno).off('child_added');
First of all, am I doing this correctly? And second, is there a more universal way I can close child_added references, to avoid the possibility of one being left open accidentally?
Meaning, can I simply write Javascript that will close all child_added within 'bids'? Wondering if something like this is valid:
dataRef.child('bids').off('child_added');
Is there a wildcase that would cover all children? Would that close everything, or must each specific path through child elements be written out?
Since the path is built with two variables, and only one listener is open at any given time, I decided to solve this problem by simply storing the two values separately, in their own variables, such as:
var storedPath1;
var storedPath2;
dataRef.child('bids').child(auction).child(lotno).on('child_added', function(data){
if (storedPath1!=undefined && storedPath2!=undefined{
dataRef.child('bids').child(storedPath1).child(storedPath2).off('child_added');
}
storedPath1=auction;
storedPath2=lotno;
}
If the listener was previously established, this will close it when opening the new one.
In one of our tests, we need to make sure that the tab keyboard navigation inside a form is performed in the correct order.
Question: What is the conventional way to check the tab navigation order with protractor?
Currently we are solving it by repeating the following step for as many input fields existing in a form (code below):
check the ID of the currently focused element (using getId())
send TAB key to the currently focused element
Here is the example spec:
it("should navigate with tab correctly", function () {
var regCodePage = new RegCodePage();
browser.wait(protractor.ExpectedConditions.visibilityOf(regCodePage.title), 10000);
// registration code field has focus by default
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Remember Registration Code
regCodePage.registrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.rememberRegistrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Request Code
regCodePage.rememberRegistrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.requestCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Cancel
regCodePage.requestCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.cancelButton.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved back to the input
regCodePage.cancelButton.sendKeys(protractor.Key.TAB);
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
});
where regCodePage is a Page Object:
var RegCodePage = function () {
this.title = element(by.css("div.modal-header b.login-modal-title"));
this.registrationCode = element(by.id("regCode"));
this.rememberRegistrationCode = element(by.id("rememberRegCode"));
this.requestCode = element(by.id("forgotCode"));
this.errorMessage = element(by.css("div.auth-reg-code-block div#message"));
this.sendRegCode = element(by.id("sendRegCode"));
this.cancelButton = element(by.id("cancelButton"));
this.closeButton = element(by.css("div.modal-header button.close"));
};
module.exports = RegCodePage;
It is working, but it is not really explicit and readable which makes it difficult to maintain. Also, another "smell" in the current approach is a code duplication.
If the current approach is how you would also do it, I would appreciate any insights about making it reusable.
I think the PageObject should define a tab order list, since that is really a direct property of the page, and should be expressible as simple data. An array of items seems like a sufficient representation, so something like:
this.tabOrder = [ this.registrationCode, this.rememberRegistrationCode, this.requestCode, this.cancelButton ];
Then you need a bit of generic code that can check a tab order.
function testTabOrder(tabOrder) {
// Assumes TAB order hasn't been messed with and page is on default element
tabOrder.forEach(function(el) {
expect(el.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
el.sendKeys(protractor.Key.TAB);
});
}
Then your test would be something like:
it('has correct tab order', function() {
var regCodePage = new RegCodePage(); // this should probably be in the beforeEach
testTabOrder(regCodePage.tabOrder);
});
Of course, this assumes each element has a "getId()" method that works. (That seems like a reasonable assumption to me, but some environments may not support it.)
I think this keeps the tab-order nicely isolated on the PageObject (so its easy to keep in sync with the page content and doesn't get lost in the code that verifies the order). The testing code seem "optimistic" (I suspect the real world will introduce enough problems that you will end up expanding this code a bit).
I haven't tried any of this yet, so feel free to downvote if this doesn't work. :)
Also, I believe the forEach loop will work as-is, but I wouldn't be surprised if it needs some more explicit promise handling to make the dependencies explicit.
I have a editable grid, store and a button.
The button has a handler that is supposed to copy the selected record and add the copy to the store:
var a = gridPanel.getSelectionModel().getSelectedCell();
var rec = store.getAt(a[0]).copy();
store.addSorted(rec);
alert (store.getAt(1).get('date'));
But the store and the grid are not updated. The alert has an error - cannot call method of undefined.
What is the problem here?
The issue probably is that the copied record has the same id thus when you insert it in the store another record with the same id is already present.
If you generate and apply a new id to the copied record before adding it to the store it should work. The following code generates a new ID in the record passed as argument. Check the documentation of Ext.data.Record.copy.
Ext.data.Record.id(rec);
Few things that most JavaScript developers should do:
Use firebug, if you turn on break on all errors it would probably show you that store.get(1) is returning undefing and causing an error when you try to call a function on an undefined.
Now that you have firebug use console.log() statements over window.alert(). With console.log's you can actually see the an inspect it, it is also good for asynchronous stuff and mouse events.
As for your problem:
Calling a record.copy() and then inserting it into the store will cause problems if you don't give it an id. If you had firebug and looked through the code you would stumble upon this:
if(this.containsKey(key)){
this.suspendEvents();
this.removeKey(key);
this.resumeEvents();
}
To generate a record with a unique key you can do something like this :
var rec = store.getAt(a[0]).copy();
var id = Ext.data.Record.id(id);
rec.id = id;
The code seems messy but there aren't many nice ways of doing it. If it were me I would override the copy function to take a boolean to force the record's id to be auto generated.