I am trying to analyze anchor links ( their text property ) in PhantomJS.
The retrieval happens here:
var list = page.evaluate(function() {
return document.getElementsByTagName('a');
});
this will return an object with a property length which is good (the same length I get when running document.getElementsByTagName('a'); in the console). But the vast majority of the elements in the object have the value of null which is not good.. I have no idea why this is happening.
I have been playing with converting to a real array thru slice which did no good. I have tried different sites, no difference. I have dumped the .png file to verify proper loading and the site is properly loaded.
This is obviously not the full script, but a minimal script that shows the problem on a well known public site ;)
How can I retrieve the full list of anchors from the loaded page ?
var page = require('webpage').create();
page.onError = function(msg, trace)
{ //Error handling mantra
var msgStack = ['PAGE ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
console.error(msgStack.join('\n'));
};
phantom.onError = function(msg, trace)
{ //Error handling mantra
var msgStack = ['PHANTOM ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : ''));
});
}
console.error(msgStack.join('\n'));
phantom.exit(1);
};
function start( url )
{
page.open( url , function (status)
{
console.log( 'Loaded' , url , ': ' , status );
if( status != 'success' )
phantom.exit( 0 );
page.render( 'login.png');
var list = page.evaluate(function() {
return document.getElementsByTagName('a');
});
console.log( 'List length: ' , list.length );
for( var i = 0 ; i < list.length ; i++ )
{
if( !list[i] )
{
console.log( i , typeof list[i] , list[i] === null , list[i] === undefined );
//list[i] === null -> true for the problematic anchors
continue;
}
console.log( i, list[i].innerText , ',' , list[i].text /*, JSON.stringify( list[i] ) */ );
}
//Exit with grace
phantom.exit( 0 );
});
}
start( 'http://data.stackexchange.com/' );
//start( 'http://data.stackexchange.com/account/login?returnurl=/' );
The current version of phantomjs permits only primitive types (boolean, string, number, [] and {}) to pass to and from the page context. So essentially all functions will be stripped and that is what DOM elements are. t.niese found the quote from the docs:
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
Closures, functions, DOM nodes, etc. will not work!
You need to do a part of the work inside of the page context. If you want the innerText property of every node, then you need to map it to a primitive type first:
var list = page.evaluate(function() {
return Array.prototype.map.call(document.getElementsByTagName('a'), function(a){
return a.innerText;
});
});
console.log(list[0]); // innerText
You can of course map multiple properties at the same time:
return Array.prototype.map.call(document.getElementsByTagName('a'), function(a){
return { text: a.innerText, href: a.href };
});
Related
I have a function, which will either return a cached template or if the template has not been cached - it will load it via AJAX and then return it. Here's what I've got:
var getTpl = function( name ) {
var cached = cache.get( 'templates' ) || {};
if( cached.hasOwnProperty( name ) ) {
console.log( 'template ' + name + '.mustache found in cache' );
return cached[ name ];
}
else {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
var tpl;
$.ajax( {
url: path.templates + '/' + name + '.mustache',
async: false,
success: function( data ) {
tpl = data;
var cached = store.get( 'miniTemplates' ) || {};
var newTemplate = {};
newTemplate[ name ] = data;
if( ! cached.hasOwnProperty( name ) ) cache.set( 'templates', _.extend( cached, newTemplate ) )
},
error: function() { tpl = false; }
} );
return tpl;
}
}
This works fine. However, Chrome is complaining about:
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/.
Therefore I wanted to switch to using $.deferred, but I can't wrap my head around it. How can I re-write the function above, so calling getTpl would always return a template (either form the cache or directly from the AJAX request)?
You can use promise/deferred concept to achieve your needs
var getTpl = function( name ) {
var promise;
var cached = cache.get( 'templates' ) || {};
if( cached.hasOwnProperty( name ) ) {
console.log( 'template ' + name + '.mustache found in cache' );
var df = new $.Deferred();
df.resolve(cached[ name ]);
promise = df.promise();
} else {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
promise = $.ajax({
url: path.templates + '/' + name + '.mustache'
}).then(function(data) {
tpl = data;
var cached = store.get( 'miniTemplates' ) || {};
var newTemplate = {};
newTemplate[ name ] = data;
if( ! cached.hasOwnProperty( name ) ) cache.set( 'templates', _.extend( cached, newTemplate ) )
return tpl;
});
}
return promise;
}
Then, call your method like this:
getTpl('xyz')
.then(function(template) {
// you have the template, either from cache or fetched via ajax
})
.fail(function(err) {
console.log(err);
});
Since you appear to appear to already be using underscore/lodash, you can make use of memoization rather than maintaining your own cache.
The beauty of promises is that you can access them again and again and they will always produce the same value:
var getTpl = _.memoize(function( name ) {
console.log( 'requesting ' + name + '.mustache template via AJAX' );
return $.ajax({
url: path.templates + '/' + name + '.mustache'
});
});
Yes, it really is that simple.
Then you can just use it like any other promise:
getTpl('myTemplate').then(function (template) {
// use template
}, function (error) {
console.log('Could not retrieve template.', error);
});
I am trying to call a service from inside the function "onNotification(e, $scope, currentUser)" However every time I try and log it, it either comes back with the error:
processMessage failed: Error: TypeError: Cannot set property 'Current' of undefined
or if I use "var userRegistration = this.currentUser" I get "undefined" in the log, however I know that the service is being updated as it returns the correct value when I log the result outside the function.
Here's my code:
function onNotification(e, $scope, currentUser) {
var Currentuser = this.currentUser;
$("#app-status-ul").append('<li>EVENT -> RECEIVED:' + e.event + '</li>');
switch( e.event )
{
case 'registered':
if ( e.regid.length > 0 )
{
$("#app-status-ul").append('<li>REGISTERED -> REGID:' + e.regid + "</li>");
// Your GCM push server needs to know the regID before it can push to this device
// here is where you might want to send it the regID for later use.
console.log(Currentuser);
console.log("regID = " + e.regid);
}
break;
case 'message':
// if this flag is set, this notification happened while we were in the foreground.
// you might want to play a sound to get the user's attention, throw up a dialog, etc.
if ( e.foreground )
{
$("#app-status-ul").append('<li>--INLINE NOTIFICATION--' + '</li>');
// on Android soundname is outside the payload.
// On Amazon FireOS all custom attributes are contained within payload
var soundfile = e.soundname || e.payload.sound;
// if the notification contains a soundname, play it.
var my_media = new Media("/android_asset/www/"+ soundfile);
my_media.play();
}
else
{ // otherwise we were launched because the user touched a notification in the notification tray.
if ( e.coldstart )
{
$("#app-status-ul").append('<li>--COLDSTART NOTIFICATION--' + '</li>');
}
else
{
$("#app-status-ul").append('<li>--BACKGROUND NOTIFICATION--' + '</li>');
}
}
$("#app-status-ul").append('<li>MESSAGE -> MSG: ' + e.payload.message + '</li>');
$("#app-status-ul").append('<li>MESSAGE -> MSGCNT: ' + e.payload.msgcnt + '</li>');
break;
case 'error':
$("#app-status-ul").append('<li>ERROR -> MSG:' + e.msg + '</li>');
break;
default:
$("#app-status-ul").append('<li>EVENT -> Unknown, an event was received and we do not know what it is</li>');
break;
}
}
Here's the Service within the same js file:
App.service('currentUser', function ()
{
return{};
});
How should I go about calling the service inside this function? My Angular knowledge is limited. Any help would be much appreciated.
Update: In response to user PSL. onNotification is called here:
App.controller('LogHomeCtrl', function($scope, $log, $state, currentUser)
{
var pushNotification;
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady()
{
pushNotification = window.plugins.pushNotification;
$("#app-status-ul").append('<li>registering ' + device.platform + '</li>');
if ( device.platform == 'android' || device.platform == 'Android'){
pushNotification.register(
successHandler,
errorHandler,
{
"senderID":"460885134680",
"ecb":"onNotification"
});
} else {
pushNotification.register(
tokenHandler,
errorHandler,
{
"badge":"true",
"sound":"true",
"alert":"true",
"ecb":"onNotificationAPN"
});
}
}
function successHandler (result)
{
alert('result = ' + result);
}
function errorHandler (error)
{
alert('error = ' + error);
}
});
Try Changing
var Currentuser = this.currentUser;
to
var Currentuser = currentUser;
currentUser does not belong to onNotification it is meerly being passed in as a parameter and therefore being defined as a variable in its local scope. But not as a property of itself
Edit
onNotification ought to be defined inside a controller. eg
yourModuleName.controller('controllerName', ['$scope', 'currentUser', function($scope, currentUser) {
$scope.currentUser = currentUser;
$scope.onNotification = function(e, $scope, $scope.currentUser) {
}
}]);
Here is a class I made which uses YQL to do Google Translate.
var Translator = {
source: 'ro', // default
target: 'en', // default
url: 'http://query.yahooapis.com/v1/public/yql?q=select * from google.translate where q="',
urlRemaining: '";&format=json&diagnostics=true&env=store://datatables.org/alltableswithkeys&callback=',
diacritics: Array(),
newCharacters: Array(),
replaceAll: function( string, replace, replaceWith ) {
return string.replace( new RegExp( replace, 'g' ), replaceWith );
},
replaceDiacritics: function( text ) {
string = text;
// diacritics and newCharacters should be arrays of the same length
// diacritics should only be specified in lowercase - uppercased version will be assumed
// durring the process
for ( i = 0; i < this.diacritics.length; i++ ) {
string = this.replaceAll( string, this.diacritics[i], this.newCharacters[i] );
string = this.replaceAll( string, this.diacritics[i].toUpperCase(), this.newCharacters[i].toUpperCase() );
}
return string;
},
translate: function( text, target, source ) {
target = target || this.target;
source = source || this.source;
return $.ajax({
url: this.url + encodeURIComponent( this.replaceDiacritics( text ) ) + '" and source="' + source + '" and target="' + target + this.urlRemaining,
dataType: 'json',
cache: false
});
},
spitResult: function( x, container ) {
x.success(function(realData) {
$report = realData.query.results.json.sentences;
$result = '';
if ($.isArray($report)) {
for (i = 0; i < $report.length; i++) {
$result += $report[i].trans;
}
} else {
$result = $report.trans;
}
if (container instanceof jQuery) {
container.html($result);
} else {
container.innerHTML = $result;
}
});
}
}
And now I'm calling it on a set of elements in the page
promises = Array();
Translator.diacritics = Array('ă', 'â', 'î', 'ș', 'ț');
Translator.newCharacters = Array('a', 'a', 'i', 's', 't');
$('.translate').each(function() {
$this = $(this);
promises[promises.length] = Translator.translate($this.html(), 'en', 'ro');
Translator.spitResult(promises[promises.length-1], $this);
});
This is working no problem with Firefox and Chrome. However, as usual, Internet Explorer (9 in my case) seems to be the problem. From what I've been able to deduce it resides in the promise resolver (Translate.spitResult) - which is called, but no data seem to be passed to it. I looked at it in the console. The promise array element is populated with 3 objects (which I am sure is normal), but it is:
readyState: 0
responseJSON: undefined, status: 0
statusText: "No Transfer".
I tried removing the diacritics function (now I'm not exactly sure why, because there should have been a response anyway), I also tried cache: false mode on the ajax call, but to no avail.
Does anyone know what could be the matter ?
Thank you in advance.
yes Internet Explorer is your problem...
Check http://caniuse.com/#search=promise
I think you can use a polyfill (https://github.com/taylorhakes/promise-polyfill) if that's the problem, never tried a polyfill for promises but it will work like a charm for sure
Im having a strange problem with the following code:
function getTrxData(trx,inputPar,outputPar,callback) {
var retorno = {};
var URL = '/XMII/Runner?Transaction=' + trx;
var params = "";
for(key in inputPar)
params = params + "&" + key + "=" + inputPar[key];
if(!outputPar)
outputPar = "*";
if(params)
URL = URL + params;
URL = URL + '&OutputParameter=' + outputPar;
$.ajax({
type: "GET",
url: URL,
async: true,
success: function(data){
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
callback(retorno);
},
error: function(jqXHR, textStatus, errorThrown){
retorno.tipo = 'E'; // Error
retorno.mensaje = "Error: " + textStatus;
callback(retorno);
}
});
}
function crearSelect(trx,inputPar,outputPar,selectID,campoTextoXX,campoValor,valorDefault,callback2) {
// At this point campoTextoXX exists and has a value
getTrxData(trx,inputPar,outputPar,function(retorno2) {
// At this point campoTextoXX is an object equal to callback2
if(retorno2.tipo == 'E') {
callback2(retorno2);
return false;
}
var options = "";
var selected = "";
$.each(retorno2.datos.Rowset.Row, function(k,v) {
if(valorDefault == v[campoValor]) {
selected = " selected='selected'";
} else {
selected = "";
}
options = options + "<option value='" + v[campoValor] + selected "'>";
options = options + v[campoTextoXX];
options = options + "</option>";
});
$("#" + selectID + " > option").remove();
$("#" + selectID).append(options);
callback2(retorno2);
});
}
And the call is like this:
crearSelect("Default/pruebas_frarv01/trxTest",{letra: 'V'},"*",'selectID',"CustomerID",'OrderID','',function(retorno) {
alert(retorno.tipo + ": " + retorno.mensaje);
});
The problem is that campoTextoXX and campoValor dont get any value inside the callback function. Also, debugging in Chrome shows me that campoTextoXX has the value of the callers callback function:
alert(retorno.tipo + ": " + retorno.mensaje);
I dont know what to do next.
Any ideas?
Thx
You might find it easier to mange the callback chain by exploiting $.ajax's ability to behave as a jQuery Deferred.
This allows us very simply to specify the "success" and "error" behaviour in the guise of request.done(...) and request.fail(...) at the point where getTrxData is called rather than inside getTrxData - hence the callback chain is (ostensibly) one level less deep.
function getTrxData(trx, inputPar, outputPar) {
inputPar.Transaction = trx;
inputPar.OutputParameter = (outputPar || '*');
return $.ajax({
url: '/XMII/Runner?' + $.param(inputPar)
});
}
function makeOptions(obj, selectID, campoTextoXX, campoValor, valorDefault) {
var $option, selected, $select = $("#" + selectID);
$("#" + selectID + " > option").remove();
$.each(obj.datos.Rowset.Row, function(k, v) {
selected = (valorDefault == v[campoValor]) ? ' selected="selected"' : '';
$option = $('<option value="' + v[campoValor] + selected + '">' + v[campoTextoXX] + "</option>");
$select.append($option);
});
return obj;
}
function crearSelect(trx, inputPar, outputPar, selectID, campoTextoXX, campoValor, valorDefault, callback) {
var request = getTrxData(trx, inputPar, outputPar);
request.done(function(data) {
var obj = {
datos: $.xml2json(data),
tipo: 'S',// Success
mensaje: "Datos obtenidos correctamente"
};
callback(makeOptions(obj, selectID, campoTextoXX, campoValor, valorDefault));
});
request.fail(function(jqXHR, textStatus, errorThrown) {
var obj = {
tipo: 'E',// Error
mensaje: "Error: " + textStatus
};
callback(obj);
});
}
crearSelect("Default/pruebas_frarv01/trxTest", {letra:'V'}, "*", 'selectID', "CustomerID", 'OrderID', '', function(retorno) {
alert(retorno.tipo + ": " + retorno.mensaje);
});
You will see that this is essentially a refactored version of your original code, with significant simplification of the string handling in getTrxData, which appears to work correctly.
The options code has been pulled out as a separate function, makeOptions, to make the new structure of crearSelect clearer. This is not strictly necessary and the code could be re-combined without penalty.
Tested here insomuch as to make sure it loads and runs through to the "Error" alert, which it does successfully. Without access to the server-side script, I can't test/debug the full ajax functionality so you may need to do some debugging.
The problem appears to be that you are overwriting the variable "pepe" somewhere in your code.
Also, check how you are assigning your callback function and parameter object. A quick look appears that it is not being supplied the correct parameters.
You should be careful not to use global variables within your success and error functions. so instead of:
success: function(data){
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
callback(retorno);
}
I think you should do something like:
success: function(data){
var retorno = {};
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
callback(retorno);
}
furthermore you should use Firebug for Firefox to step through your code and watch your variables to ensure that the data is coming in correctly, and not getting overwritten at any point
Your control flow is a bit confusing, and another thing you can do is check to make sure your callbacks and variables are correct using some typeof conditionals to make sure they are functions, etc. try doing things like this:
success: function(data){
var retorno = {};
retorno.datos = $.xml2json(data);
retorno.tipo = 'S'; // Success
retorno.mensaje = "Datos obtenidos correctamente";
if (typeof callback !== "function" || typeof data !== "object"){
console.log('error');
throw "callback or data is not correct type";
}
callback(retorno);
}
and make sure you aren't getting an error in the console.
This is working fine if i am writing jpg|png|jpeg|gif here...
if (!(ext && /^(jpg|png|jpeg|gif)$/.test(ext))) {
alert('Error: extension is not allowed!' + Extensions + ' file ext: ' + ext);
return false;
}
If i use variable instead of static then it is not working
var Extensions = "jpg|png|jpeg|gif";
if (!(ext && /^(Extensions)$/.test(ext))) {
alert('Error: extension is not allowed!' + Extensions + ' file ext: ' + ext);
return false;
}
Thanks in advance
Imdadhusen
You should do it like this:
(new RegExp("jpg|png|jpeg|gif")).test(ext)
You are using invalid syntax for the regular expression. If you are going to store it in a variable, you must still use your regular expression from your first example.
So:
var Extensions = /^(jpg|png|jpeg|gif)$/;
if (!(ext && Extensions.test(ext)))
will work. Your second example is trying to match the word 'Extensions'.
it wont get error
var Extensions = "/^(jpg|png|jpeg|gif)$/";
if (!(ext && Extensions.test(ext))) {
alert('Error: extension is not allowed!' + Extensions + ' file ext: ' + ext);
return false;
}
To use a variable, you need to use the RegExp object:
new RegExp('^(' + Extensions + ')$').test(ext)
Or assign the entire regex into your variable:
var Extensions = /^(jpg|png|jpeg|gif)$/;
Extensions.test(ext)
Perhaps call it allowedExtensions or something though.
Try this:
var Extensions = /^(jpg|png|jpeg|gif)$/;
if (!(ext && Extensions.test(ext))) {
alert('Error: extension is not allowed!' + Extensions + ' file ext: ' + ext);
return false;
}