We are taking a slight detour in our Building with React & Flux series to show how straight forward it is to write Single Page Applications (SPA) with React and Reflux for Salesforce. If you are just hopping into this series then definitely check out the Building with React & Flux: Getting Started to get up to speed on React and Flux.
In the last post we built a simple app to manage banner ads using React and RefluxJS, an implementation of Flux. I thought to myself, "...self, let's take this same app and build it in Salesforce and see what it takes." Fortunately, given Salesforce's support for JavaScript with Visualforce Remote Objects, it wasn't that much of an effort to get something very similar on the Force.com platform in no time.
See the video at the bottom for a complete demo or grab the code from github.
The application isn't a game-changer and aims to demonstrate the basics of React and Reflux on Force.com. It consists of the following:
- The 'home' page shows a simple table of banners from the Banner__c custom object.
- An HTML form to add a new banner that is of course inserted into the Banner__c custom object.
- A page that displays a banner and toggles its display status. Of course this updates the actual record in the custom object.
Intro to Reflux
Before we get started let's take a look at RefluxJS, one of the many Flux implementations. In general, I found Flux difficult to grok and apparently I'm not alone. I read the FB docs, waded through various blog posts (Flux For Stupid People and Getting to Know Flux) and even watched all of the egghead.io React and Flux videos but I honestly didn't like it. Luckily I stumbled across React.js architecture - Flux VS Reflux and it spoke to me. While Flux seemed architecturally complex and bloated with boilerplate code, Reflux seemed streamlined and succinct. I like its simplicity and use of mixins to add functionality. It drastically simplified the process of listening to changes in stores, made working with actions easier and less verbose and got rid of the Dispatcher entirely!! If you are interested in a comparison of the two then take some time to check out this blog post for a complete rundown.
Getting Started
We are going to the existing code from the previous post and modify it to run inside a Visualforce page. We'll use some of the same tools as last time but with a few tweaks. You won't be able to run the application locally but we will still minify our assets using gulp before upload them as static resources in Salesforce. First, clone the following repo to grab the code:
Then install all of the dependencies for the project defined in package.json:
Salesforce Configuration
The only real config we need to do is to create the following Banner__c custom object and expose a tab for it (if you'd like).
The application uses gulp to process and minify styles, concat JavaScript files and move processed files to a distribution directory so it's easier to upload them as Static Resource. Check out gulpfile.js for more details but essentially the only command we'll need is gulp watch
so run that at the command like:
You should now see a couple of folders appear in the /dist
directory. Zip up these scripts
and styles
folders and create a Static Resource in Salesforce named banners
with that zip. This will get all of the JavaScript code that we are going to talk about in a second hosted on the platform. We'll reference these files in our Visualforce page.
Code Walkthrough
Let's take a look at the important files in the application. I won't cover everything but the great thing about React and Reflux is that it's rather straightforward and easy to follow. The SPA consists of a single Visualforce page. That's it. No controller. No Apex code at all. We'll be using Visualforce Remote Objects which create proxy objects that enable basic DML operations on sObjects directly from JavaScript. All of our logic and functionality resides in the JavaScript!
Banners.page
Since Visualforce is hosting our SPA, Banners.page is the only page that we'll need. It's mostly bootstrap markup with navigation, a title and a div (id="app")
where React will render our application. Notice the script and style tags that reference the banners
static resource we just uploaded.
Perhaps the most important part of the Visualforce page are the apex tags at the top where we add the Remote Objects that the application will use. We define our remoteObjectModel and specify that it will proxy for the Banner__c
custom object and give it an alias of Banner (for ease of use in JavaScript). We also specify the fields that the proxy will have access to in the custom object.
App.js
The app.js file is almost identical to the previous version. It sets up our application, defines the routes and renders the app. For our application we'll be using react-router which provides... wait for it... wait for it... routing for our application. The routes
variable defines the view hierarchy for our application. We then declare a view hierarchy with nested
s and provide them with a React element to handle the route when it's active.
The React component simply renders the
component that, in turn, renders the currently active child route for the application. The last section, Router.run
, is somewhat magical. When the user selects a route, the run
callback receives Handler
, that has all of its appropriate information wrapped up in it. If our app would be managing some type of state, we could pass this state down the view hierarchy. For our use case, we simply use the standard boilerplate and render our application into the app
div.
BannerStore.js
The heart of the application is the Reflux store which holds all of the model, business logic and interactions with the Force.com platform. Here is the complete code. The store holds a private data
object which the users interact with through various calls from components (e.g., getBanners(), getBanner(id), etc.). The store loads this initial state as an empty array of banners.
In our init
function we are using the Banner
remote object to query the Banner__c custom object for up to 5 records. In the callback we create a banner object with simpler properties and push each one to the state's array of banners. Then we trigger the DOM update to display the records in the HTML table. We also register some actions (toggleStatus
and addBanner
) that the store listens for and binds them to individual function that handle the appropriate logic.
The addBanner
function is called whenever the store hears the addBanner
action (i.e., the user submitted the add banner form). We use the remote object again and create a new record in the Banner__c custom object using the details from the banner object passed to the function. If the record was inserted correctly, it pushes the new banner object to the array of banners in the state and updates the DOM to display the new row in the HTML table. Magic!
The toggle
function is called whenever the store hears a toggleStatus
action. This function fetches the appropriate banner in the state array by its ID and then toggles its active
property. The remote object is then used to update the record in the custom object. The trigger method passes the change notification to any listeners to update the DOM.
Actions.js
The action.js, since it uses Reflux, is much smaller and simpler than the standard Flux Dispatcher. It simply defines the actions that our app will broadcast. Not much to see here. Simplicity is good!
Banners.js
This view component is responsible for displaying our table of banner data. When initialized, the getInitialState
method is called and loads the banner data from the store into the state. When the component renders, it first creates a variable of rows
that is used to display the actual data in the table rows.
Note: there is a slight bug I ran across in the Reflux store when calling code asynchronously which seems to loose the reference to its state. It works fine when called synchronously, but doesn't render properly otherwise. The code I used ensures that the component listens to changes in the store and updates its state appropriately as a workaround.
View.js
The view.js file is pretty interesting and has a lot going on. First, the class uses an array of mixins to add functionality to the component. When the component initially mounts, is uses Reflux's ListenerMixin
to listen for changes in the BannerStore and act accordingly. The getInitialState
method grabs the ID of the banner being viewed and calls the BannerStores' getBanner
method and adds it to the state. When the component renders, it displays this state data on the page.
There is also a 'Toggle Active' button that, when clicked, broadcasts actions.toggleStatus
and passes the ID of the banner. The BannerStore is responsible for toggling the Yes/No status of the banner and then notifies any listeners that there has been a change. This view component listens for any change to the BannerStore and then calls toggleStatus
to change the state and update the DOM.
Add.js
Our final component displays a form for entering a new banner. IMHO it takes more work that I think it should. I tried to implement a couple of libraries such as react-froms and react-bootstrap but had much better luck rolling my own for this simple application.
When the component initializes, getInitialState
sets up the state with default error and banner objects. The errors object be will used to notify the user that a field is required when submitting while the banner object will default in some data to the form fields.
When the component renders it calls renderTextInput
for each of the three form fields. This adds the appropriate HTML to the DOM to make the field look pretty and sets up any error notifications when the form is submitted. The value of the form field is bound to the banner in the state and fires the handleChange
event whenever the user changes the text (i.e., typing). The handleChange
function updates the state which re-renders the DOM node for the form field.
When the user clicks the submit button, the form's onSubmit
handler calls the handleSubmit
function which check to make sure all fields are filled out. If a required field is blank, it adds this to the state's error object which display the field in a red box. If everything is filled out correctly, it calls the BannerStore's addBanner
method with the new banner data, resets the component's state and display the home page which show the newly added banner in the table.
Conclusion
So there you have it, a minimal SPA written in React and Reflux on Force.com that you can use as a starter for your own project!