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

Setup StimulusReflex controller callbacks #45

Merged
merged 30 commits into from
Sep 28, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dfe13b1
Setup custom events for success/error
hopsoft Sep 20, 2019
f5a6cd7
Dispatch error events
hopsoft Sep 20, 2019
0245889
Invoke implicit callback methods
hopsoft Sep 20, 2019
2d2a558
Update callback name
hopsoft Sep 20, 2019
6475807
Added error handling to trap bad selectors
hopsoft Sep 20, 2019
3ebdc3f
Update LOC and Rakefile
hopsoft Sep 20, 2019
defd259
Init elements with default value
hopsoft Sep 20, 2019
5139b7a
Check controller exists before invoking callbacks
hopsoft Sep 20, 2019
89482cb
Provide some guidance to help developers
hopsoft Sep 20, 2019
7e8321c
Update javascript/helpers.js
hopsoft Sep 21, 2019
ffd2579
Update javascript/helpers.js
hopsoft Sep 21, 2019
10d3c04
Address multiple callback issue
hopsoft Sep 21, 2019
21d3f47
Rework to only support callbacks
hopsoft Sep 22, 2019
fb32ed5
Support reflex behavior in any registered StimulusReflex controller
hopsoft Sep 23, 2019
198b393
Invoke callbacks on correct controller
hopsoft Sep 23, 2019
cdc89f8
Working toward custom callbacks
hopsoft Sep 24, 2019
682200a
Switch to use inflected
hopsoft Sep 24, 2019
7fe7169
Remove debug artifact
hopsoft Sep 24, 2019
ddbf6d1
Get lifecycle methods working
hopsoft Sep 26, 2019
fc2e279
cleanup
hopsoft Sep 26, 2019
8fa15b1
Update some comments
hopsoft Sep 26, 2019
2033b88
Scope lifecycle methods to nearest stimulus reflex controller
hopsoft Sep 27, 2019
be8b88f
Wait for implicit controller bindings to be established
hopsoft Sep 27, 2019
1bd4c46
Send error as 2nd argument to lifecycle methods
hopsoft Sep 27, 2019
4445f5f
Setup matching name convention for reflex + controller
hopsoft Sep 27, 2019
79f1d16
Switch to prettier-standard
hopsoft Sep 27, 2019
af81b5e
Update standardize
hopsoft Sep 27, 2019
39f5e1a
Allow explicit stimuluate calls to pass triggering element
hopsoft Sep 28, 2019
ba867ae
Merge branch 'master' into hopsoft/dom-events
hopsoft Sep 28, 2019
914f46c
Re-add badges
hopsoft Sep 28, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 39 additions & 26 deletions javascript/stimulus_reflex.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Controller } from 'stimulus';
import ActionCable from 'actioncable';
import Inflection from 'inflection';
hopsoft marked this conversation as resolved.
Show resolved Hide resolved
import CableReady from 'cable_ready';

// A reference to the Stimulus application registered with: StimulusReflex.initialize
Expand Down Expand Up @@ -74,14 +75,10 @@ const findController = (name, element) => {
// Finds the closest StimulusReflex controller name in the DOM tree
const findStimulusReflexControllerName = element => {
const controllerNames = element.dataset.controller ? element.dataset.controller.split(' ') : [];
let controllerName = controllerNames.reduce((memo, name) => {
return controllerNames.reduce((memo, name) => {
hopsoft marked this conversation as resolved.
Show resolved Hide resolved
const controller = findController(name, element);
return memo || (controller && typeof controller.stimulate === 'function') ? name : null;
}, null);

return controllerName || element.parentElement
? findStimulusReflexControllerName(element.parentElement)
: null;
};

// Invokes a callback on a StimulusReflex controller.
Expand All @@ -95,6 +92,34 @@ const invokeCallback = (name, controller) => {
if (controller && typeof controller[name] === 'function') controller[name]();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This invokeCallback probably should have a different name because it's useful for all sorts of things that aren't callbacks. Can we call it send as a homage to Ruby?

Is send a valid function name in JS?

Probably not super important, but technically we could change the name of the controller parameter to class (or klass) because really this is a cool pattern even if you're working in React. I always like the idea that people reading the code can learn new tricks, and this isn't specifically a Stimulus thing.

Of course, it might just be confusing, too. I'm not hung up either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that it's something of a misnomer to name it a callback. I'll take some time to consider whether or not there is a better naming convention we can use.

};

const invokeLifecycleCallback = (stage, element) => {
if (!element) return;
const stimulusReflexController = findController(findStimulusReflexControllerName(element), element);
const actions = element.dataset.action ? element.dataset.action.split(' ') : [];
const [_reflexClassName, reflexMethodName] = (element.dataset.reflex || '').split('#');
const genericCallbackName = ['before', 'after'].includes(stage)
? `${stage}Reflex`
: `reflex${Inflection.camelize(stage)}`;
let reflexCallbackName;
if (reflexMethodName) {
reflexCallbackName = ['before', 'after'].includes(stage)
? `${stage}${Inflection.camelize(reflexMethodName)}`
: `${Inflection.camelize(reflexMethodName, true)}${Inflection.camelize(stage)}`;
}

setTimeout(() => {
if (reflexCallbackName) {
actions.forEach(action => {
const [_eventName, handler] = action.split('->');
const [controllerName, _methodName] = handler.split('#');
const controller = findController(controllerName, element);
invokeCallback(reflexCallbackName, controller);
});
}
invokeCallback(genericCallbackName, stimulusReflexController);
}, 1);
};

// Subscribes a StimulusReflex controller to an ActionCable channel and room.
//
// controller - the StimulusReflex controller to subscribe
Expand Down Expand Up @@ -145,7 +170,7 @@ const extendStimulusController = controller => {
const target = args.shift();
const attrs = extractElementAttributes(this.element);
const data = { target, args, attrs, url };
invokeCallback('reflexStart', this);
invokeLifecycleCallback('before', this.element);
controller.StimulusReflex.subscription.send(data);
},

Expand Down Expand Up @@ -225,28 +250,16 @@ if (!document.stimulusReflexInitialized) {
document.addEventListener('turbolinks:load', setupDeclarativeReflexes);
document.addEventListener('cable-ready:after-morph', setupDeclarativeReflexes);
document.addEventListener('cable-ready:before-morph', event => {
let { attrs } = event.detail.stimulusReflex || {};
if (!attrs) return;
attrs['data-controller'].split(' ').forEach(name => {
const element = findElement(attrs);
const controllerName = findStimulusReflexControllerName(element);
let controller = findController(controllerName, element);
setTimeout(() => {
invokeCallback('reflexSuccess', controller);
invokeCallback('reflexComplete', controller);
}, 1);
});
const { attrs } = event.detail.stimulusReflex || {};
const element = findElement(attrs);
invokeLifecycleCallback('success', element);
invokeLifecycleCallback('after', element);
});
document.addEventListener('stimulus-reflex:500', event => {
hopsoft marked this conversation as resolved.
Show resolved Hide resolved
let { attrs } = event.detail.stimulusReflex || {};
if (!attrs) return;
attrs['data-controller'].split(' ').forEach(name => {
const element = findElement(attrs);
const controllerName = findStimulusReflexControllerName(element);
let controller = findController(controllerName, element);
invokeCallback('reflexError', controller);
invokeCallback('reflexComplete', controller);
});
const { attrs } = event.detail.stimulusReflex || {};
const element = findElement(attrs);
invokeLifecycleCallback('error', element);
invokeLifecycleCallback('after', element);
});
}

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"inflection": "^1.12.0"
}
}
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


inflection@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416"
integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=