salesforce lightning零基础学习(十一) Aura框架下APP构造实现

前面的一些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。相关表结构关系如下:

salesforce lightning零基础学习(十一) Aura框架下APP构造实现

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步选中的船的图标的地理信息。

salesforce lightning零基础学习(十一) Aura框架下APP构造实现

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

salesforce lightning零基础学习(十一) Aura框架下APP构造实现

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开发有一个基本的概念,此功能的代码实现不唯一,感兴趣的也可以使用其他的方式实现。篇中有错误的地方欢迎指出,有不懂的欢迎提出。

上一篇:SpringMVC 返回json的两种方式


下一篇:Java集合框架之TreeSet浅析