Is it possible to add some sort metadata to a node, object or group?

Hey, I was wondering if it is possible to add some sort of meta data or property information to a node, object or group? For example if I had a group of objects that made up a button component on an art-board, could I add some data to that group to say that this is a button? Then in my plugin panel, I would provide some options relevant to button objects.

2 Likes

Hi @HanielMoore,

SceneNode::pluginData should get you what you’re looking for: scenegraph · Adobe XD Plugin Reference

This is a simple property every SceneNode has which you can use to read and write metadata:

mySceneNode.pluginData = { /* [...] */ };

console.log(mySceneNode.pluginData);

I hope this helps,
Happy coding :slightly_smiling_face:,
Pablo

4 Likes

Hi Pablo,

Thank you for your reply! So this would be something like the below?

mySceneNode.pluginData = {
“type”: “button”
};

1 Like

Correct. pluginData could be any (I believe JSON-serializable) type, but objects are, of course, great here as you can store multiple values in a key-value-pair manner. If, however, the only thing you ever store is the type, you could equally use

mySceneNode.pluginData = 'button'; // or whatever other type ;-)
2 Likes

Does a simple string work? I thought it had to be a string in JSON format .

Metadata specific to your plugin. Must be a value which can be converted to a JSON string, or undefined to clear the stored metadata on this node.

var object = {type: "button"};
mySceneNode.pluginData = JSON.stringify(object); // or whatever other type ;-)

Hi @Velara,

According to RFC 7159 (and www.json.org), JSON text is defined as

JSON-text = ws value ws

where

value = false / null / true / object / array / number / string

Therefore, JSON can also have a string or number as “root”, meaning "some string" is as valid in JSON as, e.g., { a: "some value" }.

Therefore, a string does work as it actually is JSON. The only thing that won’t work are ring-structures (a is a property of b and b is a property of a) and functions, which, of course, can’t get serialized as JSON.

According to the documentation, however, it could be any type:

sceneNode.pluginData : *

Therefore, it is (in my opinion), fair to conclude that any JSON-serializable type should work here and then automatically gets serialized and deserialized…

2 Likes

So XD is converting the object into JSON in it’s setter… :thinking:

I read it as:

Must be converted to a JSON string

I’ve been doing that manually. :frowning: :doh:

var myObject = {a: "button", b: "name" };
sceneNode.pluginData = JSON.stringify(myObject);

Note to self: don’t read documentation half awake or half asleep. :coffee:

1 Like

I mean, in truth, it’s probably much easier to debug that way. Otherwise, if something ever went wrong in the XD-internal JSON (de-/) serialization, that could be a pretty difficult problem to track down… :slightly_smiling_face:

1 Like

Further to this, how would I go about modifying a property once it is set? I have tried a couple of different ways and it doesn’t seem to work. One of the ways I tried was:

mySceneNode.pluginData.mode = “dark”;

Any suggestions?

PluginData is stored as a string in JSON format. So you would parse the plugin data back into an object first.

var data = {};
// get existing plugin data
if (sceneNode.pluginData!=null && sceneNode.pluginData!="") {
   data = JSON.parse(sceneNode.pluginData);
   // verify its an object or the type you expect here
}

// update data
data.mode = "dark";

// update plugin data value
sceneNode.pluginData = data; // object is converted by XD API into JSON string
sceneNode.pluginData = JSON.stringify(data);  // or you convert it to string

Hi Velara, thank you for your reply. I’m pretty sure it is already an object. I have tested with the following which states it is an object.

console.log(typeof button.pluginData);

Here is what I use:

/**
 * Get data saved on the pluginData property or null if not set
 * @param {SceneNode} item 
 * @returns {Object} data
 **/
function getSceneNodePluginData(item) {
	var data = null;

	try {
   	   	if (item.pluginData) {
   	   	   var value  = item.pluginData;
   	   	   data = JSON.parse(value);
		}
	}
	catch(error) {
		log(error);
	}

	return data;
}


/**
 * Save data to pluginData property using a string value in JSON format
 * @param {SceneNode} item 
 * @param {String} value 
 * @returns {Boolean|Error} returns true if set or Error if cannot set plugin data
 **/
function setSceneNodePluginDataValue(item, value) {

		try {
			item.pluginData = value;
			return true;
		}
		catch(error) {
			return error;
		}
}

More complicated set scene node data:

/**
 * Save data to pluginData property
 * @param {SceneNode} item 
 * @param {Object} data 
 * @param {String} editLabel 
 * @returns {Boolean|Error} returns true if set or false or Error if cannot set plugin data
 **/
function setSceneNodePluginData(item, data, editLabel = null, showingPanel = false) {
	const { root, selection } = require("scenegraph");
	const { editDocument } = require("application");

	try {

		if (showingPanel) {

			editDocument( () => {
				item.pluginData = JSON.stringify(data);
			});

		}
		else {
			item.pluginData = JSON.stringify(data);
		}

		return true;
	}
	catch(error) {
		var errorString = error + "";

		if (errorString.indexOf("Panel plugin edits must be initiated from a supported UI")!=-1) {
			// Call is from panel. Needs to wrap in editDocument(). Set showingPanel
		}
		else if (errorString.indexOf("is not permitted to make changes from the background.")!=-1) {
			// call must be part of click event? or promise not completed (or await async related)
		}
		else if (errorString.indexOf("There are no edit records found.")!=-1) {
			// ?
		}
		else {
			log(error);
		}

		return error;
	}

	return false;
}

You don’t have to manually convert into JSON strings – as the docs say, you can directly use any type which is JSON-compatible …as in the post above from @HanielMoore:

mySceneNode.pluginData = {
    type: "button"
};

However, as with any object-valued property on scenenodes, you can’t trigger an update just by overwriting one field of that object. You need to re-invoke the pluginData setter:

var data = sceneNode.pluginData;
data.mode = "dark";
sceneNode.pluginData = data;

(or you can make this into a one-liner using Object.assign())

1 Like

Plus I imagine doing your own JSON-to-string-to-JSON conversion could easily miss optimizations that the direct .plugin setting could do.

Thanks @peterflynn that works perfectly for what I was trying to do!