Won't you listen 'cos I'm at it again
The Salesforce Lightning Design System provides a library of pre-built UI elements.
"Thinking in React" is essentially the art of breaking down larger user interfaces into several smaller components. Combined, the two provide an extremely powerful framework for building interactive user interfaces in Salesforce.
Lightning Tabs
Let's use the Lightning Tabs component as an example. After pasting the example code into a Visualforce page, a very nicely formatted navigation menu will appear in the page.
However, without Javascript, clicking on the tabs will have no effect.
To make this navigation tab interactive, we'll wrap the individual Tab components in React classes. We begin by deconstructing the Tab UI down into smaller classes that can be implemented in React. A hierarchy of React components representing a Lightning tab navigation: App (The application container) Tabs (A collection of tabs) Tab (A single tab instance) Content (Content associated with a tab) Data ReactJS components "react" to changes in data state. The data we're going to inject into this application is an array of Tabs, defined as follows: var tabList = [ { 'index': 0, 'title': 'Item One', 'content': <div id="tab-scoped-0" class="slds-tabs__content slds-show" role="tabpanel"> <h2>Item One Content</h2> </div> }, { 'index': 1, 'title': 'Item Two', 'content': <div id="tab-scoped-1" class="slds-tabs__content slds-show" role="tabpanel"> <h2>Item Two Content</h2> </div> }, { 'index': 2, 'title': 'Item Three', 'content': <div id="tab-scoped-2" class="slds-tabs__content slds-show" role="tabpanel"> <h2>Item Three Content</h2> </div> } ]; App Now this is where the ReactJS framework comes into play. The App class provides the outermost container for the entire application. The state of which tab is currently selected is managed in the App class. var App = React.createClass({ getInitialState: function () { return { tabList: tabList, currentTab: 0 }; }, changeTab: function(tab) { this.setState({ currentTab: tab.index }); }, render: function(){ return( <div className="slds"> <Tabs currentTab={this.state.currentTab} tabList={this.state.tabList} changeTab={this.changeTab} /> {this.state.tabList[this.state.currentTab].content} </div> ) } }); Tabs The App.render() method constructs a Tabs class, which is a container for many individual tabs. var Tabs = React.createClass({ handleClick: function(tab){ this.props.changeTab(tab); }, render: function(){ return ( <div className="slds-tabs--scoped"> <ul className="slds-tabs--scoped__nav" role="tablist"> {this.props.tabList.map(function(tab) { return ( <Tab handleClick={this.handleClick.bind(this, tab)} index={tab.index} title={tab.title} isCurrent={(this.props.currentTab === tab.index)} /> ); }.bind(this))} </ul> </div> ); } }); Tab The majority of dynamic rendering occurs in the Tab class. var Tab = React.createClass({ handleClick: function(e){ this.props.handleClick(); }, render: function(){ var tabClass = "slds-tabs__item slds-text-heading--label"; tabClass += (this.props.isCurrent ? ' slds-active' : ''); var selected = (this.props.isCurrent ? 'true' : 'false'); var path = '#' + encodeURIComponent( this.props.title.toLowerCase() ); var controls = 'tab-scoped-' + this.props.index; return ( <li className={tabClass} title={this.props.title} role="presentation"><a href={path} onClick={this.handleClick} role="tab" tabindex={this.props.index} aria-selected={selected} aria-controls={controls}>{this.props.title}</a></li> ) } }); Finally, this snippet of code sets the whole interactive model in motion. It creates an instance of the <App/> class within the document <body> tag. React.render( <App />, document.body ); Event Handling You'll notice that the Tab onClick event gets bubbled up through the Tabs container, and is ultimately handled by the App class. Events are handled at the most appropriate scope in an application. In this case, the App class updates the currentTab variable and the entire DOM is automatically updated to reflect the change in state. Also notice that no dynamic removeClass() or addClass() logic is required to re-render the tabs. This is where the real magic of React is utilized. React manages a virtual DOM (Document Object Model) and diff engine to automatically determine which components in the hierarchy need to be re-rendered every time there is a change in data state. The Whole Enchilada Wrapping Lightning Design Studio components in React classes is a remarkably powerful and efficient combination. An interactive navigation menu can be implemented in under 100 lines of code without writing any custom CSS or complex JS event handlers. Entire source code for this example is below. Need a custom Salesforce application or AppExchange product developed using the latest Lightning design studio components? Contact us for details. <apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0" cache="false"> <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <head> <title>App Title</title> <apex:stylesheet value="{!URLFOR($Resource.SLDS092, 'assets/styles/salesforce-lightning-design-system-vf.css')}" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> <script type="text/babel"> var tabList = [ { 'index': 0, 'title': 'Item One', 'content': <div id="tab-scoped-0" class="slds-tabs__content slds-show" role="tabpanel"> <h2>Item One Content</h2> </div> }, { 'index': 1, 'title': 'Item Two', 'content': <div id="tab-scoped-1" class="slds-tabs__content slds-show" role="tabpanel"> <h2>Item Two Content</h2> </div> }, { 'index': 2, 'title': 'Item Three', 'content': <div id="tab-scoped-2" class="slds-tabs__content slds-show" role="tabpanel"> <h2>Item Three Content</h2> </div> } ]; var App = React.createClass({ getInitialState: function () { return { tabList: tabList, currentTab: 0 }; }, changeTab: function(tab) { this.setState({ currentTab: tab.index }); }, render: function(){ return( <div className="slds"> <Tabs currentTab={this.state.currentTab} tabList={this.state.tabList} changeTab={this.changeTab} /> {this.state.tabList[this.state.currentTab].content} </div> ) } }); var Tabs = React.createClass({ handleClick: function(tab){ this.props.changeTab(tab); }, render: function(){ return ( <div className="slds-tabs--scoped"> <ul className="slds-tabs--scoped__nav" role="tablist"> {this.props.tabList.map(function(tab) { return ( <Tab handleClick={this.handleClick.bind(this, tab)} index={tab.index} title={tab.title} isCurrent={(this.props.currentTab === tab.index)} /> ); }.bind(this))} </ul> </div> ); } }); var Tab = React.createClass({ handleClick: function(e){ this.props.handleClick(); }, render: function(){ var tabClass = "slds-tabs__item slds-text-heading--label"; tabClass += (this.props.isCurrent ? ' slds-active' : ''); var selected = (this.props.isCurrent ? 'true' : 'false'); var path = '#' + encodeURIComponent( this.props.title.toLowerCase() ); var controls = 'tab-scoped-' + this.props.index; return ( <li className={tabClass} title={this.props.title} role="presentation"><a href={path} onClick={this.handleClick} role="tab" tabindex={this.props.index} aria-selected={selected} aria-controls={controls}>{this.props.title}</a></li> ) } }); React.render( <App />, document.body ); </script> </head> <body></body> </html> </apex:page>
0 Comments
Leave a Reply. |
AuthorMike Leach is Founder and Principal Consultant at Cubic Compass; a software design and development consultancy focused on the Salesforce Force.com platform. Archives
March 2017
Categories |