Formatting Multi-Select Values in List Formatting

Multi-select values (choice or person fields where you can pick more than one) have traditionally been tough to work with in List Formatting. You can use the values directly but right away you’ll see formatting differences from the standard/default formatting:

“txtContent”: “@currentField.title”
“txtContent”: “@currentField”

Person fields get separated by a semi-colon and a space, choice values are separated by a comma and lose the space. Aw, come on!

Fortunately, you can have some control over how these are separated using the join function. The join function takes in an array as the first parameter and the separator text to use between items. Here are some samples:

ExpressionField ValueResult
“=join(@currentField, ‘, ‘)”[Water,Wine,Beer]Water, Wine, Beer
“=join(@currentField, ‘|’)” [Water,Wine,Beer] Water|Wine|Beer
“=join(@currentField, ‘ & ‘)” [Water,Wine,Beer] Water & Wine & Beer

That’s pretty slick!

If you read my last post about using the indexOf function to see if your text contains a given value you might think you could use this to see if the selected values contain one of the choices/person fields. Not quite…

Selection Contains a Value

The indexOf function works on strings not arrays. Multi-select fields are arrays internally and the indexOf function will always return -1 when applied directly to the array value.

Fortunately, we can either use the join function shown above or the toString function on our array value first, then we can easily apply our contains logic using the indexOf function!

I recommend using the join function so that you know exactly what you’re going to get since the toString will vary it’s separator depending on the field type (choice or person).

Here are some examples of combining these functions to see if a choice is selected:

ExpressionField ValueResult
“=if(indexOf(join(@currentField,”),’Egg’) != -1, ‘Yes’,’No’)” [Egg,Hat] Yes
“=if(indexOf(join(@currentField,”),’Hat’) != -1, ‘Yes’,’No’)” [Egg,Hat] Yes
“=if(indexOf(join(@currentField,”),’Egg’) != -1, ‘Yes’,’No’)” [Hat,Dog] No

We can use this technique within our formats to create some very interesting visualizations:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
  "elmType": "div",
  "style": {
    "font-size": "16px"
  },
  "children": [
    {
      "elmType": "span",
      "attributes": {
        "title": "Water",
        "iconName": "Precipitation",
        "class": "='ms-fontColor-' + if(indexOf(join(@currentField,''),'Water') != -1, 'themeDark', 'neutralLight')"
      },
      "style": {
        "padding": "0 2px"
      }
    },
    {
      "elmType": "span",
      "attributes": {
        "title": "Coffee",
        "iconName": "CoffeeScript",
        "class": "='ms-fontColor-' + if(indexOf(join(@currentField,''),'Coffee') != -1, 'themeDark', 'neutralLight')"
      },
      "style": {
        "padding": "0 2px 0 0"
      }
    },
    {
      "elmType": "span",
      "attributes": {
        "title": "Wine",
        "iconName": "Wines",
        "class": "='ms-fontColor-' + if(indexOf(join(@currentField,''),'Wine') != -1, 'themeDark', 'neutralLight')"
      },
      "style": {
        "padding": "0 2px"
      }
    },
    {
      "elmType": "span",
      "attributes": {
        "title": "Beer",
        "iconName": "BeerMug",
        "class": "='ms-fontColor-' + if(indexOf(join(@currentField,''),'Beer') != -1, 'themeDark', 'neutralLight')"
      },
      "style": {
        "padding": "0 2px"
      }
    },
    {
      "elmType": "span",
      "attributes": {
        "title": "\"Juice\"",
        "iconName": "TestBeaker",
        "class": "='ms-fontColor-' + if(indexOf(join(@currentField,''),'\"Juice\"') != -1, 'themeDark', 'neutralLight')"
      },
      "style": {
        "padding": "0 2px"
      }
    }
  ]
}

PnP Sample: multi-choice-icons

Now you can easily work with multi-select fields and apply dynamic formats that can take these fields from strings of text to meaningful visualizations.

Love List Formatting?

Join the Bi-weekly (every other Thursday) SharePoint Patterns and Practices special interest group for general development call where I will be presenting a new List Formatting Quick Tip on each call!

Also, come get the full picture in my sessions about List Formatting at the SharePoint Conference in Las Vegas in May, or the European Collaboration Summit in Germany in May:

Formatting Values Using “Contains” in List Formatting

In my last post, I demonstrated applying conditional formats when text starts with a given value. But what if you just want to know if your text contains that value (beginning, middle, end, wherever)?

We can use that same indexOf function provided by List Formatting. This function tells you the first index (starting character position) of some text within your text. The index starts at 0 and if the text isn’t found then the result is -1. Here are some sample inputs and results:

ExpressionResult
“=indexOf(‘Unreliable Peanut’, ‘U’)” 0
“=indexOf(‘Unreliable Peanut’, ‘e’)” 3
“=indexOf(‘Unreliable Peanut’, ‘reliable’)” 2
“=indexOf(‘Unreliable Peanut’, ‘p’)” -1
“=indexOf(‘Unreliable Peanut’, ‘tasty’)” -1

As you can see, you can pass in a single character or a whole word/phrase. You can also see that this function is case-sensitive.

How does knowing the index help us? The key is that the result is always -1 when the value is not contained within the text. So we can reverse that logic to know when our text contains a value.

Here’s some examples:

ExpressionField ValueResult
“=if(indexOf(@currentField, ”) != -1, ‘Yes’, ‘Nope’)” DoneYes
“=if(indexOf(@currentField, ”) != -1, ‘Yes’, ‘Nope’)” Project DoneYes
“=if(indexOf(@currentField, ”) != -1, ‘Yes’, ‘Nope’)” So Done!!Yes
“=if(indexOf(@currentField, ”) != -1, ‘Yes’, ‘Nope’)” In ProgressNope

Let’s see it in action! Here’s a simple format using the logic above to turn the text red whenever the field contains the word “dead”:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
  "elmType": "div",
  "txtContent": "@currentField",
  "attributes": {
    "class": "=if(indexOf(@currentField,'dead') != -1, 'ms-fontColor-redDark','')"
  }
}

But what about that last entry? Remember the indexOf function is case-sensitive which may be exactly what you want sometimes. But in this case, that capital D is really messing us up.

Case-Insensitive Contains

Fortunately, we can combine our indexOf function with another function provided by List Formatting, toLowerCase to negate the casing issue.

toLowerCase takes one text parameter and returns that value in all lowercase. So now we can wrap our field value in toLowerCase and always search using a lower case value:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
  "elmType": "div",
  "txtContent": "@currentField",
  "attributes": {
    "class": "=if(indexOf(toLowerCase(@currentField),'dead') != -1, 'ms-fontColor-redDark','')"
  }
}

PnP Sample: text-contains

Aw Yeah!!!

Now your formats can be even more dynamic and awesome! In my next post, we will take this idea even further! Wowee!

Love List Formatting?

Join the Bi-weekly (every other Thursday) SharePoint Patterns and Practices special interest group for general development call where I will be presenting a new List Formatting Quick Tip on each call!

Also, come get the full picture in my sessions about List Formatting at the SharePoint Conference in Las Vegas in May, or the European Collaboration Summit in Germany in May:

Formatting Values Using “Starts With” in List Formatting

Applying conditional formats based on the value of your field is pretty straightforward in view and column formatting. But what if you only care if your field’s value starts with a given value?

Most formats rely on knowing all possible values and providing conditions and formats for them. This can quickly get out of hand and in many cases just isn’t feasible.

For instance, what if we wanted to show the flag of a country based on the international calling code portion of a phone number? There are millions of phone numbers in the world and trying to create conditions to match all of them is a terrible idea. But we don’t care about the whole phone number, just the calling code part at the beginning. That’s that +1 for US numbers or +39 for Italian numbers, etc.

List Formatting provides a function called indexOf. This function tells you the first index (starting character position) of some text within your text. The index starts at 0 and if the text isn’t found then the result is -1. Here are some sample inputs and results:

ExpressionResult
“=indexOf(‘Old Lady Wigs’, ‘O’)”0
“=indexOf(‘Old Lady wigs’, ‘d’)”2
“=indexOf(‘Old Lady wigs’, ‘Lady’)” 4
“=indexOf(‘Old Lady wigs’, ‘o’)” -1
“=indexOf(‘Old Lady wigs’, ‘rock’)” -1

As you can see, you can pass in a single character or a whole word/phrase. You can also see that this function is case-sensitive.

So, how do we use this to apply a format based on our text starting with a given value? The key is to look for that 0 index. This means that our value is at the very beginning of the text.

Using this same logic, we can check a given phone number for the presence of the Italian international calling code doing something like this:

ExpressionField ValueResult
“=if(indexOf(@currentField, ‘+39’) == 0, ‘Italy’, ‘?’)” +3933587254Italy
“=if(indexOf(@currentField, ‘+39’) == 0, ‘Italy’, ‘?’)” +13175558697?

Whoo!!

Now we can nest those conditions together to detect multiple calling codes:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
  "elmType": "div",
  "children": [
    {
      "elmType": "img",
      "attributes": {
        "src": "='http://flags.fmcdn.net/data/flags/h20/' + if(indexOf(@currentField,'+358')==0,'fi', if(indexOf(@currentField,'+61')==0,'au', if(indexOf(@currentField,'+46')==0,'se', if(indexOf(@currentField,'+47')==0,'no', if(indexOf(@currentField,'+7')==0,'ru', if(indexOf(@currentField,'+32')==0,'be', if(indexOf(@currentField,'+31')==0,'nl', if(indexOf(@currentField,'+43')==0,'at', if(indexOf(@currentField,'+353')==0,'ie', if(indexOf(@currentField,'+39')==0,'it', 'us')))))))))) + '.png'",
        "title": "=if(indexOf(@currentField,'+358')==0,'Finland', if(indexOf(@currentField,'+61')==0,'Australia', if(indexOf(@currentField,'+46')==0,'Sweden', if(indexOf(@currentField,'+47')==0,'Norway', if(indexOf(@currentField,'+7')==0,'Russia', if(indexOf(@currentField,'+32')==0,'Belgium', if(indexOf(@currentField,'+31')==0,'Netherlands', if(indexOf(@currentField,'+43')==0,'Austria', if(indexOf(@currentField,'+353')==0,'Ireland', if(indexOf(@currentField,'+39')==0,'Italy', 'USA'))))))))))"
      },
      "style": {
        "max-width": "23px",
        "padding": "0 6px 0 0"
      }
    },
    {
      "elmType": "span",
      "txtContent": "@currentField"
    }
  ]
}

PnP Sample: text-startswith-callingcodes

This opens up all sorts of possibilities for making some really smart formats. Stick around for my next post where we’ll take this concept even further!

Love List Formatting?

Join the Bi-weekly (every other Thursday) SharePoint Patterns and Practices special interest group for general development call where I will be presenting a new List Formatting Quick Tip on each call!

Also, come get the full picture in my sessions about List Formatting at the SharePoint Conference in Las Vegas in May, or the European Collaboration Summit in Germany in May:

Making a Game in PowerApps – PowerPush: Dungeon Explorer 3000!

During my day job I create PowerApps fairly regularly for both clients and internal use. The platform has come a long way in a very short amount of time and has gone from interesting idea to amazing enterprise accelerator. So what did I decide to do with all that enterprise capability? Create a game of course!

PowerPush: Dungeon Explorer 3000!

PowerPush is a Sokoban clone made entirely in PowerApps. It has 6 levels, 4 playable characters, music, sound effects, supports right and left handed players, and has only 4 screens.

Fair warning, there’s a lot of sweet techno loops

PowerPush is open source (in as much as PowerApps can be) and you can import it into your tenant to give it a whirl and take a peek under the hood. There are no data connections at all, so it really is as simple as importing and playing.

Get it here: PowerPush.zip

Why?

To push myself and learn some stuff I wouldn’t necessarily learn by creating another form wrapper app. Believe me, I learned a lot! Number one thing I learned was that PowerApps really isn’t a great platform for making games. However, I also learned several really cool techniques (“wonky hacks” might be a better term for several of them). I’ll talk more about some of these below but I’ll also be following up with some individual tips and tricks in the next few weeks.

How it works

There are a lot of pieces to this app, and the best way to understand it is to just download it and take a look. But here are some details about each of the screens:

Screen 1: Main Menu

When the app starts, several global variables and collections are initialized. The most important of which is the levels global variable. This is an array of level objects that define the tiles to be used and details about where the crates start and where they should end up:

The levels object is later processed into a set of context variables and collections to manage current state for the active level and provide details about which image to use for each tile and whether those tiles are solid, etc.

The main menu has some animation based on a timer, but otherwise there are simple controls to set global variables used on other screens (like the character).

Screen 2: Level Start

The level start screen simply shows the current level details along with a randomly selected “tip”. This screen uses the fade navigation to create a nice transition into the level. You can also click anywhere on the screen to skip the automatic navigation based on a short timer.

One of the key things it does, however, is to process the level object into some key collections:

Set(activeLevel,Last(FirstN(levels,currentLevel)).Value);
Set(tiles,{
    background: ForAll(activeLevel.background,{
        img: LookUp(imgMap,id=Left(Value,2),img),
        solid: Right(Value,1) = "1"
    })
});
ClearCollect(crates, ForAll(activeLevel.start.crates, {x:Value.x, y:Value.y}));
ClearCollect(torches, ForAll(activeLevel.start.crates, { on: false}));
UpdateContext({tip:First(Shuffle(tips)).Value})

More details will later be pulled out of the activeLevel object to set up initial state for the player, but the info above determines how many crates are visible and where they are along with key information about the tiles. By using an image map collection we can use the strings setup in the level (first 2 characters are the tile image and the last indicates if the tile should be “solid”).

This step greatly simplifies level design by making it a simple array of strings so that you can almost visualize the level while you design it. By processing it here, we get easily usable values in a collection that tiles can reference details from by index.

This is how we can have one screen for the main game with any number of levels! This lets us simply bind to a data model and write logic (movement) to update that model. This is the area that PowerApps really shines.

Additionally, we could easily provide multiple image maps to create “themes” for levels to allow the same logic to apply a completely different tile set. The tile objects could also be extended to provide an additional layer of tiles for foreground elements if we wanted using nearly the same logic.

Screen 3: Main Game

This is where all the levels are shown. The tile system is based on 140 images laid out in 10 rows of 14 columns. Each of these tile image controls hijack their own BorderThickness property to store their “index” value. This works because the BorderStyle is set to None (so that value isn’t used).

This “index” value is used to position each of the individual tiles (the number of tiles, size, rows, columns, etc. can all be changed through some simple global variables). The “index” is also used to pull what the image should be by referencing the tiles object’s background array (shown above).

While not easy, you can reference an item within a collection by index by using this non-obvious formula:

Last(FirstN(tiles.background,tile043.BorderThickness)).img

The actual movement logic is handled in a single spot. There aren’t really global functions in PowerApps, but I’ve got something pretty close in that I’ve added a timer with a Duration value of 0. Then I set the AutoStart to a context variable. In the OnTimerEnd action I update any variables as needed and finally set that same AutoStart context variable to false.

So now to call that “global function” I simply need to call an UpdateContext function. In that call I update any “parameter” variables the global function will reference and set the AutoStart variable to true. For instance, here is the OnSelect for the Left button of the virtual D-Pad:

UpdateContext({requestedMove:{x:-1,y:0,rot:ImageRotation.Rotate180},evaluateMove:true})

Just to show you the power of the “global function” approach, here is the OnSelect for the Down button of the virtual D-Pad:

UpdateContext({requestedMove:{x:0,y:1,rot:ImageRotation.Rotate90},evaluateMove:true})

Both of these calls are nearly identical with the only exception being the “parameter” variable requestedMove.

The key part, however, is that evaluateMove:true. This will cause the timer to start and because it has a Duration of 0 it immediately performs it’s OnTimerEnd actio. This was really important because the move evaluation logic is pretty complicated and I really didn’t want to cut and paste it with some minor tweaks between 4 different buttons. That would be pretty hard to maintain.

The actual move evaluation uses a series of If statements and looks at the tiles collection to determine the current positioning of objects to determine if the requested position of the player is open and what should happen (push a crate, play a grunt since he hit a wall, etc.). It’s pretty convoluted, but you can see that some pretty complex logic can be performed to update our positioning elements (which specific controls like the player or the crates are bound too). Because of the power of PowerApps, all of the controls redraw themselves automatically just by our editing the model!

"Handle movement";
"If no wall in this direction";
If(!Last(FirstN(tiles.background,(playerY+requestedMove.y)*14+playerX+requestedMove.x+1)).solid,
    "If no crate in this direction";
    If(CountIf(crates,x=playerX+requestedMove.x And y=playerY+requestedMove.y) = 0,
	    "No crate, just move the player";
        UpdateContext({playerX:playerX+requestedMove.x, playerY:playerY+requestedMove.y, playerRot:requestedMove.rot, playerPushing:false,playSndWalk:true});
        Reset(audWalk),
		"There is a crate, see if it is up against a wall";
		If(!Last(FirstN(tiles.background,(playerY+requestedMove.y*2)*14+playerX+(requestedMove.x*2)+1)).solid,
			"Not against a wall, check for another crate";
			If(CountIf(crates,x=playerX+(requestedMove.x*2) And y=playerY+(requestedMove.y*2)) = 0,
				"Push that crate!";
				UpdateIf(crates, x=playerX+requestedMove.x And y=playerY+requestedMove.y, {x:playerX+(requestedMove.x*2),y:playerY+(requestedMove.y*2)});
				UpdateContext({playerX:playerX+requestedMove.x, playerY:playerY+requestedMove.y, playerRot:requestedMove.rot, playerPushing:true,playSndWalk:true,playSndPush:true});
                Reset(audWalk);
                Reset(audPush),
				"Can't move the crate or player (crate against a crate), but update player look";
				UpdateContext({playerRot:requestedMove.rot, playerPushing:true, playSndGrunt:true});
                Reset(audGrunt)
			),
			"Can't move the crate or player (crate against a wall), but update player look";
			UpdateContext({playerRot:requestedMove.rot, playerPushing:true, playSndGrunt:true});
            Reset(audGrunt)
		)
    ),
	"Can't move the player (wall), but update player look";
	UpdateContext({playerRot:requestedMove.rot, playerPushing:false});
    If(Rand()<.5,
        UpdateContext({playSndHuh1:true});
        Reset(audHuh1),
        UpdateContext({playSndHuh2:true});
        Reset(audHuh2)
    )
);

"Check crate positions";
ClearCollect(prevTorches, torches);
ClearCollect(torches, ForAll(crates, {on:CountIf(activeLevel.start.targets, x=Value.x And y=Value.y)>0}));
If(CountRows(Filter(torches,on=true))  CountRows(Filter(prevTorches,on=true)),
    UpdateContext({playSndIgnite:true});
    Reset(audIgnite)
);

"Check for Win";
If(CountRows(Filter(torches,on=true)) = CountRows(activeLevel.start.targets),
    UpdateContext({finalDescent:true});
    Reset(audMusicWin)
);
UpdateContext({evaluateMove:false})

Screen 4: Credits

This is a really simple screen that has a gallery bound to a custom credits collection. Then the Y value of the gallery is set based on a timer value to provide the scrolling animation. The timer is on a loop, so the scrolling is too.

This screen really exists to thank the tile artists (used with permission, but still greatly appreciated) and to make my kids happy (they’re the testers listed above).

Lessons Learned

I’m going to post a few different techniques I used as separate articles over the next few weeks, but there are several pain points I identified that I’d love to see solved in the future. Here’s a few:

Need for a this operator

You refer to a control by it’s name in your formulas. This works great. Even better, if you copy one or more controls they’ll get renamed and all the formulas updated. This is super powerful. But… if you suddenly need to update that formula on multiple controls (say 140 tiles), you can’t just select them all and update their formula because all of those formulas are now unique because they refer to specific controls. Simply introducing a this keyword that would refer to the current control in it’s own functions would be awesome.

Related Idea Entry – Go Vote!

Additional Idea Entry – Go Vote!

Audio should pause in the editor

Currently, if you loop and play your audio file it will continue to play even in the editor. This is weird behavior since, for instance, the timer stops running in the editor. This means if you do something like add really annoying techno music (see video above), you’ll never escape it.

Related Idea Entry – Go Vote!

True Image Rotation is needed

Right now you can flip an image control horizontally or vertically and you can rotate in 90 degree increments. It would be far more powerful if you could specify an exact degree of rotation. This would make animations far better.

Related Idea Entry – Go Vote!

Custom Object Props Please

To simplify things, I shoved an index variable for the tiles, crates, torches, etc. into their border width property. This works because I wasn’t using this property (BorderStyle: None). This made creating multiple controls that knew what variables to pull from far simpler. However, it would be even better if we could just associate custom properties of a given type directly on controls instead of hijacking an unused property (since this could have unintended consequences).

Related Idea Entry – Go Vote!

Reference Media by Name (text)

When you assign an image to an image control you do so by the Media variable. This is true for other controls as well. This is fine, but it can make dynamic assignments difficult since the control will need to be able to reference (know about) the actual media variables directly. I solved this using an image map collection so that I could build strings and then find the referenced image that way. This works pretty well, but it would be even easier if there were just a Media(“MediaName”) function.

Related Idea Entry – Go Vote!

Support for Key Down Events

PowerPush was designed for touch controls on a phone using a simulated d-pad. This works, but is clunky for the web. It would be far nicer to move with the arrow keys. Unfortunately, there isn’t a key down event to do this.

Related Idea Entry – Go Vote!

Update

I demoed the game on the SharePoint Patterns and Practices General Development call on 1/24/2019. Check it out here:

Applying Column Formats to Multi-line Text Fields

Multi-line text columns don’t provide the standard “Format this column” option under Column settings in the modern list view column menu. They used to, but now they don’t. Fortunately, there is still a way to apply column formatting to these fields!

There are 2 ways in within the interface to apply column formatting for a column (you can also do it programatically). The easiest and most common way is to use the “Format this column” option mentioned above, but it’s not the only way! The advanced settings for a column provide an additional spot where you can paste your formats. Aw yeah!

The Format

I’m using the text-wrap-format sample from PnP created by Aaron Miao. This is a great format for when you really want to see your full text (instead of the cut-off fade provided by default). I’ve modified the sample slightly to apply the primary theme color for the text to make it even more obvious. Here’s the full format:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
  "elmType": "div",
  "txtContent": "@currentField",
  "style": {
    "white-space": "normal",
    "padding": "11px 0"
  },
  "attributes": {
    "class": "ms-fontColor-themePrimary"
  }
}

Applying the Format

Here’s what our list view looks like before applying column formatting to the multi-line text field, “Synergy”:

To apply column formatting to a multi-line text column:

  • Navigate to the List Settings (Site Actions > List Settings):
  • Choose the multi-line column from the column settings
  • Scroll to the bottom of the multi-line column settings and paste your format in the Column Formatting section:
This same option is also available for site columns!
  • Click OK, then return to your list view and refresh to see the format applied:
The blue is the theme color and was added to the sample just to make it more obvious a format was applied. The key thing to notice is that the full text is now shown.
  • Weep at the beauty of thy column!

NOTE – List Formatting encodes values prior to rendering which makes the use of enhanced (rich text) multi-line fields basically unusable in your formats. These values come back as HTML and that HTML will be encoded and then displayed inline with your values. It is NOT recommended to use Rich text fields in your formats.

Update

See this demoed on the PnP Call (Live from MVP Summit):

Love List Formatting?

Join the Bi-weekly (every other Thursday) SharePoint Patterns and Practices special interest group for general development call where I will be presenting a new List Formatting Quick Tip on each call!

Also, come get the full picture in my sessions about List Formatting at the SharePoint Conference in Las Vegas in May, or the European Collaboration Summit in Germany in May:

New List Formatting Magic String @currentWeb!

There are several special string values that can be used within both view and column formatting. The most common is, of course, @currentField which will return the value of the field you are formatting in column formatting or the title field in view formatting.

These “magic strings” are placeholders for contextual information that are replaced when the format is applied. For instance, @now will be replaced with the exact date/time the format is rendered. This is really helpful to provide dynamic formats.

While poking around today, I found a new one! @currentWeb can now be used to return the absolute url for the site! This is the equivalent of the page context’s webAbsoluteUrl.

Why is this exciting? Previously, you had 2 options when trying to link to something on your site or pulling in an image and they each had major drawbacks:

  • Hardcode the Base URL (https://tenant.sharepoint.com/yourresource)
    • Pro: You’ll always get the image/link you wanted
    • Con: Your format can’t be reused on other sites without manually fixing these links
  • Use a Relative URL (../../yourresource)
    • Pro: Your format is reusable across sites
    • Con: Your URLs are dependent on your relative location. So if someone uses your format within a web part on a different level of your site (folder), your URLs could break

Now, by using @currentWeb, you can have all the good with none of the bad!

For instance, just recently I demoed a quick tip on the PnP call that showed you how to use a relative URL to reference a local image in order to keep the format generic enough to be used with PnP Remote Provisioning. Now my dots can just be replaced with @currentWeb!

Here’s the original relative URL in the contenttype-format view formatting sample:

  "elmType": "img",
  "attributes": {
    "src": "='../../Shared Documents/Fruit/' + if([$ContentType]=='Apple',[$AppleType],[$OrangeType]) + '.png'"
  },

Again, that works fine as long as the format is being applied at the same relative distance from the image files. But now, we can just write:

  "elmType": "img",
  "attributes": {
    "src": "=@currentWeb + '/Shared Documents/Fruit/' + if([$ContentType]=='Apple',[$AppleType],[$OrangeType]) + '.png'"
  },

Now, the resulting URL will always resolve to my images regardless of where in the site my format is being rendered!

Here’s an even simpler example for when I want to link to a document in my documents library:

{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
  "elmType": "a",
  "attributes": {
    "href": "=@currentWeb + '/Shared Documents/MyDoc.pdf"
  }
}

What a great addition! Now List Formatting is even more powerful!

Note: @currentWeb is only supported in SharePoint Online and is not available in SharePoint 2019

Update!

See this demoed on the PnP Call (Live from MVP Summit):

Love List Formatting?

Join the Bi-weekly (every other Thursday) SharePoint Patterns and Practices special interest group for general development call where I will be presenting a new List Formatting Quick Tip on each call!

Also, come get the full picture in my sessions about List Formatting at the SharePoint Conference in Las Vegas in May, or the European Collaboration Summit in Germany in May:

Ramp up on SharePoint Framework Extensions at SPC 2019

I will be speaking at the SharePoint Conference 2019 in Las Vegas, May 21st-23rd! I’m honored to have the opportunity to talk about two of my favorite SharePoint subjects: List Formatting AND SharePoint Framework (SPFx) Extensions!

I’m, of course, super excited to be able to talk about List Formatting (see my previous post for details about that session). I am a huge proponent of using view and column formatting to customize your modern list views. There’s so much you can accomplish using simple JSON. But, I’m also fully aware of the limitations. When you hit those limitations, that’s the perfect time to use the SharePoint Framework.

SharePoint Framework Extensions allow you to not only customize your fields (Field Customizers) to take them far beyond what Column Formatting can do, they provide extensibility and customization to modern pages using the same awesome tooling and APIs available for client-side web parts.

Need a custom footer or header? SPFx Extension. Need to provide toast notifications? SPFx Extension. Need to add a custom menu option to the action bar or the context menu? SPFx Extension. Need to add your own analytics to every page? SPFx Extension. Need to create custom field renderings that provide interactivity? SPFx Extension.

I love the SharePoint Framework. Client-side web parts are awesome and they get most of the attention (they also came out first) and I’ve created several. But, my favorite part of SPFx are Extensions. They are often easier to create than web parts (depending on how much interface you actually need), and they provide some really awesome deployment models that can really simplify global customizations across your tenant.

Understanding SharePoint Framework (SPFx) Extensions

SharePoint Framework Extensions are the replacement for Custom Actions, JS Link, and more for Modern Pages in Office 365 and SharePoint 2019. Whether you’ve started experimenting with the SharePoint Framework or not, come find out exactly what the Extensions are, when to use them, limitations, and advantages. SharePoint Framework Extensions are not only powerful and flexible tools to customize SharePoint, when it comes to modern pages, they’re the only way.

That’s the overview of my session on SPFx Extensions, but an even simpler description is just: SPFx Extensions 101.

At first glance it might seem strange that I would be presenting a session in the Business Apps track (List Formatting) and then this one within the SharePoint Development track. However, both sessions are about enabling you to customize modern pages.

Modern sites and pages are awesome and if you’re still using on-premises SharePoint 2016 or earlier or if your organization is still using a large number of classic sites, you may not even know just how awesome they are yet (another reason to attend SPC). While so many features that used to require customizations to either provide (or provide a more usable/functional implementation) are available and no longer require custom implementations, there are still cases where some minor tweaks are required. These can be anything from dynamic visualizations, to corporate logos and universal links on every page, to 3rd party integrations, to disclaimers for specific site classifications, and more.

Knowing when to use List Formatting vs SPFx Extensions and understanding what both are intended for is a key skill for not just developers, but power users, managers, architects, admins, and more. Both sessions can be attended independently, but I highly encourage you to attend both as they will work together to create a much fuller picture of modern site customization options and techniques.

SharePoint Framework extensions are available in both Office 365 and SharePoint 2019. If you are on either environment or suspect that you will be eventually, this will be an essential session to understand what can be accomplished and the tools needed to do so. This session will serve as both a comprehensive overview for admins, architects, and admins as well as provide implementation details (code/tooling/samples) for developers. I’m really looking forward to this session and hope you’ll join me!

If you still have questions or ideas, I’ll also be helping with the Patterns and Practices (PnP) booth and will be happy to talk with you further! Just come find me. We can just talk, or you can bring your laptop and we’ll take a look at your code together!

Register!

The SharePoint Conference 2018 was fantastic. It’s amazing to be in the presence of so many SharePoint (and related tech like PowerApps, Flow, OneDrive, Yammer, Teams, and more) experts and have them be so approachable. If you are a SharePoint professional (user, admin, manager, developer, etc.) then you should definitely attend SharePoint Conference 2019. Go ahead and register now. You can even save $50 by using the discount code KENT!

See and hear from the experts directly!

Whether you attend my sessions or not, I hope you’ll come and say hello. In the meantime, feel free to reach out on this blog, twitter, or attend one of the PnP Calls!