The collection has something common with Dictionary in VBScript, but they are not the same thing.
Collection is object such as Session.Contents property.
Dictionary is an object created using the following code.
Dim dictObj
Set dictObj = CreateObject("Scripting.Dictionary")
I want to find the differences and commons with these two type objects, in order to know how to use them.
I have found these useful articles, but still not clearly understand what a collection really is in asp using VBScript.
Working with Collections
Using the Dictionary Class in VBA
My questions are:
Is there no Collection data type in VBScript? you can create a dictionary but you can not create a collection object.
When loop through the collection using For Each, what is the x is, the key of collection or the collection item value?
for each x in Session.Contents
Why can you use collection this way?
Session("FirstName")
is the same as
Session.Contents("FirstName")
What is the documentation of collection object?
The documentation of dictionary I can find is this
I may have misunderstood collection object, I so please tell me. Thank you so much.
Is there no Collection data type in VBScript?
Correct. There is no built-in Collection data type. It exists in VB, but VBScript does not have it. You can work with collections on existing objects in VBS, but you can't create new ones. Use the Scripting.Dictionary, which offers the same features.
When loop through the collection using For Each, what is the x, the key of collection or the collection item value?
For Each loops always give you the key on collection-like objects. You can then use the key to access the value. (If it gave you the value instead, there would be no way to get to the key in a for-each loop, i.e. you would have no idea what the value means.)
Dim key, val
For Each key In Session.Contents
val = Session(key)
Response.Write Server.HtmlEncode(key) & " = " & Server.HtmlEncode(val) & "<br>"
Next
Why can you use collection this way?
Session("FirstName")
Because the default property of the Session object is the Contents collection. And the default property of the collection objects is Item.
The default property is the one that gets called when you don't name a property in your code but still use parentheses on an object.
Therefore these are the effectively the same:
Session("FirstName") = "Foo"
Response.Write( Session("FirstName") & "<br>" )
Session.Contents("FirstName") = "Bar"
Response.Write( Session.Contents("FirstName") & "<br>" )
Session.Contents.Item("FirstName") = "Baz"
Response.Write( Session.Contents.Item("FirstName") & "<br>" )
Where the last variant is what actually happens while the other two variants are syntactic sugar. The Session.Contents collection is based on a regular collection, but has some added magic. For example, accessing missing keys does not throw an error.
What is the documentation of collection object?
Microsoft did a great job at not only abandoning the VB 6.0 documentation, but also making whatever remains of it impossible to find. The closest thing is the documentation of the Collection Object in Office VBA.
Otherwise there is the documentation of the IIS Session object, which applies to classic ASP. Related: Storing and Removing Data from the ASP Session Object.
Related
My task is to check the value of data from the global data sheet within different UIs, each of them having lots of data.
My idea was to do this in a generic way.
I create a array with the objects name, which corresponds with the name of the data sheet column
And then I just compare the content
Browser("").Page("").GENERIC_TYPE(label).GetROProperty("value") = datasheet.GetParameter(label)
Is there such a Generic Type that works for WebEdit and WebList?
You can use WebElement and this is generic as all elements on the page are web elements.
If you are reading the objects from OR, then you might have to update the element type to WebElement and it's tidious (if you are dealing with multiple objects). So the alternative way is using the below approach.
Browser("").Page("").WebElement("xpath:=//*[#common_attribute=" + element_attribute_value + "]").GetROProperty("value") = datasheet.GetParameter(label)
I’m experimenting with scripting a batch of OmniFocus tasks in JXA and running into some big speed issues. I don't think the problem is specific to OmniFocus or JXA; rather I think this is a more general misunderstanding of how getting objects works - I'm expecting it to work like a single SQL query that loads all objects in memory but instead it seems to do each operation on demand.
Here’s a simple example - let’s get the names of all uncompleted tasks (which are stored in a SQLite DB on the backend):
var tasks = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})
var totalTasks = tasks.length
for (var i = 0; i < totalTasks; i++) {
tasks[i].name()
}
[Finished in 46.68s]
Actually getting the list of 900 tasks takes ~7 seconds - already slow - but then looping and reading basic properties takes another 40 seconds, presumably because it's hitting the DB for each one. (Also, tasks doesn't behave like an array - it seems to be recomputed every time it's accessed.)
Is there any way to do this quickly - to read a batch of objects and all their properties into memory at once?
Introduction
With AppleEvents, the IPC technology that JavaScript for Automation (JXA) is built upon, the way you request information from another application is by sending it an "object specifier," which works a little bit like dot notation for accessing object properties, and a little bit like a SQL or GraphQL query.
The receiving application evaluates the object specifier and determines which objects, if any, it refers to. It then returns a value representing the referred-to objects. The returned value may be a list of values, if the referred-to object was a collection of objects. The object specifier may also refer to properties of objects. The values returned may be strings, or numbers, or even new object specifiers.
Object specifiers
An example of a fully-qualified object specifier written in AppleScript is:
a reference to the name of the first window of application "Safari"
In JXA, that same object specifier would be expressed:
Application("Safari").windows[0].name
To send an IPC request to Safari to ask it to evaluate this object specifier and respond with a value, you can invoke the .get() function on an object specifier:
Application("Safari").windows[0].name.get()
As a shorthand for the .get() function, you can invoke the object specifier directly:
Application("Safari").windows[0].name()
A single request is sent to Safari, and a single value (a string in this case) is returned.
In this way, object specifiers work a little bit like dot notation for accessing object properties. But object specifiers are much more powerful than that.
Collections
You can effectively perform maps or comprehensions over collections. In AppleScript this looks like:
get the name of every window of Application "Safari"
In JXA it looks like:
Application("Safari").windows.name.get()
Even though this requests multiple values, it requires only a single request to be sent to Safari, which then iterates over its own windows, collecting the name of each one, and then sends back a single list value containing all of the name strings. No matter how many windows Safari has open, this statement only results in a single request/response.
For-loop anti-pattern
Contrast that approach to the for-loop anti-pattern:
var nameOfEveryWindow = []
var everyWindowSpecifier = Application("Safari").windows
var numberOfWindows = everyWindowSpecifier.length
for (var i = 0; i < numberOfWindows; i++) {
var windowNameSpecifier = everyWindowSpecifier[i].name
var windowName = windowNameSpecifier.get()
nameOfEveryWindow.push(windowName)
}
This approach may take much longer, as it requires length+1 number of requests to get the collection of names.
(Note that the length property of collection object specifiers is handled specially, because collection object specifiers in JXA attempt to behave like native JavaScript Arrays. No .get() invocation is needed (or allowed) on the length property.)
Filtering, and why your code example is slow
The really interesting part of AppleEvents is the so-called "whose clause". This allows you provide criteria with which to filter the objects from which the values will be returned from.
In the code you included in your question, tasks is an object specifier that refers to a collection of objects that have been filtered to only include uncompleted tasks using a whose clause. Note that this is still just reference at this point; until you call .get() on the object specifier, it's just a pointer to something, not the thing itself.
The code you included then implements the for-loop anti-pattern, which is probably why your observed performance is so slow. You are sending length+1 requests to OmniFocus. Each invocation of .name() results in another AppleEvent.
Furthermore, you're asking OmniFocus to re-filter the collection of tasks every time, because the object specifier you're sending each time contains a whose clause.
Try this instead:
var taskNames = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false}).name.get()
This should send a single request to OmniFocus, and return an array of the names of each uncompleted task.
Another approach to try would be to ask OmniFocus to evaluate the "whose clause" once, and return an array of object specifiers:
var taskSpecifiers = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})()
Iterating over the returned array of object specifies and invoking .name.get() on each one would likely be faster than your original approach.
Answer
While JXA can get arrays of single properties of collections of objects, it appears that due to an oversight on the part of the authors, JXA doesn't support getting all of the properties of all of the objects in a collection.
So, to answer you actual question, with JXA, there is not a way to read a batch of objects and all their properties into memory at once.
That said, AppleScript does support it:
tell app "OmniFocus" to get the properties of every flattened task of default document whose completed is false
With JXA, you have to fall back to the for-loop anti-pattern if you really want all of the properties of the objects, but we can avoid evaluating the whose clause more than once by pulling its evaluation outside of the for loop:
var tasks = []
var taskSpecifiers = Application('OmniFocus').defaultDocument.flattenedTasks.whose({completed: false})()
var totalTasks = taskSpecifiers.length
for (var i = 0; i < totalTasks; i++) {
tasks[i] = taskSpecifiers[i].properties()
}
Finally, it should be noted that AppleScript also lets you request specific sets of properties:
get the {name, zoomable} of every window of application "Safari"
But there is no way with JXA to send a single request for multiple properties of an object, or collection of objects.
Try something like:
tell app "OmniFocus"
tell default document
get name of every flattened task whose completed is false
end tell
end tell
Apple event IPC is not OOP, it’s RPC + simple first-class relational queries. AppleScript obfuscates this, and JXA not only obfuscates it even worse but cripples it too; but once you learn to see through the faux-OO syntactic nonsense it makes a lot more sense. This and this may give a bit more insight.
[ETA: Omni recently implemented its own embedded JavaScriptCore-based scripting support in its apps; if JS is your thing you might find that a better bet.]
I have a script in which I filter the data in a module by a certain attribute value. When I then loop through these objects, for now, I am displaying the absolute number of the objects in an infoBox. However, the script is displaying absolute numbers of objects that are not in the dataset. Upon further investigation, I found that the extra absolute numbers were for each table within the entire module. I can't figure out why the script would include these tables when they are not in the filtered module data. I have even tried manually filtering the module on this attribute value then use the "Tools -> Edit DXL" to loop through the resulting items and it still displays the numbers for the tables that are not included. Why would it do this?
Here's my code:
bm2 = moduleVar
Filter fltr = contains(attribute "RCR_numbers", sRCRNum, false);
filtering on;
set(bm2, fltr);
for oObj in document(bm2) do {
absNum = oObj."Absolute Number";
infoBox("Object #" absNum ".");
}
I have also tried removing the document cast so it says "for oObj in bm2 do" instead, but this doesn't change the output. Why is the code giving me objects that are not in the filter? Any help would be greatly appreciated since this is a high priority issue for my project and I'm out of ideas myself.
Chris
In the DOORS 9.6.1 DXL Reference Manual you can see that:
for object in document
Assigns the variable o to be each successive
object in module. It is equivalent to the for object in module loop,
except that it includes table header objects, but not the row header
objects nor cells.
So, you must either use for object in module or, within your existing loop, test the hidden attribute TableType - this will be set to TableNone for anything that is not part of a table, table headers included.
I believe that I had not drafted my question clearly enough in my previous posting.
My question relates to the old technology. Yet it matters a lot to me to get a through answer. Could a Visual Basic expert answer me or include links to other sites and sheds light on this memory usage question please?
Consider the following VB 6 code in a custom COM plus object on a page on an e-commerce site:
Assign long and short strings to the globalArray elements. Also some of the elements of globalArray hold smaller arrays.
And also load the xml:
Dom globalArray(30, 100000)
Set objGlobalDom = CreateObject("msxml2.FreeThreadedDomDocument.6.0")
objGlobalDom.loadXML (xmlStr)
Application.lock
Set Application(“objGlobalDom”) = objGlobalDom
Application(“globalArray”) = globalArray
Application.unlock
With any new session the following variable assignments are done:
set Session(“objGlobalDom”) = Application(“objGlobalDom”)
session(“globalArray”) = Application(“objGlobalDom”)
The Application(“objGlobalDom”) will contain an xml with some 1000 nodes and each node takes about 3k of memory. The array will take some 50 meg of memory.
Considering VB6 and COM object:
I understand that each instance of the object references the object’s data. What I don’t understand is:
1- If Session(“objGlobalDom”) does not contain a copy of the Application(“objGlobalDom”), why changes in the data of Session(“objGlobalDom”) are not automatically reflected in the data of Application(“objGlobalDom”) or does the Session(“objGlobalDom”) have a copy of the Application(“objGlobalDom”)?
2- According to Microsoft, in a situation such as my globalArray example, the Session(“globalArray”) always gets a copy of the Application(“globalArray”) and so Microsoft discourages assigning the array to session variables. But it is not clear to me that in the case of COM object and object references, does the assignment of set Session(“objGlobalDom”) = Application(“objGlobalDom”) copies the array to the session variable?
I really appreciate your answers and thank you in advance for your response.
COM Object are just UDT 'User Defined Type' (structs int c, record in delphi). that hold pointers to function, and private data related for each instance of the object.
when you see in VB the keyword set it does not mean it will copy all data from var to var, it just copy its reference (address) and add one to the refcount of that object. and when you do test var=nothing the object will not be freed until refcount reach 0. So set Obj=Nothing decrements Object RefCount , and if RefCount =0 the the Object Free Itself.
If I have a collection of forms (myForms) and I want to switch the position of two forms in the collection (say items 3 and 4 for example), I would expect that the following code would work:
Dim temp as Form
Set temp = myForms(3)
Set myForms(3) = myForms(4)
Set myForms(4) = temp
But that doesn't work. It fails at the third line with the error "Controls property is read only." If I change the line to:
myForms(3) = myForms(4)
I get a type mismatch error instead.
If myForms is a standard collection:
Dim myForms as New Collection
(which is actually different from the controls collection) and you've added the forms using:
myForms.Add frmOne, myForms.Add frmTwo
etc then (yes) you do need to use the Add and Remove methods because of the way the collection references the added objects.
Otherwise the interpretation is that you actually want to replace one form with another and this is not allowed. You can't say:
Set frmOne = frmTwo
unless these are actually variables of type Form.
Why do you need to switch the order? Are you referencing the item numbers somewhere? Would using a Dictionary to collect the forms and reference them by a key be useful?
PS. The type mismatch is simply because both items are objects and need to be 'Set'.
You can't actually swap around items in the controls collection in VB6. You need to use the Add and Remove functions associated with each. Check out this article:
http://support.microsoft.com/kb/190670
Hope this helps!