• 検索結果がありません。

クリックしてタイトルを入力

N/A
N/A
Protected

Academic year: 2021

シェア "クリックしてタイトルを入力"

Copied!
21
0
0

読み込み中.... (全文を見る)

全文

(1)

Lightning コンポーネント開発の勘所

Salesforce World Tour Tokyo 2016

[Session 9-4]

2016年12月14日

株式会社テラスカイ 製品開発部

SuPICE / SkyVisualEditor プロダクトマネージャー

吉田 寛

(2)

コンサルティング

クラウド・

インテグレーション

クラウドサービス

運用支援

(3)
(4)

どんな

Lightningコンポーネント

が作れる

(5)
(6)
(7)

Copyright © TerraSky Co., Ltd. All Rights Reserved.

7

コーディング

してみる?

(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

(9)

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

(10)

[開発者向け] Lightning コンポーネント開発の勘所

(11)

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

(12)
(13)

Copyright © TerraSky Co., Ltd. All Rights Reserved.

13

デベロッパとは、「開発(develop)する者」を意味する一般的な英語であり、IT用語とし

ては、主にソフトウェアの製作を手がける事業者を指す語として用いられる。

傾向としては、デベロッパの語は事業者、開発に携わる組織全体を指す語として用いられる。

個人を指して「プログラマ」や「エンジニア」と同じ意味合いでデベロッパと呼ぶ場合もあ

るが、

デベロッパは必ずしもプログラミングを手がける個人を意味するとは限らない

なお、不動産関連の分野では、宅地の造成を手がける事業者をデベロッパという。

「IT用語辞典バイナリ」より引用

http://www.sophia-it.com/content/developer

(14)

プロトタイプ開発

(設定)

Salesforceの設定

デモ

プロトタイプ開発

(コーディング)

Visualforce、Apexクラス、Apexトリガー 設計

提案

UT

IT

ST

UAT

Fit & Gap

(設定 or 開発)

(利用 or スクラッチ)

Fit & Gap

見積り

要件定義

設計・開発

テスト

移行

本番稼働

見積書作成

要件定義書作成

データ

移行

変更

セット

(15)

Copyright © TerraSky Co., Ltd. All Rights Reserved.

15

プロトタイプ開発

(設定)

Salesforceの設定

デモ

プロトタイプ開発

(コーディング)

Visualforce、Apexクラス、Apexトリガー 設計

コーディング

提案

UT

IT

ST

UAT

Fit & Gap

(設定 or 開発)

(利用 or スクラッチ)

Fit & Gap

見積り

要件定義

設計・開発

テスト

移行

本番稼働

見積書作成

要件定義書作成

データ

移行

変更

セット

Fit & Gap

(利用 or スクラッチ)

Lightning、Apexクラス、Apexトリガー 設計

コーディング

既存(Visualforce、Apex)開発

とLightning開発の差分

(16)

良い設計が

できる人

(17)

Copyright © TerraSky Co., Ltd. All Rights Reserved.

17

プロトタイプ開発

(設定)

Salesforceの設定

デモ

プロトタイプ開発

(コーディング)

Visualforce、Apexクラス、Apexトリガー 設計

コーディング

提案

UT

IT

ST

UAT

Fit & Gap

(設定 or 開発)

(利用 or スクラッチ)

Fit & Gap

見積り

要件定義

設計・開発

テスト

移行

本番稼働

見積書作成

要件定義書作成

データ

移行

変更

セット

Fit & Gap

(利用 or スクラッチ)

Lightning、Apexクラス、Apexトリガー 設計

Fit & Gap

(設定 or 開発)

(利用 or スクラッチ)

Fit & Gap

Lightning、Apexクラス、Apexトリガー 設計

(18)

コード

アプリケーション

ページ

ページ

ページ

コンポーネント

コンポーネント

コンポーネント

クラス

/ファイル

メソッド

/ファンクション

(19)

Copyright © TerraSky Co., Ltd. All Rights Reserved.

19

(20)

ま と め

• Developer は実施する作業が沢山あります

コーディングだけが仕事ではない

• 設計が重要

⁃ Salesforce標準設定

スクラッチ開発

⁃ 既存アプリ、コンポーネント

スクラッチ開発

⁃ コンポーネント

切り分け

Lightningコンポーネントを作成する

「SuPICE」という製品もあります

(21)

D-1

ハンズオン

会場

photo

カウンター

参照

関連したドキュメント

パスワード 設定変更時にパスワードを要求するよう設定する 設定なし 電波時計 電波受信ユニットを取り外したときの動作を設定する 通常

処理水 バッファ タンク ろ過水 タンク 3号機 原子炉圧力容器. 処理水より 補給用 補給用

充電器内のAC系統部と高電圧部を共通設計,車両とのイ

サンプル 入力列 A、B、C、D のいずれかに指定した値「東京」が含まれている場合、「含む判定」フラグに True を

病院と紛らわしい名称 <例> ○○病院分院 ○○中央外科 ○○総合内科 優位性、優秀性を示す名称 <例>

・電源投入直後の MPIO は出力状態に設定されているため全ての S/PDIF 信号を入力する前に MPSEL レジスタで MPIO を入力状態に設定する必要がある。MPSEL

①生活介護 定員 60 名 ②施設入所支援 定員 40 名 ③短期入所 定員10名 ④グループホーム 定員10名 ⑤GH 併設短期入所 定員3名. サービス 定員 延 べ 利

処理水 バッファ タンク ろ過水 タンク 常用高台炉注水ポンプ