lightning コンポーネント開発の勘所 - amazon s3...lightning...
TRANSCRIPT
Lightning コンポーネント開発の勘所
Salesforce World Tour Tokyo 2016[Session 9-4]
2016年12月14日
株式会社テラスカイ 製品開発部
SuPICE / SkyVisualEditor プロダクトマネージャー
吉田 寛
Copyright © TerraSky Co., Ltd. All Rights Reserved. 8
【AccountSearch.cmp】
<aura:component controller="AccountSearchController" implements="force:appHostable,flexipage:availableForAllPageTypes">
<ltng:require scripts="/resource/TS_AccountSearch/jquery-1.11.3.min.js,
/resource/TS_AccountSearch/lodash.min.js" />
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<div class="condition">
<!-- SearchBox Start -->
<div class="section">
<div class="sectionTitile">Search Box</div>
<div class="searchCondition">
<div class="searchItem">
<span class="searchItemLabel"></span>
<input type="text" id="selectInputItem" palaceholder="Account Name"/>
</div>
<div class="searchItem">
<span class="searchItemLabel"></span>
<select id="selectSearchItem">
<option value="">--None--</option>
</select>
</div>
<div class="searchButtonBox">
<input class="searchButton" type="button" value="Search" onclick="{!c.doSearch}"/>
</div>
</div>
</div>
<!-- Results Start -->
<div class="section">
<div class="sectionTitile">Search Results</div>
<div class="searchResult"></div>
</div>
</div>
<!-- detail Start -->
<div class="detail" style="display:none">
<div class="section">
<div id="TS_DetailSection" tabindex="0" class="sectionTitile">Detail</div>
<div class="detailList"></div>
<input type="button" value="Close" onclick="{!c.closeDetail}"/>
</div>
</div>
</aura:component>
【AccountSearchController.js】
({
doInit : function(component, event, helper){
var action = component.get('c.getOptions');
action.setCallback(this,function(response){
var sel = document.getElementById('selectSearchItem');
for(var i =0; i<response.getReturnValue().length; i++){
var val = response.getReturnValue()[i].BillingState;
if(val){
var op = document.createElement('option');
op.setAttribute('value',val);
op.innerHTML = val;
sel.appendChild(op);
}
}
});
$A.enqueueAction(action);
},
doSearch : function(component, event, helper) {
var accName = document.getElementById('selectInputItem').value;
var accState = document.getElementById('selectSearchItem').value;
var action;
if(accName !="" && accState == ""){
action = component.get('c.getAccountName');
}else if (accName == "" && accState != ""){
action = component.get('c.getAccountState');
}else{
action = component.get('c.getAccounts');
}
action.setParams({
'accName':accName,
'accState':accState
});
action.setCallback(this,function(response){
var str = '<% records.forEach(function (r) { %>¥
<div class="wrap">¥
<div class="mapframe">¥
<img class="map" src="/resource/mapicon/mapicon/icon_1r_64.png" address="<%= r.Street %>" />¥
</div>¥
<div id="<%= r.Id %>" class="recordList">¥
<div class="Name"><%= r.Name %></div>¥
<div>BillingAddress <br/>¥
Country : <%= r.Country %><br/>¥
PostalCode : <%= r.PostalCode %><br/>¥
State : <%= r.State %><br/>¥
City : <%= r.City %><br/>¥
Street : <%= r.Street %>¥
</div>¥
<div>Phone : <%= r.Phone %></div>¥
</div>¥
</div>¥
<% }); %>';
helper.setResult(component,str,response.getReturnValue(),event);
});
$A.enqueueAction(action);
},
closeDetail: function(){
// $('.detail').fadeOut('normal');
$('.detail').hide();
$('.condition').fadeIn('normal');
},
navigate : function() {
},
})
【AccountSearchHelper.js】
({
setResult: function(cmp,str,record,event) {
var self = this;
function toArray(fakeArray) {
return Array.prototype.slice.call(fakeArray);
}
$(function () {
var records = Array.apply(null, new Array(record.length)).map(function (n, i) {
var address = encodeURIComponent(record[i].BillingStreet);
return {
Id: record[i].Id,
Name: record[i].Name,
Country: record[i].BillingCountry,
PostalCode: record[i].BillingPostalCode,
State: record[i].BillingState,
City: record[i].BillingCity,
Street: record[i].BillingStreet,
Phone: record[i].Phone,
Address: address
};
});
var template = _.template(str);
document.getElementsByClassName('searchResult')[0].innerHTML = template({records: records});
});
/* 詳細表示用 */
$('.recordList').click(function (ev) {
self.setDetailList(cmp,record,ev);
$('.detail').fadeIn('normal');
$('.condition').hide();
});
/* 地図表示用 */
$('.map').click(function (e) {
var address = encodeURIComponent($(e.target).attr('address'));
var urlEvent = $A.get("e.force:navigateToURL");
urlEvent.setParams({
"url": 'https://www.google.com/maps/place/' + address
});
urlEvent.fire();
});
},
setDetailList: function(cmp,record,ev){
var self = this;
var recordId = ev.currentTarget.id;
var str = '<% records.forEach(function (r) { %>¥
<div class="recordDetail">¥
<div class="Name"><%= r.Name %></div>¥
<div>BillingAddress <br/>¥
Country : <%= r.Country %><br/>¥
PostalCode : <%= r.PostalCode %><br/>¥
State : <%= r.State %><br/>¥
City : <%= r.City %><br/>¥
Street : <%= r.Street %>¥
</div>¥
<div>Phone : <%= r.Phone %></div>¥
<div class="conRelatedList">¥
<table>¥
<thead>¥
<tr>¥
<th>No.</th><th>Contact Name</th>¥
</tr>¥
</thead>¥
<tbody class="conRelatedListBody">¥
</tbody>¥
</table>¥
</div>¥
<div class="oppRelatedList">¥
<table>¥
<thead>¥
<tr>¥
<th>No.</th><th>Opportunity Name</th>¥
</tr>¥
</thead>¥
<tbody class="oppRelatedListBody">¥
</tbody
>¥
</table>¥
</div>¥
</div><br/>¥
<% }); %>'
function toArray(fakeArray) {
return Array.prototype.slice.call(fakeArray);
}
$(function () {
var records = Array.apply(null, new Array(record.length)).map(function (n, i) {
return {
Id: record[i].Id,
Name: record[i].Name,
Country: record[i].BillingCountry,
PostalCode: record[i].BillingPostalCode,
State: record[i].BillingState,
City: record[i].BillingCity,
Street: record[i].BillingStreet,
Phone: record[i].Phone,
Contacts: record[i].Contacts,
Opportunities: record[i].Opportunities
};
});
var template = _.template(str);
for(var i=0; i<records.length; i++){
if(records[i].Id == recordId){
document.getElementsByClassName('detailList')[0].innerHTML = template({records: [records[i]]});
self.setRelatedList(records[i]);
}
}
});
},
setRelatedList: function(record){
var conStr = '<% records.forEach(function (r) { %>¥
<tr>¥
<td>No.<%= r.count %></td><td><%= r.cName %></td><td><i class="fa fa-phone"></i></td><td><i class="fa fa-envelope-o"></i></td>¥
</tr>¥
<% }); %>'
var oppStr = '<% records.forEach(function (r) { %>¥
<tr>¥
<td>No.<%= r.count %></td><td><%= r.oName %></td>¥
</tr>¥
<% }); %>'
function toArray(fakeArray) {
return Array.prototype.slice.call(fakeArray);
}
$(function () {
var template;
if(record.Contacts){
var cRecords = Array.apply(null, new Array(record.Contacts.length)).map(function (n, i) {
return {
count: i+1,
cName: record.Contacts[i].Name
};
});
template = _.template(conStr);
document.getElementsByClassName('conRelatedListBody')[0].innerHTML = template({records: cRecords});
}
if(record.Opportunities){
var oRecords = Array.apply(null, new Array(record.Opportunities.length)).map(function (n, i) {
return {
count: i+1,
oName: record.Opportunities[i].Name
};
});
template = _.template(oppStr);
document.getElementsByClassName('oppRelatedListBody')[0].innerHTML = template({records: oRecords});
}
});
}
})
【AccountSearchStyle.css】
.THIS .section {
padding: 3px;
border: solid 1px #ccc;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
margin: 5px;
}
.THIS .sectionTitile{
background: #717ECD;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
padding: 5px;
color: #fff;
}
.THIS .searchCondition {
padding: 5px;
}
.THIS #selectInputItem{
height: 30px;
min-width: 10em;
border:solid 1px #ccc;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
margin-left: 5px;
}
.THIS #selectSearchItem{
height: 30px;
min-width: 10em;
border:solid 1px #ccc;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
margin-left: 5px;
}
.THIS .searchItem {
display: inline-block;
margin: 5px 10px;
}
.THIS .searchItemLabel{
display: inline-block;
text-align: left;
}
.THIS .searchButtonBox{
display: inline-block;
}
.THIS .searchButton{
height: 30px;
}
.THIS .Name {
font-size: 1.2em;
font-weight: bold;
margin-bottom: 5px;
}
.THIS .wrap {
position: relative;
}
.THIS .recordList {
padding: 10px;
background: #fff;
border:1px solid rgb(199, 199, 199);
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
margin-top:10px;
box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
-webkit-box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
-moz-box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
}
.THIS .mapframe {
position: absolute;
right: 0;
top: 0;
}
.THIS .recordDetail {
padding: 10px;
background: #fff;
border:1px solid rgb(199, 199, 199);
margin-top: 3px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
-webkit-box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
-moz-box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
}
/* Contact Table */
.THIS .conRelatedList {
margin:10px 0;
}
.THIS .conRelatedList table {
border-collapse: separate;
border-spacing: 1px;
}
.THIS .conRelatedList table thead tr {
background: #56458c;
color: #fff;
}
.THIS .conRelatedList table thead tr th {
padding: 5px;
}
.THIS .conRelatedList table tbody tr td {
padding: 5px;
border-bottom: solid 1px #ddd;
}
/* Opportunity Table */
.THIS .oppRelatedList {
margin:10px 0;
}
.THIS .oppRelatedList table {
border-collapse: separate;
border-spacing: 1px;
}
.THIS .oppRelatedList table thead tr {
background: #F3AE4E;
}
.THIS .oppRelatedList table thead tr th {
padding: 5px;
}
.THIS .oppRelatedList table tbody tr td {
padding: 5px;
border-bottom: solid 1px #ddd;
}
Component
Controller
Helper Style
Copyright © TerraSky Co., Ltd. All Rights Reserved. 9
【AccordionList.cmp】
<aura:component controller="ToDoListController" implements="force:appHostable,flexipage:availableForAllPageTypes">
<aura:attribute name="toDos" type="Task[]" />
<ltng:require styles="
/resource/TS_ToDoList/Font-Awesome-master/css/font-awesome.min.css,
/resource/TS_ToDoList/Swiper-master/dist/css/swiper.min.css"
scripts="
/resource/TS_ToDoList/jquery-1.11.3.min.js,
/resource/TS_ToDoList/lodash.min.js,
/resource/TS_ToDoList/Swiper-master/dist/js/swiper.min.js,
/resource/TS_ToDoList/dateformat.js"
afterScriptsLoaded="{!c.afterScript}"/>
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<!-- Todo List -->
<div class="TS_component">
<h2 class="component_Title_header">
<div class="icon_Frame todo_Color"><img src="/img/icon/t4v32/standard/task_120.png" class="icon" alt="ToDo" title="ToDo" /></div>
<span class="component_Title">ToDo List</span>
</h2>
<div class="todo_List" id="todo_List">
</div>
</div>
</aura:component>
【ToDoListController.js】
({
doInit : function(component, event, helper) {
var action = component.get('c.getToDos');
action.setCallback(this,function(response){
component.set('v.toDos',response.getReturnValue());
});
$A.enqueueAction(action);
},
showSpinner : function (component, event, helper) {
var spinner = component.find('todo_spinner');
var evt = spinner.get("e.toggle");
evt.setParams({ isVisible : true });
evt.fire();
},
hideSpinner : function (component, event, helper) {
var spinner = component.find('todo_spinner');
var evt = spinner.get("e.toggle");
evt.setParams({ isVisible : false });
evt.fire();
},
afterScript : function(component, event, helper) {
var action = component.get('c.getToDos');
action.setCallback(this,function(response){
var str = '<% records.forEach(function (r) { %>¥
<div class="swiper-container">¥
<div class="done"><i class="fa fa-check-square-o"></i><br/>Done </div>¥
<div class="delete"><i class="fa fa-trash-o"></i><br />Del </div>¥
<div class="swiper-wrapper <%= r.style %>">¥
<div class="mark_l">¥
<i class="fa fa-caret-left"></i>¥
<i class="fa fa-hand-pointer-o"></i>Done¥
</div>¥
<div class="mark_r">¥
Delete <i class="fa fa-hand-pointer-o"></i>¥
<i class="fa fa-caret-right"></i>¥
</div>¥
<div class="swiper-slide" id="<%= r.Id %>">¥
<div class="subject"><%= r.Name %></div>¥
<span class="details"><%= r.Status %></span>¥
<span class="details"><%= r.ActivityDate %></span>¥
</div>¥
</div>¥
</div>¥
<% }); %>';
helper.setToDoList(component,str,response.getReturnValue(),event);
helper.doSwiper(component);
});
$A.enqueueAction(action);
},
})
【ToDoListHelper.js】
({
setToDoList:function(component, str, record){
var expanded;
var self = this;
function toArray(fakeArray) {
return Array.prototype.slice.call(fakeArray);
}
$(function () {
var records = Array.apply(null, new Array(record.length)).map(function (n, i) {
/* 日付フォーマットM/d/yyyy */
var dateFormat = new DateFormat("M/d/yyyy");
var str = dateFormat.format(new Date(record[i].ActivityDate));
/** 期限を確認する **/
var today = new Date();
var date = new Date(record[i].ActivityDate);
var style = '';
if (today > date) {
style = 'expired';
}
return {
Name: record[i].Subject,
Status: record[i].Status,
IsClosed: record[i].IsClosed,
ActivityDate: str,
Id: record[i].Id,
style: style
};
});
var template = _.template(str);
document.getElementById('todo_List').innerHTML = template({records: records});
});
},
doSaveToDo: function(component, recordId){
// var upsertToDo = {'sobjectType':'Task','Id':recordId,'Status':'Completed'};
var upsertToDo = {'sobjectType':'Task','Id':recordId};
var action = component.get("c.saveToDo");
action.setParams({"tasks":upsertToDo});
action.setCallback(this,function(a){
console.log("FIN!!");
});
console.log("GO!!");
$A.enqueueAction(action);
},
doSwiper: function(component){
var self = this;
var mySwiper = new Swiper('.swiper-container',{
pagination: '.pagination',
loop:false,
paginationClickable:true,
calculateHeight:true,
touchRatio:0.6,
onTransitionStart: function (swiper){
var recordId = swiper.wrapper[0].id;
if(recordId){
self.doSaveToDo(component, recordId);
}
},
onTransitionEnd: function(swiper){
if(swiper.touches.diff <= -170){
$(swiper.container[0]).css('display','none');
} else if (swiper.touches.diff >= 170){
/* $(swiper.wrapper[0]).css({'background':'#D3D3D3','border-color':'#c7c7c7'}); */
$(swiper.wrapper[0]).addClass('todoDone');
/* $(swiper.wrapper[0]).find('.subject').addClass('todoDone'); */
}
}
});
}
})
【ToDoListStyle.css】
/* Component Header */
.THIS .component_Title_header{
margin: 5px;
}
.THIS .icon_Frame {
display: inline-block;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
}
.THIS .todo_Color {
background: #4BC076;
}
.THIS .icon_Frame .icon {
width: 2rem;
height: 2rem;
vertical-align: middle;
}
.THIS .component_Title {
margin-left:10px;
}
/* Records */
.THIS .swiper-wrapper {
height: 100%;
padding: 10px;
/* background: #8BC34A; */
background: #C8E6C9;
background: #8BC34A;
background: #fff;
border:1px solid #388E3C;
margin: 3px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
-webkit-box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
-moz-box-shadow:rgba(113, 135, 164, 0.65098) 4px 3px 6px -3px;
width:auto;
}
.THIS .mark_l i{
margin-right: 2px;
}
.THIS .mark_l {
position: absolute;
top: 0;
left: 0;
margin-left: 5px;
margin-top: 5px;
color: #fff;
color: #616161;
}
.THIS .mark_r {
position: absolute;
top: 0;
right: 0;
margin-right: 5px;
margin-top: 5px;
color: #fff;
color: #616161;
}
.THIS .swiper-slide {
margin-top:20px;
width:100% !important;
border-left: solid 5px #388E3C;
padding-left: 5px;
}
.THIS .expired {
border:1px solid #D32F2F;
/* background: #eb4654; */
background: #FFCDD2;
background: #FF5252;
background: #eb4654;
background: #fff;
}
.THIS .expired .swiper-slide {
margin-top:20px;
width:100% !important;
border-left: solid 5px #D32F2F;
padding-left: 5px;
}
/* swipe時に表示される */
.THIS .done {
position: absolute;
top: 5px;
padding: 10px;
vertical-align: middle;
background: #4AB471;
color: #fff;
margin-top: 4px;
margin-left: 3px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
width:50%;
}
.THIS .delete {
position: absolute;
top: 5px;
right: 0;
padding: 10px;
text-align: right;
background: #D96383;
color: #fff;
margin-top: 4px;
margin-right: 3px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
width:48%
}
.THIS .subject {
font-size: 1.2em;
font-weight: bold;
margin-bottom: 5px;
}
.THIS .todoDone {
background: #D3D3D3;
border-color: #c7c7c7;
}
.THIS .todoDone .swiper-slide {
border-left: solid 5px #fff;
}
.THIS .details {
display: inline-block;
color: #fff;
width: 48%;
color: #616161;
}
Component
Controller
Helper
Style
Copyright © TerraSky Co., Ltd. All Rights Reserved. 11
• 全般– Lightningの進化速度
• Github : aura ソースコードについて
• コーディング– Locker Service に気をつけろ!
• React利用で困ったこと
– SPAの考慮点• ブラウザバック• LightningアプリケーションとLightning Experience
– ファイルアップロードの開発は止めた方が良い
• パフォーマンス問題– 表示切り替えで<aura:if>は使ってはいけない
• https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/components_conditional_markup.htm
– initイベントとrendering• https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_cb_init_handler.htm?search_text=invoking%20actions%20on%20component
Copyright © TerraSky Co., Ltd. All Rights Reserved. 13
デベロッパとは、「開発(develop)する者」を意味する一般的な英語であり、IT用語としては、主にソフトウェアの製作を手がける事業者を指す語として用いられる。
傾向としては、デベロッパの語は事業者、開発に携わる組織全体を指す語として用いられる。個人を指して「プログラマ」や「エンジニア」と同じ意味合いでデベロッパと呼ぶ場合もあるが、デベロッパは必ずしもプログラミングを手がける個人を意味するとは限らない。
なお、不動産関連の分野では、宅地の造成を手がける事業者をデベロッパという。
「IT用語辞典バイナリ」より引用
http://www.sophia-it.com/content/developer
Copyright © TerraSky Co., Ltd. All Rights Reserved. 14
プロトタイプ開発
(設定)
Salesforceの設定
デモ
プロトタイプ開発(コーディング)
Visualforce、Apexクラス、Apexトリガー 設計
コーディング
提案
UT
IT
ST
UAT
Fit & Gap(設定 or 開発)
Fit & Gap(利用 or スクラッチ)
見積り 要件定義 設計・開発 テスト 移行 本番稼働
見積書作成 要件定義書作成データ移行
変更セット
Copyright © TerraSky Co., Ltd. All Rights Reserved. 15
プロトタイプ開発
(設定)
Salesforceの設定
デモ
プロトタイプ開発(コーディング)
Visualforce、Apexクラス、Apexトリガー 設計
コーディング
提案
UT
IT
ST
UAT
Fit & Gap(設定 or 開発)
Fit & Gap(利用 or スクラッチ)
見積り 要件定義 設計・開発 テスト 移行 本番稼働
見積書作成 要件定義書作成データ移行
変更セット
Fit & Gap(利用 or スクラッチ)
Lightning、Apexクラス、Apexトリガー 設計
コーディング
既存(Visualforce、Apex)開発とLightning開発の差分
Copyright © TerraSky Co., Ltd. All Rights Reserved. 17
プロトタイプ開発
(設定)
Salesforceの設定
デモ
プロトタイプ開発(コーディング)
Visualforce、Apexクラス、Apexトリガー 設計
コーディング
提案
UT
IT
ST
UAT
Fit & Gap(設定 or 開発)
Fit & Gap(利用 or スクラッチ)
見積り 要件定義 設計・開発 テスト 移行 本番稼働
見積書作成 要件定義書作成データ移行
変更セット
Fit & Gap(利用 or スクラッチ)
Lightning、Apexクラス、Apexトリガー 設計
Fit & Gap(設定 or 開発)
Fit & Gap(利用 or スクラッチ)
Lightning、Apexクラス、Apexトリガー 設計
コーディング
Copyright © TerraSky Co., Ltd. All Rights Reserved. 18
コード
アプリケーション
ページページページ
コンポーネントコンポーネント コンポーネント
クラス/ファイル
メソッド/ファンクション
まとめ
Copyright © TerraSky Co., Ltd. All Rights Reserved. 20
• Developer は実施する作業が沢山ありますコーディングだけが仕事ではない
• 設計が重要
⁃ Salesforce標準設定 と スクラッチ開発
⁃ 既存アプリ、コンポーネント と スクラッチ開発
⁃ コンポーネントの切り分け
• Lightningコンポーネントを作成する「SuPICE」という製品もあります