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 an ability to hook into the Apex/Lds/CreateComponent Actions process #21

Closed
ruslan-kurchenko opened this issue Jun 14, 2018 · 11 comments
Assignees

Comments

@ruslan-kurchenko
Copy link
Owner

Define an ability to assign "hook" functions

List of hooks:
  • onPrototypeInit - an attribute on the Lax component. The user will be able to assign function that executes only once. It will be nice to use to define global settings.
  • onInit - an attribute on the Lax component. It is local hook to assigne a function that executes when an actual Lax component object created. Due to internal Lax architecture, the system has Lax Prototype object and a list of local Lax objects that inherit the functionality from the Prototype object.
  • onResolve/onSuccess - a function to "decorate/change" a resolved value (Add a flag to automatically JSON.parse response from Apex #20) that went from the server. It will has a default implementation (empty function or not, investigating...) and a user can assign his custom function implementation (in onPrototypeInit, onInit hooks or locally before an actual action queueing)
  • onReject/onError - the same logic like for onResolve, but this hook provides an ablity to dive into the error handling process (Add error parser / normaliser #19)
  • onIncomplete - the same logic like for onReject
@ruslan-kurchenko
Copy link
Owner Author

@kvizcarra, @sjurgis, could you guys comment on that and give me any feedback.
I think this "pluggable" approach should cover your cases and will provide an ability to enhance the workflow even better.

@kvizcarra
Copy link

  1. Is this how onPrototypeInit and onInit will be defined?
<c:lax context="{!this}" onPrototypeInit="{!c.onLaxPrototypeInit}" onInit="{!c.onLaxInit}"/>
  1. I don't quite understand how onResolve/onSuccess and the similar ones are defined. Will the onPrototypeInit and onInit functions be called with an object that allows us to define onResolve/onSuccess?
onLaxInit: function(lax) {
    return lax.onResolve((...) => {...});
}

@ruslan-kurchenko
Copy link
Owner Author

@kvizcarra

  1. Yes, that is exactly what I thought.
  2. In general, yes. But, I'm investigating that to find the best solution for this case.
    For example, pass Aura.Action or register a component event and provide an API using this way.
    My original idea was to provide an ability to pass the function that has a defined structure (gets an object, returns a decorated object, properties, etc, it is don't matter), but the first issue I've encountered is that Function type attribute is not working as a true aura:attribute. The system breaks when I trying to do that...
    So, now I'm trying to find a solution to meet all requirements and don't break the Lax component workflow, I mean it should be synchronous process: init prototype lax -> init lax -> call action

If you have ideas or propositions, please share them here.

@adamantine-dev
Copy link

adamantine-dev commented Jun 15, 2018

@ruslan-kurchenko

Try to define an attribute type as Object:

<aura:attribute name="onPrototypeInit" type="Object"/>

Then in the lax component you can easly call this attribute as a simple Aura.Action:

var onPrototypeInit = cmp.get('v.onPrototypeInit');
onPrototypeInit.setCallback(this, function(response) { ... });
$A.enqueueAction(onPrototypeInit)

Then in the consumer's action passed to lax as attribute onPrototypeInit developers can return an object with predefined properties, methods, etc.:

<c:lax context="{!this}" onPrototypeInit="{!c.onLaxPrototypeInit}"/>

Consumer's action in the controller:

onLaxPrototypeInit: function (component, event, helper) {
    return {
        predefinedProp1: '',
        predefinedProp2: '',
        predefinedMethod: function(arguments) {
            // some logic here
        },
        ...
    };
},

And then in the lax component, you will have a full control on the returned object. It means you will have an ability to invoke a predefinedMethod() as well as an simple access to predefinedProp1, predefinedProp2, etc.

@kvizcarra
Copy link

@sergey-prishchepa
If the lax component calls the function asynchronously with $A.enqueueAction then returning the object from the consumer action would not make it to the lax component. I'm not sure if this is possible but the lax component could enqueue the consumer action, pass a callback as a param, and the consumer would call that callback with the prepared object.

@ruslan-kurchenko
I like Sergey's idea and I want to propose something a little similar:

The lax component fires an event with a callback param. The consumer will handle that event and call the callback param with the prepared object. In this callback, the lax component will enqueue the actual action. My reasoning for suggesting an event over an Aura.Action is to keep the hook logic in one place rather than spread out over each component that uses Lax. We can do that with a container component that catches the event with includeFacets="true" in its aura:handler.

MyComponent.cmp

...
<c:laxWrapper>
    <c:lax context="{!this}"/>
</c:laxWrapper>
...

laxWrapper.cmp

<aura:handler name="laxEvent" event="c:laxEvent" action="{!c.onLaxEvent}" includeFacets="true"/>

laxWrapperController.js

({
    onLaxEvent: function(component, event, helper) {
        const params = event.getParam('params');

        switch (event.getParam('action')) {
            case 'onResolve':
                params.callback({
                    ...params.lax,
                    onResolve: (...) => {...}
                });
                break;
        }
    }
})

@adamantine-dev
Copy link

@kvizcarra you proposed a good idea, but I can't say that solution is 'little similar' :) It requires to create an additional component laxWrapper and event laxEvent. Also, in the consumer component a developer has to handle laxEvent, call a callback again with the prepared object... It's little bit complicated logic for me.

@ruslan-kurchenko
If an asynchronous Aura:Action will be a problem in the case proposed by me, we can go in a similar way but uses a simple Aura:Method (documentation). In this case you will synchronously invoke the Aura:Method of consumer's component and that's all. On the developer side, this solution requires just to declare the Aura:Method with a predefined name, create one controller's action and return from there the prepared object. Completly the same I proposed in the first case but uses Aura:Method to avoid an asynchronous callback.

ConsumerConponent.cmp

<c:lax context="{!this}"/>
<aura:method name="onLaxPrototypeInit"/>

ConsumerConponentController.js

onLaxPrototypeInit: function (component, event, helper) {
    return { ... }; // the predefined object
},

In the lax you can just invoke it something like:

var prototypeInitObject = context.onLaxPrototypeInit ? context.onLaxPrototypeInit() : null;

@ruslan-kurchenko
Copy link
Owner Author

@kvizcarra, @sergey-prishchepa thank you guys for these awesome ideas!

@kvizcarra
My original idea is to provide as much flexibility for users as possible. I want to provide an ability to assign listeners/handlers on two stages: prototype init and local init.
In the case of the prototype - basically, it would be global listeners for all Lax components (invoked for all Lax component actions).
Also, users can assign local listeners for every distinct Lax Prototype Child (invoked for certain Lax component actions) (prototype inheritance).

lax prototype

Using onPrototypeInit and onInit attributes as listeners is more native and straightforward, for me personally. The only one question is - is local Aura.Action invocation works as much synchronously as we need to assign listeners before first ApexAction/LdsAction/CreateComponentAction completed.

As you can see, the idea isn't only an ability to assign listeners on Apex Actions. I want to add hooks for LdsAction (Lightning Data Service), CreateComponentAction and maybe even for other cases.

@sergey-prishchepa
The approach with aura:method is very interesting. When I complete testing with Aura.Action, I'll try this one and let you guys know the result. Also, I'll try to show you how it looks like for user perspective and I hope we will find the best approach!

Thank you guys again for your contribution!

@ruslan-kurchenko
Copy link
Owner Author

ruslan-kurchenko commented Jun 21, 2018

@sergey-prishchepa @kvizcarra @scottbcovert @rsoesemann

Hey folks!
Well, I've done some work on this ticket and I want to share that with you.

First of all, there are two branches that contain the hooks implementation:

My own cons/pros:

  • Aura.Action:
    • pros:
      • type safe: attribute name on Lax component require to enter that correctly, if not - compile time error
    • cons:
      • not fully synchronous: a callback of an action runs in a different EventLoop cycle, (not it main execution flow) it may (or not...) cause unexpected behavior when your action completed before listeners were assigned
      • transparent: it is more clearly defines an ability to assign Listener/Handler
  • aura:method:
    • pros:
      • synchronous: fully synchronous safe, works in the same script execution flow
    • cons:
      • not type safe: if developer provide wrong aura:method name, it won't run

Guys, it is my own thoughts. Could you test these two approaches and give me a feedback, please.
I'm going to wait for your response and then we will decide, which approach is better!
Thanks!

P.S.
The branches listed above contain example components that you can deploy to your test org and play with them. There is also example usage of hooks functionality (LaxExampleApplication, ContantList, ExceptionComponent)

@ruslan-kurchenko ruslan-kurchenko changed the title Add an ability to hook into the Apex Action process Add an ability to hook into the Apex/Lds/CreateComponent Actions process Jun 22, 2018
@ruslan-kurchenko
Copy link
Owner Author

Released in v1.2.4

@scottbcovert
Copy link
Contributor

@ruslan-kurchenko Sorry to be commenting on this after the fact, but just went through the commit and it looks like you went with the aura:method solution-I think this was the right choice. In my opinion the benefit of being synchronous outweighs the problem of not being type safe.

@ruslan-kurchenko
Copy link
Owner Author

@scottbcovert, not a problem at all. I totally agree with you.

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

No branches or pull requests

4 participants