目次
-
sfdxとmetadata
-
LWC関連
-
API関連
-
apex関連
-
SFDX Bug関連
- 2022/10時点のbug:
sfdx deployコマンドでdeploy途中妙に組織と繋がらないエラーが出て、でもsf組織のリリース記録を観ると、deployが出来ている;ならこのエラーがsfdxのbugかも知れない 参考URL
- 2022/10時点のbug:
salesforce inspectorでmetadata取得
- Salesforce inspectorをChromeに追加
- 目標sf組織にログイン(システム管理者権限ユーザ)、salesforce inspectorを開く、右上のこのアイコン
- 「download metadata」をクリック、次の画面でdownloadしたいコンポーネントを選び、「download metadata」をクリック、そしてdownload完了後「save download metadata」をクリック
- download出来たzipファイルの中、package.xmlを取り出し、目標sf組織に繋ぐvscodeのsfプロジェクトフォルダに入れ、package.xml右クリックし、「Retrieve Source in Manifest from Org」をクリック、そしてpackage.xml中のコンポーネントはdownloadされる。
DevToolsについて
salesforceライセンスとオブジェクト権限
lwc基礎tipsと開発環境設定関連
- lwcがブラウザ側のCaching機能を使わ無い場合、クイック検索 →セッションの設定 →「キャッシュ」下の「パフォーマンスを向上させるためにブラウザの安全で永続的なキャッシュを有効にする」をoffにする。
- sf環境のデバッグモードを有効したい場合、クイック検索 →デバッグモード →適用したいユーザ左にチェック入れ、「有効化」を押す。
- lwc中apex使わず直接レコード操作行うには、jsコードに
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
をimportし、const FIELD_IN_JSCODE = 'OBJECTNAME.FIELDNAME';
でjsコード中のフィールとを定義、const
の使用でフィールドを変更不可にする。 - lwcが埋められたレコードのrecordIDを取得には、
@api recordId;
をjsコードに埋める。 - lwc画面に、データの取得を確定後、該当lwcを表示させるには
<template if:true={JS_FIELD.data}></template>
で、JS_FIELD取得出来た場合のみ、template内部の画面を表示する。 - html側でjsからのレコードを反復処理の場合
<template for:each={js_field} for:item="property_name">
を使用、じゃ側のコードは以下の様に:js側よく使う、命令型apexでレコードlistをjsフィールド渡す方法loadRecords() { getAllRecords() //ここは大体apexメソッド使用し、レコードlist取得 .then(result => { this.records = result; }) .catch(error => { this.error = error; }); }
.data
成功結果と.error
失敗結果両方含まれているため、<template if:true={records}>
で画面表示コントロールしたい場合、<template if:true={records.data}>
に置き換える。js側よく使う、ワイヤードapexでレコードlistをjsフィールド渡す方法@wire(getAllRecords) records;
lwcに選択リスト
lightning-comboboxコンポーネントで選択リスト実現
//選択リスト表示コントローラー、まずjsコードで表示用list作成、そしてコントローラーをtrueにする
<lightning-layout-item size="6" padding="around-small">
<template if:true={loaded}>
<lightning-combobox
name="machine_code"
label="カメラの機器コード"
value={value}
placeholder="カメラの機器コードを選び下し"
options={options}
onchange={handleChange}
></lightning-combobox>
</template>
</lightning-layout-item>
import { LightningElement, api, wire, track } from "lwc";
import getProductNoSmartVCAList from "@salesforce/apex/RMS_RegisterServiceCtrl.getProductNoSmartVCAList";
export default class RmsRegisterServiceCameraInf extends LightningElement {
//カメラの機器コード選択リスト表示内容保存リスト
@track machineCodeList = [];
//カメラの機器コード選択リスト表示コントローラー
loaded = false;
//「カメラの機器コード」と「smartVCA対応」数値紐つくためのmap
productNoSmartVCAMap = new Map();
//カメラの機器コード選択リスト結果保存用テキスト
machineCode = '';
//選択されたカメラの機器コードに該当する「smartVCA対応」保存用数値
smartVCA = 1;
// ApexからList<ProductNoSmartVCA>受け取る
@wire(getProductNoSmartVCAList)
wiredLabels({error, data}){
if(data){
for (var key in data) {
//カメラの機器コード選択リストに内容追加
this.machineCodeList.push({
label: data[key].productNo,
value: data[key].productNo
//value如果有重复的话会导致label没法正确选择;value如果是数字会导致选择之后没有显示被选项;;lightning-combobox label是选项上方的标题,每次选完有变化的是value;不在html加上<template if:true={loaded}>会导致html不能加载lightning-combobox的list
//value: data[key].smartVCA.toString()
});
//「カメラの機器コード」と「smartVCA対応」数値を紐つくためのmapに内容追加
this.productNoSmartVCAMap.set(
data[key].productNo,
data[key].smartVCA
);
};
//カメラの機器コード選択リスト表示出来る様にする
this.loaded = true;
}
if(error){
this.selectOptions = undefined;
this.error=error;
}
}
//カメラの機器コード選択リストに表示内容を渡す
get options() {
return this.machineCodeList;
}
//カメラの機器コード選択後、選択結果と紐付いた「smartVCA対応」数値を保存
handleChange(event){
this.machineCode = event.target.value;
this.smartVCA = this.productNoSmartVCAMap.get(this.machineCode);
console.log('Value :'+ this.machineCode)
//console.log('options.label :'+ event.target.options.find(opt => opt.value === event.detail.value).label)
//this.machineCode = event.target.value;
console.log('smartVCA :'+ this.smartVCA)
}
//「smartVCA対応」数値が”0”(該当カメラにはAI対応機能がない)の場合、連動項目に”0”を固定
get setDisabled() {
//console.log("machine_code" + this.template.querySelector('[data-id="machine_code"]').options)
return !(this.smartVCA != 0);
}
/* 画面入力を監視し、入力値に合わせて、disablesdを更新
machineCode;
machine_codeChange(event){
this.machineCode = this.template.querySelector('[data-id="machine_code"]').value;
}
get setDisabled() {
return !(this.machineCode != 'FD9166-HN');
}
*/
注意点:
- htmlに渡す項目は
@track
の宣言が必要、js内部のみ使う項目にはアノテーションが不要、でも引用の時、this.
の宣言が必要 - htmlで選択listを実現したいとき、listを作成し、表示用の”label”と値”value”を設置し、このlistをhtmlに渡す。ただし、valueは重複したり、数字になった利すると、html側”label”の表示が消えたら、”value”との組み合わせがズレたりするので、そこが注意必要。
- html側にjsの項目を渡すとき、js getメソッドを用意し、returnで項目を渡す。html側では
{項目}
パラメータで項目を引き取る。選択リストの場合、html側はoptionsでリストを取得、そしてvalueで選択された値を保存。 - html項目が変わったとき、jsにそれを合わせて処理を追加の場合、html側目標項目にonchangeを追加し、js側で
onchange項目(event)
メソッドを作成 - ページを開く時、html側でjsの処理が終わった後、その結果を表示したい場合、html項目に
<template if:true={loaded}>
を追加、そしてjsで先ずloadedをfalseにし、該当処理が終わった後、loadedをtrueに変える
public class ProductNoSmartVCA {
@AuraEnabled
public String productNo { get; set; }
@AuraEnabled
public Decimal smartVCA { get; set; }
public ProductNoSmartVCA() {
}
}
/**
* デバイスマスタID(device_mst_id)で
* 機器マスタ(DEVICE_MST)型番(product_no)と
* Sightsカメラ機能マスタ(SIGHTS_CAMERA_FUNCTION_MST)smartVCA対応(smart_vca)
* を紐つく。
*
* @param null
* @return List<ProductNoSmartVCA>
*/
@AuraEnabled(cacheable=true)
public static List<ProductNoSmartVCA> getProductNoSmartVCAList() {
//List<Map<Integer,Integer>> productNoSmartVCAList = new List<Map<Integer, Integer>>();
//Map<String, Integer> productNoSmartVCAMap = new Map<String, Integer>();
List<ProductNoSmartVCA> productNoSmartVCAList = new List<ProductNoSmartVCA>();
//ProductNoSmartVCA productNoSmartVCA = new ProductNoSmartVCA();
List<DEVICE_MST__x> deviceMSTList = [
SELECT
device_mst_id__c,
product_no__c
FROM DEVICE_MST__x
order by device_mst_id__c
];
List<SIGHTS_CAMERA_FUNCTION_MST__x> sightsCameraFunctionMSTList = [
SELECT
device_mst_id__c,
smart_vca__c
FROM SIGHTS_CAMERA_FUNCTION_MST__x
order by device_mst_id__c
];
for(Integer i = 0; i < deviceMSTList.size(); i++) {
ProductNoSmartVCA productNoSmartVCA = new ProductNoSmartVCA();
productNoSmartVCA.productNo = deviceMSTList[i].product_no__c;
productNoSmartVCA.smartVCA = sightsCameraFunctionMSTList[i].smart_vca__c;
productNoSmartVCAList.add(productNoSmartVCA);
//productNoSmartVCAList.add(new Map<Integer, Integer> {'label'=>deviceMSTList[i].product_no__c, 'value'=>sightsCameraFunctionMSTList[i].smart_vca__c});
}
return productNoSmartVCAList;
}
/**
* デバイスマスタ(DEVICE_MST)テーブルのレコードlistを取得
*
* @param null
* @return List<DEVICE_MST__x>
*/
/*
@AuraEnabled(cacheable=true)
public static List<DEVICE_MST__x> getDeviceMSTList() {
return [
SELECT
device_mst_id__c,
product_no__c //,
//AI_Flag__c
FROM DEVICE_MST__x
order by product_no__c
];
}
*/
/**
* 申込情報登録時、カメラ情報「カメラの機器コード」項目で「デバイスマスタ(DEVICE_MST)テーブル」を検索、「AI設定の有/無フラグ」項目を返す
*
* @param string 画面で入力されたカメラの機器コード
* @return int AI設定の有/無フラグ
*/
/*
public static Integer getCameraAIConfigFlag(String machineCode) {
System.debug('★★★★★ getCameraAIConfigFlag ★★★★★');
System.debug('★' + 'カメラの機器コード : ' + machineCode);
List<DEVICE_MST__x> deviceMSTList = [
SELECT
device_mst_id__c
, AI_Flag__c
FROM
DEVICE_MST__x
WHERE
product_no__c = :machineCode
];
System.debug('★★★★★ getCameraAIConfigFlag ★★★★★');
System.debug('★' + '機器マスタList.size() : ' + deviceMSTList.size());
if (deviceMSTList.size() == 1) {
return deviceMSTList[0].AI_Flag__c;
} else if (eviceMSTList.size() < 1) {
//該当カメラの機器コードは未登録
return 1;
} else if (eviceMSTList.size() > 1) {
//該当カメラの機器コードは重複登録
return 1;
}
}
*/
注意点:
- apexのsoqlでは普通のSQLみたいのinner join機能がないため、一つのkey columnで二つのオブジェクトtableを結合できない。
- ループ内soqlの使用を避けるべき
- そのため、オブジェクトlistをそれぞれ単独で取得、この時もし、key columnが一意の場合、order by 「key column」で二つのオブジェクトリスト順番を一致化出来る。
- jsコードに検索結果を返す時、カスタムオブジェクトのlistを返した方が操作し易い
lwcページでレコード作成
apexコードは使わず、直接レコードの作成や変更できるが、ロジックの整理が困難なので、lwcを純粋のページにし、ダータベースの操作やロジックをapexで行った方がいい
account(取引先)レコード作成を例に、
<template>
<lightning-card>
<lightning-record-form
object-api-name={objectApiName}
fields={fields}
onsuccess={handleSuccess}>
</lightning-record-form>
</lightning-card>
</template>
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
export default class AccountCreator extends LightningElement {
objectApiName = ACCOUNT_OBJECT;
fields = [NAME_FIELD, REVENUE_FIELD, INDUSTRY_FIELD];
handleSuccess(event) {
const toastEvent = new ShowToastEvent({
title: "Account created",
message: "Record ID: " + event.detail.id,
variant: "success"
});
this.dispatchEvent(toastEvent);
}
}
onsuccess={handleSuccess}
はレコード作成が成功の時、図の様なポップアップが表示される。表示内容詳細はhandleSuccess(event)
で設定
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
</targets>
</LightningComponentBundle>
今回はapp内でlwcページを表示する為、xmlファイルに<target>lightning__AppPage</target>
を追加。lwcページ表示出来る様に、該当lwcをオブジェクトの編集ページ、またAPPページに入れる必要がある。APPページの新規方法は:クイック検索「Lightning アプリケーションビルダー」→ 「新規」→ 「アプリケーションページ」。このAPPページはsfの「アプリケーションランチャー」で検索出来る。
lwcとapex使ってデータ操作
apexメソッドに@AuraEnabled
をつけてLWCにも使用出来る様にする。
- そのうち、
(cacheable=true)
をつけると、メソッドがキャッシュ可能なる。 - この場合、データ操作言語 (DML) による操作は許可されません。そしてキャッシュが更新されるまで新しく追加または変更されたレコードのバージョンが返されません、手動更新が必要。
- ただ、jsファイルで、命令的なApexコールすると、手動更新がでいない。命令的なApexコール例:
import { LightningElement, api, wire } from 'lwc';
import getContactsBornAfter from '@salesforce/apex/ContactController.getContactsBornAfter';
export default class CallApexImperative extends LightningElement {
@api minBirthDate;
handleButtonClick() {
getContactsBornAfter({ //imperative Apex call
birthDate: this.minBirthDate
})
.then(contacts => {
//code to execute if related contacts are returned successfully
})
.catch(error => {
//code to execute if related contacts are not returned successfully
});
}
}
命令的なApexコールする以外、@wire
を使用したApexコールの場合、以下の例で、account(取引先)のName、AnnualRevenue、Industryを取得し、lwcページで表示する。
public with sharing class AccountController {
@AuraEnabled(cacheable=true)
public static List<Account> getAccounts() {
return [
SELECT Name, AnnualRevenue, Industry
FROM Account
WITH SECURITY_ENFORCED
ORDER BY Name
];
}
}
import { LightningElement, wire } from 'lwc';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
const COLUMNS = [
{ label: 'Account Name', fieldName: NAME_FIELD.fieldApiName, type: 'text' },
{ label: 'Annual Revenue', fieldName: REVENUE_FIELD.fieldApiName, type: 'currency' },
{ label: 'Industry', fieldName: INDUSTRY_FIELD.fieldApiName, type: 'text' }
];
export default class AccountList extends LightningElement {
columns = COLUMNS;
// 結果を accounts プロパティに保存します。操作が成功すると、レコードは accounts.data でアクセス可能になります。失敗するとエラーが account.error に表示されます。
// 命令的なApexコールする場合、「.catch(error =>」を使う必要がある、@wireデコレートの場合、errorプロパティ属性でアクセスできる
@wire(getAccounts)
accounts;
}
<template>
<lightning-card>
<template if:true={accounts.data}>
<lightning-datatable
key-field="Id"
data={accounts.data}
columns={columns}
>
</lightning-datatable>
</template>
</lightning-card>
</template>
コンポーネントをアプリケーションページで使用できるように
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>48.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
</targets>
</LightningComponentBundle>
参考: Apex を使用したデータの操作
サーバエラーの処理
-
@wire
を使用してプロパティをデコレートしている場合、エラーは error プロパティ属性でアクセスできる、エラー情報の取得方法二つある、-
@wire
デコレートされたメソッド結果取得後、errorsのget関数でエラー情報取得
wireApexProperty.jsimport { LightningElement, api, wire } from 'lwc'; // reduceErrors ヘルパー関数を ldsUtils モジュールからインポート import { reduceErrors } from 'c/ldsUtils'; import getContactsBornAfter from '@salesforce/apex/AccountController.getContactsBornAfter'; export default class WireApexProperty extends LightningElement { @api minBirthDate; @wire(getContactsBornAfter, { birthDate: '$minBirthDate' }) contacts; // getter で reduceErrors ヘルパー関数を使用して this.contacts.error の形式を設定し、この関数はエラーオブジェクトを受け取り、発生したすべてのエラーメッセージの配列を返す get errors() { return (this.contacts.error) ? reduceErrors(this.contacts.error) : []; } }
-
@wire
デコレートされたメソッドパラメーターとしてエラー (エラーがあった場合) を含むオブジェクトを受け取る
wireApexFunction.jsimport { LightningElement, api, wire } from 'lwc'; // reduceErrors ヘルパー関数を ldsUtils モジュールからインポート import { reduceErrors } from 'c/ldsUtils'; import getContactsBornAfter from '@salesforce/apex/AccountController.getContactsBornAfter'; export default class WireApexFunction extends LightningElement { @api minBirthDate; // errorsという名前のプロパティを定義 errors; // 結び付けられた関数がエラーを受け取るたびに、reduceErrorsヘルパー関数を使用してエラーの形式を設定し、この関数は発生したすべてのエラーの配列を返す @wire(getContactsBornAfter, { birthDate: '$minBirthDate' }) wiredContacts({data, error}) { if (error) this.errors = reduceErrors(error); } }
-
-
@wire
を使用してプロパティをデコレートし無い、命令的に関数をコールする場合のエラー処理
import { LightningElement, api, wire } from 'lwc';
import { reduceErrors } from 'c/ldsUtils';
import getContactsBornAfter from '@salesforce/apex/AccountController.getContactsBornAfter';
export default class CallApexImperative extends LightningElement {
@api minBirthDate;
//errors という名前のプロパティを定義
errors;
//getContactsBornAfter関数を命令的に呼び出し、関数はpromiseを返す、promiseが解決された場合、contactsを処理;promiseが却下された場合、reduceErrorsヘルパー関数を使用して受け取ったエラーの形式を設定し、errorsプロパティに保存
handleButtonClick() {
getContactsBornAfter({
birthDate: this.minBirthDate
})
.then(contacts => {
// code to execute if the promise is resolved
})
.catch(error => {
this.errors = reduceErrors(error); // code to execute if the promise is rejected
});
}
}
- その中、ldsUtilsのreduceErrorsコンポーネントはヘルパー関数、lwcとして組織にimportできる
- htmlでerrorsの配列を表示するコード、エラーメッセージの配列として表示
<template if:true={errors}>
<p>{errors}</p>
</template>
apexメソッドに、以下のコード入れは、強制的にエラーを作成できる
throw new AuraHandledException('Forced error');
LWCコンポーネントのテストフォルダー
LWCを作成の時__tests__
と言うテストフォルダーが自動生成される、この中にはjsコード為のJestコードを入れる事ができるが、Jestテストではsf組織と接続出来無いため、テスト出来るのはjsのロジックだけです。
このテストフォルダーを無理矢理にsf組織にdeployすると、エラーが発生になる、そのため、ローカル環境い.forceignore
ファイルの下に、**/__tests__/**
を入れる事で、lwcコンポーネントをsf環境へdeployの時、フォルダー内ののテストフォルダーを無視出来る。
参考記事: Jest テストの記述
salesforceデータを読み取りlwcをテストする方法
- 汎用ワイヤーアダプター
詳細は下の 参考記事 を参照 - Lightning データサービス (LDS) ワイヤーアダプター。カスタムオブジェクトや標準オブジェクトにすばやくアクセスできる。
__test__
フォルダ下でテスト用レコードjsonファイルを用意し、sf環境データとして、lwc資材をテスト出来る。詳細は 参考記事 を参照。
テストの時注意必要のは、もしテストコードでjsonレコードファイルを読み込み時、require('./data/getRecord.json');
を使用の場合、lwcテスト画面の①の部分で虫マークをクリック必要がある。もし②の部分の虫マークでテスト始めたら、jsonファイルが見つから無いエラーが出る。
- Apex ワイヤーアダプター
上の「Lightning データサービス (LDS) ワイヤーアダプター」テストに似ているが、apex class使用されたlwcをテストする方法。
参考記事: ワイヤサービスの Jest テストの記述
jsコードのtroubleshooting
-
chrome上devtools開き、「sources」タブに入り、lwc資材のjsコードなら
lighting/n /modules/c
の下にある、もしコードは図の様に1行だけの場合、左下の{}
をクリックし、formattedのコードが表示される。
-
lwcのframeworkコードをdevtoolsから無視させるため、「ignore list」に
/aura_prod.*.js$
と/components/.*.js$
を追加する。
-
devtools使って、コードにbreakpoint入れる操作はUse Breakpointsを参考
子lwcから親lwcとの連動
-
子lwcのボタンクリックで親lwc値をコントロール、コード例の中、「count」値のIncrement(+)とDecrement(-)はこれで操作する。
- 先ず子lwcを親lwcに含まれる、
<c-子lwc名>
下の「numerator.html(親)」を参照。 - そして子jsコードの中に、子画面ボタンと紐付けたメソッドに中に
this.dispatchEvent(new CustomEvent('イベント名'));
で親側で受けたイベントを定義、そして親側の<c-子lwc名>
の中、onイベント名={親ベント名}
で親イベントと紐つく。
下の「numerator.html(親)」と「controls.html(子)」「controls.js(子)」を参照
- 先ず子lwcを親lwcに含まれる、
-
子lwcのイベント中のデータを親lwcに渡す、コード例の中、「count」値のMultiply(x2, x3)はこれで操作する。
- この場合先ず子lwcの中、イベント中のデータを定義、
<lightning-button>
の中data-factor="数値"
を定義、event.target.dataset.factor
でfactorを取得し、CustomEventのdetailに渡すthis.dispatchEvent(new CustomEvent('multiply', { detail: factor }));
コード例の「controls.html(子)」「controls.js(子)」を参照 - そして親側で同じく
onmultiply={handleMultiply}
でイベント受け取り、そすてhandleMultiply(event) { const factor = event.detail;
でevent.detail
を受け取る。ここで注意必要のは、上のボタンクリックの例では、単純に親lwcイベント呼ぶ為、親jsコードにevent
を渡す必要が無いが、データを渡す場合handleMultiply(event)
の様に、event
を渡す必要がある
コード例の「numerator.html(親)」「numerator.js(親)」を参照
- この場合先ず子lwcの中、イベント中のデータを定義、
<template>
<lightning-card title="Numerator" icon-name="action:manage_perm_sets">
<p class="slds-text-align_center slds-var-m-vertical_medium">
Count: <lightning-formatted-number value={counter}></lightning-formatted-number>
</p>
<!-- ここの<c-controls>は”controls”と言うlwcを子として入れる
onadd={handleIncrement}は Count のプラス
onsubtract={handleDecrement}は Count のマイナス
onmultiply={handleMultiply}>は Count の掛け算
-->
<c-controls
class="slds-show slds-is-relative"
onadd={handleIncrement}
onsubtract={handleDecrement}
onmultiply={handleMultiply}>
</c-controls>
</lightning-card>
</template>
import { LightningElement } from 'lwc';
export default class Numerator extends LightningElement {
counter = 0;
handleIncrement() {
this.counter++;
}
handleDecrement() {
this.counter--;
}
handleMultiply(event) {
const factor = event.detail;
this.counter *= factor;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
</targets>
</LightningComponentBundle>
<template>
<lightning-card title="Controls" icon-name="action:upload">
<lightning-layout>
<lightning-layout-item flexibility="auto" padding="around-small">
<lightning-button
label="Subtract"
icon-name="utility:dash"
onclick={handleSubtract}>
</lightning-button>
</lightning-layout-item>
<lightning-layout-item flexibility="auto" padding="around-small">
<lightning-button
label="2"
data-factor="2"
icon-name="utility:close"
onclick={handleMultiply}>
</lightning-button>
<lightning-button
label="3"
data-factor="3"
icon-name="utility:close"
onclick={handleMultiply}>
</lightning-button>
</lightning-layout-item>
<lightning-layout-item flexibility="auto" padding="around-small">
<lightning-button
label="Add"
icon-name="utility:add"
onclick={handleAdd}
icon-position="right">
</lightning-button>
</lightning-layout-item>
</lightning-layout>
</lightning-card>
</template>
import { LightningElement } from 'lwc';
export default class Controls extends LightningElement {
handleAdd() {
this.dispatchEvent(new CustomEvent('add'));
}
handleSubtract() {
this.dispatchEvent(new CustomEvent('subtract'));
}
handleMultiply(event) {
const factor = event.target.dataset.factor;
this.dispatchEvent(new CustomEvent('multiply', {
detail: factor
}));
}
}
<!-- 「x2」[x3]ボタン作成コードを以下のと入れ替え、「x0~6」のボタンを自動生成、ボタンは「botton」lwcで定義 -->
<lightning-layout-item flexibility="auto" padding="around-small" onbuttonclick={handleMultiply}>
<template for:each={factors} for:item="factor">
<c-button
key={factor}
label={factor}
data-factor={factor}
icon="utility:close">
</c-button>
</template>
</lightning-layout-item>
// 「x0~6」のボタンを自動生成
factors = [0,2,3,4,5,6];
<template>
<lightning-button
label={label}
data-factor={label}
icon-name={icon}
onclick={handleButton}>
</lightning-button>
</template>
import { LightningElement, api } from 'lwc';
export default class Button extends LightningElement {
@api label;
@api icon;
handleButton() {
this.dispatchEvent(new CustomEvent('buttonclick',{
// ここの定義は、botton lwcがクリックされた時、controls.htmlの「onbuttonclick={handleMultiply}」を呼ぶ
bubbles: true
}));
}
}
親lwcから子lwcとの連動
// 先ず本来のnumerator.jsのcounterに @api デコレーターをつける、以下の様に
@api counter = 0;
<template>
<lightning-card title="Augmentor" icon-name="action:download">
<lightning-layout>
<lightning-layout-item flexibility="auto" padding="around-small">
<lightning-input
label="Set Starting Counter"
type="number"
min="0"
max="1000000"
value={startCounter}
onchange={handleStartChange}>
</lightning-input>
</lightning-layout-item>
</lightning-layout>
<!-- ここでnumeratorを子コンポーネントとして入れる、
そしてnumerator.jsのcounterプロパティが @api がある為、startCounterの値を渡せる -->
<c-numerator
class="slds-show slds-is-relative"
counter={startCounter}>
</c-numerator>
</lightning-card>
</template>
import { LightningElement } from 'lwc';
export default class Augmentor extends LightningElement {
startCounter = 0;
handleStartChange(event) {
// ここのparseIntは入力string値を10ベースのintに変換。ここでstring入れる時、keyboard入力は「英字」に必要がある、「日本語」の場合表示され無い
this.startCounter = parseInt(event.target.value);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>55.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
</targets>
</LightningComponentBundle>
// 以下メソッド追加、numeratorのcounterプロパティを直接1000000になる
@api
maximizeCounter() {
this.counter += 1000000;
}
<!-- このボタンをlightning-inputの後ろに入れ -->
<lightning-button
class="slds-var-p-vertical_xx-small"
label="Add 1m To Counter"
onclick={handleMaximizeCounter}>
</lightning-button>
// 以下のメソッドを追加、template.querySelectorでnumeratorコンポーネントを取得し、そのmaximizeCounter()を呼ぶ
handleMaximizeCounter() {
this.template.querySelector('c-numerator').maximizeCounter();
}
- publicのgetterとsetterで子のprivateプロパティを制御
<!-- 以下内容を<P>countの前に追加 -->
<p class="slds-text-align_center slds-var-m-vertical_medium">
Prior Count: <lightning-formatted-number value={priorCount}></lightning-formatted-number>
</p>
// @api counter = 0; counterをコメントアウト
// 以下内容を追加
// 現在の数値表す_currentCountと前一個の数値表示用priorCountを用意、そして本来public counterプロパティに対する取得、と変更をpublicメソッドに変える、jsの中にparameterとmethod調用方法がほぼ同じ
_currentCount = 0;
priorCount = 0;
@api
get counter() {
return this._currentCount;
}
set counter(value) {
this.priorCount = this._currentCount;
this._currentCount = value;
}
REST API
参考資料:REST API を使用する
http calloutコード書き方
-
先ずアクセスしたいエンドポイントをsf環境に追加のは必要、「設定」→「リモートサイトの設定」→「新規」→end point情報追加
-
apexコードでそのend pointにrequest投げる。
コードtestの時は「開発者コンソール」「Debug (デバッグ)」「Open Execute Anonymous Window (実行匿名ウィンドウを開く)」でテスト用ウインドを開き。「open log」にチェック入れexecuteすると、直接実行logが表示される
そして「debug only」にチェック入れると、”System.debug()”の内容だけが表示される
Http http = new Http();
HttpRequest request = new HttpRequest();
//end point設置
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
//requestメソッド設置
request.setMethod('GET');
HttpResponse response = http.send(request);
// If the request is successful, parse the JSON response.
// 返されるresponseは {"animals":["majestic badger","fluffy bunny","scary bear","chicken"]} の様なjsonコード
if(response.getStatusCode() == 200) {
// Deserialize the JSON string into collections of primitive data types.
Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
// Cast the values in the 'animals' key as a list
List<Object> animals = (List<Object>) results.get('animals');
//”System.debug()”を使ってlogに出力文字は”|DEBUG|”が付いている
System.debug('Received the following animals:');
for(Object animal: animals) {
System.debug(animal);
}
}
- http postメソッドでrequest投げる時、投げるjsonコードの”Content-Type”の入力も必要
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('POST');
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
// Set the body as a JSON object
request.setBody('{"name":"mighty moose"}');
HttpResponse response = http.send(request);
// Parse the JSON response
if(response.getStatusCode() != 201) {
System.debug('The status code returned was not expected: ' + response.getStatusCode() + ' ' + response.getStatus());
} else {
System.debug(response.getBody());
}
- jsonをオブジェクトへの解析は JSON2Apex で可能
http callout testコード書き方
apex testコードでは本番のend pointへのアクセス出来ないが、予想の正しいend pointからのresponseを作成し、疑似コールアウトをcalloutに返す。この場合、apex test classTest.setMock
メソッドを使う
- classでhttp calloutコードの書き方
public class AnimalsCallouts {
public static HttpResponse makeGetCallout() {
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('GET');
HttpResponse response = http.send(request);
// If the request is successful, parse the JSON response.
if(response.getStatusCode() == 200) {
// Deserializes the JSON string into collections of primitive data types.
Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
// Cast the values in the 'animals' key as a list
List<Object> animals = (List<Object>) results.get('animals');
System.debug('Received the following animals:');
for(Object animal: animals) {
System.debug(animal);
}
}
return response;
}
public static HttpResponse makePostCallout() {
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('POST');
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
request.setBody('{"name":"mighty moose"}');
HttpResponse response = http.send(request);
// Parse the JSON response
if(response.getStatusCode() != 201) {
System.debug('The status code returned was not expected: ' +
response.getStatusCode() + ' ' + response.getStatus());
} else {
System.debug(response.getBody());
}
return response;
}
}
- 疑似コールアウトは二つの方法で作成できる、一つは静的リソース、もう一つは疑似インターフェース
- 静的リソースの場合、まずテスト用の静的リソース作成、「開発者コンソール」→[File (ファイル)」→「New (新規)」→「Static Resource (静的リソース)」、「MIME タイプ」に”text/plain”を選択。入れるresponse bodyは1行に収まるべき。
- 疑似インターフェースの場合、まず上静的リソースの代わり、responseを返すapex classの作成が必要(今回は AnimalsHttpCalloutMock を作成)、そして
Test.setMock(HttpCalloutMock.class, mock);
をTest.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());
に変える。
@isTest
private class AnimalsCalloutsTest {
@isTest
static void testGetCallout() {
// Create the mock response based on a static resource
StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
// 取得したい静的リソース名を入れる
mock.setStaticResource('GetAnimalResource');
mock.setStatusCode(200);
mock.setHeader('Content-Type', 'application/json;charset=UTF-8');
// 本来end pointのresponseを疑似コールアウトに入れ替える
Test.setMock(HttpCalloutMock.class, mock);
// 実際responseを投げるapex http calloutメソッドを呼ぶ
HttpResponse result = AnimalsCallouts.makeGetCallout();
// Verify mock response is not null
System.assertNotEquals(null,result, 'The callout returned a null response.');
// Verify status code
System.assertEquals(200,result.getStatusCode(), 'The status code is not 200.');
// Verify content type
System.assertEquals('application/json;charset=UTF-8',
result.getHeader('Content-Type'),
'The content type value is not expected.');
// Verify the array contains 3 items
Map<String, Object> results = (Map<String, Object>)
JSON.deserializeUntyped(result.getBody());
List<Object> animals = (List<Object>) results.get('animals');
System.assertEquals(3, animals.size(), 'The array should only contain 3 items.');
}
}
@isTest
static void testPostCallout() {
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());
// This causes a fake response to be sent
// from the class that implements HttpCalloutMock.
HttpResponse response = AnimalsCallouts.makePostCallout();
// Verify that the response received contains fake values
String contentType = response.getHeader('Content-Type');
System.assert(contentType == 'application/json');
String actualValue = response.getBody();
System.debug(response.getBody());
String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}';
System.assertEquals(expectedValue, actualValue);
System.assertEquals(200, response.getStatusCode());
}
@isTest
global class AnimalsHttpCalloutMock implements HttpCalloutMock {
// Implement this interface method
global HTTPResponse respond(HTTPRequest request) {
// Create a fake response
HttpResponse response = new HttpResponse();
response.setHeader('Content-Type', 'application/json');
response.setBody('{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}');
response.setStatusCode(200);
return response;
}
}
- テストの時「Always Run Asynchronously (常に非同期に実行)」の選択について補充説明。非同期 Apexは、複数の処理を後で別個のスレッドで実行するために使用します。非同期処理とは、ユーザーがタスクの終了まで待つことなく、タスクを「バックグラウンドで」実行するプロセスまたは機能です。そして、このオプションが有効になっていない場合は、1 つのクラスのテストのみが含まれるテスト実行が同期して実行されます。複数のクラスが含まれるテスト実行は、このオプションが有効になっているかどうかに関係なく非同期に実行されます。
- 参考記事:Apex REST コールアウト HttpCalloutMock インターフェースの実装による HTTP コールアウトのテスト
apex web サービス
- Apex クラスを rest Web サービスとして公開。sample code中Apex REST のベースエンドポイントは、
https://<sf組織インスタンス>.my.salesforce.com/services/apexrest/Account/*
です。URL 対応付けでは大文字と小文字が区別される。
// 一個のREST エンドポイント(apex class)中に、get、post、putなどのメソッドは一回のみ使用できる
@RestResource(urlMapping='/Account/*')
global with sharing class MyRestResource {
@HttpGet
global static Account getRecord() {
// Add your code
}
}
- 作ったrest end pointを動作確認の時、workBenchを使用できる。画面に入って、「Environment (環境)」で環境選択(Playgroundの場合は”Production (本番)”)、環境に合わせたアカウントを登録、そして「utilities (ユーティリティ)」を”REST Explorer”選択、メソッドを選び、相対パス
/services/apexrest/<apexコードに合わせ>
を入れる。実行結果を見たい場合「show/hide raw response」をクリック
@RestResource(urlMapping='/Cases/*')
global with sharing class CaseManager {
@HttpGet
global static Case getCaseById() {
RestRequest request = RestContext.request;
// grab the caseId from the end of the URL
String caseId = request.requestURI.substring(
request.requestURI.lastIndexOf('/')+1);
Case result = [SELECT CaseNumber,Subject,Status,Origin,Priority
FROM Case
WHERE Id = :caseId];
return result;
}
@HttpPost
global static ID createCase(String subject, String status,
String origin, String priority) {
Case thisCase = new Case(
Subject=subject,
Status=status,
Origin=origin,
Priority=priority);
insert thisCase;
return thisCase.Id;
}
@HttpDelete
global static void deleteCase() {
RestRequest request = RestContext.request;
String caseId = request.requestURI.substring(
request.requestURI.lastIndexOf('/')+1);
Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
delete thisCase;
}
@HttpPut
global static ID upsertCase(String subject, String status,
String origin, String priority, String id) {
Case thisCase = new Case(
Id=id,
Subject=subject,
Status=status,
Origin=origin,
Priority=priority);
// Match case by Id, if present.
// Otherwise, create new case.
upsert thisCase;
// Return the case ID.
return thisCase.Id;
}
@HttpPatch
global static ID updateCaseFields() {
RestRequest request = RestContext.request;
String caseId = request.requestURI.substring(
request.requestURI.lastIndexOf('/')+1);
Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
// Deserialize the JSON string into name-value pairs
Map<String, Object> params = (Map<String, Object>)JSON.deserializeUntyped(request.requestbody.tostring());
// Iterate through each parameter field and value
for(String fieldName : params.keySet()) {
// Set the field and value on the Case sObject
thisCase.put(fieldName, params.get(fieldName));
}
update thisCase;
return thisCase.Id;
}
}
- workBench以外、コマンドラインcURLを使って、end pointアクセスできる。windowsやMacやLinuxのコマンドを入力前、目標組織に接続用アプリ作成必要、「アプリケーションマネージャ」→「新規接続アプリケーション」→「OAuth 設定の有効化」チェック入れ →「コールバック URL」に”https://”入れ →「選択した OAuth 範囲」に”選択した OAuth 範囲”入れる。
もし下のコマンド入れて、{ "error" : "invalid_grant", "error_description" : "authentication failure" *Connection #0 to host login.salesforce.com left intact }
が出たら、接続アプリ「manage」で「IP 制限の緩和」を”IP 制限の緩和”に変えたら多分できるようになる。
curl -v https://login.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=<your_consumer_key>" -d "client_secret=<your_consumer_secret>" -d "username=<your_username>" -d "password=<your_password_and_security_token>" -H "X-PrettyPrint:1"
- 上のコマンドで「access_token」取得した後、Getメソッドで取得したいレコードIDとユーザ認証用のaccess_tokenをコマンドに入れる。ここの「yourInstance」は組織の識別名、例えPlaygroundホームページURLは
https://resilient-fox-gcanmz-dev-ed.lightning.force.com/lightning/page/home
の場合、yourInstance == resilient-fox-gcanmz-dev-ed
curl https://<yourInstance>.my.salesforce.com/services/apexrest/Cases/<Record_ID> -H "Authorization: Bearer <access_token>" -H "X-PrettyPrint:1"
- Apex RESTテストコード書き方
@IsTest
private class CaseManagerTest {
@isTest static void testGetCaseById() {
Id recordId = createTestRecord();
// Set up a test request
RestRequest request = new RestRequest();
request.requestUri =
'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
+ recordId;
request.httpMethod = 'GET';
RestContext.request = request;
// Call the method to test
Case thisCase = CaseManager.getCaseById();
// Verify results
System.assert(thisCase != null);
System.assertEquals('Test record', thisCase.Subject);
}
@isTest static void testCreateCase() {
// Call the method to test
ID thisCaseId = CaseManager.createCase(
'Ferocious chipmunk', 'New', 'Phone', 'Low');
// Verify results
System.assert(thisCaseId != null);
Case thisCase = [SELECT Id,Subject FROM Case WHERE Id=:thisCaseId];
System.assert(thisCase != null);
System.assertEquals(thisCase.Subject, 'Ferocious chipmunk');
}
@isTest static void testDeleteCase() {
Id recordId = createTestRecord();
// Set up a test request
RestRequest request = new RestRequest();
request.requestUri =
'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
+ recordId;
request.httpMethod = 'DELETE';
RestContext.request = request;
// Call the method to test
CaseManager.deleteCase();
// Verify record is deleted
List<Case> cases = [SELECT Id FROM Case WHERE Id=:recordId];
System.assert(cases.size() == 0);
}
@isTest static void testUpsertCase() {
// 1. Insert new record
ID case1Id = CaseManager.upsertCase(
'Ferocious chipmunk', 'New', 'Phone', 'Low', null);
// Verify new record was created
System.assert(Case1Id != null);
Case case1 = [SELECT Id,Subject FROM Case WHERE Id=:case1Id];
System.assert(case1 != null);
System.assertEquals(case1.Subject, 'Ferocious chipmunk');
// 2. Update status of existing record to Working
ID case2Id = CaseManager.upsertCase(
'Ferocious chipmunk', 'Working', 'Phone', 'Low', case1Id);
// Verify record was updated
System.assertEquals(case1Id, case2Id);
Case case2 = [SELECT Id,Status FROM Case WHERE Id=:case2Id];
System.assert(case2 != null);
System.assertEquals(case2.Status, 'Working');
}
@isTest static void testUpdateCaseFields() {
Id recordId = createTestRecord();
RestRequest request = new RestRequest();
request.requestUri =
'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
+ recordId;
request.httpMethod = 'PATCH';
request.addHeader('Content-Type', 'application/json');
request.requestBody = Blob.valueOf('{"status": "Working"}');
RestContext.request = request;
// Update status of existing record to Working
ID thisCaseId = CaseManager.updateCaseFields();
// Verify record was updated
System.assert(thisCaseId != null);
Case thisCase = [SELECT Id,Status FROM Case WHERE Id=:thisCaseId];
System.assert(thisCase != null);
System.assertEquals(thisCase.Status, 'Working');
}
// Helper method
static Id createTestRecord() {
// Create test record
Case caseTest = new Case(
Subject='Test record',
Status='New',
Origin='Phone',
Priority='Medium');
insert caseTest;
return caseTest.Id;
}
}
apex class通じ、APIをアクセス
-
salesforceのApex class通じ、APIをアクセス
-
次Apex classの中request作成、send()メソッドでresponseを貰う、
Http http = new Http(); HttpRequest request = new HttpRequest(); request.setTimeout(120000); //setEndpoint()で「指定ログイン情報」/「アクセスパス」の形で設置 //以下の例では、「RMS_APIGW」と言う指定ログイン情報のapiをアクセス、apiの具体のパスは:「/devices/cameras/standard_img/approval」 request.setEndpoint( 'callout:RMS_APIGW/api/devices/cameras/standard_img/approval' ); request.setMethod('POST'); request.setHeader('Authorization', IdToken); //受信可能なレスポンスデータのメディアタイプを指定 request.setHeader('Accept', 'application/json'); response = http.send(request); System.debug('★' + 'response : ' + response); return response;
-
apiの設定により、requestにbodyが必要と必要無い場合がある、
- 必要の場合(大体json形式)
String json = '{' + '"項目名1" : "' + 値1 + '",' + '"項目名2" : "' + 値2 + '",' + '"項目名3" : "' + 値3 + '",' + '"サブjson" : {' + '"サブ項目名1" : "' + サブ値1 + '",' + '"サブ項目名2" : "' + サブ値2 + '"' + '}' + '}'; request.setBody(json);
- 必要無い場合(大体pathで情報含む)
例えば:apiの具体のパスは:「/devices/cameras/[項目]/standard_img/approval」の場合、request.setEndpoint()には以下のように設定:request.setEndpoint( 'callout:RMS_APIGW/api/devices/cameras/' + [項目] + '/standard_img/approval' );
- 必要の場合(大体json形式)
salesforceコンポーネントとmetadataの対照
- salesforce資材名
各salesorce資材やカスタマイズ設置が「変更セット」で表示された名前。下のコンポーネント一覧をコピー出来る、そのままexclにペースト、整理できる。
- metaData保存パス:
ローカル環境salesforce project中、資材の保存パス。force-app/main/default/
の下(vscodeの例)
- metaData取得パス:
vscode「org Browser」上salesfroce metadataの保存先(vscodeの例)
- package.xml 項目名:
metadataをdeployや取得用のpackage.xml中で、<name>
中に入れる資材名 package.xml参考ファイル
salesforce資材名 | metaData保存パス | metaData取得パス | package.xml 項目名 | 追加説明 |
---|---|---|---|---|
サイト | custom sites | CustomSite | ||
リモートサイト | remote site settings | RemoteSiteSetting | ||
Aura コンポーネントバンドル | aura | aura components | AuraDefinitionBundle | |
visualforce page | pages | visualforce pages | ApexPage | |
Apex クラス | classes | apex classes | ApexClass | |
Apex トリガ | triggers | apex triggers | ApexTrigger | |
カスタムオブジェクト | objects | custom objects | CustomObject | |
カスタム項目 | objects/オブジェクト名/fields | objects/オブジェクト名/fields | CustomField | custom objectと同時にdownload |
標準項目選択リストカスタマイズ後の値 | standard value sets | StandardValueSet | 参考記事 | |
ページレイアウト | objects/オブジェクト名/layouts | layouts | Layout | record画面のオブジェクトを編集で開く |
lightingページ | flexipages | flexipages | FlexiPage | record画面の編集ページで開く & オブジェクト詳細「Lightning レコードページ」画面 |
リストビュー | objects/オブジェクト名/listViews | custom objects | ListView | custom objectと同時にdownload |
ボタン、リンク、およびアクション | quickActions | QuickAction | record画面のオブジェクトを編集で開く | |
レコードタイプ | objects/オブジェクト名/recordTypes | custom objects | RecordType | |
入力規則 | objects/オブジェクト名/validationRules | custom objects | ValidationRule | |
カスタムメタデータ型 | custom objects | CustomObject | 参考記事 | |
カスタムメタデータ型の項目 | fields | custom objects | CustomField | custom objectと同時にdownload |
カスタムメタデータ型のレコード | custom metadatas | CustomMetadata | 参考記事 | |
標準項目タブと表示ラベルの名称変更 | objectTranslations | Custom Object Translation | CustomObjectTranslation | metadataをdeployの時オブジェクトを分けてdeployする 参考記事 |
タブ | tabs | custom tabs | CustomTab | |
ケースの割り当てルール | assignmentRules | Assignment Rules | AssignmentRules | metadata通じての直接削除でき無い、環境上手動で必要 |
フロー | flows | flows | Flow | flowsはフロー本体を保存用、flowDefinitionsはバージョン記録用 |
カスタム表示ラベル | labels | custom labels | CustomLabel(CustomLabelsは全部まとめ用) | |
アセットファイル | contentassets | content assets | ContentAsset | |
アプリケーション | applications | custom applications | CustomApplication | |
メールテンプレート | email templates | EmailTemplate | ||
ワークフローメールアラート | workflows | workflows | Workflow | |
レポート作成スナップショット | analyticSnapshots | Analytic Snapshot | AnalyticSnapshot | ”*”で全て選ぶは有効しない |
上記資材がsalesforce画面上のイメージ:
- オブジェクト、「設定」の「オブジェクトマネージャ」画面
- カスタム項目、オブジェクト詳細画面に入った後、「項目とリレーション」をクリック
- アクション、オブジェクト詳細画面に入った後、「ボタン、リンク、およびアクション」をクリック
- ページレイアウト、オブジェクト詳細画面に入った後、「ページレイアウト」をクリック
- 入力規則、オブジェクト詳細画面に入った後、「入力規則」をクリック
- リストビュー、オブジェクト詳細画面に入った後、「リストビューボタンレイアウト」をクリック
- Apex クラス & Apex トリガ、「設定」の「Apex クラス」と「Apex トリガ」画面
- フロー、「設定」の「フロー」画面
- lightingページ、オブジェクト詳細画面に入った後、「Lightning レコードページ」をクリック
- 標準項目タブと表示ラベルの名称変更、「設定」の「タブと表示ラベルの名称変更」画面、その内「標準タブ」は標準オブジェクトの表示名と項目の表示名をカスタマイズ
- アセットファイル、「設定」の「アセットファイル」画面
- アプリケーション、「設定」の「アプリケーションマネージャ」画面
- タブ、「設定」の「タブ」画面
- カスタム表示ラベル、「設定」の「カスタム表示ラベル」画面
- レポートタイプ、「設定」の「レポートタイプ」画面
- メールテンプレート、「設定」の「Classic メールテンプレート」画面
- Aura コンポーネントバンドル、「設定」の「Lightning コンポーネント」画面
- ワークフローメールアラート、「設定」の「メールアラート」画面
- レポート作成スナップショット、「設定」の「レポート作成スナップショット」画面
metadataでプロファイルをdeploy時の注意点
追加説明:
spring26(約2025年)プロファイル中のアクセス権限まわりが権限セットに移行され、プロファイルはレコードタイプやログインIPなどの権限を持つ 参考URL
結論から言う:
- salesforceはmetadataの形でA環境のプロファイルをB環境に移すのは推奨し無い
- metadataでも、変更セットでも、プロファイルの移行が効率よくないので;
- 効率が良い方法は:まず本番環境で空っぽのプロファイルを作成、そして本番からsandboxを作る。登録時間帯やIPのみこの空っぽプロファイルからコピーを取得し、コントロールする。オブジェクトやシステム権限などは権限セットでコントロール。
- salesforce環境(webページ)でプロファイル設置する時、項目レベルセキュリティーは画面遷移ある為、時間がかかる上、抜け漏れしやすい。でも他の内容は画面遷移がないため、設定の手間が少ない。加えて、プロファイルのmetadataで取得出来る内容は少ない、オフィシャル説明にもハッキリ書いていない。
以上の原因で、metadataでプロファイルをdeployする場合、「項目レベルセキュリティー」と「オブジェクトアクセス」と「タグ」と「アプリ」の設定だけmetadataで取得し、目標環境にdeployして、他の部分は手動で追加。- sfdxでは、”プロファイル”と言うフォルダが無いので、package.xmlでプロファイルのmetadataを取得する、package.xml例↓ 標準オブジェクト含むversion
//"*"でカスタム・外部オブジェクトの設定を取得出来るが、標準オブジェクトの設定を取得ため、 //手動追加が必要”Account”の様に、全ての標準オブジェクトを含むpackage.xmlファイルは上の”標準オブジェクト含むversion”を参考 <?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> <types> <members>Account</members> <members>*</members> <name>CustomObject</name> </types> <types> <members>*</members> <name>CustomApplication</name> </types> <types> <members>*</members> <name>CustomTab</name> </types> <types> <members>*</members> <name>Profile</name> </types> <version>53.0</version> </Package>
- 標準オブジェクトのmetadata取得の時、もし標準オブジェクトは何の変更がなあい場合は、そのmetadataは取得し無い。
- もし既に目標環境の中には”ABCプロファイル”が存在した場合、”ABCプロファイル”のmetadataをdeployすると、metadataの中にある項目は目標環境”ABCプロファイル”の項目を上書きする;metadataの中にない項目なら、上書きし無い
- 補充説明
metadataで権限セットをdeploy時の注意点
以下図のように、「dreamhouse」と言う権限セットのmetadata取得出来るかどうかを検証。
結果として:
- 取得出来る
- 割り当てられたアプリケーション
- オブジェクト設定
- 標準オブジェクト
- タブの設定
- オブジェクト権限
- 項目権限
- 標準オブジェクト
- アプリケーション権限
- Apex クラスアクセス
- Visualforce ページのアクセス
- フローアクセス
- システム権限
- 取得できない
- 割り当てられた接続アプリケーション
- 指定ログイン情報アクセス
- 未確認
- (未確認)外部データソースアクセス
- (未確認)カスタム権限
- (未確認)カスタムメタデータ型
- (未確認)カスタム設定の定義
- (未確認)サービスプロバイダ
metadataで「フロー」資材をdeploy / 削除
-
(deploy)取得先該当フローが無効化状態
- 最新versionのフロー資材が取得、フローAPI名.flow-meta.xml ファイルにObsoleteステータスが表示されます
- 資材追加用package.xml構成例:API名が「test03」のフロー資材を追加
フローdeploy用package.xml例
<?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> <types> <members>test03</members> <name>flow</name> </types> <version>53.0</version> </Package>
- deploy結果:
- deploy先環境で該当フロー存在しない場合
- 該当フローが作成され、version番号1に設定、フローが無効化されます。
- deploy先環境で該当フロー存在する場合
- 該当フロー有効化されたversionが最新versionの場合、versionが追い番され、deploy資材になります
- 該当フロー有効化されたversionが最新versionでは無い場合、該当フロー最新のversionがdeploy資材に上書きされます
- deploy先環境で該当フロー存在しない場合
-
(deploy)取得先該当フローが有効化状態
- 有効化されたversionの資材が取得、フローAPI名.flow-meta.xml ファイルにActiveステータスが表示されます
- deploy結果:
- deploy先環境で該当フロー存在しない場合:該当フローが作成され、version番号1に設定、フローが有効化されます。
- deploy先環境で該当フロー存在する場合:該当フローversionが追い番され、且つ最新versionが有効化されます。
-
salesforce組織上「フロー」資材を削除
- フロー資材を削除の時、一つのフロー丸ごと削除はできません、フローのversion番号を指定し、version別々削除する必要があります。フローの全versionが削除後、フロー自体も消します
例:API名「test03」のフローには三つのversionが存在します、該当フローを削除するため、三つのversion全部削除必要がありますフロー削除用package.xml例<?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> <types> <members>test03-1</members> <members>test03-2</members> <members>test03-3</members> <name>flow</name> </types> </Package>
- フロー資材を削除の時、一つのフロー丸ごと削除はできません、フローのversion番号を指定し、version別々削除する必要があります。フローの全versionが削除後、フロー自体も消します
-
追加説明
- salesforce「フロー」をmetadataで組織へdeployするには、以下の二つパーツと絡む
- 「flow」一つのフローの中身、version情報とか付いていない、
- 「flowDefinitions」フローのversion情報とかをつ
- vscode、githubでmetadata管理、deploy(所謂CI/CD)を行う場合
- 「flowDefinitions」フォルダをgitignoreに入れ、触らない方が便利
- フローの削除や特定versionの削除操作は、SF環境でやった方がいい;もしsfdx使って、ローカル環境でpackage.xmlの方法でSF環境上のフローの特定versionの削除操作を行う場合;sfdxGUIのフローは図の様に、綺麗に名前で分けるではなく、「flow name-versionXXX」の羅列表示になってしまう、かなり醜くなる、余り進めない。そうなった場合、も一回sfdx環境を構築したら図の様な表示に戻る。
- 「flowDefinitions」作用の検証
検証用:テストtask5796検証用フロー(testTask5796)作成 flowDifinitionをdeploy資材に入れる version1を有効化、他version存在なし flowDifinitionの<activeVersionNumber>を除去し flowとflowDifinition両方をdeploy 結果:追い番なし、version1のまま、無効化に成る version1を無効化、他version存在なし flowDifinitionの<activeVersionNumber>を除去し flowとflowDifinition両方をdeploy 結果:追い番あり、version2追い番、無効化に成る version1を有効化、他version2存在あり flowDifinitionの<activeVersionNumber>を除去し flowとflowDifinition両方をdeploy 結果:追い番あり、version3追い番、無効化に成る version3を有効化、他version1・2存在あり flowDifinitionの<activeVersionNumber>を除去し flowとflowDifinition両方をdeploy 結果:追い番あり、version4追い番、無効化に成る version全部無効化、flowファイルversion4と同じ中身、他version存在あり flowDifinitionの<activeVersionNumber>を除去し flowとflowDifinition両方をdeploy 結果:追い番あり、version5追い番、無効化に成る flowDifinitionをdeploy資材に入れない version全部無効化、flowファイルversion5と同じ中身、他version存在あり flowDifinitionファイルdeployしない 結果:追い番あり、version6追い番、有効化に成る version6を有効化、他version存在あり flowDifinitionファイルdeployしない 結果:追い番あり、version7追い番、有効化に成る
- salesforce「フロー」をmetadataで組織へdeployするには、以下の二つパーツと絡む
外部オブジェクト項目削除
外部オブジェクト項目を削除の時、sfdxコマンドでdistrictivePackage.xmlをdeployによっての削除方法はできない、環境上手動作業が必要。
そして削除後、該当項目API名後ろに”_del”が追加される。一度削除された項目を復元しても、”_del”のサブフィックスは自動的に消せない、そこも手動修正が必要。
apex class管理
apex classのmetadata管理の時、force-app/main/default/classes
の下に更にフォルダを作って、違う用途のapex classを分ける。この場合、apex classの名前はグローバルで一意である必要。参考URL
フォルダ分けると、こんな感じ:
sf環境との接続エラー
sf環境接続後、もし、「org Browser」をリフレッシュやmetadataダウンロードの時、「Unexpected end of JSON input」のエラーが発生し、接続できなくなったの場合。
-
.sfdx
フォルダーを確認、中には空っぽのjsonファイルがあるばい、それを削除 参考記事 -
sfdx force:org:list
コマンドで接続状況を確認、もし接続正常の場合。vscodeを何分放置し、接続が治るはず
gitブランチ間の差分でmetadataのpackageファイルを作成
以下のコマンドで修正資源のみがリストされる差分package.xmlファイルを生成(修正資源のファイルパスが空白を含む時は(2)で実施)
(1) sfdx force:source:manifest:create --sourcepath `git diff 変更前ブランチ名 変更後ブランチ名 --name-only --diff-filter=MRA | awk '{printf ","$1;} '` --manifestname ./manifest/package.xml
(2) sfdx force:source:manifest:create -p "`git diff 変更前ブランチ名 変更後ブランチ名 --name-only --diff-filter=MRA | awk '{ o = NF > 1 ? "'\''" $0 "'\''" : $0; s = length(s) > 0 ? s "," o : s = o } END{ print s }'`" -n ./manifest/package.xml
deplpoy検証
manifest/package.xmlの内容は目標組織にdeploy出来るかどうかを検証、-c
を取り除くと普通のdeployコマンド
sfdx force:source:deploy -c --manifest manifest/package.xml --postdestructivechanges manifest/destructiveChangesPost.xml -u ユーザ名(省略可能)
// 今回デプロイするApexテスト + デプロイ先に存在するすべてのApexテストを実行する
// 共通クラスの修正をしているような場合はこれで他に影響が出ていないことを確認する
sfdx force:source:deploy -c --manifest manifest/package.xml --postdestructivechanges manifest/destructiveChangesPost.xml -l RunLocalTests -u ユーザ名(省略可能)
// 指定したApexテストのみを実行する
sfdx force:source:deploy -c --manifest manifest/package.xml --postdestructivechanges manifest/destructiveChangesPost.xml -l RunSpecifiedTests -r APEXテストクラス名01,APEXテストクラス名02 -u ユーザ名(省略可能)
vscode上、apex classテストコード走る
vscode上、apex classテストコード走る時、テストコードはリモート組織上で走っている、その為、先ず新しいテストコードを組織へdeployする必要がある
apexでレコード大量作成
chatGPTに実現したいことを詳しく説明すれば、コードを作ってくれる
public class CreateDeleteClass {
public static void createEvents() { //「行動」(event)作成用
DateTime sampleDateTime01 = DateTime.newInstance(2023, 5, 24, 10, 0, 0); // サンプルの日時を設定
DateTime sampleDateTime02 = DateTime.newInstance(2023, 5, 25, 10, 0, 0); // サンプルの日時を設定
Id ownerId = '0059D000004SHxdQAG'; // 参照所有者ユーザレコード
Id whatId = '0069D00000SaodSQAR' ; // 参照商談レコード
List<Event> records = new List<Event>(); // 399個行動レコード作成
for (Integer i = 0; i < 400; i++) {
Event obj = new Event(
Subject = 'Sample Event ' + i,
StartDateTime = sampleDateTime01,
EndDateTime = sampleDateTime01.addHours(1),
OwnerId = ownerId,
WhatId = whatId
);
records.add(obj);
}
insert records;
}
public static void deleteEvents() { // 「行動」(event) 削除用
DateTime sampleDateTime01 = DateTime.newInstance(2023, 5, 24, 10, 0, 0); // 削除したい特定の日付を指定
List<Event> eventsToDelete = [SELECT Id FROM Event WHERE StartDateTime >= :sampleDateTime01];
delete eventsToDelete;
}
public static void createTasks() { // 「Todo」(task) 作成用
List<Task> tasks = new List<Task>();
Id ownerId = '0059D000004SHxdQAG';
Id whatId = '0069D00000SaodSQAR' ;
Date dateToSet = Date.newInstance(2023, 5, 24);
for (Integer i = 0; i < 400; i++) {
Task task = new Task(
Subject = 'Sample Task ' + i,
ActivityDate = dateToSet,
OwnerId = ownerId,
WhatId = whatId
);
tasks.add(task);
}
insert tasks;
}
public static void deleteTasks() { // 「Todo」(task) 削除用
Date dateToSet = Date.newInstance(2023, 5, 24);// 削除したい特定の日付を指定
List<Task> tasksToDelete = [SELECT Id FROM Task WHERE ActivityDate >= :dateToSet];
delete tasksToDelete;
}
}
apex class保存後、メソッドを呼ぶ場合、開発者コンソールを開き、「Debug」タブの「Open Execute Anonymous Window」を開き、class名.method名
入れ、「execute」でメソッドを実行。
apex classテストクラス作成時、参照関係の書き方
例:
テスト用contact作成時、参照関係になるaccountも作成され;その参照関係を保つため、
contact.AccountId = account.Id