Alexander Brett

Presenting to #Geomob

11 September 2016

Following on from my post about visualising London, I was invited to speak at #Geomob at the BCS! I had a lot of fun.

If you’re interested in the slideshow, you can see the slides here, and some source code is here. Press p to see my presentation notes - you drive the show by pressing the left mouse button.

The slideshow was driven by remark.js in order to be able to get all of the animations to work nicely. I did in fact have to roll my own fork of remark to pull it off - basically I ripped out the click event handling and replaced it with something intensely hacky, so it’s not code I’ll share for now!

During and after my talk, this happened!

Thank you everyone!

Visualising London

01 June 2016

I recently started working on a couple of interesting data-visualisation projects at work, which lead me to researching visualisation libraries, watching this video, and thinking that it would be cool to do something like that for London.

Happily, data.london.gov.uk exists and has a bizarrely good selection of datasets, so I threw together the following visualisation tool to provide a little window into the state of London, ward-by-ward.

Click to load
Abbey 1.3sq. km Barking and DagenhamAlibon 1.4sq. km Barking and DagenhamBecontree 1.3sq. km Barking and DagenhamChadwell Heath 3.4sq. km Barking and DagenhamEastbrook 3.5sq. km Barking and DagenhamEastbury 1.4sq. km Barking and DagenhamGascoigne 1.1sq. km Barking and DagenhamGoresbrook 1.3sq. km Barking and DagenhamHeath 2sq. km Barking and DagenhamLongbridge 1.6sq. km Barking and DagenhamMayesbrook 1.9sq. km Barking and DagenhamParsloes 1.2sq. km Barking and DagenhamRiver 3.5sq. km Barking and DagenhamThames 7.9sq. km Barking and DagenhamValence 1.3sq. km Barking and DagenhamVillage 2.1sq. km Barking and DagenhamWhalebone 1.6sq. km Barking and DagenhamBrunswick Park 3.2sq. km BarnetBurnt Oak 2.1sq. km BarnetChilds Hill 3.1sq. km BarnetColindale 2.6sq. km BarnetCoppetts 2.7sq. km BarnetEast Barnet 3.7sq. km BarnetEast Finchley 2.5sq. km BarnetEdgware 5.6sq. km BarnetFinchley Church End 2.7sq. km BarnetGarden Suburb 4.7sq. km BarnetGolders Green 3sq. km BarnetHale 5.4sq. km BarnetHendon 2.8sq. km BarnetHigh Barnet 8.3sq. km BarnetMill Hill 9.4sq. km BarnetOakleigh 3.3sq. km BarnetTotteridge 8.8sq. km BarnetUnderhill 4.6sq. km BarnetWest Finchley 2.2sq. km BarnetWest Hendon 3.4sq. km BarnetWoodhouse 2.6sq. km BarnetBarnehurst 2.9sq. km BexleyBelvedere 3.2sq. km BexleyBlackfen and Lamorbey 1.7sq. km BexleyBlendon and Penhill 2.1sq. km BexleyBrampton 2sq. km BexleyChristchurch 2.6sq. km BexleyColyers 1.6sq. km BexleyCrayford 4sq. km BexleyCray Meadows 7.2sq. km BexleyDanson Park 2.7sq. km BexleyEast Wickham 2.2sq. km BexleyErith 4.8sq. km BexleyFalconwood and Welling 1.7sq. km BexleyLesnes Abbey 2.4sq. km BexleyLonglands 2.6sq. km BexleyNorth End 5.7sq. km BexleyNorthumberland Heath 1.8sq. km BexleySt. Mary's 4.4sq. km BexleySt. Michael's 1.4sq. km BexleySidcup 2.7sq. km BexleyThamesmead East 4.8sq. km BexleyAlperton 2.1sq. km BrentBarnhill 3sq. km BrentBrondesbury Park 1.7sq. km BrentDollis Hill 2.3sq. km BrentDudden Hill 1.7sq. km BrentFryent 2.7sq. km BrentHarlesden 1.1sq. km BrentKensal Green 1.1sq. km BrentKenton 2.2sq. km BrentKilburn 0.9sq. km BrentMapesbury 1.4sq. km BrentNorthwick Park 2.7sq. km BrentPreston 2.4sq. km BrentQueens Park 1.5sq. km BrentQueensbury 2.1sq. km BrentStonebridge 4.1sq. km BrentSudbury 2.1sq. km BrentTokyngton 2.8sq. km BrentWelsh Harp 2.3sq. km BrentWembley Central 1.6sq. km BrentWillesden Green 1.5sq. km BrentBickley 4.9sq. km BromleyBiggin Hill 8.3sq. km BromleyBromley Common and Keston 8.3sq. km BromleyBromley Town 3.7sq. km BromleyChelsfield and Pratts Bottom 12.6sq. km BromleyChislehurst 10.4sq. km BromleyClock House 2.3sq. km BromleyCopers Cope 3.3sq. km BromleyCray Valley East 13.3sq. km BromleyCray Valley West 3.9sq. km BromleyCrystal Palace 2.3sq. km BromleyDarwin 29sq. km BromleyFarnborough and Crofton 7sq. km BromleyHayes and Coney Hall 10.8sq. km BromleyKelsey and Eden Park 5.2sq. km BromleyMottingham and Chislehurst North 2.4sq. km BromleyOrpington 4.3sq. km BromleyPenge and Cator 3sq. km BromleyPetts Wood and Knoll 4.3sq. km BromleyPlaistow and Sundridge 4sq. km BromleyShortlands 2.5sq. km BromleyWest Wickham 4.4sq. km BromleyBelsize 0.8sq. km CamdenBloomsbury 1sq. km CamdenCamden Town with Primrose Hill 1.2sq. km CamdenCantelowes 0.8sq. km CamdenFortune Green 1sq. km CamdenFrognal and Fitzjohns 1.5sq. km CamdenGospel Oak 0.7sq. km CamdenHampstead Town 2.4sq. km CamdenHaverstock 0.7sq. km CamdenHighgate 3.2sq. km CamdenHolborn and Covent Garden 1.2sq. km CamdenKentish Town 1sq. km CamdenKilburn 0.7sq. km CamdenKing's Cross 0.6sq. km CamdenRegent's Park 1.3sq. km CamdenSt. Pancras and Somers Town 1.4sq. km CamdenSwiss Cottage 1.3sq. km CamdenWest Hampstead 0.9sq. km CamdenAddiscombe 1.6sq. km CroydonAshburton 2.7sq. km CroydonBensham Manor 1.4sq. km CroydonBroad Green 2.7sq. km CroydonCoulsdon East 7.7sq. km CroydonCoulsdon West 5sq. km CroydonCroham 3.5sq. km CroydonFairfield 3.8sq. km CroydonFieldway 1.5sq. km CroydonHeathfield 8.7sq. km CroydonKenley 6.5sq. km CroydonNew Addington 2.4sq. km CroydonNorbury 2.5sq. km CroydonPurley 4.2sq. km CroydonSanderstead 6.3sq. km CroydonSelhurst 2.3sq. km CroydonSelsdon and Ballards 6.4sq. km CroydonShirley 2.8sq. km CroydonSouth Norwood 2.4sq. km CroydonThornton Heath 1.7sq. km CroydonUpper Norwood 2.6sq. km CroydonWaddon 3.6sq. km CroydonWest Thornton 2sq. km CroydonWoodside 2.2sq. km CroydonActon Central 1.8sq. km EalingCleveland 2.2sq. km EalingDormers Wells 2.3sq. km EalingEaling Broadway 1.9sq. km EalingEaling Common 2.1sq. km EalingEast Acton 4.3sq. km EalingElthorne 2sq. km EalingGreenford Broadway 2.5sq. km EalingGreenford Green 3.4sq. km EalingHanger Hill 3.3sq. km EalingHobbayne 2.2sq. km EalingLady Margaret 1.5sq. km EalingNorthfield 1.5sq. km EalingNorth Greenford 3.3sq. km EalingNortholt Mandeville 2.8sq. km EalingNortholt West End 3.5sq. km EalingNorwood Green 3.8sq. km EalingPerivale 3.4sq. km EalingSouth Acton 1.7sq. km EalingSouthall Broadway 1.6sq. km EalingSouthall Green 1.6sq. km EalingSouthfield 1.4sq. km EalingWalpole 1.5sq. km EalingBowes 1.5sq. km EnfieldBush Hill Park 2.5sq. km EnfieldChase 16.9sq. km EnfieldCockfosters 10.4sq. km EnfieldEdmonton Green 3.1sq. km EnfieldEnfield Highway 4.6sq. km EnfieldEnfield Lock 3.3sq. km EnfieldGrange 3.3sq. km EnfieldHaselbury 1.8sq. km EnfieldHighlands 5.1sq. km EnfieldJubilee 3.9sq. km EnfieldLower Edmonton 2.2sq. km EnfieldPalmers Green 1.9sq. km EnfieldPonders End 3.5sq. km EnfieldSouthbury 2.9sq. km EnfieldSouthgate 2.7sq. km EnfieldSouthgate Green 2.6sq. km EnfieldTown 2.2sq. km EnfieldTurkey Street 2.3sq. km EnfieldUpper Edmonton 2.6sq. km EnfieldWinchmore Hill 2.8sq. km EnfieldAbbey Wood 2.7sq. km GreenwichBlackheath Westcombe 2.1sq. km GreenwichCharlton 2sq. km GreenwichColdharbour and New Eltham 2.5sq. km GreenwichEltham North 2.6sq. km GreenwichEltham South 4.6sq. km GreenwichEltham West 2.4sq. km GreenwichGlyndon 1.7sq. km GreenwichGreenwich West 2.8sq. km GreenwichKidbrooke with Hornfair 2.4sq. km GreenwichMiddle Park and Sutcliffe 3.3sq. km GreenwichPeninsula 4.5sq. km GreenwichPlumstead 2.3sq. km GreenwichShooters Hill 3.8sq. km GreenwichThamesmead Moorings 4.8sq. km GreenwichWoolwich Common 2.6sq. km GreenwichWoolwich Riverside 3.3sq. km GreenwichAddison 0.6sq. km Hammersmith and FulhamAskew 0.8sq. km Hammersmith and FulhamAvonmore and Brook Green 0.9sq. km Hammersmith and FulhamCollege Park and Old Oak 3.4sq. km Hammersmith and FulhamFulham Broadway 0.7sq. km Hammersmith and FulhamFulham Reach 0.9sq. km Hammersmith and FulhamHammersmith Broadway 1.1sq. km Hammersmith and FulhamMunster 0.6sq. km Hammersmith and FulhamNorth End 0.6sq. km Hammersmith and FulhamPalace Riverside 1.5sq. km Hammersmith and FulhamParsons Green and Walham 0.9sq. km Hammersmith and FulhamRavenscourt Park 1.2sq. km Hammersmith and FulhamSands End 1.4sq. km Hammersmith and FulhamShepherd's Bush Green 1.1sq. km Hammersmith and FulhamTown 0.7sq. km Hammersmith and FulhamWormholt and White City 0.9sq. km Hammersmith and FulhamAlexandra 2.6sq. km HaringeyBounds Green 1.4sq. km HaringeyBruce Grove 0.9sq. km HaringeyCrouch End 1.4sq. km HaringeyFortis Green 2sq. km HaringeyHarringay 1.6sq. km HaringeyHighgate 2.5sq. km HaringeyHornsey 1.1sq. km HaringeyMuswell Hill 1.7sq. km HaringeyNoel Park 1.2sq. km HaringeyNorthumberland Park 1.9sq. km HaringeySt. Ann's 1.1sq. km HaringeySeven Sisters 1.3sq. km HaringeyStroud Green 1.1sq. km HaringeyTottenham Green 1.4sq. km HaringeyTottenham Hale 1.9sq. km HaringeyWest Green 1.4sq. km HaringeyWhite Hart Lane 1.7sq. km HaringeyWoodside 1.5sq. km HaringeyBelmont 1.8sq. km HarrowCanons 5.6sq. km HarrowEdgware 1.4sq. km HarrowGreenhill 1.7sq. km HarrowHarrow on the Hill 3.6sq. km HarrowHarrow Weald 4.6sq. km HarrowHatch End 3.3sq. km HarrowHeadstone North 3.3sq. km HarrowHeadstone South 1.5sq. km HarrowKenton East 1.3sq. km HarrowKenton West 1.8sq. km HarrowMarlborough 1.6sq. km HarrowPinner 3.3sq. km HarrowPinner South 2.3sq. km HarrowQueensbury 1.6sq. km HarrowRayners Lane 1.5sq. km HarrowRoxbourne 1.5sq. km HarrowRoxeth 1.6sq. km HarrowStanmore Park 4.5sq. km HarrowWealdstone 1.2sq. km HarrowWest Harrow 1.5sq. km HarrowBrooklands 4.2sq. km HaveringCranham 6.6sq. km HaveringElm Park 3.7sq. km HaveringEmerson Park 4.6sq. km HaveringGooshays 7.8sq. km HaveringHacton 2.5sq. km HaveringHarold Wood 7.6sq. km HaveringHavering Park 9.8sq. km HaveringHeaton 3.4sq. km HaveringHylands 2.9sq. km HaveringMawneys 3sq. km HaveringPettits 4sq. km HaveringRainham and Wennington 16.9sq. km HaveringRomford Town 2.9sq. km HaveringSt. Andrew's 2.7sq. km HaveringSouth Hornchurch 6.9sq. km HaveringSquirrel's Heath 2.7sq. km HaveringUpminster 22.5sq. km HaveringBarnhill 2.1sq. km HillingdonBotwell 4.4sq. km HillingdonBrunel 3.3sq. km HillingdonCavendish 2sq. km HillingdonCharville 3sq. km HillingdonEastcote and East Ruislip 4.1sq. km HillingdonHarefield 15.2sq. km HillingdonHeathrow Villages 23.5sq. km HillingdonHillingdon East 3.6sq. km HillingdonIckenham 6.2sq. km HillingdonManor 2sq. km HillingdonNorthwood 7.4sq. km HillingdonNorthwood Hills 3.3sq. km HillingdonPinkwell 2.7sq. km HillingdonSouth Ruislip 7.4sq. km HillingdonTownfield 3.5sq. km HillingdonUxbridge North 4.6sq. km HillingdonUxbridge South 3.1sq. km HillingdonWest Drayton 3.5sq. km HillingdonWest Ruislip 4.4sq. km HillingdonYeading 2.3sq. km HillingdonYiewsley 4sq. km HillingdonBedfont 4.4sq. km HounslowBrentford 3.2sq. km HounslowChiswick Homefields 2.3sq. km HounslowChiswick Riverside 2.1sq. km HounslowCranford 2.7sq. km HounslowFeltham North 3.4sq. km HounslowFeltham West 3.2sq. km HounslowHanworth 3.2sq. km HounslowHanworth Park 3.7sq. km HounslowHeston Central 1.7sq. km HounslowHeston East 2.1sq. km HounslowHeston West 3.8sq. km HounslowHounslow Central 1.7sq. km HounslowHounslow Heath 2.8sq. km HounslowHounslow South 1.8sq. km HounslowHounslow West 1.6sq. km HounslowIsleworth 2sq. km HounslowOsterley and Spring Grove 6.3sq. km HounslowSyon 2.9sq. km HounslowTurnham Green 1.8sq. km HounslowBarnsbury 0.8sq. km IslingtonBunhill 1.1sq. km IslingtonCaledonian 1.1sq. km IslingtonCanonbury 0.8sq. km IslingtonClerkenwell 0.9sq. km IslingtonFinsbury Park 0.9sq. km IslingtonHighbury East 1sq. km IslingtonHighbury West 1.1sq. km IslingtonHillrise 0.8sq. km IslingtonHolloway 1sq. km IslingtonJunction 1sq. km IslingtonMildmay 0.8sq. km IslingtonSt. George's 0.8sq. km IslingtonSt. Mary's 0.9sq. km IslingtonSt. Peter's 0.8sq. km IslingtonTollington 0.8sq. km IslingtonAlexandra 2.7sq. km Kingston upon ThamesBerrylands 1.5sq. km Kingston upon ThamesBeverley 1.9sq. km Kingston upon ThamesCanbury 1.2sq. km Kingston upon ThamesChessington North and Hook 1.9sq. km Kingston upon ThamesChessington South 7.6sq. km Kingston upon ThamesCoombe Hill 4.4sq. km Kingston upon ThamesCoombe Vale 1.6sq. km Kingston upon ThamesGrove 1.9sq. km Kingston upon ThamesNorbiton 1.3sq. km Kingston upon ThamesOld Malden 1.8sq. km Kingston upon ThamesSt. James 2.2sq. km Kingston upon ThamesSt. Mark's 1.4sq. km Kingston upon ThamesSurbiton Hill 1.7sq. km Kingston upon ThamesTolworth and Hook Rise 2.6sq. km Kingston upon ThamesTudor 1.6sq. km Kingston upon ThamesBishop's 1.5sq. km LambethBrixton Hill 1.1sq. km LambethClapham Common 1.3sq. km LambethClapham Town 1.1sq. km LambethColdharbour 1.2sq. km LambethFerndale 0.9sq. km LambethGipsy Hill 1.6sq. km LambethHerne Hill 2sq. km LambethKnight's Hill 1.5sq. km LambethLarkhall 1.1sq. km LambethOval 1.3sq. km LambethPrince's 1.2sq. km LambethSt. Leonard's 1.4sq. km LambethStockwell 0.9sq. km LambethStreatham Hill 1.3sq. km LambethStreatham South 1.7sq. km LambethStreatham Wells 1.4sq. km LambethThornton 1.1sq. km LambethThurlow Park 1.5sq. km LambethTulse Hill 1sq. km LambethVassall 1.1sq. km LambethBellingham 3.1sq. km LewishamBlackheath 2.3sq. km LewishamBrockley 1.7sq. km LewishamCatford South 1.9sq. km LewishamCrofton Park 1.7sq. km LewishamDownham 2.4sq. km LewishamEvelyn 1.8sq. km LewishamForest Hill 1.8sq. km LewishamGrove Park 2.4sq. km LewishamLadywell 1.6sq. km LewishamLee Green 1.8sq. km LewishamLewisham Central 2.1sq. km LewishamNew Cross 1.8sq. km LewishamPerry Vale 1.7sq. km LewishamRushey Green 1.8sq. km LewishamSydenham 1.7sq. km LewishamTelegraph Hill 1.5sq. km LewishamWhitefoot 2.2sq. km LewishamAbbey 1.4sq. km MertonCannon Hill 2.2sq. km MertonColliers Wood 1.1sq. km MertonCricket Green 3sq. km MertonDundonald 1.2sq. km MertonFigge's Marsh 1.1sq. km MertonGraveney 0.9sq. km MertonHillside 1.2sq. km MertonLavender Fields 1.2sq. km MertonLongthornton 1.5sq. km MertonLower Morden 1.8sq. km MertonMerton Park 1.8sq. km MertonPollards Hill 2.2sq. km MertonRavensbury 1.8sq. km MertonRaynes Park 1.9sq. km MertonSt. Helier 1.8sq. km MertonTrinity 1.1sq. km MertonVillage 6.2sq. km MertonWest Barnes 2sq. km MertonWimbledon Park 2.3sq. km MertonBeckton 6.3sq. km NewhamBoleyn 0.9sq. km NewhamCanning Town North 2.5sq. km NewhamCanning Town South 1.8sq. km NewhamCustom House 2.1sq. km NewhamEast Ham Central 1sq. km NewhamEast Ham North 0.9sq. km NewhamEast Ham South 1.8sq. km NewhamForest Gate North 1.2sq. km NewhamForest Gate South 1.2sq. km NewhamGreen Street East 0.7sq. km NewhamGreen Street West 0.8sq. km NewhamLittle Ilford 1.9sq. km NewhamManor Park 1.3sq. km NewhamPlaistow North 1sq. km NewhamPlaistow South 1.4sq. km NewhamRoyal Docks 4.7sq. km NewhamStratford and New Town 4.2sq. km NewhamWall End 1.4sq. km NewhamWest Ham 1.3sq. km NewhamAldborough 8.6sq. km RedbridgeBarkingside 1.5sq. km RedbridgeBridge 2.6sq. km RedbridgeChadwell 1.5sq. km RedbridgeChurch End 1.5sq. km RedbridgeClayhall 2.5sq. km RedbridgeClementswood 1.3sq. km RedbridgeCranbrook 2.4sq. km RedbridgeFairlop 3.6sq. km RedbridgeFullwell 2.2sq. km RedbridgeGoodmayes 1.6sq. km RedbridgeHainault 5.7sq. km RedbridgeLoxford 1.3sq. km RedbridgeMayfield 1.9sq. km RedbridgeMonkhams 3.1sq. km RedbridgeNewbury 2.1sq. km RedbridgeRoding 2.4sq. km RedbridgeSeven Kings 2.1sq. km RedbridgeSnaresbrook 2sq. km RedbridgeValentines 1.5sq. km RedbridgeWanstead 5.2sq. km RedbridgeBarnes 3sq. km Richmond upon ThamesEast Sheen 5.8sq. km Richmond upon ThamesFulwell and Hampton Hill 1.9sq. km Richmond upon ThamesHam, Petersham & Richmond Riverside 9.4sq. km Richmond upon ThamesHampton 6.9sq. km Richmond upon ThamesHampton North 1.9sq. km Richmond upon ThamesHampton Wick 2.7sq. km Richmond upon ThamesHeathfield 1.9sq. km Richmond upon ThamesKew 3.6sq. km Richmond upon ThamesMortlake and Barnes Common 1.9sq. km Richmond upon ThamesNorth Richmond 2.8sq. km Richmond upon ThamesSt. Margarets & North Twickenham 2sq. km Richmond upon ThamesSouth Richmond 2.7sq. km Richmond upon ThamesSouth Twickenham 1.7sq. km Richmond upon ThamesTeddington 4.3sq. km Richmond upon ThamesTwickenham Riverside 2sq. km Richmond upon ThamesWest Twickenham 2.5sq. km Richmond upon ThamesWhitton 1.6sq. km Richmond upon ThamesBrunswick Park 0.9sq. km SouthwarkCamberwell Green 1sq. km SouthwarkCathedrals 1.8sq. km SouthwarkChaucer 0.8sq. km SouthwarkCollege 3.2sq. km SouthwarkEast Dulwich 1sq. km SouthwarkEast Walworth 1.1sq. km SouthwarkFaraday 0.9sq. km SouthwarkGrange 1.2sq. km SouthwarkLivesey 1.4sq. km SouthwarkNewington 0.8sq. km SouthwarkNunhead 1.3sq. km SouthwarkPeckham 0.9sq. km SouthwarkPeckham Rye 2.3sq. km SouthwarkRiverside 1.3sq. km SouthwarkRotherhithe 1.5sq. km SouthwarkSouth Bermondsey 1sq. km SouthwarkSouth Camberwell 1.3sq. km SouthwarkSurrey Docks 1.9sq. km SouthwarkThe Lane 1.4sq. km SouthwarkVillage 2.8sq. km SouthwarkBeddington North 5sq. km SuttonBeddington South 3.1sq. km SuttonBelmont 2.3sq. km SuttonCarshalton Central 2sq. km SuttonCarshalton South and Clockhouse 7.1sq. km SuttonCheam 3.9sq. km SuttonNonsuch 1.9sq. km SuttonSt. Helier 1.5sq. km SuttonStonecot 2sq. km SuttonSutton Central 1.3sq. km SuttonSutton North 1.9sq. km SuttonSutton South 1.3sq. km SuttonSutton West 1.8sq. km SuttonThe Wrythe 1.5sq. km SuttonWallington North 1.6sq. km SuttonWallington South 1.7sq. km SuttonWandle Valley 2.1sq. km SuttonWorcester Park 2sq. km SuttonCann Hall 0.9sq. km Waltham ForestCathall 1.1sq. km Waltham ForestChapel End 1.9sq. km Waltham ForestChingford Green 3.7sq. km Waltham ForestEndlebury 1.9sq. km Waltham ForestForest 2sq. km Waltham ForestGrove Green 0.9sq. km Waltham ForestHale End and Highams Park 2.3sq. km Waltham ForestHatch Lane 2.4sq. km Waltham ForestHigh Street 3.1sq. km Waltham ForestHigham Hill 3.2sq. km Waltham ForestHoe Street 1.1sq. km Waltham ForestLarkswood 2.1sq. km Waltham ForestLea Bridge 2.6sq. km Waltham ForestLeyton 2sq. km Waltham ForestLeytonstone 1.3sq. km Waltham ForestMarkhouse 1.5sq. km Waltham ForestValley 2sq. km Waltham ForestWilliam Morris 1sq. km Waltham ForestWood Street 1.9sq. km Waltham ForestBalham 1.2sq. km WandsworthBedford 1.8sq. km WandsworthEarlsfield 1.4sq. km WandsworthEast Putney 1.6sq. km WandsworthFairfield 1.5sq. km WandsworthFurzedown 1.4sq. km WandsworthGraveney 1sq. km WandsworthLatchmere 1.1sq. km WandsworthNightingale 1.2sq. km WandsworthNorthcote 1.7sq. km WandsworthQueenstown 3.2sq. km WandsworthRoehampton 4.4sq. km WandsworthSt. Mary's Park 1.4sq. km WandsworthShaftesbury 0.9sq. km WandsworthSouthfields 1.5sq. km WandsworthThamesfield 1.9sq. km WandsworthTooting 1.6sq. km WandsworthWandsworth Common 2.8sq. km WandsworthWest Hill 1.7sq. km WandsworthWest Putney 1.8sq. km WandsworthAbbey Road 1.1sq. km WestminsterBayswater 0.5sq. km WestminsterBryanston and Dorset Square 0.7sq. km WestminsterChurchill 0.6sq. km WestminsterChurch Street 0.4sq. km WestminsterHarrow Road 0.5sq. km WestminsterHyde Park 1sq. km WestminsterKnightsbridge and Belgravia 3.6sq. km WestminsterLancaster Gate 0.6sq. km WestminsterLittle Venice 0.6sq. km WestminsterMaida Vale 0.6sq. km WestminsterMarylebone High Street 1sq. km WestminsterQueen's Park 0.6sq. km WestminsterRegent's Park 2.3sq. km WestminsterSt. James's 3.5sq. km WestminsterTachbrook 0.4sq. km WestminsterVincent Square 0.7sq. km WestminsterWarwick 0.6sq. km WestminsterWestbourne 0.7sq. km WestminsterWest End 2sq. km WestminsterBethnal Green 1.2sq. km Tower HamletsBlackwall and Cubitt Town 1.9sq. km Tower HamletsBow East 1.9sq. km Tower HamletsBow West 1.3sq. km Tower HamletsBromley North 0.6sq. km Tower HamletsBromley South 0.7sq. km Tower HamletsCanary Wharf 1.6sq. km Tower HamletsIsland Gardens 1.5sq. km Tower HamletsLansbury 1.3sq. km Tower HamletsLimehouse 0.5sq. km Tower HamletsMile End 1.2sq. km Tower HamletsPoplar 0.7sq. km Tower HamletsSt Dunstan's 0.7sq. km Tower HamletsSt Katharine's and Wapping 1.5sq. km Tower HamletsSt Peter's 1.1sq. km Tower HamletsShadwell 0.6sq. km Tower HamletsSpitalfields and Banglatown 0.9sq. km Tower HamletsStepney Green 0.6sq. km Tower HamletsWeavers 0.7sq. km Tower HamletsWhitechapel 1sq. km Tower HamletsBrownswood 0.5sq. km HackneyCazenove 0.7sq. km HackneyClissold 1sq. km HackneyDalston 0.5sq. km HackneyDe Beauvoir 0.6sq. km HackneyHackney Central 0.8sq. km HackneyHackney Downs 1sq. km HackneyHackney Wick 1.6sq. km HackneyHaggerston 0.9sq. km HackneyHomerton 0.8sq. km HackneyHoxton East and Shoreditch 1sq. km HackneyHoxton West 0.6sq. km HackneyKing's Park 1.9sq. km HackneyLea Bridge 1.1sq. km HackneyLondon Fields 1sq. km HackneyShacklewell 0.4sq. km HackneySpringfield 1.2sq. km HackneyStamford Hill West 0.7sq. km HackneyStoke Newington 1sq. km HackneyVictoria 0.8sq. km HackneyWoodberry Down 0.9sq. km HackneyAbingdon 0.6sq. km Kensington and ChelseaBrompton and Hans Town 1.1sq. km Kensington and ChelseaCampden 1sq. km Kensington and ChelseaChelsea Riverside 0.7sq. km Kensington and ChelseaColville 0.5sq. km Kensington and ChelseaCourtfield 0.6sq. km Kensington and ChelseaDalgarno 0.9sq. km Kensington and ChelseaEarl's Court 0.5sq. km Kensington and ChelseaGolborne 0.6sq. km Kensington and ChelseaHolland 1sq. km Kensington and ChelseaNorland 0.5sq. km Kensington and ChelseaNotting Dale 0.6sq. km Kensington and ChelseaPembridge 0.4sq. km Kensington and ChelseaQueen's Gate 0.6sq. km Kensington and ChelseaRedcliffe 0.7sq. km Kensington and ChelseaRoyal Hospital 1sq. km Kensington and ChelseaSt Helen's 0.5sq. km Kensington and ChelseaStanley 0.7sq. km Kensington and ChelseaCity of London 3.1sq. km City of London 0.4sq. km 29sq. km

What you’re looking at

London is broken down into 35 boroughs, 630 electoral wards, or 3000ish postcodes. I chose to visualise wards because although there is a huge amount of per-borough data kicking around, having only 35 bubbles doesn’t provide quite the effect I was looking for, and conversely, although having each postcode would be very beautiful and granular, it would be very slow to render for all but the most grunty CPUs. Per-ward data seems to strike a good balance of granularity, data availability, and performance.

Each ward tries to stay close to its geographical location, but can get pushed away as the wards around it grow or shrink - the idea is to give a broad sense of geography, so I think it’s ok that they can get slightly mixed up.

Limitations and choices

Working with the data at hand meant being aware of certain limitations of the data set and my ability to present it. For instance, the first version of this used a data set from 2012, which I subsequently was unable to reconcile with the 2016 Mayoral Election results because of the adjustment of certain wards in 2014. This meant becoming aware of exactly which year and which ward boundaries each dataset I looked at corresponded to. It also means that in this visualisation, the data for pre-2014 has been massaged into a post-2014 shape (this was handled at the source by GLA, not by me, thankfully!).

The locations were generated by taking the entire ONS London Postcode database, grouping by ward, and averaging the postcodes. This is OK for a relatively coarse application like this, but for more precise work a more carefully considered dataset would probably make sense.

I’m also aware that every act of communication exposes the author’s biases and conceptions, whether intentionally or not: for instance, when exposing a set of data as sizes, I have to decide both the minimum and maximum sizes, as well as the scaling function. Data can look much more dramatic with one set of choices than with another:

Prices, scaled linearly Prices, scaled by square root

Similarly, choosing which colours to use to present data about ethnic minority population or socially rented housing could appear to make value judgements about the data presented - for instance, does red signify high or low or good or bad?

Technology

To draw this data, I used d3.js force layout with the wards as nodes. Each ward has three competing forces acting on it: the first is a spring-like force pulling it towards its geographical location, and the second is a very strong short-range force attempting to prevent it overlapping its neighbours, and the third is a charge force which distributes the wards slightly more evenly across space. Choosing this set of forces allows a sense of which part of London is which, whilst allowing different areas to grow and shrink in an organic way. It does mean that wards can get a little mixed up, so the idea is only to give a broad sense of geography.

The data is served as a single SQLite file which is interrogated using SQL.js. This allows a lot of flexibility in loading in data - the ability to do joins and aggregations on-the-fly saves a lot of code and memory, and it means that whilst developing I was able to load in table as and when I wanted to try a new data shape.

Bundling App::SFDC for fun and profit

12 August 2015

Motivation

Whilst installing perl and App::SFDC along with a (quite large) number of dependancies is fun, effective and powerful, it’s not always the best solution for Salesforce deployment tools. When you’re deploying from throwaway AWS instances or sending your tools to developers in foreign countries who want something that just works now, you may want to provide a ready-to-go bundle of code. Fortunately, programs such as PerlApp provide a pretty good way to achieve this.

I’m going to run through how to bundle App::SFDC to a standalone .exe suitable for deploying and retrieving metadata on a windows machine.

Introduction to PerlApp

The idea behind PerlApp is pretty straightforward: you point it at a script, and it calculates the module dependancies and bundles the perl interpreter along with all required modules into a .exe, which can then be run without a local perl installation - essentially, you run perlapp --exe SFDC.exe C:\perl64\site\bin\SFDC.pl .

Loading prerequisites

Of course, when you do this and run the resulting executable, there are some modules missing - it’s hard to detect all of the prerequisites, especially when they’re being dynamically loaded in. Examples of this are that WWW::SFDC loads in modules by running:

for my $module (qw'
    Apex Constants Metadata Partner Tooling
'){
    has $module,
      is => 'ro',
      lazy => 1,
      default => sub {
        my $self = shift;
        require "WWW/SFDC/$module.pm"; ## no critic
        "WWW::SFDC::$module"->new(session => $self);
      };
  }

In a similar way, when you create a screen appender for Log::Log4perl , it quietly loads in Log::Log4perl::Appender::Screen. To fix this sort of issue, we add a few more arguments to perlapp:

perlapp  --add MooX::Options::Role^
 --add App::SFDC::Role::^
 --add Log::Log4perl::Appender::Screen^
 --add WWW::SFDC::^
 --exe SFDC.exe C:\perl64\site\bin\SFDC.pl

Fixing SSL certification

Perl isn’t great at picking up a system’s SSL settings, especially installed certificates - and when the entire purpose of a script is to send HTTPS requests, it’s something that you just have to get right - lest you get errors like 500 C:\Users\ALEXAN~1\AppData\Local\Temp\pdk-alexanderbrett/Mozilla/CA/cacert.pem on disk corrupt at /<C:\Dev\App-SFDC\SFDC.exe>WWW/SFDC.pm line 66..

One successful workaround I’ve found to this sort of error, which works whenever curl is installed, is to use curl’s Certificate Authority file instead of perl’s. You can find this by running curl -v https://login.salesforce.com >nul and looking for the lines like:

* successfully set certificate verify locations:
*   CAfile: C:\Program Files (x86)\Git\bin\curl-ca-bundle.crt

Then, you set HTTPS_CA_FILE=C:\Program Files (x86)\Git\bin\curl-ca-bundle.crt and your HTTPS connections start working again. This amount of manual faffing around is more than most developers or AWS images want to do, and fortunately PerlApp has our back again - we can bind in arbitrary files, and specifiy arbitrary environment variables. Let’s add more arguments to perlapp:

...
--bind certs/cafile.crt[file="C:\Program Files (x86)\Git\bin\curl-ca-bundle.crt",text,mode=666]^
--env HTTPS_CA_FILE=certs/cafile.crt^
...

Binding Retrieve plugins

Since we’re going to be wanting to use App::SFDC::Command::Retrieve, we need to make sure the plugins and manifests mentioned are, in fact, included. By default they are installed to the perl share/ location, and PerlApp won’t see them! This is how to bind in the default values:

...
--bind manifests/base.xml[file=C:\perl64\site\lib\auto\Share\dist\App-SFDC-Metadata\manifests\base.xml,text,mode=666]^
--bind manifests/all.xml[file=C:\perl64\site\lib\auto\Share\dist\App-SFDC-Metadata\manifests\all.xml,text,mode=666]^
--bind plugins/retrieve.plugins.pm[file=C:\perl64\site\lib\auto\Share\dist\App-SFDC-Metadata\plugins\retrieve.plugins.pm,text,mode=777]^
...

We should also ensure any dependencies from retrieve.plugins.pm are loaded:

...
--scan C:\perl64\site\lib\auto\Share\dist\App-SFDC-Metadata\plugins\retrieve.plugins.pm
...

This is the point at which you may want to override these values! If you have specific requirements for your manifests, for the folders you want to retrieve, or anything like that, create your own versions of those files and bundle those in instead.

Why doesn’t it work yet?

This was all great up to v0.13, but now this approach stopped working from v0.14 onwards. At that point I moved from a monolithic everything-in-one package approach to a dynamically loading plugin-oriented architecture, which allows anybody to create a command by naming their package App::SFDC::Command::Foo. The code that makes that happen is:

find
   {
       wanted => sub {push @commands, $1 if m'App/SFDC/Command/(\w*)\.pm'},
       no_chdir => 1
   },
   grep {-e} map {$_.'/App/SFDC'} @INC;

…and this approach is completely broken by PerlApp - when running this, you get the error invalid top directory at /<C:\Dev\App-SFDC\SFDC.exe>File/Find.pm line 472., because PerlApp doesn’t create any recognisable directory structure for bundled modules - it provides an overloaded version of require which gets the required module from somewhere non-obvious.

After trying a few different things, it seems that the simplest way to achieve a nicely-bundled .exe is going to be to write a new script which avoids the pitfalls of detecting commands at runtime. We can, in fact, write a small perl program which writes the script for us (compare the output of this to SFDC.pl - it’s the same idea, but static):

#!perl
use strict;
use warnings;
use 5.12.0;
use App::SFDC;

my $commandArrayDefinition = 'my @commands = ("'
    . (join '","', @App::SFDC::commands) . '");';

say <<'HEAD';
package SFDC;
use strict;
use warnings;
HEAD

say "use App::SFDC::Command::$_;" for @App::SFDC::commands;

say 'my @commands = ("'
        . (join '","', @App::SFDC::commands)
        . '");';

say <<'BODY';

my $usage = join "\n\n",
    "SFDC: Tools for interacting with Salesforce.com",
    "Available commands:",
    (join "\n", map {"\t$_"} @commands),
    "For more detail, run: SFDC <command> --help";

my $command = shift;
exit 1 unless do {
    if ($command) {
        if (my ($correct_command) = grep {/^$command$/i} @commands) {
            "App::SFDC::Command::$correct_command"->new_with_options->execute();
        } else {
            print $usage;
            0;
        }
    } else {
        print $usage;
    }
}
BODY

__END__

Tying it all together

Using perl -x in a batch file, we can combine the perl script-writing script and the call to PerlApp into one easy-to-digest package, by using some syntax like:

perl -x %0 > static_SFDC.pl

perlapp ^
 ...
 --info CompanyName=Sophos;LegalCopyright="This software is Copyright (c) 2015 by Sophos Limited https://www.sophos.com/. This is free software, licensed under the MIT (X11) License"^
 --norunlib --force --exe SFDC.exe static_SFDC.pl

goto :endofperl

#!perl
use strict;

...

__END__

:endofperl

For a full version, I’ve created a gist to play with.

Tags: SFDC Perl

Logging::Trivial - or, why to hold off on that logging module you wrote

03 May 2015

A while back, I wrote WWW::SFDC as well as a few programs calling it, and I wanted the world’s most trivial logging module which still allowed for 5-level (DETAIL, DEBUG, INFO, WARN, ERROR) logging. I couldn’t find anything appropriate on cpan, so I rolled my own and called it Logging::Trivial.

Now, I was pretty happy with this, and got it all ready to be a grown-up cpan module (my first), so I went on prepan and said, guys, what do you think. I wouldn’t call it a slap-down, but I got some pretty robust advice not to publish Yet Another Logging Module, and as a result I decided that I’d sit on it - I wouldn’t refactor it out until I found a suitable replacement, but I wouldn’t publish.

Today I revisited the issue and found Log4Perl’s easy-mode, and I’m very pleased because it does exactly what I want, with very, very little rewriting of code. I ran perl -i.bak -pe 's/Logging::Trivial/Log4Perl ":easy"/; s/DETAIL/TRACE/; s/ERROR/LOGDIE/;' and was essentially done.

I think the moral of the story is that when you’re not quite sure that your solution is going to stand the test of time, wait a while to see whether it does. In my case, it didn’t, but I’m better off for that, and so is cpan.

Tags: Perl

Creating attachments with WWW::SFDC

27 April 2015

At my company, we have a problem with attachments; namely, one of the things we have to do most releases is update some mail merge templates which are stored as attachments against Salesforce records. Historically, this has been a great cause for error; at some point the release manager ends up with a sea of .docx files in some directory or other, having to remember which ones she’s already uploaded, which ones need to be uploaded against which records, and so on.

Checking these files into source control helped; now, instead of a soup of randomly named files kicking around, at least she knows she has the latest version, and has an easy way to ascertain which ones have changed. It’s still a tedious and error-prone manual process though, and one slip-up means that a distributor recieves a million dollars worth of junk on their license schedule.

Fortunately, the correct automation solution is at hand: WWW::SFDC to the rescue. Let’s imagine we’ve been sensible and we’ve stored a load of .docx and .xslx files inside source control already, and with even more foresight, we have named each file the same as the Name on the Doc_Template__c record against which we need to store them. What we need to do is create Attachment records for each changed document against each relevent record.

Let’s automate it in 10 easy steps.

  1. Get the changed files:

    sh git diff --name-only --diff-filter=AM --relative attachments/Doc_Template__c/ # Or, if you'd rather just have everything (for instance, populating a new sandbox) git ls-files attachments/Doc_Template__c/

  2. Basic perl script setup, including making sure creds are set up:

    ```perl use strict; use warnings; use Getopt::Long; use WWW::SFDC::Partner; use File::Slurp ‘read_file’;

    my %creds = ( url => ‘https://login.salesforce.com’ );

    GetOptions ‘p|password:s’ => $creds{password}, ‘url:s’ => $creds{url}, ‘u|username:s’ => $creds{username};

    WWW::SFDC::Partner->instance(creds=>\%creds); ```

  3. Read in the changed files, checking that they exist and trimming whitespace:

    ```perl my @filenames = grep {chomp and -e } <>;

    exit unless scalar @filenames; # if there aren’t any, we have no work to do. ```

  4. Parse the filenames and read the files. We’ll store the file contents as base64-encoded data which is what the Salesforce.com partner API expects up to provide in the body field. Fortunately, SOAP::Lite makes this a breeze, via the SOAP::Data->type() method.

    perl my @documents = map { /([^\/]*)\.(docx|xlsx)$/ ? +{ # +{} forces this to be a hashref, rather than an arbitrary code block name => $1, extension => $2, body => SOAP::Data->name( body => read_file $_ # read_file is exported by File::Slurp and does what it says on the tin )->type('base64') # prepackaged SOAP::Lite magic. } : (); # return empty list. } @filenames;

  5. We’re need the IDs of the Doc_Template__c records to store these against, so we’ll start by constructing a where clause…

    perl my $whereClause = join " OR ", map { "Name='$$_{name}'" } @documents; # that was easy

  6. …and we’ll go and execute that query.

    perl my @parentRecords = WWW::SFDC::Partner->instance()->query( "SELECT Id, Name FROM Doc_Template__c WHERE $whereClause" );

  7. We’re going to need to look up IDs from the name, so we’ll create a hash:

    perl my %parentIds = map { $_->{Name} => $_->{Id} } @parentRecords;

  8. The interesting bit. From each document, create an Attachment suitable for passing into a create call.

    perl my @recordsToCreate = map { $parentIds{$_->{name}} # check that the record exists; ? +{ type => 'Attachment', ParentId => $parentIds{$_->{name}}, name => "$$_{name}.$$_{extension}", body => $_->{body}, } : () } @documents;

  9. Wrap it all up with a create call

    perl WWW::SFDC::Partner->instance()->create(@recordsToCreate);

  10. Put it all together (tada!):

    git diff --name-only --diff-filter=AM --relative attachments/Doc_Template__c/^
     | perl automagic_attachments.pl -u myUsername -p passwordPlusToken
    

Now, this may feel trivial. However, having repeatable, automatic, guaranteed-error-free deployments of templates every month saves up hours of effort on release day, and hours of tracking down bugs later on.

Tags: SFDC Perl

A New Pattern for Apex Triggers

24 April 2015

This is going to be a pretty long post. If you want the meaty bit, scroll down to ‘Solving the problem by using object-orientation properly’

A brief history of trigger patterns

1: The anti-pattern

I feel like an organisation or developer will go through different stages as they learn better ways of dealing with Apex Triggers. A rookie developer in a simple instance with only 501 training might write triggers that look like this:

trigger QuoteTaxUpdate on Quote (before insert, before update) {
  for (Quote q : Trigger.new) {
    if(Trigger.isInsert || Trigger.oldMap.get(q.Id).Value__c != q.Value__c) {
      q.Tax__c = TaxCalculator.calculateTaxes(q);
      // ... more logic goes here
    }
  }
}
trigger QuoteRelatedOpportunityUpdate on Quote (after insert, after update) {
  for (Quote q : Trigger.new) {
    // Do Stuff
  }
}

There are several major problems with this approach:

  • Having multiple triggers on the same object means that it’s hard to isolate the code file causing problematic behaviour
  • It can be hard to debug complex interactions between triggered processes
  • Once you hit a couple of hundred objects with a handful of processes on each, you have a huge number of source code files to track

2: One trigger per object

With this in mind, the tendancy is to combine triggers so that each object has precisely one trigger class:

trigger QuoteTrigger on Quote (before insert, before update, after insert, after update) {
  if (Trigger.isBefore) {
    if (Trigger.isInsert) {
      //Do some things
    } else if (Trigger.isUpdate) {
      //Do other things
    }
  } else if (Trigger.isAfter) {
    if (Trigger.isInsert) {
      // More stuff
    } else if (Trigger.isUpdate) {
      // You're getting the hang of this...
    }
  }
}

Having now refactored your code into some beautiful, well-organised triggers, you notice that the stuff you’re doing in the Before Update and Before Insert is close enough that you want a method to parameterise - but you can’t have methods in triggers. Additionally, this makes it very difficult to unit test a trigger. All this leads to:

3: The Trigger Handler

You move your logic into its own class, defin

public class QuoteTriggerHandler {
  public static void onBeforeUpdate () {
    sharedMethod();
  }
  
  public static void onAfterUpdate () {
    sharedMethod();
  }
  
  // ...
  
  public sharedMethod(){
    if (Trigger.isInsert) {
      // logic goes here
    }
  }
}

This is great, because you can have some unit tests, some shared methods, and so on and so forth. However, you often find that you’re looping through the records once to choose which ones to update the taxes on, again to work out the new owner, a third time for some dynamic sharing rules, and before long you run through the ‘total lines of code executed in one transaction’ governor limit (or your customers complain about your Quotes taking agonising seconds to save).

4: The Advanced Trigger Handler

How do we fix this? We need some architecture. Let’s set it up so that we only iterate over the records once to ascertain which ones are appropriate to our needs, then each process will need to iterate over fewer records. Once you’ve got more than a couple of jobs, this really pays off. Additionally, it’s very easy to create shared and testable logic like this, because you require that each process has its own method which you can unit test to your heart’s content.

public class QuoteTriggerHandler {
	private List<Quote> QuotesForTaxUpdate = new List<Quote>();
	private List<Quote> QuotesForOpportunityOwnerUpdate = new List<Quote>();
	// more lists

	private Boolean DoTaxUpdate = false;
	private Boolean DoOpportunityOwnerUpdate;
	// more toggles

	public void onBeforeUpdate () {
		DoTaxUpdate = true;
		chooseRecords();
		doLogic();
	}

	public void onAfterInsert () {
		DoOpportunityUpdate = true;
		chooseRecords();
		doLogic();
	}

	public void chooseRecords () {
		for (Quote q : Trigger.new) {
			if (DoTaxUpdate && /*conditions*/) {
				QuotesForTaxUpdate.add(q);
			}
			if (DoOpportunityUpdate && /*conditions*/) {
				QuotesForOpportunityUpdate.add(q);
			}
		}
	}

	public void doLogic () {
		if (DoTaxUpdate)
			TaxUpdateLogic(QuotesForTaxUpdate);
		if (DoOpportunityUpdate) 
			OpportunityUpdateLogic(QuotesForOpportunityUpdate);
	}
}

So why don’t I like this? There are several remaining problems.

  • Your trigger handler will probably be somewhere between quite long and REALLY REALLY long. If I’ve identified a bug in a particular operation, I don’t want to sift through a thousand lines of code to find the method implementing that operation, and try to work out whether the bug is there, or in the if clause somewhere inside chooseRecords() (which is probably a hundred lines long all by itself).
  • This pattern is quite complicated, and it’s going to take a good while for your new hires to be confident enough to dive into said several-hundred-lines long chunk of code and make changes, out of sheer fear of breaking things.
  • You’re going to have lots of code which looks similar in the trigger handler for every object, which smells.

Solving the problem by using object orientation properly

A rough outline

If you take another look at the example above, you’ll see that we’ve basically created a contract for each TriggerOperation (I’m starting to outline the names I’ll use in the solution). This is:

  • An OperationName, which tells people what you’re trying to do - for instance, UpdateTaxes or UpdateOpportunityOwners.
  • An InitialLoopOperation, which will normally select an appropriate subset of records for your operation
  • Some OperationLogic, which is executed at the end.

With this is mind, it seems like a TriggerOperation would be a very good candidate for creating a generic class, which could then be extended and overridden, but which crucially would define a standard interface. This means that when I know roughly what’s gone wrong, I only have one small class to check through for code errors, and when a new developer want to write a new process, he knows where to start without reading reams of documentation and still breaking the build for everyone else.

The second part of this puzzle is going to have to be a TriggerOperationDispatcher, which consumes the TriggerOperation implementations and calls their operations as applicable. This class can then be shared between every SObject, reducing duplication and meaning that if you want to add functionality, you can do it once and benefit everywhere (I’ll go into this idea more further on).

Sample Implementation (MKI)

With the introductions out of the way, let’s give an outline of the classes. It’s going to get very code-heavy hereafter:

// This SHOULD have a type parameter to allow derivation for
// a particular object, but that was deprecated in API v26 :(
public virtual TriggerOperation {
	public abstract void initialLoopOperation (SObject obj);

	public void execute() {
		this.logic();
	}

	// protected means that execute() can access the
	// overridden version in the implementation class
	protected abstract void logic();
}
public TriggerOperationDispatcher {

	private List<TriggerOperation> operations = new List<TriggerOperation>();

	private Boolean isUpdate;
	private Boolean isBefore;
	private Map<Id,SObject> oldMap;
	//etc.
	
	public TriggerOperationDispatcher () {
		this(
			Trigger.newMap, Trigger.oldMap, Trigger.isUpdate,
			Trigger.isInsert, Trigger.isDelete, Trigger.isUndelete,
			//etc
		);
	}
	
	// We're going to want to carefully unit test this class, and
	// this constructor is an easy way to simulate trigger conditions
	@testVisible 
	private TriggerOperationDispatcher (Map<Id,SObject> newMap, /*etc*/) {
		this.newMap = newMap;
		//and so on...
	}

	public TriggerOperationDispatcher addOperation (TriggerOperation op) {
		operations.add(op);
		return this; //chainable
	}

	public void dispatch () {
		List<SObject> relevantObjects = (isInsert || isUpdate || isUndelete)
			? newList
			: oldList;
		
		for (SObject obj : relevantObjects)
			for (TriggerOperation op : operations)
				operation.initialLoopOperation(obj);
		
		for (TriggerOperation op : operations)
			operation.execute();
	}
}

Let’s look at how we’d refactor the QuoteTrigger to take account of this:

trigger QuoteTrigger on Quote (before insert, /*blah blah*/) {
	TriggerOperationDispatcher dispatcher = new TriggerOperationDispatcher();
	if (Trigger.isBefore) {
		if (Trigger.isInsert)
			dispatcher.addOperation(new QuoteTaxOperation(Trigger.oldMap))
				.addOperation(new SomeOtherOperation());
		if (Trigger.isUpdate)
			// The rest of this is predictable
	} else if () {
	} //etc.
}

and to make our QuoteTaxOperation:

public class QuoteTaxOperation : TriggerOperation {
	private List<Quote> QuotesForTaxUpdate;
	private Map<Id,Quote> oldMap;

	public QuoteTaxOperation (Map<Id,Quote> oldMap) {
		this.oldMap = oldMap;
	}
	
	public void override initialLoopOperation (SObject obj) {
		Quote q = (Quote)obj;
		if (oldMap == null || q.Value__c != oldMap.get(q.Id).Value__c)
			QuotesForTaxUpdate.add(q);
	}

	protected void logic () {
		for (Quote q : QuotesForTaxUpdate) {
			calculateTax(q);
		}
	}
}

Turning stuff on and off

Let’s say we’ve just updated a sandbox and we’re going to go through every email field and add something to the end, so that our customers don’t get spammed whilst we test. We’ve got an awesome apex script to accomplish this, but if all our trigger run, it’ll take forever to perform the update! We need to turn off the operations. Luckily, most of the framework for this is in place.

What we need is:

  • A catalogue of operations
public enum TriggerOperationName {
	QUOTE_UPDATE_TAX,
	QUOTE_UPDATE_OPPORTUNITY_OWNER,
	...
}
  • Each operation to be named
public virtual class TriggerOperation {
	public TriggerOperationName name;
}
public class QuoteTaxOperation {
	public QuoteTaxOperation (...) {
		this.name = TriggerOperationName.QUOTE_UPDATE_TAX
		...
	}
	
	...
}
  • A static variable to store disabled operations
public class TriggerOperationManager {
	private static Set<TriggerOperationName> disabledOperations = new List<TriggerOperationName>();
	
	private static Boolean allOperationsDisabled = false;
	
	public static void disableOperation (TriggerOperationName op) {
		disabledOperations.add(op);

	public static void disableAllOperations () {
		allOperationsDisabled = true;
	}
	
	public static boolean isOperationDisabled (TriggerOperation op) {
		return (allOperationsDisabled || disabledOperations.contains(op.name));
	}
}
  • A check for whether to use that operation
//in TriggerOperationDispatcher
	public TriggerOperationDispatcher addOperation (TriggerOperation op) {
		if (!TriggerOperationManager.isOperationDisabled(op)) operations.add(op);
		return this; //chainable
	}

… well, that was pretty easy. We can now exercise fine-grained control over whether to turn things on or off. Want to do that bulk job from anonymous apex? Chuck TriggerOperationManager.disableAllOperations() at the top of your script! Of course, you could also create a custom setting so that operations were disabled on certain profiles (such as your data migration user?) or similar.

This demonstrates the power of using object-oriented patterns well. By using inheritance and by having a single shared dispatcher class, we can achieve powerful org-wide changes with only a couple of dozen lines of code.

Logging

It’s obviously good to know what you’ve done in a transaction. What if we modified these classes to help us do that, even when we’ve burnt waaaaaaaay past the 2MB logging limit?

You could create a new object called Operation_Log__c, and a custom setting called Operation_Logging_Settings__c (to temporarily turn on logging per-profile).

Then, create a class called TriggerOperationLogger with a private list of TriggerOperationName and methods markAsExecuted and logExecution. At the end of TriggerOperationDispatcher.dispatch(), add a call to logExecution, and at the end of TriggerOperation.execute() add a call to markAsExecuted.

Again, you could achieve powerful and flexible logging of trigger operations across the entire org with only a handful of changes.

Summary

Most of the apex code I’ve seen fails to leverage even simple object-oriented concepts such as inheritance, but making use them can lead to elegant and powerful designs which save effort and increase maintainability. Give it a try!

Have I got something wrong? Propose an edit to this page on GitHub.

Tags: SFDC Apex