Javascript: How to loop through files - javascript

I am making an Illustrator CS6 Javascript that does the following:
Open a folder of Illustrator files
Open each file in the folder (these files are called the source files)
Select all the contents of the source file
Copy the contents of the source file
Create a new target file Paste these contents into the target file as a new layer
Ensure the new layer has the same name as the old source file
My script works except, it doesn't loop through the files in the source folder correctly. Instead, it runs fine on the first source file. But then it endlessly just pastes the second source file in the destination document (I.e. it doesn't move onto any of the other source file). It just endlessly pastes and so I have to force quit!
How can I get it to loop through the folders properly and then move onto the next file.
Here is my code:
// JavaScript Document
//Set up vairaibles
var destDoc, sourceDoc, sourceFolder, newLayer;
// Select the source folder.
sourceFolder = Folder.selectDialog('Select the folder with Illustrator files that you want to mere into one', '~');
destDoc = app.documents.add();
// If a valid folder is selected
if (sourceFolder != null) {
files = new Array();
// Get all files matching the pattern
files = sourceFolder.getFiles();
if (files.length > 0) {
// Get the destination to save the files
for (i = 0; i < files.length; i++) {
sourceDoc = app.open(files[i]); // returns the document object
var myLayers = sourceDoc.layers; // Select All layers in Active Document
//Go through all layers of source document and copy artwork
for (i = 0; i < myLayers.length; i++) {
myLayers[i].hasSelectedArtwork = true;
};
with(sourceDoc) {
var count = pageItems.length;
for (var i = 0; i < count; i++) {
pageItems[i].selected = true;
}
redraw();
copy();
for (var i = 0; i < count; i++) {
pageItems[i].selected = false;
}
}
//Create a new title variable that has the title of the source document
var title = sourceDoc.name;
var title = title.substring(0, title.length - 4); //(remove extension from name)
//Close the Source Document
sourceDoc.close(SaveOptions.DONOTSAVECHANGES);
//Open the Destination Document and create a new layer in it that is named after the title variation
newLayer = destDoc.layers.add();
newLayer.name = title;
//Paste into this new layer
newLayer = app.paste();
}
}
else {
alert('No matching files found');
}
}
Ps. I wasn't sure if I should post this in Code Review or Graphic Design, but I think Stack overflow is the best place to post this as it is a general question about javascript looping, so I hope this is the right place.

It appears that you are using "i" for the variable in each of your loops, giving it a range of unexpected values in other loops that also use that same variable. I would try using a separate variable for each loop. E.g. for j=0, for k=0, for l=0, etc.

Related

How to paste clipboard into a channel using a photoshop script?

I'm trying to create a image RGB or CMYK using separate greyscale documents for each channel.
My current code copy all from greyscale document and paste in a new document creating a new layer for eachone. I don't want that, I want copy from each document and paste into eachone channels new document, same as manual way, selecting each channel and clicking Edit->paste->paste especial->paste in place.
// Loop to copy each separate channel into a new merged document
for (var doc_index = 0; doc_index < 3; doc_index++ ){
//Switch documents
app.activeDocument = split_channels[0];
// copy active layer from separate channel document
app.activeDocument.selection.selectAll();
app.activeDocument.selection.copy();
// close document whithout save
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
//select activeDocument
app.activeDocument = mergedDoc;
// reference to whole channels
//var channelIndex = doc_index;
var myChannels = app.activeDocument.channels;
// the channel must be visible to paste greyscale layer
myChannels[doc_index].visible= true;
// turn off the rest of channels
for (var secondaryIndex = 0; secondaryIndex < myChannels.length; secondaryIndex++) {
if (doc_index != secondaryIndex) {
myChannels[secondaryIndex].visible= false
}
}
//paste layer for each new document channel (no works correctly)
app.activeDocument.paste();
//reset the channel count after copy and close each document
var split_channels = app.documents;
}
thanks a lot for any suggestion
When copying the layer contents, you do want the layer contents to be visible so they are captured by the selection when you copy. When pasting, however, the destination of the selection is determined on the active layer/channel. Note that the document.activeChannels member requires an array when assigning to it, even if you are only wanting a single channel.
Also, be mindful of changing the underlying array while iterating over it (aka closing documents before exiting the for-loop). In my code below, I'm specifically iterating over a reference array, instead of the actual app.documents collection, to ensure the length of my array doesn't change when a document is closed.
For the sake of brevity, the following code assumes all documents are of equal dimensions/color modes/resolutions, the three source documents are flattened, and there's at least one pixel on each opposite extremity of the canvas to ensure the size/position is the same after pasting.
const colorMode = NewDocumentMode.RGB;
const numChannels = 3;
var srcDocs = new Array();
if (app.documents.length == numChannels) {
for (var i = 0; i < app.documents.length; i++)
srcDocs.push(app.documents[i]);
} else throw new Error("Channel merging for " + colorMode + " requires " + numChannels + " source documents.");
var doc = app.activeDocument;
var destDoc = app.documents.add(doc.width, doc.height, doc.resolution, "mergedDoc", colorMode);
var destChannels = new Array();
for (var i = 0; i < numChannels; i++)
destChannels.push(destDoc.channels[i]);
for (var i in srcDocs) {
// copy
app.activeDocument = srcDocs[i];
app.activeDocument.selection.selectAll();
app.activeDocument.selection.copy();
app.activeDocument.selection.deselect();
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
// pasta
app.activeDocument = destDoc;
var targetChannels = [destChannels[i]];
app.activeDocument.activeChannels = targetChannels;
app.activeDocument.paste();
}
app.activeDocument.selection.deselect();
app.activeDocument.activeChannels = destChannels;

How to access the contents of a directory passed in the URL parameters passed to a browser

First off, for Chromecast reasons, I want to, currently, limit the solution to the Chrome browser.
What I'd like to do is package up a directory of images, with a launcher batch file (cmd or sh) and an html file. The html file is to be completely self contained, with no imports.
The bat file would contain something like:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files "file://%CD%\SlideShow1.2.html?slideDir=%CD%\Slides"
Currently I can use
<input type="file" id="slideInput" multiple="multiple" webkitdirectory="webkitdirectory" onchange="appendToSlideList();" accept=".png,.gif,.jpg,.jpeg" />
to manually select files, and
var slideList = [];
var numSlides = 0;
function appendToSlideList()
{
var slideInput = document.getElementById("slideInput");
var slides = slideInput.files;
for(j = 0; j < slides.length; j++)
{
slideList[numSlides++] = slides[j];
}
}
to append pictures from selected directories to the master slide list. Then the following, via a Timer() object displays the slides:
function showNext()
{
if(picture == null)
{
picture = document.getElementById("slideShow");
intervalElem = document.getElementById("interval");
}
if(currentPicture == numSlides)
{
currentPicture = 0;
}
// this comes from https://www.w3.org/TR/file-upload/#file
picture.src = window.URL.createObjectURL(slideList[currentPicture++]);
var interval = parseInt(intervalElem.value) * 1000;
timer = new Timer(showNext, interval);
}
If you've read this far, kudos :). So, all this (plus other auxiliary code that is not germane to the desired solution) works to show a slideshow based on user input from the object.
My desire is to package things up so that all the user has to do is double click on the bat file, and the browser proceeds to show the slide show.
So, after all this, my question is, how do I take the directory passed in, and get all the graphic files in that directory, for use in the already working code.
I've spent the last six hours researching this question, much on StackOverflow, and it appears, to me, currently, that this is an impossible quest.
Here's a fiddle with 'complete' minimalist code: https://jsfiddle.net/hrvrdfjs/
Thanks!
Tom.
There is currently no way for JavaScript in the browser to list files from a directory without the user explicitly choosing the directory in the browser.
However, you can easily create a JavaScript file that contains a list of the image files in the directory from a batch file like this:
#echo off
echo var toC = ` > "C:\slides\data.js"
dir C:\slides\*.png,*.gif,*.jpg,*.jpeg /B >> "C:\slides\data.js"
echo `; >> C:\slides\data.js
This will create file data.js which looks like this:
var toC = `
funny.png
serious.png
holidays.jpeg
`;
Explanations:
echo var toC = ` > "C:\slides\data.js"
Creates or overwrites the file C:\slides\data.js with the javascript code that is the first part of creating a template literal.
dir C:\slides\*.png,*.gif,*.jpg,*.jpeg /B >> "C:\slides\data.js"
Tells dir to list the files with the given extensions in the given directory and appends the file names to the file data.js. The parameter /B makes dir only output the files names, no other information and also skip outputting a header and footer for the listing.
echo `; >> C:\slides\data.js
This appends the end of the javascript template literal.
In JavaScript ES6 and newer, template literals can be used to create string literals that span multiple lines and can contain arbitrary characters.
Load the file data.js dynamically from your html document (by inserting a script tag that refers to the file) and you can access the variable toC which contains the list of files as a multi-line string.
This is a demo where the data.js file is statically included:
<html>
<head>
<script src="C:\slides\data.js"></script>
</head>
<body>
<script>
window.alert(toC);
</script>
</body>
</html>
I wasn't able to use the "dir" command as given above, perhaps because I'm using windows 7. Here is what I came up with, instead:
mkdir C:\Temp\slideShow
rem Replace each instance of a back slash with two, as javascript will remove a single backslash
rem (Unless it is followed by a 'valid' backslash char (r,n,etc.), which is not what we want.).
echo var topLocation = '%CD:\=\\%'; > C:\Temp\slideShow\topLocation.js
echo var slideDirContents = ' > "C:\Temp\slideShow\slideDirContents.js"
dir %CD%\Slides\*.jpeg %CD%\Slides\*.jpg %CD%\Slides\*.png %CD%\Slides\*.gif /B /ON >> "C:\Temp\slideShow\slideDirContents.js"
echo '; >> C:\Temp\slideShow\slideDirContents.js
Then, to split out each file from the input .js file, and create a file path that would later be used to build an URL that the img.src could present the file (NOTE: the add function had to be done after the page was loaded, so the textarea element would be present for the javascript to modify):
function doOnloadFunctions()
{
addStaticsToSlideList();
}
function addStaticsToSlideList()
{
// Empty dir on Windows == size 2 (CR-LF)
// Empty dir on others == size 1 (CR or LF)
if((slideDirContents.length != 2) && (slideDirContents.length != 1))
{
var slidelistElem = document.getElementById("slidelist");
// Linux/Unix/BSD based line separator = \n
// Windows based line separator = \r\n
// Mac based line separator = \r
var re = /\n|\r\n|\r/;
var slides = slideDirContents.split(re);
for(j = 0; j < slides.length; j++)
{
var aFile = {};
var theName = slides[j].trim();
if(theName.length > 0)
{
var fileName = slides[j];
aFile.fileName = topLocation + '\\Slides\\' + fileName;
// Set the original index to the current insertion point.
aFile.oi = numSlides;
// Set the shuffle index simply to instantiate it. We'll
// set the si for real when we want to shuffle the array.
aFile.si = 0;
var listElem = document.createElement("li");
listElem.appendChild(document.createTextNode(fileName));
slidelistElem.appendChild(listElem);
slideList[numSlides++] = aFile;
}
}
}
}
There's a <file> input element that can be used to append local files to the slide list, the <file> input doesn't fill in fileName it just fills in name thus this is the differentiating code that instantiates each <img> with the proper url:
if(!(typeof slideList[currentPicture].fileName === 'undefined' || slideList[currentPicture].fileName === null))
{
// variable is defined and not null
picture.src = "file://" + slideList[currentPicture++].fileName;
}
else
{
// this comes from https://www.w3.org/TR/file-upload/#file
picture.src = window.URL.createObjectURL(slideList[currentPicture++]);
}
Thanks to NineBerry for the code and suggestions!

JavaScript Loop(Make sure my loop isn't rewriting)

So I need some help saving some files that people will submit via a file upload.
Right now my code replaces it each time, but I want it to not replace each time. Like for example my user chooses a certain amount of files like 3 files, uploads them, and then chooses 1 file and uploads that as well. That would be a total of 4 files. What my code is doing now is if my user uploads 3 files, it saves the 3 files and then if they upload 1 file it gets rid of the 3 and only has one file.
I want it to not get rewritten, and have 4 files. Please help. This seem very trivial but I can't seem to get it.
function handleFileSelect(e) {
if (!e.target.files) return;
selDiv.innerHTML = "";
var files = e.target.files;
for (var i = 0; i < files.length; i++) {
var f = files[i];
selDiv.innerHTML += f.name + "<br/>";
}
}
I believe your issue is you are resetting the innerHTML in the function call. I assume this function is called every time files are uploaded?
function handleFileSelect(e) {
if (!e.target.files) return;
selDiv.innerHTML = ""; // <--- this will empty the div
var files = e.target.files;
for (var i = 0; i < files.length; i++) {
var f = files[i];
selDiv.innerHTML += f.name + "<br/>";
}
}
If you have uploaded files, then called this function and displayed them and then upload more files and call this function it will constantly reset the innerHTML. I believe if you remove that line it will continue to append the uploaded files without overwriting the previous ones.

Javascript get all files minus certain file extentions

I have a script that returns all the files contained within a folder. However, there are some file types in there that I do not want my script to do anything with. I just want it to literally skip over it as if it wasn't there and only deal with the other file types.
How can I achieve this?
So far this is how I'm getting all the files contained within a folder:
var samplesFolder = Folder(Path)
//Get the files
var fileList = samplesFolder.getFiles()
//Creat Array to hold names
var renderTypes = new Array();
//Parse Initial name to get similar render elements
var beautyRender = fileList[0].name
beautyRender = beautyRender.substr(0, beautyRender.length-4)
//Get the render elements with a similar name
for (var i = 0; i < fileList.length; i++)
{
if(fileList[i].name.substring(0,beautyRender.length) === beautyRender)
{
renderTypes[i] = fileList[i].name
}
}
This is not used for web purposes I should hasten to add.
edit
Above is the complete code I have to get all the image files in a folder and bring them into photoshop once the user has selected the folder they want to use. At the moment it is bringing in every single image in the folder when there is a single type I want it to ignore.
You can iterate over the list and only collect those with extensions you care about. i see photoshop so I'll assume image files only:
var distilledFileList = [];
for (var i = 0; i < fileList.length; i++){
if (/\.(?:jpe?g|png|gif|psd)$/i.test(fileList[i].name)){
distilledFileList.push(fileList[i]);
}
}
Now distilledFileList contains only *.jpg, *.jpeg, *.png, *.gif, and *.psd files.
if you want an easier (more readable) way to check extensions (maybe you're not as fluent as regular expressions):
// fileList = ....
// setup an array of bad extensions here:
var bad = ['txt', 'log', 'db'],
// holds new list of files that are acceptable
distilledFileList = [];
// iterate over entire list
for (var i = 0; i < fileList.length; i++){
// grab the file extenion (if one exists)
var m = fileList[i].name.match(/\.([^\.]+)$/);
// if there is an extenions, make sure it's now in the
// 'bad' list:
if (m && bad.indexOf(m[1].toLowerCase()) != -1){
// it's safe, so add it to the distilled list
distilledFileList.push(fileList[is]);
}
}
Assuming fileList is just an array of strings you could do something along the lines of:
for (var i = 0, len = fileList.length; i < len; i++) {
var filename = fileList[i].name;
if (filename.match(/\.(txt|html|gif)$/i) !== null) { continue; }
// Your logic here
}
Where txt, html and gif are file extensions you want to skip over, you can add more by separating them with |

How do I focus another document with photoshop javascript scripting?

This following script almost does what I need. What I'm trying to do is go through the opened documents, 139 of them, and save them as jpeg. However what it's lacking is moving from one opened document to the other, so it saved the same image over 139 times. I assumed doc.close() would close the opened document and give a new one focus, but it doesn't.
Here's the code:
var destination = "C:/Documents and Settings/Administrator/My Documents/small images"
for(var i = 0; i < 5; i++)
{
doc = documents[i];
name_ = doc.name.substring(0, doc.name.indexOf('.'))
saveForWebPNG(destination, name_);
doc.close();
}
function saveForWebPNG(outputFolderStr, filename)
{
var opts, file;
opts = new ExportOptionsSaveForWeb();
opts.format = SaveDocumentType.JPEG;
opts.quality = 60;
if (filename.length > 27) {
file = new File(outputFolderStr + "/temp.jpg");
activeDocument.exportDocument(file, ExportType.SAVEFORWEB, opts);
file.rename(filename + ".jpg");
}
else {
file = new File(outputFolderStr + "/" + filename + ".jpg");
activeDocument.exportDocument(file, ExportType.SAVEFORWEB, opts);
}
}
According to the Adobe Photoshop CS2 JavaScript Scripting Guide it looks like you need to assign to the Application.activeDocument property to make that document the currently selected one for any actions. This makes sense since you're using that property in the saveForWebPNG function without explicitly activating the document in the iterator in the first block. It might be as simple as the following change:
for (var i = 0; i < 5; i++) {
var doc = documents[i];
app.activeDocument = doc; // Select that document.
var name = doc.name.substring(0, doc.name.indexOf('.'))
saveForWebPNG(destination, name);
doc.close();
}
However, I don't have a copy of Photoshop and haven't verified this solution.
I know this is a lot late, but I just came across this question and feel like I might have a solution that someone down the road may be able to use. So here goes.
One solution is to adjust the for loop. Let's say there are 5 documents open. app.documents.length would be 5. When you close one, the length is now 4, then 3, etc. i is counting up as app.documents.length is decreasing. When i = 3, app.documents.length = 2. I think that iterating backwards would do the trick.
for (i = app.documents.length - 1; i >= 0; i--) { ... }
The answer from #maerics is correct.
However, if you don't know the index of the given document you can reference it by name like in this example:
// Assume 'hello.psd' and 'world.psd' are in same folder at script.
// Open the first file. It is the active document.
var scriptFile = new File($.fileName)
var scriptPath = scriptFile.parent.fsName
var file1obj = File(scriptPath + '/hello.psd')
var file1 = open(file1obj)
// Open the second file. The second file is now the active document.
var file2obj = File(scriptPath + '/world.psd')
var file2 = open(file2obj)
// Make the first file the active document
app.activeDocument = file1
// Make the second file the active document
app.activeDocument = file2
+1 for jbiz's solution. I couldn't figure out why my loop wouldn't complete.
for (i=app.documents.length-1; i >= 0; i--) {
doc = app.documents[i];
app.activeDocument = doc;
...
do whatever you need, save & close
...
}
app.documents.length gets shorter with every iteration though the loop if you close the document when you're done.
You want to close the current selected (or active) document:
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);

Categories

Resources