Your SlideShare is downloading. ×
How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Upcoming SlideShare
Loading in...5

Thanks for flagging this SlideShare!

Oops! An error has occurred.


Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

How We Built the Private AppExchange App (Apex, Visualforce, RWD)


Published on

The AppExchange and Success Community team built a brand new app this year: the Private AppExchange. Join us and learn how the team built this managed package, the choices we made and why. We will …

The AppExchange and Success Community team built a brand new app this year: the Private AppExchange. Join us and learn how the team built this managed package, the choices we made and why. We will talk about the AppExchange Search Framework that all three of these products are built upon and we will talk about how we made a responsive UI that works on whatever device you choose.

Published in: Technology, Design

  • Be the first to comment

  • Be the first to like this

No Downloads
Total Views
On Slideshare
From Embeds
Number of Embeds
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

No notes for slide


  • 1. How we built the Private AppExchange app Using Apex, VisualForce and RWD Pratima Nambiar Jochem Geerdink Tech Lead AppExchange & Communities Product Designer AppExchange & Communities
  • 2. Safe harbor Safe harbor statement under the Private Securities Litigation Reform Act of 1995: This presentation may contain forward-looking statements that involve risks, uncertainties, and assumptions. If any such uncertainties materialize or if any of the assumptions proves incorrect, the results of, inc. could differ materially from the results expressed or implied by the forward-looking statements we make. All statements other than statements of historical fact could be deemed forward-looking, including any projections of product or service availability, subscriber growth, earnings, revenues, or other financial items and any statements regarding strategies or plans of management for future operations, statements of belief, any statements concerning new, planned, or upgraded services or technology developments and customer contracts or use of our services. The risks and uncertainties referred to above include – but are not limited to – risks associated with developing and delivering new functionality for our service, new products and services, our new business model, our past operating losses, possible fluctuations in our operating results and rate of growth, interruptions or delays in our Web hosting, breach of our security measures, the outcome of any litigation, risks associated with completed and any possible mergers and acquisitions, the immature market in which we operate, our relatively limited operating history, our ability to expand, retain, and motivate our employees and manage our growth, new releases of our service and successful customer deployment, our limited history reselling products, and utilization and selling to larger enterprise customers. Further information on potential factors that could affect the financial results of, inc. is included in our annual report on Form 10-K for the most recent fiscal year and in our quarterly report on Form 10-Q for the most recent fiscal quarter. These documents and others containing important disclosures are available on the SEC Filings section of the Investor Information section of our Web site. Any unreleased services or features referenced in this or other presentations, press releases or public statements are not currently available and may not be delivered on time or at all. Customers who purchase our services should make the purchase decisions based upon features that are currently available., inc. assumes no obligation and does not intend to update these forward-looking statements.
  • 3. Agenda  Intro  UI and RWD  Technical Deep Dive
  • 4. Our Team AppExchange Success Community
  • 5. What is the Private AppExchange? Private AppExchange is a private, corporate app store that enables companies to distribute apps to their employees.  Closed ecosystem  Secure and easy access to apps  Web and mobile apps
  • 6. Requirements  Desktop, tablet, phone  Professional, custom look & feel  Built using Apex and VisualForce  Managed Package
  • 7. UI – User Interface Design Specs IA – UX VisD HTML Mockups
  • 8. Mobile Technologies Considered  Native apps  Mobile version of the application  Web application using Responsive Web Design
  • 9. Definition RWD Responsive Web Design (RWD) is an approach to web design in which a site is crafted to provide an optimal viewing experience—easy reading and navigation with a minimum of resizing, panning, and scrolling—across a wide range of devices (from desktop computer monitors to mobile phones). In short: the site should be usable on all devices and should feel optimized for all devices.
  • 10. How to RWD? Media Queries! Media queries allow the page to use different CSS styles based on device capabilities. For RWD, we will mostly look at browser width.
  • 11. Media queries code structure
  • 12. RWD and Images  Use background images when possible  Lazy loading for better page performance
  • 13. Use background images in sprites Very useful for icons Think about HD displays (Retina)
  • 14. Code example background images <div class="msg-success"> <div class="msg-icon"></div> <p>The store is online.</p> </div>
  • 15. Lazy loading of images for better page performance
  • 16. Lazy loading of images for better page performance <span id="phone-test"></span> <span id="small-test"></span> <span id="large-test"></span> #phone-test, #small-test, #large-test { width: 1px; height: 1px; display: none; }
  • 17. Lazy loading of images for better page performance getCurrentSiteState = function() { var state = 'medium'; if (jQuery('#phone-test').css('display') === 'block') { state = 'phone'; } else if (jQuery('#small-test').css('display') === 'block') { state = 'small'; } else if (jQuery('#large-test').css('display') === 'block') { state = 'large'; } return state; };
  • 18. Lazy loading of images for better page performance
  • 19. Tile - Example <apex:component id="tile" > <apex:attribute name="tData" description="Data object" type="TileData" required="true" /> <div class="df-tile"> <div class="tile-img tile-img-brand"> <img src="{!$Resource.uilib}/img/p.gif" data-src="{!tData.bigImgURL}" class="desktop-img" /> </div> <div class="tile-img tile-img-logo"> <a href="#"><img src="{!tData.imgURL}" /></a> </div> <div class="txt-primary"> <a href="#"><apex:outputText value="{!tData.description}" /></a> </div> </div> </apex:component>
  • 20. Search Framework Objectives  Keyword Search • Relevant keyword search results for all objects  Filtering • Ability to add filters easily to quickly meet requirements  Sorting • Ability to add sort options easily to quickly meeting requirements
  • 21. Keyword Search - SOSL  Advantages • Allows you to search in text, phone and email fields in multiple objects with one simple query  Limitations • SOSL searches within all text fields and no one field or set of fields can be given more importance
  • 22. Keyword Search – Our Solution Our solution uses a combination of SOQL and SOSL  Example Listing object Field Name Features Short Description Long Description …… Type Text Text TextArea LongTextArea
  • 23. Keyword Search – Our Solution Group fields and assign a score to each group Field Group Name Features, Short Description Long Description Score 10 5 2
  • 24. Keyword Search – Our Solution Cont.  Decide whether to use SOQL or SOSL for searching within each group of fields Field Group Name Features, Short Description Long Description SOQL/SOSL SOSL SOQL SOSL  Build a score map to track the keyword relevance score of each result /* id to keyword relevance score map */ Map<ID,Integer> idToScoreMap = new Map<ID,Integer>();
  • 25. Keyword Search – Our Solution Cont.  Execute SOSL on the Name field FIND '*outlook integration*' IN NAME FIELDS RETURNING Listing__c (Id WHERE Public__c = true)  Execute SOQL using the “like” clause /* Execute this SOQL for every field group and update the score map */ For (Listing__c lst : [SELECT id FROM Listing__c WHERE (Features__c LIKE ‘%outlook%integration%’ OR ShortDescription__c LIKE ‘%outlook%integration%’) AND Public__c = true]) { Integer score = idToScoreMap.get(; score += WEIGHT_FOR_THIS_FIELD_GROUP; /* 5 in this example */ idToScoreMap.put(,score); }  Define a new object to store long text area fields and execute SOSL on that object FIND '*outlook integration*' IN ALL FIELDS RETURNING ListingExtension__c (Listing__c WHERE RecordType.Name=‘Description’ AND Listing__r.Public__c = true)
  • 26. Keyword Search – Our Solution Cont.  Sort by keyword relevance score /*Implement the Comparable interface to sort results by score*/ public class SearchResult implements Comparable public Integer compareTo(Object compareTo) { ……. } }
  • 27. Search Framework - Filtering Define a filter tree with a node to represent each filter you would like to support. • Data structure used to render filters UI and capture user’s selection • Search engine uses the filter tree to execute SOQL and return filtered results
  • 28. Search Framework - Filtering Supports the following types of filters  Filters based on a where clause (Eg. Type__c = ‘iOS’)  Filters based on pick list fields  List filters that are dependent on other list filters  Hierarchical set of filters
  • 29. Filter Tree - Example
  • 30. Filtering - Filters based on a where clause public class BuiltinFilterNode extends FilterNode { public BuiltinFilterNode(String label, String clause, String filterNodeId) { … } public override String getWhereClause(String objRef) { if (getIsSelected()) { return (objRef != null ? objRef + '.' : '') + predicate; } return null; } } new BuiltinFilterNode (‘iOS’,‘Type__c = ‘iOS’’, ‘ios’); new BuiltinFilterNode (‘4 stars & up’,‘Rating__c >= 4’, ‘rt4’);
  • 31. Filtering - Filters based on pick list fields public virtual class ListFilterNode extends FilterNode { public override void setSelectedValue(String val) { for(ListOption lo : listValues) { if (lo.val == selectedVal) { selectedLabel = lo.label; lo.isSelected = true; break; } } } public virtual override String getWhereClause(String objRef) { if (!String.isBlank(selectedVal)) { if (isMultiSelectDataType) clause = (objRef != null ? objRef + '.' : '') + fieldName + ' includes ('' + selectedVal + '')'; clause = (objRef != null ? objRef + '.' : '') + fieldName + ' = '' + selectedVal + '''; else } return clause; }
  • 32. Filtering – Generating the filter clause
  • 33. Search - Bringing it all together Start Start Keyword Keyword ? ? yes Perform Keyword Perform Keyword Search Search no Filter results & sort Filter results & sort based on user based on user selection. Construct selection. Construct list of ids of the list of ids of the current page’s objects current page’s objects Retrieve all data Retrieve all data needed to render UI needed to render UI Filter Results Based Filter Results Based on user selection on user selection yes no Keyword Keyword relevance relevance sort ? sort ? Sort by relevance Sort by relevance score. Construct list score. Construct list of ids of the of ids of the current page results current page results
  • 34. Filtering – Constructing the tree FilterNode filterRoot = new FilterNode.RootFilterNode(); GroupFilterNode appTypeGroup = new FilterNode.GroupFilterNode(Label.APP_TYPE,FilterNode.ShowAsType.TOP_FILTER); filterRoot.add(appTypeGroup); appTypeGroup.add(new FilterNode.BuiltinFilterNode(‘iOS Apps’,’Type__c = ’ios’’, ‘ios’)); appTypeGroup.add(new FilterNode.BuiltinFilterNode(‘Android Apps’,’Type__c = ’android’’, ‘android’)); appTypeGroup.add(new FilterNode.BuiltinFilterNode(‘Web Apps’,’Type__c = ’web’’, ‘web’)); ListFilterNode langListNode = new ListFilterNode(LANGUAGE_FILTER_ID,sObjectType.App__c.fields.Languages__c.label, System.Label.AllLanguages,'Languages__c‘,filterRoot,AppDO.languageSelectOptions, FilterNode.ShowAsType.TOP_FILTER,true); filterRoot.add(langListNode); ListFilterNode catListNode = new ListFilterNode(CATEGORY_FILTER_ID,sObjectType.App__c.fields.Categories__c.label, System.Label.AllCategories,'Categories__c‘, filterRoot,AppDO.categorySelectOptions, FilterNode.ShowAsType.LEFT_NAV_FILTER,true); filterRoot.add(catListNode);
  • 35. Configuring the search engine String listingWhereClause = ' Listing__c.Status__c = 'Live' AND (Listing__c.Language__c = '' + usersLanguage + '' OR Listing__c.isDefaultAppListing__c = true) '; String appWhereClause = ' IsActive__c = true ' : ' LiveListings__c > 0 AND IsActive__c = true '; String appNameSosl = 'FIND '*{0}*' IN NAME FIELDS RETURNING App__c (Id WHERE ' + appWhereClause + ')'; String descriptionSosl = 'FIND '*{0}*' IN ALL FIELDS RETURNING ListingExtension__c (App__c WHERE RecordType.Name = 'Description' AND ' + listingWhereClause.replace('Listing__c.', 'Listing__r.') + ')'; String requirementsSosl = 'FIND '*{0}*' IN ALL FIELDS RETURNING ListingExtension__c (App__c WHERE RecordType.Name = 'Requirements' AND ' + listingWhereClause.replace('Listing__c.', 'Listing__r.')) + ‘)'; List<KeywordGroupConfig> groupConfigs = new List<KeywordGroupConfig>(); groupConfigs.add(new KeywordSOSLGroupConfig(APP_NAME_FIELD_WEIGHTING, appNameSosl)); groupConfigs.add(new KeywordSOSLGroupConfig(DESCRIPTION_FIELD_WEIGHTING, descriptionSosl)); groupConfigs.add(new KeywordSOSLGroupConfig(REQUIREMENTS_FIELD_WEIGHTING, requirementsSosl));
  • 36. Configuring the search engine cont. /* field group with field “TagLine” */ groupConfigs.add(new KeywordSOQLGroupConfig(new List<String>{'tagline__c'}, TAGLINE_FIELD_WEIGHTING, 'SELECT App__c FROM Listing__c’, listingWhereClause)); /* field group with field categories */ groupConfigs.add(new KeywordSOQLGroupConfig('categories__c', CATEGORIES_FIELD_WEIGHTING, 'SELECT Id FROM App__c’, appWhereClause, AppDO.categoriesLabelLookup)); super.initialize(new KeywordSearchConfig(groupConfigs,’App__c’, APP_FIELDS), null, appWhereClause);
  • 37. Other useful patterns  Data Access Object • Define a DAO class that acts as a layer between the business logic and the database – Code Reusability – Easy Maintenance  Data Object • Define a DO class to encapsulate a SObject. This class has methods to create, update, delete this SObject – Add convenience methods – Relate different DO objects to help with implementation of your business logic.
  • 38. Data Access Object - Example
  • 39. Data Object - Example public class ListingDO extends BaseData { private Listing__c listingObj; private AppDO appObj; public ListingDO(Listing__c listing) { init(listing); } public Boolean getIsLive() { return listingObj.Status__c == STATUS_LIVE; } public String getLanguageLabel() { return langLabelLookup.get(listingObj.Language__c); } public AppDO getApp() { if (appObj == null) appObj = new AppDO(listingObj.App__r); return appObj; } public Boolean save() { /* insert or update here */ } ... }
  • 40. Enabling the Salesforce1 Experience  Enable Visualforce pages for mobile.  Include the application’s tabs in the mobile navigation.
  • 41. Pratima Nambiar Tech Lead AppExchange & Communities Jochem Geerdink Product Designer AppExchange & Communities