Skip to main content
Parts4Arts
Inspiring
February 10, 2022
Answered

the best way to bring JSON and UI together

  • February 10, 2022
  • 3 replies
  • 3935 views

Hello again,

JSON is good for saving presets. However, I am looking for an easy way to bring JSON and the UI variables together.

 

My project: I have over 50 JSON entries in a javascript project for Illustrator that need to be loaded, changed and saved again in a UI.

It is very annoying to have to call a function like 'function fJSON2UI()' every time a change is made, which transfers the values from JSON to the variables for the UI and back again.

 

The wish: It would be simpler if I could write in the UI:

JO.uiFile = gInput1.add ("statictext", undefined, "(no file)"); 

But this does not work.

 

QUESTION: Is there a trick or shortcut to connect the JSON and the variables of the UI more easily?

 

Part of my Javascript:

var vJSON = {
  uiFile: "~/Desktop/"
};
var w = new Window ('dialog {text: "Preferences", orientation: "column", alignChildren:["fill","fill"], properties: {closeButton: false}}'); 
gInput1 = w.add ('group {orientation: "row"}'); gInput1.alignment = "left";
bInput1 = gInput1.add ("button", undefined, "Load");
vInput1 = gInput1.add ("statictext", undefined, "(no file)"); vInput1.characters = 40;
bInput2 = gInput1.add ("button", undefined, "Save");
bInput1.onClick = function () { 
  vResult = File.openDialog( "Select a JSON-file:", "JSON:*.json" );
  if (vResult != null) { vInput1.text = vResult; fReadJSON(vResult); 
     fJSON2UI();
  } 
  else { vInput1.text = "(keine Datei gewählt)"; vResult = null; }
  bInput1.active = false; // reset button
} // end of bInput1.onClick function ()
bInput2.onClick = function () { 
  if (vInput1.text != "(keine Datei gewählt)") { 
    vFile = new File(vResult); vResultSave = vFile.saveDlg( "Save as JSON-file:", "JSON:*.json" ); }
  else { vResultSave = File.saveDialog( "Save as JSON-file:", "JSON:*.json" ); }
  if (vResultSave != null) { vInput1.text = vResultSave; fWriteJSON(vResultSave); 
    fUI2JSON(); 
  }
  bInput2.active = false; // reset button
} // end of bInput2.onClick function ()

w.show();
function fReadJSON(pFile){ } // read the JSON file
function fWriteJSON(pFile){ } // write the JSON file

function fJSON2UI() {
  vInput1.text = vJSON.uiFile; // and 49 variables more
}
function fUI2JSON() {
  vJSON.uiFile = vInput1.text; // and 49 variables more
}

Looking forward to your help,

– j.

This topic has been closed for replies.
Correct answer Parts4Arts

I think you should focus on 3 methods which are 1) create the UI, 2) set data to UI, 3) get data from UI.
Now assuming there's a javascript object like this:
var x = {
  prop_1 : "ABC",

  prop_2 : 123,
};
You can create the UI using a loop and push the UI items to a data object to store for reference.
var uiItems = {};
for (var all in x) {
  uiItems[all] = someParentGroup.add("edittext", undefined, "");
  uiItems[all].characters = 10; // some default width to the box.
}

Then you can use another block of code to form another method with that can set the values:
for (var all in x) {
  uiItems[all].text = x[all];
}

You can use the uiItems object to create a method which gets the data:
var result = {};
for (var all in x) {
  result[all] = uiItems[all].text;
}


Thanks for your help.

Here is my solution. It works in AI v26.0.3.

var JO = { JSname: "Plus", JSversion: "1.0", uiProject: "Mustermann", uiPrintFormat: "ai", uiCuttingFormat: "pdf", uiPrintSaveFolder: "~/Desktop/", uiPrintSave: true, uiCuttingSave: true, uiPageSize: "A4", uiGridSheetWidth: 210.0, uiGridSheetHeight: 297.0, uiPageMarginLeft: 7.0, uiPageMarginRight: 7.0, uiPageMarginTop: 7.0, uiPageMarginBottom: 7.0, uiShowPageMargin: true, }; // the JSON object
var aUI = new Array; // the values for the UI
aUI.text = ""; aUI.value;

var aJSONkeys = new Array; // the JSON keys
var vSplit; // split the JSON with comma + space or comma only

function fJsonString(pObject) { // ObjectToString 
	var vString = ""+ (pObject.toSource());
	return fDeleteFL(vString,")"); // tip: return without ( ... ) at start and end
} // end-of-function

function fJsonParse(pString) { // String to Object 
	return Function('"use strict";return (' + pString + ')')();
} // end-of-function

function fDeleteFL(pString,pLastChar) { // delete the first and the last char of a string
	return pString.substring(1, pString.lastIndexOf(pLastChar));
}

var vQuote = String.fromCharCode(34);
function fJSONtoUI(pJSON) { // convert values of a linear JSON to a linear array
	var vJString = fJsonString(pJSON);
	if (vJString.indexOf(", ") > -1) { vSplit = ", "; } else { vSplit=","; } // comma + space or just comma
	vJString = fDeleteFL(vJString,"}");
	var aArray = vJString.split(vSplit)
	for (x=0; x < aArray.length; x++){ 
		var aRow = aArray[x].split(":"); 
		aJSONkeys.push(aRow[0]);
		if (aRow[1].indexOf(vQuote) > -1) {aUI.push({text:aRow[1]});}
		else {aUI.push({value:aRow[1]});}
	} 
	return aUI;
}

function fUItoJSON(pArray) {
	var vString = "{";
	for (x=0; x < pArray.length; x++){ 
		if (aUI[x].text != undefined) { 
			vValue = aUI[x].text; 
			if (vValue.indexOf(vQuote) != 0) { vValue = vQuote+vValue+vQuote;} // add the quotes again
		}
		else { vValue = aUI[x].value; }
		vString += aJSONkeys[x] + ":" + vValue + vSplit; 
	}
	vString = vString.substring(0, vString.lastIndexOf(",")) + "}";
	return fJsonParse(vString);
}
// ---- main script ----
aUI = fJSONtoUI(JO); // run this function first
// Test with a small UI
var myWindow = new Window ("dialog", "Form");
myWindow.add ("statictext", undefined, "0. Name:");
aUI[0] = myWindow.add ("edittext", undefined, fDeleteFL(aUI[0].text,vQuote));
aUI[0].characters = 30; aUI[0].active = true;
myWindow.add ("statictext", undefined, "10. Number:");
aUI[10] = myWindow.add ("edittext", undefined, aUI[10].value);
aUI[10].characters = 30;
myWindow.add ("statictext", undefined, "7. Checkbox:");
vC7 = myWindow.add ("checkbox", undefined, "show it"); vC7.value = aUI[7].value;
vC7.onClick = function() { aUI[7].value = !aUI[7].value; } // boolean not function
var buttons = myWindow.add ("group")
buttons.add ("button", undefined, "OK");
// show dialog
myWindow.show();
JO =  fUItoJSON(aUI); 
// show the result in JSON
alert(fJsonString(JO));
// end of javascript

 You can change the there fields in the UI and it will be part of the JSON, when the dialog is closed.

Only the the checkbox I need a extra variable. But that's much better as to create over 50 for every JSON entry.

 

Have a good time and good night from Germany.

– j.

3 replies

pixxxelschubser
Community Expert
Community Expert
February 11, 2022

Hallo Jens,

Bitte verstehe mich nicht falsch. Aber die ganze Herangehensweise ist doch nur eine „Krücke“. Vielleicht verstehe ich auch einfach dein Gesamtkonzept nicht wirklich und ich weiß nicht, was du wirklich benötigst.

 

Es stimmt, Objekte - nennen wir sie einmal mehrstufige Arrays - sind mit Javascript nicht besonders gut zu händeln. Allerdings kann man sie meiner Meinung nach doch recht simpel „auflösen“ und so mit den Elementen des Objektes ganz gut umgehen.

 

Ich glaube nicht, dass es wirklich notwendig ist, alles umständlich in einen String umzuwandeln. Dann mühseliges Hin- und Herspringen zwischen Funktionen mit diversen verschachtelten IndexOf- und If-Else Ersetzen-Vorgängen, For-Schleifen und anschließendem mehrfaches Rückumwandeln in multiple Arrays …

 

Mein Vorschlag:

Lies die Werte direkt aus - oder speichere die keys and values in einem Array zwischen. Versuche z.B. einmal den nachfolgenden Weg:

 

var JO = { JSname: "Plus",JSversion: "1.0",uiProject: "Mustermann", uiPrintFormat: "ai", uiCuttingFormat: "pdf", uiPrintSaveFolder: "~/Desktop/", uiPrintSave: true, uiCuttingSave: true, uiPageSize: "A4", uiGridSheetWidth: 210.0, uiGridSheetHeight: 297.0, uiPageMarginLeft: 7.0, uiPageMarginRight: 7.0, uiPageMarginTop: 7.0, uiPageMarginBottom: 7.0, uiShowPageMargin: true, }; // the JSON object
var aUI = new Array (); // the values for the UI
var aJSONkeys = new Array (); // the JSON keys
var key;

// entweder direktes Auslesen
alert("value 1 direkt: " + JO.JSname);
alert("value 4 direkt: " + JO.uiPrintFormat);

// oder zwischenspeichern in "deinen" Arrays
fJSONtoUI(JO);

alert("length of aUI array: " + aUI.length + "\nfirst key: " + aJSONkeys[0] + "\nfirst value: " + aUI[0]);
alert("length of aJSONkeys array: " + aJSONkeys.length + "\nfourth key: " + aJSONkeys[3] + "\nfourth value: " + aUI[3]);

function fJSONtoUI(pJSON) {
    for (key in pJSON) {
        aJSONkeys.push(key);
        aUI.push(pJSON[key]);
    }
    return aUI, aJSONkeys;
}

 

 

oder als Variante „dein Weg“

(über String-Konverierung-->Mehrfach-Ersetzen-->Array-Zwischenkonvertierung-->FinaleArrays)  nur etwas aufgeräumt und optimiert:

 

var JO = { JSname: "Plus",JSversion: "1.0",uiProject: "Mustermann", uiPrintFormat: "ai", uiCuttingFormat: "pdf", uiPrintSaveFolder: "~/Desktop/", uiPrintSave: true, uiCuttingSave: true, uiPageSize: "A4", uiGridSheetWidth: 210.0, uiGridSheetHeight: 297.0, uiPageMarginLeft: 7.0, uiPageMarginRight: 7.0, uiPageMarginTop: 7.0, uiPageMarginBottom: 7.0, uiShowPageMargin: true, }; // the JSON object
var aUI = new Array (); // the values for the UI
var aJSONkeys = new Array (); // the JSON keys

var aArray = JO.toSource().toString().replace (/[(){}\"]/g, "").split(/, ?/);
for (x=0; x < aArray.length; x++) {
    var aRow = aArray[x].split(":");
    aJSONkeys.push(aRow[0]);
    aUI.push(aRow[1]);
}

alert("length of array: " + aUI.length + "\nfirst value: " + aUI[0]);
alert("aUI:\n" + aUI);
alert("aJSONkeys:\n" + aJSONkeys);

 

 

Parts4Arts
Inspiring
February 11, 2022

Guten Morgen und Danke für Deinen Code.

Schön, wenn es so einfach wäre. Jedoch brauche ich für die Eingabefelder Variablen wie vInput, die entweder vInput.text für Texteingaben oder vInput.value für Zahlen und Boolean. Zum Aktualisieren der Eingabefelder nach dem  Öffnen/Lesen der JSON Datei muss die Zuweisung mit Gleichheitszeichen erfolgen. vInput.text = "Neuer Text". Das nur Auffüllen des Array mit neuen Werten reicht nicht.

Meine Lösung mag nicht elegant sein, jedoch sie erfüllt ihren Zweck. 🙂

– j.

Silly-V
Legend
February 11, 2022

I have provided examples with the large library in them. There are numerous problems which could potentially be very bad issues with a non JSON-object approach, but one issue is that as pixxel implied, is that a home-made function that does anything other than produce the same exact result as the JSON object (by doing all the things to all the things that it does), you only will get valid JSON by accident (so you might as well use .txt extension anyway; it's an arbitrary text format of your engineering at this point). It could be very well that you are going to create conditions to enforce this 'accident' 100% of the time, however it is dangerous.
So, indeed Ai does not have the JSON object, that's why in my screenshot, in my snippets all over this thread there is the huge library pasted right in, its the 'JSON object'. It's sure scary and ugly, but don't worry: just put it into a separate file called JSON.jsx and use #include "path/path/JSON.jsx" in your scripts to take advantage of this object. Try to paste my last large snippet into your ESTK and view the magic results, it is obvious you have not done this yet and you are missing out indeed.

pixxxelschubser
Community Expert
Community Expert
February 10, 2022

Hallo Jens,

mit JSON kann ich dir leider nicht weiterhelfen.

 

Deshalb nur eine kleine Randnotiz

Es gibt sicherlich Dutzende unterschiedlicher Wege, um ans Ziel zu kommen. Könnte es sein, dass z.B. deine fJsonString () function durch eine etwas abgewandelte Schreibweise direkter ausgeführt wird?

function fJsonString(pObject) { // ObjectToString 
	//alert( pObject.toSource().toString().replace (/[()]/g, ""));
	return pObject.toSource().toString().replace (/[()]/g, "");
}

 

 

Parts4Arts
Inspiring
February 10, 2022

Hallo Pixxelschubser

Du hast recht, mit regexp geht's auch bzw. besser. Ich bin einfach zu sehr noch in der klassischen Programmierung verhaftet, dass ich eher über javascript-string-befehle (nach)denke und gehe. 🙂

 

JSON ist eine sehr lohnende Sache, wenn es um Voreinstellungen und Datenaustausch geht. Komplexe Strukturen habe ich damit bisher nicht gebaut. Jedoch meine über 50 Eingabefehler in einer Benutzeroberfläche, die sechs Karteireiter umfasst, kann ich einfach als Textdatei (Endung .json) schreiben und lesen und damit die Benutzeroberfläche auffüllen, so dass der Anwender ggf nur noch Änderungen eintragen kann, wie z. B. den Projektnamen.

Bis auf die Checkboxen kann ich zwischen JSON und struktiertem Array einfach hin- und herwechseln. Feine Sache.

 

Schönen Abend

– j.

 

Nachtrag: Hier noch ein Ausschnitt aus der Benutzeroberfläche. Nach dem Laden der JSON-Datei wechselt der Name für das Projekt und viele, viele andere Felder auch.

Silly-V
Legend
February 10, 2022

You can only do something like this:

Create an object literal or class which can be used to help describe each one of the 50 variables.
var JsonObjTemplate = {

  name : {

    name : "name", // helps to locate this object if it ends up being inside an array & not this template for some reason at run-time.
    type : "string", // This is a fundamental necessary property which helps with what sort of UI control to produce.
    defaultValue : "ABCD", // a property that helps future operations do something unique.
  },
  age : {
    name : "age",
    type : "number",
    defaultValue : 0,
  },

};
Create a method or a bunch of methods which can operate on the data template object and show the variables as some sort of UI controls.
One way to do this is to have not a lot of separate sections but to have one listbox which can hold an unlimited number of items and then another area such as group or panel which has orientation = "stack" and different UI controls can be hidden or shown based on the selection in the listbox.
Another way is to maybe not have a listbox and have any arbitrary UI you please.
Here's an example of a method that can output a control based on the data template.
If some code isn't working due to a need for a polyfill such as JSON object, please obtain any polyfills you might need:
function getUIControl (typeDescObj, parent) {
  var type = typeDescObj.type;

  var name = typeDescObj.name;

  var control = null;

  switch (key) {
    case "string":
        control = parent.add("edittext", undefined, typeDescObj.defaultValue);
      break;
    case "number":
        control = parent.add("slider", undefined, typeDescObj.defaultValue); // The typeDescObj would need extra properties for different controls. Such as 'min/max' on a 'slider'.
      break;
    default:
        throw new Error("There wasn't a control-creation block found for '" + name + "' [" + type + "].");
      break;
  }

  return control;
}

Then you'd make a window, run the variables template through a loop and add all the controls you would need.
You may want to create a method for getting all data from the UI into a JSON object and also for setting the data to an existing UI window.
It is possible to add your own custom prototypes to ScriptUI widgets such as 'edittext'.
EditText.prototype.setValue = function (val) {

  this.text = val;

};
With such prototypes you can add additional arguments, or you can add additional properties to instantiated EditText objects which can be used by the prototype getValue/setValue methods to do more than just `this.text = ""`.
For example, assuming a second argument of "typeDescObj" is passed in. Before setting a text value, this prototype method could now check for the 'type' property or any other properties and do such manipulations as math rounding or replacing unwanted characters with an underscore '_'.
The advantage of using a prototype is that throughout the code you can always rely on your EditText(s) and StaticText(s) being able to get and set values without having to paste a method everywhere. However, it doesn't have to be a prototype, it can be just a regular function that you do paste everywhere and there's nothing wrong with that.
It can be a little bit of work, but if you do this then for all future time you'll be able to make verbose UIs for many differents data objects - as long as your code supports those properties. So if you are doing a new script that wants a new data type, all you do is add the new block in your code library and now it's enabled in all of your scripts!

 

Here is an example of my own code which uses these techniques. I make a settings menu out of this which doesn't use any listbox or dynamic display area per row, it just adds sections to one dialog box. The objective for this was never to have more than 10 or so settings variables. If it were to increase, I would make it into a listbox for sure. One can also go all the way with this 'architecture' by creating nested controls which are also dynamic like so. Your control object isn't an EditText, it's a Group or Panel which has its own "setValue" and "getValue", and those can wrap the same structure meaning it can have nested EditTexts and StaticTexts and other Group or Panel on and on.

 

Parts4Arts
Inspiring
February 10, 2022

Thank you very much for your answer. Sounds to complicated to my, because I have a very simple, linear JSON of string and booleans. So I guess the fastes way to put JSON into the variables and back again is just a long list in two functions like this:

function fJSONtoUI() {
  vInput1.value = JO.uiAutoSave; 
  vInput2.text = JO.uiFileName; 
  // more to come
}
function fUItoJSON() {
  JO.uiAutoSave = vInput1.value;
  JO.uiFileName = vInput2.text;
  // more to come
}
To tested yet, but perhabs I can use array to let the javascript do the job.
I will try and report about it.
– j.
Silly-V
Legend
February 10, 2022

Don't think of it as putting something back into "JSON", it's simply putting all the items into an object. That object can be written as JSON or anything else. So, what you are really after is loops and properties that can assign values from UI widgets to the object. If you have a fJSONtoUI() method with 100 lines of  x.text = "ABC", that's a valid way to get to your goal surely.