I am trying to use jsdom to analyze some html content. Examples that i have seen use the .createWindow() method of a jsdom.jsdom document based on html content.
But when I try to follow these examples, my document does not have a .createWindow() method.
var getaPage=function (req, res, callback) {
jsdom.defaultDocumentFeatures={
FetchExternalResources : ['script'],
ProcessExternalResources : ['script'],
MutationEvents : '2.0',
QuerySelector : false
};
var htmlDoc = '<html lang="en-US">' +
'</html>';
var tstDocument=jsdom.jsdom(htmlDoc);
for (var attr in tstDocument){
if (attr == 'createWindow') {
console.log('Found it');
}else{
console.log('not it');
};
};
};
When I run this, I get a bunch of 'not it' and no 'Found it'.
Why do I not have a .createWindow() method?
jsdom's API changed quite a bit with the 1.0 release. The createWindow() method is from the old API. You should be able to get the document's window just by accessing tstDocument.defaultView, just like you'd do if you were in a browser.
Related
I am searching for a way to get the control on the elements inside a document. The problem is, the document is attached to an iFrame.
Here's an example:
.goto('https://wirecard-sandbox-engine.thesolution.com/engine/hpp/')
.use(iframe.withFrameName('wc', function (nightmare) {
nightmare
.type('#account_number', accountNumber)
.screenshot(resultfolder + '\\06-Card-Number.png')
.type('#card_security_code', testData.securityCode)
.selectIndex('#expiration_month_list', 1)
.selectIndex('#expiration_year_list', 4)
.click('span[id="hpp-form-submit"]')
}))
What I am trying to do above is:
Getting the iFrame with the Url.
Using this to get the control on every element in iFrame.
I encountered a similar issue. For one reason or another (Windows? Outdated?) the package nightmare-iframe was not working for me.
So I did this using pure DOM, which would transcribe for you:
var info = {
"account": account_number,
"security": testData.security_code;
};
nightmare.wait( () => {
/* Return true when the iframe is loaded */
var iframe = document.querySelector("iframe[name='wc']");
if (!iframe) {
return false;
}
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
return iframeDocument.querySelector("#account_number") != null;
}).evaluate( (info) => {
var iframe = document.querySelector("iframe[name='wc']");
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
iframeDocument.querySelector("#account_number").value = info.account;
iframeDocument.querySelector("#card_security_code").value = info.security;
//also use DOM to set proper date
...
iframeDocument.querySelector("span#hpp-form-submit").click();
}, info).then( () => {
console.log("submit done!");
}).catch((error) => {
console.error("Error in iframe magic", error);
});
This only works of course if the iframe is in the same domain as the main page and same-origin is allowed for the iframe. My use case was logging into paypal from a merchant's page.
For iframes with different origin, nightmare-iframe would be the package to use but then an error is needed to see what's the problem.
I have just begun to learn node.js. Over the last two days, I've been working on a project that accepts userinput and publishes a ICS file. I have all of that working. Now consider when I have to show this data. I get a router.get to see if I am at the /cal page and..
router.get('/cal', function(req, res, next)
{
var db = req.db;
var ical = new icalendar.iCalendar();
db.find({
evauthor: 'mykey'
}, function(err, docs) {
docs.forEach(function(obj) {
var event2 = ical.addComponent('VEVENT');
event2.setSummary(obj.evics.evtitle);
event2.setDate(new Date(obj.evics.evdatestart), new Date(obj.evics.evdateend));
event2.setLocation(obj.evics.evlocation)
//console.log(ical.toString());
});
});
res.send(ical.toString());
// res.render('index', {
// title: 'Cal View'
// })
})
So when /cal is requested, it loops through my db and creates an ICS calendar ical. If I do console.log(ical.toString) within the loop, it gives me a properly formatted calendar following the protocol.
However, I'd like to END the response with this. At the end I do a res.send just to see what gets published on the page. This is what gets published
BEGIN:VCALENDAR VERSION:2.0
PRODID:calendar//EN
END:VCALENDAR
Now the reason is pretty obvious. Its the nature of node.js. The response gets sent to the browser before the callback function finishes adding each individual VEVENT to the calendar object.
I have two related questions:
1) Whats the proper way to "wait" till the callback is done.
2) How
do I use res to send out a .ics dynamic link with
ical.toString() as the content. Do I need to create a new view for
this ?
edit: I guess for number 2 I'd have to set the HTTP headers like so
//set correct content-type-header
header('Content-type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename=calendar.ics');
but how do I do this when using views.
Simply send the response, once you got the neccessary data! You are not required to end or send directly in your route but can do it in a nested callback as well:
router.get('/cal', function(req, res, next) {
var db = req.db;
var ical = new icalendar.iCalendar();
db.find({
evauthor: 'mykey'
}, function(err, docs) {
docs.forEach(function(obj) {
var event2 = ical.addComponent('VEVENT');
event2.setSummary(obj.evics.evtitle);
event2.setDate(new Date(obj.evics.evdatestart), new Date(obj.evics.evdateend));
event2.setLocation(obj.evics.evlocation)
});
res.type('ics');
res.send(ical.toString());
});
});
I also included sending the proper Content-Type by using res.type.
Also: Don't forget to add proper error handling. You can for example use res.sendStatus(500) if an error occured while retrieving the documents.
I am updating a form and i want to make an update request on the serverwith an id
my model is:
var CampaignEditModel = Backbone.Model.extend({
urlRoot:"http://localhost:3033/campaign/update/",
url : function(){
var url = this.urlRoot + this.id;
return url;
},
idAttribute: "_id",
defaults:{
"id":null ,
"Name" :""
}
});
render function is called here:
$contents.empty().append(new EditView({model:editCampaigns}).render({id:id}).el);
and render function is:
render: function(options){
this.$el.append( _.template(EditTemplate));
this.model.set({"id":options.id})
console.log(this.model.get("id"));
this._modelBinder.bind(this.model, this.el);
return this;
},
events: {
'click .saveCampaign ': 'save'
},
save:function(){
this.model.set({
"Name" :$('#edname').val(),
});
this.model.save(null, {success: function(data){
console.log("data:" + data);
require(['campaignroute'],function(routes){
var router = routes.pageRouter;
router.navigate('gridView', {trigger: true});
});
}});
return false;
}
the problem is even i have set an id in the model still when save method is called
the request go like this
http://localhost:3033/campaign/update/undefined
and console shows the eror:
Failed to load resource: the server responded with a status of 404 (Not Found)
how to solve this problem?
Instead of passing options to your custom render(options) function and setting the model id there, set the it directly on the editCampaigns model, before entering render(options):
editCampaigns.set('id', id);
$contents.empty().append(new EditView({model:editCampaigns}).render().el);
and remove the extra
this.model.set({"id":options.id})
from render(options) together with the options parameter. It should look like similar to this:
render: function(){
this.$el.append( _.template(EditTemplate));
console.log(this.model.get("id"));
this._modelBinder.bind(this.model, this.el);
return this;
}
Your model also has an extra url function:
url : function(){
var url = this.urlRoot + this.id;
return url;
}
you don't need this one since the models' id is automatically appended after urlRoot.
Unrelated to you problem I see you used
http://localhost:3033/campaign/update
to define your update URL. The HTTP method you use, already says what kind of action will be executed, this is the reason why you can (and should) write URLs without verbs. Just remove the extra /update.
Here is a quick summary about best-practices:
How to create REST URLs without verbs?
Double check that the request is a post request and not a put request. 'Failed to load resource' errors is usually related to a missing request handler.
So I have been trawling the net for a day now trying to find a complete example of how to upload an image along with a normal POST (create) request from a backbone model. So after some initial digging around I discovered the FileReader api in HTML5 - After some testing I had this working great outside backbone by creating a XMLHttpRequest()
The problem im now trying to solve is how can I make bacbone send the FILES data along with the POST request like you would experience in a normal multipart form work flow. Im pretty new to backbone so please forgive any obvious errors. Heres what I have so far.
model
define(
[
'backbone'
],
function (Backbone) {
var Mock = Backbone.Model.extend({
url: '/api/v1/mocks',
idAttribute: '_id',
readFile: function(file){
var reader = new FileReader();
self = this;
reader.onload = (function(mockFile){
return function(e){
self.set({filename: mockFile.name, data: e.target.result});
};
})(file);
reader.readAsDataURL(file);
}
});
return Mock;
}
);
view
define(
[
'jquery',
'backbone',
'underscore',
'text!../templates/create_mock-template.html',
'models/mock',
'collections/mocks'
],
function ($, Backbone, _, createMockTemplate, Mock, mocksCollection) {
var CreateMockView = Backbone.View.extend({
template: _.template(createMockTemplate),
events: {
'submit form': 'onFormSubmit',
'click #upload-link': 'process',
'change #upload': 'displayUploads'
},
initialize: function () {
this.render();
return this;
},
render: function () {
this.$el.html(this.template);
},
process: function(e){
var input = $('#upload');
if(input){
input.click();
}
e.preventDefault();
},
displayUploads: function(e){
// Once a user selects some files the 'change' event is triggered (and this listener function is executed)
// We can access selected files via 'this.files' property object.
var formdata = new FormData();
var img, reader, file;
var self = this;
for (var i = 0, len = e.target.files.length; i < len; i++) {
file = e.target.files[i];
if (!!file.type.match(/image.*/)) {
if (window.FileReader) {
reader = new FileReader();
reader.onloadend = function (e) {
self.createImage(e.target.result, e);
};
self.file = file;
reader.readAsDataURL(file);
}
}
}
},
createImage: function(source, fileobj) {
var image = '<img src="'+source+'" class="thumbnail" width="200" height="200" />'
this.$el.append(image);
},
onFormSubmit: function (e) {
e.preventDefault();
// loop through form elements
var fields = $(e.target).find('input[type=text], textarea');
var data = {};
// add names and vlaues to data object
$(fields).each(function(i, f) {
data[$(f).attr('name')] = $(f).attr('value');
});
// create new model from data
var mock = new Mock(data);
// this should set the filename and data attributes on the model???
mock.readFile(this.file);
// save to the server
mock.save(null, {
wait: true,
error: function(model, response) {
console.log(model)
},
success: function(model, response) {
mocksCollection.add(model);
console.log(model, response);
}
});
},
});
return CreateMockView;
}
);
I totally appreciate this may all be a bit hand wavey, its more a proof of concept than anything else and a good chance to learn backbone too. So the crux of my question is this
When the request is sent by backbone to the api why arent I seeing the data and filename attrs in the request to the node/express server
Is what im trying to do even possible, I basically thought i'd be able to read the data attr and create and image on the server?
Is there a way to perhaps overide the sync method on the Mock model and create a properly formed request where POST and FILES are correctly set.
Im sure this is possible but I just cant seem to find the bit of info I need to plough on and get this working.!
Hope someone can help!
CHEERS
edit
Just wanted to provide a bit more info as by my understanding and some brief chats on the document cloud irc channel this should work. So when I call
mock.readFile(this.file)
the fileName and data attributes dont seem to get set. In fact a console log here dosent even seem to fire so im guessing this could be the problem. I would be happy with this approach to basically build up the Image on the node end based on the value of data and fileName. So why arent these properties being set and passed along in the post request to the api?
I have resolved this situation splitting the Model creation in two steps:
Basic information
Assets
For example if my Model is a Film, I show a create-form that only include title and synopsis. This information is sent to the server and create the Model. In the next step I can show the update-form and now is very easier to use File Uploads plugins like:
jQuery File Upload Very profesional and stable
BBAssetsUpload Very beta, but based on Backbone, and you can say you know the programmer ;P
You can also check their source code for references to try to achieve a one-step Backbone Model with File creation solution.
jquery-file-upload-middleware for express is probably worth mentioning.
I have been working through the tutorial by Alex Young on using flash messages. According to this tutorial, one is able to override the default flash messages formatting using dynamicHelpers. Unfortunately there are no details provided on what is going on, and it is not possible to post any comments on the tutorial page to ask relevant questions.
What I fail to see is the relationship between the call to req.flash() in the file 'app.js', and the FlashMessage object exported in file 'helpers.js'. Why would a regular call to req.flash(), which is a standard function in express.js, link to this FlashMessage prototype in the first place? I can't see how this is happening when I look at the code.
At first I thought the FlashMessage object might have been provided to req.flash() by express.js, in which case we are just extending or overriding it in our helper file. The problem with this is that I cannot find any reference to FlashMessage in the source code of express.js.
I would be really grateful if someone could explain it to me.
The flash message is set in file 'apps.js' by calling:
req.flash('info', 'Document created.');
The FlashMessage obect is exported 'helpers.js':
FlashMessage.prototype = {
// Get css definition string for icon.
get icon() {
switch (this.type) {
case 'info':
return 'ui-icon-info';
case 'error':
return 'ui-icon-alert';
}
},
// Get css class for message container.
get stateClass() {
switch (this.type) {
case 'info':
return 'ui-state-highlight';
case 'error':
return 'ui-state-error';
}
},
// Returns HTML formatted message.
toHTML: function() {
return '<div class="ui-widget flash">' +
'<div class="' + this.stateClass + ' ui-corner-all">' +
'<p><span class="ui-icon ' + this.icon + '"></span>' + this.messages.join(', ') + '</p>' +
'</div>' +
'</div>';
}
};
exports.dynamicHelpers = {
flashMessages: function(req, res) {
var html = '';
['error', 'info'].forEach(function(type) {
var messages = req.flash(type);
if (messages.length > 0) {
html += new FlashMessage(type, messages).toHTML();
}
});
return html;
}
};
In app.js file the full routing function which calls req.flash is as follows:
// Attach dynamicHelpers to app.
app.dynamicHelpers(require('./helpers.js').dynamicHelpers);
// Routing function which calls req.flash.
app.post('/documents', loadUser, function(req, res) {
// Create Document object and assign value.
console.log('Document content: %s', req.body['document']);
var document = new Document(req.body['document']);
document.save(function() {
// Redirect to another page.
req.flash('info', 'Document created.');
res.redirect('/documents');
}
});
});
There are 2 different things here:
a) req.flash() which is implemented by Express itself - not by you, you're just using that function
b) your dynamic helper:
From the Express guide:
Dynamic view helpers are simply functions which accept req, res, and
are evaluated against the Server instance before a view is rendered.
The return value of this function becomes the local variable it is
associated with.
app.dynamicHelpers({
session: function(req, res){
return req.session;
}
});
Let's "translate" that into your code:
// Attach dynamicHelpers to app.
app.dynamicHelpers(require('./helpers.js').dynamicHelpers);
That means that when you call the variable flashMessages in your view code, you'll get the html representation of those flash variables defined.
So the most important thing here is to consider you are only using req.flash(), not implementing it. You are implementing a helper that is using that function.