Mass deleting picklist values in Salesforce with AJAX javascript hack (2018 version)

More than 5 years ago I wrote an article on how to Mass delete Picklist values in Salesforce, this is still my most visited article and I have been meaning to get back to it for years. At least now it seems like it will be a part of the standard functionality sometime in the near future (mass delete picklist values (setup)) but today I had to do this at a customer so I had to solve it once again.

I tried using my old script but ended up with errors, Lightning doesn’t like loading external JavaScript things into itself. I respect that and switched to Classic, this is a Sysadmin Only exercise anyways.

Aura Content Security Policy directive

It works out of the box in Classic but it’s very quiet about what’s happening and you don’t really know what’s happening. Also if you accidentally clicked the bookmark in a production environment you’re going to have a bad time

Fuck It, We’ll Do It Live

Updated JavaScript looks like this:

javascript:

var allClear = function() {
    location.reload();
};
var links = document.getElementsByTagName("a");
var whatToDelete = prompt("Do you want to Delete 'Active' or 'Inactive' picklist values?'''", "Inactive");
if(!(whatToDelete === "Active" || whatToDelete === "Inactive")) {
    window.alert("Invalid choice, quitting");
} else {
    var onlyInactive = whatToDelete === "Inactive";

    var delLinks = new Array();
    for (var i = 0; i < links.length-1; i++) {
      var link = links[i];

      if(onlyInactive) {
        if(link.innerHTML === "Activate") {
            var link = links[i-1];
        } else {
            continue;
        }
      }
      if (link.innerHTML == "Del") {
        delLinks.push(link);
      }
    }

    if(delLinks.length == 0) {
        window.alert("Nothing to delete");
    } else {
        var goAhead = confirm("You're about to delete " + delLinks.length + " picklist values");
        if(goAhead) {
            for (var i = 0; i < delLinks.length; i++) {
              var delLink = delLinks[i].href
              // Synchronous AJAX style
              xmlhttp = new XMLHttpRequest();
              xmlhttp.open("GET",delLink, false);
              console.log("Deleting #" + i + ": " +  delLink);
              xmlhttp.send();
            }

            window.setTimeout(allClear, 2000);
        }
    }
}

You can still load it from where I store it by creating a bookmark with this URL:

javascript:(function()%7Bvar s = document.createElement("script"); s.src = "https://superfredag.com/massdelete_v2.js"; void(document.body.appendChild(s));%7D)()

Clicking it on a Global Value Set Detail page will give you a prompt:

Clicking ok will go ahead and select the inactive Picklist values and prompt again:

Clicking Cancel will abort at all times.
Clicking on on this last Confirmation dialogue will delete your Inactive picklist values and the page will refresh.

If you want to delete Active Picklist values you'll have to change the "Inactive" string to "Active" after clicking the bookmark:

Same thing will happen next, you're asked to confirm:

Picklists are deleted and page reloaded. If you have a lot of values it might take some time so starting up the Developer Console is not a bad idea:

Deleting #262: https://mydomain--sandboxname.csXX.my.salesforce.com/setup/ui/picklist_masterdelete.jsp?id=01J0E000006zu8g&tid=0Nt&pt=0Nt0E0000000RFx&retURL=%2F0Nt0E0000000RFx&deleteType=0&_CONFIRMATIONTOKEN=VmpFPSxNakF4T0Mwd09TMHhORlF4TXpvd016bzBPUzR4TmpWYSxEY29WbGQtQUZ2NFM1SFM0Y3ZvNUpmLFpXSXdPRFZq
massdelete_v2.js:39 
Deleting #263: https://mydomain--sandboxname.csXX.my.salesforce.com/setup/ui/picklist_masterdelete.jsp?id=01J0E000006zu8h&tid=0Nt&pt=0Nt0E0000000RFx&retURL=%2F0Nt0E0000000RFx&deleteType=0&_CONFIRMATIONTOKEN=VmpFPSxNakF4T0Mwd09TMHhORlF4TXpvd016bzBPUzR4TmpWYSxkbHZYWnlodjdFT0xLb0lUT3FaNzJlLFpqUTNNV1Uw
massdelete_v2.js:39 
Deleting #264: https://mydomain--sandboxname.csXX.my.salesforce.com/setup/ui/picklist_masterdelete.jsp?id=01J0E000006zu8i&tid=0Nt&pt=0Nt0E0000000RFx&retURL=%2F0Nt0E0000000RFx&deleteType=0&_CONFIRMATIONTOKEN=VmpFPSxNakF4T0Mwd09TMHhORlF4TXpvd016bzBPUzR4TmpWYSxmd0xkOFBUN1F0VHRkUmlVNmsxQUl0LFltVXlZVE16
massdelete_v2.js:39 
Deleting #265: https://mydomain--sandboxname.csXX.my.salesforce.com/setup/ui/picklist_masterdelete.jsp?id=01J0E000006zu8j&tid=0Nt&pt=0Nt0E0000000RFx&retURL=%2F0Nt0E0000000RFx&deleteType=0&_CONFIRMATIONTOKEN=VmpFPSxNakF4T0Mwd09TMHhORlF4TXpvd016bzBPUzR4TmpaYSwxdnZiTndrNFFIN0R4UEU0SzhBZDM3LE1qSXpZalpq
massdelete_v2.js:39 
Deleting #266: https://mydomain--sandboxname.csXX.my.salesforce.com/setup/ui/picklist_masterdelete.jsp?id=01J0E000006zu8k&tid=0Nt&pt=0Nt0E0000000RFx&retURL=%2F0Nt0E0000000RFx&deleteType=0&_CONFIRMATIONTOKEN=VmpFPSxNakF4T0Mwd09TMHhORlF4TXpvd016bzBPUzR4TmpaYSw5QmxpX1BueEF1SDdSUVRDUHhic2FsLE9XVXdNRFF3
massdelete_v2.js:39 
Deleting #267: https://mydomain--sandboxname.csXX.my.salesforce.com/setup/ui/picklist_masterdelete.jsp?id=01J0E000006zu8l&tid=0Nt&pt=0Nt0E0000000RFx&retURL=%2F0Nt0E0000000RFx&deleteType=0&_CONFIRMATIONTOKEN=VmpFPSxNakF4T0Mwd09TMHhORlF4TXpvd016bzBPUzR4TmpaYSx2bjdkSUxRamljYUR4dTBlZzlSMmYyLFlqY3pNR016 

I deleted 350 picklist values in just over 2 minutes so it will not take forever, next step for this script would be to add a spinning progressbar and some bells and whistles but for now this at least solves the problem.

Since you need to have at least 1 value in a Value Set it will not be able to delete all of the Active picklist values but at least you'll save some mouse clicks.

You'll get a warning in the browser that synchronous XML requests are deprecated:

Running the requests asynchronous works fine but the browser will be super swamped if you're deleting hundreds of picklist values so this one is better. When the day finally comes and support for synchronous XML requests are removed I'll make sure to update this but until then this hack is good enough.

Visualise Big Object data in a Lightning Component

Good evening,

In my previous post (Upgrade your Electric Imp IoT Trailhead Project to use Big Objects
) I showed how you can use Big Objects to archive data and now I will show how you can visualise the data in a Lightning Component.

So now we have big objects being created but the only way to see them is by executing a SOQL query in the Developer Console (SELECT DeviceId__c, Temperature__c, Humidity__c, ts__c FROM Fridge_Reading_History__b).

I have created a Lightning Component that uses an Apex Class to retrieve the data.

Lets start with a screen shot on how it looks and then post the wall of code

And in Salesforce1

And here’s the code:
Lightning Component




    
    
    
    
    
    
    
    

Controller

/**
 * Created by Johan Karlsteen on 2017-10-08.
 */
({
    doinit : function(component,event,helper){
        var today = new Date();
        component.set("v.today", today.toISOString());
        console.log(document.documentElement);
        component.set("v.width", document.documentElement.clientWidth);
        component.set("v.height", document.documentElement.clientHeight);
        helper.refreshData(component,event,helper);
    },
    refreshData : function(component,event,helper) {
        helper.refreshData(component,event,helper);
    }
})

Helper

/**
 * Created by Johan Karlsteen on 2017-10-08.
 */
({
        addData : function(chart, labels, data) {
            chart.data.labels = labels;
            chart.data.datasets[0] = data[0];
            chart.data.datasets[1] = data[1];
        },
        redrawData : function(component, event, helper, readings, chart, datasets) {
            helper.addData(chart, readings.ts, datasets);
            chart.update();
        },
        displayData : function(component, event, helper, readings) {
            var datasets = [readings.temperature, readings.humidity];
            var chart = window.myLine;
            if(chart != null) {
                helper.redrawData(component,event,helper,readings, chart, datasets);
            }
            var config = {
                type: 'line',
                data: {
                    labels: readings.ts,
                    datasets: [{
                                 label: 'Temperature',
                                 backgroundColor: 'red',
                                 borderColor: 'red',
                                 data: readings.temperature,
                                 yAxisID: "y-axis-1",
                                 fill: false,
                             },
                             {
                                 label: 'Humidity',
                                 backgroundColor: 'blue',
                                 borderColor: 'blue',
                                 data: readings.humidity,
                                 yAxisID: "y-axis-2",
                                 fill: false,
                             }]
                },
                options: {
                    maintainAspectRatio: true,
                    responsive: true,
                    title:{
                        display:false,
                        text:'Temperature'
                    },
                    tooltips: {
                        mode: 'index',
                        intersect: false,
                    },
                    hover: {
                        mode: 'nearest',
                        intersect: true
                    },
                    scales: {
                        yAxes: [{
                            type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
                            display: true,
                            position: "left",
                            id: "y-axis-1",
                        }, {
                            type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance
                            display: true,
                            position: "right",
                            id: "y-axis-2",

                            // grid line settings
                            gridLines: {
                                drawOnChartArea: false, // only want the grid lines for one axis to show up
                            },
                        }],
                    }
                }
            };
            var ctx = document.getElementById("temperature").getContext("2d");
            window.myLine = new Chart(ctx, config);
        },
    refreshData : function(component,event,helper) {
        var spinner = component.find('spinner');
        $A.util.removeClass(spinner, "slds-hide");
        var action = component.get("c.getFridgeReadings");
        var endDate = component.get("v.today");
        var results = component.get("v.results");
        action.setParams({
        	deviceId : "2352fc042b6dc0ee",
        	results : results,
        	endDate : endDate
    	});
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                var fridgereadings = JSON.parse(response.getReturnValue());
                helper.displayData(component,event,helper,fridgereadings);
            }
            var spinner = component.find('spinner');
            $A.util.addClass(spinner, "slds-hide");
        });
        $A.enqueueAction(action);
    }
})

And the Apex Class that fetches the data:

/**
 * Created by Johan Karlsteen on 2017-10-08.
 */

public with sharing class FridgeReadingHistoryController {

    public class FridgeReading {
        public String deviceId {get;set;}
        public List ts {get;set;}
        public List doorTs {get;set;}
        public List door {get;set;}
        public List temperature {get;set;}
        public List humidity {get;set;}
        public FridgeReading(String deviceId) {
            this.deviceId = deviceId;
            this.ts = new List();
            this.doorTs = new List();
            this.door = new List();
            this.temperature = new List();
            this.humidity = new List();
        }
        public void addReading(Fridge_Reading_History__b  fr) {
            addReading(fr.Temperature__c, fr.Humidity__c, fr.ts__c, fr.Door__c);
        }
        public void addReading(Decimal t, Decimal h, DateTime timeStamp, String d) {
            String tsString = timeStamp.format('HH:mm dd/MM');
            this.ts.add(tsString);
            temperature.add(t);
            humidity.add(h);
            Integer doorStatus = d == 'open' ? 1 : 0;
            if(door.size() == 0 || doorStatus != door.get(door.size()-1)) {
                door.add(doorStatus);
                doorTs.add(tsString);
            }
        }
    }

    @AuraEnabled
    public static String getFridgeReadings(String deviceId, Integer results, DateTime endDate) {
        if(results == null) {
            results = 200;
        }
        FridgeReading fr = new FridgeReading(deviceId);
        system.debug('RESULTS: ' +results);
        List frhs = [
                SELECT DeviceId__c, Temperature__c, Humidity__c, Door__c, ts__c
                FROM Fridge_Reading_History__b
                WHERE DeviceId__c = :deviceId AND ts__c <: endDate
                LIMIT :Integer.valueof(results)
        ];
        for (Integer i = frhs.size() - 1; i >= 0; i--) {
            Fridge_Reading_History__b frh = frhs[i];
            fr.addReading(frh);
        }
        return JSON.serialize(fr);
    }
}

The component assumes you have Charts.js as a static resource, mine is here.

There are no test cases anywhere and the code is probably not production grade.

The next step would be to use aggregate functions on the Big Objects to show data over a longer period of time.

Cheers,
Johan

Mass deleting picklist values in Salesforce with AJAX javascript hack

I have written an updated version of this post Mass deleting picklist values in Salesforce with AJAX javascript hack (2018 version)

Today I was gonna do a small change in our Salesforce instance, namely replacing to 1100+ picklists with brand new values. Last time I did this I used Eclipse and just saved the file (Lead.object for example).

But I realized that only new values are added and old ones are not removed and as usual there are number of requests on success.salesforce.com for this feature but it is not implemented yet.

Anyways after Googling I found this article:
http://frombelvideres4thfloor.blogspot.se/2011/05/javascript-hack-to-mass-delete-custom.html
which basically solves the problem by opening new windows.

This seemed to put a lot of stress on my poor MacBook so I edited it to use synchronous AJAX requests instead.


/* Gather all of the a elements on the page. */

var links = document.getElementsByTagName("a");

/* Pick out the Del links. */

var delLinks = new Array();
for (var i = 0; i < links.length; i++) {
  var link = links[i];

  if (link.innerHTML == "Del") {
    /*alert("Del link found!");*/
    /*alert(link.attributes['href'].value);*/
    delLinks[delLinks.length] = link;
  }
}

/* AJAX request each Del link to delete the associated
   picklist value.

   This code can be augmented as desired
   to only delete certain values.

   However, for custom picklists it's probably
   easier to just delete all of the values
   and then re-add the desired values. */

for (var i = 0; i < delLinks.length; i++) {
  var delLink = delLinks[i];
  // Synchronous AJAX style
  xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET",delLink.attributes['href'].value,false);
  xmlhttp.send();
}

The last 3 lines are what I’ve changed and it will call all delete links on a picklist configuration page. To get it running just create a new bookmark in your browser.

javascript:(function()%7Bvar s = document.createElement("script"); s.src = "https://superfredag.com/massdelete.js"; void(document.body.appendChild(s));%7D)()

And replace the url with where you put your javascript file, or use mine if you want to.

Then just go to a picklist edit page and click it, it will take a while to execute but you can see that things are happening by opening Developer Tools (I’m using Chromium) and go to the Network tab.

Use it with caution though, it worked for me but you never know when Salesforce changes the layout. Test in your staging environment first 😉

This helped me remove 1100+ picklist values with very little effort.

Big thanks to  for writing the original post.