Using JSON.stringify but SSJS variant in XPages - json

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.

Related

Need to get the 2sxc Field Type from an Entity

After getting everything working on this previous question Is there a way to Clone one or many Entities (records) in Code, I wanted to clean it up and make it more useful/reusable. So far, I am deciding how to copy/add the field using the content-type field names, so attribute.Key inside the foreach on Attributes. What I need instead is to know the Entity field's Type; meaning String, Number, Hyperlink, Entity, etc.
So I want something like if(AsEntity(original).FieldType == "HyperLink") { do this stuff }. I have explored the API docs but have not spotted how to get to the info. Is it possible?
I did figure out that the attribute.Value has a Type that I could use to answer most of them, but Hyperlink and String are both showing, System.String.
Here are, in order, String, Hyperlink, Entity, and Number:
atts: ToSic.Eav.Data.Attribute`1[System.String]
atts: ToSic.Eav.Data.Attribute`1[System.String]
atts: ToSic.Eav.Data.Attribute`1[ToSic.Eav.Data.EntityRelationship]
atts: ToSic.Eav.Data.Attribute`1[System.Nullable`1[System.Decimal]]
So is there a way from the Entity or its Attributes or some other pathway of object/methods/properties to just get the answer as the field Type name? Or is there a wrapper of some kind I can get to that will let me handle (convert to/from) Hyperlinks? I am open to other ideas. Since the fields.Add() is different by "FieldType" this would be really helpful.
It's kind of simple, but needs a bit more code because of the dynamic nature of Razor. Here's a sample code that should get you want you need:
#using System.Collections.Generic;
#using System.Linq;
#using ToSic.Eav.Data;
var type = AsEntity(Content).Type;
var attributes = type.Attributes as IEnumerable<IContentTypeAttribute>;
var typeOfAwards attributes.First(t => t.Name == "Awards").Type; // this will return "Entity"
I created a quick sample for you here: https://2sxc.org/dnn-tutorials/en/razor/data910/page

AS3 Dynamic variable naming

Is dynamic variable naming like this possible in ActionScript 3?
for (var xz=0;xz<10;xz++){
var this['name' + xz]:Number = xz;
}
You should use an array for this kind of list of variables.
While you can create properties dynamically, you usually want to avoid it. Especially in your case, where the actual identifier of each variable is not a String, but a number. So why not use something that does exactly that: identify its elements by a number? And that's exactly what an array does.
code example:
var xzs:Array = [];
for (var xz:uint = 0; xz < 10; xz++){
xzs.push(xz);
}
Short answer is: no, you can't declare at runtime typed properties.
Long answer is: kinda.
If you want to create new typed properties you'll have to store them in a Vector<>.
Anything else would let you do it but untyped, dynamic class, store in object, store in array, etc ...
Yes, it sure is - AS3 comes from ECMA script, so this is setting a property to an object (in this case it's this). So you can dynamically set properties. But you are a little bit wrong about how to do it - there is no need to use var, because you don't declare it, you set it. It's like using:
this.propertyName = 'value';
From now on, this will have propertyName equal to 'value'. Therefore you should just use:
this['name' + xz] = xz;
That's all!
Edit: as BotMaster mentioned - if you are using classes and you want to dynamically add properties, the class must be set as dynamic. Most of the commonly used ones are already dynamic (as Aaron mentioned :)).
I didn't go into much details as I think you simply need to do this on your timeline. If not - please specify this in your question so that you can get more accurate answer than this one. The same goes if your new property needs to be typed (can't think of any point wanting this) - you should see BotMaster's answer :)

Removing commas from multidimensional arrays

I'm having a bit of trouble removing commas from an array when I go to write it to a cell in google spreadsheets. array.join(''); doesn't seem to do the trick. I would appreciate any help. Also, anchor[i][j].join('') returns an error.
Here is a bit of code:
anchor[i][j].join('') returns an error
anchor[i].join('') doesn't seem to have an effect.
for (var i=0; i(less than) rangeKW.length-2;i++){
anchor[i] = [];
for (var j=0; j<rangeURL.length;j++){
anchor[i][j] = ahref + rangeKW[i] + ahref1 + rangeURL[j] + "</a>|";
}
}
cell.setValue("{" + anchor);
}
Suppose you have
var x = [[1,2,3],[4,5,6]]
Then either of these lines will give you "123456":
Array.prototype.concat.apply([], x).join('')
x.map(function(a){ return a.join(''); }).join('');
The first one constructs the array [1,2,3,4,5,6] and then joins it. The second one joins each inner array first, constructing ["123", "456"] and then joins that. I think the first one is likely to be a tiny bit more efficient, although we are talking peanuts here, but the second one gives you a bit more control if you want to put something different between rows and columns.
In both cases, this doesn't change the original value in x. You can assign the new value to x if that's what you want.
array.join() works with "normal" arrays, by normal I mean 1 dimension and applies to the array itself, not on an single element (error on [i][j]), beside that I don't really understand what you want to do and how your code is related to your question ...
concerning anchor[i].join('');// doesn't seem to have an effect. I don't know how many elements are in there and how they look like but the syntax is correct. You can also use toString() if you want to make it a CSV string.
EDIT : (thanks for the information in comments.)
I think the easiest way (or at least the most clear) would be to create a couple of new variables that you could log separately to see exactly what happens.
for example
var anchorString = anchor[i].join(); // or toString() they both return strings from 1D arrays
Logger.log(anchorString)
if every index i of anchor have to be in the same cell then write it like this in the i-loop :
var anchorString += anchor[i].join();
Logger.log(anchorString)

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];

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

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