I would like to run code by testing only the current selection (not the whole document) and I'm having difficulty understanding exactly how the array "app.selection" and its methods work. To start, I use a "for" loop to cycle through each item selected by using:
for(loop = 0; loop < app.selection.length; loop++){
var sel = loop;
}
This works okay, but when I want to get into determining what each item IS, it gets a little weird. For example,
for(txt = 0; txt < app.selection[sel].textFrames.length; txt++){
// do something to each text frame in the selection here.
}
does not work as expected, but
for(img = 0; img < app.selection[sel].allGraphics.length; img++){
// do something to each graphic in the selection here
}
seems to work fine, regardless if the selection includes more than just graphics alone, or whether it is inside or outside a group.
At times, it seems like app.selection[0] is the only way to access the item by itself. In other words, if a text frame is selected, app.selection[0] might be the same as app.document.textFrames[0], in which case it would be redundant (and incorrect) to say
app.document.textFrames[0].textFrames[0]
And yet the same concept on different page items works like a charm. It is quite puzzling to follow. Furthermore, it seems impossible to determine what kind of object the item is. I want to say something along the lines of:
if (app.selection[0] == [object TextFrame])
but that does not seem to work for me. Is there a way to clearly test if the current item is a group, a graphic or a text frame and do different things depending on the result?
app.selection returns an array of Objects, so each item in the array can be of a different type, and the properties and methods available to it will differ. When using the Extendscript Javascript Console you can see what a particular item in the array is on the fly by just typing
app.selection[0]
(or whatever number). The result will be something like [object TextFrame].
While looping through the selection array, you could use app.selection[0].constructor.name to determine the type of each. Or, if you're only interested in certain types,
if (app.selection[i] instanceof TextFrame){}
At that point you'll know more about which properties you can access, depending on the type.
To answer the second part of the question, there isn't an allTextFrames property, but there is an allPageItems property. This returns an array of pageItems (textFrames, groups, etc.), and you can work with it similarly to app.selection. So, if I have three text frames grouped on the first page of my document (and nothing else), I can see that the following are all true:
app.activeDocument.pages[0].textFrames.length == 0;
app.activeDocument.pages[0].allPageItems.length == 4;
app.activeDocument.pages[0].allPageItems[0] instanceof Group;
app.activeDocument.pages[0].allPageItems[1].constructor.name == "TextFrame";
So you could probably cycle through that array if it's more useful to you than the textFrames collection. Just keep in mind that you don't have access to the special collection properties of TextFrames (like everyItem()).
App.selection is indeed an array which every item can be accessed by its index:
var sel = app.selection //May be null on no open documents ! An empty array on no selection with an open document. One to n length array in case of selection.
then given that you selected one or several items, you can reach those objects by its index
sel[0] //This returns the first item of the array. Javascript starts counting at zero.
Once that said if you access, say sel[4] and selection count less than 5 items or column 5 is empty, then you get an undefined value. So you need to carefully check for selection item validity before using it and never presume it will return something.
HTH,
Loic
http://www.ozalto.com
Related
I'm creating a To-Do list app, pretty basic and I copied the idea from Youtube (still learning JS). So, each time you add a new To-Do it's stored (just the text you typed and whether it's done or not) in an array and the HTML element is added, everything's good until here.
The problem begins when I try to delete that element from the array. Each item (To-do) has an ID which is basically the index where it's stored in the array, so I coded array.splice(item.id, 1) and It works great if you delete the items patiently one by one, but if you click the delete button faster the items deleted in the array doesn't match, it's like the index passed messes up. I was wondering if I could make like a wait until the current delete() function ends or something like that. Btw the list container has an eventListener and if the delete button of any item was clicked it runs the delete() function passing the item by e.target.parentElement (which is the item container).
I want the array for a localeStorage. Thanks!
First time posting and English isn't my first language, sorry for any mistake.
Great question. So .splice() is indeed the Array method you want to call, but it doesn't quite use the syntax you're expecting.
First of all, I'm going to point you to the MDN docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
MDN is managed by Mozilla, and is an authoritative source on most things javascript-in-the-browser. If you're just getting started with javascript, that resource will be invaluable. The MDN documentation is some of the best written technical documentation out there, and is largely written so that people with minimal experience in the language can understand them.
That being said, let's go over what Array.splice is actually doing.
So you have an array of todos:
const todos = [] // Array of to-dos
You want to delete one of the the todos, and you have access to the id. I'm going to assume your Todos have a shape similar to this:
const todo = {
id: 1 // number
name: 'my todo' // string
description: 'my todo description' // string
}
In order to delete an element from this array, given the id of the item you're deleting, you would actually need to do the following:
1) Find the location in the array of the todo that has the id you are looking for.
2) Splice that element out of the array using the index you just found.
Let's see how to do that:
function removeItemFromTodos(itemId, todos) {
// find the index of the todo with the id you are looking for
const indexOfTodoToDelete = todos.findIndex((todoInArray) => todoInArray.id === itemId));
// remove that todo:
todos.splice(indexOfTodoToDelete, 1) // delete the todo
}
Okay, so let's unpack that:
First, the findIndex method loops over the array. It will start at index 0, and work up until it reaches the end of the array. If the item it is currently looking at has an id that matches the id we are looking for, then the function will immediately return the index of that todo's location in the array, and stop searching in the array.
Once you have the index, you can delete the item. The splice function takes in the location that you want to start cutting elements as the first argument, and the number of elements you want to cut as the second argument. The splice method returns the elements that were deleted. So it is actually mutating the array in place, and not making a copy of it in memory to perform its operation.
Let me know if this solution doesn't work for you or if it isn't clear!
I have a 2D array. As you might expect, it is composed of an array of rows with columns in the form of a second array in each row. When I assign a value to a particular [Row][Column] pair, I would like to have a function that can capture the indices for the element that has been modified and then be able to do something with them.
I have tried to use a proxy, but I can only make it intercept changes that are made to the Row, not the combination of Row and Column.
i.e.
row[3] = {1,2,3,4}; //works!
row[3][2] = 42; //Does not work :/
I have searched extensively in SO, W3Schools, Google, etc. but I cannot find anything that addresses this specific requirement.
var row=new Array();
for(var loop=0; loop<10;loop++) //Create 10 rows
{
row.push(new Array(10)); //10 columns per row for the sake of example
}
row[0][0]="Added a Value at (0,0)";
row[3][7]="Added a value at (3,7)";
console.log(row[3][7]); //outputs "Added a value at (3,7)" as expected.
This works fine and I'm happy with being able to manage data in this grid construct. I would like to be able to capture when a value is assigned and have access to the two indices so I can perform validation and subsequent activities. Any guidance would be greatly appreciated.
Proxy getter/setters only respond to top level property changes. See https://github.com/geuis/bodega/blob/master/src/store.js
Alternatively, you could assign a new Proxy object that has a getter/setter for a property like row and/or column in each parent array index. That would let you detect when one of those properties is updated.
I have an array called objs that holds all of my application objects. Objects get added and removed from this list depending upon what happens in the application.
I am having this problem where some objects disappear (or are overwritten) only sometimes. If I step through the add and remove functions, the app always runs as it should, however many times when it is run without the debugger, one or two objects that were added to the end of the list disappear from the list.
objects are added to the array like this:
this.objs[this.objs.length]=obj;
and are removed from the array like this:
for(var i=0;i<this.objs.length;i++)
if(this.objs[i]==obj)
return this.objs.splice(i,1);
I put this code at the end of my add and remove functions:
console.log("add! ");
console.log(this.objs);
Linked is an image of a console log during a session where an object dissapeared: http://ilujin.com/error.png
The first 4 objects in the list shown at the top should remain in the list throughout the session, but the object at index 3 (highlighted in red), gets overwritten by the next object that gets added (highlighted in blue).
The other weird thing is that the second list shown already has all of the changes (4 objects removed and 1 added), even though the remove function has only been called once and the add function not at all.
This makes me conclude that the problem is timing - if one add hasn't finished before the next add is called, the first one will be overwritten. And all of the console prints are the same because they all happen before the console can read and print.
Does this makes sense? For some reason I thought JS never ran parallel code and only moved on to a new function when the last function finished. Is the problem that I'm using the length of the objs list as the new index when I add to the list?
How can I fix this issue? I can't figure it out, and the debugger and console have proven useless.
Here is the app: http://iioengine.com/neuro/study2.htm
you only need to enter an id and see if the instructions pop up. If they do, than its working and refresh. If they don't, that means that the Text Object got overwritten.
You would really be better served by using Javascript's array methods.
Add to array:
this.objs.push(obj);
Remove from array:
this.objs.splice(this.objs.indexOf(obj), 1);
Also, note that splice edits the original array and returns the elements that have been removed. It's hard to tell from your limited code sample, but that might also be causing issues.
In the javascript, there are two arrays:tags[] and tags_java[]. I use .splice to delete certain items, which of the same index in the two arrays. The tags[] works fine, but tags_java doesn't, it seems always delete the last item.
Here is the code and the jsfiddle link.
var tag = $(this).text();
var index = $.inArray(tag, tags);
tags.splice(index,1);
tags_java.splice(index,1);
Nah, both don't work, because you're not actually finding the correct index of your tag.
Why not? Because $(this).text() includes the delete mark you added, Ă— - e.g. "MorningĂ—". Since that's not in your tags array, index will be -1. tags.splice(-1, 1); will remove 1 item from the end of the array.
In general, it's never a good idea to use presentation text (i.e. the text of your tag element) as data (e.g. using that text as a lookup value in an array). It's very likely that it'll be broken when something changes in the presentation - like here. So a suggestion would be to store the data (what you need to look up the tags) as data - e.g. using the jQuery-provided data() API - even if it seems redundant.
Here's a quick example - just adding/replacing two lines, which I've marked with comments starting with "JT": JSFiddle
Now, instead of looking up by $(this).text(), we're looking up by the data value "tagValue" stored with $(this).data() - that way, the lookup value is still bound to the element, but we're not relying on presentation text.
If the tag is not in the tags array, $.inArray will return -1, which would then cause the last item to be deleted.
You have to make sure that the item is actually in the array.
I'm using Knockout-sortable to drag-and-drop/sort records in my table, but I've run into a problem. I have no clue how to keep track of the position in the sort index of an element. (I.e. element A, B and C appear in that order and have 1,2,3 as index respectively, but if B gets dropped above A the correct index would be 2,1,3)
Nothing in my code is custom: I just include knockout-sortable and it's plug and play. I usually always include a code snippet, but I don't feel that's useful. The only thing I know is that I'm probably gonna need a ko.computed(), but I have no idea what to fill it in with.
If you look at example http://jsfiddle.net/rniemeyer/Jr2rE/, you can see that the plug-in works by updating an observable array of data. Because of this, you don't have to keep track of the index value. The order of the records, technically, gives you all the information you need.
That being said, I ran into the same issue in last year. To solve my problem, I added a consecutively numbered index property to each object in my observable array. Then, when the sortable plug-in re-arranged the contents of the observable array, I just had to read out the new index property to know the sort order.