Salesforce Lightning Example – Ideas Experiment

Over the holidays I had some down time to dive more deeply into Lightning and create a sample application. I had already done a bunch of work around leads and cases, so I decided to use the Ideas capability as the foundation and place a new face on it with Lightning technology.

salesforce-lighting-ideas-example-brunswick

I focused on learning key aspects like Components, Controllers, Rendering Life Cycle, Expressions, Style, Events and Attributes to name a few and see how my existing skills (CSS & JS) could be leveraged within the framework. Big thanks to Jeff’s Douglas’s Lightning Blog Post Tutorial that provided a great sample to learn from.

Overall Thoughts

  • Scoping – nicely done using some Selector magic so that client markup from Components should not collide
  • Discrete Components – awesome future potential, as components can be designed to require well defined context / inputs
  • Style – no vendor prefixes. Did I mentioned no vendor prefixes? Not only does it save many lines of code, but my finger’s health appreciate it.
  • Events – structured way to have components react to one another in an loosely coupled manner
  • Attributes – great for not only storing basic values, but objects to be accessed in the controller without creating some global variable (for instance, many JS libraries assume very broad scope of variables)

Code in Action
The following video gives an overview of our code in action

A Few Highlights
I ended up using an Attribute of type Object to store a variable that is from a 3rd party library (Isotope), as it would generally be assigned to a global variable. It needed to be accessible once created in the controller for subsequent use and the Attribute worked nicely.

For sequential loading of JS and CSS libraries I used a load component from https://github.com/rajaraodv/loadcomponent

Install & Get the Code
1. Enable Lightning Components
2. Setup your namespace prefix (you will need to update the sample code to match this value [e.g.
3. Install the Unmanaged Package from https://login.salesforce.com/packaging/installPackage.apexp?p0=04tj0000000Jrpy
3. Update the prefixes in your code (by default I use JMB, update to reflect whatever you need)
4. Create an Idea in the OOTB UI to prepare some data to use in the UI
5. Use Workbench to execute some SOQL to get the CommunityId that your Idea was created within (e.g. SELECT Body,CommunityId FROM Idea)
6. Update line 18 in CardViewer.cmp to change ‘CommunityId’: ’09aj00000005dCsAAI’ to your CommunityID
7. Update CaseCards.app, CardViewer.cmp and VoteBox.cmp with your vendor prefix
8. Navigate to – https://naXX.lightning.force.com/yourprefix/CaseCards.app to run your app! (you will have to use the na instance for your Salesforce org)

A Peak at the Code
This is the main portion of our code and it illustrates how the various pieces come together to create the user experience.


<aura:component controller="jmb.CaseCards" implements="force:appHostable">
<aura:attribute name="idea" type="Idea" default="{'sobjectType': 'Idea'}"/>
<aura:attribute name="ideas" type="Idea[]"/>
<aura:attribute name="profilePhotoUrl" type="String"/>
<aura:attribute name="ideaComments" type="IdeaComment[]"/>
<aura:attribute name="textareaNewIdeaValue" type="String" default="" />
<aura:attribute name="searchTerm" type="String" default="" />

<!-- Setting a Community ID by deafult for this example - update to use your own
you can find the ID using http://workbench.developerforce.com/login.php and querying
SELECT Body,CommunityId FROM Idea
for Ideas that you have created via the OOTB UI
-->
<aura:attribute name="newIdea" type="Idea"
default="{ 'sobjectType': 'Idea',
'Title': '',
'Body': '',
'CommunityId': '09aj00000005dCsAAI'
}"/>
<aura:attribute name="newIdeaComment" type="IdeaComment" default="{ 'sobjectType': 'IdeaComment', 'CommentBody': ''}"/>

<!-- To load JS libraries in a particular order, I used the code from
https://github.com/rajaraodv/loadcomponent -->
<jmb:load
filesInParallel="/resource/bootstrapcssmin.sfcss,/resource/animate.sfcss"
filesInSeries="/resource/isotope.sfjs,/resource/jquerymin.sfjs,/resource/modernizr.sfjs,/resource/timeago.sfjs,/resource/bootstrapjs.sfjs" />

<aura:handler event="jmb:staticResourcesLoaded" action="{!c.initScripts}"/>

<!-- This attribute will hold out JS object that represents our "cards" -->
<aura:attribute name="objIsotope" type="Object" />

<!-- Note - fixed header will only work in non S1 environments with our example -->
<div id="fixedheader">
<div class="titleheader">
<img src="/resource/lightningidealogo" class="logo"/>
</div>

<nav class="navbar navbar-default" role="navigation">
<div class="container">

<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapsible">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>

<div class="navbar-collapse collapse tabs" id="navbar-collapsible">

<ul class="nav navbar-nav">
<li class="tabitem active">
<a id="buttonFilterFresh" href="#" onclick="{!c.getFilterFresh}" class="filterbutton">fresh<span></span></a>
</li>
<li class="tabitem">
<a id="buttonFilterOpen" href="#" onclick="{!c.getFilterTop}" class="filterbutton">trending<span></span></a>
</li>
<li class="tabitem">
<a id="buttonFilterComments" href="#" onclick="{!c.getFilterComments}" class="filterbutton">comments<span></span></a>
</li>
<li class="tabitem">
<a id="buttonFilterTotal" href="#" onclick="{!c.getFilterVoteTotal}" class="filterbutton">total votes<span></span></a>
</li>
</ul>

</div>
</div>
</nav>

</div>

<!-- Using Isotope library - if used commercially you will need a license. Very inexpensive and worth it! -->
<div id="grid-gallery" class="grid-gallery" style="-webkit-overflow-scrolling: touch;">
<section class="grid-wrap">
<div class="grid">

<aura:iteration items="{!v.ideas}" var="p">
<div class="card box-shadow-outset" onclick="{!c.openIdeaDetail}" data-ideaid="{!p.id}">

<div class="carddetails">
<img src="{!p.CreatorFullPhotoUrl}" class="profilephoto"/>

<aura:if isTrue="{!p.NumComments > 0}">
<div class="commentcount">
{!p.NumComments}
</div>
</aura:if>

<h3>{!p.title}</h3>
<p>
by {!p.CreatorName} <span class="timeago case-date" title="{!p.CreatedDate}">{!p.CreatedDate}</span>
</p>
</div>

<!-- The "chart" is the color that is applied to each card background -->
<jmb:VoteChart ideaObject="{!p}"/>

<!-- Hidden to be used for filtering - could be more elegant, but this is simple -->
<p class="votescore" style="display:none">{!p.VoteScore}</p>
<p class="numcomments" style="display:none">{!p.NumComments}</p>
<p class="freshdate" style="display:none">{!p.CreatedDate}</p>
<p class="votetotal" style="display:none">{!p.VoteTotal}</p>
</div>
</aura:iteration>

</div>
</section>
</div>

<!-- Bootstrap modal used to show details for a given idea -->
<div class="modal fade" id="ideadetail">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header detail">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&amp;times;</span></button>
<h4 class="modal-title">{!v.idea.title}</h4>
<jmb:renderHTML rawHTML="{!v.idea.Body}"/>
<jmb:VoteBox parentIdeaId="{!v.idea.id}"/>

</div>
<div class="modal-body comments">
<div class="container-fluid">
<aura:iteration items="{!v.ideaComments}" var="ic" indexVar="i">
<div class="row comment fade-in" style="{!'-webkit-animation-delay:' + i + '00ms;'}">
<div class="col-md-1"><img src="{!ic.CreatorSmallPhotoUrl}" class="commentphoto"/></div>
<div class="col-md-11"><div class="ideacommentbody">{!ic.CommentBody}<br/>
<span class="timeago case-date" title="{!ic.CreatedDate}">{!ic.CreatedDate}</span></div>
</div>
</div>
</aura:iteration>
</div>
</div>
<div class="modal-body">

<div class="container-fluid">
<div class="row commentcreate">
<div class="col-xs-3 col-md-1"><img src="{!v.profilePhotoUrl}" class="commentphoto currentuser"/></div>
<div class="col-xs-9 col-md-10">

<div class="group">
<ui:inputText aura:id="commentBody" label="" value="{!v.newIdeaComment.CommentBody}" required="true"/>
<span class="highlight"></span>
<span class="bar"></span>
<label class="material">Your Comment</label>
</div>

</div>
<div class="col-xs-12 col-md-1">
<a href="#" onclick="{!c.postIdeaComment}" data-ideaid="{!v.idea.id}" class="linkstandard">Post</a>
<!--<button type="button" class="btn btn-primary" onclick="{!c.postIdeaComment}" data-ideaid="{!v.idea.id}">Post</button>-->
</div>
</div>
</div>

</div>
<div class="modal-footer" style="display:none">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

<!-- Bootstrap modal used for people to create ideas -->
<div class="modal fade" id="newidea" data-easein="fadeInLeft" data-easeout="fadeOutLeft" >
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&amp;times;</span></button>
<h4 class="modal-title">Share an Idea</h4>
</div>
<div class="modal-body">
<div class="group">
<ui:inputText aura:id="createtitle" value="{!v.newIdea.Title}" required="true"/>
<span class="highlight"></span>
<span class="bar"></span>
<label class="material">Name</label>
</div>

<p>

<ui:inputText aura:id="createdescription" label="" placeholder="Details" value="{!v.newIdea.Body}" class="hidden" />

<textarea rows="4" cols="50" onchange="{!c.changeField}" placeholder="Tell us about it..."></textarea>

</p>
</div>
<div class="modal-footer">

<a id="postButton" href="#" onclick="{!c.postIdea}" class="linkstandard">Post</a>
<a id="cancelButton" data-dismiss="modal" class="linkstandard grey">Cancel</a>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

<!-- Button to create an idea, the position is done via JS. Not ideal, but it will check to see where
it is continously, as I could not get a clean way to get an onscroll JS event for the UI
when using in S1 -->
<div class="row">
<button type="button" class="btn btn-primary circle" id="createidea" data-toggle="modal"
data-target="#newidea">+</button>
</div>

</aura:component>

Final Note
To make the sample more polished – we should use an aura:asyncRerender event to let our app know when an aura:iteration element is done rendering new data into the UI based on what you have done in the controller. In the sample code, we simply wait a moment and then perform other operations against the DOM to update it and assume that aura:iteration is done adding elements to the UI.

29 Comments
  • moshekarmel1
    Reply
    February 3, 2015

    This looks really cool!

  • moshekarmel1
    Reply
    February 3, 2015

    Just got this to work in my developer org! Amazing how you recreated the material design look and feel. I’m more of a code guy, but is all that magic via the static resources? Can it be done with standard jQuery and CSS alone?

  • John Brunswick
    Reply
    February 4, 2015

    moshekarmel1 thanks very much for the kind words!  The magic is all done via Javascript and CSS.  The specific library that does the “card” style sorting can work with or without JQuery (JQuery just generally makes coding client side easier, so as not to deal with platform specifics between IE, Chrome, etc).

    Basically, the libraries like Isotope – just help to encapsulate a lot of JS code.

    To make the “magic” work, I needed to spend time to understand the Lightning life cycle and how these other libraries could work seamlessly with it.  Specifically, using Isotope, I needed to actually store the result of its work in a variable that was needed in various Controllers, etc.

    Thanks again for the kind words – awesome that you got it up and running!

  • TimMonks
    Reply
    February 22, 2015

    Are there any prerequisites required to be able to install? – I’m getting an install failure in my dev org. I’ve done the basics like enable Lightning, set namespace, create an idea, create a community.
    Keen to explore this – thanks

  • John Brunswick
    Reply
    February 24, 2015

    TimMonks thanks – sorry for the delay, have been traveling and just catching up… were you able to pin down what the issue might be?  Is this an existing org?  Could you attempt in a new org?  Thanks!

  • ankitg
    Reply
    February 28, 2015

    Hi John,

    I’m getting error while installing this unmanage package.

    Error is “Your requested install failed. Please try this again.
    None of the data or setup information in your salesforce.com organization should have been affected by this error.
    If this error persists, contact salesforce.com Support through your normal channels and reference number: 1059976480-23688 (-795222364)

  • John Brunswick
    Reply
    March 5, 2015

    ankitg thanks – on vacation this week, but will look when I get back next week.  I suspect that it may have to do with me using a namespace for the code, which is now optional in the new release.  Thanks for your patience!

  • goleo
    Reply
    March 8, 2015

    Hi I get this error as well:

    Your requested install failed. Please try this again.
    None of the data or setup information in your salesforce.com organization should have been affected by this error.
    If this error persists, contact salesforce.com Support through your normal channels and reference number: 1715943594-53771 (-265527156)

  • John Brunswick
    Reply
    March 11, 2015

    goleo thanks – will update the post above, but it has to do with the namespace settings.  Have posted the raw source @ https://github.com/JohnBrunswick/Lightning-Ideas-Experiment for you to use directly until I get a chance to update the above

  • John Brunswick
    Reply
    March 11, 2015

    ankitg thanks – will update the post above, but it has to do with the namespace settings.  Have posted the raw source @ https://github.com/JohnBrunswick/Lightning-Ideas-Experiment for you to use directly until I get a chance to update the above

  • brianromanowski
    Reply
    March 26, 2015

    John Brunswick goleo IT looks like the github project is missing some things vs the unmanaged package.

  • brianromanowski
    Reply
    March 27, 2015

    Hi John,
    I’ve been trying to work with the source on github but there appears to be missing components. Are you going to be updating it? Specifically the renderHTML and CommentHTML.’
    Thanks!

  • Sachin Bhadane
    Reply
    March 31, 2015

    Hi John,
    I am not able to install this package neither on my Enterprise org nor on my dev org.
    Following are Error Details:

    Your requested install failed. Please try this again.
    None of the data or setup information in your salesforce.com organization should have been affected by this error.
    If
    this error persists, contact salesforce.com Support through your normal
    channels and reference number: 1165502268-89333(-1606994532)
     Please guide me ?

  • John Brunswick
    Reply
    March 31, 2015

    brianromanowski thanks – just updated the repository!  Take a look now and let me know if it works.

  • brianromanowski
    Reply
    April 3, 2015

    John Brunswick Thanks John. Almost there. 

    There’s something going on with the onclick for openIdeaDetail. The modal for the idea doesn’t show. I’ve placed a window.alert before and after the evt,setparams on it and it only works before, not after. Also if I comment out the evt.setparams I get the modal showing with the description and comments.

    Also there was a typo in the cardviewer component, title was lowercase and was case sensitive for me:

    <div class=”carddetails”>
                              <img src=”{!p.Merchants__PhotoURL__c}” class=”profilephoto”/>
                                <aura:if isTrue=”{!p.NumComments > 0}”>
                                    <div class=”commentcount”>                           
                                        {!p.NumComments}
                                    </div>
                                </aura:if>                                    
                                <h3>{!p.Title}</h3>
                                <p>
                                    by {!p.Merchants__Submitted_By__c} <span class=”timeago case-date” title=”{!p.CreatedDate}”>{!p.CreatedDate}</span> 
                                </p>
                            </div>

  • brianromanowski
    Reply
    April 3, 2015

    John Brunswick Oops it took my bold and underline and made it all caps. It should be Title

  • brianromanowski
    Reply
    April 6, 2015

    John Brunswick Figured it out, the namespace was case sensitive.

  • moshekarmel1
    Reply
    May 4, 2015

    I ran into some issues with opening the detail for a card. The Id getting passed in was undefined. When I changed this line

    <div class=”card box-shadow-outset” onclick=”{!c.openIdeaDetail}” data-ideaid=”{!p.id}”>

    To 

    <div class=”card box-shadow-outset” onclick=”{!c.openIdeaDetail}” data-ideaid=”{!p.Id}”> 

    Everything started working again. Seems like there is some case sensitivity.

  • moshekarmel1
    Reply
    May 4, 2015

    Id instead of id

  • John Brunswick
    Reply
    May 7, 2015

    moshekarmel1 thanks – much appreciated!  Good catch

  • brianromanowski
    Reply
    May 8, 2015

    How could attaching a file be added when creating an idea?

  • John Brunswick
    Reply
    May 14, 2015

    brianromanowski have not tried it yet, but take a peak at http://peterknolle.com/file-upload-lightning-component/.  In general – using JS on the client, some magic needs to be done for the file encoding, but the good part is that is a well-worn path, as we had to do the same thing with VF Remoting, etc.

  • brianromanowski
    Reply
    May 14, 2015

    John Brunswick brianromanowski Thanks John, I actually found that and got it working yesterday. Now I’m trying to figure out how to filter with Isotope. I’ve added a class to the isotope card for the status and I want to filter on it. Filtering on the class itself seems easy but filtering based on the value of the class is difficult, Most of what I’ve found is for filtering on a numerical value(i.e.>50 votes), not for text like ideas in the submitted stage.

  • brianromanowski
    Reply
    May 14, 2015

    John Brunswick
    Figured it out. I changed the class for the card Div to include the field I want to filter on and then you can just change the filters in the controller.

                      <div class=”{!’card box-shadow-outset ‘+ p.Stage}” onclick=”{!c.openIdeaDetail}” data-ideaid=”{!p.Id}”>

  • dancinllama
    Reply
    May 27, 2015

    This is a great post and I’m looking forward to diving more into the components.  I see others have also had the installation failure for the unmanaged package.  I think it’d be helpful to at least add the link to your github repository in the post above.  Also, Andrew Fawcett wrote a useful “github to salesforce” deployment button, which you could use in lieu of the unmanaged package until you resolve the installation issue.  Here’s the link for that: http://andyinthecloud.com/2013/09/24/deploy-direct-from-github-to-salesforce/

  • hinarayan
    Reply
    June 11, 2015

    dancinllama I have the same problem of not being able to use the GitHub code or the unmanaged package to leverage the cool SFDC ideas capability. Can somebody share the latest code so that it can be installed on a dev sandbox?John Brunswick

  • vppravi93
    Reply
    November 28, 2015

    Hi,its a grear post john.But package inatallation doesn’t seem to work.It show this message
    No EVENT named markup://jmb:staticResourcesLoaded found : [markup://c:CardViewer]

  • coder312
    Reply
    April 11, 2016

    Hi John Brunswick –

    We are getting the following error even though we’ve removed the references to your namespace.  Can you please help?

    Uncaught TypeError: Cannot read property ‘fire’ of undefined

  • John Brunswick
    Reply
    June 11, 2019

    Some test here…

Leave a comment