#Dynamic listeners for classes?

1 messages · Page 1 of 1 (latest)

oblique sky
#

I mainly code Javascript for NodeJS projects, so I do understand I may be at fault for trying to incorrectly attempt to provide a solution to my needs.

I'm trying to implement a custom class that will handle all GUI interfaces and hopefully keep it as a standard with most of my projects to keep my workflow fast and if there are any breaking changes, all I have to do is edit this one class.

The following are the source code:
ScreenManager - https://pastebin.com/EBiYRukm
Screen - https://pastebin.com/Gc56fkQt

Please assume the main driver (Command) is running the following line.
new ScreenManager(plugin).get("Screen1", (Player) commandSender).display((Player) commandSender);

My interpretation is that I want the events handler to come up when necessary when this class is active, meaning it'll handle it's own click for it's own gui screen. When the command is finish, the class dispose itself as part of memory management and so does the listeners.

However I have encountered an issue that stops my code from running due to the fact that in my main plugin driver class, I register the events with a blank class instance of the ScreenManager and it actually doesn't parse it to the commands instance of the ScreenManager itself. (My fault here and this is where I'm kind of stuck)

I want some feedback in regards to how to encounter and develop a solution for this as I want to improve on myself and hopefully any new skills I pick up from here or any point of view I will be able to transfer it to other technologies and languages. Thank you for reading!

mental coral
#

Can you summarize what you want in just a sentence or something? XD

oblique sky
#

Yeah sorry my bad

#

Trying to make custom GUI class with it's own click event. Not worky cause brain dumb and I can't figure out how to route the event to execute per class gui.

mental coral
#

Oh I'm just a bit tired so that's why I don't have the effort to read some entire paragraphs.

#

ah okay

oblique sky
#

I have multiple classes that holds GUI data like items, the action to display and ofc how to handle click, drags and other events

#

Although that sounds okay on paper, it seems to be bad in practical since the plugin driver has to actually register the one instance of the class and that kinda breaks the idea I had

mental coral
#

Are you familiar with design patterns in object oriented programming?

oblique sky
#

Ya

mental coral
#

So yeah usually you have one listener

#

and in that listener you setup your event callbacks

#

@EventHandler onInventoryClick(InventoryClickEvent e)

#

as such ^

oblique sky
#

Yeah

mental coral
#

then you have store a set of observers which you call, so essentially the observers will be your strategies for handling the event type

#

Hmm I can write an example Ig

wooden fulcrum
#

Just a Consumer<T>

mental coral
#
class InterfaceManager {
  void handleClick(InventoryClickEvent event) {
    var inv = event.getClickedInventory();
    var holder = inv.getHolder();
    if (holder instanceof Menu menu) {
      menu.onClick(event);
    }
  }
}
abstract class Menu implements InventoryHolder {
  protected Inventory inv;

  Menu(Inventory inventory) {
    this.inv = inventory;
  }

  @Override public Inventory getInventory() { return this.inv; }

  abstract void onClick(InventoryClickEvent event);
}
class InventoryListener implements Listener {
  InterfaceManager interfaceManager;

  InventoryListener(InterfaceManager interfaceManager) {
    this.interfaceManager = interfaceManager;
  }

  @EventHandler void onClick(InventoryClickEvent event) {
    this.interfaceManager.handleClick(event);
  }
}

//two ways of using Menu
class SimpleMenu extends Menu {
  SimpleMenu(Inventory inv) {
    super(inv);
  }
  
  Consumer<? super InventoryClickEvent> clickHandler = e -> {};

  void setClickHandler(Consumer<? super InventoryClickEvent> action) {
    this.clickHandler = action;
  }

  void appendClickHandler(Consumer<? super InventoryClickEvent> action) {
    this.clickHandler = this.clickHandler.andThen(action);
  }

  @Override void onClick(InventoryClickEvent event) {
    clickHandler.accept(event);
  }
}

// or

class DerivedMenu extends Menu {
  DerivedMenu() {
    super(Bukkit.createInventory(...));
  }

  @Override void onClick(InventoryClickEvent event) {
    //basically handle it here
  }
}```
#

using something like SimpleMenu can be preferred if the functionality isn't something exceptionally behavioral

#

but else I'd suggest going with a derived type of Menu

#

also as you're a javascript programmer you're probably familiar with first class function objects

oblique sky
#

Just briefly looking over this, am I correct with the flow that it'll be InventoryListener -> InterfaceManager -> SimpleMenu|DerivedMenu

mental coral
#

in java you'd use something like Runnable, Consumer, Supplier, Predicate, Function with a lambda as java doesnt have that

#

yes

oblique sky
#

Ahh i see

mental coral
#

The idea here is that you'd be able to substitute InventoryListener without impacting InterfaceManager

oblique sky
#

Lambda is pretty much arrow functions right?

mental coral
#

yep

oblique sky
#

Seems to be understandable and I like this interpretation and the effort you've put in.

I'm a little confused on how this line functions.
if (holder instanceof Menu menu) { (Line 5)

I understand how each GUI can contain holders, and how declaring the ownership of a GUI to a class can represent that class instance itself, but why do we declare Menu menu?

#

And then somehow the next line uses that new var which isn't specified, to be able to run the handleClick

#

Does it somewhat extract the class from that?

mental coral
#

if (holder instanceof Menu menu) {

}

is similar to

if (holder instanceof Menu) {
Menu menu = (Menu) holder
}

so it checks if the object of holder is of the type Menu, if so then it creates casts it to the type Menu with the variable menu, where menu would then be a variable that lives inside the declared if statement.

#

Java's var infers the type.

oblique sky
#

Ahhhh thats interesting

mental coral
#

yeah

#

the type is there, just not declared explicitly

#

so unlike JavaScript's var, you would not be able to assign an object of different type to that variable

oblique sky
#

Yeah we dont have casting in js

mental coral
#

dynamically typed languages 🥴

oblique sky
#

yeah haha

mental coral
#

But yeah the gist here is the type theory Java sort of embraces

#

classes are blueprints to create objects, but they can also be types

oblique sky
#

its breezy if it's your first language, but after learning a couple of typed ones it iffy af (TS ftw)

#

Mm yeah

mental coral
#

ah yeah TS

oblique sky
#

Awesome, i'll give that example a go since I completely understand it. Thank you for helping me and providing a really good and easy example uwu

mental coral
#

no worries,

#

however keep in mind the InventoryHolder is an api interface

#

so in some versions you may get weird side effects due to incorporating your own implementation of the api

#

just mentioning this so md5 or choco wont come in here nitpicking it

oblique sky
#

Oh interesting

#

I'll keep that in mind, thanks!