how we built the private appexchange app (apex, visualforce, rwd)
Post on 26-Jan-2015
114 Views
Preview:
DESCRIPTION
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
top related