How to execute an external script in jsdom - javascript

I have a method in a products.js file like so:
var handler = function(errors, window) {...}
and would like to execute it within a jsdom env callback:
jsdom.env({
html : "http://dev.mysite.com:3000/products.html",
scripts : [ "http://code.jquery.com/jquery.js", "page-scrapers/products.js" ],
done : function(errors, window) {
handler(errors, window)
}
});
When executed, it tells me 'handler is not defined'. Am I getting close?

Context of the problem is to scrape data from an existing web site. We want to associate a javascript scraper for each page, and access the scraped data via URLs served up via a node.js server.
As suggested by Juan, the key is using node.js modules. The bulk of the hander method is exported from product.js:
exports.handler = function(errors, window, resp) {...
and then imported in the node.js-based server instance:
//note: subdir paths must start with './' :
var products = require('./page-scrapers/products.js');
This creates a reference to the method by name 'products.handler', which can then be called in the request handler:
var router = new director.http.Router({
'/floop' : {
get : funkyFunc
}
})
var funkyFunc = function() {
var resp = this.res
jsdom.env({
html : "http://dev.mySite.com:3000/products.html",
scripts : [ "http://code.jquery.com/jquery.js"],
done : function(errors, window) {products.handler(errors, window, resp)}
});
}
And that works.

If you want a variable to be accessible to another file, you have to export it. http://nodejs.org/api/modules.html
//products.js
exports.handler = function(window, error) {...}
// another.file.js
var products = require('products.js');
jsdom.env({
html : "http://dev.mysite.com:3000/products.html",
scripts : [ "http://code.jquery.com/jquery.js", "page-scrapers/products.js" ],
// This can be simplified as follows
done : products.handler
});
This sounds like a bad idea though, why would a handler be made into a global? I think you should restructure your code

Related

Can't save/create files using Store.js

So I wanted to save a file on the client storage using Store.js.
I can change the date using store.set and i can log it to console to see the change, but then it's supposed to be saved in app data where it's not created.
I tried to get the Path where it's being saved and it's :
C:\Users\USER\AppData\Roaming\stoma2/Categories.json
I noticed that there is a "/" so I tried :
C:\Users\USER\AppData\Roaming\stoma2\Categories.json
and :
C:/Users/USER/AppData/Roaming/stoma2/Categories.json
But all 3 of them didn't work.
This is my Store.js :
const fs = require('browserify-fs');
var fs2 = require('filereader'),Fs2 = new fs2();
const electron = window.require('electron');
const path = require('path');
class Store {
constructor(opts) {
// Renderer process has to get `app` module via `remote`, whereas the main process can get it directly
// app.getPath('userData') will return a string of the user's app data directory path.
//const userDataPath = (electron.app || electron.remote.app).getPath('userData');
var userDataPath = (electron.app || electron.remote.app).getPath('userData');
for(var i=0;i<userDataPath.length;i++){
if(userDataPath.charAt(i)=="\\"){
userDataPath = userDataPath.replace("\\","/");
}
}
// We'll use the `configName` property to set the file name and path.join to bring it all together as a string
this.path = path.join(userDataPath, opts.configName + '.json');
this.data = parseDataFile(this.path, opts.defaults);
console.log(this.path);
}
// This will just return the property on the `data` object
get(key) {
return this.data[key];
}
// ...and this will set it
set(key, val) {
this.data[key] = val;
// Wait, I thought using the node.js' synchronous APIs was bad form?
// We're not writing a server so there's not nearly the same IO demand on the process
// Also if we used an async API and our app was quit before the asynchronous write had a chance to complete,
// we might lose that data. Note that in a real app, we would try/catch this.
fs.writeFile(this.path, JSON.stringify(this.data));
}
}
function parseDataFile(filePath, data) {
// We'll try/catch it in case the file doesn't exist yet, which will be the case on the first application run.
// `fs.readFileSync` will return a JSON string which we then parse into a Javascript object
try {
return JSON.parse(Fs2.readAsDataURL(new File(filePath)));
} catch(error) {
// if there was some kind of error, return the passed in defaults instead.
return data;
}
}
// expose the class
export default Store;
There might be a probleme fith js.writeFile() (well that's the source of probleme).
and this is my call :
//creation
const storeDefCat = new Store({
configName: "Categories",
defaults: require("../data/DefaultCategorie.json")
})
//call for the save
storeDefCat.set('Pizza',{id:0,path:storeDefCat.get('Pizza').path});
For now if possible,I might need to find another way to save the file.
And i tried : fs : It doesn't work for me for some reason (I get strange errors that they don't want to be fixed..) .
If anyone has an Idea then please I would be grateful.
So I managed to fix the probleme, Why fs was sending me errors about undefined functions?Why file wasn't getting created ? It has NOTHING to do with the code it self, but the imports...
To clearify, I was using :
const fs = require('fs');
And the solution is to make it like :
const fs = window.require('fs');
Just adding window. fixed all the problems .Since it's my first time using electron I wasn't used to import from the window but it seems it's necessary.And more over...There was no posts saying this is the fix.

JSDOM is not loading JavaScript included with <script> tag

Note: This question is not a duplicate of other existing questions because this question does not use jsdom.env() function call which older version of JSDOM use.
File bar.js:
console.log('bar says: hello')
File foo.js:
var jsdom = require('jsdom')
var html = '<!DOCTYPE html><head><script src="bar.js"></script></head><body><div>Foo</div></body>'
var window = new jsdom.JSDOM(html).window
window.onload = function () {
console.log('window loaded')
}
When I run foo.js, I get this output.
$ node foo.js
window loaded
Why did bar says: hello output did not come? It looks like bar.js was not loaded. How can I make jsdom load the file in the script tag?
[EDIT/SOLUTION]: Problem solved after following a suggestion in the answer by Quentin. This code works:
var jsdom = require('jsdom')
var html = '<!DOCTYPE html><head><script src="bar.js"></script></head><body><div>Foo</div></body>'
var window = new jsdom.JSDOM(html, { runScripts: "dangerously", resources: "usable" }).window
window.onload = function () {
console.log('window loaded')
}
Go to the JSDOM homepage.
Skim the headings until you find one marked Executing scripts
To enable executing scripts inside the page, you can use the
runScripts: "dangerously" option:
const dom = new JSDOM(`<body>
<script>document.body.appendChild(document.createElement("hr"));</script>
</body>`, { runScripts: "dangerously" });
// The script will be executed and modify the DOM:
dom.window.document.body.children.length === 2;
Again we emphasize to only use this when feeding jsdom code you know
is safe. If you use it on arbitrary user-supplied code, or code from
the Internet, you are effectively running untrusted Node.js code, and
your machine could be compromised.
If you want to execute external scripts, included via <script
src="">, you'll also need to ensure that they load them. To do this,
add the option resources: "usable" as described below.
Given I was unable to reproduce the url-based solution from the code above...
Brutal bundle alternative : inline it all !
Read the various .js files, inject them as string into the html page. Then wait the page to load as in a normal navigator.
These libraries are loaded into _window = new JSDOM(html, { options }).window; and therefor available to your node script.
This is likely to prevent you from doing xhr calls and therefore only partially solve the issue.
say-hello.js
// fired when loaded
console.log("say-hello.js says: hello!")
// defined and needing a call
var sayBye = function(name) {
var name = name ||'Hero!';
console.log("say-hello.js says: Good bye! "+name)
}
main.js:
const fs = require("fs");
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
var NAME = process.env.NAME; // variable from terminal
var html = '<!DOCTYPE html><head></head><body><div>Foo</div></body>'
var _window = new JSDOM(html, {
runScripts: "dangerously",
resources: "usable" }).window;
/* ************************************************************************* */
/* Add scripts to head ***************************************************** */
var jsFiles = [
'say-hello.js'
];
var scriptsContent = ``;
for(var i =0; i< jsFiles.length;i++){
console.log(__dirname + '/'+ jsFiles[i])
let scriptContent = fs.readFileSync( jsFiles[i], 'utf8');
scriptsContent = scriptsContent + `
/* ******************************************************************************************* */
/* `+jsFiles[i]+` **************************************************************************** */
`+scriptContent;
};
let scriptElement = _window.document.createElement('script');
scriptElement.textContent = scriptsContent;
_window.document.head.appendChild(scriptElement);
/* ************************************************************************* */
/* Run page **************************************************************** */
_window.document.addEventListener('DOMContentLoaded', () => {
console.log('main says: DOMContentLoaded')
// We need to delay one extra turn because we are the first DOMContentLoaded listener,
// but we want to execute this code only after the second DOMContentLoaded listener
// (added by external.js) fires.
_window.sayBye(NAME); // prints "say-hello.js says: Good bye!"
});
Run it:
NAME=John node main.js # expects hello and good bye to john messages
Source:
https://github.com/jsdom/jsdom/issues/1914
https://github.com/jsdom/jsdom/issues/3023
Using JSDOM option url : file://${__dirname}/index.html could work, according to a source. If you test it, please report result here.

create a template url-routing script

I want to create an url-routing script using javascript as much as possible, but also accepting jQuery in the code. The js file has to change the url path (although I used location.hash instead of location.pathname) and the content of a div with the view id (from external files) accordingly.
Example configuration:
root/index.html
root/tpl/home.html
root/tpl/about.html
home.html content:
<p>This is content of home page</p>
about.html content:
<p>This is the content of the about page </p>
What I have done so far:
'use strict';
var Router = {
root: '/',
routes: [],
urls: [],
titles: [],
navigate: function() {
location.hash = this.root;
return this;
},
add: function(thePath, theUrl, theTitle) {
this.routes.push(thePath);
this.urls.push(theUrl);
this.titles.push(theTitle);
},
loading: function() {
this.navigate();
var r = this.routes;
var u = this.urls;
window.onload = function() {
$("#view").load("tpl/home.html");
};
window.onhashchange = function() {
for (var i = 0; i < r.length; i++) {
if (location.hash == r[i]) {
$("#view").load(u[i]);
}
}
};
}
};
Router.add("#/home", "tpl/home.html", "Home Page");
Router.add("#/about", "tpl/about.html", "About Page");
Router.loading();
Desired type of url:
http://mywebsite.com/
http://mywebsite.com/about
I know there are more than enough libraries that make the routing, like AngularJS and Crossroad, I want to know how this could be done.
To make this URL work - http://mywebsite.com/about - you need a server that knows how to route this request. Since the actual file name is about.html your server must know what to do with extensionless URLs.
Usually, the server uses the file extension as a clue for how to serve up content. For example, if it sees file.php it knows to use the PHP component, for .aspx it knows to use the ASP.NET component, and for .htm or .html it knows to respond with plain HTML (and usually serves the file instead of processing it). Your server must have some rules for what to do with any request, whether it has an extension or not, but without an extension you need to provide an explicit routing rule for that request..
The capabilities for JavaScript to do routing are limited because it requires the user to already be on your site. You can do some interesting things if you parse the URL parameters or use hashes and use them for routing, but that still requires requesting a page from your site as the first step.
For example: the server is already doing some level of "extensionless routing" when you give it this request:
http://mywebsite.com/
The parts of the URL are:
http - protocol
(port 80 is implied because it is default HTTP port)
mywebsite.com - domain AKA host
/ the path
The server sees / and uses what IIS calls a "default document" (I think apache calls it "default index" or "default page"). The server has been configured to return a file such as "index.html" or "default.htm" in this case. So when you request http://mywebsite.com/ you actually may get back the equivalent of http://mywebsite.com/index.html
When the server sees http://mywebsite.com/about it may first look for a folder named about and next for a file named about, but since your file is actually named about.html and is in a different folder (/tpl) the server needs some help to know how to translate http://mywebsite.com/about into the appropriate request - which for you would be http://mywebsite.com/#/about so that it requests the routing page (assuming it is the default document in the web app root folder) so that the browser can parse and execute the JavaScript that does the routing. Capisce?
You might be interested by frontexpress.
My library fix your case like below:
// Front-end application
const app = frontexpress();
const homeMiddleware = (req, res) => {
document.querySelector('#view').innerHTML = '<p>This is content of home page</p>';
}
app.get('/', homeMiddleware);
app.get('/home', homeMiddleware);
app.get('/about', (req, res) => {
document.querySelector('#view').innerHTML = '<p>This is the content of the about page </p>';
});
Obviously, you can get the template files from the server.
The #view will be feeded as below:
document.querySelector('#view').innerHTML = res.responseText;
More detailed sample in this gist
I have worked with what your answers and I have build the following Router. The only issue remains that it still uses location.hash
(function() {
var Router = {
root: '#/',
routes: [],
urls: [],
titles: [],
add: function(thePath, theUrl, theTitle) {
this.routes.push(thePath);
this.urls.push(theUrl);
this.titles.push(theTitle);
},
navigate: function() {
var routes = this.routes,
urls = this.urls,
root = this.root;
function loading() {
var a = $.inArray(location.hash, routes),
template = urls[a];
if (a === -1) {
location.hash = root;
$("#view").load(urls[0]);
}
else {
$("#view").load(template);
if (a === 0) {
window.scrollTo(0, 0);
}
else {
window.scrollTo(0, 90);
}
}
}
window.onload = loading;
window.onhashchange = loading;
}
};
Router.add("#/", "tpl/home.html", "Home Page");
Router.add("#/about", "tpl/about.html", "About Page");
Router.add("#/licence", "tpl/licence.html", "MIIT Licence");
Router.add("#/cabala", "tpl/cabala.html", "Cabala Checker");
Router.add("#/articles/esp", "tpl/article1.html", "ESP");
Router.add("#/fanfics/the-chupacabra-case", "tpl/article2.html", "The Chupacabra Case");
Router.navigate();
})();
Your request reads like what you're attempting to do is to add a "path+file" ("/tpl/about.html") to a base url ("www.myhost.com"). If that's the case, then you need to dynamically extract the host name from your current document and then append the new URL to the existing base. You can get the existing host name by executing the following commands in javascript:
var _location = document.location.toString();
var serverNameIndex = _location.indexOf('/', _location.indexOf('://') + 3);
var serverName = _location.substring(0, serverNameIndex) + '/';
This will return a string like: "http://www.myhost.com/" to which you can now append your new URL. All of this can be done in javascript prior to sending to the server.
If you only want the server name, without the leading http or https, then change the last line to be:
var serverName = _location.substring(_location.indexOf('://') + 3, serverNameIndex) + '/';
Lets break your code down a little bit:
function loading() {
"location.hash" is set based on the URL most recently clicked (www.myhost.home/#about). One essential question is, is this the action that you want, or do you want to pass in a value from the html for the onClick operation? It seems like that would be a more effective approach for you.
var a = $.inArray(location.hash, routes),
template = urls[a];
var a will be set to either -1 or the location of "location.hash" in the "routes array. If location.hash does not exist in routes, then a==-1 and the script will fail, because you're setting template = urls[-1]. You may want to move setting template to inside the "else" statement.
if (a === -1) {
location.hash = root;
$("#view").load(urls[0]);
}
else {yada yada yada
}
You could use a sequence in your html analogous to:
<a onclick="loading('#about')">Go to About Page</a>

requirejs: load script/module from a web service

I have a WebService that query a SQL database. In a sql table, I store some javascript and I want to use it in a webpage using RequireJS.
I try this :
var url = "http://localhost:64952/breeze/app/Objectss?$filter=Id%20eq%201&$select=Script";
require([url], (test) => {
debugger
arguments[0];
});
The server respond correctly :
http://i.stack.imgur.com/05lE7.png
But I'm not sure RequireJS is able to load script like this.
I try something else :
var req = breeze.EntityQuery.from("Commands")
.where("Id", "eq", "1")
.select("Script");
dataservice.manager.executeQuery(req)
.then((res : breeze.QueryResult) => {
if (res.results[0]) {
require([(<any>res.results[0]).Script], (hekki) => {
debugger
});
}
});
Doesn't work too...
Do you have any idea to help me please ?!
Create a requirejs plugin responsible for loading dependencies via the breeze api you've put together...
breezeloader.js:
define({
load: function (name, req, onload, config) {
// load the script using the breeze api you've put together...
var query = breeze.EntityQuery
.from("Commands")
.where("Id", "eq", name)
.select("Script");
dataservice.manager.executeQuery(query)
.then((queryResult: breeze.QueryResult) => {
var text = queryResult.results[0].Script;
// Have RequireJS execute the JavaScript within
//the correct environment/context, and trigger the load
//call for this resource.
onload.fromText(text);
});
}
});
express dependencies that should be loaded with the breeze loader using the requirejs plugin syntax:
require(['breezeloader!1', 'jquery', 'foo'], function (hekki, jquery, foo) {
...
});

Alloy Titanium & Google Cloud Endpoint

I would like to know how (the right way) to work with Google Cloud Endpoint in an Alloy Titanium application. And I would like to use the library that Google has for the API endpoints.
I am new to Alloy and CommonJS, therefore trying to figure out the right way to do this.
From my understanding Alloy prefers (or only allows) including javascript via modules (CommonJS - exports...).
var module = require('google.js');
google.api.endpoint.execute();
This would be the way CommonJS would expect things to work. Although in the google javascript library it just creates a global variable called "gapi".
Is there a way, I can include this file ?
Is there a way, I can create global variables ?
Should I stay away from creating them in first place ?
Thanks !
The client.js library that Google has for the API endpoints can be run only from browsers (Titanium.UI.WebView in this case), it can't be run directly from Titanium code since it contains objects not available in Titanium Appcelerator.
Also, using a Google Cloud Endpoint into an Alloy Titanium application requires having the js code available into the project at compile time, as it is used by Titanium to generate the native code for the desired platforms.
To anwser your questions:
Is there a way, I can include this file ?
No, if you plan to run the code as Titanium code, for the reasons mentioned above. Instead you could use the following code snippet to connect to a Google Cloud Endpoint:
var url = "https://1-dot-projectid.appspot.com/_ah/api/rpc";
var methodName = "testendpoint.listGreetings";
var apiVersion = "v1";
callMethod(url, methodName, apiVersion, {
success : function(responseText)
{
//work with the response
},
error : function(e) { //onerror do something
}
});
function callMethod(url, methodName, apiVersion, callbacks) {
var xhr = Titanium.Network.createHTTPClient();
xhr.onload = function(e) {
Ti.API.info("received text: " + this.responseText);
if (typeof callbacks.success === 'function') {
callbacks.success(this.responseText);
}
};
xhr.onerror = function(e) {
Ti.API.info(JSON.stringify(e));
//Ti.API.info(e.responseText);
if (typeof callbacks.error === 'function') {
callbacks.error(e);
}
};
xhr.timeout = 5000; /* in milliseconds */
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-Type', 'application/json-rpc');
//xhr.setRequestHeader('Authorization', 'Bearer ' + token);
var d = [{
jsonrpc: '2.0',
method: methodName,
id: 1,
apiVersion: apiVersion,
}];
Ti.API.info(JSON.stringify(d));
// Send the request.
xhr.send(JSON.stringify(d));
}
Yes, if you use the embeded device's browser like this (as can be found in web client GAE samples)
webview = Titanium.UI.createWebView({
width : '100%',
height : '100%',
url : url // put your link to the HTML page
});
, to call your server HTML page which should contain:
script src="https://apis.google.com/js/client.js?onload=init">
Is there a way, I can create global variables ?
Yes, insert into the app/alloy.js file the global variables, see the default comments in the file:
// This is a great place to do any initialization for your app
// or create any global variables/functions that you'd like to
// make available throughout your app. You can easily make things
// accessible globally by attaching them to the Alloy.Globals
// object. For example:
//
Alloy.Globals.someGlobalFunction = function(){};
Alloy.Globals.someGlobalVariable = "80dp";
Should I stay away from creating them in first place ?
I suppose you are reffering to global variables containing the module code for connecting to GAE enpoind methods. It's your call, here is how you can use them.
a) Create a file named jsonrpc.js in the app/lib folder of your Titanium project, put the following code into it, and move the function code from above as the function body:
JSONRPCClient = function () {
};
JSONRPCClient.prototype = {
callMethod : function (url, methodName, apiVersion, callbacks) {
// insert the function body here
}
};
exports.JSONRPCClient = JSONRPCClient;
b) Into app/alloy.js file define your global variable:
Alloy.Globals.JSONRPCClient = require('jsonrpc').JSONRPCClient;
c) Use it (eg. from your controller js files):
var client = new Alloy.Globals.JSONRPCClient();
var url = "https://1-dot-projectid.appspot.com/_ah/api/rpc";
var methodName = "testendpoint.listGreetings";
var apiVersion = "v1";
client.callMethod(url, methodName, apiVersion,
{success: function(result) {
//result handling
Ti.API.info('response result=', JSON.stringify(result));
//alert(JSON.stringify(result));
},
error: function(err) {
Ti.API.info('response out err=', JSON.stringify(err));
//error handling
}
});

Categories