How to stop executing Javascript function containing infinite loop after some time - javascript

Suppose I have following piece of code that contains an infinite loop:
function infiniteLoop() {
while(true) {
//do something, eg.
document.getElementById("someID").innerHTML = "Blah";
}
}
If we execute this code in an online compiler, browser will crash. I want to prevent that from happening. So I tried following code following this answer:
function willNotCrash() {
myVar = setInterval(infiniteLoop, 5000);
setTimeout(function(){
clearInterval(myVar);
}, 4000);
}
This code doesn't make the browser to crash, because I am stopping the execution before infiniteLoop() gets called by clearInterval(myVar).
My question is how do I stop executing such functions if they don't response within some period of time (eg. after 5 seconds or before the browser is crashed).
For example, if we copy paste following java code in https://www.compilejava.net/
public class HelloWorld {
public static void main(String[] args) {
while(true) {
System.out.println("Blah");
}
}
}
we get a nice output saying,
Script was taking longer than 5 seconds to execute so it was killed.
Here is my current code: http://js.do/code/106546

This is a bit tricky but perfectly doable. You need to tokenize the script and then rebuild it but insert a counter increment in every loop and function call. If the counter goes above some threshold, then bomb out. I did it here: https://littleminigames.com/
You can see the source at https://bitbucket.org/cskilbeck/littleminigames/src
The interesting bits are in wrapper.js (https://bitbucket.org/cskilbeck/littleminigames/src/ac29d0d0787abe93c75b88520050a6792c04d34d/public_html/static/js/wrapper.js?at=master&fileviewer=file-view-default)
Google escodegen, estraverse and esprima
I relied heavily on this: https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js
wrapper.js, as requested:
// Don't obfuscate this file! We depend on the toString() of functions!
// this was all nicked from https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js
(function(mainApp) {
'use strict';
var esprima = window.esprima,
estraverse = window.estraverse,
escodegen = window.escodegen,
errors = [],
eng,
Syntax = estraverse.Syntax;
// This implements the jankiest possible "source map", where we keep an array
// of [generatedLine, knownSourceLine]. Seems to essentially work.
function SourceNode(line, col, _sourceMap, generated) {
this.line = line;
this.col = col;
this.generated = generated;
}
SourceNode.prototype.toStringWithSourceMap = function toStringWithSourceMap() {
var code = [];
var mapLines = {};
var map = [];
// assumes that wrapCode adds two lines
var line = 3;
var lastMapLine = null;
function walk(node) {
if (typeof(node) === "string") {
if (node) {
code.push(node);
var matches = node.match(/\n/g);
if (matches !== null) {
line += matches.length;
}
}
} else if (node instanceof SourceNode) {
if (node.line !== null) {
if (!mapLines[line]) {
map.push([line, node.line]);
mapLines[line] = node.line;
}
}
walk(node.generated);
} else {
node.forEach(walk);
}
}
walk(this);
return {
code: code.join(''),
map: map
};
};
SourceNode.prototype.toString = function toString() {
return this.toStringWithSourceMap().code;
};
// This is used by escodegen
window.sourceMap = {
SourceNode: SourceNode
};
// TODO (chs): add in all the things that need to be masked
function runWrapper($userCode, __sys) {
var clear = __sys.clear,
setpixel = __sys.setpixel,
rectangle = __sys.rectangle,
box = __sys.box,
line = __sys.line,
getpixel = __sys.getpixel,
getpixeli = __sys.getpixeli,
keypress = __sys.keypress,
keyrelease = __sys.keyrelease,
keyheld = __sys.keyheld,
reset = __sys.reset;
__sys.userFunction = __sys.catchErrors($userCode);
}
function extractCode(fn) {
var code = fn.toString();
return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
}
function makeOneLine(code) {
return code.replace(/(\/\/[^\n]+|\n\s|\r\n\s*)/g, '');
}
var runTemplate = makeOneLine(extractCode(runWrapper));
function wrapCode(code, template, functionName, postCode) {
// avoid interpretation of the replacement string by using a fun.
// otherwise mo' $ mo problems.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter
return ("'use strict';" + template.replace(/\$userCode/, function() {
return 'function ' + functionName + '() {\n' + code + postCode + '\n}';
}));
}
var injectStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?');").body[0];
var injectElseStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?'); else ;").body[0];
function CallExpression(callee, args) {
this.callee = callee;
this.arguments = args;
}
CallExpression.prototype.type = Syntax.CallExpression;
function Identifier(name) {
this.name = name;
}
Identifier.prototype.type = Syntax.Identifier;
function BlockStatement(body) {
this.body = body;
}
BlockStatement.prototype.type = Syntax.BlockStatement;
function ReturnStatement(argument) {
this.argument = argument;
}
ReturnStatement.prototype.type = Syntax.ReturnStatement;
function FunctionExpression(id, params, body) {
this.id = id;
this.params = params;
this.body = body;
this.defaults = [];
this.expression = false;
this.generator = false;
this.rest = null;
}
FunctionExpression.prototype.type = Syntax.FunctionExpression;
function wrapId(node, defaultName) {
if (node.loc) {
var id = (node.id || {
name: null,
loc: null
});
var loc = id.loc || node.loc;
var name = id.name || defaultName;
return new Identifier(name + '$' + loc.start.line);
} else {
return node.id;
}
}
function instrumentAST(ast) {
var identifierStack = [];
function pushIdentifier(s) {
identifierStack[identifierStack.length - 1].push(s);
}
function popIdentifierStack() {
identifierStack.pop();
}
function pushIdentifierStack() {
identifierStack.push([]);
}
function peekLastIdentifier() {
var lastStackIdx = identifierStack.length - 1;
if (lastStackIdx >= 0) {
var stack = identifierStack[lastStackIdx];
if (stack.length) {
return stack[stack.length - 1];
}
}
return '';
}
pushIdentifierStack();
return estraverse.replace(ast, {
enter: function enterAST(node) {
switch (node.type) {
case Syntax.VariableDeclarator:
if (node.id.type === Syntax.Identifier) {
pushIdentifier(node.id.name);
}
break;
case Syntax.MemberExpression:
if (node.object.type === Syntax.Identifier) {
var id = node.object.name;
if (node.property.type === Syntax.Identifier) {
id += '__dot__' + node.property.name; // huh? why mangle these?
// console.log(id);
}
pushIdentifier(id);
} else if (node.property.type === Syntax.Identifier) {
pushIdentifier(node.property.name);
}
break;
case Syntax.FunctionDeclaration:
pushIdentifierStack();
break;
case Syntax.FunctionExpression:
pushIdentifierStack();
break;
default:
break;
}
return node;
},
leave: function leaveAST(node) {
switch (node.type) {
case Syntax.DoWhileStatement:
break;
case Syntax.ForStatement:
break;
case Syntax.FunctionDeclaration:
break;
case Syntax.FunctionExpression:
break;
case Syntax.WhileStatement:
break;
default:
return estraverse.SKIP;
}
// modify the BlockStatement in-place to inject the instruction counter
if(node.body.body === undefined) {
// they have used a non-block statement as the body of a function or loop construct
// not allowed for function declarations - should never get here
if(node.type === Syntax.FunctionDeclaration) {
errors.push({
message: "Missing {",
line: node.loc.start.line,
column: node.loc.start.column
});
}
else {
// otherwise insert the test
var newBody = angular.copy(injectElseStatement);
newBody.alternate = node.body;
node.body = newBody;
}
return estraverse.SKIP;
}
node.body.body.unshift(injectStatement);
if (node.type === Syntax.FunctionExpression) {
popIdentifierStack();
// __catchErrors(node)
node.id = wrapId(node, peekLastIdentifier());
return new CallExpression(
new Identifier("__sys.catchErrors"), [node]);
}
if (node.type === Syntax.FunctionDeclaration) {
popIdentifierStack();
// modify the BlockStatement in-place to be
// return __catchErrors(function id() { body });
var funBody = node.body;
node.body = new BlockStatement([
new ReturnStatement(
new CallExpression(
new CallExpression(
new Identifier("__sys.catchErrors"), [new FunctionExpression(
wrapId(node, peekLastIdentifier()), [],
funBody)]), []))
]);
}
return node;
}
});
}
// mainApp.sandbox('var a = 1; function update(frame) { clear(0); }').code
// give it the source code as a string
mainApp.sandbox = function(code) {
var rc = {};
this.errors = [];
try {
this.ast = instrumentAST(esprima.parse(code, { range: true, loc: true }));
this.map = escodegen.generate(this.ast, { sourceMap: true, sourceMapWithCode: true });
this.code = wrapCode(this.map.code, runTemplate, '', ';\n__sys.updateFunction = (typeof update === "function") ? update : null;');
}
catch(e) {
this.errors.push({
message: e.description,
line: e.lineNumber,
column: e.column
});
}
if(this.code) {
this.code = "eng.clientFunction = function(__sys) {" + this.code + "};";
}
};
mainApp.sandbox.prototype.searchMap = function(needle) {
// binary search
var lo = 0;
var hi = this.map.map.length;
var mid, here;
while (true) {
mid = lo + ((hi - lo) >> 1);
here = this.map.map[mid];
if (mid === lo || here[0] === needle) {
return here[1];
} else if (here[0] > needle) {
hi = mid;
} else {
lo = mid;
}
}
};
})(mainApp);

Typically all JavaScript runs in one thread, so it is impossible to run any JavaScript that could stop your loop while your loop is running. Using HTML5 web workers, you can run the infinite loop in a separate thread, and then you can terminate it:
var myWorker = new Worker( '/infinite.js ');
setTimeout( function ( ) {
myWorker.terminate( );
}, 5000 );
However your web worker won't have access to the DOM, so the contents of your infinite loop would need to be different that what you have in your question.

I found exactly what I was looking for in Bergi's comment,
Alternatively, place a if (Date.now() > dateAtStartOfExecution+5000) return; in every loop body.
So now my code looks like:
function infiniteLoop() {
dateAtStartOfExecution = Date.now();
while(true) {
//do something
document.getElementById("someID").innerHTML = "Blah";
if (Date.now() > dateAtStartOfExecution+5000) {
alert("Taking too much time. Killing.");
return;
}
}
}
If I run this code after 5 seconds I will get an alert and the execution will stop. Try this:
http://js.do/code/106565

Related

JavaScript array has elements but length is zero

I've done some searching around the web and nothing seems to solve my problem. I have the following jQuery code:
function youtube_data_parser(data) {
//---> parse video data - start
var qsToJson = function(qs) {
var res = {};
var pars = qs.split('&');
var kv, k, v;
for (i in pars) {
kv = pars[i].split('=');
k = kv[0];
v = kv[1];
res[k] = decodeURIComponent(v);
}
return res;
}
//---> parse video data - end
var get_video_info = qsToJson(data);
if (get_video_info.status == 'fail') {
return {
status: "error",
code: "invalid_url",
msg: "check your url or video id"
};
} else {
// remapping urls into an array of objects
//--->parse > url_encoded_fmt_stream_map > start
//will get the video urls
var tmp = get_video_info["url_encoded_fmt_stream_map"];
if (tmp) {
tmp = tmp.split(',');
for (i in tmp) {
tmp[i] = qsToJson(tmp[i]);
}
get_video_info["url_encoded_fmt_stream_map"] = tmp;
}
//--->parse > url_encoded_fmt_stream_map > end
//--->parse > player_response > start
var tmp1 = get_video_info["player_response"];
if (tmp1) {
get_video_info["player_response"] = JSON.parse(tmp1);
}
//--->parse > player_response > end
//--->parse > keywords > start
var keywords = get_video_info["keywords"];
if (keywords) {
key_words = keywords.replace(/\+/g, ' ').split(',');
for (i in key_words) {
keywords[i] = qsToJson(key_words[i]);
}
get_video_info["keywords"] = {
all: keywords.replace(/\+/g, ' '),
arr: key_words
};
}
//--->parse > keywords > end
//return data
return {
status: 'success',
raw_data: qsToJson(data),
video_info: get_video_info
};
}
}
function getVideoInfo() {
var get_video_url = $('#ytdlUrl').val();
var get_video_id = getUrlVars(get_video_url)['v'];
var video_arr_final = [];
var ajax_url = "video_info.php?id=" + get_video_id;
$.get(ajax_url, function(d1) {
var data = youtube_data_parser(d1);
var video_data = data.video_info;
var player_info = data.video_info.player_response;
var video_title = player_info.videoDetails.title.replace(/\+/g, ' ');
var fmt_list = video_data.fmt_list.split(',');
var video_thumbnail_url = video_data.thumbnail_url;
var video_arr = video_data.url_encoded_fmt_stream_map;
//create video file array
$.each(video_arr, function(i1, v1) {
var valueToPush = {};
valueToPush.video_url = v1.url;
valueToPush.video_thumbnail_url = video_thumbnail_url;
valueToPush.video_title = video_title;
$.each(fmt_list, function(i2, v2) {
var fmt = v2.split('/');
var fmt_id = fmt[0];
var fmt_quality = fmt[1];
if (fmt_id == v1.itag) {
valueToPush.fmt_id = fmt_id;
valueToPush.fmt_quality = fmt_quality;
}
});
video_arr_final.push(valueToPush);
});
});
return video_arr_final;
}
function getUrlVars(url) {
var vars = {};
var parts = url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) {
vars[key] = value;
});
return vars;
}
function fillInOptions(ytOptions) {
//console.log(ytOptions);
//alert(ytOptions[0]);
var ytFill = ytOptions;
console.log(ytFill);
//ytFill.forEach(function(i,v) {
var ytdlOptions = $('#ytdlOptions');
ytFill.forEach(function(i,v) {
console.log(i);
ytdlOptions.append(new Option(v.fmt_quality, v.fmt_id));
});
return true;
}
function showYTDLLoader() {
$('#ytdlInput').fadeOut(1000, function() {
$('#ytdlLoader').fadeIn(500);
});
var options = getVideoInfo();
//console.log(options);
if (fillInOptions(options) == true) {
//do rest
}
}
function showYTDLOptions() {
return true;
}
function startDownload() {
showYTDLLoader();
}
function hideYTDLLoader() {
$('#ytdlLoader').fadeOut(500);
}
function animateCSS(element, animationName, callback) {
const node = $(element);
node.addClass(animationName);
function handleAnimationEnd() {
node.removeClass(animationName);
node.animationend = null;
if (typeof callback === 'function') callback();
}
node.animationend = handleAnimationEnd();
}
When my button is clicked, I call showYTDLLoader() which gets an array of objects from the YouTube API that looks like this:
[
{
"video_url": "https://r7---sn-uxanug5-cox6.googlevideo.com/videoplayback?expire=1572496003&ei=Iw66Xa24H8PL3LUPiN25mAs&ip=2001%3A8003%3A749b%3Aa01%3A5cd8%3Ac610%3A6402%3Ad0fe&id=o-ADsVnoOoBQ6-SWzYZU7gHES06s7xQptJG6hn9WcakITY&itag=22&source=youtube&requiressl=yes&mm=31%2C29&mn=sn-uxanug5-cox6%2Csn-ntqe6n7r&ms=au%2Crdu&mv=m&mvi=6&pl=39&initcwndbps=1655000&mime=video%2Fmp4&ratebypass=yes&dur=917.768&lmt=1572418007364260&mt=1572474311&fvip=4&fexp=23842630&c=WEB&txp=5535432&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cmime%2Cratebypass%2Cdur%2Clmt&sig=ALgxI2wwRgIhAIp-4gyUTLoXFetbY0ha_YnR7DJqsp_MNjjIxqDdfPZJAiEA_WPd21jgX9broBcigf8rcSEVoJb2_NX7t3XZQqytsSM%3D&lsparams=mm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AHylml4wRAIgacvP3zjEq-rVEZFrX7a_hC6TR-Zab7Ii-Fbaupjs_PcCIHdZht4l4ioYL3ERz7WNiSbnOnhm5iYxEECaQXPP2hUp",
"video_title": "Arnold Schwarzenegger on Son-in-law Chris Pratt, Pranking Sylvester Stallone & Terminator’s Return",
"fmt_id": "22",
"fmt_quality": "1280x720"
},
{
"video_url": "https://r7---sn-uxanug5-cox6.googlevideo.com/videoplayback?expire=1572496003&ei=Iw66Xa24H8PL3LUPiN25mAs&ip=2001%3A8003%3A749b%3Aa01%3A5cd8%3Ac610%3A6402%3Ad0fe&id=o-ADsVnoOoBQ6-SWzYZU7gHES06s7xQptJG6hn9WcakITY&itag=18&source=youtube&requiressl=yes&mm=31%2C29&mn=sn-uxanug5-cox6%2Csn-ntqe6n7r&ms=au%2Crdu&mv=m&mvi=6&pl=39&initcwndbps=1655000&mime=video%2Fmp4&gir=yes&clen=44248820&ratebypass=yes&dur=917.768&lmt=1572416976690256&mt=1572474311&fvip=4&fexp=23842630&c=WEB&txp=5531432&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cmime%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&sig=ALgxI2wwRQIhANTZJlBHFWQWCnfK11yvLiPUV26c6NzvqIMKjDwmsByMAiBUSy0ZJMo4GdHSiRU4xBDDLxLtzwKZAqAKCiB-1aViDQ%3D%3D&lsparams=mm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AHylml4wRAIgacvP3zjEq-rVEZFrX7a_hC6TR-Zab7Ii-Fbaupjs_PcCIHdZht4l4ioYL3ERz7WNiSbnOnhm5iYxEECaQXPP2hUp",
"video_title": "Arnold Schwarzenegger on Son-in-law Chris Pratt, Pranking Sylvester Stallone & Terminator’s Return",
"fmt_id": "18",
"fmt_quality": "640x360"
}
]
But when I try and loop through each entry with fillInOptions(), my loop is never completed because the length is apparently zero. However, when I dump the array using console.log() it tells me the length is 2, and displays the above. I need to be able to add each option to my dropdown.
Thankyou!
UPDATE: Added full code, sorry!
It looks like your .forEach() is the root of the problem. The parameters of a forEach are currentValue, index like this: array.forEach(function(currentValue, index) {}); but it looks like you're using them in the opposite way
Try rewriting that iteration to this:
ytFill.forEach(function(v, i) {
console.log(i);
ytdlOptions.append(new Option(v.fmt_quality, v.fmt_id));
});
Notice the difference in the order of v and i in the parameters.

Regex to change a html element class with javascript not working

I have the following javascript function to open and close sub list elements on an onclick event:
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
if (subMen.className == "nav nav-second-level collapse in") {
subMen.className = "nav nav-second-level collapse";
} else {
subMen.className += " in";
}
}
}
The "collapse" is a css class which makes display=none hiding the sub list and "in" is a class which makes display=block showing the sub list, creating a menu with submenus.
I found in this question Change an element's class with JavaScript in the first(accepted) answer use of a regex in order to do this. I tried it like this:
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
if (subMen.className.match(/(?:^|\s)in(?!\S)/)) {
subMen.className.replace(/(?:^|\s)in(?!\S)/g, '');
} else {
subMen.className += " in";
}
}
}
The code without the regex works perfectly but with the regex it doesn't. I checked the regex in regex101.com and it seems to work there. As I understand it's more appropriate to use the regex than a long string of all the class names and also I also have a nav-third-level class that I have to close and open so the regex seems to be the convenient and proper way to do it.
What's wrong?
Thank you.
No need of regex here. You can use classList
Using classList is a convenient alternative to accessing an element's list of classes as a space-delimited string via element.className.
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
subMen.classList.toggle('in');
}
}
toggle() will toggle the class of the element. If the element already has the class, it'll remove it, if not then toggle will add the class to the element.
Check the Browser Compatibility.
You can use following SHIM from MDN for IE9,
/*
* classList.js: Cross-browser full element.classList implementation.
* 2014-07-23
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
/*global self, document, DOMException */
/*! #source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
if ("document" in self) {
// Full polyfill for browsers with no classList support
if (!("classList" in document.createElement("_"))) {
(function (view) {
"use strict";
if (!('Element' in view)) return;
var
classListProp = "classList",
protoProp = "prototype",
elemCtrProto = view.Element[protoProp],
objCtr = Object,
strTrim = String[protoProp].trim || function () {
return this.replace(/^\s+|\s+$/g, "");
},
arrIndexOf = Array[protoProp].indexOf || function (item) {
var
i = 0,
len = this.length;
for (; i < len; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
}
// Vendors: please allow content code to instantiate DOMExceptions
,
DOMEx = function (type, message) {
this.name = type;
this.code = DOMException[type];
this.message = message;
},
checkTokenAndGetIndex = function (classList, token) {
if (token === "") {
throw new DOMEx(
"SYNTAX_ERR", "An invalid or illegal string was specified"
);
}
if (/\s/.test(token)) {
throw new DOMEx(
"INVALID_CHARACTER_ERR", "String contains an invalid character"
);
}
return arrIndexOf.call(classList, token);
},
ClassList = function (elem) {
var
trimmedClasses = strTrim.call(elem.getAttribute("class") || ""),
classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [],
i = 0,
len = classes.length;
for (; i < len; i++) {
this.push(classes[i]);
}
this._updateClassName = function () {
elem.setAttribute("class", this.toString());
};
},
classListProto = ClassList[protoProp] = [],
classListGetter = function () {
return new ClassList(this);
};
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function (i) {
return this[i] || null;
};
classListProto.contains = function (token) {
token += "";
return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function () {
var
tokens = arguments,
i = 0,
l = tokens.length,
token, updated = false;
do {
token = tokens[i] + "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token);
updated = true;
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.remove = function () {
var
tokens = arguments,
i = 0,
l = tokens.length,
token, updated = false,
index;
do {
token = tokens[i] + "";
index = checkTokenAndGetIndex(this, token);
while (index !== -1) {
this.splice(index, 1);
updated = true;
index = checkTokenAndGetIndex(this, token);
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.toggle = function (token, force) {
token += "";
var
result = this.contains(token),
method = result ?
force !== true && "remove" :
force !== false && "add";
if (method) {
this[method](token);
}
if (force === true || force === false) {
return force;
} else {
return !result;
}
};
classListProto.toString = function () {
return this.join(" ");
};
if (objCtr.defineProperty) {
var classListPropDesc = {
get: classListGetter,
enumerable: true,
configurable: true
};
try {
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
} catch (ex) { // IE 8 doesn't support enumerable:true
if (ex.number === -0x7FF5EC54) {
classListPropDesc.enumerable = false;
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
}
}
} else if (objCtr[protoProp].__defineGetter__) {
elemCtrProto.__defineGetter__(classListProp, classListGetter);
}
}(self));
} else {
// There is full or partial native classList support, so just check if we need
// to normalize the add/remove and toggle APIs.
(function () {
"use strict";
var testElement = document.createElement("_");
testElement.classList.add("c1", "c2");
// Polyfill for IE 10/11 and Firefox <26, where classList.add and
// classList.remove exist but support only one argument at a time.
if (!testElement.classList.contains("c2")) {
var createMethod = function (method) {
var original = DOMTokenList.prototype[method];
DOMTokenList.prototype[method] = function (token) {
var i, len = arguments.length;
for (i = 0; i < len; i++) {
token = arguments[i];
original.call(this, token);
}
};
};
createMethod('add');
createMethod('remove');
}
testElement.classList.toggle("c3", false);
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
// support the second argument.
if (testElement.classList.contains("c3")) {
var _toggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function (token, force) {
if (1 in arguments && !this.contains(token) === !force) {
return force;
} else {
return _toggle.call(this, token);
}
};
}
testElement = null;
}());
}
}
If you're using jQuery, you can use toggleClass():
function ShowHideDtls(itId) {
$('#' + itId).toggleClass('in');
}
Edit
If you still want to use regex:
if (/\bin\b/.test(subMen.className))
subMen.className.replace(/\bin\b/, '');
} else {
subMen.className += " in";
}
You can also use split() and indexOf as follow to check if a class is present on element.
var classes = className.split(/\s+/),
classIndex = classes.indexOf('in');
if (classIndex > -1) {
classes.splice(classIndex, 1);
subMen.className = classes.join(' ');
} else {
subMen.className += " in";
}
replace function returns the resultant value, it do not assign value indirectly.
So do following:
function ShowHideDtls(itId) {
var subMen = document.getElementById(itId);
if (subMen != null) {
if (subMen.className.match(/(?:^|\s)in(?!\S)/)) {
subMen.className = subMen.className.replace(/(?:^|\s)in(?!\S)/g, '');
}
else {
subMen.className += " in";
}
}
}

Doubts about NodeJS Module Development

I'm trying to code my very first NodeJS module, but I'm having trouble grasping some concepts.
Here's the code that I currently have (a genexer counter/generator):
"use strict";
var ret = require('ret');
module.exports = function (regex) {
if (Object.prototype.toString.call(regex) === '[object RegExp]') {
regex = regex.source;
}
else if (typeof regex !== 'string') {
regex = String(regex);
}
try {
var tokens = ret(regex);
}
catch (exception) {
return false;
}
return {
charset: '',
reference: [],
count: function (token) {
var result = 0;
if ((token.type === ret.types.ROOT) || (token.type === ret.types.GROUP)) {
if (token.hasOwnProperty('stack') === true) {
result = 1;
token.stack.forEach(function (node) {
result *= count(node);
});
}
else if (token.hasOwnProperty('options') === true) {
var options = [];
token.options.forEach(function (stack, i) {
options[i] = 1;
stack.forEach(function (node) {
options[i] *= count(node);
});
});
options.forEach(function (option) {
result += option;
});
}
if ((token.type === ret.types.GROUP) && (token.remember === true)) {
reference.push(token);
}
}
else if (token.type === ret.types.POSITION) {
}
else if (token.type === ret.types.SET) {
token.set.forEach(function (node) {
if (token.not === true) {
if ((node.type === ret.types.CHAR) && (node.value === 10)) {
}
}
result += count(node);
});
}
else if (token.type === ret.types.RANGE) {
result = (token.to - token.from + 1);
}
else if (token.type === ret.types.REPETITION) {
if (isFinite(token.max) !== true) {
return Infinity;
}
token.value = count(token.value);
for (var i = token.min; i <= token.max; ++i) {
result += Math.pow(token.value, i);
}
}
else if (token.type === ret.types.REFERENCE) {
if (reference.hasOwnProperty(token.value - 1) === true) {
result = 1;
}
}
else if (token.type === ret.types.CHAR) {
result = 1;
}
return result;
}(tokens),
generate: function () {
return false;
},
};
};
Questions:
am I calling count correctly on my first iteration? count: function (token) {}(tokens)?
how can I recursively call the count method? I get a "ReferenceError: count is not defined"
is this the correct (or best-practice) approach of defining a small module with several methods?
Forgive me for not posting 3 different questions, but I'm not very familiar with all the terminology yet.
The convention for immediately invoked closures is count: (function(args) {return function() {}})(args) but your way will also work in all environments.
You can't because count is a closure unfortunately - see 3.
If you want to use methods on your module inside your module I would declare the module outside of the return statement. If you want a good example of this see underscore/lodash source code.
So you can define your module using a declaration like the skeleton below
module.exports = function (regex) {
//...
var count = function(tokens) {
//...
return function() {
//...
var ret *= count(node);
return ret;
}
}
var mymod = {
count: count(tokens)
//...
};
//...
return mymod;
};

Add space between numbers/digits and letters/characters

I have a code like this
(function($, window, document, undefined) {
$.fn.quicksearch = function (target, opt) {
var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({
delay: 100,
selector: null,
stripeRows: null,
loader: null,
noResults: '',
bind: 'keyup',
onBefore: function () {
return;
},
onAfter: function () {
return;
},
show: function () {
this.style.display = "";
},
hide: function () {
this.style.display = "none";
},
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
testQuery: function (query, txt, _row) {
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
}, opt);
this.go = function () {
var i = 0,
noresults = true,
query = options.prepareQuery(val),
val_empty = (val.replace(' ', '').length === 0);
for (var i = 0, len = rowcache.length; i < len; i++) {
if (val_empty || options.testQuery(query, cache[i], rowcache[i])) {
options.show.apply(rowcache[i]);
noresults = false;
} else {
options.hide.apply(rowcache[i]);
}
}
if (noresults) {
this.results(false);
} else {
this.results(true);
this.stripe();
}
this.loader(false);
options.onAfter();
return this;
};
this.stripe = function () {
if (typeof options.stripeRows === "object" && options.stripeRows !== null)
{
var joined = options.stripeRows.join(' ');
var stripeRows_length = options.stripeRows.length;
jq_results.not(':hidden').each(function (i) {
$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
});
}
return this;
};
this.strip_html = function (input) {
var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
output = $.trim(output.toLowerCase());
return output;
};
this.results = function (bool) {
if (typeof options.noResults === "string" && options.noResults !== "") {
if (bool) {
$(options.noResults).hide();
} else {
$(options.noResults).show();
}
}
return this;
};
this.loader = function (bool) {
if (typeof options.loader === "string" && options.loader !== "") {
(bool) ? $(options.loader).show() : $(options.loader).hide();
}
return this;
};
this.cache = function () {
jq_results = $(target);
if (typeof options.noResults === "string" && options.noResults !== "") {
jq_results = jq_results.not(options.noResults);
}
var t = (typeof options.selector === "string") ? jq_results.find(options.selector) : $(target).not(options.noResults);
cache = t.map(function () {
return e.strip_html(this.innerHTML);
});
rowcache = jq_results.map(function () {
return this;
});
return this.go();
};
this.trigger = function () {
this.loader(true);
options.onBefore();
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
e.go();
}, options.delay);
return this;
};
this.cache();
this.results(true);
this.stripe();
this.loader(false);
return this.each(function () {
$(this).bind(options.bind, function () {
val = $(this).val();
e.trigger();
});
});
};
}(jQuery, this, document));
I try to figure out where and how I can make a split/add space between numbers and letters. Cause some people type for example "ip1500" and the script cant match the input with an element that is like "ip 1500". My problem ist that Im a js beginner.
I was trying and trying but i cant get it work. I also tried this
I found this spot and I think it can be done here where the everything get splitted by an " " (space):
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
Would be very nice if somebody can help me.
If you want "123abc345def" to "123 abc 345 def". The replace function may help. The code is like this.
var str = "123abc345def";
str = str.replace(/(\d+)/g, function (_, num){
console.log(num);
return ' ' + num + ' ';
});
str = str.trim();
The code you linked didn't work mainly because it's using a different programming language to javascript. In theory, it should work, but javascript does not support regular expression lookbehinds (at this present time)..
Instead, I have re-wrote that fragment of code:
prepareQuery: function (val) {
function isNotLetter(a){
return (/[0-9-_ ]/.test(a));
}
var val=val.toLowerCase().split("");
var tempArray=val.join("").split("");
var currentIndex=1;
for (var i=0;i<val.length-1;i++){
if (isNotLetter(val[i]) !== isNotLetter(val[i+1])){
tempArray.splice(i+currentIndex, 0, " ");
currentIndex++;
}
}
return tempArray.join("");
}
Since you're new to javascript, I'm going to explain what it does.
It declares a function in prepareQuery to check whether or not a string contains a letter [this can be moved somewhere else]
It then splits val into an array and copies the content of val into tempArray
An index is declared (explained later)
A loop is made, which goes through every single character in val
The if statement detects whether or not the current character (val[i] as set by the loop) is the same as the character next to it (val[i+1]).
IF either one are different to the other (ie the current character is a letter while the next isn't) then a space is added to the tempArray at that "index"
The index is incremented and used as an offset in #6
The loop finishes, joins the "array" into a string and outputs the result.
DEMO:
http://jsbin.com/ebitus/1/edit
(JSFiddle was down....)
EDIT:
Sorry, but I completely misinterpreted your question... You failed to mention that you were using "quicksearch" and jQuery. In that case I'm assuming that you have a list of elements that have names and you want to search through them with the plugin...
A much easier way to match the user's query (if there is no space) is to strip the space from the search table along with the query itself - though original reverse method will work (just not as efficiently) [aka: expanding the user's query]
In this case, stripping the space from both the search table and user input would be a better method
prepareQuery: function (val) {
return val.toLowerCase().replace(/ /ig,'').split(" ");
},
testQuery: function (query, txt, _row) {
txt=txt.toLowerCase().replace(/ /ig,'');
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
DEMO:
http://jsfiddle.net/q9k9Y/3/
Edit 2:
It seems like your real intent is to create a fully functioning search feature on your website, not to just add spaces between letters and numbers. With this, I suggest using Quicksilver. I would love to work out an algorithm to extend quickSearcher but at the current time I cannot (timezones). Instead, I suggest using Quicksilver
http://jsbin.com/oruhet/12/

PhantomJS -- Scraping data feed -- Some AJAX content not loading, even with generous setTimeout

I have a generic PhantomJS setup for scraping feeds (we are doing this with owner's permission, for a client) -- you give it a URL, jQuery/javascript code for turning the page, and a selector to select links from the feed.
This feed seems to be loading everything fine except for the buttons to turn the page [see images].
Rendered by PhantomJS:
Rendered by Chrome on my computer:
I have been stumped for over a day.
Any help much appreciated.
My code:
var page = new WebPage({
settings: {
loadPlugins: true,
userAgent : "Mozilla/5.0 (X11; Linux i686) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5",
XSSAuditingEnabled: false,
webSecurityEnabled: false
},
viewportSize: { width: 1366, height: 768 }
}),
output_phantom = {errors: [], results: null};
var turn_page_jquery = "$('#yui-pg0-0-next-link').click_link()",
url_selector_jquery = "$('h3 a').multiAttr('href')",
url = "http://www.springcjd.com/new/search?dpt=2#QryString=%3FlogSearch%3Dfalse%26sortCol%3Dnull%26sortType%3Dnull%26VIN%3Dnull%26dealerStockID%3Dnull%26stockType%3D2%26year%3Dnull%26make%3Dnull%26model%3Dnull%26subModel%3Dnull%26body%3Dnull%26minMileage%3Dnull%26maxMileage%3Dnull%26numOfDoors%3Dnull%26certified%3Dnull%26minDFList%3Dnull%26maxDFList%3Dnull%26onLotAfter%3Dnull%26onLotBefore%3Dnull%26pageNum%3D1%26carsPerPage%3D30%26fullText%3Dnull%26%26mpghmn%3Dnull%26%26lotID%3Dnull%26daysOnLotMin%3Dnull%26%26output%3Djson%22";
// Start things going here
get_links_from_pages(url, url_selector_jquery, turn_page_jquery);
// Allows you to pass args to function in page
function evaluate(page, func) {
var args = [].slice.call(arguments, 2);
var fn = "function() { return (" + func.toString() + ").apply(this, " + JSON.stringify(args) + ");}";
return page.evaluate(fn);
}
page.onError = function (msg, trace) {
console.log(msg);
trace.forEach(function(item) {
console.log(' ', item.file, ':', item.line);
});
};
// Communicator between phantomJS and the page.
page.onConsoleMessage = function(msg)
{
var msg_json = JSON.parse(msg);
if(msg_json && msg_json.type)
{
var type = msg_json.type;
var message = msg_json.message;
switch(type)
{
case 'return_value':
output_phantom.results = message;
console.log(JSON.stringify(output_phantom));
phantom.exit();
break;
case 'message':
output_phantom.errors.push(message);
break;
case 'exit':
phantom.exit();
break;
case 'render':
var photo_name = 'phantom_test.png';
if(message != '')
photo_name = message;
page.render(photo_name);
break;
}
}
else
output_phantom.errors.push(msg);
};
function inject_scripts()
{
// Inject jquery and our additional script if they don't exist
// Would like to be able to overwrite an older version of jQuery
if(page.evaluate(function () { return typeof jQuery;}) == 'undefined')
{
if(page.evaluate(function () { return typeof $;}) == 'function') {
console.log("'$' symbol already used.");
}
else
{
if (!page.injectJs("/../../jquery.js")) {
console.log("jQuery not loaded...");
phantom.exit();
}
}
}
// Inject scripts that allow communication using fn onConsoleMessage
if (!page.injectJs("/../lf_additional.js")) {
console.log("Additional scripts not loaded...");
phantom.exit();
}
}
function run_phantom(url, fn/* args here, just not seen */)
{
var extra_args = [].slice.call(arguments, 2);
page.open(url, function (status) {
// if (status !== 'success') {
// console.log('Unable to load the url! (URL: '+url+')');
// phantom.exit();
// }
// else
// {
inject_scripts();
output_phantom = {errors: [], results: null};
// run our js code inside the headless browser.
extra_args.unshift(page, fn);
evaluate.apply(this, extra_args);
// }
});
}
function get_links_from_pages(url, url_selector_jquery, turn_page_jquery)
{
var fn = function(url_selector_jquery, turn_page_jquery)
{
var results = [];
var i = 0;
var interval = setInterval(function()
{
// Photograph page right before selecting values
phantom_render('ph_scjd_'+i+'.png');
var selected_data = eval(url_selector_jquery);
//
results.push(selected_data);
// Try to turn the page
eval(turn_page_jquery);
// Get the first 4 pages
if(i >= 3) {
phantom_return(results);
clearInterval(interval);
}
i++;
}, 3000);
};
return run_phantom(url, fn, url_selector_jquery, turn_page_jquery);
}
//////////////////////////////////////////////////////////
// Other stuff
// Improved json parsing
(function() {
var parse = JSON.parse;
JSON = {
stringify: JSON.stringify,
validate: function(str) {
try {
parse(str);
return true;
} catch(err){
return err;
}
},
parse: function(str) {
try {
return parse(str);
} catch(err){
return undefined;
}
}
}
})();
The injected file 'additional.js':
/*
* These are additional scripts intended to make selection of multiple elements
* much more concise.
*/
$.fn.click_link = function() {
simulateMouseClick(this.selector);
};
$.fn.collect = function(fn) {
var values = [];
if (typeof fn == 'string') {
var prop = fn;
fn = function() { return this.attr(prop); };
}
$(this).each(function() {
var val = fn.call($(this));
values.push(val);
});
return values;
};
$.fn.multiAttr = function(attrName) {
return this.collect(attrName);
};
// .text() should be pretty close, except concatenated?
$.fn.multiHtml = function() {
var val_array = this.collect(function() { return this.html(); });
return val_array;
};
$.fn.multiVal = function() {
return this.multiAttr('value');
};
// The commented out code is much more concise, but probably less efficient
// $(arr1).not(arr2).length == 0 && $(arr2).not(arr1).length == 0
jQuery.extend({
compareArray: function (arrayA, arrayB) {
if (arrayA.length != arrayB.length) { return false; }
// sort modifies original array
// (which are passed by reference to our method!)
// so clone the arrays before sorting
var a = jQuery.extend(true, [], arrayA);
var b = jQuery.extend(true, [], arrayB);
a.sort();
b.sort();
for (var i = 0, l = a.length; i < l; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
});
function simulateMouseClick(selector) {
var targets = document.querySelectorAll(selector),
evt = document.createEvent('MouseEvents'),
i, len;
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
for ( i = 0, len = targets.length; i < len; ++i ) {
targets[i].dispatchEvent(evt);
}
}
function send_console_command(type, message)
{
var msg = {};
if(!message) message = '';
msg.message = message;
msg.type = type;
msg.validation = 'phantom_js_communicator';
console.log(JSON.stringify(msg));
}
function phantom_exit() {
send_console_command('exit');
}
function phantom_message(msg) {
send_console_command('message', msg);
}
function phantom_return(return_val) {
send_console_command('return_value', return_val);
}
function phantom_render(photo_name) {
send_console_command('render', photo_name);
}
Isn't it:
var page = require('webpage').create();
page.viewportSize = { width: 1366, height: 768 };
Untested, just reading the docs.

Categories

Resources