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

51
How we built the Private AppExchange app Using Apex, VisualForce and RWD Pratima Nambiar Tech Lead AppExchange & Communities Jochem Geerdink Product Designer AppExchange & Communities

description

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.

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

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

How we built the Private AppExchange appHow we built the Private AppExchange appUsing Apex, VisualForce and RWD

Pratima NambiarTech Lead

AppExchange & Communities

Jochem GeerdinkProduct Designer

AppExchange & Communities

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

Safe harborSafe 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 salesforce.com, 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 non-salesforce.com products, and utilization and selling to larger enterprise customers. Further information on potential factors that could affect the financial results of salesforce.com, 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. Salesforce.com, inc. assumes no obligation and does not intend to update these forward-looking statements.

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

Agenda

Intro

UI and RWD

Technical Deep Dive

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

Our Team

AppExchange

https://appexchange.salesforce.com/

Success Community

https://success.salesforce.com/

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

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

Page 6: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 7: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 8: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 9: How We Built the Private AppExchange App (Apex, Visualforce, RWD)

Requirements

Desktop, tablet, phone

Professional, custom look & feel

Built using Apex and VisualForce

Managed Package

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

UI – User Interface Design

Specs

IA – UX

VisD

HTML Mockups

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

Mobile Technologies Considered

Native apps

Mobile version of the application

Web application using

Responsive Web Design

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

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.

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

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.

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

Media queries code structure

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

RWD and Images

Use background images when possible

Lazy loading for better page performance

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

Use background images in sprites

Very useful for icons

Think about HD displays (Retina)

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

Code example background images

<div class="msg-success">

<div class="msg-icon"></div>

<p>The store is online.</p>

</div>

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

Lazy loading of images for better page performance

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

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;

}

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

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;

};

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

Lazy loading of images for better page performance

Page 22: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 23: How We Built the Private AppExchange App (Apex, Visualforce, RWD)

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>

Page 24: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 25: How We Built the Private AppExchange App (Apex, Visualforce, RWD)

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

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

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

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

Keyword Search – Our Solution

Our solution uses a combination of SOQL and SOSL Example Listing object

Field Type

Name Text

Features Text

Short Description TextArea

Long Description LongTextArea

……

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

Keyword Search – Our Solution

Group fields and assign a score to each group

Field Group Score

Name 10

Features, Short Description 5

Long Description 2

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

Keyword Search – Our Solution Cont. Decide whether to use SOQL or SOSL for searching within each group

of fields

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>();

Field Group SOQL/SOSL

Name SOSL

Features, Short Description SOQL

Long Description SOSL

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

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(lst.id);

score += WEIGHT_FOR_THIS_FIELD_GROUP; /* 5 in this example */

idToScoreMap.put(lst.id,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)

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

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) {

…….

}

}

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

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

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

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

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

Filter Tree - Example

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

Filtering - Filters based on a where clausepublic 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’);

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

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 + '\')';

else

clause = (objRef != null ? objRef + '.' : '') + fieldName + ' = \'' + selectedVal + '\'';

}

return clause;

}

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

Filtering – Generating the filter clause

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

Search - Bringing it all together

Keyword ?

Keyword ?

Perform Keyword Search

Perform Keyword Search

Filter Results Based on user selection

Filter Results Based on user selection

yes

Filter results & sort based on user

selection. Construct list of ids of the

current page’s objects

Filter results & sort based on user

selection. Construct list of ids of the

current page’s objects

no

Sort by relevance score. Construct list

of ids of the current page results

Sort by relevance score. Construct list

of ids of the current page results

Keyword relevance sort ?

Keyword relevance sort ?

yesno

StartStart

Retrieve all data needed to render UIRetrieve all data

needed to render UI

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

Filtering – Constructing the treeFilterNode 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);

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

Configuring the search engineString 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));

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

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);

Page 42: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 43: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 44: How We Built the Private AppExchange App (Apex, Visualforce, RWD)

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.

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

Data Access Object - Example

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

Data Object - Examplepublic 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 */

}

...

}

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

Enabling the Salesforce1 Experience Enable Visualforce pages for mobile.

Include the application’s tabs in the mobile navigation.

Page 48: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 49: How We Built the Private AppExchange App (Apex, Visualforce, RWD)
Page 50: How We Built the Private AppExchange App (Apex, Visualforce, RWD)

Pratima NambiarPratima Nambiar

Tech LeadAppExchange & Communities

Jochem GeerdinkJochem Geerdink

Product DesignerAppExchange & Communities

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