Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add powermoves to stimulus-use #138

Open
Matts opened this issue May 4, 2021 · 6 comments
Open

Add powermoves to stimulus-use #138

Matts opened this issue May 4, 2021 · 6 comments

Comments

@Matts
Copy link

Matts commented May 4, 2021

Powermoves allow you to access controller functions as element properties, "powermove" being coined by this blog:
https://dev.to/leastbad/the-best-one-line-stimulus-power-move-2o90

Not sure how much this breaks Stimulus's semantics on how controllers should work and communicate. But If you think of a controller added to an element, as an enclosed component. This will make more sense.
You can use this to register certain attributes (& maybe overwrite default html attributes 🤔) to the element which can be called from anywhere.

Made a rough concept, will be willing to share if you agree that this is cool.

  1. In this example it may not make sense to use such functionality seeing as I could also just implement the logic on the left from within the AjaxFormController on the right. But does show roughly what is possible.
    image
  2. In this example you can see where this may become powerful. Allowing other stimulus controllers, and maybe non-stimulus logic to call functions on the Dom defined in a controller
    image

image

@Matts
Copy link
Author

Matts commented May 4, 2021

🕺 Rough sketch of the possible featureset 🕺

static powermoves = []
Define what attributes should be exposed to the dom element. Can be empty seeing as you also have the option to expose the complete controller in the usePowermoves options

usePowermoves(controller, options)
Configure the powermove controller.
options:

  • exposeController: default false
    Will allow you to expose the complete controller as shown in the original blog post linked above
  • playNice: default true
    Will the functions play nice with other stimulus-use features such as debounce. -> is this possible or is this something that will have to be kept in mind on order of execution of the (for example) useDebounce(this) function
  • name: default null
    If not null then this will be the name for the power moves set (that can be used in hasPowermoves) otherwise will just use the known stimulus controller identifier

hasPowermoves?.(setName)
Check if the given powermoves set is available and initialized on the element
The reason for the optional chaining ?. syntax, is because when the element has no power moves registered, the hasPowermoves function will not be available.

@marcoroth
Copy link
Member

marcoroth commented May 4, 2021

Hey @Matts, thanks for bringing this up! 😊

I've also created a similar composable function last year to make use of @leastbad's Stimulus Power move.

import { Controller } from 'stimulus';

function camelize(value: string) {
  return value.split('--').slice(-1)[0].split(/[-_]/).map(w => w.replace(/./, m => m.toUpperCase())).join('').replace(/^\w/, c => c.toLowerCase())
}

// The best one-line Stimulus power move
// https://leastbad.com/stimulus-power-move
//
export function usePowerMove(controller: Controller) {
  const identifier = `${camelize(controller.identifier)}Controller`
  (controller.element as { [index: string]: any })[identifier] = controller
}

While I appreciate your thoughts about static powermoves = [] I'm not sure if we really need this. A normal Stimulus Controller also exposes all "functions" to the public. That's why I'm asking myself why should you limit or need to explicitly allow functions to be exposed? Couldn't you just turn them into private functions?

The only difference (which we could make configureable) is that I append a Controller to the identifier to make sure it doesn't overwrite anything. Plus it follows the semantics you are used to from Stimulus, think Target, Value, Class.

One interesting aspect, which is not handled in my implementation, is the use of multiple controllers on one element. So maybe it could make sense to also add [identifier]Controllers which returns an array. Also here: similar to Targets and Classes

@Matts
Copy link
Author

Matts commented May 4, 2021

@marcoroth thanks for the feedback 💯

I think that where leastbad's idea falls short, and where the value here may start, is the option to overwrite dom attributes. Overwriting default HTML Dom logic to better fit your component. This is done without reference to your controller, and thus implementing logic does not need to know anything about how your stimulus controller(s) are configured. (does not even need to be in "stimulus land")

The example I have here indeed supported multiple controllers. I see them as "powermove sets" that all have their own responsibility (SRP). All controllers will have their own discretion in adding/overwriting attributes within the element Node. This will require a developer to be careful not to overwrite an attribute he may need.

Coming to your remark of all functions being exposed to the public in normal stimulus, I am not entirely sure what you mean with that. I thought the entire idea that leastbad's powermoves were based around, was to make it public?

The reason I think it may be handy to explicitly expose attributes is that, technically, Javascript does not have a way to create private attributes iirc. There are standards (one of them is starting with _), but nothing that is commonly implemented over all frameworks/JS language implementations (may be wrong on this one).

@leastbad
Copy link

leastbad commented May 4, 2021

👋

You folks don't have to keep calling them "power moves" - I'm tickled, but I wasn't naming anything.

Moreover, I don't know that my idea fell short. That's not a defensive statement; I just think you're describing a very different kind of functionality or use case.

The blog post offered up a pattern which I use most often with controllers that are attached to the same element. Otherwise, I find that event bubbling is generally the best way to design systems. The exception is when you have attached controllers to the body tag. For example, sometimes I will attach an audio element to body, and use it from different places.

The thing that all of these stories share in common is that there's no concept of defensive programming; quite simply, I don't ever put anything into my Stimulus controllers that could be used in an adversarial way - even though it's actually extremely difficult for a casual observer to even notice that a DOM element has some extra keys. That would sound an awful lot like security by obscurity, except that generally speaking, my dropdown navigation isn't a security asset.

I'm not saying that you don't need the things you're describing! I just know that as soon as you're looking to lock down / expose certain functions and not others, we're on a different page. 💙

@Matts
Copy link
Author

Matts commented May 4, 2021

Heyo @leastbad

Too bad, you are stuck with the term power moves. It has a certain vibe to it 💃💃

In all seriousness. After reading your comment I indeed agree that the question I am trying to answer is largely based on the idea you originally proposed but has its own swing to it. (pun intended)

After eating away on the idea for a while, I don't think the goal I tried to verbalize was "power moves but locked down". But rather on making easy-to-use & easy to understand component APIs to interact with the controller functions from the outside.

Basically "Custom Elements" for Stimulus. Because what is easier than calling a function directly on a DomNode (document.querySelector('form').isValid())

Instead of calling this a bigger feature set than was proposed with "power moves", it may be more useful to call it an alternative to the event system, and so allowing different design patterns (non-event-based) to be used when developing and implementing features within/outside of Stimulus.

@marcoroth
Copy link
Member

Overwriting default HTML Dom logic to better fit your component. This is done without reference to your controller, and thus implementing logic does not need to know anything about how your stimulus controller(s) are configured. (does not even need to be in "stimulus land")

I kind of like this idea, but I guess it would make sense that this goes into a separate composable function. The difference between the "power move" and the functionality you are describing is that you are calling and potentially overwriting those function on the element level vs calling those functions on the controller.

element.myFunction() vs. element.[identifier]Controller.myFunction()

Coming to your remark of all functions being exposed to the public in normal stimulus, I am not entirely sure what you mean with that.

I meant that with data-action you are also able to call any function of the controller. All of the functions are with that exposed to the "public".

My question was like, why would we need to expose the functions of the controller if you want to call them on element.[identifier]Controller.myFunction() when you are also able to call them via the data attribute (data-action="[identifier]#myFunction) without any restriction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants