I need a little help here. I want a script that converts all the AI files in all the subfolders into PDFs and puts them in a single folder at the root and I want it to skip any folders named "Resources". I found a script that mostly does what I need it to do and I have modified it further to suit our exact needs. I'm struggling with 2 parts of it though. Specifically lines 33 and 80 are my issues now.
#target illustrator
main();
//
function main() {
var topLevel = Folder.selectDialog( 'Select the top level folder to start converting AI files to PDFX files' );
var proofFolder = new Folder( topLevel + '/Proofs2/');
proofFolder.create();
if ( topLevel != null ) {
processDocs( recursiveFolders( topLevel, /\.ai$/i ), getPDFOptions() );
};
};
//
function processDocs( aiFiles, opts ) {
var i, baseName, doc, saveFile;
for ( i = 0; i < aiFiles.length; i++ ) {
doc = app.open( aiFiles[i] );
baseName = decodeURI( doc.name.match( /(.*)\.[^\.]+$/ )[1] );
//This is line 33// saveFile = File( proofFolder.path + baseName + '.pdf' );
doc.saveAs( saveFile, opts );
doc.close( SaveOptions.DONOTSAVECHANGES );
};
};
//
function getPDFOptions() {
var pdfSaveOpts = new PDFSaveOptions();
pdfSaveOpts.acrobatLayers = true;
pdfSaveOpts.colorBars = false;
pdfSaveOpts.colorCompression = CompressionQuality.AUTOMATICJPEGHIGH;
pdfSaveOpts.compressArt = true;
pdfSaveOpts.embedICCProfile = true;
pdfSaveOpts.enablePlainText = true;
pdfSaveOpts.generateThumbnails = true;
pdfSaveOpts.optimization = true;
pdfSaveOpts.pageInformation = true;
pdfSaveOpts.preserveEditability = true;
pdfSaveOpts.pDFXStandard = PDFXStandard.PDFX1A2001;
pdfSaveOpts.viewAfterSaving = false;
return pdfSaveOpts;
};
//
function recursiveFolders( fold, exp ) {
var fileList = Array(); // Our matching files…
getFiles( fold, exp, fileList );
return fileList;
};
//
function getFiles( fold, exp, array ) {
//This is line 80// if (Folder.name !== /\Resources$/){
var i, temp;
temp = Folder( fold ).getFiles(); // All files and folders…
for ( i = 0; i < temp.length; i++ ) {
if ( temp[i] instanceof File && RegExp( exp ).test( temp[i].fsName ) ){
array.push( temp[i] );
};
if ( temp[i] instanceof Folder ) {
getFiles( temp[i].fsName, exp, array );
};
};
return array;
};
};
Line 33:
Variables in Javascript are local to the function they are defined in. That is, if you try
a();
function a()
{
var a1 = 1;
b();
}
function b()
{
alert ("var a1 = "+a1);
}
you will find a1 is undefined inside function b. Two common solutions (there may be more) are to put the variable in the function definition:
b(a1);
..
function b (myVarArg)
{
alert ("var a1 = "+myVarArg);
}
or -- simple but slightly error-prone -- declare the variable at the very top of your program before main:
var a1;
This is a quick and simple solution; the variable will be 'visible' inside all functions, and assigning a new value anywhere will work. It is 'slightly error-prone' because a local variable with the same name (defined inside a function) will effectively 'hide' the original. Thus, if used careless, you could end up with a construction such as this:
var a1 = 1;
a();
alert ("var a1 = "+a1);
function a()
{
var a1 = 2;
b();
alert ("var a1 = "+a1);
}
function b()
{
alert ("var a1 = "+a1);
}
Fortunately, your use of clear, descriptive variable names will help you here.
Line 80
if (Folder.name !== /\Resources$/) ...
Improper use of proper Javascript syntax ☺
!== is not a valid comparison operator. Use either == (Test if Equal) or === (Test if Very Equal -- see Does it matter which equals operator (== vs ===) I use in JavaScript comparisons? for some gritty details), and != for Test if Not Equal.
You can compare a string to another but not to a GREP expression. The notation /.../ is reserved for GREP expressions only, for strings you need "..." or '...'. While it's true that some functions may accept both GREP and regular strings, this test does not.
To use a GREP string in a comparison, use the String function match:
if (Folder.name.match(/\\Resources$/)) ...
Note the double end parentheses (one for the enclosing if and one for the match function) and double backslashes, because the backslash is a 'special character' inside a GREP string. (It is special inside a Javascript string as well, so whatever you intended to use, it possibly could be called Improper Syntax #4.)
Will this Fix Everything and Make It Work?
Untested. Without these corrections, your script will not work. With them, it should at least do something (if your logic is sound, your paths exist, your input is right and your Illustrator constants have the correct name).
Provisionally, I'd hazard to say it should work.
Addendum
It took some debugging to find out the real problem .. Debug strategy: inserting alert before suspicious lines, showing values of important variables. Run, wait for results, rinse, repeat.
First off: passing on array as a function parameter seems to have been a Not-Good idea. I found that the array got copied over and over onto itself (or perhaps it was just the result of my tinkering). I think the safer way is to have getFiles return only the newly-added files, and concatenate the result to its current version (i.e., "in" the routine from whence you called getFiles, where it doesn't matter if it was a previous incarnation of getFiles or not). See (I think!) my warning on modifying 'local', 'global', and/or 'passed' variables above.
function getMyFiles( fold, exp ) {
//This is line 80//
if (!fold.name.match (/(\\|\/)Resources$/)) {
var i, temp;
var array = [];
temp = Folder( fold ).getFiles(); // All files and folders…
for ( i = 0; i < temp.length; i++ ) {
if ( temp[i] instanceof File && RegExp( exp ).test( temp[i].fsName ) ){
// alert ('path '+fold+', adding '+temp[i]);
array.push( temp[i] );
};
if ( temp[i] instanceof Folder ) {
array = array.concat (getFiles( temp[i], exp ));
};
};
// alert ('array is '+array.length+'\n'+array.join('\n'));
return array;
};
return [];
};
You would call this function as before in your original recursiveFolders function, but this time don't pass the array but assign it:
function recursiveFolders( fold, exp )
{
var fileList = Array(); // Our matching files…
fileList = getFiles( fold, exp );
// alert ('Filelist is '+fileList.length+'\n'+fileList.join ('\n'));
return fileList;
};
Only when I got that part working, I got the same error you did, per your comment "fold.match is not a function". That took a minute or so of head-scratching. As it turns out, the Javascript parser was right. Initially, the fold argument is a Folder and to get its name, you need fold.name. But look in your own code: you used in the recursive call to itself the argument temp[i].fsName, which is a String! Hence, no name, hence, error.
First, I bluntly converted fold to always-a-String using fold.toString (which works on Folder and String alike), but then I changed my mind and used fold.name for consistency, and call getFiles with the "proper" argument: temp[i] instead of temp[i].fsName. Now it seems to work -- for real. (Said with slightly more confidence.)
Related
is there a way to find all variables with a specific value in JS?
I know there is a variable holding value integer 374646 but I can not find it in sources (large codebase).
I think one can implement a solution by recursively checking all variables returned by this but I am not a JS developer
update:
the original question was confusing I think.
is there any way that I can find variables by values in a browser debugger (Chrome/Firefox)?
I think one can implement a solution by recursively checking all variables returned by this but I am not a JS developer
No, you can't. Properties are not variables (except in one special case: a certain class of global variables are properties of the global object).
Unless your variable is a global, and it's the kind of global that's available as a property on the global object, you can't find it. There is no way to get a list of other kinds of variables.
For example, there is no way for code at global scope to find a in the following:
const handle = (() => {
const a = 374646;
return setInterval(() => {
console.log(a);
}, 500);
})();
setTimeout(() => clearInterval(handle), 4000);
Please note that it's dangerous and do not access the sites, that you don't know with that approach!!! All your data may get stolen, your girlfriend may get r#$%ped, your grandma may die, your dog may bite you in the crotch, all the glaciers in the world may melt, you can get herpes or even start a global nuclear war! Use at own risk! A very high risk if you don't know what running a browser without cross-domain policies means!
Run your browser with cross-domain policies disabled (for example Chromium / Chrome with --disable-web-security param, note : Kill all chrome instances before running command or it won't work), then in the console copy-paste this function:
function globalSearch(startObject, value) {
var stack = [[startObject,'']];
var searched = [];
var found = false;
var isArray = function(test) {
return Object.prototype.toString.call( test ) === '[object Array]';
}
while(stack.length) {
var fromStack = stack.pop();
var obj = fromStack[0];
var address = fromStack[1];
if( typeof obj == typeof value && obj == value) {
var found = address;
break;
}else if(typeof obj == "object" && searched.indexOf(obj) == -1){
if ( isArray(obj) ) {
var prefix = '[';
var postfix = ']';
}else {
var prefix = '.';
var postfix = '';
}
for( i in obj ) {
stack.push( [ obj[i], address + prefix + i + postfix ] );
}
searched.push(obj);
}
}
return found == '' ? true : found;
}
After that you can search variable by values with:
globalSearch(window,value);
Once again! Remember that it's dangerous and do not access the sites, that you don't know with that approach!!! Disclaimer: If something will happen, you didn't even see that post, I didn't write it! I don't know what StackOverflow is! Never been here.
I have a small script that looks like this:
hideElements = arguments.shift().split ( ',' );
for ( iterator in hideElements ) {
console.log (' --> hiding ' + hideElements[iterator] );
lg_transitions ( {kind:"slide-up"} , { target : hideElements[iterator] } );
}
When I run it in the debugger all things start quite rationally. I put a breakpoint at the first line listed above. After pressing the "step over next function call" button to initialise the "hideElements" variable to the following:
This is what is what I would have expected but then after completing the first (and should be the only) iteration it comes back to the head of the loop and the "iterator" which had started at 0 has now strangely changed to "remove". Huh? No idea where that came from. But in the console.log message that follows there might be a hint ... it prints the following to the console:
This is a function called -- you guessed it -- "remove". It is a function that I added recently for a different reason but it is not called directly or indirectly and so I'm at a loss as to why this would be picked up here. For anyone interested in the full code for "remove", here it is:
// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
}
ADDITION:
The code I had neglected to add earlier was the initialisation of the arguments array. Here's what I had (note I've since changed the name to "args" instead of arguments):
function ConditionalFormatting ( key , eventObject , setOfRules ) {
console.log ("Entering conditional formatting: key is " + key + ", eventObject is " + eventObject.attr('id') + ", setOfRules is " + setOfRules );
var ruleStrings = [];
ruleStrings = setOfRules.split (';');
var targetOverride = false;
jQuery.each ( ruleStrings , function ( i , ruleString ) {
// There is a rule, now let's find out which one
var targetElement;
var args = [];
args = ruleString.split('::');
var rule = args.shift();
#Yoshi is right: for in will list all fields in the object - which is what the array actually is.
Try using the hasOwnProperty method:
hideElements = arguments.shift().split ( ',' );
for (iterator in hideElements ) {
if (hideElements.hasOwnProperty(iterator))
{
console.log (' --> hiding ' + hideElements[iterator] );
lg_transitions ( {kind:"slide-up"} , { target : hideElements[iterator] } );
}
}
This is why you should usually avoid using for in to iterate over arrays and use a normal for loop instead. jsHint/jsLint will give you a message like this for the code you posted:
The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
Sidenote:
for (iterator in hideElements ) will create a global variable iterator, while for (var iterator in hideElements ) won't.
The arguments object is not an actual array. Therefore it doesn't have the shift() function. If you want the first object in the array, obtain the element at index 0 (first object). Moreover, use a regular for loop to traverse the arguments object.
hideElements = arguments[0].split(',');
for ( var i = 0; i < hideElements.length; i++ ) {
...
}
arguments is not a real array, however, if you wish to use it like it were an array, then use Array.prototype.slice.call( arguments );:
if ( arguments.length > 0)
hideElements = Array.prototype.slice.call( arguments ).shift();
From there on you can use hideElements as an array.
I've got a function that wants to access a global variable, the name of which arrives as a string argument. This is how it looks now, using eval:
function echoVar(whichone){
document.write(eval(whichone));
}
I thought I'd just use the window[] syntax and have this:
function echoVar(whichone) {
document.write(window[whichone]);
}
If I create a var and call it like this, it doc writes ABC as expected:
var abc = "ABC";
echoVar("abc");
If the var I want to access is an array element though, it doesn't work:
var def = ["DEF"];
echoVar("def[0]"); //fails with undefined
Obviously that's actually executing window[def[0]] which rightly gives undefined (because there's no variable called DEF). What I actually want to happen is that it executes window["def"][0].
The only way I know to achieve this, is to do a split on the whichone parameter with "[" as the delimiter and then use the split [0] as the window index and a parseInt on split [1] to get the index, like this:
function echoVar(whichone){
if(whichone.indexOf("[")==-1){
document.write(window[whichone]);
}
else{
var s = whichone.split("[");
var nam = s[0];
var idx = parseInt(s[1]);
document.write( window[nam][idx] );
}
}
Am I overlooking something obvious? I'd rather keep the eval than have to do all that.
If you dislike using eval in your code, you can always do this:
function echoVar(whichone) {
document.write(Function("return " + whichone)());
}
Unless this is some sick experiment, you should never be writing Javascript code that looks like this. This is terrible; you need to re-think your design. I know this isn't what you're looking for, but it's the right answer.
The fact is you've got a piece of a javscript expression in a string so you either have to parse it yourself or use eval to parse it for you unless you change the way it's passed like this:
function echoVar(a,b) {
var x = window[a];
if (b) {
x = x[b];
}
document.write(x);
}
And, then you can pass it differently like this:
var def = ["DEF"];
echoVar("def", 0); // def[0]
You could even make this support multiple dimensions if you needed to.
function echoVar(a) {
var x = window[a];
for (var i = 1; i < arguments.length; i++) {
x = x[arguments[i]];
}
document.write(x);
}
var def = {myObject: {length: 3}}
echoVar("def", "myObject", "length"); // def["myObject"]["length"] or def.myObject.length
You can see it work here: http://jsfiddle.net/jfriend00/dANwq/
It would be simpler to lose the brackets and call the item with dot notation
function reval(s, O){
s= String(s);
O= O || window;
var N= s.split(".");
while(O && N.length) O= O[N.shift()];
return O || s;
}
window.def= ['definition'];
alert(reval('def.0'))
/* returned value: (String)
definition
*/
I've got a bunch of parameters being passed to a page by URL variables. The URL looks sort of like:
file.aspx?category[]=1&category[]=7&category[]=3&id=8az
Using the jQuery getUrlParam extension I can get url variables very easily, but rather than returning category as an array (which is what I want) it gets returned as null.
Is there a way for me to read these into a javascript array?
I previously pointed to this question: Get QueryString values with jQuery - but as #Crescent Fresh pointed out, those examples don't deal with arrays in the query string (and besides, they're a bit slow I think.
So I cooked up my version of this function:
function getQueryString () {
var ret = {};
var parts = (document.location.toString().split('?')[1]).split('&');
for (var i = 0; i < parts.length; i++) {
var p = parts[i].split('=');
// so strings will be correctly parsed:
p[1] = decodeURIComponent(p[1].replace(/\+/g, " "));
if (p[0].search(/\[\]/) >= 0) { // then it's an array
p[0] = p[0].replace('[]','');
if (typeof ret[p[0]] != 'object') ret[p[0]] = [];
ret[p[0]].push(p[1]);
} else {
ret[p[0]] = p[1];
}
}
return ret;
}
But there are caveats. It will only work on a correctly formed query string - there's no error detection. Also, it does not work on numbered/indexed arrays.. that is when your array is defined in the query string as:
?category[3]=1&category[4]=7&category[20]=3&id=8az
It would be trivial to add to the .search() query a regex for finding that as well, but I'm not the best regex expert... anybody got ideas?
Shouldn't it be: file.aspx?category=1&category=7&category=3
I just saw a video of Nicholas Zakas of Yahoo, at GoogleTalks talking about speeding up your website. One of the things he mentioned was doing loops in reverse order to skip one of two comparisons: for (i = len; i--;) {}
And he said to keep away from JS libraries implementations of for each. Just for fun I thought I'd try it out. Turns out he was wrong.
var array1 = new Array();
var array2 = new Array();
var start = 0;
var finished = 0;
start = (new Date).getTime();
$("#newDivTest").children().each(function(i){
array1[i] = $(this).get(0).id;
});
finished = (new Date).getTime() - start;
alert(finished);
start = (new Date).getTime();
var len = $("#newDivTest").children().length;
for (i = len; i--;) {
array2[i] = $(this).get(0).id;
}
finished = (new Date).getTime() - start;
alert(finished);
newDivTest holds 1000 empty divs with an id starting at "0" and going up to "999". Another note is that $(this).get(0).id is about 3 times faster than $(this).attr("id") for some reason, anyone know why?
For FF3.5, the results are "7" and "45", IE7 gives "30" and "45", Chrome2 gives "4" and "17", Opera10 gives "16" and "16", and lastly Safari4 gives "4" and "16".
So it seems the approach Nicholas is hardest against is actually the faster in almost all instances.
I'm not smart enough to know what's going on behind the scenes for jQuery's each()-method, but it must be doing something right...right?
One flaw in your setup is that your second test will not actually work. You wrote:
for (i = len; i--;) {
array2[i] = $(this).get(0).id;
}
But this is not defined there, so the entire operation will fail. You'd have to do something like:
var children = $("#newDivTest").children();
for (i = children.length; i--;) {
array2[i] = children.get(i).id;
}
And this gets at a more pressing issue than performance: although calls to something like jQuery's .each() function do result in added function calls (and the associated added overhead), they also tend to make it much easier to express what you want the code to do.
Quoting Michael Jackson: "The First Rule of Program Optimization: Don't do it. The Second Rule of Program Optimization (for experts only!): Don't do it yet."
Aren't your tests doing different things?
In the second test this is not the same as the first one.
Slightly off topic and not a direct answer to your main question but, jQuery's each method is implemented like so (jQuery 1.3.2)
jQuery.extend({
/* ... Code taken out for brevity ... */
// args is for internal usage only
each: function( object, callback, args ) {
var name, i = 0, length = object.length;
if ( args ) {
if ( length === undefined ) {
for ( name in object )
if ( callback.apply( object[ name ], args ) === false )
break;
} else
for ( ; i < length; )
if ( callback.apply( object[ i++ ], args ) === false )
break;
// A special, fast, case for the most common use of each
} else {
if ( length === undefined ) {
for ( name in object )
if ( callback.call( object[ name ], name, object[ name ] ) === false )
break;
} else
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
}
return object;
}
/* ... Code taken out for brevity ... */
);
as you can see, a callback function is applied to each property of object. the jQuery object has a length property defined so will perform the following loop (generally, no args are supplied)
for ( var value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
in each iteration, the callback function will increase the scope chain length by 1, thus will take longer to resolve the reference to the object's property.
I notice that your question is "JS looping and populating array. Which is faster?", but your examples are actually testing the speed of various selectors of JQuery, right? You might be interested in checking out :http://mootools.net/slickspeed/
As for "JS looping and populating array. Which is faster?", see here : http://blogs.oracle.com/greimer/resource/loop-test.html