Rich Text Notes in Zoho CRM : Solved via Custom Widget (Workaround)

Rich Text Notes in Zoho CRM : Solved via Custom Widget (Workaround)

Hi All Zoho Users & Developers

I am creating this post as a starting the discussion on the Rich Text notes feature in Zoho CRM. Seems this is been open for more than seven years. We, our team has developed a simple workaround via widgets.

Problem:

Despite being introduced in 2005, Zoho CRM still lacks a Rich Text notes feature. Integrating this functionality could significantly enhance usability and productivity for users. Given the importance of comprehensive and visually appealing notes in managing customer relationships, it's surprising this feature hasn't been implemented yet. Its addition could further strengthen Zoho CRM's position in the market.


Innovatively, despite Zoho CRM lacking a native Rich Text notes feature, our team has devised a workaround by creating a widget that serves this purpose. This widget fills a crucial gap, empowering users to format text, embed images, and enhance their notes within the CRM platform itself. This solution showcases our commitment to enhancing user experience and addressing their needs proactively. By integrating this widget seamlessly into Zoho CRM, we're bridging the functionality gap and offering users a comprehensive solution for managing customer relationships effectively.

Solution:

Our team has tried developing a workaround by creating a widget that serves this purpose. We believe, this widget will fill a crucial gap atleast for now , empowering users to format text, embed images, and enhance their notes within the CRM platform itself.  We still believe there is a lot of improvements to meeting the use case. We call fellow developers to come up with the improvement ideas and suggestions.

Steps to archive this:

1.Node JS Installation LINK

2. Zoho Extension toolkit cli Installation (zet) LINK

3.Creating CRM widget in your local machine (CLI commands)

4. Folder Structure as follows

5. Code for "widget.html"

  1. <!DOCTYPE html>
  2. <html lang="en">

  3. <head>
  4.   <meta charset="UTF-8">
  5.   <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.   <title>To-Do List</title>
  7.   <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  8.   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
  9.   <style>
  10.     body {
  11.       font-family: Arial, sans-serif;
  12.       margin: 0;
  13.       padding: 0;
  14.     }

  15.     .container {
  16.       max-width: 90%;
  17.       margin: 0 auto;
  18.       padding: 20px;
  19.     }

  20.     #editor-container {
  21.       margin-bottom: 10px;
  22.     }

  23.     #todo-list {
  24.       list-style-type: none;
  25.       padding: 0;
  26.     }

  27.     #todo-list li {
  28.       position: relative;
  29.       display: flex;
  30.       justify-content: space-between;
  31.       align-items: center;
  32.       margin-bottom: 10px;
  33.       padding: 10px;
  34.       background-color: #f5f5f5;
  35.       border-radius: 4px;
  36.     }

  37.     #todo-list li button {
  38.       background-color: transparent;
  39.       color: #555;
  40.       border: none;
  41.       padding: 5px;
  42.       border-radius: 4px;
  43.       cursor: pointer;
  44.       transition: background-color 0.3s;
  45.     }

  46.     #todo-list li button:hover {
  47.       background-color: #ddd;
  48.     }
  49.   </style>
  50. </head>

  51. <body>
  52.   <div class="container">
  53.     <ul id="todo-list"></ul>
  54.     <div id="editor-container">
  55.       <div id="toolbar"></div>
  56.       <textarea id="editor"></textarea>
  57.       <div class="button-group mt-2 d-flex justify-content-end">
  58.         <button id="add-todo" class="btn btn-primary mr-2">Save</button>
  59.         <button id="cancel-todo" class="btn btn-danger">Cancel</button>
  60.       </div>
  61.     </div>
  62.   </div>

  63.   <script src="https://live.zwidgets.com/js-sdk/1.2/ZohoEmbededAppSDK.min.js"></script>
  64.   <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
  65.   <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
  66.   <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
  67.   <script src="https://cdn.ckeditor.com/ckeditor5/23.0.0/classic/ckeditor.js"></script>

  68.   <script>
  69.     let editor;
  70.     ClassicEditor
  71.       .create(document.querySelector('#editor'))
  72.       .then(newEditor => {
  73.         editor = newEditor; // Initialize CKEditor
  74.       })
  75.       .catch(error => {
  76.         console.error(error);
  77.       });

  78.     const addTodoButton = document.getElementById('add-todo');
  79.     const cancelTodoButton = document.getElementById('cancel-todo');
  80.     const todoList = document.getElementById('todo-list');

  81.     let todoData = {};
  82.     let recordId;
  83.     let currentEditId = null;

  84.     // Event handler for page load
  85.     ZOHO.embeddedApp.on("PageLoad", function(data) {
  86.       if (data && data.Entity) {
  87.         recordId = data.EntityId;
  88.         console.log("Current Record ID: " + recordId);

  89.         // Fetch the record data from Zoho CRM
  90.         ZOHO.CRM.API.getRecord({
  91.             Entity: "Leads",
  92.             approved: "both",
  93.             RecordID: recordId
  94.           })
  95.           .then(function(data) {
  96.             const description = data.data[0].Description;
  97.             if (description) {
  98.               todoData = JSON.parse(description); // Parse the stored to-do list
  99.             } else {
  100.               todoData = {};
  101.             }

  102.             // Clear the todoList
  103.             todoList.innerHTML = '';

  104.             // Add each todo item
  105.             for (const [id, item] of Object.entries(todoData)) {
  106.               addTodoItemToList(id, item.data);
  107.             }
  108.           })
  109.           .catch(function(error) {
  110.             console.error('Error fetching description:', error);
  111.           });

  112.         addTodoButton.addEventListener('click', saveTodo);
  113.         cancelTodoButton.addEventListener('click', cancelTodo);
  114.       }
  115.     });

  116.     // Function to add a to-do item to the list
  117.     function addTodoItemToList(id, data) {
  118.       const li = document.createElement('li');
  119.       li.setAttribute('data-id', id);
  120.       const todoContent = document.createElement('div');
  121.       todoContent.innerHTML = data; // Set the content of the to-do item
  122.       li.appendChild(todoContent);
  123.       li.appendChild(createButtonsContainer('fa-pencil-alt', 'fa-trash-alt'));
  124.       todoList.appendChild(li);

  125.       addEventListeners(li, id);
  126.     }

  127.     // Function to edit a to-do item
  128.     function editTodoItem(id, data) {
  129.       editor.setData(data); // Set the editor data to the selected to-do item's data
  130.       currentEditId = id;
  131.       addTodoButton.textContent = 'Update Todo';
  132.     }

  133.     // Function to save a to-do item
  134.     function saveTodo() {
  135.       const todoText = editor.getData();
  136.       if (todoText.trim() !== '') {
  137.         if (currentEditId) {
  138.           // Update existing item
  139.           todoData[currentEditId] = {
  140.             data: todoText
  141.           };
  142.           const li = document.querySelector(`[data-id='${currentEditId}']`);
  143.           li.innerHTML = '';
  144.           const todoContent = document.createElement('div');
  145.           todoContent.innerHTML = todoText;
  146.           li.appendChild(todoContent);
  147.           li.appendChild(createButtonsContainer('fa-pencil-alt', 'fa-trash-alt'));

  148.           addEventListeners(li, currentEditId);

  149.           currentEditId = null;
  150.           addTodoButton.textContent = 'Save';
  151.         } else {
  152.           // Add new item
  153.           const uniqueId = 'id' + new Date().getTime();
  154.           todoData[uniqueId] = {
  155.             data: todoText
  156.           };
  157.           addTodoItemToList(uniqueId, todoText);
  158.         }
  159.         editor.setData(''); // Clear the editor content
  160.         updateRecord(recordId, todoData); // Update the record in Zoho CRM
  161.       }
  162.     }

  163.     // Function to cancel editing or adding a to-do item
  164.     function cancelTodo() {
  165.       editor.setData(''); // Clear the editor content
  166.       currentEditId = null;
  167.       addTodoButton.textContent = 'Save';
  168.     }

  169.     // Function to create buttons for editing and deleting
  170.     function createButtonsContainer(editIcon, deleteIcon) {
  171.       const container = document.createElement('div');
  172.       const editButton = document.createElement('button');
  173.       editButton.classList.add('edit', 'btn', 'btn-secondary', 'mr-1');
  174.       editButton.innerHTML = `<i class="fas ${editIcon}"></i>`;
  175.       editButton.setAttribute('data-toggle', 'tooltip');
  176.       editButton.setAttribute('data-placement', 'top');
  177.       editButton.setAttribute('title', 'Edit');
  178.       const deleteButton = document.createElement('button');
  179.       deleteButton.classList.add('delete', 'btn', 'btn-danger');
  180.       deleteButton.innerHTML = `<i class="fas ${deleteIcon}"></i>`;
  181.       deleteButton.setAttribute('data-toggle', 'tooltip');
  182.       deleteButton.setAttribute('data-placement', 'top');
  183.       deleteButton.setAttribute('title', 'Delete');
  184.       container.appendChild(editButton);
  185.       container.appendChild(deleteButton);
  186.       return container;
  187.     }

  188.     // Function to add event listeners to the edit and delete buttons
  189.     function addEventListeners(li, id) {
  190.       const deleteButton = li.querySelector('.delete');
  191.       deleteButton.addEventListener('click', () => {
  192.         deleteTodoItem(id);
  193.       });

  194.       const editButton = li.querySelector('.edit');
  195.       editButton.addEventListener('click', () => {
  196.         editTodoItem(id, todoData[id].data);
  197.       });
  198.     }

  199.     // Function to delete a to-do item
  200.     function deleteTodoItem(id) {
  201.       delete todoData[id];
  202.       const liElement = document.querySelector(`[data-id='${id}']`);
  203.       if (liElement) {
  204.         liElement.remove();
  205.       }
  206.       updateRecord(recordId, todoData); // Update the record in Zoho CRM
  207.     }

  208.     // Function to update the record in Zoho CRM
  209.     function updateRecord(recordId, data) {
  210.       var config = {
  211.         Entity: "Leads",
  212.         APIData: {
  213.           "id": recordId,
  214.           "Description": JSON.stringify(data),
  215.         },
  216.         Trigger: ["workflow"]
  217.       };

  218.       ZOHO.CRM.API.updateRecord(config)
  219.         .then(function(data) {
  220.           console.log('Description updated:', data);
  221.         })
  222.         .catch(function(error) {
  223.           console.error('Error updating description:', error);
  224.         });
  225.     }

  226.     // Initialize the Zoho Embedded App
  227.     ZOHO.embeddedApp.init();
  228.   </script>
  229. </body>

  230. </html>

6.Pack the widget

Once you execute the commands "zet validate" and "zet pack", a "dist" folder will be created within the folder structure, containing the zipped file.



7.Create Widget in Zoho CRM

Settings > Developer Hub > Widgets > Create New Widget
Select the type as related list
Select the hosting as zoho(we are using internal hosting here)
Select the zip file from the dict folder.
Set the index pages as “/widget.html”
Click save




8. Execution 

Select Add Related List from the left tabs



Select the widget from the pop up


Click install the widget you have added then you will get this interface in Leads Module








Hope this helps upto some level. Looking forward for improvements and suggestions.


Regards

Vigneshwaran Kalaivanan
Certified Zoho CRM & Creator Developer
TraqMetrix Solutions LLP - Zoho Authorized Partner
WhatsApp Logo +919944486228