有几段试验性的代码因为公司要更新沙盒,删除了。在本地虽然还保存了副本,但怕以后刷新时误删,所以贴一份在这里,以便需要时拷贝。

1.用aura组件包装一个flow

foo.cmp:

<aura:component implements="flexipage:availableForAllPageTypes,lightning:isUrlAddressable,lightning:availableForFlowScreens,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction"  access="global">
<aura:handler name="init" value="{!this}" action="{!c.init}" />
<lightning:flow aura:id="flowData" onstatuschange="{!c.handleStatusChange}" />
<lightning:workspaceAPI aura:id="workspace"/>
</aura:component>

fooController.js:

({
init : function (component) {
// Find the component whose aura:id is "flowData"
var flow = component.find("flowData");
// In that component, start your flow. Reference the flow's API Name.
flow.startFlow("myFlow");
},
handleStatusChange : function (component, event) {
if(event.getParam("status") === "FINISHED") {
var workspaceAPI = component.find("workspace");
workspaceAPI.getFocusedTabInfo().then(function(response) {
let focusedTabId = response.tabId;
workspaceAPI.closeTab({tabId: focusedTabId});
})
}
}
})

上面的handleStatusChange的主要作用是因为缺省方式是flow执行完后,自动跳到开头,重复执行,所以用关闭tab页的方式退出flow。

2. 在tab页显示flow

上面的组件包装了flow之后,可以作为QuickAction放到页面上,或者Actions and Recommendations里,但是QuickAction缺省情况下显示在对话框里,这样有些Flow显示起来就很难看。下面的代码将Flow还是显示在tab页:

bar.cmp

<aura:component implements="flexipage:availableForAllPageTypes,lightning:isUrlAddressable,lightning:availableForFlowScreens,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction"  access="global" >
<lightning:workspaceAPI aura:id="workspace"/>
<aura:handler name="init" value="{!this}" action="{!c.init}" />
</aura:component>

barController.js

({
init: function(component, event, helper) {
var workspaceAPI = component.find("workspace");
workspaceAPI.getEnclosingTabId().then(function(enclosingTabId) {
workspaceAPI.openSubtab({
parentTabId: enclosingTabId,
pageReference: {
"type": "standard__component",
"attributes": {
"componentName": "c__foo"
}
}
}).then(function(subtabId) {
console.log("The new subtab ID is:" + subtabId);
$A.get("e.force:closeQuickAction").fire();
}).catch(function(error) {
console.log("error");
});
});
}
})

3. Process Builder里要删除一个Process,如果这个Process有多个版本,就必须手工一个个版本地删除。版本一多,颇为麻烦。查了资料,有个方法是手工编制一个destructiveChanges.xml,然后发布到服务器。但对于我这样的懒人来说,写这个xml都觉得费劲,于是写了个油猴插件,其实可以实现一键删除,但为了保险起见,避免误删,还是需要输入要删除的Process的标签,然后再一键删除:

// ==UserScript==
// @name Whatever name you like
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Delete all versions of a process in a batch
// @author you
// @match *.lightning.force.com/lightning/setup/ProcessAutomation/home
// @grant none
// @run-at document-end
// ==/UserScript== (function() {
'use strict'; function confirmError(doc, count) {
if (count > -1) {
count--;
let toClick = [];
let spans = doc.getElementsByTagName('SPAN');
for (let i = 0; i < spans.length; i++) {
if (spans[i].innerText == "OK") {
toClick.push(spans[i]);
}
}
if (toClick.length < 1) {
setTimeout(function() {
confirmError(doc,count);
}, 2000);
}
else {
toClick.forEach(function(item) {
item.click();
});
}
}
} function delVersions(doc) {
let processLabel = prompt('Please enter the label of the process you want to delete');
console.log(processLabel);
var process = doc.getElementsByClassName('bodyRow processuimgntConsoleListRow versionOpen');
if (process.length == 0) {
alert('Please expand the process you want to delete');
}
else if (process.length > 1) {
alert('Please expand only 1 process');
}
else {
var versions = [];
var versionTrs = doc.getElementsByClassName('bodyRow summary processuimgntVersionListRow processuimgntConsoleListRow');
var hasActive = false;
if (processLabel == versionTrs[0].children[0].getAttribute('title')) {
for (let i = 0; i < versionTrs.length; i++) {
//console.log(versionTrs[i]);
versions.push(versionTrs[i].children[6].firstChild);
if (versionTrs[i].children[5].getAttribute('title') == 'Active') {
hasActive = true;
break;
}
}
if (hasActive) {
alert('cannot delete the process with an active version');
}
else {
console.log(versions.length);
versions.forEach(function(v) {
v.click();
});
setTimeout(function() {//'Confirm' dialogues may not pop up instantly, so delay a bit
let confirms = [];
let d = doc;
let spans = d.getElementsByTagName('SPAN');
for (let i = 0; i < spans.length; i++) {
if (spans[i].innerText == "Confirm") {
confirms.push(spans[i]);
}
}
confirms.forEach(function(c) {
c.click();
});
setTimeout(function() {//confirm the 'error' prompt
confirmError(d, 10);
}, 3000); }, 5000); }
}
}
} function addButton(count) {
if (count > -1) {
count--;
let topmost = document.getElementsByClassName("viewport");
//console.log(titleDiv);
console.log(topmost.length);
if (topmost != null && topmost.length > 0) {
//let titleDiv = titleH2.parent.parent;
//console.log(topmost[0]);
let ifrm = topmost[0].getElementsByTagName('IFRAME');
console.log(ifrm.length);
if (ifrm.length > 0) {
//console.log(ifrm[0]);
var doc = ifrm[0].contentDocument ? ifrm[0].contentDocument: ifrm[0].contentWindow.document;
console.log(doc);
let titleDiv = doc.getElementsByClassName('myprocesses'); if (titleDiv == null || titleDiv.length < 1) {
setTimeout(function() {
addButton(10);
},1000);
}
else {
console.log(titleDiv[0]);
var btnDiv = document.createElement('div');
btnDiv.innerHTML = '&nbsp;&nbsp;&nbsp;&nbsp;<button type="button" id="btnDelVersions" value="true" ><em>Mass Delete Versions</em></button>';
titleDiv[0].appendChild(btnDiv);
let btnDelVersions = doc.getElementById('btnDelVersions');
btnDelVersions.addEventListener('click', function() {
delVersions(doc);
}, false);
}
return;
}
else {
setTimeout(function() {
addButton(10);
}, 2000);
} }
else {
setTimeout(function() {
addButton(count);
}, 2000);
}
}
else {
alert('Please refresh your page');
}
} addButton(10);//try 10 times
})();

4. Salesforce的Developer Console的查询器里不支持注释,这对于用惯了sql server的我来说感觉很不方便,另外,soql也不支持select * from,开始也颇不习惯,写soql查数据时,不得不查Salesforce的参考手册。后来安装了vs code的一个插件,Salesforce schema explorer,大致解决了select * from的问题,但不支持注释语句还是个问题。花了点时间改写了这个插件的代码,大体支持类似sql server里的注释符号--了。

修改了out\views目录下的soql.js:

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SOQLView = void 0;
const vscode = require("vscode");
const sfAPIOperations_1 = require("../sfAPIOperations");
const fileUtil_1 = require("../fileUtil");
let SOQLView = /** @class */ (() => {
class SOQLView {
constructor(context) {
this.currentPanel = undefined;
SOQLView.isAppend = 'no';
SOQLView.oldSoqlString = '';
this.strippedSoql = '';
this.promisifiedWithProgress = (soqlString, userName) => new Promise((resolve, reject) => {
let message = 'Fetch successful';
vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Fetching records......",
cancellable: false
}, (progress, token) => __awaiter(this, void 0, void 0, function* () {
console.log(progress, token);
try {
const conn = yield sfAPIOperations_1.SFAPIOperations.getConnection(userName);
const records = yield sfAPIOperations_1.SFAPIOperations.fetchRecords(conn, soqlString);
records.forEach(function (index) { delete index.attributes; });
console.log('runSOQL: ', records); // This line is just to check connection validity
vscode.window.showInformationMessage(message, { modal: false });
resolve(records);
}
catch (error) {
message = 'Unable to fetch records';
vscode.window.showErrorMessage(error.message, { modal: false });
reject(error);
}
}));
});
this.context = context;
}
getCommentStrippedSOQL(soqlString) {
let soqls = soqlString.split(';');
let result = soqlString;
for (let i = soqls.length - 1; i > -1; i--) {
if (soqls[i].trim() != '' && soqls[i].trim().startsWith('--') == false) {
result = soqls[i].trim();
break;
}
}
console.log(result);
return result.trim().startsWith('--') ? '' : result;
}
runSOQL(soqlString, username) {
return __awaiter(this, void 0, void 0, function* () {
let records = [];
console.log("runSOQL.userName: ", username);
this.strippedSoql = soqlString;
let stripped = this.getCommentStrippedSOQL(soqlString);
if (stripped == '')
return records;
records = yield this.promisifiedWithProgress(stripped, username);
SOQLView.queryResult = records;
SOQLView.oldSoqlString = SOQLView.isAppend == 'yes' ? SOQLView.oldSoqlString + soqlString : soqlString;
this.strippedSoql = stripped;
return records;
});
}
generateWebView(soqlString, isAppend) {
let soqlStr = isAppend == 'yes' ? SOQLView.oldSoqlString + soqlString : soqlString;
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SOQL</title>
</head>
<body>
<div style="width: 100%; mqrgin-top: 2% !important">
<textarea class="soql-textarea" id="soql-textarea" name="soql" rows="6" oninput="getCurrentSoqlString(this.value);">${soqlStr}</textarea>
</div>
<div class="buttons-div">
<button class="query-button" onclick="runSOQL();">Run Query</button>
<button class="clipboard-button" onclick="copyToClipboard();">Copy to Clipboard</button>
<input type="checkbox" id='isAppend' onclick="toggleAppendMode(this.checked);" >Append SOQL</input>
</div>
<div id="query-result-container" style="overflow-x:auto; overflow-y:auto;"> </div>
<script>
const vscode = acquireVsCodeApi();
function runSOQL() {
const soqlString = document.getElementById("soql-textarea").value;
vscode.postMessage({
command: 'runSOQL',
text: soqlString
});
} function copyToClipboard() {
const soqlString = document.getElementById("soql-textarea").value;
vscode.postMessage({
command: 'copyToClipboard',
text: soqlString
});
} const flattenObject = function(ob) {
var toReturn = {}; for (var i in ob) {
if (!ob.hasOwnProperty(i)) continue; if ((typeof ob[i]) == 'object') {
var flatObject = flattenObject(ob[i]);
for (var x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue; toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = ob[i];
}
}
return toReturn;
}; function splitSOQLString(soqlString) {
console.log('soql:' + soqlString);
let index = soqlString.search(/FROM/i);
console.log(index);
console.log(soqlString.indexOf('from'));
return soqlString.slice(0, index).replace(/^(SELECT)/i,"").trim().split(',').map(item => item.trim().toLowerCase());
} function renderTable(records,oldSOQLString) {
let soqlString = "";
if(!oldSOQLString) {
soqlString = document.getElementById("soql-textarea").value;
} else {
soqlString = oldSOQLString;
}
const fieldsArray = splitSOQLString(soqlString);
let flattenedRecords = [];
for(let record of records) {
let newObj = keyToLowerCase(record);
flattenedRecords.push(flattenObject(newObj));
} let queryContainer = document.getElementById("query-result-container");
queryContainer.innerHTML = "";
if(flattenedRecords.length > 0) {
queryContainer.appendChild(generateHTMLtable(fieldsArray, flattenedRecords));
} else {
queryContainer.innerHTML = "<H4>No Records Returned.</H4>";
} } function keyToLowerCase(obj) {
let key, keys = Object.keys(obj);
let n = keys.length;
let newobj={};
while (n--) {
key = keys[n];
newobj[key.toLowerCase()] = obj[key];
}
return newobj;
} function generateHTMLtable(fieldsArray, flattenedRecords) {
let soqlTable = document.createElement('TABLE');
soqlTable.classList.add("soql-table");
soqlTable.innerHTML = "";
var columnCount = fieldsArray.length; let theadRow = soqlTable.insertRow(-1);
for (let column of fieldsArray) {
var headerCell = document.createElement("TH");
headerCell.innerHTML = column;
theadRow.appendChild(headerCell);
}
soqlTable.appendChild(theadRow); let tBodyElement = "";
for (let record of flattenedRecords) {
row = soqlTable.insertRow(-1);
for (let field of fieldsArray) {
var cell = row.insertCell(-1);
cell.innerHTML = record[field] ? record[field] : "";
}
soqlTable.appendChild(row);
}
return soqlTable;
} // Handle the message inside the webview
window.addEventListener('message', event => {
const message = event.data; // The JSON data our extension sent switch (message.command) {
case 'displayQuery':
console.log('displayQuery in html');
console.log(message.records);
renderTable(message.records, message.soqlString);
break;
case 're-renderTable':
console.log('SOQLView.queryResult: ',message.records);
console.log('SOQLView.oldSoqlString: ',message.soqlString);
renderTable(message.records, message.soqlString);
break;
case 'setIsAppend':
console.log('isappend:'), message.flag;
document.getElementById('isAppend').checked = message.flag == 'yes' ? true : false;
}
}); function toggleAppendMode(isChecked) {
let isAppend = isChecked ? 'yes' : 'no';
vscode.postMessage({
command: 'append',
text: isAppend
});
} function getCurrentSoqlString(soqlString) {
vscode.postMessage({
command: 'currentSoql',
text: soqlString
});
}
</script>
<style>
body.vscode-light {
color: black;
} body.vscode-dark {
color: #a8abaff2;
} body.vscode-high-contrast {
color: red;
} .query-button {
margin: 1%;
background-color: #0a77e8;
border-color: #0a77e8;
padding: 5px;
} .clipboard-button {
margin: 1%;
background-color: #8a8f92;
border-color: #8a8f92;
padding: 5px;
} .soql-textarea {
width: 100%;
font-size: medium;
color: inherit;
} body.vscode-dark .query-button {
color: #eaf1f1;
} body.vscode-light .query-button {
color: #f8f8f9;
} body.vscode-dark .clipboard-button {
color: #eaf1f1; /*#f8f8f9*/
} body.vscode-light .clipboard-button {
color: #f8f8f9;
} body.vscode-dark .soql-textarea {
color: #a8abaff2;
background-color: #2d38454f;
} body.vscode-light .soql-textarea {
color: #46484af2;
background-color: #bfc6ce4f;
} body.vscode-dark .soql-table, td, th {
border: 1px solid #eaf1f185; /*#474a4a85*/
} body.vscode-light .soql-table, td, th {
border: 1px solid #474a4a85;
} table.soql-table {
border-collapse: collapse;
width: 100%;
height: auto; } th {
height: 30px;
}
</style>
</body>
</html>`;
}
displaySOQL(soqlString, username) {
console.log("userName: ", username);
const columnToShowIn = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
if (this.currentPanel) {
// If we already have a panel, show it in the target column
this.currentPanel.webview.html = this.generateWebView(soqlString, SOQLView.isAppend);
this.currentPanel.name = `${username} - Query Runner`;
console.log('SOQLView.queryResult in currentPanel: ', SOQLView.queryResult);
console.log('SOQLView.oldSoqlString: ', SOQLView.oldSoqlString);
if (SOQLView.queryResult) {
this.currentPanel.webview.postMessage({ command: 're-renderTable', soqlString: SOQLView.oldSoqlString, records: SOQLView.queryResult });
}
this.currentPanel.reveal(columnToShowIn);
}
else {
// Otherwise, create a new panel
this.currentPanel = vscode.window.createWebviewPanel('SOQL', `${username} - Query Runner`, vscode.ViewColumn.One, {
enableScripts: true
});
this.currentPanel.webview.html = this.generateWebView(soqlString, SOQLView.isAppend);
this.currentPanel.webview.postMessage({ command: 're-renderTable', soqlString: SOQLView.oldSoqlString, records: SOQLView.queryResult });
// Reset when the current panel is closed
this.currentPanel.onDidDispose(() => {
this.currentPanel = undefined;
}, null);
// Handle messages from the webview
this.currentPanel.webview.onDidReceiveMessage((message) => __awaiter(this, void 0, void 0, function* () {
switch (message.command) {
case 'copyToClipboard': {
fileUtil_1.FileUtil.copyToClipboard(message.text);
//this.currentPanel.webview.postMessage({ command: 'Copied'});
return;
}
case 'runSOQL':
{
console.log('message.command: ', message.command);
const records = yield this.runSOQL(message.text, username);
console.log('records in panel: ', records);
this.currentPanel.webview.postMessage({ command: 'displayQuery', records: records, soqlString: this.strippedSoql });
return;
}
case 'append':
{
console.log('message.command: ', message.command);
SOQLView.isAppend = message.text;
if (SOQLView.isAppend == 'no') {
SOQLView.oldSoqlString = '';
}
return;
}
case 'currentSoql':
{
console.log('message.command: ', message.command);
SOQLView.oldSoqlString = message.text;
}
return;
}
}), undefined, this.context);
}
this.currentPanel.webview.postMessage({ command: 'setIsAppend', flag: SOQLView.isAppend });
}
}
SOQLView.queryResult = undefined;
return SOQLView;
})();
exports.SOQLView = SOQLView;
//# sourceMappingURL=soql.js.map

Salesforce学习笔记之代码若干的更多相关文章

  1. Salesforce学习笔记(一)

    Force平台简介 一.Force平台应用程序的优点1.以数据为中心的应用程序(一个对象就是一个数据库表) 由于该平台以数据库为中心,它让你能够编写以数据为中心的应用程序.以数据为中心的应用程序是基于 ...

  2. [学习笔记] SSD代码笔记 + EifficientNet backbone 练习

    SSD代码笔记 + EifficientNet backbone 练习 ssd代码完全ok了,然后用最近性能和速度都非常牛的Eifficient Net做backbone设计了自己的TinySSD网络 ...

  3. Salesforce学习笔记之Actions and Recommendations(续)

    上次对这个Actions and Recommendations进行了初步研究,因为一些问题没有得到很好的解决,又花了很多时间,终于得到了一个比较好的解决方案.小结一下. 1. 生成Actions a ...

  4. 转--Android学习笔记-实用代码合集

    http://blog.csdn.net/yf210yf/article/details/7295577 转载请注明原文出处:奔跑的蜗牛(袁方的技术博客)点击打开链接 一.当利用textview显示内 ...

  5. JavaWeb学习笔记--Servlet代码集

    目录: 登录系统提交表单数据打开PDFCookieURL传递参数URL重写跟踪会话使用HttpSession对象跟踪会话Servlet间协作过滤器Filter 登录系统 <!DOCTYPE HT ...

  6. [知了堂学习笔记]_Java代码实现MySQL数据库的备份与还原

    通常在MySQL数据库的备份和恢复的时候,多是采用在cmd中执行mysql命令来实现. 例如: mysqldump -h127.0.0.1 -uroot -ppass test > d:/tes ...

  7. webpack学习笔记--压缩代码

    浏览器从服务器访问网页时获取的 JavaScript.CSS 资源都是文本形式的,文件越大网页加载时间越长. 为了提升网页加速速度和减少网络传输流量,可以对这些资源进行压缩. 压缩的方法除了可以通过 ...

  8. [学习笔记]Java代码中各种类型变量的内存分配机制

    程序运行时,我们最好对数据保存到什么地方做到心中有数.特别要注意的是内存的分配.有六个地方都可以保存数据: (1) 寄存器 这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部.然而 ...

  9. Scala学习笔记——简化代码、柯里化、继承、特质

    1.简化代码 package com.scala.first import java.io.File import javax.management.Query /** * Created by co ...

随机推荐

  1. 题解 CF510E 【Fox And Dinner】

    可以用网络流解决这个题. 注意到\(a_i \geqslant 2\),所以当相邻数字要和为质数时,这两个数要一个为奇数,一个为偶数. 所以就先将所有数按奇偶分为两列,其就构成了一个二分图,二分图中和 ...

  2. Nginx 服务器配置支持SignalR (WebSocket)

    今天SignalR部署在测试环境服务器前端出现无法连接,前端报错如下: failed: Error during WebSocket handshake: Unexpected response co ...

  3. Android仿支付宝高顶部功能条伸缩动画

    参考:https://blog.csdn.net/aqi00/article/details/72621176

  4. ubuntu的docker安装

    安装docker 安装 介绍一下docker 的中央仓库们 Docker官方中央仓库: https://hub.docker.com/ 因为docker 网站在国外所以访问速度和你的运气有关还有网络. ...

  5. 记一次针对静态页面的DDOS基本防护

    可以说是我试图进入安全口的天才第一步了,能走多远鬼知道呢 背景 去年年前接到的一个外包项目,是一个base在日本的中国人留学机构做的静态页面.出于锻炼自己的目的,选择为他们按次结薪做长期服务维护.20 ...

  6. 点format方式输出星号字典的值是键

    dic = {'a':123,'b':456} print("{0}:{1}".format(*dic)) a:b 2020-05-08

  7. 3-Pandas之Series和DataFrame区别

    一.Pandas pandas的数据元素包括以下几种类型: 类型 说明 object 字符串或混合类型 int 整型 float 浮点型 datetime 时间类型 bool 布尔型 二.Series ...

  8. SpringBoot注解综合

    SpringBoot注解综合 @Bean 注解通常会应用在一些配置类(由@Configuration注解描述)中,用于描述具备返回值的方法,然后系统底层会通过反射调用其方法,获取对象基于作用域将对象进 ...

  9. Linux的VMWare中Centos7用户和用户管理三个系统文件(/etc/passwd-shadow-group解读)和批量创建用户user及用户工作环境path

    Linux 用户和用户组管理 用户工作环境PATH Linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统. 用 ...

  10. Boolean源码解剖学

    一.类继承 Boolean的源码类定义部分如下: 1 public final class Boolean implements java.io.Serializable, 2 Comparable& ...