Coder Social home page Coder Social logo

ruslan-kurchenko / sfdc-lax Goto Github PK

View Code? Open in Web Editor NEW
112.0 12.0 19.0 328 KB

The service Lightning Component to write a clear asynchronous JavaScript code

License: MIT License

JavaScript 100.00%
salesforce salesforce-lightning lightning-component salesforce-lightning-components aura promise

sfdc-lax's Introduction

Logo

Lax is a Lightning Component to write a clear asynchronous JavaScript code. It helps you to remove the boilerplate code in Lightning Components. Lax combines capabilities to efficiently call Apex with powerful exception handling and list of utility techniques, giving you an ability to write asynchronous code in a modern approach.

Features

  • lax gets the context of consumer component
  • Supports the Promise API
    • Set server-side action callback (success, failure, incomplete)
    • Chain server-side actions
    • Perform multiple concurrent server-side actions
    • Call Lightning Data Service actions (create/save/delete)
    • Dynamically create components
  • Construct and enqueue server-side action using Builder Pattern approach
  • Construct and fire Application or Component event using Builder Pattern approach
  • Automatically wraps callback by $A.getCallback()
  • Use lax in consumer's component aura:valueInit event handler

Installing

Click on the button below to deploy the component to the org

Deploy

Usage

Define lax component in a custom component markup:

<!-- ContactsComponent.cmp -->
<aura:component controller="LaxExamplesController">
    <!-- Define lax component and pass consumer's component object as a context attribute (required) -->
    <c:lax context="{!this}" />
    
    <aura:attribute name="records" type="Contact[]" access="private"/>
    <aura:handler name="init" action="{!c.onInit}" value="{!this}" />
</aura:component>

Enqueue an action in component's aura:valueInit event handler function to get initial data:

// ContactsComponentController.js
({
    onInit: function (component, event, helper) {
        // equeue getContacts server-side action and set the callback
        component.lax.enqueue('c.getContacts').then(contacts => {
            // $A.getCallback is not required. lax does it automatically
            component.set('v.records', contacts);
        });
    }
});
NOTE

API Reference

Navigate to component API Reference documentation to see full list of available functions and capabilities

Examples

Actions can be enqueued by passing the relevant parameters and options.

lax.enqueue(name[, params[, options]]).then(callback) - call action with parameters, options and simple callback
component.lax.enqueue('c.getContact', { id: recordId }, { background: true })
    .then(contact => {
        component.set('v.record', contact);
    });
NOTE
lax.enqueue(name[, params[, options]]).then(callback).catch(callback) - handle errors thrown by the server
component.lax.enqueue('c.save', { record: record })
    .then(id => {
        component.set('v.record.id', id);
    })
    .catch(errors => {
        console.error(errors);
    });
lax.enqueue(name[, params[, options]]).then(callback).then(callback) - performing multiple chained actions
component.lax.enqueue('c.getParentValue')
    .then(parentValue => {
        component.set('v.parentValue', parentValue);
        return component.lax.enqueue('c.getDependentValue', { parentValue: parentValue });
    })
    .then(dependentValue => {
        component.set('v.dependentValue', dependentValue);
    });
lax.enqueueAll(actions).then(callback) - performing multiple concurrent actions
component.lax.enqueueAll([
    // { name : '...', params: {...}, options: {...} }
    { name: 'c.getContacts' }, 
    { name: 'c.getAccounts' },
    { name: 'c.getOpportunities' }
])
.then(results => {
    // results: [ [contacts], [accounts], [opportunities] ]
    const contacts = results[0];
    const accounts = results[1];
    const opportunities = results[2];
});
NOTE
  • actions - The array of actions to enqueue concurrently:
    • name - the name of an action
    • params - an object with list of parameters (optional)
    • options - an object with list of options that can be applied to the action (optional)
  • The success callback will call when all enqueued actions be back from the server
  • results - The list of values returned from enqueued actions
lax.action(name) - create and return LaxAction builder
component.lax
    .action('c.getContact')
    .setStorable()
    .setParams({ id: recordId })
    .setThen(contact => {
        component.set('v.record', contact)
    })
    .setCatch(error => {
        console.error(error);
    })
    .enqueue();
NOTE
  • LaxAction is an object with the list of functions to construct a server-side action
  • The approach is useful for Storable actions, because LaxAction does not use Promise API
  • Actions can't be chained or called concurrently using LaxAction builder approach
  • The list of function available on LaxAction:
    • setParams(params) - set an object with list of parameters
    • setThen(callback) - set success callback function
    • setCatch(callback) - set failure callback function to handler server-side errors
    • enqueue() - enqueue constructed action. The only function LaxAction that return undefined
    • setStorable() - set an action as a Storable
    • setBackground() - set an action as a Background
lax.lds(id) - create and return LaxDataService to execute Lightning Data Service (LDS) actions based on Promise API
component.lax.lds('serviceAuraId');
lax.lds(id).getNewRecord(sobjectType[, recordTypeId[, skipCache]]) - the function to load a record template to the LDS targetRecord attribute
component.lax.lds('contactRecordCreator')
  .getNewRecord('Contact')
  .then(() => {
    const rec = component.get("v.newContact");
    const error = component.get("v.newContactError");

    if (error || (rec === null)) {
      console.log("Error initializing record template: " + error);
    }
  });
lax.lds(id).saveRecord() - the function to save the record that loaded to LDS in the edit EDIT mode
component.lax.lds('recordHandler')
  .saveRecord()
  .then(result => {
    // handle "SUCCESS" or "DRAFT" state
  })
  .error(e => {
    // handle "ERROR" state
  })
  .incomplete(e => {
    // handle "INCOMPLETE" state
  })
lax.lds(id).deleteRecord() - the function to delete a record using LDS
component.lax.lds('recordHandler')
  .deleteRecord()
  .then(result => {
    // handle "SUCCESS" or "DRAFT" state
  })
  .catch(e => {
    // handle "ERROR" or "INCOMPLETE" state. 
    // or you can use divided handlers: .error(), .incomplete()
  })
lax.event(eventName) - creates an object with LaxEventBuilder prototype and the context event by provided name
// Fire Component Event:
component.lax.event('sampleComponentEvent')
  .setParams({payload: { type: 'COMPONENT'} })
  .fire();

// Fire Application Event:
component.lax.event('e.c:AppEvent')
  .setParams({payload: { type: 'APPLICATION'} })
  .fire();
lax.createComponent(type[, attributes]) - creates a component from a type and a set of attributes
component.lax.createComponent('aura:text', { value: 'Single Component Creation' })
  .then(result => {
    // result has a property "component" with created component
    single.set('v.body', result.component);
  })
  .catch(e => {
    // handle "ERROR" or "INCOMPLETE" state
  });
lax.createComponent(type[, attributes]) - creates an array of components from a list of types and attributes
lax.createComponents([
    ['aura:text', { value: 'Multiple Component Creation #1'}],
    ['aura:text', { value: 'Multiple Component Creation #2'}],
    ['aura:text', { value: 'Multiple Component Creation #3'}]
  ])
  .then(result => {
    // result has a property "components" with list of created components
    multiple.set('v.body', result.components);
  })
  .incomplete(e => {
    // handle "INCOMPLETE" state
  })
  .error(e => {
    // handle "ERROR" state
  });

License

MIT

sfdc-lax's People

Contributors

ruslan-kurchenko avatar scottbcovert avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sfdc-lax's Issues

Add an ability to hook into the Apex/Lds/CreateComponent Actions process

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 (#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 (#19)
  • onIncomplete - the same logic like for onReject

An ability to run `$A.createComponent()` functionality using Promise API manner

It would be nice to have an ability to create components dynamically with Promise API.

Here is the official documentation about this functionality with raw callback approach: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_cb_dynamic_cmp_async.htm

Below is a rough example of the implementation usage:

component.lax.createComponent(
  'lightning:button',
  {
    'aura:id': 'saveBtn',
    'label': 'Save',
    'onclick': component.getReference('c.onClickSave')
  }
)
.then(button => {
  const body = component.get('v.body');
  body.push(button);
  component.set('v.body', body);
})
.catch(error => {
  console.log("Error: " + error);
});

.createComponent(), .then() and .catch() fuctions must return LaxPromise object.
The API should be similar to server-side actions functionality.
Users will have an ability to chain several components creation:

component.lax.createComponent()
  .then(firstComponent => {
    return component.lax.createComponent(...)
  })
  .then(secondComponent => {
    // custom logic here
  })
  .catch(error => {
    console.log("Error: " + error);
  });

Note: The same functionality should be implemented to create a list of components - $A.createComponents(definitions)

Add a flag to automatically `JSON.parse` response from Apex

We return a JSON-serialized Map of data from all our @AuraEnabled methods and were JSON.parseing the response in our custom Apex callout component. Now that we've switched to Lax we have to append a .then(JSON.parse) to all our Lax calls. It's not a big deal but it's really easy to forget. It would be nice if there was a flag we could set either in a component attribute, ala v. attributeName, or in the options object passed to component.lax.enqueue().

Questions about storable and background actions

Neighter from your (great) nor from Salesforce's documentation I understand:

  1. Why I can't use the setStorable caching when I use Promises. Even Lax doesn't allow me to use it besides in its action builder.
  2. What the background option is and when I can use it? Can I use it whenever I don't need the response from the server?

An ability to call Lightning Data Service component function within Lax context

Lax object should have a function lds() (Lightning Data Service) to find an LDS in the component markup and return wrapped. The returned object should have all standard LDS component API and provide an ability to call actions in Promise based manner.

An example of result client code:

    // component.lax.lds(id) - return wrapped <force:recordData /> component
    const service = component.lax.lds('record');
    service.saveRecord()
      .then(result => {
        // handle SUCCESS or DRAFT save result

        // chain async calls using Promise API
        return service.getNewRecord('Contact', null, false);
      })
      .then(() => {
        // handle record template initialization
      })
      .error(e => {
          // handle ERROR result
      })
      .incomplete(e => {
          // handle INCOMPLETE result
      });

An ability to set action callback without Aura context wrapper - `$A.getCallback()`

By default, then(), catch() and finally() function wraps callback function with $A.getCallback().
Lax should provide an ability to avoid that.
There are can be situations where it is redundant, for example when callback doesn't change any component attribute and doesn't manipulate DOM.

An example of usage:

    component.lax.enqueue('c.actionName')
      .then(() => {
        // custom logic
      }, false)

API:

  • then(callback, isAuraContext)
  • catch(callback, isAuraContext)
  • finally(callback, isAuraContext)

isAuraContext - is true by default

Cannot define property lax, object is not extensible

Hi,
I'm just trying this out for the first time, but it isn't working as expected.

Instead the page fails to render and I get the following error message:
Uncaught Action failed: c:lax$controller$onInit [Cannot define property lax, object is not extensible] Callback failed: serviceComponent://ui.flexipage.components.page.FlexipageControllerV2/ACTION$getPage

This is the stack trace:
Object.init()@https://th15-dev-ed.lightning.force.com/one/components/c/lax.js:39:12 onInit()@https://th15-dev-ed.lightning.force.com/one/components/c/lax.js:10:12

If it helps, I'm trying to adapt code from a trailhead

This is my markup:
`<aura:component access="global" controller="Books4EveryoneHomeAuraCtrl" implements="flexipage:availableForAllPageTypes">
<c:lax context="{!this}" />

<aura:attribute name="bookList" type="Book__c[]" />
<aura:handler name="init" action="{!c.doInit}" value="{!this}" />

<lightning:card title="Books4Everyone Books">
	<table class="slds-table slds-table_bordered slds-table_cell-buffer">
		<thead>
			<tr class="slds-text-title_caps">
				<th scope="col">Book Titles</th>
				<th scope="col">Book Descriptions</th>
			</tr>
		</thead>
		<tbody>
			<aura:iteration items="{!v.bookList}" var="book">
				<tr scope="row">
					<td>{!book.Name}</td>
					<td>{!book.Description__c}</td>
				</tr>
			</aura:iteration>
		</tbody>
	</table>
</lightning:card>

</aura:component>
`

and this is my controller:
({ doInit : function(component, event, helper) { component.lax.enqueue('c.getBooks') .then(bookList => { component.set('v.bookList', bookList); }); } })

Add record reload method to LDS helper

It seems that LDS doesn't automatically load records on recordId change - you need to execute reloadRecord method, which takes in callback.

My use case is convert lead, load Opportunity and Account, throw some data there...

[question] How to mimic a finally?

How can I call code at the end of the chain? No matter if an error occurs or everything went well. Something like a finally or a then after the catch.

How to JS-throw error in then() to be catched in catch()

I have a use case that is not easy to handle with AuraHandledExceptions alone. Before I query a lot of records in Apex I first count them. Depending on the resultCount I want the below JS code to eighter fire a new callback or throw the same type of error that an Apex side exception would have thrown. Why? So I can handle it with the same LAX catch.

Is that possible?

 cmp.lax.enqueue("c.countResults")
    .then((resultCount) => {
        if(resultCount < 1000)
            return cmp.lax.enqueue("c.queryData");
        }
        else {
            // Throw error to be catched and display in below catch
        }
    })
    .then((result) => {
        ...
    })
    .catch((e) => {
        cmp.find("toast").toastError(e);
    });

An option to handle `"INCOMPLETE"` server-side action state in `.catch()` callback

The client should be able to handle "INCOMPLETE" action state in LaxPromise catch() callback or LacAction setCatch() callback.

An example of the result usage:

component.lax.enqueue('c.getIncomplete')
  .then(message => {
    component.set('v.message', message);
  })
  .catch((errors, params) => {
    if (params.incomplete) {
        component.set('v.message', 'INCOMPLETE actions status has been caught, you are offline!')
    } else {
      console.log(errors);
    }
  })

Recommended Migration to Lightning Web Components

I am in the process of migration a few douzens of Aura-based components to LWC. All of them used LAX for their callback handling. What do you propose how to migrate them.

Just stop using Lax because LWC solves everything natively or keep stuff?

Add error parser / normaliser

The resulting error array is quite painful to parse, as a result I've wrote this spaghetti that recursively parses errors into a single array of messages.

It could probably be improved by adding errorType property.
It would be nice to just go enqueue(...).withErrorParser() and get simply iterable list of exceptions that you could just .join() and print to the screen.

getErrors : function(errors) {
   var messages = [];
   var helper = this;
   if(errors){
     var errata = Array.isArray(errors) ? errors : errors.entries;
     if(errata){
       errata.forEach(function(i){
        if(i.message) messages.push(i.message);
        if(i.pageErrors && i.pageErrors.length > 0) messages = [].concat(messages, helper.getErrors(i.pageErrors));
        if(i.fieldErrors) messages = [].concat(messages, helper.getErrors([].concat.apply([], Object.keys(i.fieldErrors).map(function(key) { return i.fieldErrors[key] }))));
       }) 
     }
   }
   return messages;
  }

Finally doesn't work as expected

When I do

      cmp.lax.enqueue("c.saveAndDistribute", { changesJson: JSON.stringify(volumeAggregates) })
            .catch((e) => {
                cmp.find("toast").toastError(e);
            })
            .then(() => {
                cmp.find("spinner").hide();
            });

it works as expected, but not when I use finally(). The spinner is not hidden in the error case.

      cmp.lax.enqueue("c.saveAndDistribute", { changesJson: JSON.stringify(volumeAggregates) })
            .catch((e) => {
                cmp.find("toast").toastError(e);
            })
            .finally(() => {
                cmp.find("spinner").hide();
            });

An ability to assign finally callback on LaxPromise chain

The client should have an ability to set finally callback on LaxPromise:

// start loading logic...
component.lax.enqueue('c.save', { record: record })
    .then(id => {
        component.set('v.record.id', id);
    })
    .catch(errors => {
        console.error(errors);
    })
    .finally(() => {
        // stop loading logic...
    )};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.