lighting experience withour lighting components

Lightning Experience without Lightning Components

If you like new Lightning Experience UI but have a lot of actual projects that, you think, will not work correctly with enabled Lightning Experience, there is an ability to make Classic Salesforce appearance belike Lightning. Maybe Salesforce Lightning Design System (SLDS) will help us solve this problem.

Lightning Design System is a CSS framework like Twitter Bootstrap. It contains a lot of components that you can see in Salesforce with enabled Lightning Experience. Resources of the Design System:

  • CSS framework – components and grid layout system;
  • Icons – PNG and SVG icons;
  • Font – Salesforce Sans font;
  • Design Tokens – customizable variables that includes colors, fonts, spacing, and sizing.

But if you want to use the complete SLDS functionality you must know a few things about it:

  • Apex tags such as <apex:inputField> and <apex:pageBlock> are not supported.
  • For best results you have to use JavaScript Remoting and Remote Objects.
  • If you need to use the standard Salesforce header and sidebar, you cannot use SVG icons.

If these restrictions don’t challenge you, let’s try SLDS in action, because it has only CSS framework without any Javascript. We need also JS to make it fully work as expected. Of course, you can create your own customJS library for SLDS, but also you can use Appiphony Lightning JS (ALJS). It has status Beta, but contains all the main components supporting SLDS.

Before you start to develop Visualforce page, you need to install SLDS and ALJS to your organization. To get SLDS just install the latest unmanaged package from the official web site. Afterwards, download ALJS library and upload aljs.zip file from the “dist” directory to your org’s Static Resources and name it “aljs”.

And now create new VF page to view all projects (custom object) and add new ones:

<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">
<!-- xmlns for supporting SVG sprite maps -->
<html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

<head>
<title>Project Manager powered by SLDS with ALJS</title>

<!-- importing SLDS' CSS -->
<apex:stylesheet value="{! URLFOR($Resource.SLDS100, 'assets/styles/salesforce-lightning-design-system-vf.min.css') }" />

<script>
// define the assets location for SLDS (this enables to use SLDS' fonts and icons)
var assetsLocation = '{! URLFOR($Resource.SLDS100) }';
</script>
<!-- jQuery required for ALJS -->
<script src="{! $Resource.jQuery }"></script>
<!-- moments.js for ALJS' datepicker -->
<script src="{! $Resource.moments }"></script>
<!-- importing all modules of ALJS -->
<script src="{! URLFOR($Resource.aljs, 'jquery/jquery.aljs-all.min.js') }"></script>
</head>
<body>
<!-- required SLDS wrapper -->
<div class="slds">

</div>
</body>
</html>
</apex:page>

As you can see, we define the SLDS assets location. It is used by ALJS to access SLDS resources. We also add xmlns and xlink attributes to html tag to enable SVG images (if you are going to use Internet Explorer 11, you need to add svg4everybody script on your page.

The .slds class is a main wrapper of Design System. It’s like .container of Twitter Bootstrap. All SLDS classes use standard class naming convention named Block-Element-Modifier Syntax. A block is a high-level component (e.g. .tree), an element is a child component (e.g. .tree__leave) and a modifier is a state of a block or an element (e.g. .tree__leave-yellow).

Now let’s add a header to our page into the SLDS wrapper:

<!-- PAGE HEADER -->
<div class="slds-page-header" role="banner">
<!-- LAYOUT GRID -->
<div class="slds-grid">
<!-- GRID COL -->
<div class="slds-col">
<div class="slds-media">
<div class="slds-media__figure">
<span class="slds-avatar slds-avatar--large">
<img src="{!$Resource.ProjectIcon}" alt="" />
</span>
</div>
<div class="slds-media__body">
<p class="slds-text-heading--label">Projects</p>
<h1 class="slds-text-heading--medium">All Projects</h1>
</div>
</div>
</div>
</div>
<!-- / LAYOUT GRID -->
</div>
<!-- / PAGE HEADER -->

Grid system of SLDS is a bit like that of the Bootstrap. Inside .slds-grid you can add the required number of columns by adding .slds-col. See the example:

<div class="slds-grid">
  <div class="slds-col">Column 1</div>
  <div class="slds-col">Column 2</div>
  <div class="slds-col">Column 3</div>
</div>

f you need define a column width, you can use sizing helper classes. Use the.slds-size--X-of-Y format where X is a column width and Y is a total column space (it can be 2, 3, 4, 5, 6 or 12). If you want to specify a grid for small screens (>480px) you have to use .slds-small-size--X-of-Y and .slds-medium-size--X-of-Y for larger screens (> 768px).

Add an action button into our header to create a new Project:

<!-- ACTION BUTTON -->
<div class="slds-col slds-no-flex slds-align-middle">
<button class="slds-button slds-button--neutral" data-aljs="modal" data-aljs-show="modal-1">
Add Project
</button>
</div>
<!-- / ACTION BUTTON -->
lighting experience withour lighting components

As you can see, we have added ALJS attributes data-aljs and data-aljs-show to the button tag. The first attribute initializes an element for using it by ALJS and the second one contains element id that will be open. Then we insert body modal div in the end of page:

<!-- ADD PROJECT MODAL -->
<div class="aljs-modal-container">
<div aria-hidden="true" role="dialog" class="slds-modal slds-fade-in-open slds-hide" id="modal-1">
<div class="slds-modal__container">
        <div class="slds-modal__header">
            <h2 class="slds-text-heading--medium">New Project</h2>
            <button class="slds-button slds-button--icon-inverse slds-modal__close" data-aljs-dismiss="modal">
                <svg aria-hidden="true" class="slds-button__icon slds-button__icon--large">
                    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{! URLFOR($Resource.SLDS100, '/assets/icons/action-sprite/svg/symbols.svg#close') }"></use>
                </svg>
                <span class="slds-assistive-text">Close</span>
            </button>
        </div>
        <div class="slds-modal__content slds-p-around--medium">
                <!-- NEW PROJECT FORM -->
    <!-- / NEW PROJECT FORM -->
        </div>
        <div class="slds-modal__footer">
            <button class="slds-button slds-button--neutral" data-aljs-dismiss="modal">Cancel</button>
            <button class="slds-button slds-button--neutral slds-button--brand" data-aljs-dismiss="modal">Save</button>
        </div>
    </div>
</div>
</div>
<!-- / ADD PROJECT MODAL -->

But it does not work yet. We need initialize our modal in JavaScriptby adding

$(document).ready():

$(document).ready(function() {

$('[data-aljs="modal"]').modal();

});

Now reload page and try to click the button:

lighting experience withour lighting components

How to add header into your page

Our page looks empty. Let's create some data! First, we add a header of the future project table:

<!-- PROJECT LIST TABLE -->
<div id="projectList" class="slds-p-vertical--medium">
<table class="slds-table slds-table--bordered">
<thead>
<tr class="slds-text-heading--label">
<th class="slds-cell-shrink">
<label class="slds-checkbox">
<input type="checkbox" name="options" />
<span class="slds-checkbox--faux"></span>
<span class="slds-assistive-text">Select All</span>
</label>
</th>
<th class="slds-is-sortable" scope="col">
<div class="slds-truncate">Project Name
<button class="slds-button slds-button--icon-bare">
<svg aria-hidden="true" class="slds-button__icon slds-button__icon--small">
<use xlink:href="{! URLFOR($Resource.SLDS100, '/assets/icons/utility-sprite/svg/symbols.svg#arrowdown') }"></use>
</svg>
<span class="slds-assistive-text">Sort</span>
</button>
</div>
</th>
<th class="slds-is-sortable" scope="col">
<div class="slds-truncate">Estimated Amount
<button class="slds-button slds-button--icon-bare">
<svg aria-hidden="true" class="slds-button__icon slds-button__icon--small">
<use xlink:href="{! URLFOR($Resource.SLDS100, '/assets/icons/utility-sprite/svg/symbols.svg#arrowdown') }"></use>
</svg>
<span class="slds-assistive-text">Sort</span>
</button>
</div>
</th>
<th class="slds-is-sortable" scope="col">
<div class="slds-truncate">Estimated Hours
<button class="slds-button slds-button--icon-bare">
<svg aria-hidden="true" class="slds-button__icon slds-button__icon--small">
<use xlink:href="{! URLFOR($Resource.SLDS100, '/assets/icons/utility-sprite/svg/symbols.svg#arrowdown') }"></use>
</svg>
<span class="slds-assistive-text">Sort</span>
</button>
</div>
</th>
<th class="slds-is-sortable" scope="col">
<div class="slds-truncate">Status
<button class="slds-button slds-button--icon-bare">
<svg aria-hidden="true" class="slds-button__icon slds-button__icon--small">
<use xlink:href="{! URLFOR($Resource.SLDS100, '/assets/icons/utility-sprite/svg/symbols.svg#arrowdown') }"></use>
</svg>
<span class="slds-assistive-text">Sort</span>
</button>
</div>
</th>
<th class="slds-is-sortable" scope="col">
<div class="slds-truncate">Due Date
<button class="slds-button slds-button--icon-bare">
<svg aria-hidden="true" class="slds-button__icon slds-button__icon--small">
<use xlink:href="{! URLFOR($Resource.SLDS100, '/assets/icons/utility-sprite/svg/symbols.svg#arrowdown') }"></use>
</svg>
<span class="slds-assistive-text">Sort</span>
</button>
</div>
</th>
<th class="slds-is-sortable" scope="col">
<div class="slds-truncate">Customer
<button class="slds-button slds-button--icon-bare">
<svg aria-hidden="true" class="slds-button__icon slds-button__icon--small">
<use xlink:href="{! URLFOR($Resource.SLDS100, '/assets/icons/utility-sprite/svg/symbols.svg#arrowdown') }"></use>
</svg>
<span class="slds-assistive-text">Sort</span>
</button>
</div>
</th>
<th class="slds-cell-shrink"></th>
</tr>
</thead>
</table>
</div>
<!-- / PROJECT LIST TABLE -->

We use Remote Object to fill the header with data:

<!-- REMOTE OBJECT -->
<apex:remoteObjects>
<apex:remoteObjectModel name="Project__c" jsShorthand="Project" fields="Name,Id,Status__c,Estimated_Amount__c,Estimated_Hours__c,Due_Date__c,Customer__c">
</apex:remoteObjectModel>
</apex:remoteObjects>
<!-- / REMOTE OBJECT -->

Bellow there is a script that creates a table body and fill it by projects:

// remote object Project__c
var project = new SObjectModel.Project();
var projectTable = document.getElementById("projectTable");

// fetching all projects and fill the table
function fetchAllProjects(){
  project.retrieve({orderby: [{Name: 'ASC'}]},
      function(error, records) {
        if (error) {
          alert(error.message);
        } else {
          // build table body
          var tableBody = projectTable.appendChild(document.createElement('tbody'));
          var dataRow, dataRowCell1, dataRowCell2, dataRowCell3, dataRowCell4, dataRowCell5, dataRowCell6;
          var projectName, projectAmount, projectHours, projectStatus, projectDueDate, projectCustomer;
          records.forEach(function(record) {
dataRow = tableBody.insertRow();
dataRow.setAttribute("class", "slds-hint-parent");
dataRowCell1 = dataRow.insertCell(0);
dataRowCell1.setAttribute("class", "slds-cell-shrink");
dataRowCell1.setAttribute("data-label", "Select Row");
var lbl = document.createElement('label');
lbl.setAttribute("class", "slds-checkbox");
lbl.innerHTML = '<input type="checkbox" name="options" /><span class="slds-checkbox--faux"></span><span class="slds-assistive-text">Select Row</span>';
dataRowCell1.appendChild(lbl);
dataRowCell1 = dataRow.insertCell(1);
projectName = document.createElement('a');
projectName.setAttribute('href','/' + record.get("Id"));
projectName.innerHTML = record.get("Name");
dataRowCell1.appendChild(projectName);
dataRowCell1.setAttribute("data-label", "Project Name");
dataRowCell2 = dataRow.insertCell(2);
projectAmount = document.createTextNode(record.get("Estimated_Amount__c"));
dataRowCell2.appendChild(projectAmount);
dataRowCell2.setAttribute("data-label", "Estimated Amount");
dataRowCell3 = dataRow.insertCell(3);
projectHours = document.createTextNode(record.get("Estimated_Hours__c"));
dataRowCell3.appendChild(projectHours);
dataRowCell3.setAttribute("data-label", "Estimated Hours");
dataRowCell4 = dataRow.insertCell(4);
projectStatus = document.createTextNode(record.get("Status__c"));
dataRowCell4.appendChild(projectStatus);
dataRowCell4.setAttribute("data-label", "Status");
dataRowCell5 = dataRow.insertCell(5);
projectDueDate = record.get("Due_Date__c") === undefined ? document.createTextNode("") : document.createTextNode(record.get("Due_Date__c"));
dataRowCell5.appendChild(projectDueDate);
dataRowCell5.setAttribute("data-label", "Due Date");
dataRowCell5.setAttribute("class", "slds-truncate");
dataRowCell1 = dataRow.insertCell(6);
dataRowCell1.setAttribute("class", "slds-cell-shrink");
dataRowCell1.setAttribute("data-label", "Actions");
var btn = document.createElement('button');
btn.setAttribute("class", "slds-button slds-button--icon-border-filled slds-button--icon-x-small");
btn.innerHTML = "<svg aria-hidden=\"true\" class=\"slds-button__icon slds-button__icon--hint slds-button__icon--small\"><use xlink:href=\"{! URLFOR($Resource.SLDS100, '/assets/icons/utility-sprite/svg/symbols.svg#down') }\"></use></svg><span class=\"slds-assistive-text\">Show More</span>";
dataRowCell1.appendChild(btn);
          });
        }
      }
    );

Now our page contains some information:

lighting experience withour lighting components

How to add a footer on the page

For greater perfection we can add a footer on the page:

<!-- FOOTER -->
<footer role="contentinfo" class="slds-p-around--large">
<!-- LAYOUT GRID -->
<div class="slds-grid slds-grid--align-spread">
<p class="slds-col">Powered by SLDS and ALJS</p>
<p class="slds-col">&copy; WaveOC</p>
</div>
<!-- / LAYOUT GRID -->
</footer>
<!-- / FOOTER -->
lighting experience withour lighting components

Further, we will see what SLDS in tandem with ALJS provide for working with forms. Let's append to our modal simple form with two inputs:

 <!-- NEW PROJECT FORM -->
<form class="slds-form">
<div class="slds-form-element slds-text-align--left">
<label class="slds-form-element__label" for="estimateAmount">Estimate Amount</label>
<div class="slds-form-element__control">
<input id="estimateAmount" class="slds-input" type="text" placeholder="Amount"/>
</div>
</div>
<div class="slds-form-element slds-text-align--left">
<label class="slds-form-element__label" for="estimateHours">Estimate Hours</label>
<div class="slds-form-element__control">
<input id="estimateHours" class="slds-input" type="text" placeholder="Hours"/>
</div>
</div>
</form>
<!-- / NEW PROJECT FORM -->

It looks very simple, because it is just text inputs. Let's add something else:

<div class="slds-form-element slds-text-align--left">
<label class="slds-form-element__label" for="status">Status</label>
<div id="status" class="slds-picklist" aria-expanded="true" data-aljs="picklist">
<button class="slds-button slds-button--neutral slds-picklist__label" aria-haspopup="true">
    <span class="slds-truncate">Select Status</span>
    <svg aria-hidden="true" class="slds-icon">
        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#down"></use>
    </svg>
</button>
<div class="slds-dropdown slds-dropdown--left slds-dropdown--menu slds-hide">
    <ul class="slds-dropdown__list" role="menu">
        <li id="menu-0-0" href="#" class="slds-dropdown__item">
            <a href="javascript:void(0)" role="menuitemradio">
                <p class="slds-truncate">
                    <svg aria-hidden="true" class="slds-icon slds-icon--selected slds-icon--x-small slds-icon-text-default slds-m-right--x-small">
                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#check"></use>
                    </svg>
                    Active
                </p>
            </a>
        </li>
        <li id="menu-0-1" href="#" class="slds-dropdown__item">
            <a href="javascript:void(0)" role="menuitemradio">
                <p class="slds-truncate">
                    <svg aria-hidden="true" class="slds-icon slds-icon--selected slds-icon--x-small slds-icon-text-default slds-m-right--x-small">
                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#check"></use>
                    </svg>
                    Completed
                </p>
            </a>
        </li>
        <li id="menu-0-2" href="#" class="slds-dropdown__item">
            <a href="javascript:void(0)" role="menuitemradio">
                <p class="slds-truncate">
                    <svg aria-hidden="true" class="slds-icon slds-icon--selected slds-icon--x-small slds-icon-text-default slds-m-right--x-small">
                        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#check"></use>
                    </svg>
                    Cancelled
                </p>
            </a>
        </li>
    </ul>
</div>
</div>
</div>

As you can guess it is a picklist. This is where the trouble begins. And it relates to the Beta status of ALJS. After we initialize the pick list in $(document).ready(), it will break our markup. Guys from Appiphony have not considered all the features of Salesforce static resources. And dynamic elements that use SVG and change it by JS doesn't work correctly. But I hope they will fix it in future releases.

Let's comment SVG usage and initialize elements to see our pick list:

$('[data-aljs="picklist"]').picklist();
lighting experience withour lighting components

Now we will add a nice date picker (but it uses SVG and faces the same problems like the pick list above):

<div class="slds-form-element slds-text-align--left">
    <label class="slds-form-element__label" for="date">Due Date</label>
    <div class="slds-form-element__control">
        <div class="slds-input-has-icon slds-input-has-icon--right">
            <!-- <svg aria-hidden="true" class="slds-input__icon slds-icon-text-default">
                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#event"></use>
            </svg> -->
            <input id="date" class="slds-input" type="text" placeholder="Pick a Date" label="Date Picker Label" />
        </div>
    </div>
</div>
lighting experience withour lighting components

Let's add another input that I really like in Lightning Experience:

<div class="slds-lookup" data-select="single" data-scope="single">
    <div class="slds-form-element slds-text-align--left">
        <label class="slds-form-element__label" for="lookup-1">Customers</label>
        <div class="slds-form-element__control slds-input-has-icon slds-input-has-icon--right">
            <!-- <svg aria-hidden="true" class="slds-input__icon">
                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons/utility-sprite/svg/symbols.svg#search"></use>
            </svg> -->
            <input id="lookup-1" class="slds-input" type="text" aria-autocomplete="list" role="combobox" aria-expanded="true" aria-activedescendant="" />
        </div>
    </div>
</div>

Yes, it's lookup field with combobox! Of course we need initialize the element:

$(document).ready(function() {
fetchAccounts();
    $('#lookup-1').lookup({
        items: customers,
    });
});

// remote object
var account = new SObjectModel.Account();
var customers = [];
// populate dropdown menu of lookup field
function fetchAccounts(){
account.retrieve({orderby: [{Name: 'ASC'}]},
function(error, records){
if (error) {
alert(error.message);
} else {
records.forEach(function(record){
customers.push({id: record.get("Id"), label: record.get("Name")});
});
return customers;
}
}
);
lighting experience withour lighting components

ALJS provides other elements of SLDS, such as icon groups, tabs, popovers and notifications. I hope those guys will not stop the development and will add new SLDS items, because someone can be not ready for the final transition from Classic Salesforce to Lightning Experience.

For more informatios about SLDS and ALJS, read in their official documentation:

  • Salesforce Lightning Design System https://www.lightningdesignsystem.com/
  • Appiphony Lightning JS http://aljs.appiphony.com/