Lightning コンポーネント開発の勘所
Salesforce World Tour Tokyo 2016
[Session 9-4]
2016年12月14日
株式会社テラスカイ 製品開発部
SuPICE / SkyVisualEditor プロダクトマネージャー
吉田 寛
コンサルティング
クラウド・
インテグレーション
クラウドサービス
運用支援
どんな
Lightningコンポーネント
が作れる
?
Copyright © TerraSky Co., Ltd. All Rights Reserved.
7
コーディング
してみる?
【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; }