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

Post on 26-Jan-2015

110 views 0 download

Tags:

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

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

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.

Agenda

Intro

UI and RWD

Technical Deep Dive

Our Team

AppExchange

https://appexchange.salesforce.com/

Success Community

https://success.salesforce.com/

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

Requirements

Desktop, tablet, phone

Professional, custom look & feel

Built using Apex and VisualForce

Managed Package

UI – User Interface Design

Specs

IA – UX

VisD

HTML Mockups

Mobile Technologies Considered

Native apps

Mobile version of the application

Web application using

Responsive Web Design

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.

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.

Media queries code structure

RWD and Images

Use background images when possible

Lazy loading for better page performance

Use background images in sprites

Very useful for icons

Think about HD displays (Retina)

Code example background images

<div class="msg-success">

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

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

</div>

Lazy loading of images for better page performance

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;

}

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;

};

Lazy loading of images for better page performance

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>

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

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

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

……

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

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

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)

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

…….

}

}

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

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

Filter Tree - Example

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

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;

}

Filtering – Generating the filter clause

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

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

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

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

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.

Data Access Object - Example

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 */

}

...

}

Enabling the Salesforce1 Experience Enable Visualforce pages for mobile.

Include the application’s tabs in the mobile navigation.

Pratima NambiarPratima Nambiar

Tech LeadAppExchange & Communities

Jochem GeerdinkJochem Geerdink

Product DesignerAppExchange & Communities