Displaying Multiselect Values as Chips in List Pages

Displaying Multiselect Values as Chips in List Pages

Creating a visually appealing display for multiselect values can greatly enhance the usability of your application. Here's a guide on how to transform comma-separated multiselect values into colored chips in your list page.

Step 1: Store Multiselect Values in a Text Field

First, you need to create a text field in your data model to store the multiselect values as a comma-separated string. For example:

 

"Home,Grant,Actual,Business,Home Delivery"

This text field will serve as the data source for your chips display.

Step 2: Implement the View Model Handler

Add the following code to your handler to transform the text values into visual chips:

View Model Handler Code

	{
    request: "crt.HandleViewModelAttributeChangeRequest",
    handler: async (request, next, context) => {
		//console.log("Data changed");
 
      const columnId = "d83d0646-68df-e743-f996-e007039be534";
 
      const injectChipStyles = () => {
        const style = document.createElement("style");
        style.innerHTML = `
          .custom-chip {
            display: inline-block;
            padding: 1px 5px;
            margin: 3px 5px 3px 0px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: 500;
            color: white;
            position: relative;
            padding-right: 5px;
            background-color: #666;
          }
          .custom-chip::after {
            font-weight: bold;
            position: absolute;
            right: 5px;
            top: 50%;
            transform: translateY(-50%);
            cursor: default;
          }
        `;
        document.head.appendChild(style);
      };
 
      const stringToColor = (str) => {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
          hash = str.charCodeAt(i) + ((hash << 5) - hash);
        }
        let color = '#';
        for (let i = 0; i < 3; i++) {
          const value = (hash >> (i * 8)) & 0xFF;
          color += (`00${value.toString(16)}`).slice(-2);
        }
        return color;
      };
 
      const renderChips = () => {
        const cells = document.querySelectorAll(
          `td[crt-data-table-column-id="${columnId}"] .crt-data-table-cell-value`
        );
 
        if (cells.length === 0) return;
 
        cells.forEach(cell => {
          const title = cell.getAttribute("title");
          if (title && cell.children.length === 0) {
            cell.innerHTML = "";
            title.split(",").forEach(value => {
              const trimmed = value.trim();
              const chip = document.createElement("span");
              chip.className = "custom-chip";
              chip.innerText = trimmed;
              chip.style.backgroundColor = stringToColor(trimmed);
              cell.appendChild(chip);
            });
          }
        });
 
        clearInterval(timer);
      };
 
      injectChipStyles();
      const timer = setInterval(renderChips, 500);
      await next?.handle(request);
		}
	}	{
    request: "crt.HandleViewModelAttributeChangeRequest",
    handler: async (request, next, context) => {
		//console.log("Data changed");
 
      const columnId = "d83d0646-68df-e743-f996-e007039be534";
 
      const injectChipStyles = () => {
        const style = document.createElement("style");
        style.innerHTML = `
          .custom-chip {
            display: inline-block;
            padding: 1px 5px;
            margin: 3px 5px 3px 0px;
            border-radius: 20px;
            font-size: 12px;
            font-weight: 500;
            color: white;
            position: relative;
            padding-right: 5px;
            background-color: #666;
          }
          .custom-chip::after {
            font-weight: bold;
            position: absolute;
            right: 5px;
            top: 50%;
            transform: translateY(-50%);
            cursor: default;
          }
        `;
        document.head.appendChild(style);
      };
 
      const stringToColor = (str) => {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
          hash = str.charCodeAt(i) + ((hash << 5) - hash);
        }
        let color = '#';
        for (let i = 0; i < 3; i++) {
          const value = (hash >> (i * 8)) & 0xFF;
          color += (`00${value.toString(16)}`).slice(-2);
        }
        return color;
      };
 
      const renderChips = () => {
        const cells = document.querySelectorAll(
          `td[crt-data-table-column-id="${columnId}"] .crt-data-table-cell-value`
        );
 
        if (cells.length === 0) return;
 
        cells.forEach(cell => {
          const title = cell.getAttribute("title");
          if (title && cell.children.length === 0) {
            cell.innerHTML = "";
            title.split(",").forEach(value => {
              const trimmed = value.trim();
              const chip = document.createElement("span");
              chip.className = "custom-chip";
              chip.innerText = trimmed;
              chip.style.backgroundColor = stringToColor(trimmed);
              cell.appendChild(chip);
            });
          }
        });
 
        clearInterval(timer);
      };
 
      injectChipStyles();
      const timer = setInterval(renderChips, 500);
      await next?.handle(request);
		}
	}

How This Code Works

  1. Column Identification: Replace the columnId with your specific column ID where the multiselect values are stored.
  2. Style Injection: The injectChipStyles function adds custom CSS to your page that defines how chips will look.
  3. Color Generation: The stringToColor function creates a unique color for each value based on its string content, ensuring visual distinction between different values.
  4. Chip Rendering: The renderChips function:
    • Identifies all cells in the specified column
    • Splits the comma-separated values
    • Creates a chip element for each value
    • Applies the generated color
    • Adds the chips to the cell
  5. Timing: The code uses a timer to ensure cells are found and processed even if they load asynchronously.

Implementation Steps

  1. Locate your list page configuration
  2. Add the handler code to your view model handlers
  3. Make sure to update the columnId with your specific column ID
  4. Test the implementation by viewing the list page

Result

Your multiselect values will appear as color-coded chips instead of a comma-separated string:

  • Instead of: "Home,Grant,Actual,Business,Home Delivery"
  • You'll see: [Home] [Grant] [Actual] [Business] [Home Delivery] (where each value has its own colored background)

This approach not only makes the information more visually appealing but also improves readability and user experience.
images 

 After:

Like 6

Like

Share

4 comments

This is amazing. Great work! We will definitely benefit from this.

Great Wotk!!

Hello smit suthar,

In your article, you mention:

"First, you need to create a text field in your data model to store the multiselect values as a comma-separated string. For example: 'Home,Grant,Actual,Business,Home Delivery'"

My question: How did you populate this text field with the MultiSelect values? What code/handler did you use to automatically update the text field when users select/deselect items in the MultiSelect control?

I need help with this as soon as possible. I would really appreciate any assistance you can provide!

Thanks in advance!

Valerie Bsereni,

You could use an Entity Event Layer.
This should be based on your Multi-Select object.

When you select values (OBJECT: Multi-Select Contact/yours created)
Set an event save, delete for the object.
Enable the live update.

the new text field gets automatically updated when you add or remove values from the list.

using System;
using System.Linq; 
using Terrasoft.Core;
using Terrasoft.Core.Entities;
using Terrasoft.Core.Entities.Events;
namespace Terrasoft.Configuration
{
   [EntityEventListener(SchemaName = "USrContatMultiSelect")] 
   public class ContatSubscriptionEventListener : BaseEntityEventListener
   {
       public override void OnSaved(object sender, EntityAfterEventArgs e) {
           base.OnSaved(sender, e);
           UpdateContactText(sender as Entity);
       }
       public override void OnDeleted(object sender, EntityAfterEventArgs e) {
           base.OnDeleted(sender, e);
           UpdateContactText(sender as Entity);
       }
       public void UpdateContactText(Entity entity) {
           if (entity == null) return;          
           var userConnection = entity.UserConnection;
           var contactId = entity.GetTypedColumnValue&lt;Guid&gt;("ContactId");
           if (contactId == Guid.Empty) return;
           var esq = new EntitySchemaQuery(userConnection.EntitySchemaManager, "ContatLookup");
           esq.AddColumn("Lookup.Name"); 
           esq.Filters.Add(esq.CreateFilterWithParameters(FilterComparisonType.Equal, "Contact", contactId));
           var entities = esq.GetEntityCollection(userConnection);
           var namesList = entities.Select(x =&gt; x.GetTypedColumnValue&lt;string&gt;("Lookup_Name"));
           string finalString = string.Join(", ", namesList);
           var contactSchema = userConnection.EntitySchemaManager.GetInstanceByName("Contact");
           var contact = contactSchema.CreateEntity(userConnection);
           if (contact.FetchFromDB(contactId)) {
               contact.SetColumnValue("LookupText", finalString);
               contact.Save(false);
           }
       }
   }
}		

 

 

BR,
Bala.

Show all comments