I am using closure compiler to create single javascript file for all my code.
I am running my code through PhantomJS.
here is my code
function process(inputParams, dataCollector) {
var webpage = require('webpage').create();
webpage.open(entityResolvedFilePath, function(status) {
var hasnodes = webpage.evaluate(function() {
var nodesInfo= (document.getElementsByTagName('requirednode').length;
if (nodesInfo) {
MathJax.Hub.Register.MessageHook("Math Processing Error",function (message) {
throw message;
});
MathJax.Hub.queue.Push(function() {
mathJaxCleaner.cleanMathJaxOutput();
window.callPhantom();
});
}
return hasMathNodes;
});
if (!hasMathTags) {
webpage.onCallback();
}
}
else {
webpage.onCallback();
}
}
});
I wanted to call cleanMathJaxOutput function inside MathJax.Hub.queue.Push.
It works locally because i am not running minified code locally.
But when i minify this code through closure compile then my code fails with and error that reference error could not find mathJaxCleaner
This might be happing becasue Phantomjs's webpage.evaluate create a different closure scope where i don't have to global variable mathJaxCleaner.
I have declared cleanMathJaxOutput like this.
var mathJaxCleaner = new Object();
mathJaxCleaner.cleanMathJaxOutput =function() {}
I have also tried to declare mathJaxCleaner as a function and then attached functions on it's prototype but none of thing worked for me.
After minification code become something like this.
var P = {
A: function() {
function a(a) {
a && a.parentNode.removeChild(a)
}
function b(a) {
if (a)
for (; 0 != a.length;) this.removeNode(a[0])
}
function d(a) {
var b = document.createElement("defs");
a.insertBefore(b, a.childNodes[0]);
a = a.getElementsByTagName("use");
for (var c = 0; c < a.length; ++c) {
var d = a[c].getAttribute("href");
b.appendChild(document.getElementById(d.substr(1)).cloneNode(!0))
}
}
for (var c = document.getElementsByClassName("MathJax_SVG"), e = 0; e < c.length; e++) {
for (var f = c[e], v = f.childNodes, w = 0; w < v.length; w++) "svg" ==
v[w].tagName && d(v[w]);
f.style.fontSize = "inherit";
"inline-block" === f.style.display && (f.style.display = "inline")
}
some more code here...
}
};
Function call in minified code look likes P.A()
but at execution time PhantomJS says Reference error Can't find variable: P
How to fix this issue.
If you use Closure Compiler with a compilation_level of ADVANCED_OPTIMIZATIONS, you have to export the symbols (var, function etc.) you want to be available for the outside world (not compiled).
Use #export before or use goog.exportSymbol(publicPath, object) after each symbol you need to export.
For that you need to include the closure library and add the following arguments to closure compiler : --generate_exports --js closure-library-path/closure/goog/base.js
I got it the solution for this problem.
In PhantomJS function inside of webpage.evaluate is not just a closure, it exists inside of another context (that of a webpage) in which all outer variables and function do not exist, but a web page's DOM is available instead.
So I have added my function explicitly in window object.
window['myfunctionName'] = myfunctionName;
function myfunctionName()
{
// do something
}
Related
I've got gulp-jasmine-phantom running, but I get a ReferenceError: Tictactoe is not defined. I have the feeling I'm making some fundamental mistake.
My file-structure:
gulpfile.js
spec/test.js
source/js/tictactoe.js
A simplified version of the code:
gulp:
gulp.task('site:js:test', function() {
return gulp.src( 'spec/test.js')
.pipe(jasmine())});
test.js:
require("../source/js/tictactoe");
describe("Tictactoe", function() {
var t = new Tictactoe();
it("expects there to be able to add a mark", function() {
t.addMark([0,0], 1);
expect(t.board[0,0]).toBe(1);
});
});
tictactoe.js
function Tictactoe(size) {
this.size = size;
// default variables:
this.EMPTY = "";
this.board = [];
for ( var i = 0; i < this.size; i++ ) {
this.board[i] = [];
for ( var j = 0; j < this.size; j++) {
this.board[i][j] = this.EMPTY;
}
}
}
Tictactoe.prototype.addMark = function(position, player)
{}
I've also tried gulp.src( ['source/js/tictactoe.js', 'spec/test.js'] ) and then no require in the test.js. Also no success.
I've also tried gulp-jasmine and gulp-jasmine-browser. Can you help me make it work?
Thanks so much in advance!!
A related question to yours has been answered already over here: How to Load a File for Testing with Jasmine Node?
In short, you will need to create a module out of your tictactoe library, export the Tictactoe function, and then require it into a local variable that is accessible by Jasmine. The Tictactoe function is currently simply scoped into the local scope of the required JavaScript file.
I got the following code which works perfectly. What it does is: in a table it highlights the corresponding table header cell and table first column cell when you hover over any table cell.
// Row & Column Highlight
(function() {
var gridCellRow = null,
gridCellCol = null,
tableElement = document.getElementsByClassName('inner_table');
for (var i = 0, len_i = tableElement.length; i < len_i; i++) {
if (tableElement[i].getElementsByClassName('row_label_cell').length > 0) {
var gridCell = tableElement[i].getElementsByClassName('input_cell');
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
function gridCellParents(currentCell) {
return gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
gridCell[j].addEventListener('mouseover', (function() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}));
gridCell[j].addEventListener('mouseout', (function() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}));
}
}
}
}());
However, JSHint tells me, that
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
function gridCellParents(currentCell) {
return gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
is not best practice "Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function."
as well as
gridCell[j].addEventListener('mouseover', (function() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}));
gridCell[j].addEventListener('mouseout', (function() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}));
}
is not best practice "Don't make functions within a loop."
So how am I correctly and according to best practice building this whole function?
Function deceleration shouldn't be within loops because it makes no
sense to re-create the same function over and over again, in a
"continuous flow" (unlike other situation where the same function
might be created again, in a more complex code). The main reason is
because of hoisting and it strongly goes against javascript
principles to write functions declarations inside loops.
A good starting point, with a more ordered code:
// Row & Column Highlight
(function() {
var gridCellRow,
gridCellCol,
gridCell,
tableElement = document.getElementsByClassName('inner_table');
function gridCellParents(currentCell) {
gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
function onMouseEnter() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}
function onMuoseLeave() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}
for (var i = 0, len_i = tableElement.length; i < len_i; i++) {
if (tableElement[i].getElementsByClassName('row_label_cell').length > 0) {
gridCell = tableElement[i].getElementsByClassName('input_cell');
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
gridCell[j].addEventListener('mouseenter', onMouseEnter);
gridCell[j].addEventListener('mouseleave', onMuoseLeave);
}
}
}}());
As you can see, I've modified your events to mousenter and mouseleave which might better suit your needs and be better for overall performance.
Update - delegated version:
// Row & Column Highlight
(function() {
var gridCell,
tableElement = document.querySelectorAll('.inner_table');
function getCellParents(cell){
return {
row : cell.parentNode.firstElementChild, // row
col : cell.parentNode.parentNode.rows[0].cells[cell.cellIndex] // col
};
}
function updateGridCellParents(cell, state) {
state = state ? 'add' : 'remove';
var parents = getCellParents(cell);
parents.row.classList[state]('highlight');
parents.col.classList[state]('highlight');
}
funciton checkTarget(target){
// make sure the element is what we expected it to be
return target.className.indexOf('input_cell') != 0;
}
function onMouseEvents(e){
checkTarget(e.target) && updateGridCellParents(e.target, e.type == "mouseover");
}
document.body.addEventListener('mouseover', onMouseEvents);
document.body.addEventListener('mouseout', onMouseEvents);
})();
In addition to the previous answer, I think it is important to also state why it is a bad practice.
The issue when creating functions inside loops is that they often use values that depends on the loop's iteration. Let's have an example.
// Create three function, that writes their number
var funcs = [];
for(var i=0; i<3; i++){
funcs.push(function(){
document.write(i);
});
}
// Call them.
funcs.forEach(function(f){
f();
});
One may expect the above code to write 1 then 2 then 3. However, because variables in JS are not block-scoped but function-scoped (except for the new let and const), the closure of all three of these functions will actually use the exact same i: 3, the last value it had been given (and thus the value it still has).
Because of this behaviour, this is very easy to make mistakes. Hence, it is not recommended.
If you need to create a function that depends of the value of a loop, you can use a factory.
// Create a factory function that returns a
// function that writes the argument.
function writerFactory(msg){
return function(){
document.write(msg);
}
}
// Create three functions, that write their number.
var funcs = [];
for(var i=0; i<3; i++){
funcs.push(writerFactory(i));
}
// Call them.
funcs.forEach(function(f){
f();
});
This time, each function has a different closure: the one that is created by each call of the factory. They all have access to a different msg.
Im new to js and its sometimes hard for me to get used to its code conventions. So i have a question, how i should declare function expression? Look at my code, is it right how i did it, or there are better practices?
function onAddButtonClick() {
var engWord = document.getElementById('engWord'),
japWord = document.getElementById('japWord'),
engVal = engWord.value,
japVal = japWord.value,
engExpr = (engVal !== ""),
japExpr = (japVal !== ""),
duplicateNum,
checkImg,
numOfWords;
duplicateNum = (function () {
var i,
pair;
for (i = 0; i < dictionary.length; i++) {
pair = dictionary[i];
if (pair.eng === engVal && pair.jap === japVal) {
return 3;
} else if (pair.jap === japVal) {
return 2;
} else if (pair.eng === engVal) {
return 1;
}
}
return 0;
}());
//remove focus from inputs
engWord.blur();
japWord.blur();
...
}
Thanks in advance.
You did fine. Using the opening ( is not syntactically required in this context, but it makes a great warning to the human reader of the code about what is going on. The convention helps.
At the end, the invoking parens () can go inside, or outside, the closing ). Doug Crawford recommends inside and many linters check for this. Despite his claims of dog balls1, it really doesn't matter.
By the way, the idea of function expression being declared and then immediately running is called an IFFE -- Immediately Invoked Function Expression
is there a way to define global namespace, so that i can call function from this namespace from all my page?
e.g
// in one file i define below code
DefineNameSpace("my.namespace.api", {
addObject: function(obj) {
// store obj into indexDB
},
readAllObject: function() {
// return array of object from indexdb
}
})
// so that in another javascript file i can do
my.namespace.api.addObject({name: "foo", desc: "bar"});
is there a way to implement "DefineNameSpace" method?
Thanks
one way to do it, which is very simple, is this:
my = {
namespace: {
api : {}
}
}
my.namespace.api.addObject = function (obj) { }
you're actually creating objects but in this way it will function as a namespace just as well :)
hm it's not the method you're implementing. But building a namespace with a method would require the function to be called before the script files are loaded where the namespace is used like that, otherwise those lines of code are called before the DefineNamespace method is called and you will run into parts of namespaces that are undefined at that point. With above solution that won't be the case, although it is not dynamic unfortunately.
building a namespace dynamically can be done in the following way:
// the root of the namespace would still be handy to have declared here
var my = {};
function defineNamespace(namespaceStr) {
var namespaceSegments = namespaceStr.split(".");
var namespaceSoFar = null;
// iterate through namespace parts
for (var i = 0; i < namespaceSegments.length; i++) {
var segment = namespaceSegments[i];
if (i == 0) {
// if namespace starts with my, use that
if (segment == "my") {
// set pointer to my
namespaceSoFar = my;
}
else {
// create new root namespace (not tested this, but think this should work)
var otherNamespace = eval(segment);
if (typeof otherNamespace == "undefined") {
eval(segment + " = {};");
}
// set pointer to created root namespace
namespaceSoFar = eval(segment);
}
}
else {
// further build the namespace
if (typeof namespaceSoFar[segment] == "undefined") {
namespaceSoFar[segment] = {};
}
// update the pointer (my -> my.namespace) for use in the next iteration
namespaceSoFar = namespaceSoFar[segment];
}
}
}
I tried debugging my code for like a few hour but I got nothing out of it. The issue is that it makes absolutely no sense on why it reports an error every time I tried to use document.forms[0][i] (i as the iterator) in the event listener but "this" satisfies the code.
//broken
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(formFields[i]);
});
}
}
};
//works
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function () {
checkNonEmpty(this);
});
}
}
};
Wouldn't "this" refer to document.forms[0][i]?... formFields references to document.forms[0]. However the exact same code (with "this" where formFields[i] is at) works just fine.
Here is the demo: http://jsfiddle.net/PbHwy/
Cranio's answer already contains the root of the matter. To get rid of this you can either include formFields[i] by using closures
var blurCallbackGenerator = function(element){
return function () {
checkNonEmpty(element);
};
};
formFields[i].onblur = blurCallbackGenerator(formFields[i]);
/* // dense version:
formFields[i].onblur = (function(element){
return function () {
checkNonEmpty(element);
};
})(formFields[i]);
*/
or simply using this.
See also:
MDN: Creating closures in loops: A common mistake
Because you define formFields in a scope outside (or better, different than) the event listener. When the event listener is called, it is called not in the addListeners function where you define formFields, but "independently", so the reference is lost and its value is undefined (but this works because it is not dependent on that scope).
The problem is that the variable i (referred to in each of your handlers) is the exact same variable in each of them, which by the time the loop has finished has value formFields.length+1 and is therefore wrong for all of them. Try this instead [note: the below used to say something VERY WRONG before I edited it -- thanks to Zeta for pointing out my mistake]:
var addListeners = function() {
var i;
var formFields = document.forms[0];
var formSubmit = formFields["submit"];
for (i = 0; i < formFields.length; i++) {
if (formFields[i] != formSubmit) {
formFields[i].onblur = (function(j) {
return (function () {
checkNonEmpty(formFields[j]);
})(i);
});
}
}
};
and you'll find it works (unless there's another bug that I haven't noticed).
If you can afford to support only Javascript 1.7 and above, you can instead write your old code but make your for look like this: for (let i=0; i<formFields.length; i++). But you quite possibly can't.