E4X/AS3, get an array of text elements without looping - actionscript-3

this is part of an XML file I retrieve using AS3 E4X:
<links>
<link>
<label>Versions</label>
<href>http://mylink1</href>
</link>
<link>
<label>Configurations</label>
<href>http://myLink2</href>
</link>
</links>
I want to retrieve the values of labels, so I write:
document.links.link.label.text();
This returns VersionsConfigurations. I need this as Array ([Versions, Configurations]) but I would like not to use a loop. Is there any other way?

Well, this is a "don't try this at home" solution, but here you are. :)
You can use E4X search expression to do whatever you want to nodes of an XMLList.
This works as follows: someXMLList.(expression), where expression is any AS3 code that can access each node's properties and methods with no need of qualifying their names. For instance, you could do the following:
yourXML.descendants("label").(trace("label text: ", text()));
Note that I'm using text() here with no access . operations. Actually this will return an new XMLList for all nodes, where expression evaluated to true. Since trace() returns void, the resulting list will be empty. Internally there is of course a loop through all nodes of XMLLIst that is created by calling descendants() (or using .. operator).
You can construct your array the same way.
var doc:XML =
<links>
<link>
<label>Versions</label>
<href>http://mylink1</href>
</link>
<link>
<label>Configurations</label>
<href>http://myLink2</href>
</link>
<link>
<label>A label
with
multiple
line
breaks</label>
<href>http://myLink3</href>
</link>
</links>;
trace(doc.descendants("label").text().toXMLString().split("\n"));
/* Trace output (incorrect):
Versions,Configurations,A label
,with
,multiple
,line
,breaks
*/
var list:Array = [];
doc.descendants("label").(list.push(text().toString()));
trace(list);
/* Trace output (correct):
Versions,Configurations,A label
with
multiple
line
breaks
*/
That may be useful when performing some complicated searches on an XMLList. However in your case I think you should instead use simple splitting of a string representation or a regular expression as Shane suggests.

An alternative technique could be to use a regular expression, although this particular example is dependent on your labels always starting with a capital and otherwise containing only lower case characters.
var regex:RegExp = /[A-Z][a-z]+/g;
var inString:String = "VersionsConfigurations";
var outArray:Array = inString.match(regex);
trace(outArray.length); // 2

Related

Look up for DOM element by XPath, starting from other DOM element

I found that in Chrome DevTools it is suggested to use $x for XPath expressions lookups. But what if one already gets the desired DOM element and wants to continue searching, or may be wants to get some parents attributes.
Is there a way to use $x search starting from the given DOM element (treating this element as a DOM root)?
Try
$x
in the Google Chrome console and it outputs
ƒ $x(xpath, [startNode]) { [Command Line API] }
so that suggests there is an optional second argument to be treated as the context node or start node.
For instance on this page when I use
var codeElements = $x('//*[. = "$x"]');
I get
(5) [code, code, pre.lang-xml.prettyprint.prettyprinted, code, span.pln]
in the console for codeElements and when I use
var parentElements = codeElements.map(el => $x('..', el)[0]);
to compute the parent node of each item in the codeElements array I get
(5) [p, p, div#wmd-preview.wmd-preview, pre.lang-xml.prettyprint.prettyprinted, code]
for parentElements.
So you can pass in the context node as the second argument to $x.
Yes, $x accepts a second argument named startNode which, as its name suggests, is exactly what you are looking for.
You can use it like this:
var myNode = document.getElementById('myId'); // obtain any node in any manner you need to
var childElements = $x('*', myNode);
console.log(childElements.length);
Yes, but you need to use an xpath expression to start from one specific tag, then jump to another tag or get attributes,
Example
//span[#id='test23']//..//a[#id='233']
Also using keywords like "ancestor", "following", "following-sibling"etc.
I suggest you lookup more on Xpath on the net

Using JSON.stringify but SSJS variant in XPages

for an application I am building an administration panel where a power user should be able to check the JSON structure of a selected object.
I would like to display the JSON object in a computed text field but display/format it nicely so it is better human readable, something similar as in pretty print.
Is there any function I could use in SSJS that results in something similar so I can use display json nicely in computed text / editable fields?
Use stringify's third parameter "space":
JSON.stringify(yourObject, null, ' ');
space
A String or Number object that's used to insert white
space into the output JSON string for readability purposes. If this is
a Number, it indicates the number of space characters to use as white
space; this number is capped at 10 if it's larger than that. Values
less than 1 indicate that no space should be used. If this is a
String, the string (or the first 10 characters of the string, if it's
longer than that) is used as white space. If this parameter is not
provided (or is null), no white space is used.
As XPages doesn't support JSON.stringify yet you can include JSON's definition as SSJS resource and use it.
As Knut points out, you can certainly add json2.js to XPages; I've previously used an implementation as Marky Roden's post outlines. This is probably the "safest" way of doing so, from the SSJS side of things.
It does ignore the included fromJson and toJson SSJS methods provided out of the box in XPages. While imperfect, they are functional, especially with the inclusion of Tommy Valand's fix snippet. Be advised, using Tommy's fix does wrap responses to ensure a proper JS object can be parsed by shoving an Array into an object with a values property for the array; so no direct pulling of an Array only.
Additionally, I believe it would be useful to point out that a bean, providing a convenience method or two as wrappers to use either the com.ibm.commons.util.io.json methods to abstract the conversion method, or switching in something like Google GSON, might be more powerful and unified, based on your style of development.
Knut, Eric, I came so far myself already.
function prettyPrint(id) {
var ugly = dojo.byId(id).value;
var obj = $.parseJSON( "[" + ugly + "]" );
var pretty = JSON.stringify(obj, undefined, 4);
dojo.byId(id).innerHTML = pretty;
}
and I call it e.g.
var name = x$('#{id:input-currentObjectCollectionFiltered}').attr("name");
prettyPrint(name);
I tried to make use the x$ function but was not able to make the ID dynamic there e.g.
var ugly = x$('#{id:" + id + "}').val();
not sure why. would be nicer if I just would call prettyPrint('input-currentObjectCollectionFiltered'); and the function would figure it out.
Instead of dojo.byId(id).value I tried:
var ugly=$("#" + id).val();
but things returns and undefined object: I thought jquery would be smarter to work with dynamic id's.
anyway stringify works just fine.

AS3 Advanced Variable Usage - Retrieve Value In Variable Selected Based On Value In Another Variable

I am working on creating a project which allows me to define objects in an XML document and have flash create the required items and display at run time. Needless to say, it is slow progress making this up and learning as I go along.
I have now reached a hurdle, which by comparison should be really easy. But I'm drawing a blank. Despite spending several hours with my good friend Google, I don't seem to be able to phrase this in such as way as to find other people who want to achieve the same.
Description
Currently I have a menu handling class, which is where the issue lays. An instance of the menuHandler class is created. It is used by calling a class (LoadFramework) which is passed two strings. One is the address of an XML file, the other is a tag within that XML document to specify what part of it needs to be read.
The XML document is loaded by a class and then parsed into a variable. Then depending on the second string passed (the value of the XML tag), the appropriate part of that XML document is translated onto the screen. This all works fine, but here is the catch...
The only way I have managed to get this working is to use a switch statement with a case for every potential tag. Then I write an appropriate for-each-in loop. The following is an example of one of the case statements (they are all identical except for the "event.dat.XYZ.element" part). "event.dat" is a variable passed by a custom handler, it contains the XML data. XYZ is the name of the tag (in this case "home") and each "element" is a tag containing an attribute.
case "home":
for each (var menuField:XML in event.datPass.home.element) {
mnDataHold.push(menuFieldHome);
loopCount ++;
} // for each (var menuField:XML in event.datPass.home)
ActionMenu(loopCount);
break;
An example of the XML that is held in event.datPass.
<home label="home">
<element label="menuType">HomeScreen</element>
<element label="screenType">Game.assets.menuScreens.homeScreen</element>
<element label="buttonCoords">400,50,400,100,400,150,400,200</element>
<element label="buttonNames">baseButton,baseButton,baseButton,baseButton</element>
<element label="labelCoords">440,51,425,101,430,151,438,201</element>
<element label="labelTexts">Play,Options,Credits,More</element>
<element label="labelColors">0xFFFFFF,0xFFFFFF,0xFFFFFF,0xFFFFFF</element>
<element label="labelSizes">22,22,22,22</element>
<element label="labelBolds">true,true,true,true</element>
<element label="buttonCommands">startGame,diplaysMnuOptions,displayMnuCredits,displayMnuMoreGames</element>
</home>
The Request
What I want to do is replace part or all of the object in the for-each-in loop with a variable, and then evaluate the string as a reference rather than using it as the value.
So that I can end up ditching the switch and simply have something like:
for each (var menuField:XML in [mnVariable]) {
mnVariable would contain the string, eg "event.datPass.home.element".
Or:
for each (var menuField:XML in event.dat.[mnVariable].element) {
mnVariable would contain the string, eg "home".
However, written the first way, all that happens is that the for-each-in loop looks in mnVariable to find the value in menuField, which it won't find. Written the second way it will just throw an error. I need [mnVariable] to be used to evaluate as meaning 'look in the object named by this string'
Any pointers are very gladly received.
Generally not a fan of having data-defining element names in XML, but - assuming event.dat is actually an XML object, and not an arbitrary object that you've manually parsed the XML into (can't tell from the question) - this is really a question about ECMAScript for XML (E4X) rather than variables in general.
If so, this should work:
for each (var menuField:XML in event.dat.child(mnVariable).element)
{
// Do something
}
Can't test at the moment, but this may also work (it's part of E4X standard, but not sure if Adobe implemented it):
// Note, no dot before the indexer:
for each (var menuField:XML in event.dat[mnVariable].element)
{
// Do something
}
The latter is also generally the way to access a property of an object using a string property name, so if event.dat is an arbitrary object of manually parsed XML - as opposed to an actual XML object - the second example should also work.

retrieving a variable value from lower level

well i created some variables in the main stage level, with something like this:
for(i=0,i<10,i++){
var var_name="var_num_"+i;
this[var_name]="some value";
}//<-----------------------------------------------------works
so i get 10 variables named "var_num0", "var_num1", "var_num2" each one with some value.
and i can acces them any where calling this
var second_var=MovieClip(root).var_num0;//<--------------works
my problem comes when i want to call all the variables from a lower level or in another frame or somewhere else using another loop:
var third_var;
for(j=0,j<3,j++){
third_var=this["MovieClip(root).var_num_"+j];//<---------DOSNT WORK
trace(this["MovieClip(root).var_num_"+j]);//<------------returns "undefined"
}
how can i make this work? i tried a lot of things and nothing...
thanks you all
In your case both "root" and "this" are the scope you want to access the vars from. so try this:
var third_var:MovieClip;
for(j = 0; j < 3; j++)
{
third_var = MovieClip(root)[var_num_ + j];
trace(third_var);
}
Also you should have semi-colons in your for loop rather than comers.
I'd like to preface my answer with a suggestion you use a 'Document Class' with AS3 to make things like namespaces and inheritance much clearer. You know exactly where things are accessible when using document based, object oriented programming versus the timeline programming available through the Flash IDE (its only there because of AS1/2). Tut: http://www.kirupa.com/forum/showthread.php?223798-ActionScript-3-Tip-of-the-Day/page14
On to the answer: You are trying to move two levels of inheritance in one set of [] Another way of writing your first "Doesn't work" line is:
this.myMovieClip["var_num"+j"];
You could also use: this["MovieClip"]["var_num"+j];
Basically, you need to take the "MovieClip(root)" out of the string you are using to call your variable because you are passing through two levels of inheritance: this->MovieClip->targetVar
You need to use two periods, a period and a set square bracket or two sets square brackets to move two levels of inheritance. A period . and a set of square brackets [] both accomplish the task of moving one level deeper, so putting the . inside the string used to call up your variable won't work.
Explanation:
The following three examples all return the same variable:
myMovieClip.my_variable
myMovieClip["my_variable"]
var str:String = "my_variable";
myMovieClip[str];

Get page selection including HTML?

I'm writing a Chrome Extension, and I was wondering if it was possible to get the selected text of a particular tab, including the underlying HTML? So if I select a link, it should also return the <a> tag.
I tried looking at the context menu event objects (yes, I'm using a context menu for this), and this is all that comes with the callback:
editable : false
menuItemId : 1
pageUrl : <the URL>
selectionText : <the selected text in plaintext formatting, not HTML>
It also returns a Tab object, but nothing in there was very useful, either.
So I'm kind of at a loss here. Is this even possible? If so, any ideas you might have would be great. Thanks! :)
Getting the selected text of a page is fairly easy, you can do something like
var text = window.getSelection().toString();
and you'll get a text representation of the currently selected text that you can pass from a content script to a background page or a popup.
Getting HTML content is a lot more difficult, mostly because the selection isn't always at a clean HTML boundary in the document (what if you only select a small part of a long link, or a few cells of a table for example). The most direct way to get all of the html associated with a selection is to reference commonAncestorContainer, which is a property on a selection range that corresponds with the deepest node which contains both the start and end of the selection. To get this, you'd do something like:
var selection = window.getSelection();
// Only works with a single range - add extra logic to
// iterate over more ranges if needed
var range = selection.getRangeAt(0);
var container = range.commonAncestorContainer;
var html = container.innerHTML
Of course, this will likely contain a lot of HTML that wasn't actually selected. It's possible that you could iterate through the children of the common ancestor and prune out anything that wasn't in the selection, but that's going to be a bit more involved and may not be necessary depending on what you're trying to do.
To show how to wrap this all up into an extension, I've written a short sample which you can reference:
http://github.com/kurrik/chrome-extensions/tree/master/contentscript-selection/
If you don't want all of the siblings, just the selected HTML, use range's other methods like .cloneContents() (to copy) or .extractContents() (to cut).
Here I use .cloneContents():
function getSelectedHTML() {
var range = window.getSelection().getRangeAt(0); // Get the selected range
var div = document.createElement("div");
div.appendChild(range.cloneContents()); // Get the document fragment from selected range
return div.innerHTML; // Return the actual HTML
}