前面的一些lightning文章讲述了aura的基础知识,aura封装的常用js以及aura下的事件处理。本篇通过官方的一个superbadge来实现一个single APP的实现。

superbadge的网址如下:https://trailhead.salesforce.com/en/content/learn/superbadges/superbadge_lcf

通过步骤安装相关的app exchange即可安装相关的表结构以及初始化数据,详细可以看这个superbadge的细节描述。安装以后主要有3个表,Boat Type、Boat、BoatReview。相关表结构关系如下:

Boat Type在这个demo中用来存储 船的类型,当然这个数据也可以维护在custom setting中;

Boat在这个demo中用来存储船的详情信息;

Boat Review在这个demo中用来存储船的一些评价信息。

接下来说一下想要实现的UI,这个superbadge主要想实现以下的功能:

1. 头部展示这个APP 的头部信息,包括图标标题等;

2. 搜索区域展示Boat Type数据,选中某个Boat Type点击Search后在区域3展示数据;

3. 展示2步搜索出来的数据,点击某个船的信息会在右面区域展示详细信息以及地图信息;

4. 展示一个tab,分别对应详情,评价以及添加评价;

5. 根据不同的tab展示不同的子元素信息;

6. 展示3步选中的船的图标的地理信息。

说完需要实现的功能再说一下实现所需的元素组件,官方在包中已经封装好了实现这些功能对应的组件元素的名称,名称的结构如下所示:

FriendsWithBoats: 一个single APP, 包含了四部分组件,分别对应 BoatHeader 、 BoatSearch 、 BoatDetails 以及 Map;

BoatHeader:上图中1部分内容,用于展示logo和标题;

BoatSearch:上图中的2,3部分内容,包含两个子组件,分别对应 BoatSearchForm、BoatSearchResults;

BoatDetails: 上图中的4,5部分内容,包含3个子组件,分别对应 BoatDetail、BoatReviews、AddBoatReview;

Map:上图中的6部分内容;

BoatSearchForm:上图中的2部分,主要功能为展示船的类型,并且根据类型进行搜索;

BoatSearchResults:上图中的3部分,用来展示搜索出来的列表。包含一个子组件,名字为BoatTile;

BoatDetail:对应4中切换到Details部分下的5部分内容;

BoatReviews:对应4中切换到Reviews部分下的5部分内容;

AddBoatReview:对应4中切换到Add Review部分下的5部分内容;

BoatTile:上图中的3部分搜索出来列表的每个子单元的内容展示;

FiveStarRating:AddBoatReview中会有对当前船进行评价,此元素标签用于展示5星评价组件。

说完这些用到的component以外再说一下实现这些功能需要用到哪些事件。我们之前在事件阶段也说过,事件分成两种,COMPONENT/APPLICATION。如果两种都可以实现功能的情况下,官方推荐使用COMPONENT类型的。COMPONENT分成bubble以及capture两种类型,不同的传播方式会执行不同的顺序,详情可以参看以前的事件阶段的博客。这个demo中,因为当我们在matching boats区域选中某个子单元情况下,信息要显示在右侧的区域详情等地方。通过上面的bom图可以看到他们不再同一个父子节点中,COMPONENT类型的event只能处理父子关系,这种兄弟关系或者类兄弟关系只能通过APPLICATION的event通过广播订阅机制去实现。下面说以下demo中设计到的几个主要的事件阶段:

BoatSelect:用于当子单元选中以后的选中效果展示,边框加样式等操作(COMPONENT类型);

BoatSelected:用于当子单元选中以后,将信息传递至BoatDetail中(APPLICATION类型);

plotMapMarker:用于当子单元选中以后,将选中的经纬度等信息传到Map组件中(APPLICATION类型);

以上几个事件用于 BoatTile中注册事件。

formsubmit:用于当点击search按钮后,将表单提交并且对数据进行处理(COMPONENT类型);

以上事件用于BoatSearchForm中注册事件。

BoatReviewAdded:用于当添加一条船的评论信息后,切换到BoatReview的tab并且刷新tab里面的内容(COMPONENT类型)。

以上事件用于AddBoatReview中注册事件。

这个APP中注册的事件整理完以后整理一下这个执行的事件阶段以及相关controller和component的实现。

事件的传播顺序为 capture -> target -> bubble,所以上面的COMPONENT类型的事件在组件中的执行顺序应该如下:

FriendsWithBoats -> BoatSearch -> BoatSearchForm -> BoatSearch -> FriendsWithBoats

FriendsWithBoats -> BoatSearch -> BoatSearchResults -> BoatTile -> BoatSearchResults -> BoatSearch -> FriendsWithBoats

FriendsWithBoats -> BoatDetails -> AddBoatReview -> BoatDetails -> FriendsWithBoats

相关Event的声明如下:

BoatSelect.evt

 <aura:event type="COMPONENT" description="Boat Event">
<aura:attribute name="boatId" type="String"/>
</aura:event>

BoatSelect.evt

BoatSelected.evt

 <aura:event type="APPLICATION" description="BoatSelected fired from BoatTileController's onBoatClick handler">
<aura:attribute name="boat" type="Boat__c"/>
</aura:event>

BoatSelected.evt

plotMapMarker.evt

 <aura:event type="APPLICATION" description="Event template" >
<aura:attribute name="sObjectId" type="String" />
<aura:attribute name="lat" type="String" />
<aura:attribute name="long" type="String" />
<aura:attribute name="label" type="String" />
</aura:event>

plotMapMarker.evt

FormSubmit.evt

 <aura:event type="COMPONENT" description="Event template" >
<aura:attribute name="formData" type="object"/>
</aura:event>

FormSubmit.evt

BoatReviewAdded.evt

 <aura:event type="COMPONENT" description="Event template" />

BoatReviewAdded

接下来按照上图中的DOM结构从下往上构建代码。

BoatTile.cmp:注册了三个事件,当点击的时候会触发三个事件从而根据相关的传播路径去执行相关的handler

 <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes">
<aura:attribute name="boat" type="Boat__c" />
<aura:registerEvent name="BoatSelect" type="c:BoatSelect"/>
<aura:registerEvent name="BoatSelected" type="c:BoatSelected" />
<aura:registerEvent name="plotMapMarker" type="c:PlotMapMarker" />
<aura:attribute name='selected' type='Boolean' default='false'/>
<lightning:button class="{!v.selected ? 'tile selected' : 'tile'}" onclick="{!c.onBoatClick}">
<div style="{!'background-image:url(\'' + v.boat.Picture__c + '\'); '}" class="innertile">
<div class="lower-third">
<h1 class="slds-truncate">{!v.boat.Contact__r.Name}</h1>
</div>
</div>
</lightning:button>
</aura:component>

BoatTile.cmp

BoatTileController.js:需要注意的是,获取COMPONENT/APPLICATION两种类型的事件的方式不一样。针对COMPONENT类型的事件,需要使用component.getEvent('registerEventName')方式获取Event实例;针对APPLICATION类型的事件,需要使用$A.get("e.namespace:registerEventName"),这里默认的namespace为c,所以这个里面的获取方式为:$A.get("e.c:BoatSelected");

 ({
onBoatClick : function(component, event, helper) {
var myEvent = component.getEvent("BoatSelect");
var boat=component.get("v.boat");
myEvent.setParams({"boatId": boat.Id});
myEvent.fire(); var appEvent = $A.get("e.c:BoatSelected");
appEvent.setParams({
"boat": boat
});
appEvent.fire(); var plotEvent = $A.get("e.c:PlotMapMarker");
plotEvent.setParams({
"lat": boat.Geolocation__Latitude__s,
"sObjectId": boat.Id,
"long": boat.Geolocation__Longitude__s,
"label":boat.Name
});
plotEvent.fire();
}
})

BoatTileController.js

此元素组件实现了当点击了搜索出来的列表的某个子单元以后,便会触发三个事件,从而会根据绑定这些事件的元素组件按照事件传播方式进行分别执行。

BoatTile.css

 .THIS.tile {
position:relative;
display: inline-block;
background-size: cover;
background-position: center;
background-repeat: no-repeat; height: 220px;
padding: 1px !important; }
.THIS.selected { border:3px solid rgb(0, 112, 210);
} .THIS .innertile {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
width: 220px;
height: 100%;
} .THIS .lower-third {
position: absolute;
bottom:;
left:;
right:;
color: #FFFFFF;
background-color: rgba(0, 0, 0, .4);
padding: 6px 8px;
}

BoatTile.css

BoatTile.svg

 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="120px" height="120px" viewBox="0 0 120 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M120,108 C120,114.6 114.6,120 108,120 L12,120 C5.4,120 0,114.6 0,108 L0,12 C0,5.4 5.4,0 12,0 L108,0 C114.6,0 120,5.4 120,12 L120,108 L120,108 Z" id="Shape" fill="#2A739E"/>
<path d="M77.7383308,20 L61.1640113,20 L44.7300055,63.2000173 L56.0543288,63.2000173 L40,99.623291 L72.7458388,54.5871812 L60.907727,54.5871812 L77.7383308,20 Z" id="Path-1" fill="#FFFFFF"/>
</g>
</svg>

BoatTile.svg

BoatSearchResults.cmp:用于显示搜索出来的列表以及增加了BoatTile事件中的handler,当BoatTile中的BoatSelect事件触发以后,会执行其对应的controller.js中的onBoatSelect方法,将selectedBoatId赋值,因为aura架构的变量都是双向绑定,会同时作用到子组件中从而实现选中后的样式变化。

这里面使用了一个组件名字叫做aura:method,这个用于定义一个component的API的方法,允许你直接在controller.js中直接调用你的相关的方法,通常用于在父组件中直接调用子组件的某个方法。本篇demo中会在BoatSearchController.js中调用这个方法。

 <aura:component controller="BoatSearchResults" implements="force:appHostable,flexipage:availableForAllPageTypes" access="global">

     <aura:attribute name="boats" type="Boat__c[]" />
<!-- set up the aura:method for search -->
<aura:attribute name="boatTypeId1" type="String"/>
<aura:method name="search" access="global" action="{!c.search}" >
<aura:attribute name="boatTypeId" type="String"/>
</aura:method>
<aura:handler name="BoatSelect" event="c:BoatSelect" action="{!c.onBoatSelect}"/>
<aura:attribute name="selectedBoatId" type="String" default="null"/> <lightning:layout multipleRows="true" horizontalAlign="center">
<aura:iteration items="{!v.boats}" var="boat">
<lightning:layoutItem flexibility="grow" class="slds-m-right_small" >
<c:BoatTile boat="{!boat}" selected="{!boat.Id == v.selectedBoatId ? true : false}"/>
</lightning:layoutItem>
</aura:iteration> <aura:if isTrue="{!v.boats.length==0}">
<lightning:layoutItem class="slds-align_absolute-center" flexibility="auto" padding="around-small">
<ui:outputText value="No boats found" />
</lightning:layoutItem>
</aura:if> </lightning:layout>
</aura:component>

BoatSearchResults.cmp

BoatSearchResultController.js:声明了两个方法,一个是用于父组件调用查询的方法,另外一个是当事件触发后执行的handler。

 ({
doInit: function(component, event, helper) {
},
search: function(component, event, helper){
var params = event.getParam('arguments');
component.set("v.boatTypeId1", params.boatTypeId);
helper.onSearch(component,event);
return "search complete.";
},
onBoatSelect: function(component, event, helper){
var boatId = event.getParam("boatId");
component.set("v.selectedBoatId", boatId); }
})

BoatSearchResultController.js

BoatSearchResultHelper.js

 ({
onSearch : function(component) {
var currentBoatType = component.get("v.boatTypeId1")
var action = component.get("c.getBoats");
if(currentBoatType == 'All Types'){
currentBoatType = '';
}
var action = component.get("c.getBoats");
action.setParams({
"boatTypeId":currentBoatType
}); action.setCallback(this, function(response) { var state = response.getState();
if (component.isValid() && state === "SUCCESS") {
component.set("v.boats", response.getReturnValue());
} else {
console.log("Failed with state1: " + state);
}
});
$A.enqueueAction(action);
}
})

BoatSearchResultHelper.js

BoatSearchForm.cmp:显示boattype的picklist以及注册了搜索的事件

 <aura:component controller="BoatSearchResults" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
<aura:attribute name="btypes" type="BoatType__c[]"/>
<aura:attribute name='selectedType' type='string' default='All Type'/>
<aura:registerEvent name="formsubmit" type="c:FormSubmit"/> <lightning:layout horizontalAlign="center" verticalAlign="end" >
<lightning:layoutItem padding="horizontal-medium" class="slds-grid_vertical-align-center">
<lightning:select aura:id="boatTypes" label="" name="selectType" onchange="{!c.handleChange}">
<option value="">All Types</option>
<aura:iteration items="{!v.btypes}" var="item">
<option text="{!item.Name}" value="{!item.Id}" />
</aura:iteration>
</lightning:select>
</lightning:layoutItem>
<lightning:layoutItem class="slds-grid_vertical-align-center" padding="horizontal-medium" >
<lightning:button class="slds-button" variant="brand" label="Search" onclick="{!c.onFormSubmit}"/>
</lightning:layoutItem>
</lightning:layout> </aura:component>

BoatSearchForm.cmp

BoatSearchFormController.js

 ({
doInit: function(component, event, helper) {
var action = component.get("c.getboattypes");
action.setCallback(this, function(response) {
var state = response.getState();
if (component.isValid() && state === "SUCCESS") {
component.set("v.btypes", response.getReturnValue());
}
else {
console.log("Failed with state: " + state);
}
}); // Send action off to be executed
$A.enqueueAction(action);
},
onFormSubmit:function(component, event, helper) { var boatTypeId = component.get("v.selectedType");
console.log("selected type : " + boatTypeId);
var formSubmit = component.getEvent("formsubmit");
formSubmit.setParams({"formData":
{"boatTypeId" : boatTypeId}
});
formSubmit.fire();
},
handleChange:function(component, event, helper) {
var selectedBoatType = component.find("boatTypes").get("v.value");
console.log("selectedBoatType : "+ selectedBoatType);
component.set("v.selectedType",selectedBoatType);
} })

BoatSearchController.js

BoatSearch.cmp

 <aura:component controller="BoatSearchResults" implements="force:appHostable,flexipage:availableForAllPageTypes" access="global" >
<aura:attribute name="boats" type="Boat__c[]" />
<lightning:card title="Find a Boat" class="slds-m-bottom_10px">
<c:BoatSearchForm />
</lightning:card>
<lightning:card title="Matching Boats" >
<c:BoatSearchResults aura:id="BSRcmp"/>
</lightning:card>
<aura:handler name="formsubmit"
event="c:FormSubmit"
action="{!c.onFormSubmit}"
phase="capture"/>
</aura:component>

BoatSearch.cmp

BoatSearchController.js:三个核心的方法:初始化boat type,改变boat type的handler以及form submit 的handler

 ({
onFormSubmit: function(component, event, helper){
console.log("event received by BoatSearchController.js");
var formData = event.getParam("formData");
var boatTypeId = formData.boatTypeId;
console.log("boatTypeId : "+boatTypeId); var BSRcmp = component.find("BSRcmp");
var auraMethodResult = BSRcmp.search(boatTypeId);
console.log("auraMethodResult: " + auraMethodResult);
}
})

BoatSearchController.js

左侧的功能已经实现。左侧的功能主要是显示所有的Boat Type,选择一个Boat Type后点击search进行事件处理调用子元素进行搜索操作以及进行赋值操作,当选择子元素组件以后触发两个APPLICATION 的事件将选中的boat信息进行广播。下面的内容为右侧的部分。

FiveStarRating.cmp

 <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:attribute name="value" type="Integer" default='0'/>
<aura:attribute name="readonly" type="boolean" default='false' />
<ltng:require styles="{!$Resource.fivestar + '/rating.css'}" scripts="{!$Resource.fivestar + '/rating.js'}" afterScriptsLoaded="{!c.afterScriptsLoaded}" />
<aura:handler name="change" value="{!v.value}" action="{!c.onValueChange}"/>
<ul class="{!v.readonly ? 'readonly c-rating' : 'c-rating'}" aura:id="ratingarea" >
</ul>
</aura:component>

FiveStarRating

FiveStarRatingController.js

 ({
afterScriptsLoaded : function(component, event, helper) {
debugger
var domEl = component.find("ratingarea").getElement(); var currentRating = component.get('v.value');
var readOnly = component.get('v.readonly');
var maxRating = 5;
var callback = function(rating) {
component.set('v.value',rating);
}
component.ratingObj = rating(domEl,currentRating,maxRating,callback,readOnly);
}, onValueChange: function(component,event,helper) {
if (component.ratingObj) {
var value = component.get('v.value');
component.ratingObj.setRating(value,false);
}
}
})

FiveStarRatingController.js

AddBoatReview.cmp:一个form表单用来提交boat的评价信息,保存后触发boatReviewAdded的事件进行事件触发处理。

 <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:attribute name="boat" type="Boat__c"/>
<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
<aura:attribute name="boatReview" type="BoatReview__c"/>
<aura:attribute access="private" name="recordError" type="String"/>
<aura:registerEvent name="boatReviewAdded" type="c:BoatReviewAdded" />
<force:recordData aura:id="service"
fields="Id,Name,Comment__c, Rating__c, Boat__c"
targetError="{!v.recordError}"
targetFields="{!v.boatReview}"
recordUpdated="{!c.onRecordUpdated}"
/> <lightning:layout multipleRows="true">
<lightning:layoutItem size="12" padding="around-small">
<lightning:input name="title" label="Title" value="{!v.boatReview.Name}"/>
</lightning:layoutItem> <lightning:layoutItem size="12" padding="around-small">
<label class="slds-form-element__label" for="input-id-01">Description</label>
<lightning:inputRichText value="{!v.boatReview.Comment__c}" disabledCategories="FORMAT_FONT"/>
</lightning:layoutItem>
<lightning:layoutItem size="12" padding="around-small">
<label class="slds-form-element__label" for="input-id-01">Rating</label>
<ul class="slds-post__footer-actions-list slds-list_horizontal">
<li class="slds-col slds-item slds-m-right_medium">
<c:FiveStarRating value="{!v.boatReview.Rating__c}" /> </li>
</ul>
</lightning:layoutItem>
<lightning:layoutItem size="12" class="slds-align--absolute-center">
<lightning:button iconName="utility:save" label="Submit" onclick="{!c.onSave}"/>
</lightning:layoutItem>
</lightning:layout>
</aura:component>

AddBoatReview.cmp

AddBoatReviewController.js

 ({
doInit: function(component, event, helper) {
helper.onInit(component, event,helper);
},
onSave : function(component, event, helper) {
var boat = component.get("v.boat");
var boatr = component.get("v.boatReview"); component.set("v.boatReview.Boat__c",boat.Id); component.find("service").saveRecord(function(saveResult){
if(saveResult.state==="SUCCESS" || saveResult.state === "DRAFT") {
var resultsToast = $A.get("e.force:showToast");
if(resultsToast) {
resultsToast.setParams({
"title": "Saved",
"message": "Boat Review Created"
});
resultsToast.fire();
} else {
alert('Boat Review Created');
}
} else if (saveResult.state === "ERROR") {
var errMsg='';
for (var i = 0; i < saveResult.error.length; i++) {
errMsg += saveResult.error[i].message + "\n";
}
component.set("v.recordError", errMsg);
} else {
console.log('Unknown problem, state: ' + saveResult.state + ', error: ' + JSON.stringify(saveResult.error));
}
var boatReviewAddedEvnt=component.getEvent("boatReviewAdded");
boatReviewAddedEvnt.fire();
helper.onInit(component,event,helper);
});
},
onRecordUpdated: function(component, event, helper) {
}
})

AddBoatReviewController.js

AddBoatReviewHelper.js:初始化表单的初始值信息

 ({
onInit : function(component, event,helper) {
component.find("service").getNewRecord(
"BoatReview__c", // sObject type (entityAPIName)
null, // recordTypeId
false, // skip cache?
$A.getCallback(function() {
var rec = component.get("v.boatReview");
var error = component.get("v.recordError");
var boat=component.get("v.boat");
if(error || (rec === null)) {
console.log("Error initializing record template: " + error);
}
else {
component.set("v.boatReview.Boat__c",boat.Id);
var test=component.get("v.boatReview");
}
})
);
}
})

AddBoatReviewHelper.js

BoatReviews.cmp:声明一个aura:method方法供父类调用实现当 保存完boat的评价后可以跳转到评价列表,初始化评价信息列表。

 <aura:component controller="BoatReviews" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:attribute name="boat" type="Boat__c" />
<aura:attribute name="boatReviews" type="BoatReview__c[]" access="private" />
<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
<!-- set up the aura:method for refresh -->
<aura:method name="refresh"
action="{!c.doInit}"
description="invokes refresh whenever boat is updated" access="public">
</aura:method>
<aura:handler name="change" value="{!v.boat}" action="{!c.doInit}"/> <aura:dependency resource="markup://force:navigateToSObject" type="EVENT"/>
<ui:scrollerWrapper class="scrollerSize">
<!--Scrollable content here -->
<aura:if isTrue="{!v.boatReviews.length==0}">
<lightning:layoutItem class="slds-align_absolute-center" flexibility="auto" padding="around-small">
<ui:outputText value="No Reviews Available" />
</lightning:layoutItem>
</aura:if>
<div class="slds-feed" style="max-height: 250px;">
<ul class="slds-feed__list">
<aura:iteration items="{!v.boatReviews}" var="boatReview">
<li class="slds-feed__item">
<header class="slds-post__header slds-media">
<div class="slds-media__figure">
<img alt="Image" src="{!boatReview.CreatedBy.SmallPhotoUrl}" title="" />
</div>
<div class="slds-media__body">
<div class="slds-grid slds-grid_align-spread slds-has-flexi-truncate">
<p>
<a href="javascript:void(0)" onclick="{!c.onUserInfoClick}" data-userid="{!boatReview.CreatedBy.Id}">
{!boatReview.CreatedBy.Name}
</a> - {!boatReview.CreatedBy.CompanyName}
</p>
</div>
<p class="slds-text-body_small">
<lightning:formattedDateTime value="{!boatReview.CreatedDate}"
year="numeric" month="short" day="numeric"
hour="2-digit" minute="2-digit" hour12="true"/>
</p>
</div>
</header>
<div class="slds-post__content slds-text-longform">
<div>
<ui:outputText value="{!boatReview.Name}" />
</div>
<div>
<ui:outputRichText class="slds-text-longform" value="{!boatReview.Comment__c}" />
</div>
</div>
<footer class="slds-post__footer">
<ul class="slds-post__footer-actions-list slds-list_horizontal">
<li class="slds-col slds-item slds-m-right_medium">
<c:FiveStarRating aura:id="FiveStarRating" value="{!boatReview.Rating__c}" readonly="true"/>
</li>
</ul>
</footer>
</li>
</aura:iteration>
</ul>
</div>
</ui:scrollerWrapper>
</aura:component>

BoatReviews.cmp

BoatReviewsController.js

 ({
doInit : function(component, event, helper) {
helper.onInit(component, event);
},
onUserInfoClick : function(component,event,helper){
var userId = event.currentTarget.getAttribute("data-userid");
var navEvt = $A.get("e.force:navigateToSObject");
navEvt.setParams({
"recordId" : userId,
});
navEvt.fire() }
})

BoatReviewsController.js

BoatReviewsHelper.js

 ({
onInit : function(component, event) {
var boat=component.get("v.boat");
var action = component.get("c.getAll");
action.setParams({
"boatId":boat.Id
});
action.setCallback(this, function(response) { var state = response.getState();
if (component.isValid() && state === "SUCCESS") {
component.set("v.boatReviews", response.getReturnValue());
}
else {
console.log("Failed with state: " + state);
}
});
$A.enqueueAction(action);
}
})

BoatReviewsHelper.js

BoatDetail.cmp:

 <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes">
<aura:attribute name="boat" type="Boat__c[]" />
<aura:dependency resource="markup://force:navigateToSObject" type="EVENT"/>
<lightning:card iconName="utility:anchor">
<aura:set attribute="title">
{!v.boat.Contact__r.Name}'s Boat
</aura:set> <aura:set attribute="Actions">
<aura:if isTrue='{!v.showButton}'>
<lightning:button label="Full Details" onclick="{!c.onFullDetails}" />
</aura:if>
</aura:set> <lightning:layout multipleRows="true">
<lightning:layoutItem size="6" padding="around-small"> <div class="slds-p-horizontal--small">
<div class="boatproperty">
<span class="label">Boat Name: </span>
<span>{!v.boat.Name}</span>
</div>
<div class="boatproperty">
<span class="label">Type:</span>
<span>{!v.boat.BoatType__r.Name}</span>
</div>
<div class="boatproperty">
<span class="label">Length:</span>
<span> {!v.boat.Length__c}ft</span>
</div>
<div class="boatproperty">
<span class="label">Est. Price:</span>
<span><lightning:formattedNumber value="{!v.boat.Price__c}" style="currency"
currencyCode="USD" currencyDisplayAs="symbol"/></span>
</div>
<div class="boatproperty">
<span class="label">Description:</span>
<span><ui:outputRichText value="{!v.boat.Description__c}"/></span>
</div>
</div> </lightning:layoutItem> <lightning:layoutItem size="6" padding="around-small"> <lightning:button variant='neutral' label='Full Details' onclick='{!c.onFullDetails}'/>
<div class="imageview" style="{!'background-image:url(\'' + v.boat.Picture__c + '\'); '}" />
</lightning:layoutItem> </lightning:layout> </lightning:card> </aura:component>

BoatDetail.cmp

BoatDetailController.js

 ({
onFullDetails: function(component, event, helper) {
var navEvt = $A.get("e.force:navigateToSObject");
navEvt.setParams({
"recordId": component.get("v.boat.Id") });
navEvt.fire();
}
})

BoatDetailController.js

BoatDetail.css

 .THIS .label {
font-weight: bold;
display: block;
}
.THIS .boatproperty {
margin-bottom: 3px;
}
.THIS .imageview {
background-repeat: no-repeat;
background-size: contain;
height: 200px;
margin: 2px;
}

BoatDetail.css

BoatDetails.cmp:包含了两个事件的handler,分别是Application Event Boat选择的事件处理以及 add boat Review的事件处理

 <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<aura:attribute name="selectedTabId" type="String"/>
<aura:attribute name="boat" type="Boat__c"/>
<aura:attribute name="id" type="Id" />
<aura:attribute name="recordError" type="String"/>
<aura:dependency resource="markup://force:navigateToSObject" type="EVENT"/>
<aura:handler event="c:BoatSelected" action="{!c.onBoatSelected}" />
<aura:handler name="boatReviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}"/>
<force:recordData aura:id="service"
layoutType="FULL"
recordId="{!v.id}"
fields="Id,Name,Description__c,Price__c,Length__c,Contact__r.Name,
Contact__r.Email,Contact__r.HomePhone,BoatType__r.Name,Picture__c"
targetError="{!v.recordError}"
targetFields="{!v.boat}"
mode="EDIT"
recordUpdated="{!c.onRecordUpdated}"
/> <lightning:tabset variant="scoped" selectedTabId="{!v.selectedTabId}" aura:id="details">
<lightning:tab label="Details" id="details" >
<aura:if isTrue="{!not(empty(v.id))}">
<c:BoatDetail boat="{!v.boat}"/>
</aura:if>
</lightning:tab>
<lightning:tab label="Reviews" id="boatreviewtab" > <aura:if isTrue="{!not(empty(v.id))}">
<c:BoatReviews boat="{!v.boat}" aura:id="BRcmp"/>
</aura:if>
</lightning:tab>
<lightning:tab label="Add Review" id="addReview" >
<aura:if isTrue="{!not(empty(v.id))}">
<c:AddBoatReview boat="{!v.boat}"/>
</aura:if>
</lightning:tab>
</lightning:tabset> <aura:if isTrue="{!not(empty(v.recordError))}">
<div class="recordError">
<ui:message title="Error" severity="error" closable="true">
{!v.recordError}
</ui:message>
</div>
</aura:if>
</aura:component>

BoatDetails.cmp

BoatDetailsController.js

 ({
init: function(component, event, helper) {
component.set("v.enableFullDetails", $A.get("e.force:navigateToSObject"));
},
onBoatSelected : function(component, event, helper) {
var boatSelected=event.getParam("boat");
component.set("v.id",boatSelected.Id);
component.find("service").reloadRecord() ; },
onRecordUpdated : function(component, event, helper){ },
onBoatReviewAdded : function(component, event, helper) {
console.log("Event received");
component.find("details").set("v.selectedTabId", 'boatreviewtab');
var BRcmp = component.find("BRcmp");
console.log(BRcmp);
var auraMethodResult = BRcmp.refresh();
} })

BoatDetailsController.js

BoatHeader.cmp

 <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" >
<lightning:layout class="slds-box">
<lightning:layoutItem >
<lightning:icon iconName="custom:custom54" alternativeText="FriendswithBoats"/>
</lightning:layoutItem>
<lightning:layoutItem padding="horizontal-small">
<div class="page-section page-header">
<h1 class="slds-page-header__title slds-truncate slds-align-middle" title="FriendswithBoats">Friends with Boats</h1>
</div>
</lightning:layoutItem>
</lightning:layout>
</aura:component>

BoatHeader.cmp

Map.cmp

 <aura:component implements="flexipage:availableForAllPageTypes" access="global" >

     <aura:attribute access="private" name="leafletMap" type="Object" />

     <aura:attribute name="width"  type="String" default="100%" />
<aura:attribute name="height" type="String" default="200px" />
<aura:attribute name="location" type="SObject"/>
<aura:attribute name="jsLoaded" type="boolean" default="false"/>
<aura:handler event="c:PlotMapMarker" action="{!c.onPlotMapMarker}"/>
<ltng:require styles="{!$Resource.Leaflet + '/leaflet.css'}"
scripts="{!$Resource.Leaflet + '/leaflet-src.js'}"
afterScriptsLoaded="{!c.jsLoaded}" />
<lightning:card title="Current Boat Location" >
<div aura:id="map" style="{!'width: ' + v.width + '; height: ' + v.height}">
<div style="width:100%; height:100%" class="slds-align_absolute-center">Please make a selection</div>
</div>
</lightning:card>
</aura:component>

Map.cmp

Map.css

 .THIS {
width: 100%;
height: 100%;
border: 1px dashed black;
}

Map.css

Map.design

 <design:component label="Map">
<design:attribute name="width" label="Width" description="The width of the map as a percentage (100%) or pixels (100px)" />
<design:attribute name="height" label="Height" description="The height of the map as a percentage (100%) or pixels (100px)" />
</design:component>

Map.design

Map.svg

 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
<title>Slice</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Rectangle" fill="#62B7ED" x="0" y="0" width="100" height="100" rx="8"></rect>
<path d="M84.225,26.0768044 L62.925,15.4268044 C61.8895833,14.9830544 60.70625,14.9830544 59.81875,15.4268044 L40.1458333,25.3372211 L20.325,15.4268044 C19.1416667,14.8351377 17.6625,14.8351377 16.6270833,15.5747211 C15.5916667,16.1663877 15,17.3497211 15,18.5330544 L15,71.7830544 C15,73.1143044 15.7395833,74.2976377 16.9229167,74.8893044 L38.2229167,85.5393044 C39.2583333,85.9830544 40.4416667,85.9830544 41.3291667,85.5393044 L61.15,75.6288877 L80.8229167,85.5393044 C81.2666667,85.8351377 81.8583333,85.9830544 82.45,85.9830544 C83.0416667,85.9830544 83.78125,85.8351377 84.3729167,85.3913877 C85.4083333,84.7997211 86,83.6163877 86,82.4330544 L86,29.1830544 C86,27.8518044 85.4083333,26.6684711 84.225,26.0768044 L84.225,26.0768044 Z M78.6041667,32.8809711 L78.6041667,60.9851377 C78.6041667,62.6122211 77.125,63.7955544 75.6458333,63.2038877 C70.1729167,61.1330544 74.6104167,51.9622211 70.6166667,46.9330544 C66.91875,42.3476377 62.1854167,47.0809711 57.6,39.8330544 C53.3104167,32.8809711 59.0791667,27.8518044 64.4041667,25.1893044 C65.14375,24.8934711 65.8833333,24.8934711 66.475,25.1893044 L77.4208333,30.6622211 C78.3083333,31.1059711 78.6041667,31.9934711 78.6041667,32.8809711 L78.6041667,32.8809711 Z M48.8729167,74.0018044 C47.9854167,74.4455544 46.95,74.2976377 46.2104167,73.7059711 C44.73125,72.3747211 43.5479167,70.3038877 43.5479167,68.2330544 C43.5479167,64.6830544 37.63125,65.8663877 37.63125,58.7663877 C37.63125,52.9976377 30.8270833,51.5184711 25.0583333,52.1101377 C23.5791667,52.2580544 22.54375,51.2226377 22.54375,49.7434711 L22.54375,28.1476377 C22.54375,26.3726377 24.31875,25.1893044 25.7979167,26.0768044 L38.51875,32.4372211 C38.6666667,32.4372211 38.8145833,32.5851377 38.8145833,32.5851377 L39.2583333,32.8809711 C44.5833333,35.9872211 43.5479167,38.5018044 41.3291667,42.3476377 C38.8145833,46.6372211 37.7791667,42.3476377 34.2291667,41.1643044 C30.6791667,39.9809711 27.1291667,42.3476377 28.3125,44.7143044 C29.4958333,47.0809711 33.0458333,44.7143044 35.4125,47.0809711 C37.7791667,49.4476377 37.7791667,52.9976377 44.8791667,50.6309711 C51.9791667,48.2643044 53.1625,49.4476377 55.5291667,51.8143044 C57.8958333,54.1809711 59.0791667,58.9143044 55.5291667,62.4643044 C53.4583333,64.5351377 52.5708333,68.9726377 51.6833333,71.9309711 C51.5354167,72.5226377 51.0916667,73.1143044 50.5,73.4101377 L48.8729167,74.0018044 L48.8729167,74.0018044 Z" id="Shape" fill="#FFFFFF"></path>
</g>
</svg>

Map.svg

MapController.js

 ({
jsLoaded: function(component) {
component.set("v.jsLoaded", true);
} ,
onPlotMapMarker: function(component,event,helper) {
debugger
var id = event.getParam('sObjectId');
var latitude = event.getParam('lat');
var longitude = event.getParam('long');
var label = event.getParam('label');
var leafletMap = helper.getLeafletMap(component, latitude, longitude);
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(leafletMap); L.marker([latitude, longitude]).addTo(leafletMap)
.bindPopup(label)
.openPopup();
}
})

MapController.js

MapHelper.js

 ({
getLeafletMap : function(component, latitude, longitude) { var leafletMap = component.get('v.leafletMap'); if (!leafletMap) {
var mapContainer = component.find('map').getElement(); leafletMap = L.map(mapContainer, {zoomControl: false, tap: false})
.setView([latitude, longitude], 13);
component.set('v.leafletMap', leafletMap); } else {
leafletMap.setView([latitude, longitude], 13);
}
return leafletMap;
}
})

MapHelper.js

MapRenderer.js

 ({
rerender: function (component) { var nodes = this.superRerender(); var location = component.get('v.location'); if (!location) { } else {
// If the Leaflet library is not yet loaded, we can't draw the map: return
if (!window.L) {
return nodes;
} // Draw the map if it hasn't been drawn yet
if (!component.map) {
var mapElement = component.find("map").getElement();
component.map = L.map(mapElement, {zoomControl: true}).setView([42.356045, -71.085650], 13);
component.map.scrollWheelZoom.disable();
window.L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {attribution: 'Tiles © Esri'}).addTo(component.map);
} if (location && location.lat && location.long) {
var latLng = [location.lat, location.long];
if (component.marker) {
component.marker.setLatLng(latLng);
} else {
component.marker = window.L.marker(latLng);
component.marker.addTo(component.map);
}
component.map.setView(latLng);
} return nodes;
} }
})

MapRenderer.js

BoatReviews.cls

 public class BoatReviews {
@AuraEnabled
public static list<BoatReview__c> getAll(Id boatId ) { return [SELECT Id,Name,Comment__c,Rating__c,LastModifiedDate,CreatedDate,CreatedBy.Name,CreatedBy.SmallPhotoUrl,CreatedBy.CompanyName FROM BoatReview__c WHERE Boat__c=:boatId];
} }

BoatReviews.cls

BoatSearchResults.cls

 public class BoatSearchResults  {

     public list<Boat__c> Boats{get;set;}

     @AuraEnabled
public static List<BoatType__c> getboattypes() {
return [SELECT Name, Id FROM BoatType__c];
} @AuraEnabled
public static List<Boat__c> getBoats(string boatTypeId ) {
list<Boat__c> obj = new list<Boat__c>();
if(boatTypeId!='') {
obj=[SELECT id, BoatType__c, picture__c, name,contact__r.Name, Geolocation__Latitude__s, Geolocation__Longitude__s
FROM Boat__c
WHERE BoatType__c =: boatTypeId];
}else {
obj=[SELECT id, BoatType__c,picture__c, name,contact__r.Name, Geolocation__Latitude__s, Geolocation__Longitude__s
FROM Boat__c];
}
return obj;
}
}

BoatSearchResults

FriendsWithBoats.app

 <aura:application extends="force:slds" >
<c.BoatHeader/>
<lightning:layout > <div class="slds-col slds-size_2-of-3">
<c.BoatSearch/>
</div>
<div class="slds-col slds-size_1-of-3"> <c.BoatDetails />
<c.Map />
</div>
</lightning:layout>
</aura:application>

FriendsWithBoats.app

 效果展示:

https://v.youku.com/v_show/id_XNDA5MzYyMDUwMA==.html?spm=a2h3j.8428770.3416059.1

总结:通过本篇可以大致对Aura架构下的一个简单的APP开发有一个基本的概念,此功能的代码实现不唯一,感兴趣的也可以使用其他的方式实现。篇中有错误的地方欢迎指出,有不懂的欢迎提出。

salesforce lightning零基础学习(十一) Aura框架下APP构造实现的更多相关文章

  1. salesforce lightning零基础学习(十) Aura Js 浅谈三: $A、Action、Util篇

    前两篇分别介绍了Component类以及Event类,此篇将会说一下 $A , Action以及 Util.  一. Action Action类通常用于和apex后台交互,设置参数,调用后台以及对结 ...

  2. salesforce lightning零基础学习(八) Aura Js 浅谈一: Component篇

    我们在开发lightning的时候,常常会在controller.js中写 component.get('v.label'), component.set('v.label','xxValue'); ...

  3. salesforce lightning零基础学习(九) Aura Js 浅谈二: Event篇

    上一篇介绍了Aura Framework中 Component类的部分方法,本篇将要介绍Event常用的方法. 1. setParam (String key , Object value):设置事件 ...

  4. salesforce lightning零基础学习(十五) 公用组件之 获取表字段的Picklist(多语言)

    此篇参考:salesforce 零基础学习(六十二)获取sObject中类型为Picklist的field values(含record type) 我们在lightning中在前台会经常碰到获取pi ...

  5. salesforce lightning零基础学习(十七) 实现上传 Excel解析其内容

    本篇参考: https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader https://github.com/SheetJS/sheetjs ...

  6. salesforce lightning零基础学习(二) lightning 知识简单介绍----lightning事件驱动模型

    看此篇博客前或者后,看一下trailhead可以加深印象以及理解的更好:https://trailhead.salesforce.com/modules/lex_dev_lc_basics 做过cla ...

  7. salesforce lightning零基础学习(十二) 自定义Lookup组件的实现

    本篇参考:http://sfdcmonkey.com/2017/01/07/custom-lookup-lightning-component/,在参考的demo中进行了简单的改动和优化. 我们在ht ...

  8. salesforce lightning零基础学习(十四) Toast 浅入浅出

    本篇参考: https://developer.salesforce.com/docs/component-library/bundle/force:showToast/specification h ...

  9. salesforce lightning零基础学习(四) 事件(component events)简单介绍

    lightning component基于事件驱动模型来处理用户界面的交互.这种事件驱动模型和js的事件驱动模型也很相似,可以简单的理解成四部分: 1.事件源:产生事件的地方,可以是页面中的输入框,按 ...

随机推荐

  1. bugku题目“cookie欺骗”

    先上成功截图 题目写的cookie欺骗,但其实是一道考察写脚本能力和代码审计类的题目,首先观察开始的页面 可以看到只有这一串字母,粗略观察可以认为这绝对不是密码,而是胡乱写上的字符,在观察页面源代码后 ...

  2. django中的modelform和modelfoemset

    一. ModelForm ModelForm是根据Model来定制的Form 二. ModelForm的创建 from django import forms from app import mode ...

  3. ntelliJ IDEA 仿照vs2017快捷键设置,以及字体颜色设置

    因后期工作需要使用java技术栈,所以近期抽空下载了intelliJ IDEA工具,但是作为一个Net开发者,在使用了vs以后,感觉在使用别的开发工具感觉就是没法和vs相比,毕竟vs被称为宇宙最强id ...

  4. Linux下mysql定时自动备份并FTP到远程脚本

    1.添加backupmysqleveryday.sh(vi /data/shell/backupmysqleveryday.sh) #!/bin/sh #this shell is user for ...

  5. 第46章:MongoDB-监控应用状态

    ① MongoDB监控  1 db.serverStatus() 查看实例运行状态(内存使用.锁.用户连接等信息) 通过比对前后快照进行性能分析 "connections" # 当 ...

  6. 底图与蒙版的过渡效果transition

    我用2种方法写了底图与蒙版的过渡效果 方法一:用js方法 <!DOCTYPE html> <html> <head> <meta http-equiv=&qu ...

  7. 跨域资源共享(CROS)

    跨域资源共享(CROS) 同源策略(Same Origin Policy, SOP) 同源策略允许运行在页面的脚本可以无限制的访问同一个网站(同源)中其他脚本的任何方法和属性.当不同网站页面(非同源) ...

  8. php中出现乱码

    对于初学着来说,编辑中文php时,会出现乱码 在php代码中加入 随后在浏览器中,就会看到如下页面 这样就解决了php 中文乱码的问题.

  9. springboot 不同环境切换不同的配置文件

    开发的流程是本地>测试>预发布>正式,所以不同的环境,肯定是不同的配置文件,所以我们需要针对不同的环境做不同的配置切换. 下面我们来说说 springboot 是怎么来切换的: 1. ...

  10. day_3各种数据类型与各种运算符

    首先我们复习一下昨天的内容 1:语言的分类: --有三种 机器语言,汇编语言,高级语言 运行的效率是机器语言最高  开发效率 是高级语言最高 2:计算机由五大部分组成:控制器+运算器+存储器+inpu ...