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

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

最新文章

  1. OGNL相关代码
  2. 10 行 Python 代码写的模糊查询
  3. CentOS 6.8_x64 Oracle 12C 安装
  4. 78. Android之 RxJava 详解
  5. Excel 中 Index 和 Match 方法的使用
  6. Android 隐藏软键盘方法
  7. leetcode 118
  8. oracle_11g 不同用户之间的数据迁移
  9. 高效 jquery 的奥秘
  10. ThinkPHP 3.1.2 查询方式的一般使用2
  11. 在Outlook中设置QQ邮箱
  12. Leetcode-Database-176-Second Highest Salary-Easy(转)
  13. T4模板demo
  14. 怎样编制excel序列目录
  15. 谈谈Python中的decorator装饰器,如何更优雅的重用代码
  16. SpriteBuilder中锚点的一般用法
  17. 第十三节 Ajax基础
  18. prototype 与 proto的关系是什么:
  19. Django REST Framework API Guide 01
  20. Android开发 ---基本UI组件7 :分页功能、适配器、滚动条监听事件

热门文章

  1. 题解 洛谷 P4695 【[PA2017]Banany】
  2. Python for循环学习总结笔记
  3. 主机无法访问虚拟机中运行的Django项目
  4. javascript中的堆栈、深拷贝和浅拷贝、闭包
  5. 清晰架构(Clean Architecture)的Go微服务—重大升级
  6. 【Nginx】如何实现Nginx的高可用负载均衡?看完我也会了!!
  7. PHPSTORM Live-Templates变量速查表
  8. 最长公共子序列dp入门
  9. Python unichr() 函数
  10. 【新生学习】第二周:卷积神经网络_part_1