聞いてスッキリ!Lightningの理解ポイント
~ひと味足りない業務アプリに
「SuPICE」を効かせて欲しかったを実現~
2015年12月04日
株式会社テラスカイ 製品開発部
吉田 寛
Salesforce World Tour Tokyo 2015
Developer Theater Session
会社紹介:概要
Copyright © 2015 Terrasky Co., Ltd. All Rights Reserved. 2
社 名 : 株式会社テラスカイ
所在地 : 〒103-0027
東京都中央区日本橋1-3-13
東京建物日本橋ビル7階
事業所 : 大阪、名古屋
設 立 : 平成18(2006)年4月
資本金 : 4億5403.5万円
代表者 : 代表取締役社長 佐藤 秀哉
情報管理: ISO 27001/IS 561777
URL : http://www.terrasky.co.jp/
会社紹介:多くの専門技術者
Copyright © TerraSky Co., Ltd. All Rights Reserved. 3
2015年12月1日時点
AWS認定ソリューションアーキテクト
認定SalesCloud
コンサルタント
認定ServiceCloud
コンサルタント
認定上級
デベロッパー
認定上級
アドミニストレーター
Force.com MVP 認定テクニカルアーキテクト
みずほキャピタル 様 富士通ゼネラル 様
損保ジャパン日本興亜システムズ様 エフエーサービス様
昭和シェル石油 様 ダンアンドブラッドストリート 様
小田急電鉄 様 デジタルハリウッド 様
小田急バス 様 パソナグループ 様
アリスタ ライフサイエンス 様 リンクイベントプロデュース 様
日立アロカメディカル 様 富士通ラーニングメディア 様
楽天 様 世界自然保護基金ジャパン 様
KVH 様 その他 多数
会社紹介:卓越したクラウド導入実績
Copyright © TerraSky Co., Ltd. All Rights Reserved. 4
Salesforce導入実績
2,000案件超!
金融からコールセンター、
サービス業まで業種業態を問わず
豊富な導入実績
金融 ・ 流通
医療 ・ IT
製造 ・ サービス
不動産 ・ 教育
非営利
5
Lightning とは
Copyright © TerraSky Co., Ltd. All Rights Reserved. 6
Lightning とは
(出所:Youtube Dreamforce Keynoteより画面を引用)
7
Lightning とは、Force.com の新しいプラットフォームサービス群
Lightning コンポーネント
Lightning App Builder
Lightning Schema Builder
Lightning Connect
Salesforce Connect
Lightning Process Builder
Lightning Community Builder
(出所:salesforce.com社App Cloudサイトより画像を引用)
8
Lightning とは、Force.com の新しいプラットフォームサービス群
Lightning コンポーネント
Lightning App Builder
Lightning Schema Builder
Lightning Connect
Salesforce Connect
Lightning Process Builder
Lightning Community Builder
(出所:salesforce.com社App Cloudサイトより画像を引用)
今回のお話の範囲
Visualforce から Lightning へ
Copyright © TerraSky Co., Ltd. All Rights Reserved. 9
⁃ 新しいUIへ
⁃ 「ページ」から『コンポーネント』へ
Lightningページに
配置
Lightning の 画面開発
Copyright © TerraSky Co., Ltd. All Rights Reserved. 10
コンポーネントを
入手/開発
(出所:salesforce.com社資料「Building Lightning Components for ISVs」,Blog「Welcome to the Future of CRM. Welcome to Salesforce Lightning.」より図を引用)
デスクトップ、各デバイスで
利用
今までの利用者の作業範囲今までの開発者の作業範囲
これからの開発者の作業範囲 これからの利用者の作業範囲
Lightning の 画面開発
Copyright © TerraSky Co., Ltd. All Rights Reserved. 11
(出所:salesforce.com社資料「Building Lightning Components for ISVs」,Blog「Welcome to the Future of CRM. Welcome to Salesforce Lightning.」より図を引用)
⁃ 利用者の作業可能範囲が広がる
⁃ (イメージとして)開発者に求める作業期間が短くなる
より速い開発が必要
開発言語・使用技術 の 変化
Copyright © TerraSky Co., Ltd. All Rights Reserved. 12
⁃ 業務ロジックの実装はApexからLightning側に(と言われている)
⁃ JavaScript、CSSが多くなる
Copyright © TerraSky Co., Ltd. All Rights Reserved. 13
どんなコンポーネントが作れる?
Lightningで
Sample 1 : アコーディオンリスト
Copyright © TerraSky Co., Ltd. All Rights Reserved. 14
【AccordionList.cmp】
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" controller="AccountListController" >
<ltng:require styles="/resource/TS_AccordionList/TS_AccordionList/css/font-awesome.min.css"
scripts="/resource/TS_AccordionList/TS_AccordionList/js/jquery.min.js,
/resource/TS_AccordionList/TS_AccordionList/js/jquery-ui.min.js,
/resource/TS_AccordionList/TS_AccordionList/js/lodash.js" afterScriptsLoaded="{!c.afterScriptsLoaded}" />
<div class="sg-icon-art sg-icn--fnt center tc icon-utility-download" title="download" />
<!-- AccordionList -->
<div class="TS_component">
<h2 class="component_Title_header">
<div class="icon_Frame account_Color"><img src="/img/icon/t4v32/standard/account_120.png" class="icon " alt="Account"
title="Account" /
<span class="component_Title">Account Lists</span>
</h2>
<div class="component_Body">
<table id="AccordionListTable" ></table>
</div>
</div>
</aura:component>
【AccordionListController.js】
({
afterScriptsLoaded : function(component, event, helper) {
var expanded;
$(function () {
/* ここから */
var action = component.get("c.findAll");
var records;
action.setCallback(this, function(response){
var state = response.getState();
if (state === "SUCCESS") {
var res = response.getReturnValue();
records = Array.apply(null, new Array(res.length)).map(function (n, i) {
return {
Name: res[i].Name,
State:res[i].BillingState,
Phone: res[i].Phone,
Fax: res[i].Fax,
Address: res[i].BillingState + res[i].BillingCity + res[i].BillingStreet
};
});
}
var template = _.template('¥
<% records.forEach(function (r) { %>¥
<tr class="row">¥
<td>¥
<div class="form-control">¥
<span class="accName"><%= r.Name %></span><span class="accState"><%= r.State %></span>¥
</div>¥
<div class="content">¥
<div class="fields">¥
<span class="content_Icon"><i class="fa fa-phone"></i></span>¥
<span class="content_Value"><%= r.Phone %></span>¥
</div>¥
<div class="fields">¥
<span class="content_Icon"><i class="fa fa-fax"></i></span>¥
<span class="content_Value"><%= r.Fax %></span>¥
</div>¥
<div class="fields">¥
<span class="icon_Address"><i class="fa fa-building-o"></i></span>¥
<span class="content_Address"><%= r.Address %></span>¥
</div>¥
</div>¥
</td>¥
</tr>¥
<% }); %>¥
');
document.getElementById('AccordionListTable').innerHTML = template({records: records});
$('.row').click(function () {
if (expanded) {
$(expanded).find('.content').slideUp(300);
$(this).find('.accName').removeClass('textBold');
}
if (expanded === this) {
expanded = null;
return;
}
expanded = this;
var content = $(this).find('.content');
content.hide();
content.slideDown(300);
$('.accName').removeClass('textBold');
$(this).find('.accName').addClass('textBold');
});
});
$A.enqueueAction(action);
});
}
})
【AccordionListStyle.css】
/* Component Header */
.THIS .TS_component {
margin-left: 10px;
}
.THIS .component_Title_header{
margin: 5px;
}
.THIS .icon_Frame {
display: inline-block;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
}
.THIS .account_Color {
background: #7F8DE1;
}
.THIS .icon_Frame .icon {
width: 2rem;
height: 2rem;
vertical-align: middle;
}
.THIS .component_Title {
margin-left:10px;
}
/* AccordionList */
.THIS .component_Body{
width: 98%;
margin: auto;
border:solid 1px #ccc;
background: #fff;
padding: 10px;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
}
.THIS #AccordionListTable {
margin:0 auto;
width: 100%;
}
.THIS #AccordionListTable td {
padding:10px;
border-top: solid 1px #ddd;
}
.THIS .form-control{
padding:5px;
color:#2A94D6;
}
.THIS .content {
display: none;
border: solid 1px #ccc;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
background:#F0F1F2;
background:#fff;
padding:5px;
}
.THIS .fields {
margin-bottom: 5px;
}
/* AccordionList Label */
.THIS .accName{
display: inline-block;
width:60%;
}
.THIS .accState{
display: inline-block;
text-align:right;
width:40%;
}
/* AccordionList content */
.THIS .content_Icon{
width: 30px;
text-align: center;
display: inline-block;
}
.THIS .content_Value{
display: inline-block;
width: auto;
}
/* content Addess only */
.THIS .icon_Address{
width: 30px;
text-align: center;
display: inline-block;
float: left;
}
.THIS .content_Address{
display: block;
width: auto;
margin-left: 30px;
}
.THIS .textBold {
font-weight:bold;
color:#344A5F;
}
【SuPICE.cls】
interface Action {
Object run(Object data);
}
virtual public class ActionResult {
@AuraEnabled
public final Boolean error { get; private set; }
@AuraEnabled
public final String[] messages { get; private set; }
@AuraEnabled
public final Object value { get; private set; }
public ActionResult(String[] messages, Object value) {
this.error = messages != null;
this.messages = messages;
this.value = value;
}
public ActionResult(Boolean success, String[] messages, Object value) {
this.error = success;
this.messages = messages;
this.value = value;
}
}
@AuraEnabled
global static Object action(String data) {
try {
Map<String, Object> dataMap = (Map<String, Object>) JSON.deserializeUntyped(data);
String type = (String) dataMap.get('action');
Object action = ACTION_MAP.get(type);
if (action == null) {
throw new SuPICEException('Unsupported action type: ' + type);
}
Object result = ((Action) action).run(dataMap.get('data'));
return result;
} catch (SuPICEException e) {
// SuPICE処理内で予測される例外
return new ActionResult(new String[] {e.getMessage()}, null);
} catch (Exception e) {
// 予期しない例外。とりあえずStackTraceを返す
return new ActionResult(new String[] {
ERROR.getStackTrace(e)
}, null);
}
}
private static final Map<String, Action> ACTION_MAP = new Map<String, Action> {
'query' => new QueryAction(),
'describe' => new DescribeAction(),
'save' => new SaveAction(),
'delete' => new DeleteAction()
};
class QueryActionArguments {
public String objectName { get; private set; }
public String[] fields { get; private set; }
public ConditionOperator condition { get; private set; }
public QueryActionArguments(Object root) {
Map<String, Object> rootMap = (Map<String, Object>) root;
objectName = getObject(rootMap);
fields = getFields(rootMap);
Set<String> fieldSet = new Set<String>();
fieldSet.add('Id'); //Idは必ず取得する
fieldSet.addAll(getFields(rootMap));
fields = new String[0];
fields.addAll(fieldSet);
Map<String, Object> untypedCondition = getCondition(rootMap);
if (untypedCondition != null) {
condition = parseOperator(untypedCondition);
}
}
String getObject(Map<String, Object> untyped) {
return (String) untyped.get('object');
}
String[] getFields(Map<String, Object> untyped) {
String[] typed = new String[0];
List<Object> fields = (List<Object>) untyped.get('fields');
for (Object o : fields) {
typed.add((String) o);
}
return typed;
}
Map<String, Object> getCondition(Map<String, Object> untyped) {
Object condition = untyped.get('condition');
if (condition == null) {
return null;
} else {
return (Map<String, Object>) condition;
}
}
}
class DescribeAction implements Action {
public ActionResult run(Object untyped) {
String objectName = (String)((Map<String, Object>) untyped).get('object');
SObjectType soType = Schema.getGlobalDescribe().get(objectName);
if (soType == null) {
throw ERROR.getSObjectNotFound(objectName);
}
DescribeSObjectResult describe = soType.getDescribe();
return new ActionResult(null, new DescribeResult(describe));
}
}
リストをクリック(or タップ)を
すると、詳細データが表示
JavaScript CSSApex
Sample 2 : ToDo の スワイプリスト
Copyright © TerraSky Co., Ltd. All Rights Reserved. 15
【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;
}
JavaScript CSSApex
リストをスワイプをすると、
データ編集、削除等の
アクションを実行可能
Sample 3 : 取引先の検索画面
Copyright © TerraSky Co., Ltd. All Rights Reserved. 16
【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;
}
JavaScript CSSApex
検索ボタンを実行すると、
検索結果リストを表示
新サービス「SuPICE」
Copyright © TerraSky Co., Ltd. All Rights Reserved. 17
Lightning Component をノンコーディングで作成できる
世界初のクラウドサービス
Unit
Property
Deploy
来年2月
発売
予定
1. マウス操作で簡単に作成
Copyright © TerraSky Co., Ltd. All Rights Reserved. 18
「SuPICE」で何ができる?
Copyright © TerraSky Co., Ltd. All Rights Reserved. 19
2. プレビューでデザイン、動作を確認
Copyright © TerraSky Co., Ltd. All Rights Reserved. 20
3. Lightningのデザインに準拠
3つのSampleコンポーネントもSuPICEで作れます
Copyright © TerraSky Co., Ltd. All Rights Reserved. 21
アコーディオン リスト スワイプ リスト 検索画面
現在地
Camp Quick Start
I-1
T-3
Kids Coding
カウンター
IoTPartner
• ブースでデモをご覧になれますので、お立ち寄りください
ノンプログラミングで
Lightning Component
を作れる

聞いてスッキリ!Lightningの理解ポイント

  • 1.
  • 2.
    会社紹介:概要 Copyright © 2015Terrasky Co., Ltd. All Rights Reserved. 2 社 名 : 株式会社テラスカイ 所在地 : 〒103-0027 東京都中央区日本橋1-3-13 東京建物日本橋ビル7階 事業所 : 大阪、名古屋 設 立 : 平成18(2006)年4月 資本金 : 4億5403.5万円 代表者 : 代表取締役社長 佐藤 秀哉 情報管理: ISO 27001/IS 561777 URL : http://www.terrasky.co.jp/
  • 3.
    会社紹介:多くの専門技術者 Copyright © TerraSkyCo., Ltd. All Rights Reserved. 3 2015年12月1日時点 AWS認定ソリューションアーキテクト 認定SalesCloud コンサルタント 認定ServiceCloud コンサルタント 認定上級 デベロッパー 認定上級 アドミニストレーター Force.com MVP 認定テクニカルアーキテクト
  • 4.
    みずほキャピタル 様 富士通ゼネラル様 損保ジャパン日本興亜システムズ様 エフエーサービス様 昭和シェル石油 様 ダンアンドブラッドストリート 様 小田急電鉄 様 デジタルハリウッド 様 小田急バス 様 パソナグループ 様 アリスタ ライフサイエンス 様 リンクイベントプロデュース 様 日立アロカメディカル 様 富士通ラーニングメディア 様 楽天 様 世界自然保護基金ジャパン 様 KVH 様 その他 多数 会社紹介:卓越したクラウド導入実績 Copyright © TerraSky Co., Ltd. All Rights Reserved. 4 Salesforce導入実績 2,000案件超! 金融からコールセンター、 サービス業まで業種業態を問わず 豊富な導入実績 金融 ・ 流通 医療 ・ IT 製造 ・ サービス 不動産 ・ 教育 非営利
  • 5.
  • 6.
    Copyright © TerraSkyCo., Ltd. All Rights Reserved. 6 Lightning とは (出所:Youtube Dreamforce Keynoteより画面を引用)
  • 7.
    7 Lightning とは、Force.com の新しいプラットフォームサービス群 Lightningコンポーネント Lightning App Builder Lightning Schema Builder Lightning Connect Salesforce Connect Lightning Process Builder Lightning Community Builder (出所:salesforce.com社App Cloudサイトより画像を引用)
  • 8.
    8 Lightning とは、Force.com の新しいプラットフォームサービス群 Lightningコンポーネント Lightning App Builder Lightning Schema Builder Lightning Connect Salesforce Connect Lightning Process Builder Lightning Community Builder (出所:salesforce.com社App Cloudサイトより画像を引用) 今回のお話の範囲
  • 9.
    Visualforce から Lightningへ Copyright © TerraSky Co., Ltd. All Rights Reserved. 9 ⁃ 新しいUIへ ⁃ 「ページ」から『コンポーネント』へ
  • 10.
    Lightningページに 配置 Lightning の 画面開発 Copyright© TerraSky Co., Ltd. All Rights Reserved. 10 コンポーネントを 入手/開発 (出所:salesforce.com社資料「Building Lightning Components for ISVs」,Blog「Welcome to the Future of CRM. Welcome to Salesforce Lightning.」より図を引用) デスクトップ、各デバイスで 利用
  • 11.
    今までの利用者の作業範囲今までの開発者の作業範囲 これからの開発者の作業範囲 これからの利用者の作業範囲 Lightning の画面開発 Copyright © TerraSky Co., Ltd. All Rights Reserved. 11 (出所:salesforce.com社資料「Building Lightning Components for ISVs」,Blog「Welcome to the Future of CRM. Welcome to Salesforce Lightning.」より図を引用) ⁃ 利用者の作業可能範囲が広がる ⁃ (イメージとして)開発者に求める作業期間が短くなる より速い開発が必要
  • 12.
    開発言語・使用技術 の 変化 Copyright© TerraSky Co., Ltd. All Rights Reserved. 12 ⁃ 業務ロジックの実装はApexからLightning側に(と言われている) ⁃ JavaScript、CSSが多くなる
  • 13.
    Copyright © TerraSkyCo., Ltd. All Rights Reserved. 13 どんなコンポーネントが作れる? Lightningで
  • 14.
    Sample 1 :アコーディオンリスト Copyright © TerraSky Co., Ltd. All Rights Reserved. 14 【AccordionList.cmp】 <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" controller="AccountListController" > <ltng:require styles="/resource/TS_AccordionList/TS_AccordionList/css/font-awesome.min.css" scripts="/resource/TS_AccordionList/TS_AccordionList/js/jquery.min.js, /resource/TS_AccordionList/TS_AccordionList/js/jquery-ui.min.js, /resource/TS_AccordionList/TS_AccordionList/js/lodash.js" afterScriptsLoaded="{!c.afterScriptsLoaded}" /> <div class="sg-icon-art sg-icn--fnt center tc icon-utility-download" title="download" /> <!-- AccordionList --> <div class="TS_component"> <h2 class="component_Title_header"> <div class="icon_Frame account_Color"><img src="/img/icon/t4v32/standard/account_120.png" class="icon " alt="Account" title="Account" / <span class="component_Title">Account Lists</span> </h2> <div class="component_Body"> <table id="AccordionListTable" ></table> </div> </div> </aura:component> 【AccordionListController.js】 ({ afterScriptsLoaded : function(component, event, helper) { var expanded; $(function () { /* ここから */ var action = component.get("c.findAll"); var records; action.setCallback(this, function(response){ var state = response.getState(); if (state === "SUCCESS") { var res = response.getReturnValue(); records = Array.apply(null, new Array(res.length)).map(function (n, i) { return { Name: res[i].Name, State:res[i].BillingState, Phone: res[i].Phone, Fax: res[i].Fax, Address: res[i].BillingState + res[i].BillingCity + res[i].BillingStreet }; }); } var template = _.template('¥ <% records.forEach(function (r) { %>¥ <tr class="row">¥ <td>¥ <div class="form-control">¥ <span class="accName"><%= r.Name %></span><span class="accState"><%= r.State %></span>¥ </div>¥ <div class="content">¥ <div class="fields">¥ <span class="content_Icon"><i class="fa fa-phone"></i></span>¥ <span class="content_Value"><%= r.Phone %></span>¥ </div>¥ <div class="fields">¥ <span class="content_Icon"><i class="fa fa-fax"></i></span>¥ <span class="content_Value"><%= r.Fax %></span>¥ </div>¥ <div class="fields">¥ <span class="icon_Address"><i class="fa fa-building-o"></i></span>¥ <span class="content_Address"><%= r.Address %></span>¥ </div>¥ </div>¥ </td>¥ </tr>¥ <% }); %>¥ '); document.getElementById('AccordionListTable').innerHTML = template({records: records}); $('.row').click(function () { if (expanded) { $(expanded).find('.content').slideUp(300); $(this).find('.accName').removeClass('textBold'); } if (expanded === this) { expanded = null; return; } expanded = this; var content = $(this).find('.content'); content.hide(); content.slideDown(300); $('.accName').removeClass('textBold'); $(this).find('.accName').addClass('textBold'); }); }); $A.enqueueAction(action); }); } }) 【AccordionListStyle.css】 /* Component Header */ .THIS .TS_component { margin-left: 10px; } .THIS .component_Title_header{ margin: 5px; } .THIS .icon_Frame { display: inline-block; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; } .THIS .account_Color { background: #7F8DE1; } .THIS .icon_Frame .icon { width: 2rem; height: 2rem; vertical-align: middle; } .THIS .component_Title { margin-left:10px; } /* AccordionList */ .THIS .component_Body{ width: 98%; margin: auto; border:solid 1px #ccc; background: #fff; padding: 10px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; } .THIS #AccordionListTable { margin:0 auto; width: 100%; } .THIS #AccordionListTable td { padding:10px; border-top: solid 1px #ddd; } .THIS .form-control{ padding:5px; color:#2A94D6; } .THIS .content { display: none; border: solid 1px #ccc; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; background:#F0F1F2; background:#fff; padding:5px; } .THIS .fields { margin-bottom: 5px; } /* AccordionList Label */ .THIS .accName{ display: inline-block; width:60%; } .THIS .accState{ display: inline-block; text-align:right; width:40%; } /* AccordionList content */ .THIS .content_Icon{ width: 30px; text-align: center; display: inline-block; } .THIS .content_Value{ display: inline-block; width: auto; } /* content Addess only */ .THIS .icon_Address{ width: 30px; text-align: center; display: inline-block; float: left; } .THIS .content_Address{ display: block; width: auto; margin-left: 30px; } .THIS .textBold { font-weight:bold; color:#344A5F; } 【SuPICE.cls】 interface Action { Object run(Object data); } virtual public class ActionResult { @AuraEnabled public final Boolean error { get; private set; } @AuraEnabled public final String[] messages { get; private set; } @AuraEnabled public final Object value { get; private set; } public ActionResult(String[] messages, Object value) { this.error = messages != null; this.messages = messages; this.value = value; } public ActionResult(Boolean success, String[] messages, Object value) { this.error = success; this.messages = messages; this.value = value; } } @AuraEnabled global static Object action(String data) { try { Map<String, Object> dataMap = (Map<String, Object>) JSON.deserializeUntyped(data); String type = (String) dataMap.get('action'); Object action = ACTION_MAP.get(type); if (action == null) { throw new SuPICEException('Unsupported action type: ' + type); } Object result = ((Action) action).run(dataMap.get('data')); return result; } catch (SuPICEException e) { // SuPICE処理内で予測される例外 return new ActionResult(new String[] {e.getMessage()}, null); } catch (Exception e) { // 予期しない例外。とりあえずStackTraceを返す return new ActionResult(new String[] { ERROR.getStackTrace(e) }, null); } } private static final Map<String, Action> ACTION_MAP = new Map<String, Action> { 'query' => new QueryAction(), 'describe' => new DescribeAction(), 'save' => new SaveAction(), 'delete' => new DeleteAction() }; class QueryActionArguments { public String objectName { get; private set; } public String[] fields { get; private set; } public ConditionOperator condition { get; private set; } public QueryActionArguments(Object root) { Map<String, Object> rootMap = (Map<String, Object>) root; objectName = getObject(rootMap); fields = getFields(rootMap); Set<String> fieldSet = new Set<String>(); fieldSet.add('Id'); //Idは必ず取得する fieldSet.addAll(getFields(rootMap)); fields = new String[0]; fields.addAll(fieldSet); Map<String, Object> untypedCondition = getCondition(rootMap); if (untypedCondition != null) { condition = parseOperator(untypedCondition); } } String getObject(Map<String, Object> untyped) { return (String) untyped.get('object'); } String[] getFields(Map<String, Object> untyped) { String[] typed = new String[0]; List<Object> fields = (List<Object>) untyped.get('fields'); for (Object o : fields) { typed.add((String) o); } return typed; } Map<String, Object> getCondition(Map<String, Object> untyped) { Object condition = untyped.get('condition'); if (condition == null) { return null; } else { return (Map<String, Object>) condition; } } } class DescribeAction implements Action { public ActionResult run(Object untyped) { String objectName = (String)((Map<String, Object>) untyped).get('object'); SObjectType soType = Schema.getGlobalDescribe().get(objectName); if (soType == null) { throw ERROR.getSObjectNotFound(objectName); } DescribeSObjectResult describe = soType.getDescribe(); return new ActionResult(null, new DescribeResult(describe)); } } リストをクリック(or タップ)を すると、詳細データが表示 JavaScript CSSApex
  • 15.
    Sample 2 :ToDo の スワイプリスト Copyright © TerraSky Co., Ltd. All Rights Reserved. 15 【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; } JavaScript CSSApex リストをスワイプをすると、 データ編集、削除等の アクションを実行可能
  • 16.
    Sample 3 :取引先の検索画面 Copyright © TerraSky Co., Ltd. All Rights Reserved. 16 【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; } JavaScript CSSApex 検索ボタンを実行すると、 検索結果リストを表示
  • 17.
    新サービス「SuPICE」 Copyright © TerraSkyCo., Ltd. All Rights Reserved. 17 Lightning Component をノンコーディングで作成できる 世界初のクラウドサービス Unit Property Deploy 来年2月 発売 予定
  • 18.
    1. マウス操作で簡単に作成 Copyright ©TerraSky Co., Ltd. All Rights Reserved. 18 「SuPICE」で何ができる?
  • 19.
    Copyright © TerraSkyCo., Ltd. All Rights Reserved. 19 2. プレビューでデザイン、動作を確認
  • 20.
    Copyright © TerraSkyCo., Ltd. All Rights Reserved. 20 3. Lightningのデザインに準拠
  • 21.
    3つのSampleコンポーネントもSuPICEで作れます Copyright © TerraSkyCo., Ltd. All Rights Reserved. 21 アコーディオン リスト スワイプ リスト 検索画面
  • 22.
    現在地 Camp Quick Start I-1 T-3 KidsCoding カウンター IoTPartner • ブースでデモをご覧になれますので、お立ち寄りください ノンプログラミングで Lightning Component を作れる