Regular expressions are those I don’t use so frequently so I need to wrap them all in a method with a short explanation about what they do. So I created a simple JavaScript method that removes all newlines and multiple spaces (including tab spaces) by a single space
/**
* It replace all new lines and multiple spaces (including tab spaces) by a single space
* @param {string } text
* @return {string} a new cleaned string
*/
function cleanUpSpaces(text) {
if (!text) {
return text;
}
// s{2,} matches any white space (length >= 2) character (equal to [rntfv ])
return text.replace(/s{2,}/g, ' ');
};
Output example
// given
`SELECT *
FROM account WHERE id = 1234`
// output
SELECT * FROM account WHERE id = 1234
As you probably know, JavaScript buttons are not supported in Lightning Experience (LEX). In particular, you can’t use REQUIRESCRIPT.
In this post, the idea is to show how to migrate a particular JavaScript button that uses sforce.connection.query sentence.
Suppose our JavaScript button is like this:
{!REQUIRESCRIPT("/soap/ajax/37.0/connection.js")}
{!REQUIRESCRIPT("/soap/ajax/37.0/apex.js")}
var query ="SELECT Id, Name FROM Account LIMIT 10";
var queryRecords= sforce.connection.query(query);
var records = queryRecords.getArray("records");
//Do something with the records...
A Lightning equivalent solution requires several steps. I assume that you have some knowledge about Lightning Components development as well as Apex development.
Well, let’s go!
Create a Lightning component called MyQueryResult and Its JavaScript controller
MyQueryResult.cmp
<aura:component controller="MyQueryResultService" >
<aura:attribute name="queryResult" type="SObject[]" />
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<!-- It just displays the results. Modify It depending on your needs -->
<aura:iteration items="{! v.queryResult}" var="item">
{!item.Id} - {!item.Name}<br/>
</aura:iteration>
</aura:component>
MyQueryResult.js
({
doInit : function(component, event, helper) {
var myQuery = 'SELECT Id, Name FROM Account LIMIT 10';
var action = component.get("c.executeQuery");
action.setParams({
"theQuery": myQuery
});
action.setCallback(this, function(response) {
var state = response.getState();
if(state == "SUCCESS" && component.isValid()){
console.log("success") ;
var queryResult = response.getReturnValue();
console.log(queryResult);
component.set("v.queryResult", queryResult);
}else{
console.error("fail:" + response.getError()[0].message);
}
});
$A.enqueueAction(action);
}
})
We will need also a service (an Apex class) to execute our query
MyQueryResultService
public class MyQueryResultService {
@AuraEnabled
public static List<sObject> executeQuery(String theQuery){
try{
String query = String.escapeSingleQuotes(theQuery);
return Database.query(query);
}catch(Exception e){
throw new AuraHandledException('Error doing the query: '+theQuery+' Error: '+e.getMessage());
}
}
}
After that, we need a quick action pointing to our component.
The last step is to add our quick action to the layouts we need
What about if we add a component with a pie chart on the Campaign object’s record page? It’s very useful to see how the campaign is going in terms of leads’ statuses. So we want to know the percentage of leads converted, not converted, contacted, etc.
I will use Lightning in a Developer org that I just created to take advantage of its default data. So let’s go!
Assumption
You are familiar with a Salesforce org, Lightning and you have created some reports in the past. If you haven’t created any report yet, you can read this module in Trailhead: Reports & Dashboards for Lightning Experience
Add leads to a campaign
First of all, let’s add some leads to a campaign. You only have to add leads to a campaign, you don’t have to create any data.
Create the report
Go to Reports, New Report, expand Campaigns, select Campaign with leads and click on Create.
Drag Lead status field and group by this field
Save the report with the name you want and, importantly, in a public folder.
Run the report and add a pie chart. Don’t forget to save it.
Add the report component
Now let’s add our report to the Campaign’s record page.
Open any campaign and Edit Page to open the App Builder.
Drag the Report Chart component and set those parameters
Save the page and press Back to see the record page again with the report
“psql is a terminal-based front-end to PostgreSQL. It enables you to type in queries interactively, issue them to PostgreSQL, and see the query results.”
Run psql and you will see the following error
$ psql
psql: error: could not connect to server: FATAL: database "andrescanavesi" does not exist
So let’s create a db with that name in order to play with it
const parseDbUrl = require("parse-database-url");
//we have our connection url in an environment config variable. Each developer will have his own
//a connection url will look like this:
//postgres://hfbwxykfdgkurg:a75568307daad4wb1432b5d173719bae7ba908ea06e7d0ebef8bf7bd434eb655547@ec2-108-21-167-137.compute-1.amazonaws.com:5432/w5tftigeor6odh
const dbConfig = parseDbUrl(process.env.DATABASE_URL);
const Pool = require("pg").Pool;
const pool = new Pool({
user: dbConfig.user,
host: dbConfig.host,
database: dbConfig.database,
password: dbConfig.password,
port: dbConfig.port,
ssl: true,
});
module.exports.execute = pool;
In the line number 6 we have a call to a configuration environment variable
process.env.DATABASE_URL
It’s a good way to avoid versioning sensitive data like a database connection or other credentials. To run this example you can just hard-code it
Create a file called index.js
const dbHelper = require("./db_helper");
//deal with the promise
findUserById(1234)
.then(user => {
console.info(user);
})
.catch(error => {
console.error(error);
});
/**
*
* @param userId
* @returns a Promise with the user row for the given id
* @throws error if there's a connection issue or if the user was not found by the id
*/
async function findUserById(userId) {
const query = "SELECT * FROM users WHERE id = $1 LIMIT 1";
const bindings = [userId];
const result = await dbHelper.execute.query(query, bindings);
if (result.rows.length > 0) {
return result.rows[0];
} else {
throw Error("User not found by id " + userId);
}
}
Unfortunately, you cannot use async property here since the script must be loaded before the page loads.
Add this code in your page
<script type="text/javascript">
// I assume you are using jQuery. Otherwise you can use the classic way
$(document).ready(() => {
// to load images in a lazy way
// lazy loads elements with default selector as '.lozad'
const observer = lozad();
observer.observe();
console.info('lozad observing...');
});
</script>
Of course you can move this code to a different script file (let say common.js) instead of your page.
You only have to make sure that your file common.js is downloaded and ready to use before lozad call:
const observer = lozad();
observer.observe();
The last step is to modify all your images you want to load lazily.
It’s important you add alt=”your image description” because that text will be displayed while the image is loading. This will give a better user experience to your visitors.
A post written by Jozsef Torsan that I would like to share here.
—
I receive many emails from customers. I’m happy that more than 90% of these emails are about feature requests and not bugs. A small part of the emails is “how to” questions and an even smaller part is about reporting bugs. Since the last year’s October launch only 3 customers contacted me with bugs. 2 of them had ran into the emoji issue and one of them contacted me just last week with an issue about having an incorrect size of the Add Bookmark window in Opera. Fortunately it was an easy fix and I was I able to fix it by the next day. The truth is Opera was not among the browsers (Chrome, Firefox, IE, Edge, Mac Safari) I tested — shame on me. Anyway it’s worth to check the browser statistics and the trends on this page. It can give you a good hint when you plan the testing of your app in the different browsers.
The “always and ASAP” rule
My number one rule is to always give a response to the customer as soon as possible. “Always” means that even if I don’t have the answer or the solution to their question or problem right away, I inform them in an email about when I will be able to get back to them with the answer. “ASAP” means within 24 hours. If you can provide the solution or the answer for the customer only in a week, that’s not a problem. But it’s important to inform them about it within a 24 hour time frame. Regarding the priorities it’s obvious that the “how to” and “bug” emails get priority over the “feature request” emails.
The value you give to your Customers
You can give value to your customers not just with your product or service but with your customer support, too. Whenever a user contacts you it’s a good opportunity to show them how professional your customer support is. It sounds weird but you are lucky if a customer contacts you with a bug or a question. On the one hand you can fix a bug that you didn’t find during the testing, on the other hand you can show how professionally and quickly you can react and fix the bug or answer their question. Users choose a product not just considering the features and the quality of the products but the customer support is also very important for them. I often hear customers leaving a product due to the poor customer support.
Special requests
Sometimes I get special requests from customers. For example last week a user asked me if could make CSV reports about his bookmarks, tags, tabs and categories, because he wanted to make some kind of statistics on his bookmarks. I was surprised how enthusiastic he was so I was happy to help him. I quickly wrote 2 SQL queries, ran the queries and sent him the output. He was very grateful and promised me that he would update me with his statistics.
Other times it happened, that users wanted to purchase the annual Ninja subscription after the trial expired, but they couldn’t make the payment due to temporary problems with their bank accounts or credit cards. After they contacted me I offered them to extend the trial with 1 or 2 weeks. And 1 or 2 weeks later they purchased the annual subscription. It’s that easy to make customers happy and satisfied.
The hard core Ninja Users
There are quite a few very enthusiastic customers who are big Ninja fans. They are the hard core Ninja users. They keep sending me emails about their ideas, new feature requests and experiences. I love these guys! It’s like we would be a team that discusses the future developments of Ninja. We are in touch roughly on a weekly basis, so we communicate pretty frequently with each other. The input, the information I get from them is invaluable. Also if I have an idea or a new feature in my mind they are the ones I can ask about it.
Customer support matters a lot
If you put the effort in providing a good customer support, users will appreciate it. They will appreciate it very much. You will make your customers happy and they will tell other potential customers their good stories. But if they have bad experiences then it’s more likely that they will tell their friends about them.
Even though the Event Loop is a single thread we have to take care of race condition since 99% of our code will run in a non-main thread.
Callbacks and Promises are a good example of it. There are many resources along with World Wide Web about how Event Loop works like this one, so the idea of this post is to assume that we could have a resource in our code that could be accessed (read and write) by multiple threads.
Here we have a small snippet that shows how to deal with a race condition. A common scenario is when we cache some data that was expensive to get in terms of CPU, network, file system or DB.
Implementation
We might implement a cache in multiple ways. A simple way is an in-memory collection; in this case, a Map. The structure of our collection can also be a List, that will depend on our requirements.
Our Map holds users and we use the User ID as the Key and the User itself (through a Promise) the Value. That way, a method getUserById will be very fast: O(1).
I’ll explain step by step but at the end of this post you have the full source code
So let start by our map
const cache = new Map();
Our Map won’t be so smart in this example, it won’t expire elements after a while and it will add as many elements as available memory we have. An advanced solution is to add this kind of logic to avoid performance issues. Also, it will be empty after our server restarts, so is not persistent.
Let’s create a collection of users that simulate our DB
const users = [];
function createSomeUsers() {
for (let i = 0; i < 10; i++) {
const user = {
id: i,
name: 'user' + 1
};
users.push(user);
}
}
The main method that we want to take care of race condition
function getUserFromDB(userId) {
let userPromise = cache.get(userId);
if (typeof userPromise === 'undefined') {
console.info('Loading ' + userId + ' user from DB...');//IT SHOULD BE executed only once for each user
userPromise = new Promise(function (resolve, reject) {
//setTimeout will be our executeDBQuery
const threeSeconds = 1000 * 3;
setTimeout(() => {
const user = users[userId];
resolve(user);
}, threeSeconds);
});
//add the user from DB to our cache
cache.set(userId, userPromise);
}
return userPromise;
}
To test our race condition we’ll need to create multiple callbacks that simulate a heavy operation.
That simulation will be made with the classic setTimeout that will appear later.
function getRandomTime() {
return Math.round(Math.random() * 1000);
}
Finally the method that simulates the race condition
function executeRace() {
const userId = 3;
//get the user #3 10 times to test race condition
for (let i = 0; i < 10; i++) {
setTimeout(() => {
getUserFromDB(userId).then((user) => {
console.log('[Thread ' + i + ']User result. ID: ' + user.id + ' NAME: ' + user.name);
}).catch((err) => {
console.log(err);
});
}, getRandomTime());
console.info('Thread ' + i + ' created');
}
}
Our last step: call our methods to create some users and to execute the race condition
createSomeUsers();
executeRace();
Let create a file called race_condition.js and execute it like this:
node race_condition.js
The output will be:
Dummy users created
Thread 0 created
Thread 1 created
Thread 2 created
Thread 3 created
Thread 4 created
Thread 5 created
Thread 6 created
Thread 7 created
Thread 8 created
Thread 9 created
Loading 3 user from DB...
[Thread 8]User result. ID: 3 NAME: user1
[Thread 3]User result. ID: 3 NAME: user1
[Thread 1]User result. ID: 3 NAME: user1
[Thread 9]User result. ID: 3 NAME: user1
[Thread 5]User result. ID: 3 NAME: user1
[Thread 2]User result. ID: 3 NAME: user1
[Thread 7]User result. ID: 3 NAME: user1
[Thread 0]User result. ID: 3 NAME: user1
[Thread 6]User result. ID: 3 NAME: user1
[Thread 4]User result. ID: 3 NAME: user1
Notice that [Thread X] output does not appear in order. That’s because of our random time tat simulate a thread that takes time to be resolved.
Full source code
/**
* A cache implemented with a map collection
* key: userId.
* value: a Promise that can be pending, resolved or rejected. The result of that promise is a user
* IMPORTANT:
* - This cache has not a max size and a TTL so will grow up indefinitely
* - This cache will be reset every time the script restarts. We could use Redis to avoid this
*/
const cache = new Map();
/**
* Our collection that will simulate our DB
*/
const users = [];
/**
*
*/
function createSomeUsers() {
for (let i = 0; i < 10; i++) {
const user = {
id: i,
name: 'user' + 1
};
users.push(user);
}
console.info('Dummy users created');
}
/**
*
* @param {int} userId
* @returns Promise<User>
*/
function getUserFromDB(userId) {
let userPromise = cache.get(userId);
if (typeof userPromise === 'undefined') {
console.info('Loading ' + userId + ' user from DB...');//SHOULD BE executed only once for each user
userPromise = new Promise(function (resolve, reject) {
//setTimeout will be our executeDBQuery
const threeSeconds = 1000 * 3;
setTimeout(() => {
const user = users[userId];
resolve(user);
}, threeSeconds);
});
//add the user from DB to our cache
cache.set(userId, userPromise);
}
return userPromise;
}
/**
* @returns a number between 0 and 1000 milliseconds
*/
function getRandomTime() {
return Math.round(Math.random() * 1000);
}
/**
*
*/
function executeRace() {
const userId = 3;
//get the user #3 10 times to test race condition
for (let i = 0; i < 10; i++) {
setTimeout(() => {
getUserFromDB(userId).then((user) => {
console.log('[Thread ' + i + ']User result. ID: ' + user.id + ' NAME: ' + user.name);
}).catch((err) => {
console.log(err);
});
}, getRandomTime());
console.info('Thread ' + i + ' created');
}
}
createSomeUsers();
executeRace();