Tomáš Zato
Tomáš Zato

Reputation: 53318

How to post events in Node.js PhantomJS wrapper from page to Node?

I started off with this example from the wrapper repository.

const instance = await phantom.create();
const page = await instance.createPage();

To post an action from Node.js to PhantomJS - including emitting an event - I can do this:

await page.evaluate(function() {
     // Code
}

But how about the other way around. I want to listen on an event from the page in nodeJS. I wrote this promise to wait for an event:

class PageEventPromise extends Promise {
    constructor(page, event) {
        super(function(resolve, reject) {
            let callback = function() {
                let args = [];
                args.push.apply(args, arguments);
                page.off(event, callback);
                resolve(args);
            }
            page.on(event, callback);
        });
    }
}

I want to listen for DOM changes on particular element. I set up a MutationObserver in the webpage context:

await page.evaluate(function() {
    var observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
         if(mutation.type != "childList")
             return;
         for (var i=0,l=mutation.addedNodes.length; i<l; i++)
         {
             const node = mutation.addedNodes[i];
             self.dispatchEvent(new Event("logEntry", {text: node.innerHTML}));
         }
      });    
    });
    var config = { attributes: !true, childList: true, characterData: !true };
    observer.observe(document.querySelector("div.ace_layer.ace_text-layer"), config);
});

I could see the error warnings for the errors that I throw in the setTimeout callbacks, but the event did not trigger.

Therefore this line:

self.dispatchEvent(new Event("logEntry", {text: node.innerHTML}));

does not trigger events assigned to page instance.

So if I assign arbitrary event:

page.on("eventName", function() {/* callback**/});

How do I trigger it from the webpage context?

Upvotes: 3

Views: 291

Answers (1)

rlib
rlib

Reputation: 7917

UPDATE

I think this solves your problem without polling: http://phantomjs.org/api/webpage/handler/on-callback.html

Checked with phantom module:

let phantom = require('phantom');
function nodeFnc(data) { // to be called when an event inside page occurs
   console.log('data from page: '+ data);
}
(async function() {

  let ph = await phantom.create();
  let page = await ph.createPage();

  await page.property( 'onCallback', function(data, nodeFnc) {
     nodeFnc(data); // this will be called when event occurs
  });
  await page.evaluate( function() {
     setTimeout( function() { // timeout event
        window.callPhantom('from inside the page!'); 
     }, 5000):
  });

})();

====================== Polling answer ========================

While that does not answer your question directly, it may help solve your problem.

You listen to changes of DOM inside HTML and when something happens you change global variable STATUS inside that HTML from 0 to 1. From the node page you are waiting for this change with:

/**
 * @param {function} checkCond - returns promise with value of ready(true or false)
 */
function wait( checkCond ) {
  return new Promise( function( resolve, reject ) {
    function r() {
      checkCond().then(ready=>{
        if ( ready === true ) 
          return resolve();
        setTimeout(function() {r();}, 1); // The condition is checked every millisecond.
      });
    }
    r();
  });
}

wait( ()=>{
  return page.evaluate( function() {
     return STATUS;
  });
}); // returns when STATUS is true

Upvotes: 1

Related Questions