EclipseのForce.comでSalesforceのLightningコンポーネントを開発するためのForce.comのバージョンは以下です。
Force.com IDE v37.0とその以降バージョン
参考URL
https://developer.salesforce.com/docs/atlas.en-us.eclipse.meta/eclipse/eclipse_lightning_setup.htm
kinkun's blog
EclipseのForce.comでSalesforceのLightningコンポーネントを開発するためのForce.comのバージョンは以下です。
Force.com IDE v37.0とその以降バージョン
参考URL
https://developer.salesforce.com/docs/atlas.en-us.eclipse.meta/eclipse/eclipse_lightning_setup.htm
Lightning Design Systemを使ってVisualforceでLightning雰囲気のページ作成するのを共有する。
現在Salesforceの主なページ作成はVisualforceページですが、Salesforceが提供しているCSSフレームワーク Lightning Design System を使ってみる。
開発コンソル画面を開き、Visualforce ページ作成する。
<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">
<html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" lang="ja">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Salesforce Lightning Design System</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Import the Design System style sheet -->
<apex:slds />
</head>
<apex:remoteObjects >
<apex:remoteObjectModel name="Contact" fields="Id,Name,Title,LastModifiedDate,PhotoUrl"/>
</apex:remoteObjects>
<body>
<!-- REQUIRED SLDS WRAPPER -->
<div class="slds-scope">
<!-- MASTHEAD -->
<p class="slds-text-heading--label slds-m-bottom--small">Salesforce Lightning Design System </p>
<!-- / MASTHEAD -->
<!-- PAGE HEADER -->
<div class="slds-page-header">
<!-- LAYOUT GRID -->
<div class="slds-grid">
<!-- GRID COL -->
<div class="slds-col slds-has-flexi-truncate">
<!-- HEADING AREA -->
<!-- MEDIA OBJECT = FIGURE + BODY -->
<div class="slds-media slds-no-space slds-grow">
<div class="slds-media__figure">
<svg aria-hidden="true" class="slds-icon slds-icon-standard-contact">
<use xlink:href="{!URLFOR($Asset.SLDS, 'assets/icons/standard-sprite/svg/symbols.svg#contact')}"></use>
</svg>
</div>
<div class="slds-media__body">
<p class="slds-text-title--caps slds-line-height--reset">Contacts</p>
<h1 class="slds-page-header__title slds-m-right--small slds-align-middle slds-truncate" title="My Contacts">顧客リスト</h1>
</div>
</div>
<!-- / MEDIA OBJECT -->
<!-- /HEADING AREA -->
</div>
<!-- ACTION BUTTONS -->
<div class="slds-col slds-no-flex slds-grid slds-align-top">
<div class="slds-button-group" role="group">
<button class="slds-button slds-button--neutral">Add Contact </button>
<button class="slds-button slds-button--neutral">More </button>
</div>
</div>
<!-- / ACTION BUTTONS -->
</div>
<!-- / LAYOUT GRID -->
</div>
<!-- / PAGE HEADER -->
<!-- PRIMARY CONTENT WRAPPER -->
<div class="myapp slds-p-horizontal--medium">
<ul id="contact-list" class="slds-has-dividers--bottom-space"></ul>
</div>
<!-- / PRIMARY CONTENT WRAPPER -->
<!-- FOOTER -->
<footer role="contentinfo" class="slds-p-around--large">
<!-- LAYOUT GRID -->
<div class="slds-grid slds-grid--align-spread">
<p class="slds-col">Salesforce Lightning Design System Example</p>
<p class="slds-col">© Your Name Here</p>
</div>
<!-- / LAYOUT GRID -->
</footer>
<!-- / FOOTER -->
</div>
<!-- / REQUIRED SLDS WRAPPER -->
<!-- JAVASCRIPT -->
<script>
(function() {
'use strict';
var contact = new SObjectModel.Contact();
var contactList = document.getElementById('contact-list');
function createTile (record) {
return [
'<li class="slds-item">',
'<div class="slds-tile slds-media">',
'<div class="slds-media__figure">',
'<img class="slds-avatar slds-avatar--medium" src="', record.get('PhotoUrl'), '" alt="" />',
'</div>',
'<div class="slds-media__body">',
'<h3 class="slds-truncate" title="', record.get('Name'), '"><a href="javascript:void(0);">', record.get('Name') ,'</a></h3>',
'<div class="slds-tile__detail slds-text-body--small">',
'<p class="slds-truncate">', record.get('Title') ,'</p>',
'</div>',
'</div>',
'</div>',
'</li>'
].join('');
}
contact.retrieve(
{ orderby: [{ LastModifiedDate: 'DESC' }], limit: 10 },
function(error, records) {
if (error) {
alert(error.message);
} else {
contactList.innerHTML = records.map(createTile).join('');
}
}
);
})();
</script>
<!-- / JAVASCRIPT -->
</body>
</html>
</apex:page>
簡単な解説です。
・ Lightning Design SystemのSVGアイコン使用宣言
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
・ Lightning Design Systemのスタイル、アセットをVisualforceページにインポート
<apex:slds />
・ 「Contact」へアクセスし、javascriptで呼び出 し
<apex:remoteObjectModel name="Contact" fields="Id,Name,Title,LastModifiedDate,PhotoUrl"/>
・ Lightning Design Systemの適用範囲
<div class="slds-scope">...</div>
navigateToURLによるページ画面に遷移する例 は以下です。
//Component: LightningNavigate
<aura:component implements="force:appHostable">
<div id="aura-page">
<div class="container">
<ui:button label="gotoURL" press="{!c.gotoURL}" />
</div>
<div class="container">
<ui:button label="navigate" press="{!c.navigate}" />
</div>
</div>
</aura:component>
//Controller
({
gotoURL : function(component, event, helper) {
helper.gotoURL(component);
},
navigate : function(component, event, helper) {
helper.navigate(component);
}
})
//Helper
({
gotoURL : function (component, event) {
var urlEvent = $A.get("e.force:navigateToURL");
urlEvent.setParams({
"url": "/006/o"
});
urlEvent.fire();
},
navigate : function(component,event) {
var address = '/Salesforce.com+Inc/@37.793779,-122.39448,17z/';
var urlEvent = $A.get("e.force:navigateToURL");
urlEvent.setParams({
"url": 'https://www.google.com/maps/place/' + address
});
urlEvent.fire();
}
})
//App
<aura:application >
<c:LightningNavigate />
</aura:application>
1.Lightning Component作成
quickcontact.cmp
<aura:component controller="QuickContactController" implements="flexipage:availableForAllPageTypes,force:hasRecordId,force:lightningQuickAction">
<aura:attribute name="account" type="Account" />
<aura:attribute name="newContact" type="Contact"
default="{ 'sobjectType': 'Contact' }" /> <!-- default to empty record -->
<aura:attribute name="hasErrors" type="Boolean"
description="Indicate if there were failures when validating the contact." />
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<!-- Display a header with details about the account -->
<div class="slds-page-header" role="banner">
<p class="slds-text-heading--label">{!v.account.Name}</p>
<h1 class="slds-page-header__title slds-m-right--small
slds-truncate slds-align-left">Create New Contact</h1>
</div>
<!-- Display form validation errors, if any -->
<aura:if isTrue="{!v.hasErrors}">
<div class="recordSaveError">
<ui:message title="Error" severity="error" closable="true">
The new contact can't be saved because it's not valid.
Please review and correct the errors in the form.
</ui:message>
</div>
</aura:if>
<!-- Display the new contact form -->
<div class="slds-form--stacked">
<div class="slds-form-element">
<label class="slds-form-element__label"
for="contactFirstName">First Name: </label>
<div class="slds-form-element__control">
<ui:inputText class="slds-input" aura:id="contactFirstName"
value="{!v.newContact.FirstName}" required="true"/>
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label"
for="contactLastName">Last Name: </label>
<div class="slds-form-element__control">
<ui:inputText class="slds-input" aura:id="contactLastName"
value="{!v.newContact.LastName}" required="true"/>
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label" for="contactTitle">Title: </label>
<div class="slds-form-element__control">
<ui:inputText class="slds-input" aura:id="contactTitle"
value="{!v.newContact.Title}" />
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label"
for="contactPhone">Phone Number: </label>
<div class="slds-form-element__control">
<ui:inputPhone class="slds-input" aura:id="contactPhone"
value="{!v.newContact.Phone}" required="true"/>
</div>
</div>
<div class="slds-form-element">
<label class="slds-form-element__label" for="contactEmail">Email: </label>
<div class="slds-form-element__control">
<ui:inputEmail class="slds-input" aura:id="contactEmail"
value="{!v.newContact.Email}" />
</div>
</div>
<div class="slds-form-element">
<ui:button label="Cancel" press="{!c.handleCancel}"
class="slds-button slds-button--neutral" />
<ui:button label="Save Contact" press="{!c.handleSaveContact}"
class="slds-button slds-button--brand" />
</div>
</div>
</aura:component>
2.Lightning Controller作成
quickcontactController.js
({
doInit : function(component, event, helper) {
// Prepare the action to load account record
var action = component.get("c.getAccount");
action.setParams({"accountId": component.get("v.recordId")});
// Configure response handler
action.setCallback(this, function(response) {
var state = response.getState();
if(component.isValid() && state === "SUCCESS") {
component.set("v.account", response.getReturnValue());
} else {
console.log('Problem getting account, response state: ' + state);
}
});
$A.enqueueAction(action);
},
handleSaveContact: function(component, event, helper) {
if(helper.validateContactForm(component)) {
component.set("v.hasErrors", false);
// Prepare the action to create the new contact
var saveContactAction = component.get("c.saveContactWithAccount");
saveContactAction.setParams({
"contact": component.get("v.newContact"),
"accountId": component.get("v.recordId")
});
// Configure the response handler for the action
saveContactAction.setCallback(this, function(response) {
var state = response.getState();
if(component.isValid() && state === "SUCCESS") {
// Prepare a toast UI message
var resultsToast = $A.get("e.force:showToast");
resultsToast.setParams({
"title": "Contact Saved",
"message": "The new contact was created."
});
// Update the UI: close panel, show toast, refresh account page
$A.get("e.force:closeQuickAction").fire();
resultsToast.fire();
$A.get("e.force:refreshView").fire();
}
else if (state === "ERROR") {
console.log('Problem saving contact, response state: ' + state);
}
else {
console.log('Unknown problem, response state: ' + state);
}
});
// Send the request to create the new contact
$A.enqueueAction(saveContactAction);
}
else {
// New contact form failed validation, show a message to review errors
component.set("v.hasErrors", true);
}
},
handleCancel: function(component, event, helper) {
$A.get("e.force:closeQuickAction").fire();
}
})
3.Lightning Helper作成
quickcontactHelper.js
({
validateContactForm: function(component) {
var validContact = true;
// First and Last Name are required
var firstNameField = component.find("contactFirstName");
if($A.util.isEmpty(firstNameField.get("v.value"))) {
validContact = false;
firstNameField.set("v.errors", [{message:"First name can't be blank"}]);
}
else {
firstNameField.set("v.errors", null);
}
var lastNameField = component.find("contactLastName");
if($A.util.isEmpty(lastNameField.get("v.value"))) {
validContact = false;
lastNameField.set("v.errors", [{message:"Last name can't be blank"}]);
}
else {
lastNameField.set("v.errors", null);
}
// Verify we have an account to attach it to
var account = component.get("v.account");
if($A.util.isEmpty(account)) {
validContact = false;
console.log("Quick action context doesn't have a valid account.");
}
// TODO: (Maybe) Validate email and phone number
return(validContact);
}
})
4.取引先でアクション追加
オブジェクト名 : 取引先
アクション種別 : Lightning コンポーネント
Lightning コンポーネント : c. quickcontact
高さ : 250ピクセル
表示ラベル : Quick Contact
名前 : Quick_Contact
5.取引先のページレイアウトにLightningアクションを追加
6.Lightning画面にて取引先の詳細画面で、「Quick Contact」アクション押下する。
SalesforceのLightning コンポーネント作成について共有します。
1.開発コンソル開く
設定 > 開発コンソル リンク押下
2. Lightning コンポーネント 作成
開発コンソル画面にて、
メニュー > File > New > Lightning Component
名前 : helloWorld
<aura:component >
<p>helloWorld</p>
</aura:component>
2.Lightning Application作成
開発コンソル画面にて、
メニュー > File > New > Ligntning Applicaion
名前 : harnessApp
<aura:application >
<c:helloWorld/>
</aura:application>
Previewボタン押下する。
以下のエラーメッセージが表示される。
Lightning コンポーネントには [私のドメイン] が必要です。詳細は、組織のシステム管理者にお問い合わせください。
Salesforceの組織にて、私のドメイン登録する。
testdaeheuitest-test11-dev-ed.my.salesforce.com
ドメイン登録して、使えるまで数分かかる。
ユーザにリリースすると、使えるようにんります。
Previewボタンを押下すると。以下のように表示されます。
helloWorld
Salesforceのバッチスケジュールについて共有します。
1.バッチサンプル
global with sharing class Batch_sample implements Database.Batchable<sObject>
{
private String query;
//コンストラクタ
global Batch_sample(String q){
system.debug('query ---:'+query);
query = q;
}
//データ取得
global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}
//開始
global void execute(Database.BatchableContext BC, List<sObject> scope){
system.debug('scope.size() ---'+scope.size());//バッチの処理を記述
executeLogic(scope);
}
//完了
global void finish(Database.BatchableContext BC)
{
//完了後の処理があれば
String jobName = 'Batch_sample';
CronTrigger ct = getCronTrigger1(jobName);
if (ct != null) {
System.abortJob(ct.Id);
}
Datetime dt = Datetime.now().addMinutes(2);
ID pid = System.schedule(jobName, dt.format('0 mm HH dd MM ? yyyy'), new Schedule_sample());
}
public virtual void executeLogic(List<sObject> listSObj){
List<Account> accs = new List<Account>();
List<customObject__c> customs = new List<customObject__c>();
for(sObject sObj : listSobj){
Account acc = new Account();
acc.Id = String.valueOf(sObj.get('sfid__c'));
acc.Dummy__c = Boolean.valueOf(sObj.get('Dummy__c'));
accs.add(acc);
customObject__c custom = new customObject__c();
custom.Id = String.valueOf(sObj.get('Id'));
custom.IsProcessed__c = true;
customs.add(custom);
}
try {
if(accs.size() > 0){
system.debug('executeLogic --- accs.size() --- : '+accs.size());
update accs;
}
if(customs.size() > 0){
system.debug('executeLogic --- customs.size() --- : '+customs.size());
update customs;
}
} catch (Exception e) {
System.debug(e.getMessage());
}
}
public static CronTrigger getCronTrigger1(String jobName) {
List<CronTrigger> result = [
select Id
from CronTrigger
where CronJobDetail.JobType = '7' // JobType(7) = Scheduled Apex
and CronJobDetail.Name =: jobName
limit 1
];
return (result.isEmpty())? null: result[0];
}
}
2.バッチスケジュールサンプル
global class Schedule_sample implements Schedulable {
global void execute(SchedulableContext ctx) {
String soql = 'SELECT Id, SfId__c, Dummy__c, IsProcessed__c FROM customObject__c Where IsProcessed__c = false Order By Id Limit 300';
Batch_sample b = new Batch_sample(soql);
Database.executeBatch(b, 200);
}
}
3.バッチテストクラス
@isTest (SeeAllData = false)
private class TestBatch_sample {
// 同期バッチテスト1 (スケジュールクラステスト)
static testMethod void myUnitTest1() {
List<Account> accs = new List<Account>();
for(Integer i =1; i<=100; i++ ){
Account acc = new Account();
acc.Name = 'test' + String.ValueOf(i);
accs.add(acc);
}
insert accs;
List<Account> accList = new List<Account>();
accList = [SELECT Id From Account];
List<customObject__c> customs = new List<customObject__c>();
for(Integer i =0; i<100; i++ ){
customObject__c custom = new customObject__c();
custom.SfId__c = String.ValueOf(accList.get(i).Id);
custom.Dummy__c = true;
custom.IsProcessed__c = false;
customs.add(custom);
}
insert customs;
test.startTest();
ID pid = System.schedule('TestBatch_sample', Datetime.now().addMinutes(2).format('0 mm HH dd MM ? yyyy'), new Schedule_sample());
test.stopTest();
}
}
SalesforceのSOQL比較演算子について共有します。
SELECT Name FROM Account WHERE BillingState IN ('California', 'New York')
SELECT AccountId, FirstName, lastname FROM Contact WHERE lastname LIKE 'appl%'
SELECT Name FROM Account WHERE BillingState NOT IN ('California', 'New York')
SELECT Id FROM Account WHERE Id NOT IN ( SELECT AccountId FROM Opportunity WHERE IsClosed = false )
SELECT Id, Name FROM Account WHERE Parent.Name = 'myaccount'
Account A = new Account(Name='xxx'); insert A; Account B; // A simple bind B = [SELECT Id FROM Account WHERE Id = :A.Id]; // A bind with arithmetic B = [SELECT Id FROM Account WHERE Name = :('x' + 'xx')]; String s = 'XXX'; // A bind with expressions B = [SELECT Id FROM Account WHERE Name = :'XXXX'.substring(0,3)]; // A bind with an expression that is itself a query result B = [SELECT Id FROM Account WHERE Name = :[SELECT Name FROM Account WHERE Id = :A.Id].Name]; Contact C = new Contact(LastName='xxx', AccountId=A.Id); insert new Contact[]{C, new Contact(LastName='yyy', accountId=A.id)}; // Binds in both the parent and aggregate queries B = [SELECT Id, (SELECT Id FROM Contacts WHERE Id = :C.Id) FROM Account WHERE Id = :A.Id]; // One contact returned Contact D = B.Contacts; // A limit bind Integer i = 1; B = [SELECT Id FROM Account LIMIT :i]; // An OFFSET bind Integer offsetVal = 10; List<Account> offsetList = [SELECT Id FROM Account OFFSET :offsetVal]; // An IN-bind with an Id list. Note that a list of sObjects // can also be used--the Ids of the objects are used for // the bind Contact[] cc = [SELECT Id FROM Contact LIMIT 2]; Task[] tt = [SELECT Id FROM Task WHERE WhoId IN :cc]; // An IN-bind with a String list String[] ss = new String[]{'a', 'b'}; Account[] aa = [SELECT Id FROM Account WHERE AccountNumber IN :ss]; // A SOSL query with binds in all possible clauses String myString1 = 'aaa'; String myString2 = 'bbb'; Integer myInt3 = 11; String myString4 = 'ccc'; Integer myInt5 = 22; List<List<SObject>> searchList = [FIND :myString1 IN ALL FIELDS RETURNING Account (Id, Name WHERE Name LIKE :myString2 LIMIT :myInt3), Contact, Opportunity, Lead WITH DIVISION =:myString4 LIMIT :myInt5];
List keyAccounts;
「WHERE ID IN :keyAccounts」
SalesforceでVisualForce、Apex で現在のユーザ情報取得方法を共有します。
1. VisualForceで現在のユーザ情報取得方法
<apex:page> <h1>Congratulations</h1> This is your new Apex Page <p>The current company name for this user is: {!$User.CompanyName}</p> <p>Is the user active? {!$User.isActive}</p> </apex:page>
2. Apex で現在のユーザ情報取得方法
String result = UserInfo.getLocale(); System.assertEquals('en_US', result);
Salesforceでよく使われているStandardControllerとStandardSetController使い分けについて共有する。
1.カスタムボタンでの使い分け
表示ボタン : StandardController
リストビューボタン : StandardSetController
※表示ボタン : 詳細画面に配置して使用
※リストビューボタン : 関連リストのメニューに配置して使用
2.Visualforceとコントローラでの使い分け
StandardController の場合
・Visualforce page
<apex:page standardController="Account" extensions="myControllerExtension"> {!greeting} <p/> <apex:form> <apex:inputField value="{!account.name}"/> <p/> <apex:commandButton value="Save" action="{!save}"/> </apex:form> </apex:page>
・コントローラ
public class myControllerExtension { private final Account acct; // The extension constructor initializes the private member // variable acct by using the getRecord method from the standard // controller. public myControllerExtension(ApexPages.StandardController stdController) { this.acct = (Account)stdController.getRecord(); } public String getGreeting() { return 'Hello ' + acct.name + ' (' + acct.id + ')'; } }
StandardSetController
・Visualforce Page
<apex:page controller="opportunityList2Con"> <apex:pageBlock> <apex:pageBlockTable value="{!opportunities}" var="o"> <apex:column value="{!o.Name}"/> <apex:column value="{!o.CloseDate}"/> </apex:pageBlockTable> </apex:pageBlock> </apex:page>
・コントローラ
public class opportunityList2Con { // ApexPages.StandardSetController must be instantiated // for standard list controllers public ApexPages.StandardSetController setCon { get { if(setCon == null) { setCon = new ApexPages.StandardSetController(Database.getQueryLocator( [SELECT Name, CloseDate FROM Opportunity])); } return setCon; } set; } // Initialize setCon and return a list of records public List<Opportunity> getOpportunities() { return (List<Opportunity>) setCon.getRecords(); } }
SalesforceのClassicでOnClick JavaScriptボタンをLightning Experienceに移行する際にその方法の一つを共有します。
1.詳細ボタンの場合
Classic:
window.open(“/apex/Test_VF?id=”+ ids, “_blank”);
Lightning Experience:
window.location.href = '/apex/Test_VF?id={!ids}';
2.リストビューボタンの場合
Classic:
var ids = {!GETRECORDIDS($ObjectType.Test__c)};
if (ids.length === 0) {
alert('None');
} else {
var test_url = '{!URLFOR("/apex/Test_VF?id=")}' ;
window.open(test_url + ids, 'Test');
}
Lightning Experience:
<script type="text/javascript">
var __sfdcSessionId = '{!GETSESSIONID()}';
</script>
<script src="../../soap/ajax/44.0/connection.js" type="text/javascript"></script>
<script src="../../soap/ajax/44.0/apex.js" type="text/javascript"></script>
<script type="text/javascript">
var ids = "<apex:repeat value="{!selected}" var="obj">{!obj.Id},</apex:repeat>".slice(0,-1).split(',');
if (ids[0] == "") {
alert('None');
history.back();
} else {
window.location.href = '/apex/Test_VF?id='+ids;
}
</script>