1

I am trying to apply conditional formatting to my calculated field "Days" i would like for the following conditions to trigger the formatting. i cant seem to make it work. below is my code posted

  1. Green <= 90 days
  2. Amber = 91-179 days
  3. Red >= 180 days

Code:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js" type="text/javascript"></script>
<script> 
$(document).ready(function(){
    $Text = $("td .ms-vb2:contains('')").filter(function() {
        return $(this).text() == "";
    })

    $Text = $("td .ms-vb2:contains(days<=90)");
    $Text.parent().css("background-color", "green");

    $Text = $("td .ms-vb2:contains(days=92)");
    $Text.parent().css("background-color", "red");

    $Text = $("td .ms-vb2:contains(days >=180)");
    $Text.parent().css("background-color", "yellow");

}); 
</script>
2
  • What exactly does your calculated column show in each field? Is it the literal text "days <= 90", including the word "days" and the less-than-or-equals sign, or "<= 90" (not including "days", but still including the less-than-or-equals sign)? Or is it just numbers, like "27", "94", "135", etc.? Mar 14, 2021 at 17:54
  • It Column is called “Days” and formula is counting the how many days since it has been active. The tow displays in “1” , “30” just number format
    – Newb
    Mar 15, 2021 at 23:13

2 Answers 2

0

There are a couple things in this code that are probably going wrong that would prevent it from doing what you want.

  1. Too broad of a selector. The common selector you are using to try to find the elements you want to change (before you try to narrow in on them using :contains()) is td .ms-vb2. This is much too broad. I just tested this on a couple lists in SP 2013, and $('td .ms-vb2') returns almost every single table cell in the list view. That's way too much, it's not really helping you in any way to start off by finding everything. You need to find some way of narrowing down your initial selection, and I realize you are trying to do that with :contains(), but that leads us to:

  2. Incorrect understanding of how :contains() works. According to the jQuery documentation on the :contains() selector, it can be used to "select all elements that contain the specified text". What that means, though, is that you cannot put logic inside the parentheses. If you are checking for :contains(days<=90), that's going to be looking to match the literal text "days<=90", and a simple value of "75" or "90" is not going to match. So trying to use :contains() to match a range of values, or anything you have to apply logic to, is going to be very difficult. :contains() is good for when you are looking for a specific value, for instance if you had several text fields, and you wanted to find any text fields that had the word "SharePoint" in them, you could do $('td:contains("SharePoint")') and it would find them.

Since :contains() can't really contain logic, and the list view table is pretty uniform in terms of what selectors you can use, one of the bigger problems you are going to have is figuring out which cells in the table have the data you're looking for. You could do something like look through the table headers to find the header that contains the name of your column ("Days"), figure out what index it's at, and then use that index to choose the corresponding <td> in each of the regular rows in the table body. But that gets tricky.

In this case, because you are on SharePoint 2016, I would recommend you use Client Side Rendering to help you out here. Client Side Rendering is a system available in SP 2013 and 2016 (and maybe 2019? I'm not sure), but that was removed for SharePoint Online / Modern experience. You're on 2016, so you're in luck, you can still use it.

It allows you to target specific columns and customize how the data from those columns is rendered on the page. In this case, you can use it to target specifically your "Days" column, then check the value of that column, and conditionally render it based on it's value. And, since the code follows the column, you don't need to worry about column order in the view and navigating the <table> structure of the rendered list view. Your customizations will get inserted into the list view table as it's being constructed.

Here's a great introductory article about CSR.

Now, for your case, if you wanted to just change the background color of the "Days" column only (i.e. only color the individual table cell that has the Days value in it), you could do something like this:

function renderDays (ctx) {

    // get the "Days" value as a number so we can do numeric comparisons
    var days = parseInt(ctx.CurrentItem[ctx.CurrentFieldSchema.Name], 10);

    // default the color to green
    var color = 'green';

    // check to see if the value is
    // such that we want a different color
    if (90 < days && days < 180) {
        color = 'yellow';
    } else if (days >= 180) {
        color = 'red';
    }

    // return a custom <div> with the custom background color
    // but still has the "days" number as the main value.
    // this div will be inside the table cell in the list view
    // for the "Days" column
    return "<div style='background-color:" + color + "'>" + days + "</div>";
}

// register the override function with the CSR system
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
    Templates: {
        Fields: {
            'Days': {
                View: renderDays
            }
        }
    }
});

If what you really want is to color the entire row, not just the individual cell, that's doable too, just in a slightly different way. Here's an article about how to do that, and you can probably also find plenty of other examples online if you search. I'd do it slightly differently than the article though:

function colorRowsBasedOnDays (ctx) {

    // get all the rows showing in the list view
    var rows = ctx.ListData.Row;

    // loop through them
    for (var idx = 0; idx < rows.length; idx++) {
        
        // get the days value as a number so we can do numeric comparisons
        var days = parseInt(rows[idx].Days, 10);
        
        // default the color to green
        var color = 'green';

        // check to see if the value is such that we want a different color
        if (90 < days && days < 180) {
            color = 'yellow';
        } else if (days >= 180) {
            color = 'red';
        }

        // get the HTML id of the row using a built-in SharePoint provided function
        var rowID = GenerateIIDForListItem(ctx, rows[idx]);

        // get the row by it's id
        var row = document.getElementById(rowID);

        // change the background color
        row.style.backgroundColor = color;
    }
}

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
    OnPostRender: colorRowsBasedOnDays
});

You'll notice that in these examples, you don't even need to use jQuery.

In order to get the CSR system to load your override scripts, though, you need to get them on the page. You could do that the same way you are getting your current script on the page, which is put it directly in the HTML inside a <script> block.

Or, you can use some thing called "JSLink". If you start finding examples of CSR scripts, you will see the term "JSLink" a lot, and quite often you will see it used in a way that makes it seem like "JSLink" and "CSR" are the same thing. They are not the same thing. CSR is the Client Side Rendering system. JSLink is a way to pull Javascript files onto a page. You can get CSR scripts onto a page without JSLink, and you can use JSLink to pull non-CSR scripts onto a page. They do work really, really well together, but they are not the same thing.

That being said, an easy way to get a CSR script onto a page to override rendering in a list view is to use the JSLink property in a List View Web Part. So, first you would upload your script file to somewhere on the site, say for instance in the Site Assets library. Then you would go to the list where you want to apply the override script, and "edit page", and then edit the List View Web Part on that page.

In the Miscellanous section of the web part editor pane, you will see a text box called "JS Link":

screen shot of web part editor pane

In that text box, you put the path to your script file, using a replacement token for the initial part of the site URL, so for instance

~site/SiteAssets/your-csr-override-file.js

And that's it, your script will now be pulled in to that list view and used to apply your custom rendering.

There are tons more examples and resources out there if you search for SharePoint CSR, Client Side Rendering, JSLink, conditional rendering, etc, etc.


To answer your follow up question in the comments:

can you please explain to me how "var days = parseInt(ctx.CurrentItem[ctx.CurrentFieldSchema.Name], 10);" gets the column for the days.

I will explain, but first let me say you should really really read the article I linked to explaining how CSR works. In fact, here's the link again:

https://www.codeproject.com/Articles/620110/SharePoint-Client-Side-Rendering-List-Views

Now, regarding your question specifically - that line of code itself is not what is targeting/finding/getting the Days column. When that line of code runs, the Days column has already been "gotten".

This is the code that targets the Days column specifically:

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
    Templates: {
        Fields: {
            'Days': {
                View: renderDays
            }
        }
    }
});

And what that bit of code is saying is: "hey SharePoint rendering system: when you get to a column named Days, if you happen to be rendering the regular list view, please use this function I've provided called renderDays to render that column's data instead of your default rendering."

So that's the bit of code that "finds" the Days column, except it's not really "finding" it as much as registering a custom function for the CSR framework to use if it happens to run across a column named Days. (Which we very much anticipate it to do, that's the whole point.)

When you create a custom function to override the rendering like that, you have to make the custom function accept an argument called ctx. Well, technically you could name it anything you want, it's your code after all, but the point is that the CSR framework will try to pass an object into your function, and that object is the rendering context object, therefore it's helpful to name it ctx so you remember what you're dealing with there.

If you put a breakpoint on your code inside that custom renderDays function, you can inspect that object and learn a little about what it has, and what you can access through it.

A couple properties that you can access on the ctx object are ctx.CurrentItem, which has the data for the current item being rendered, and ctx.CurrentFieldSchema which has information about the particular column being rendered at that time. In the case of rendering a list view, that custom function is going to be called for every item visible in the view, so if you have say 30 items visible in the view, that renderDays function is getting called 30 times, once per row in the list view, as CSR tries to render the Days column in each row for each item, so the ctx.CurrentItem data will be different every time, but since we only registered a custom function for one column, Days, the ctx.CurrentFieldSchema will be the same every time.

So when I said in my comment in the code example

// get the "Days" value...

well, we already know that our custom renderDays function is only executing for the Days column, but we still need to get what that value is for the current list item. We could get that data by accessing

ctx.CurrentItem.Days // use the column's internal name in dot notation to access the property

That would totally work:

// this is the same thing
var days = parseInt(ctx.CurrentItem.Days, 10);

BUT... what if you have more than one field you want to render in the same way? What if you have a Days_1 field and a Days_2 field you want to render in the exact same way?

You can register the same custom function to be called for both fields:

// this tells CSR to use the same "renderDays" function
// for _both_ the "Days_1" field _and_ the "Days_2" field

SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
    Templates: {
        Fields: {
            'Days_1': {
                View: renderDays
            },
            'Days_2': {
                View: renderDays
            }
        }
    }
});

But then what happens inside the function if you try to access the data through the property on CurrentItem directly?

// when the rendering function runs for the "Days_2" field,
// this will be accessing the wrong value because we are looking at
// the "Days_1" value only

ctx.CurrentItem.Days_1

So, what we can do is use the CurrentFieldSchema to dynamically get the correct field name when the function runs for the different columns

// this will get the correct value for Days_1 when it's rendering Days_1,
// and get the correct value for Days_2 when it's rendering Days_2

ctx.CurrentItem[ctx.CurrentFieldSchema.Name]

So again, that's not really "finding" the Days column with that. We already know that ctx.CurrentFieldSchema is going to have information about the Days column because we registered the custom function to run only for the Days column. And, we know that ctx.CurrentItem is going to have all the data for the current item being rendered, so we can access the value of the Days column for the current item through ctx.CurrentItem.

You'll also notice that I do use the dot notation way of accessing the property/value in the code example that shows how to color the entire row. That's because that custom function is registered to run onPostRender, meaning, after the list view has been rendered. Therefore all the rows (items) and columns have already been rendered to the page and put in the <table>, so at that point in the process there's no longer such a concept as CurrentItem or CurrentFieldSchema, because it's after all that is done with. So in that code example, we're looping through the data for all the rows (items), and we have to target the specific field we are looking for by directly using it's name

rows[idx].FieldInternalName

I hope that clears it up a little bit, and again, I highly recommend reading that article I linked to.

3
  • Thanks, an what a great explanation thank you!
    – Newb
    Mar 29, 2021 at 14:56
  • can you please explain to me how "var days = parseInt(ctx.CurrentItem[ctx.CurrentFieldSchema.Name], 10);" gets the column for the days. so i may use this in other solutions as well. i am having a hard time understanding how it only searches the days column
    – Newb
    Mar 29, 2021 at 15:58
  • @bluedemon199 updated my answer. Mar 29, 2021 at 17:08
0

You should try to use Client side rendering list view:

Test code for your reference:

SP.SOD.executeFunc("clienttemplates.js", "SPClientTemplates", function() {

  function getBaseHtml(ctx) {
    return SPClientTemplates["_defaultTemplates"].Fields.default.all.all[ctx.CurrentFieldSchema.FieldType][ctx.BaseViewID](ctx);
  }

  function init() {

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides({



      Templates: {

           Fields: {
               "cal": {
                   View: function(ctx) { 
                     console.log(ctx.CurrentItem["cal"])
                     var cal=ctx.CurrentItem["cal"];
                     var color='';
                     if(cal<=90){
                        color='green';
                     }else if(cal>90&& cal<=179){
                     color='red ';
                     }else{
                      color='yellow'; 
                     }
                    
                     return "<div style=background-color:"+color+"><span style=background-color:"+color+">"+cal+"</span></div>"; },
      
               }
           },
      

      },

   

      ListTemplateType: 100

    });
  }

  RegisterModuleInit(SPClientTemplates.Utility.ReplaceUrlTokens("~siteCollection/Style Library/csr.js"), init);
  init();

});

Test result:

enter image description here

References: SharePoint 2013 Client Side Rendering: List Views

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.