When using JXA to script OmniFocus, it seems one needs to “push” new task or project, but use the add function to add tags. So tasks are arrays, and to add a new item to an array one uses the push method. Tags are also arrays and should one not be able to add using the push method? Have I understood something wrong?
So here is example code to illustrate my question - the code snippet below with * works, but snippet with ** does not work.
Thank you.
function run() {
const of = Application("OmniFocus");
const oDoc = of .defaultDocument;
const oWin = oDoc.documentWindows[0];
const seln = oWin.content.selectedTrees.value();
todayTags = oDoc.flattenedTags.whose({
name: {
_contains: 'Today'
}
})(); // this will result in an array of tag objects
todayTag = todayTags[0]; //choosing the first element of array
firstSelectedTask = seln[0];
// now say I want to add todayTag to firstSelectedTask
// This below code works *
of.add(todayTags, {
to: firstSelectedTask.tags
});
// but this does not seem to work. Should it not? **
firstSelectedTask.tags.push(todayTag);
firstSelectedTask.tags().push(todayTag); // Neither does this work. Added the parents after tags, to get the tags array.
};
.value() property of selectedTrees returns an array of tasks. As you found out, it’s not possible to “push” tags into the task’s tag element array. Also, we can’t “pop” an element out of it.
For reference, add method takes reference or list of reference as a direct parameter and reference or location specifier in the to: parameter.
In Applescript, examples of location specifiers would be: beginning of tasks, end of tasks.
Taken from the document I referenced above:
Parameters That Specify Locations
Many commands have parameters that specify locations. A location can be either an insertion point or another object. An insertion point is a location where an object can be added.
In the following example, the toparameter specifies the location to which to move the first paragraph. The value of the toparameter of the duplicate command is the relative object specifier before paragraph 4, which is an insertion point. AppleScript completes the specifier with the target of the tell statement, front document of application "TextEdit" .
A Code snippet from Brandon Pittman omnifocuslibrary.js… See how in this one a task object is created and then pushed into taskProject.tasks (which taskProject.tasks is an array I presume).
Will read through the document. From the snippet, it seems that location specifier can be used to construct or maybe get an object specifier, and location specifier can also be used to specify where in an array to add or find something.
I think there is a misunderstanding. taskProject.tasks retrieves a reference to an array of tasks. taskProject.tasks(), on the other hand, would return an array of tasks (with the trailing parenthesis).
It is likely that I may have caused some confusion then, because I am still not as well versed with Reference vs Value that one gets when the parenthesis are added.
But what I meant to ask originally is that should not this – firstSelectedTask.tags.push(todayTag); from the code in the first post add the todayTag to the tags array of the task? firstSelectedTask is the reference to a task, and therefore firstSelectedTask.tags is a reference to the array of tags of the task, and therefore should not the push work?
firstSelectedTask is the task object (not a reference because you use trailing parenthesis on the .value property of selectedTrees to get the actual object. And firstSelectedTask.tags is, as you say, a reference — you didn’t use parenthesis there.
Understood that for adding a task, in javascript it is a push into what appears to be an array / list of tasks.
And, deciphering from your language, it seems the difference between pushing a new task and adding a tag to an existing task is
that the new task is just a dictionary that is being used to create a new task (and so a simple push method),
whereas adding a tag to a task entails internal logic that goes beyond adding a dictionary to the task’s tags array / list
So got that. Who am I to argue how it should work, that is for Omni gods to decide :), was just trying to learn to read the dictionary right and to understand why push would not work in what appears to be adding an item to a list.
On another note, I experimented with this below
function run() {
const of = Application("OmniFocus");
const oDoc = of .defaultDocument;
const oWin = oDoc.documentWindows[0];
// return oWin.content.selectedTrees[0]; // 1. Application("OmniFocus").defaultDocument.documentWindows.at(0).content.selectedTrees.at(0)
// return oWin.content.selectedTrees.value()[0]; // 2. Application("OmniFocus").defaultDocument.tasks.byId("afbVj5Cjlm6")
Trying to clarify for myself the difference between a reference and value.
So I understand that #1 refers to an object in a window, where as #2 refers to the value of the object.
To paraphrase, say people are standing in a formation, then a pointer to the selected person in that formation is what #1 is, whereas #2 is a pointer to that person in real life. #2 will always point to that person whereas what #1 points to whoever stands in that place in the next formation.
Upon experimentation, both deliver the name(), but only #2 allows me to change the name by assigning a new value to task name.
So this works:
// code below changed the task name
const seln = oWin.content.selectedTrees.value();
firstSelectedTask = seln[0];
console.log(firstSelectedTask.name());
firstSelectedTask.name = "new task name ver2"; // this works
// but the below code does not change the name
const seln1 = oWin.content.selectedTrees;
firstSelectedTask = seln1[0];
console.log(firstSelectedTask.name()); // name of task is printed
firstSelectedTask.name = "new task name ver3"; // Error: Can't set that.
// Access not allowed. (-10003). Looked up error code using google - and saw this - -10003
// The specified object cannot be modified.
// so to say that you cannot change attributes when an object is referred to in a formation...
Understood that the dictionary is used to create an instance of the Task class.
Also, understood the difference between .name() and .name/
Got it. Thank you for sharing.
Wow. Thank you for sharing the scripts. Understood that a new tag cannot be pushed it needs to be added using the add verb of the application.
And by the way, it is a joy to read the scripts you write. The way the constants are created one after the other with commas, and then the execution. This one was so easy to understand and will try to write scripts like this.
Not clear as to reason behind writing the code that is making changes in the return statement, unless from a structure perspective might as well report the changes that the function is making and each statement generates a result I suppose.
With this said, I am already overwhelmed with your help in understanding the concepts, so not asking just observing. :)
with comments in each case and why I use return in Atom
OmniFocus Object Model contains a class Tree, that represents the tree of objects in the main window content (GUI). Getting**.value** property on a member of this class, returns the object being represented by this tree (task, tag, project, folder, etc).
Thank you for the explanation. All clear now - as far as references and value is concerned.
Understood as to how the return value of the last expression is returned. However, not sure I understand the “combine Effects and Value” idea. I guess what you are saying is that this way it is clear as what part of the code is changing something outside the function.
I have still not completed the Haskell link and if this will become clear there, then I will get it in due course of time.