Salesforce学习笔记之代码若干
有几段试验性的代码因为公司要更新沙盒,删除了。在本地虽然还保存了副本,但怕以后刷新时误删,所以贴一份在这里,以便需要时拷贝。
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 = ' <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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律