puppeteer element.click() not working and not throwing an error - puppeteer

I have a situation where a button, on a form, that is animated into view, if the element.click() happens while the animation is in progress, it doesn't work.
element.click() doesn't throw an error, doesn't return a failed status (it returns undefined) it just silently doesn't work.
I have tried ensuring the element being clicked is not disabled, and is displayed (visible) but even though both those tests succeed, the click fails.
If I wait 0.4s before clicking, it works because the animation has finished.
I don't want to have to add delays (which are unreliable, and a bodge to be frank), if I can detect when a click worked, and if not automatically retry.
Is there a generic way to detect if a click() has actually been actioned so I can use a retry loop until it does?

I have determined what is happening, and why I don't get an error, and how to work around the issue.
The main issue is with the way element.click() works. Using DEBUG="puppeteer:*" I was able to see what is going on internally. What element.click() actually does is:-
const box = element.boundingBox();
const x = box.x + (box.width/2);
const y = box.y + (box.height/2);
page.mouse.move(x,y);
page.mouse.down();
sleep(delay);
page.mouse.up();
The problem is that because the view (div) is animating the element's boundingBox() is changing, and between the time of asking for the box position, and completing the click() the element has moved or is not clickable.
An error isn't thrown (promise rejected) because its just a mouse click on a point in the viewport, and not linked to any element. The mouse event is sent, just that nothing responds to it.
One workaround is to add a sufficient delay to allow the animation to finish. Another is to disable animations during tests.
The solution for me was to wait for the position of the element to settle at its destination position, that is I spin on querying the boundingBox() and wait for the x,y to report the elements previously determined position.
In my case, this is as simple as adding at 10,10 to my test script just before the click, or specifically
test-id "form1.button3" at 10,10 click
And in action it works as follows, in this case, the view is being animated back in from the left.
00.571 [selector.test,61] at 8,410
test-id "main.add" info tag button displayed at -84,410 size 116,33 enabled not selected check "Add"
test-id "main.add" info tag button displayed at -11,410 size 116,33 enabled not selected check "Add"
test-id "main.add" info tag button displayed at 8,410 size 116,33 enabled not selected check "Add"
00.947 [selector.test,61] click
It wouldn't work for an element that was continually moving or for an element that is covered by something else. For those cases, try page.evaluate(el => el.click(), element).

Generic click with timeout function inspired by Andrea's answer. This one returns as soon as the element is clickable, so won't slow down tests.
click: async function (page, selector, timeout = 30000) {
await page.waitForSelector(selector, { visible: true, timeout })
let error;
while (timeout > 0) {
try {
await page.click(selector);
return;
} catch (e) {
await page.waitFor(100);
timeout -= 100;
error = e;
}
}
throw error;
}

The page.click() returns a promise, make sure to handle it as such, but also note that you may have issues if you are not referencing it as an xpath. That's what I had to do in order to get it working. I've tried using querySelectors and interacting with the objects that way, but I ran into issues.
page.evaluate(()=>{
await Promise.all([
page.click("a[id=tab-default-2__item]"),
//The page.waitFor is set to 15000 for my personal use.
//Feel free to play around with this.
page.waitFor(15000)
]);
});
I hope this helps.

i use a helper function to handle click
click: async function (page, selector) {
//selector must to exists
await page.waitForSelector(selector, {visible: true, timeout: 30000})
//give time to extra rendering time
await page.waitFor(500)
try {
await page.click(selector)
} catch (error) {
console.log("error clicking " + selector + " : " + error ;
}
}
using page.waitFor(500) is a VERY BAD PRACTICE in a THEORICAL WORLD, but it remove a lot of false positive in the practical with complex interfaces.
i prefer to wait 500ms more than obtain a false positive.

I've just had a very similar problem: I had Puppeteer script that used to work and now suddenly click stopped working. The culprit turned out to be zoom level. It started working again once I switched zoom to 100%. Apparently, Puppeteer does not adjust click coordinates to zoom level.

For me below code did the trick
const element = await page.$('[name="submit"]')
await this.page.evaluate(ele => ele.click(), element);

Related

Event for opened fires too soon in <paper-dialog>

I'm using <paper-dialog> to display an SVG image I'll construct programatically. I need to know the size of the rendered container before I begin. I am waiting for the opened property to change to true however that is apparently too soon as .clientWidth is 0 when it fires however later .clientWidth does provide the correct value.
<paper-dialog class="dialog" opened={{modalOpen}} modal>
<svg width="100%", height="100%" />
</paper-dialog>
How can I wait for the SVG clientWidth and clientHeight to be computed?
Sometimes JS events are fired before the DOM has time to finish processing, which appears to be the case here.
To get around this (or at least to provide additional clues on what's happening), try putting your code (for the clientWidth property) within a setTimeout() function and give it a zero (0) millisecond timeout. This will simply move your code to the end of the JS execution stack, which should process only after the DOM has finished updating... and subsequently be when the DOM element's size and positioning properties are available.
Here's an example...
modalOpen() {
setTimeout( () => {
/* Your code here... for example... */
const paperDialog = document.getElementsByTagName("paper-dialog")[0];
console.log("paperDialog width = ", paperDialog.clientWidth);
}, 0); /* 0 milliseconds = Execute immediately after everything else processes. */
}
WARNING: Using setTimeout() is typically frowned upon since it doesn't fully adhere to asynchronous development and can produce unexpected results (such as firing too soon or not firing as soon as possible). While this approach is quick and can work fine some of the times, triggering and handling events are generally the best way to solve these timing issues.
Polymer's <paper-dialog> implements iron-resizable-behavior so we can listen for the iron-resize event:
disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener('iron-resize', this.onIronResize);
}

Navigating to a page in a custom event doesn't work properly

When I navigate to a page using this event:
this.events.subscribe('liveTrackingEvent', (time, unit) => {
console.log("event triggered");
this.searchForm.controls['unitID'].setValue(this.unitSelected.unit.name);
this.GetLiveData();
});
everything gets called, also the function GetLiveData(). (I didn't post this function's code because it's irelevant)
However when I look at the page, not 1 element is updating. So this line:
this.searchForm.controls['unitID'].setValue(this.unitSelected.unit.name);
doesn't update the searchform control, however when I call this line of code from the page itself without the event getting triggered on another page, it works smoothly and updates the searchform control.
(It's like I'm on a separate thread for some reason), I'm putting this between brackets because it's just a thought.
So my question is: How do I force this page to update itself also when the event is triggered?
Thanks in advance and if you guys need more code just ask, but this is the most relevant code because everything is working just not when it gets called inside the event.
By using page life cycle events instead of custom events from the ionic framework I managed to make this work and even have a cleaner code.
example:
1st page:
GoToLiveTracking(unitID){
this.navCtrl.push(MapPage, {redirected: true, unitID: unitID});
}
2nd page:
ionViewDidEnter(){
if(this.navParams.get('redirected')){
let unit_id = this.navParams.get('unitID');
this.unitSelected = this.completeService.GetUnitByID(unit_id);
this.searchForm.controls['unitID'].setValue(this.unitSelected.unit.name);
this.GetLiveData();
}
}
I could think of only 1 reason for this behavior. You are updating your form outside of Angular Zone. That’s why the changes are not getting detected.
To fix the issue, wrapped the call of last 2 lines of event into “this.ngZone.run(() => { ... })”.
e.g
this.events.subscribe('liveTrackingEvent', (time, unit) => {
console.log("event triggered");
this.ngZone.run(()=>{
this.searchForm.controls['unitID'].setValue(this.unitSelected.unit.name);
this.GetLiveData();
});
});

AngularJS: How to block execution until function returns?

I'm trying to display a google map on click with angular JS. But my function seems to be toggling the visibility based on a function call (via ng-show). This means that my trigger of the 'resize' event executes before the map div is actually visible so it doesn't work correctly.
$scope.mapVisible = false;
$scope.toggleMap = function() {
$scope.myMap.panTo($scope.myMarkers[0].getPosition());
$scope.mapVisible = !$scope.mapVisible;
// this executes too soon. How to block until the div is really visible?
google.maps.event.trigger($scope.myMap, 'resize');
}
<div ng-show="mapVisible">
<!-- map here -->
</div>
So how can I block triggering the 'resize' event on my map until the div is truly visible?
$scope.$watch("mapVisible", function (val) {
if (val) {
google.maps.event.trigger($scope.myMap, "resize");
}
});
This would make sure the map is visible before triggering the event.
Angularjs handles two-way binding by doing dirty-checking. It basically means that the value being watched is compared to the one in previous cycle. Each cycle ($digest) starts to run when something happens which could possibly change any value in the scope. If there is a change in the value, it would be reflected after the cycle has completed.
Back to your problem, when you toggle the mapVisible property, the view does not update immediately. It waits for the cycle to end before redrawing the view. But you fire the resize event so early, when the map is still invisible, therefore rendering invalid.
$watch does indeed watch the property and the changes will be reflected in the next cycle of the change, which means the view would be updated by the time watcher function has been invoked. Putting the resize function here would hence solve your issue.
I figured the easiest case is to use the $timeout service:
scope.$on "map:ui:shown", (event, args)->
$timeout ->
#use a delay because most of the time, the resizing should occur immediately after an angular cycle
#like when an ng-show has been set to the True condition
googleMaps.event.trigger(map, 'resize')
map.fitBounds scope.bounds if scope.bounds

Debugging "Element is not clickable at point" error

I see this only in Chrome.
The full error message reads:
"org.openqa.selenium.WebDriverException: Element is not clickable at point (411, 675). Other element would receive the click: ..."
The element that 'would receive the click' is to the side of the element in question, not on top of it and not overlapping it, not moving around the page.
I have tried adding an offset, but that does not work either. The item is on the displayed window without any need for scrolling.
This is caused by following 3 types:
1.The element is not visible to click.
Use Actions or JavascriptExecutor for making it to click.
By Actions:
WebElement element = driver.findElement(By("element_path"));
Actions actions = new Actions(driver);
actions.moveToElement(element).click().perform();
By JavascriptExecutor:
JavascriptExecutor jse = (JavascriptExecutor)driver;
jse.executeScript("scroll(250, 0)"); // if the element is on top.
jse.executeScript("scroll(0, 250)"); // if the element is on bottom.
or
JavascriptExecutor jse = (JavascriptExecutor)driver;
jse.executeScript("arguments[0].scrollIntoView()", Webelement);
Then click on the element.
2.The page is getting refreshed before it is clicking the element.
For this, make the page to wait for few seconds.
3. The element is clickable but there is a spinner/overlay on top of it
The below code will wait until the overlay disppears
By loadingImage = By.id("loading image ID");
WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
wait.until(ExpectedConditions.invisibilityOfElementLocated(loadingImage));
Then click on the element.
You can also use JavaScript click and scrolling would be not required then.
IJavaScriptExecutor ex = (IJavaScriptExecutor)Driver;
ex.ExecuteScript("arguments[0].click();", elementToClick);
There seems to be a bug in chromedriver for that (the problem is that it's marked as won't fix)
--> GitHub Link
(place a bounty on FreedomSponsors perhaps?)
There's a workaround suggested at comment #27.
Maybe it'll work for you-
I had the same issue, tried all offered solutions but they did not work for me.
eventually I used this:
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("var evt = document.createEvent('MouseEvents');" + "evt.initMouseEvent('click',true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0,null);" + "arguments[0].dispatchEvent(evt);", findElement(element));
Hope this helps
Wow, a lot of answers here, and many good ones.
I hope I'll add something to this from my experience.
Well guys, in my case there was a cookie overlay hiding the element occasionally.
Scrolling to the element also works; but in my humble opinion (for my case, not a panacea for everyone) the simplest solution is just to go full screen (I was running my scripts on a 3/4 of the screen window)! So here we go:
driver.manage().window().maximize();
Hope that helps!
You need to use focus or scroll on that element. You also might have to use explict wait.
WebElement firstbutton= driver.findElement(By.xpath("Your Element"));
Actions actions = new Actions(driver);
actions.moveToElement(element);
actions.perform();
OR
The element is not clickable because of a Spinner/Overlay on top of it:
By loadingImage = By.id("loading image ID");
WebDriverWait wait = new WebDriverWait(driver, timeOutInSeconds);
wait.until(ExpectedConditions.invisibilityOfElementLocated(loadingImage));
OR
Point p= element.getLocation();
Actions actions = new Actions(driver);
actions.moveToElement(element).movebyoffset(p.x,p.y).click().perform();
OR
If still not work use JavascriptExecutor
JavascriptExecutor executor = (JavascriptExecutor)driver;
executor.executeScript("arguments[0].click();", firstbutton);
I have seen this in the situation when the selenium driven Chrome window was opened too small. The element to be clicked on was out of the viewport and therefore it was failing.
That sounds logical... real user would have to either resize the window or scroll so that it is possible to see the element and in fact click on it.
After instructing the selenium driver to set the window size appropriately this issues went away for me. The webdriver API is decribed here.
I was getting this error when running tests headless with xvfb-run. They were working flawlessly locally. Using chrome, versions of webdriver / chromedriver / chrome / java etc all identical.
The ‘won’t fix’ bug in chromedriver - GitHub Link pointed out by Tony Lâmpada suggested this may be related to what is / isn't visible on the screen.
Help message for xvfb-run shows the following:
-s ARGS --server-args=ARGS arguments (other than server number and
"-nolisten tcp") to pass to the Xvfb server
(default: "-screen 0 640x480x8")
Changing the resolution for xvfb made the error go away:
xvfb-run -s "-screen 0 1280x1024x16" ...
ruby/watir-webdriver/chrome
I use the following trick and seems like it works:
#scroll to myelement
#browser.execute_script "window.scrollTo(#{myelement.element.wd.location[0]},#{myelement.element.wd.location[1]})"
# click myelement
myelement.when_present.fire_event("click")
I, too, wrestled with this problem. Code works fine in FF, fails on Chrome. What I was trying to do was to click a tickbox - if it wasn't in view, I'd scroll to view and then click. Even scrolling into view works in Chrome, only the bottom few pixels of the tickbox wasn't visible so webdriver refused to click on it.
My workaround is this:
WebElement element = _sectorPopup.findElement(...);
((Locatable) element).getCoordinates().inViewPort();
try {
element.click();
} catch (Exception e) {
new Actions(getWebDriver()).sendKeys(Keys.PAGE_DOWN).perform();
element.click();
}
Chrome also has issues with sendKeys, using Actions is sometimes necessary. Obviously, you need to know which direction and how much you need to go so your mileage may vary. But I prefer this to the javascript hack, so I'm posting it here in case someone else will find it useful.
First, try to get the latest Chrome driver and check if it solves the issue.
In my case, it didn't fix the issue. But, the following solution worked for me so far. The following is C# code but you can follow same logic in your specific language. What we do here is,
Step 1: Focus on the element using the Selenium Actions object,
Step 2: Then do a click on the element
Step 3: If there's an exception, then we trigger a javascript "Click" event on the element by executing the javascript script through the Selenium browser driver's "ExecuteScript" method.
You can also skip step 1 and 2 and try only step 3 too. Step 3 would work on it's own but I noticed some strange behavior in one scenario in which step 3, even though it successfully clicked the element, caused unexpected behavior in other parts of my code after clicking the element.
try
{
//Setup the driver and navigate to the web page...
var driver = new ChromeDriver("folder path to the Chrome driver");
driver.Navigate().GoToUrl("UrlToThePage");
//Find the element...
var element = driver.FindElement(By.Id("elementHtmlId"));
//Step 1
new Actions(driver).MoveToElement(element).Perform();
//Step 2
element.Click();
}
catch (Exception)
{
//Step 3
driver.ExecuteScript("document.getElementById('elementHtmlId').click();");
}
I was getting the same issue while running selenium script in python. Here is what I used to click on the element:
from selenium.webdriver.common.action_chains import ActionChains
ActionChains(driver).click(element).perform()
When using Protractor this helped me:
var elm = element(by.css('.your-css-class'));
browser.executeScript("arguments[0].scrollIntoView();", elm.getWebElement());
elm.click();
I made this method based on a comment from Tony Lâmpada's answer. It works very well.
def scroll_to(element)
page.execute_script("window.scrollTo(#{element.native.location.x}, #{element.native.location.y})")
end
Today I got the same kind of issue. You don't believe me if i say how i solved the issue.
By maximizing the browser size
Yes, it is a pointer issue that means the size of the browser. For that, you just need to maximize the window size manually or through the code.
I was facing a similar problem whre i have to check two check boxes one after the other.But i was getting the same above error.hence i added wait in between my steps for checking the checkboxes....its working fine and great.here are the steps:-
When I visit /administrator/user_profiles
And I press xpath link "//*[#id='1']"
Then I should see "Please wait for a moment..."
When I wait for 5 seconds
And I press xpath link "//*[#id='2']"
Then I should see "Please wait for a moment..."
When I visit /administrator/user_profiles_updates
The reason for this error is that the element that you are trying to click is not in the viewport (region seen by the user) of the browser. So the way to overcome this is by scrolling to the desired element first and then performing the click.
Javascript:
async scrollTo (webElement) {
await this.driver.executeScript('arguments[0].scrollIntoView(true)', webElement)
await this.driver.executeScript('window.scrollBy(0,-150)')
}
Java:
public void scrollTo (WebElement e) {
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeAsyncScript('arguments[0].scrollIntoView(true)', e)
js.executeAsyncScript('window.scrollBy(0,-150)')
}
Apparently this is the result of a "Won't Fix" bug in the Chrome driver binary.
One solution that worked for me (Our Mileage May Vary) can be found in this Google Group discussion, Comment #3:
https://groups.google.com/forum/?fromgroups=#!topic/selenium-developer-activity/DsZ5wFN52tc
The relevant portion is right here:
I've since worked around the issue by navigating directly to the href of
the parent anchor of the span.
driver.Navigate().GoToUrl(driver.FindElement(By.Id(embeddedSpanIdToClick)).FindElement(By.XPath("..")).GetAttribute("href"));
In my case, I'm using Python, so once I got the desired element, I simply used
driver.get(ViewElm.get_attribute('href'))
I would expect this to only work, however, if the element you are trying to click on is a link...
Re Tony Lâmpada's answer, comment #27 did indeed solve the problem for me, except that it provided Java code and I needed Python. Here's a Python function that scrolls to the element's position and then clicks it.
def scroll_to_and_click(xpath):
element = TestUtil.driver.find_element_by_xpath(xpath)
TestUtil.driver.execute_script('window.scrollTo(0, ' + str(element.location['y']) + ');')
element.click()
This solved the problem for me in Chrome 34.0. It caused no harm in Firefox 28.0 and IE 11; those browsers aren't subject to the problem, but scrolling to the element's position before clicking it still isn't a bad thing.
This might happen if the element changes position while the driver is attempting to click it (I've seen this with IE too). The driver retains the initial position but by the time it actually gets to clicking on it, that position is no longer pointing to that element. The FireFox driver doesn't have this problem BTW, apparently it "clicks" elements programmatically.
Anyway, this can happen when you use animations or simply change the height of elements dynamically (e.g. $("#foo").height(500)). You need to make sure that you only click elements after their height has "settled". I ended up with code that looks like this (C# bindings):
if (!(driver is FirefoxDriver))
{
new WebDriverWait(driver, TimeSpan.FromSeconds(10)).Until(
d => d.FindElement(By.Id(someDynamicDiv)).Size.Height > initialSize);
}
In case of an animation or any other factor you can't easily query for, you can utilize a "generic" method that waits for the element to be stationary:
var prevLocation = new Point(Int32.MinValue, Int32.MinValue);
int stationaryCount = 0;
int desiredStationarySamples = 6; //3 seconds in total since the default interval is 500ms
return new WebDriverWait(driver, timeout).Until(d =>
{
var e = driver.FindElement(By.Id(someId));
if (e.Location == prevLocation)
{
stationaryCount++;
return stationaryCount == desiredStationarySamples;
}
prevLocation = e.Location;
stationaryCount = 0;
return false;
});
I met this because a loading dialog cover on this element. I simplely solve it by add a waiting before working with the this element.
try {
Thread.sleep((int) (3000));
} catch (InterruptedException e) {
//
e.printStackTrace();
}
Hope this help!
Explanation of error message:
The error message simply says, that the element you want to click on is present, but it is not visible. It could be covered by something or temporary not visible.
There could be many reasons why the element is not visible in the moment of the test. Please re-analyse your page and find proper solution for your case.
Solution for particular case:
In my case, this error occures, when a tooltip of the screen element i just clicked on, was poping over the element I wanted to click next. Defocus was a solution I needed.
Quick solution how to defocus would be to click to some other element in another part of the screen which does "nothing" resp. nothing happens after a click action.
Proper solution would be to call element.blur() on the element poping the tooltip, which would make the tooltip disapear.
I was facing the same problem with clj-webdriver (clojure port of Selenium). I just translated the previous solution to clojure for convenience. You can call this function before doing click or whatever to avoid that problem.
(defn scrollTo
"Scrolls to the position of the given css selector if found"
[q]
(if (exists? q)
(let [ loc (location-once-visible q) jscript (str "window.scrollTo(" (:x loc) "," (:y loc) ")") ]
(execute-script jscript))))
Maybe it's not really clean solution but it works:
try:
el.click()
except WebDriverException as e:
if 'Element is not clickable at point' in e.msg:
self.browser.execute_script(
'$("{sel}").click()'.format(sel=el_selector)
)
else:
raise
I was getting this bug because I tested a hover and then needed to click on the link underneath the tooltip. The solution was to add page.find('.sp-logo').hover before click_link to get the tooltip out of the way.
It's funny, all the time I spent looking at the various responses, no one had tried the obvious, which of course, I hadn't either. If your page has the same id used multiple times, as mine did, ("newButton",) and the one you want is not the first one found, then you will in all likelihood get this error. The easiest thing to do (C#):
var testIt = driver.FindElements(By.Id("newButton"));
Note it's FindElements, not FindElement.
And then test to see how many results came back from the retrieval. If it's the second one, you can then use:
testit[1].Click();
Or get whomever reused ids to fix them.
After testing all mentioned suggestions, nothing worked. I made this code. It works, but is not beautiful
public void click(WebElement element) {
//https://code.google.com/p/selenium/issues/detail?id=2766 (fix)
while(true){
try{
element.click();
break;
}catch (Throwable e){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
public void click(String css) {
//https://code.google.com/p/selenium/issues/detail?id=2766 (fix)
while(true){
try{
driver.findElement(By.cssSelector(css)).click();
break;
}catch (Throwable e){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
I do a kind of brute force of clicks and it works for me.
try:
elem.click()
except:
print "failed to click"
size = elem.size
mid_of_y = int(size["height"])/2
stepts_to_do_to_left = int(size["width"])
while stepts_to_do_to_left > 0:
try:
print stepts_to_do_to_left, mid_of_y
action = webdriver.common.action_chains.ActionChains(driver)
action.move_to_element_with_offset(elem, mid_of_y, stepts_to_do_to_left)
action.click()
action.perform()
print "DONE CLICK"
break
except:
pass
If you have jQuery loaded on the page, you can execute the following javascript command:
"$('#" + element_id + "').click()"
Example using python executor:
driver.execute_script("$('#%s').click()" % element_id)
Try to maximize the browser when you are working with resolutions greater than 1024x768.
driver.manage().window().maximize();

mootools - using addEvent to element not working properly?

bangin' my head against this and it's starting to hurt.
I'm having trouble with adding an event to an element.
I'm able to add the event, and then call it immediately with element.fireEvent('click'), but once the element is attached to the DOM, it does not react to the click.
example code:
var el = new Element('strong').setStyle('cursor','pointer');
el.addEvent('click',function () { alert('hi!'); });
el.replaces(old_element); // you can assume old_element exists
el.fireEvent('click'); // alert fires
however, once I attach this to the DOM, the element is not reactive to the click. styles stick (cursor is pointer when I mouseover), but no event fires. tried mouseover as well, to no avail.
any clues here? am I missing something basic? I am doing this all over the place, but in this one instance it doesn't work.
EDIT----------------
ok here's some more code. unfortunately I can't expose the real code, as it's for a project that is still under tight wraps.
basically, the nodes all get picked up as "replaceable", then the json found in the rel="" attribute sets the stage for what it should be replaced by. In this particular instance, the replaced element is a user name that should pop up some info when clicked.
again, if I fire the event directly after attaching it, all is good, but the element does not react to the click once it's attached.
HTML-----------
<p>Example: <span class='_mootpl_' rel="{'text':'foo','tag':'strong','event':'click','action':'MyAction','params':{'var1': 'val1','var2': 'val2'}}"></span></p>
JAVASCRIPT-----
assumptions:
1. below two functions are part of a larger class
2. ROOTELEMENT is set at initialize()
3. MyAction is defined before any parsing takes place (and is properly handled on the .fireEvent() test)
parseTemplate: function() {
this.ROOTELEMENT.getElements('span._mootpl_').each(function(el) {
var _c = JSON.decode(el.get('rel'));
var new_el = this.get_replace_element(_c); // sets up the base element
if (_c.hasOwnProperty('event')) {
new_el = this.attach_event(new_el, _c);
}
});
},
attach_event: function(el, _c) {
el.store(_c.event+'-action',_c.action);
el.store('params',_c.params);
el.addEvent(_c.event, function() {
eval(this.retrieve('click-action') + '(this);');
}).setStyle('cursor','pointer');
return el;
},
Works just fine. Test case: http://jsfiddle.net/2GX66/
debugging this is not easy when you lack content / DOM.
first - do you use event delegation or have event handlers on a parent / the parent element that do event.stop()?
if so, replace with event.preventDefault()
second thing to do. do not replace an element but put it somewhere else in the DOM - like document.body's first node and see if it works there.
if it does work elsewhere, see #1
though I realsie you said 'example code', you should write this as:
new Element('strong', {
styles: {
cursor: "pointer"
},
events: {
click: function(event) {
console.log("hi");
}
}
}).replaces(old_element);
no point in doing 3 separate statements and saving a reference if you are not going to reuse it. you really ought to show the ACTUAL code if you need advice, though. in this snippet you don't even set content text so the element won't show if it's inline. could it be a styling issue, what is the display on the element, inline? inline-block?
can you assign it a class that changes it on a :hover pseudo and see it do it? mind you, you say the cursor sticks which means you can mouseover it - hence css works. this also eliminates the possibility of having any element shims above it / transparent els that can prevent the event from bubbling.
finally. assign it an id in the making. assign the event to a parent element via:
parentEl.addEvent("click:relay(strong#idhere)", fn);
and see if it works that way (you need Element.delegate from mootools-more)
good luck, gotta love the weird problems - makes our job worth doing. it wouldn't be the worst thing to post a url or JSFIDDLE too...