Copy link to clipboard
Copied
I'm getting this error when running a javascript file in Illustrator using C#: "Exception has been thrown by the target of an invocation. Error 24: String().startsWith is not a function.\rLine: 2\r-> alert(String(test).startsWith(\"PANTONE\"));"
var test = 'PANTONE Test';
alert(String(test).startsWith("PANTONE"));
var xCoord = 643.9;
var yCoord = -286.2;
var boxSpace = 10;
var textOffset = 10;
var textYStart = yCoord - textOffset - 7.5;
var boxYStart = yCoord - boxSpace;
//var excludeSwatches = ["[None]", "[Registration]"];
var embColorGroupItem = activeDocument.groupItems['EMB GROUP'];
var swatches = activeDocument.swatches;
for (var i = 0; i < swatches.length; i++) {
var swatch = swatches[i];
var name = String(swatch.Name);
if (name != "[None]" && name != "[Registration]" && !name.startsWith("PANTONE")) {
var rect = embColorGroupItem.pathItems.rectangle(boxYStart, xCoord, 23.61328125, 8.34423828125);
rect.Stroked = true;
rect.StrokeWidth = 1;
var text = embColorGroupItem.textFrames.Add();
text.contents = name;
text.translate(xCoord + 30, textYStart);
boxYStart -= boxSpace;
textYStart -= textOffset;
}
}
Is this an illustrator limitation or a JavaScript issue on our server?
Illustrator's version of javascript (ES3) does not support startsWith, use indexOf instead
var test = 'PANTONE Test';
alert(test.indexOf("PANTONE"));
this is javascript though, not C#
Copy link to clipboard
Copied
Illustrator uses ES3. startsWith() was introduced in ES5 or ES6. Try a polyfill.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
Copy link to clipboard
Copied
Thank you, that's extremely outdated but will help me to not try advanced features. Not sure what a polyfill would do for me though?
Copy link to clipboard
Copied
A polyfill is code that will add some missing function to older versions of JS, i.e. you could add a polyfill so that startsWith does exist and can be used:
String.prototype.startsWith = function (searchStr) {
return new RegExp("^" + searchStr).test(this);
}
var test1 = "foo bar".startsWith("foo"), // true
test2 = "foo bar".startsWith("bar"), // false
test3 = "^$^$foobar".startsWith("^$"); // false, unless polyfill escapes special chars
Copy link to clipboard
Copied
Ahh yes, I've always referred to this as just protoyping, this is supported with ES3? I live mostly on the .net side so I haven't gotten too deep with previous/latest versions of JavaScript.
Copy link to clipboard
Copied
It's actually both. I'm extending the prototype by means of adding a polyfill to it, ha. A polyfill is just a term that specifically means to add a snippet of code that brings modern functionality to old browsers (like Internet Explorer) which don't have these natively. If you polyfill Object.defineProperty then you'd open up the ability to use most modern polyfills in scripting.
Also yes the code I posted does work in Illustrator scripting. Just be careful not to extend the prototype of Array then attempt to iterate an array via for...in since this will iterate over the new prototype methods as well.
Copy link to clipboard
Copied
thank you for teaching me the name of this concept.. I use it all the time in an attempt to modernize my workflow despite the resctrictions of ExtendScript.. But i always glossed right over the term polyfill because i had no idea it was even related to what I was doing.
In case anyone else out there is frustrated with the lack of extremely helpful array prototypes, here are some that I include in my standard "utilities" file for every script I write:
Array.prototype.indexOf = function(a, b, c){
for (c = this.length, b = (c + ~~b) % c; b < c && (!(b in this) || this[b] !== a); b++);
return b ^ c ? b : -1;
}
Array.prototype.map = function(callback) {
arr = [];
for (var i = 0; i < this.length; i++)
arr.push(callback(this[i], i, this));
return arr;
};
Array.prototype.forEach = function(callback,startPos,inc) {
if(!inc)inc=1;
if(!startPos)startPos=0;
for (var i = startPos; i < this.length; i+=inc)
callback(this[i], i, this);
};
Array.prototype.backForEach = function(callback) {
for (var i = this.length-1; i >= 0; i--)
callback(this[i], i, this);
};
Array.prototype.filter = function(callback, context) {
arr = [];
for (var i = 0; i < this.length; i++) {
if (callback.call(context, this[i], i, this))
arr.push(this[i]);
}
return arr;
};
Additionally, i've always been frustrated by the idea of "collections" in illustrator that appear like arrays, but don't behave the same way.. For example, you can't set a variable for app.documents[0].layers and then use any of the above prototypes because technically they're not arrays.. One obvious solution is to just write a standalone function instead of a prototype.. but that sacrifices cleanliness IMO. So I created a standalone function called arrayFromContainer(parent, itemType) that allows me to pass in a parent and an item type and it will return a standard javascript array without any of the extra baggage of an illustrator collection, and allow me to chain together array prototypes.
function arrayFromContainer(container,crit)
{
var result = [];
var items;
if(!crit)
{
items = container.pageItems;
}
else
{
items = container[crit];
}
for(var x=0;x<items.length;x++)
{
result.push(items[x])
}
return result;
}
To me, the main benefit of this is that you can avoid runtime errors as a result of running a for loop in the wrong direction... The most obvious example is trying to delete certain layers out of your document in a loop. If you do a normal for loop, like this:
for(var i=0;i<layers.length;i++)
{
if(layers[i].name.match(/(print|proof)/i))
{
layers[i].remove();
}
}
This will almost certainly fail (unless there aren't actually any layers to remove). So you have to remember to run the loop backwards. Not a huge deal, but something i've run up against way too many times.. Using arrayFromContainer, you can manipulate/remove/add items to/from a document without disrupting your loop. You can use a loop to add or delete items but it will never break your script because you're only processing the array of items you created which doesn't change length like collections do.
And you can easily chain the above array prototypes to the end of the arrayFromContainer function.. So, to get an array of layer names, you can use
arrayFromContainer(doc, "layers").map(function(a){return a.name});
instead of:
var layerNames = [];
for(var i=0;i<doc.layers.length;i++)
{
layerNames.push(layers[i].name);
}
but... the pies de resistance.... avoiding writing explicit loops like this allows you to keep your variables in scope without declaring new variables inside a loop, which is known to increase the frequency of MRAP/PARM errors. I used to have a big block of variable declarations preceding every loop so that I could merely assign the variable inside the loop without declaring it. Something like this:
var ppLen = ppLay.layers.length;
var pieceLen, curLay, curSize, curCoords, thisPiece;
var curVb;
for (var ma = 0; ma < ppLen && result; ma++) {
curLay = ppLay.layers[ma];
curSize = ppLay.layers[ma].name;
pieceLen = curLay.groupItems.length;
if (!coords[curSize]) {
errorList.push("Could not find placement data for " + curSize + ".");
continue;
} else {
for (var p = 0; p < pieceLen; p++) {
thisPiece = curLay.groupItems[p];
if (thisPiece.name === "") {
unnamedPieces.push(thisPiece);
continue;
} else if (!coords[curSize][thisPiece.name]) {
errorList.push("Could not find placement data for " + thisPiece.name + ".\nThis piece has been skipped.");
continue;
}
curCoords = coords[curSize][thisPiece.name];
curVb = getVisibleBounds(thisPiece);
thisPiece.left = curCoords[0] - (curVb[0] - thisPiece.left);
thisPiece.top = curCoords[1] + (thisPiece.top - curVb[1]);
}
}
}
The result of this is that whatever function contains this logic has its scope cluttered up with variables that will only ever be used inside the loop.. (this problem is addressed in modern javascript with "let" which keeps the variable in block scope).
using the above array.prototypes, you can declare your variables inside the scope of the callback function you pass in, so they'll never interfere with anything else in the parent function, and they aren't technically being declared insid the loop so we're safe from most MRAP/PARM issues.
Copy link to clipboard
Copied
Illustrator's version of javascript (ES3) does not support startsWith, use indexOf instead
var test = 'PANTONE Test';
alert(test.indexOf("PANTONE"));
this is javascript though, not C#
Copy link to clipboard
Copied
Thanks! That works, I'll write older javascript. That must also explain why I can't set the swatch to a variable and access it's properties.
Copy link to clipboard
Copied
I'm not sure how C# drives Illustrator, there's no issues setting swatches to variables in javascript, vbs, vba
Copy link to clipboard
Copied
I'm calling javascript with c#, but it's all javascript code.
For some reason the following code has "name" equal to "Adobe Illustrator", I'm assuming an object.
var swatch = swatches[i];
var name = String(swatch.Name);
alert(name);
but if I call alert(activeDocument.swatches[i].name) I get the actual color name. No big deal, setting variables in loops seems to cause some trouble anyways.
Copy link to clipboard
Copied
"name" (on its own) is a reserved identifier for an app property. It cannot be used as a variable name. Call your variable something else, e.g. name1.
Copy link to clipboard
Copied
var swatch = app.activeDocument.swatches[0];
// Properties are always camelCase:
alert(swatch.Name); // undefined
alert(swatch.name); // "PANTONE 282 U"
// A property titled "Name" will never exist, only "name".
// A property titled "PageItems" will never exist, only "pageItems".
// "name" is a reserved keyword:
var name = "test";
alert(name); // "Adobe Illustrator"
var swatchName = swatch.name;
Find more inspiration, events, and resources on the new Adobe Community
Explore Now