Add constants for frequently used types

Add API to get the type and add constants for common types.

For example,

var sceneNode = selection.items[0];

if (getType(sceneNode)==Types.ARTBOARD) {
   // do something
}

Use Case:
Person makes plugin for modifying lines. They only want to apply to Lines so they use getType() function and check against static type Type.LINE.

Again (like in Is there a way to truncate the values in the path data?), I think that that’s something that’s up to the developers (and maybe libraries) to achieve this. This sort of type-checking is already possible (I’ve also written a little library to achieve this which I plan to make open-source soon), so that’s really nothing that belongs in the “native” APIs (since it’d only clutter up the API with things we can achieve without any further functionality). For more thoughts about why something like this shouldn’t get done in my opinion, see my comment on the linked request :wink:)

I’ve read that and I disgree. In many environments it’s standard practice to add constants for the classes that you refer to.

For example:

Event.CLICK, MouseEvent.MOUSE_CLICK, MouseEvent.MOUSE_OVER, MouseEvent.MOUSE_OUT

are all constants that you use when you write code in many environments.

And in an example:

function myMouseHandler (event) {

   if (event.type==MouseEvent.MOUSE_OVER) // do something
   else if (event.type==MouseEvent.MOUSE_OUT) // do something

}
1 Like

But those are things that you couldn’t really do without this functionality. What I’m saying is that the APIs shouldn’t get cluttered up with things that only make things which are already possible easier by adding abstraction. It’s possible to check the types of nodes. In your example, type is specifically of the enum-like type (and probably the only way to differentiate those kinds of events). Since, however, different node types are different classes in the APIs, it’s already possible and I would therefore argue that it doesn’t really match the example you’ve provided.

I think that that’s a very opinionated question and I can absolutely see your point. However, as it is opinionated, we’ll both continue to argue our points. I’m just saying this because I find that to be great and don’t mean to be rude – I just think that only good debates about this will lead to the best of results, so disagreeing is really nothing personal (even if I disagree with a lot of things from your last requests :wink:), but just stating my opinion in order to get to the best solution for all :slightly_smiling_face:

PS: I think you have a typo in the request’s title since I think you’re referring to “Scenenode types” and not “Artboard types” :wink:

Well, if you want to discuss it more than I would say there’s more to it.

If you were writing a system that delivered messages over the network you would want to keep it light weight. But for an API you want to have what people need in it as in form (final form) follows function. And adding more to it is not necessarily “cluttering it up”. Adding a well defined set of functions is rounding it out. It depends really. And it doesn’t cost anything performance wise to add more methods. For a framework maybe but not an API. It is up to the developer to keep the application snappy whether it is a large application or small.

BTW I used your SelectionChecker class and even contributed to it which leads to my second point. If you are reusing a general purpose class over multiple projects it might be a candidate to be in the API.

By “Artboard Types” I mean “Artboard [element] types” or “types in the Artboard”.

On that note JavaScript is an example, in my opinion of an API that has been underdeveloped for years until recently.

For example, the Array class had maybe 10 methods and then nothing has been added for decades. Instead of improving the API to add something like:

myArray.contains(value)

you have to use

myArray.indexOf(value)!=-1

and instead of:

myArray.clone()

you have to use:

myArray.slice()

and in HTML you have methods like:

myElement.parentNode.removeChild(myElement)

instead of

myElement.delete()

And now, we are seeing a lot of impressive improvements to JS.

1 Like

I agree that to a certain point, APIs can and should be extended. However, the thing that I think is the argument here is that the way we can currently do this isn’t much more complicated (node.constructor.name === type). Also, this functionality (in pretty much this exact form) can be easily achieved by a simple external library (although I also plan on releasing the SelectionChecker, I was actually talking about something much smaller and more lightweight without so many “complex” functions).

The thing that I think we should be careful about is at what point we get to oversimplifying things. I can agree that something like this could be useful, but the question is at what point we draw the line of “oversimplifying”. Personally, I don’t have a problem with the APIs extending (since it really doesn’t affect me). However, I really enjoyed that they are as lightweight and to the point as they are, making it easy for me to get into plugin development.

I really have nothing against this request in particular, but whenever it is easy to implement a similar system or use a library for it, I think it is important to check if it’s really needed or just “abstraction for the sake of abstraction”. I actually think that some sort of “standard library”, which could be open-source and include all such things (maybe also things like my storage helper, the object utilities and other things like that) could be useful, since I think such things are more of an extension to APIs than they are a “real” part of the APIs.

Personally, I feel like this already passes this line of “oversimplifying” (since it isn’t complicated to do it manually), and think abstraction should only be done to break down complex things (like e.g., math around matrices in transformations). However, this really is an opinionated question (where this really fine line of useful or oversimplifying is) and I absolutely understand your point of view as well – but especially because of this, I feel like this is a question (not just this request, but really the question in general), where we can only get to good solutions by seeing who prefers what (not only @Velara and me, but also everyone else), since as much as we could argue for our points, I really think there is no right or wrong here – we both draw the line of what is still useful at different locations, and that’s alright and it is pretty much up to Adobe to decide which way to go :wink: .

That’s a common error that intelligent people make. That is, because something is easy for you, because it comes naturally or it was easy for you to learn that you believe that it will be easy for others to do.

“We don’t need an API call for doing X because X is easy for me to do it.”

I can’t count the number of times that developers I know would say things like that. They don’t realize that what is simple for them may be difficult for others.

One of my conditions for if something becomes a variable, function, class, library, application or API is based on how often I need to use it now or in the future. How difficult it is has no bearing on if it’s included or not.

Yes we can do ( node.constructor.name === type ) but if you need to make changes in the future you would need to find every occurance of ( node.constructor.name === type ).

For example, I could write Math.round(value * Math.pow(10, 3)) / Math.pow(10, 3) every place I need to get a shorter number value or I could write a function that does it for me:

function getShortNumber(value, places = 3) {
   value = Math.round(value * Math.pow(10, places)) / Math.pow(10, places);
   return value;
}

Again, it doesn’t cost anything performance wise to have a full API. If you expose 100 interfaces or 10k it’s negligble. It’s inperceptable. Each method points to a different memory address. But theoretically, if it did weigh an API down then yes, I would weigh each addition and performance test along the way.

1 Like

Easy might not have been the ideal term for me to choose. What I meant to say is that it is doable with not too much work, or that the change wouldn’t lower the amount of work to get something like this to work by much.

The example I’ve chosen (math around transformations) also now feels intuitive to me (since I’m studying a subject at university that involves such things), but this really would go beyond the scope of a “regular” developer (as would knowing about the chemistry of some hardware or every other thing that usually isn’t part of a developer’s expertise). However, dealing with the information available and otherwise transforming data into a format that suits a purpose is pretty much the perfect description of the job a developer has to do (on the technical side).

The thing is that while I understand your point of view, the problem is that if everything that might simplify common tasks should get included in the APIs, we’ll have 1000 feature requests for slightly different storage systems, leaving the Adobe team to implement stuff we could already do on our own and not get to the stuff we actually can’t do on our own.

And I didn’t say we shouldn’t write functions that simplify our work :wink:. However, I also wouldn’t say that this should be a standard function of JavaScript. I really think that simplification should be part of some open-source effort so that the APIs can focus on making things possible when there’s a simple library doing this abstraction work where needed.

I’m actually not talking about performance. I meant that the APIs are very clean in what they provide, simplify and what they don’t simplify. It is currently pretty easy to get into plugin development. This, however, would change if the APIs were filled with thousands of auxiliary functions (the question I’m asking isn’t if this request should be implemented, but where we draw the line here), only there to satisfy all the requests of things people wanted to get simplified.

To me, the API (as it is) feels extremely intuitive since there is a clear line defined, making it clear what’s available (the core stuff and some slight abstraction when it would be overly complicated otherwise, like for transformations) and what’s not available (simplification of things already achievable with a “regular” developer skillset). I don’t say this line cannot shift. I’m just saying that this line should be a clear one for the APIs to feel intuitive and if A gets simplified, there are a thousand other things that also could get simplified, diminishing “real” progress of the APIs. Instead, I would find it useful to leave abstraction to the open-source community and progress in the API stuff to Adobe :wink:

1 Like

I agree. :slight_smile: That’s part of the prioritization process.

So what do you think about this. At the start of the plugin program the ImageFill class had a getBase64Data() method and it was later removed.

As an alternative, a way to get a base 64 image data string that involves 500 lines of code that writes an image to the hard drive, reading the image back in and then converting it from byte array to string all asynchronously was proposed and then an example was provided.

The plan is to add the method back in. Let me say another point.

If you use external libraries it is possible that it will be much slower. External libraries written in JS are interpreted. Internal APIs are extensions of native classes.

So yes you can write your own base 64 image string class. In the past I’ve done performance tests on bitmap data methods and the native classes were 5-10x as fast. This improvement was important to function of the project. I don’t know the performance of the previous getBase64Data() method compared to the workaround example.

1 Like

I agree. However, this – as you’ve said – is about a relatively complex task (500 lines of code :wink:), which is why I would argue that while I get your point, it isn’t really comparable in my opinion.

Again, I would agree that things that are performance-heavy deserve a place in the native implementation. However, we are talking about one constant string comparison against a constant comparison of another type here (which I really wouldn’t call performance-heavy).

Performance does – I agree with you here – validly justify implementing things on the native side (although I may not like it, there’s no way around that). However, this request isn’t about performance. It is about abstraction of a concept where abstraction will do nothing else than add a few more constants as well as a new attribute, so your comparison of the two values will be interpreted just the same as they are with node.constructor.name :wink:

In case anyone only reads the last posts here, I want to clarify once again that I’m not really talking about this particular request anymore. This really is a fundamental question about what should get abstractions and what not – this request doesn’t hurt me, it’s just that I think that if something like this gets implemented, the point where we stop to request things (or where things won’t get implemented anymore) only for simplification on our side (with no performance boost or any other justifications whatsoever) should be well-defined so the API doesn’t begin to feel inconsistent (actually making it much complicated and therefore achieving the opposite of simplification for some) :wink:

Have you considered satisfaction index? Or customer service support rating? There is a saying, “The customer is always right.” I don’t know if anyone says that and it’s a broad generalization but there’s something to it. They used to say that for every email or request you receive there are 100 people that feel that same way that haven’t sent an email.

An example of the opposite of that is the Stack Overflow effect. That is, it’s so horrible there that you avoid it at all costs. That you would rather start your own Stack Overflow. Every post is nearly shot down. There was an XKCD comic about it in the last year I can’t find it. Anyway, you may have people using your products not because they want to but because they may have no other alternatives. Being open to your user base creates loyalty and improves your product whether you think it’s important to you or not.

Another example is Photoshop a few years ago added 3D support. It was one of the first times a major product shifted into a new market. The reason given was that the digitial artists needed 3D support for the art they were creating. One of the use cases for it is box art or product branding. For example, wrapping an image on a mug or wrapping a design on a box.

Update:
I’m not suggesting that every feature I post should go into the API. But I’m adding my voice to an issue that if others feel the same they can add to it as well.

And here’s another thing. If you use the product every day there are small things you can do that help improve your process. That’s part of the goal of plugins. Same with considering a feature to the API.

1 Like

@Velara I think that serves as a good conclusion to our discussion. I agree with you on that point and might actually tend to second this request after our debate. Anyhow, I think it’s (been?) a great debate which is required to get to know another perspective and therefore be able to have a (very) educated opinion about.

In the end, folks at Adobe will have to decide where this line gets drawn. If that means including things like this is Adobe’s call, but I think that both of us have made good points and it isn’t easy to have a “correct” answer to that question. We both have our opinions and also won’t change how we think about the topic (since there isn’t any right or wrong here), but I think it is great to see that we both obviously care about the APIs and that there are “supporters” of both sides (at least one on both).

I hope that this discussion (both sides) will help make the APIs better, and would also “welcome” other opinions about the topic (since right now, we know of one person for it and one person against it, but don’t know a general opinion since I am as active in this discussion as I am since I think the APIs are great and should stay great, if that means including this or not depends on the opinion of the many – whatever that may be).

Last but not least, I wish you a great day and thank you for this great debate (which – at least for me – was very interesting and of which I hope it makes the importance of finding a good compromise of both worlds clear. Thank you for this great discussion :+1:

1 Like

Just got back from vacation today, so catching up… this thread has a me a bit confused as to the actual intent here – going back to the original request in the first post:

var sceneNode = selection.items[0];

if (getType(sceneNode)==Types.ARTBOARD) {
   // do something
}

This is how I would usually do it:

const { Line } = require("scenegraph");

const sceneNode = selection.items[0];
if (sceneNode instanceof Line) {
    /* operate on lines only */
}

Are there particular use cases where the above is insufficient? I’m not against this suggestion at all (and it is always a difficult line to walk: how far do you take an API), but I’m unclear ATM as to what the suggestion would bring.

Note also that I’m not against providing constants – for example the File I/O API does this a lot in order to avoid passing easy-to-get-wrong strings around. Just trying to see where instanceof falls down here.

2 Likes

I don’t have a problem using instanceof but if you don’t order instanceof correctly then you could reel in the wrong fish.

For example, if the inheritance chain of some classes changed at some point then if I had been using object instanceof Text and Text was changed to a base or interface so that a new PointText and AreaText extended it instance of might break. If I go by class name (or construction name) then it doesn’t matter what changes go on under the hood as long as the name doesn’t change.

This a function I wrote for the Statistics plugin. It doesn’t feel right:

function getDisplayName(type) {
	var value;

	switch(type) {
	 
		case "Ellipse":
		   value = "Ellipses";
		   break;
		case "Rectangle":
		   value = "Rectangles";
		   break;
		case "Path":
		   value = "Paths";
		   break;
		case "Line":
		   value = "Lines";
		   break;
		case "Text":
		   value = "Texts";
		   break;
		case "Group":
		   value = "Groups";
		   break;
		case "BooleanGroup":
		   value = "Boolean Groups";
		   break;
		case "RepeatGrid":
		   value = "Repeat Grids";
		   break;
		case "SymbolInstance":
		   value = "Symbols";
		   break;
		case "Artboard":
		   value = "Artboards";
		   break;
		case "Total":
		   value = "Total number of elements";
		   break;
		default:
		   value = type + "s";
	}

	return value;
}

So later I switched to using constants:

switch(type) {
 
 case Types.ELLIPSE:
	transformEllipse(item);
	break;
 case Types.RECTANGLE:
	transformRectangle(iteml);
	break;
 case Types.PATH:
	transformPath(item);
	break;
 case Types.LINE:
	transformLine(item);
	break;
 case Types.TEXT:
	transformText(item);
	break;
 case Types.GROUP:
	transformGroup(item);
	break;
 case Types.BOOLEAN_GROUP:
	transformGroup(item);
	break;
 case Types.REPEAT_GRID:
	transformRepeatGrid(item);
	break;
 case Types.SYMBOL_INSTANCE:
	transformGroup(item);
	break;
 case Types.ARTBOARD:
	transformArtboard(item);
	break;
 default:
	//throw Error("Type not found");
 }

Here is one for getting icons:

function getIconPath(type) {

    switch(type) {
 
	case Items.ELLIPSE:
	   break;
	case Items.RECTANGLE:
	   break;
	case Items.PATH:
	   break;
	case Items.LINE:
	   break;
	case Items.TEXT:
	   break;
	case Items.GROUP:
	   break;
	case Items.BOOLEAN_GROUP:
	   break;
	case Items.REPEAT_GRID:
	   break;
	case Items.SYMBOL_INSTANCE:
	   break;
	case Items.ARTBOARD:
	   break;
	case Items.TOTAL:
		if (searchAllArtboards) {
			return "icons/" + Items.ARTBOARD + "s Icon." + "png"
		}
		else {
			return "icons/" + Items.ARTBOARD + " Icon." + "png"
		}
		break;
	default:
    }

    return "icons/" + type + " Icon." + "png";

}

This isn’t the highest priority feature request as the discussion went over that extensively, but more as they say, a constant a day keeps the errors away. Probably no one says that :expressionless:

1 Like

Thanks for sharing the snippets, though – because that helps illustrate the use where having constants instead of instanceof would prove useful. instanceof isn’t exactly something use can use in a switch construct short of doing something really hacky like this:

switch (true) {
    case (sceneNode instanceof Line): console.log("line"); break;
    case (sceneNode instanceof Rectangle): console.log("rect"); break;
}

… which is so …???, I want to run away, screaming in abject horror.

2 Likes

This, however, could probably be simplified (or beautified) by using switch (sceneNode.constructor.name) :wink: – I’m not saying it’s pretty, but at least not as “hacky” as

Last, but not least, there are also node types which can’t be checked with instanceOf (which was a problem I only found out about when I created my SelectionChecker class). This, however, can really be fixed by using node.constructor.name.

If I’m not mistaken, the syntax @Velara would like to have can be achieved by using something like this:

function getType(node) {
  return node.constructor.name;
}

const Types = {
  ARTBOARD: 'Artboard',
  GROUP: 'Group'
};

with the few exceptions of things like BOOLEAN_GROUP and other child classes…

1 Like

Note that you can also do this:

switch (node.constructor) {
    case Ellipse:
        break;
    case Rectangle:
        break;
    // etc.
}

None of the schemes being discussed here are guaranteed to be 100% robust if the set of scenenode types is heavily reorganized though. When thinking about future-proofing, there are cases when you’d want to be inclusive of any potential future subclasses of a type (which instanceof does) and there are other cases where you’d prefer to exclude any unanticipated future subclasses (which constructor === and a hypothetical getType() === both do). The coding options that are already available let you make that choice on a case-by-case basis.

As @pklaschka mentioned though, you’re free to use these building blocks to create your own getType() helper method if you prefer that style.

Might just be my brain fried but I’m getting an error with this:

 if (item instanceof SceneNode) {

ReferenceError: SceneNode is not defined

When I require it using this:

const { SceneNode } = require("scenegraph");

Property ‘SceneNode’ does not exist on type “…scenegraph”

Revisiting this thread to how can you find out of if an object is a SceneNode?

Update:
It looks like it works if I import the type.

Visual Studio Code is giving the error but it works at runtime.

const { SceneNode } = require("scenegraph");

   if (item instanceof SceneNode) { log("it is a scene node"); }

@Velara what version of XD are you testing this in? The SceneNode base class wasn’t exported as a public identifier until XD 28 (March 2020 release).

1 Like

Wow, could have sworn I was using a year ago…

1 Like