Home Analyst Portal

How to trigger Assign to Analyst by Group

Mikkel_MadsenMikkel_Madsen Customer Advanced IT Monkey ✭✭✭
edited November 2018 in Analyst Portal
Hi
When you select a request in Team Request view (or Mywork/Active work) you can hit the Task button on the drawer task bar an select assign to analyst by group.

What I would like to do is making an icon in the assigned to column and trigger the same function when you click on that icon but I can't find out how trigger it.

Anyone who can help with this or guide me?

Best Answers

Answers

  • Mikkel_MadsenMikkel_Madsen Customer Advanced IT Monkey ✭✭✭
    No one?
  • Mikkel_MadsenMikkel_Madsen Customer Advanced IT Monkey ✭✭✭
    @Justin_Workman
    Can you help? :smile:

  • Mikkel_MadsenMikkel_Madsen Customer Advanced IT Monkey ✭✭✭
    yes, I know about that one but if it's possible to trigger the Assign to Analyst task I will create an icon the same way as this add-on https://community.cireson.com/discussion/1919/custom-quick-assign-work-item-to-me-from-grid#latest 

  • Tom_HendricksTom_Hendricks Customer Super IT Monkey ✭✭✭✭✭
    There is an ugly way, and a better way.

    The ugly way is to trigger a click on the button itself:
    $('a[data-bind="localize: AssignToAnalystByGroup"]').trigger('click')
    A better way would be to find the "analystByGroup" function that is actually firing within the click event, and call it directly.  These functions typically live within the view when forms are involved, but I do not see the objects I am looking for since pageForm does not exist on these grid pages.
  • Justin_WorkmanJustin_Workman Cireson Support Super IT Monkey ✭✭✭✭✭
    The solution provided by @Tom_Hendricks would certainly work.  I think it would work pretty well personally.  I don't think it's ugly at all! :)
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited November 2018
    Another small update to my ongoing work in progress:
    • Updated the column template to only show the edit buttons for Incidents and Service Requests to match the enabled/disabled behavior of analystByGroup.
    • Updated gridTasksViewModel PendingTasks to operate as an ObservableArray with push/pop/remove functionality to account for additional column tasks in the future.
    • Binding to the analystByGroup task's view model change event vs. the grid's app events in order to better handle the order of events for grid selection and triggering the Assign To Analyst By Group window.
    New AssignedUser column template to handle non-Incidents/Service Requests:
    "template": "<span style=\"display: inherit; width: 100%\">#: AssignedUser #</span># if (session.user.Analyst && (!_.isUndefined(WorkItemType) && (WorkItemType==='System.WorkItem.Incident' || WorkItemType==='System.WorkItem.ServiceRequest'))) { #<ul class=\"ra-grid-task-menu\" style=\"display: inherit; list-style: none;\"><li onclick=\"app.events.publish('newGridTask','AssignToAnalystByGroup');\"><a class=\"ra-icon\" style=\"display: inherit;\"><i class=\"fa fa-pencil\"></i></a></li></ul># } #"


    New custom.js script:
    var gridTasksViewModel = new kendo.observable({
        pending: [],
        lib: {
            push: function push (taskName) {
                console.log("gridTasksViewModel push", {taskName: taskName});
                if (gridTasksViewModel.pending.indexOf(taskName) === -1) {
                    gridTasksViewModel.pending.push(taskName);
                }
            },
            remove: function remove (taskName) {
                console.log("gridTasksViewModel remove", {taskName: taskName});
                gridTasksViewModel.pending.remove(taskName);
            },
            process: function ProcessPendingTask (vm, taskName) {
                console.log("gridTasksViewModel process", {vm: vm, taskName: taskName});
                if (!_.isUndefined(gridTasksViewModel.tasks.get(taskName)) && gridTasksViewModel.pending.indexOf(taskName) !== -1) {
                    gridTasksViewModel.pending.remove(taskName);
                    gridTasksViewModel.tasks[taskName](vm);
                }
            }
        },
        tasks: {
            AssignToAnalystByGroup: function AssignToAnalystByGroup (vm) {
                console.log("gridTasksViewModel AssignToAnalystByGroup", {vm: vm});
                vm.analystByGroup();
            }
        }
    });
    
    app.events.subscribe("newGridTask", function (event, taskName) {
        console.log("grid:newGridTask event", {event: event, taskName: taskName});
        gridTasksViewModel.lib.push(taskName);
    });
    
    $('li[data-bind*="click: analystByGroup"]').get(0).kendoBindingTarget.source.bind("change", function (event) {
        var vm = _.isUndefined(event.sender.observable) ? event.sender : event.sender.source,
            taskName = "AssignToAnalystByGroup",
            isEnabled = vm.get("isEnabled");
        console.log("analystByGroup change", {event: event, field: event.field, taskName: taskName, vm: vm, isEnabled: isEnabled});
        switch (event.field) {
            case "isEnabled":
                // TBD Update edit buttons to show enabled/disabled status
                if (isEnabled) {
                    gridTasksViewModel.lib.process(vm, taskName);
                } else {
                    gridTasksViewModel.lib.remove(taskName);
                }
                break;
            case "currentSelection":
                if (isEnabled) {
                    gridTasksViewModel.lib.process(vm, taskName);
                } else {
                    var keepPendingTask = false,
                        selectedDataItems = vm.get("currentSelection"),
                        selectedTypes = _.pluck(selectedDataItems, "WorkItemType"),
                        singleSelectedType = _.reduce(selectedTypes, function (memo, currentVal) {
                            return (memo === currentVal) ? memo : false;
                        });
                    if (singleSelectedType && selectedDataItems.length > 0 && selectedDataItems.length <= 10) {
                        switch (singleSelectedType) {
                            case "System.WorkItem.ServiceRequest":
                            case "System.WorkItem.Incident":
                                keepPendingTask = true;
                                break;
                        }
                    }
                    if (!keepPendingTask) {
                        gridTasksViewModel.lib.remove(taskName);
                    }
                }
            break;
        }
    });
    TBD:
    • Enabled/Disabled styling for buttons.
    • Update column template to handle additional buttons.

    EDIT: Modified the custom.js script to account for edge cases of swapping back and forth between non-IR/SR selections and then immediately selecting a valid option as well as moving tasks functions into a small library under gridTasksViewModel.lib for a better interface.
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited November 2018
    Hello!
    Hope everyone had a great holiday weekend.  For this small update of my work in progress I've made a bit of a breakthrough in altering grid columns dynamically without removing existing bindings or functionality.  Here are some screenshots of multiple button types per field, conditional filtering and formatting based on field values:
    My Work:

    Team Work:

    I'm still working on the grid tasks framework but so far it has provided a wealth of potential benefits:
    • No need to modify Cireson's source files, create custom views or modify view definitions in ServiceManagement db.
    • Efficiently uses Kendo's templating system to handle refreshes, page navigation and grid changes without mutation observers, timers or excessive subscriptions to any event systems.
    • Easily expandable to handle multiple separate functions per field.  So far I am working on the following functionality: edit buttons, info/popup buttons, conditional formatting and access control to these custom features.
    On another note, I've moved away from allowing multiple selections at once when clicking on individual field buttons so clicking an edit button will select that single row and un-select all other rows.  This still leaves the option of selecting multiple items and then clicking Assign To Analyst By Group under Tasks.

    Please let me know if anyone has any thoughts or ideas as I'm working on this one.  Thanks!
  • Mikkel_MadsenMikkel_Madsen Customer Advanced IT Monkey ✭✭✭
    Looks very promising :smiley:

  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited November 2018
    Hello!
    It's me again with another update on my work in progress for the custom buttons/styles in grids portion of this thread.
    I have created app.custom.gridTasks (in a similar vein to app.custom.formTasks) to handle adding custom tasks to specific grids. Using each Kendo grid's own column template array to store our custom data and anonymous functions to return angular template strings we can have any grid populate and use our custom code however we would like to without destroying the grids and undoing existing bindings from Cireson, subscriptions or other scripts.
    app.custom.gridTasks:
    app.custom.gridTasks = {
        add: function add (gridData, field, type, name, template, callback) {
    		var that = this,
                // Look for provided column in grid by field name
                taskColumn = $.grep(gridData.columns, function (colValue, colIndex) {
    			return colValue.field === field;
    		});
    		
    		if (taskColumn.length > 0) {
    			taskColumn = taskColumn[0];
    			if (_.isUndefined(taskColumn["style"])) {
    				// Add default blank style template function to column template
    				taskColumn["style"] = function defaultStyle (data) { return ""; };
    			}
    			
    			if (_.isUndefined(taskColumn["tasks"])) {
    				// Add empty tasks array to column template
    				taskColumn["tasks"] = [];
    			}
    			
    			switch (type) {
    				case "style":
    					// Set style template function to provided template
    					taskColumn["style"] = template
    					break
    				case "task":
    					var existingTask = that.get(gridData, field, name);
    					if (existingTask) {
    						// Merge new task with existing one in the column template
    						$.extend(existingTask, {
    							name : name,
    							template: template,
    							callback: callback
    						});
    					} else {
    						// Add new task to the column template
    						taskColumn["tasks"].push({
    							name : name,
    							template: template,
    							callback: callback
    						});
    					}
    					break;
    			}
    		} else {
    			console.log("gridTasks:add", "Warning! Unable to find field '" + field + "'.");
    		}
    	},
    	get: function get (gridData, field, name) {
    		// Look for provided column in grid by field name
    		var taskColumn = $.grep(gridData.columns, function (colValue, colIndex) {
    			return colValue.field === field;
    		});
    		
    		if (taskColumn.length > 0) {
    			taskColumn = taskColumn[0];
    			if (_.isUndefined(name)) {
    				// Return all tasks for the provided field				
    				return taskColumn["tasks"];
    			} else {
    				// Look for the specific task named in the provided field
    				var gridTask = $.grep(taskColumn["tasks"], function (taskValue, taskIndex) {
    					return taskValue.name === name;
    				});
    				
    				if (gridTask.length > 0) {
    					// Return the specific task in the provided field
    					return gridTask[0];
    				} else {
    					console.log("gridTasks:get", "Warning! Unable to find task '" + name + "' in field '" + field + "'.");
    					return null;
    				}
    			}
    		} else {
    			console.log("gridTasks:get", "Warning! Unable to find field '" + field + "'.");
    			return null;
    		}
    	},
    	callback: function callback (itemEle) { // item is the task element clicked 
    		var that = this,
                item = $(itemEle),
    			gridData = item.closest("div[data-role='grid']").data("kendoGrid"),
    			itemData = item.data(),
    			itemRowEle = item.closest("tr").get(0),
    			dataItem = gridData.dataItem(itemRowEle);
    			
    		console.log("gridTasks:callback", {
    			gridData: gridData,
    			itemRowEle: itemRowEle,
    			dataItem: dataItem,
    			itemData: itemData
    		});
    		
    		var existingTask = that.get(gridData, itemData.field, itemData.role);
    		if (existingTask) {
    			existingTask.callback(dataItem);
    		} else {
    			console.log("gridTasks:callback", "Unable to find task for callback.");
    		}
    	},
    	updateGrid: function (gridData) {
    		console.log("gridTasks:updateGrid", "Updating grid column templates");
    		var that = this,
                bUpdateGridTemplate = false;
    		
    		$.each(gridData.columns, function (colIndex, column) {
    			if (!_.isUndefined(column["style"])) {
    				column.template = that.build.cell(column);
    				bUpdateGridTemplate = true;
    			}
    		});
    		
    		if (bUpdateGridTemplate) {
    			// Update grid row templates if custom tasks/styles are added 
    			gridData.rowTemplate = gridData._tmpl(gridData.options.rowTemplate, gridData.columns);
    			gridData.altRowTemplate = gridData._tmpl(gridData.options.rowTemplate, gridData.columns);
    
    			// Refresh grid to show column template changes
    			gridData.refresh();
    		}
    	},
    	build: {
    		cell: function cell (column) {
    			var template = " \
    				<div class=\"ra-grid-task-container\" style=\"" + column.style(column) + "\"> \
    					<ul class=\"ra-grid-task-menu\">";
    						$.each(column["tasks"], function (taskIndex, task) {
    							template += task.template(column);
    						});
    						template += " \
    					</ul> \
    					<span class=\"ra-grid-task-content\"> \
    						#: " + column.field + " # \
    					</span> \
    				</div>";
    			return template;
    		},
    		listItem: function listItem (field, role, icon) {
    			icon = icon || 'fa-pencil';
    			
    			var template = " \
    				<li class=\"ra-grid-task-item\" data-role=\"" + role + "\" data-field=\"" + field + "\" onclick=\"app.custom.gridTasks.callback(this);\"> \
    					<a class=\"ra-icon ra-grid-task-icon\"> \
    						<i class=\"fa " + icon + "\"></i> \
    					</a> \
    				</li>";
    			return template;
    		}
    	}
    };
    

    Adding a grid style to Priority with different colors depending on value:
    var gridData = $("div[data-role='grid']").data("kendoGrid");
    app.custom.gridTasks.add(gridData, "Priority", "style", "", function (column) {
        // Custom Priority Style Template
        var template = " \
            # if (!_.isUndefined(Priority)) { \
                switch (Priority) { \
                    case \"4\": \
                        # # \
                        break; \
                    case \"3\": \
                        # background-color:rgba(0, 255, 0, 0.25); # \
                        break; \
                    case \"2\": \
                    case \"Medium\": \
                        # background-color:rgba(255, 255, 0, 0.25); # \
                        break; \
                    case \"1\": \
                    case \"High\": \
                        # background-color:rgba(255, 0, 0, 0.25); # \
                        break; \
                } \
            } #";
        return template;
    });


    Adding two grid tasks to AssignedUser with unique callbacks and filters:
    app.custom.gridTasks.add(gridData, "AssignedUser", "task", "GetUserInfo", function (column) {
        // Custom GetUserInfo Task Template
        var template = " \
            # if (!_.isUndefined(WorkItemType) && (WorkItemType==='System.WorkItem.Incident' || WorkItemType==='System.WorkItem.ServiceRequest')) { \
                if ( !_.isUndefined(AssignedUser) && AssignedUser !== \"\") { # \
                    " + app.custom.gridTasks.build.listItem("AssignedUser", "GetUserInfo", "fa-info-circle") + " \
                # } \
            } #";
        return template;
    }, function (dataItem) {
        console.log("GetUserInfo Callback", dataItem);
        return "";
    });
    
    app.custom.gridTasks.add(gridData, "AssignedUser", "task", "AssignToAnalystByGroup", function (column) {
        // Custom AssignToAnalystByGroup Task Template        
        var template = " \
            # if (!_.isUndefined(WorkItemType) && (WorkItemType==='System.WorkItem.Incident' || WorkItemType==='System.WorkItem.ServiceRequest')) { # \
                " + app.custom.gridTasks.build.listItem("AssignedUser", "AssignToAnalystByGroup", "fa-pencil") + " \
            # } #";
        return template;
    }, function (dataItem) {
        console.log("AssignToAnalystByGroup Callback", dataItem);
        return "";
    });
    var gridData = $("div[data-role='grid']").data("kendoGrid");
    


    Some custom CSS for our new grid task elements:
    .ra-grid-task-container {
    	display: block;
    	width: 100%;
    	height: 100%;
    	margin:-5px -6px -4px;
    	padding: 5px 6px 4px 6px;
    }
    
    .ra-grid-task-menu {
    	display: inherit;
    	list-style: none;
    	float:right;
    	text-align:right;
    	margin:-5px -6px -4px;
    	padding: 5px 0px 0px 6px;
    }
    
    .ra-grid-task-item {
    	display: inline-block;
    	margin: 0px 6px 4px 0px;
    }
    
    .ra-grid-task-icon {
    	display: inherit;
    }
    
    .ra-grid-task-content {
    	display: inline;
    	width: 100%;
    }

    And finally updating the grid to show our changes:
    var gridData = $("div[data-role='grid']").data("kendoGrid");
    app.custom.gridTasks.updateGrid(gridData);
    TBD:
    - Combining this new grid task functionality with my previous trigger script for Assign To Analyst By Group.
    - Adding the option of using a html file to define Kendo external templates.
    - Still potentially adding enable/disable functionality to buttons rather than just not rendering them.

    Please let me know if anyone has any thoughts or ideas as I'm working on this one.  Thanks!
  • Mikkel_MadsenMikkel_Madsen Customer Advanced IT Monkey ✭✭✭
    Wow... I'm looking forward to get time to try some  of it out :smiley:

  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited December 2018
    Hello!
    Hope you had a good weekend.  I have added quite a bit more polish on this (attached).
    - Custom task templates now support tasks and links.
    - Callbacks can now dynamically stop click propagation to the built-in click functionality.
    - Added to custom.css for better highlighting of tasks in the grid title and regular cell sections and removed the built-in right-arrow in Team Work/Active Work views.
    - Fully functional Assign To Analyst By Group and custom Title links for same page and new tab. (below)
    custom.js:
    </code>app.custom.gridTasks = {
        add: function add (gridData, field, type, name, template, callback) {
            var that = this,
                // Look for provided column in grid by field name
                taskColumn = $.grep(gridData.columns, function (colValue, colIndex) {
                    return colValue.field === field;
                })[0];
    
            if (!_.isUndefined(taskColumn)) {
                if (_.isUndefined(taskColumn["style"])) {
                    // Add default blank style template function to column template
                    taskColumn["style"] = function defaultStyle (data) { return ""; };
                }
    
                if (_.isUndefined(taskColumn["tasks"])) {
                    // Add empty tasks array to column template
                    taskColumn["tasks"] = [];
                }
    
                switch (type) {
                    case "style":
                        // Set style template function to provided template
                        taskColumn["style"] = template
                        break
                    case "task":
                        var existingTask = that.get(gridData, field, name);
                        if (existingTask) {
                            // Merge new task with existing one in the column template
                            $.extend(existingTask, {
                                name : name,
                                template: template,
                                callback: callback
                            });
                        } else {
                            // Add new task to the column template
                            taskColumn["tasks"].push({
                                name : name,
                                template: template,
                                callback: callback
                            });
                        }
                        break;
                }
            } else {
                console.log("gridTasks:add", "Warning! Unable to find field '" + field + "'.");
            }
        },
        get: function get (gridData, field, name) {
            // Look for provided column in grid by field name
            var taskColumn = $.grep(gridData.columns, function (colValue, colIndex) {
                return colValue.field === field;
            })[0];
    
            if (!_.isUndefined(taskColumn)) {
                if (_.isUndefined(name)) {
                    // Return all tasks for the provided field
                    return taskColumn["tasks"];
                } else {
                    // Look for the specific task named in the provided field
                    var gridTask = $.grep(taskColumn["tasks"], function (taskValue, taskIndex) {
                        return taskValue.name === name;
                    })[0];
    
                    if (!_.isUndefined(gridTask)) {
                        // Return the specific task in the provided field
                        return gridTask;
                    } else {
                        console.log("gridTasks:get", "Warning! Unable to find task '" + name + "' in field '" + field + "'.");
                        return null;
                    }
                }
            } else {
                console.log("gridTasks:get", "Warning! Unable to find field '" + field + "'.");
                return null;
            }
        },
        callback: function callback (itemEle, bClickPropagation) { // item is the task element clicked, bClickPropagation determines if click event should propagate
            var that = this,
                item = $(itemEle),
                gridData = item.closest("div[data-role='grid']").data("kendoGrid"),
                itemData = item.data(),
                itemRowEle = item.closest("tr").get(0),
                dataItem = gridData.dataItem(itemRowEle),
                data = {
                    gridData: gridData,
                    itemRowEle: itemRowEle,
                    dataItem: dataItem,
                    itemData: itemData
                }
    
            console.log("gridTasks:callback", data);
    
            var existingTask = that.get(gridData, itemData.field, itemData.task);
            if (existingTask) {
                // Stop click propagation for jQuery click events if requested
                if (!bClickPropagation) {
                    event.stopPropagation();
                }
                existingTask.callback(data);
            } else {
                console.log("gridTasks:callback", "Unable to find task for callback.");
            }
        },
        updateGrid: function (gridData) {
            var that = this,
                bUpdateGridTemplate = false;
    
            $.each(gridData.columns, function (colIndex, column) {
                if (!_.isUndefined(column["style"])) {
                    column.template = that.template.cell(column);
                    bUpdateGridTemplate = true;
                }
            });
    
            if (bUpdateGridTemplate) {
                // Update grid row templates if custom tasks/styles are added
                gridData.rowTemplate = gridData._tmpl(gridData.options.rowTemplate, gridData.columns);
                gridData.altRowTemplate = gridData._tmpl(gridData.options.rowTemplate, gridData.columns);
    
                // Refresh grid to show column template changes
                gridData.refresh();
            }
        },
        template: {
            cell: function cell (column) {
                var template = " \
                    <div class=\"ra-grid-task-container\" style=\"" + column.style(column) + "\"> \
                        <ul class=\"ra-grid-task-menu\">";
                            $.each(column["tasks"], function (taskIndex, task) {
                                template += task.template(column, task);
                            });
                            template += " \
                        </ul> \
                        <span class=\"ra-grid-task-content\"> \
                            #: " + column.field + " # \
                        </span> \
                    </div>";
                return template;
            },
            listItem : {
                task: function task (field, task, options) {
                    var properties = {
                        field: field,
                        task: task,
                        icon: "fa-pencil",
                        bClickPropagation: true
                    };
    
                    $.extend(properties, options);
    
                    var template = " \
                        <li class=\"ra-grid-task-item\" data-task=\"" + properties.task + "\" data-field=\"" + properties.field + "\" onclick=\"app.custom.gridTasks.callback(this, " + properties.bClickPropagation + ");\"> \
                            <a class=\"ra-icon ra-grid-task-icon\"> \
                                <i class=\"fa " + properties.icon + "\"></i> \
                            </a> \
                        </li>";
                    return template;
                },
                link: function link (field, task, options) {
                    var properties = {
                        field: field,
                        task: task,
                        icon: "fa-external-link",
                        bClickPropagation: false,
                        className: "",
                        href: "/",
                        target: "_blank"
                    }
    
                    $.extend(properties, options);
    
                    var template = " \
                        <li class=\"ra-grid-task-item " + properties.className + "\" data-task=\"" + properties.task + "\" data-field=\"" + field + "\" onclick=\"app.custom.gridTasks.callback(this, " + properties.bClickPropagation + ");\"> \
                            <a class=\"ra-icon ra-grid-task-icon\" href=\"" + properties.href + "\" target=\"" + properties.target + "\" > \
                                <i class=\"fa " + properties.icon + "\"></i> \
                            </a> \
                        </li>";
                    return template;
                }
            }
        }
    };<br></pre><div>custom.css:<br><pre class="CodeBlock"><code>/*
        Custom Grid Tasks
    */
    
    .k-grid tr:hover td.grid-highlight-column-last a:after,
    .k-grid tr:hover td.grid-highlight-column-title a:after,
    .k-grid .k-state-selected td.grid-highlight-column-last a:after,
    .k-grid .k-state-selected td.grid-highlight-column-title a:after {
        content: none;
    }
    
    .k-grid .ra-grid-task-container {
        display: block;
        width: 100%;
        height: 100%;
        margin:-5px -6px -4px;
        padding: 5px 6px 4px 6px;
    }
    
    .k-grid .grid-highlight-column .ra-grid-task-menu,
    .k-grid .grid-highlight-column-title .ra-grid-task-menu,
    .k-grid .grid-highlight-column-last .ra-grid-task-menu {
        margin-right: -20px;
        visibility: hidden;
    }
    
    .k-grid tr:hover .ra-grid-task-menu,
    .k-grid .k-state-selected .ra-grid-task-menu {
        visibility: visible;
    }
    
    .k-grid .k-state-selected td.grid-highlight-column .ra-grid-task-menu i,
    .k-grid .k-state-selected td.grid-highlight-column-title .ra-grid-task-menu i,
    .k-grid .k-state-selected td.grid-highlight-column-last .ra-grid-task-menu i,
    .grid-highlight-column .ra-grid-task-container i,
    .grid-highlight-column-title .ra-grid-task-container i,
    .grid-highlight-column-last .ra-grid-task-container i {
        color: #a3b8c2;
    }
    
    .k-grid .k-state-selected td.grid-highlight-column .ra-grid-task-menu i:hover,
    .k-grid .k-state-selected td.grid-highlight-column-title .ra-grid-task-menu i:hover,
    .k-grid .k-state-selected td.grid-highlight-column-last .ra-grid-task-menu i:hover,
    .k-grid .k-state-selected .ra-grid-task-container i:hover,
    .grid-highlight-column .ra-grid-task-container i:hover,
    .grid-highlight-column-title .ra-grid-task-container i:hover,
    .grid-highlight-column-last .ra-grid-task-container i:hover,
    .k-grid tr:hover .ra-grid-task-menu .ra-highlight-default-icon i {
        color: #fff;
    }
    
    .grid-highlight-column ul.ra-grid-task-menu:hover li.ra-highlight-default-icon i,
    .grid-highlight-column-title ul.ra-grid-task-menu:hover li.ra-highlight-default-icon i,
    .grid-highlight-column-last ul.ra-grid-task-menu:hover li.ra-highlight-default-icon i {
        color: #a3b8c2;
    }
    
    .grid-highlight-column ul.ra-grid-task-menu:hover li.ra-highlight-default-icon i:hover,
    .grid-highlight-column-title ul.ra-grid-task-menu:hover li.ra-highlight-default-icon i:hover,
    .grid-highlight-column-last ul.ra-grid-task-menu:hover li.ra-highlight-default-icon i:hover {
        color: #fff;
    }
    
    .k-grid .ra-grid-task-menu {
        display: inherit;
        list-style: none;
        float:right;
        text-align:right;
        margin:-5px -6px -4px;
        padding: 5px 0px 0px 6px;
    }
    
    .k-grid .ra-grid-task-item {
        display: inline-block;
        margin: 0px 6px 4px 0px;
    }
    
    .k-grid .ra-grid-task-icon {
        display: inherit;
    }
    
    .k-grid .ra-grid-task-content {
        display: inline;
        width: 100%;
    }

    WIP In Action:
    The script below shows a fully functional customization with:
    - Internal/External Links in Title.
    - Quick edit buttons for Assign To Analyst By Group in Assigned User
    - Background color conditional formatting for Priority.

    // Adding a grid style to Priority with different depending on value:
    var gridData = $("div[data-role='grid']").data("kendoGrid");
    app.custom.gridTasks.add(gridData, "Priority", "style", "", function (column) {
        // Custom Priority Style Template
        var template = " \
            # if (!_.isUndefined(Priority)) { \
                switch (Priority) { \
                    case \"4\": \
                        # # \
                        break; \
                    case \"3\": \
                        # background-color:rgba(0, 255, 0, 0.25); # \
                        break; \
                    case \"2\": \
                    case \"Medium\": \
                        # background-color:rgba(255, 255, 0, 0.25); # \
                        break; \
                    case \"1\": \
                    case \"High\": \
                        # background-color:rgba(255, 0, 0, 0.25); # \
                        break; \
                } \
            } #";    
        return template;
    });
    
    if (session.user.Analyst) {
        // Adding custom internal and external links to the Title column
        app.custom.gridTasks.add(gridData, "Title", "task", "TitleLinks", function (column, task) {
            // Custom Title Links Task Template
            var template = " \
                # var url = app.gridUtils.getLinkUrl(data, \"***\"); \
                if (!_.isUndefined(WorkItemType) && (WorkItemType==='System.WorkItem.Incident' || WorkItemType==='System.WorkItem.ServiceRequest')) { #" +
                    app.custom.gridTasks.template.listItem.link(column.field, task.name, {
                        href: "#=url#"
                    }) +
                "# } else if ((!_.isUndefined(WorkItemType)&& WorkItemType.indexOf('Activity') != -1)) { \
                    var approvalUrl = app.gridUtils.getApprovalLinkUrl(data); # " +
                    app.custom.gridTasks.template.listItem.link(column.field, task.name, {
                        icon: "fa-check",
                        href: "#=approvalUrl#"
                    }) + " \
                # } # " +
                app.custom.gridTasks.template.listItem.link(column.field, task.name, {
                    icon: "fa-arrow-right",
                    bClickPropagation: true,
                    className: "ra-highlight-default-icon",
                    href: "#=url#",
                    target: ""
                });
            return template;
        }, function (data) {
            console.log("TitleLinks Callback", data);
        });
    
        // Adding grid task to trigger AssignToAnalystByGroup
        app.custom.gridTasks.add(gridData, "AssignedUser", "task", "AssignToAnalystByGroup", function (column, task) {
            // Custom AssignToAnalystByGroup Task Template
            var template = " \
                # if (!_.isUndefined(WorkItemType) && (WorkItemType==='System.WorkItem.Incident' || WorkItemType==='System.WorkItem.ServiceRequest')) { #" +
                    app.custom.gridTasks.template.listItem.task(column.field, task.name, {
                        icon: "fa-pencil",
                        bClickPropagation: false
                    }) + " \
                # } #";
            return template;
        }, function (data) {
            console.log("AssignToAnalystByGroup Callback", data);
            data.gridData.clearSelection();
            data.gridData.select(data.itemRowEle);
    
            var assignToAnalystByGroupButton = $('li[data-bind*="click: analystByGroup"]').first();
    
            assignToAnalystByGroupButton.click();
        });
    }
    
    // Updating the grid to show our changes:
    app.custom.gridTasks.updateGrid(gridData);
    TBD:
    - Adding the option of using html files to define Kendo external templates.
    - Potentially adding enable/disable functionality to buttons rather than just not rendering them. Leaning more on a no on this feature as I go.
    - Removing custom tasks and potentially resetting grid columns to out of the box.

    Please let me know if anyone has any thoughts or ideas as I'm working on this.  Thanks!
  • Justin_WorkmanJustin_Workman Cireson Support Super IT Monkey ✭✭✭✭✭
    @Ryan_Lane -  These customizations look awesome!  You're knocking it out of the park!  You might consider a github project for organization's sake(and potential contribution by others).
  • Brian_WiestBrian_Wiest Customer Super IT Monkey ✭✭✭✭✭
    Both the previous version and the latest I am getting an error "Uncaught TypeError: cannot read property "columns' of undefined."
    Testing on the latest portal release v8.10.0.x and v8.11.0.x
    Suggestions?
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited December 2018
    @Justin_Workman Good point! I've created CustomGridTasks on GitHub.
    @Brian_Wiest That's most likely due to the script triggering before the grid is ready to be modified. A quick subscription to the dynamicPageReady event and a new variable to track if our customization is done at app.custom.gridTasks.built should do it.  I have updated, renamed and pushed custom.js and custom.css to the github repo.

    Related new code from custom.js (updated with latest push):
    app.custom.gridTasks = {
        built: false,
    ...
    };
    
    app.events.subscribe("dynamicPageReady", function () {
        console.log("dynamicPageReady Event", "Triggered at " + performance.now());
        if (app.custom.gridTasks.built) {
            return;
        }
    
        var gridData = $("div[data-role='grid']").data("kendoGrid");
        if (!_.isUndefined(gridData)) {
    ...
            app.custom.gridTasks.built = true;
        }
    });
    

    Thanks for the feedback!

    EDIT: I've also added a quick check after the dynamicPageReady event to confirm the page actually has a grid to customize.  Updated custom.js has been pushed.
  • Brian_WiestBrian_Wiest Customer Super IT Monkey ✭✭✭✭✭
    That did the trick. Thanks
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    @Brian_Wiest Thanks for testing!  I have updated CustomGridTasks to disable enumeration on the custom properties _tasks and _style so they are no longer passed to jQuery when additional Ajax queries are made.  This was mainly a problem after resizing a column but will fix any other potential queries as well.

  • Brian_WiestBrian_Wiest Customer Super IT Monkey ✭✭✭✭✭
    edited December 2018
    I have rolled it out to production and analysts are liking the priority colors. Just have to work on the pencil icon as our color scheme has it yellow against a dark grey background for the bottom bar. So its yellow in the column and hard to see. Playing with options for a pencil 2 with different color. BTW excellent work. :)


  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited December 2018
    Thanks @Brian_Wiest!
    If you'd like to target the non-title section icons you can use the following CSS:
    a.ra-grid-task-icon i {
        color: #FF0000;
    }
    
    a.ra-grid-task-icon i:hover {
        color: #00FF00;
    }
    This works with the other CustomGridTask CSS in order of complexity so we can keep the title section links replicating the out of the box behavior while targeting the new icons with new coloring.

    Example 1:
    1. Selected Row
    2. Unselected Row
    3. Hovered Row & Icon


    Example 2:
    1. Selected Row & Hovered Icon 
    2. Unselected Row
    3. Unselected Row

    If you'd also like to change the icon color behavior when selected and hovering let me know and I'll see about the CSS for that scenario.


    EDIT:  I have added the examples as commented out classes in custom.css.  Also, I have made some light changes to custom.js to minimize the amount of whitespace in the final cell templates so that jQuery isn't sending unnecessarily large numbers of empty characters to the server when columns are converted to params:


    Have a good weekend all!
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    Hello all!
    It's been a busy couple of weeks but I've got a few updates for my gridTasks plugin.  I'm not sure how many others make use of the RequireJS optimizer in production but we do in our environment and I've updated the repo with it as well as other functionality and bug-fixes:
    1. Custom Session Debugging
    Added app.storage.custom to handle storing debug data for a session to make troubleshooting from the client side easier.  Now you can enable/disable debug mode via:
    app.storage.custom.set("debug", true); // Enable DEBUG Mode via Console/Script/Plugin
    app.storage.custom.set("debug", false); // Disable DEBUG Mode via Console/Script/Plugin
    In my case I made a quick tampermonkey script to quickly enable/disable debug mode:

    2. Custom Utilities
    Included a small number of utility functions under app.custom.utils to be used with this and other custom scripts:
    - getCachedScript
    - getCSS
    - isGuid
    - sortList
    - stringFormat

    3. Rewrote gridTask as a RequireJS module
    RequireJS has a great option for optimizing module dependencies in production that we use for all of the *main.js files (wiMain/viewMain/profileMain) so gridTasks has been refactored to work as a module.  The "built" folder in the repo contains the RequireJS Configs and a batch file to build /custom.min.css and /Scripts/grids/gridTaskMain-built.min.js.

    4. RequireJS Ready Monitoring
    Added a small section bit of code at the end of custom.js to monitor for RequireJS so we can immediately start loading the plugin as soon as the page is ready by subscribing to requirejsReady.
    </code>app.events.subscribe("requirejsReady", function () {
        /* Begin Loading gridTasks Here */
    }</pre><br>5. Dynamically loading gridTasks and the <b>gridTasksReady </b>event<br>GridTasks is now loaded via calling the utility function&nbsp;<b>app.custom.utils.getCachedScript</b>:<br><pre class="CodeBlock"><code>app.events.subscribe("requirejsReady", function () {
        app.custom.utils.getCachedScript("/CustomSpace/Scripts/grids/gridTaskMain-built.min.js");
    }
    After loading, gridTasks will publish the gridTasksReady event.  So we subscribe to gridTasksReady and place any custom grid task function calls within the callback argument:
    app.events.subscribe("gridTasksReady", function () {
        /* Custom Grid Tasks Here */
    }

    6. New HTML templates and OnClick callback behavior bug-fixes
    Moved the Angular templates used creating the final column templates for gridTasks from inline variables to individual HTML files in /Scripts/grids/tasks.  Now customizing them and adding new types should be an easier process.  I have also updated the OnClick callback behavior to handle stopping click propagation in browsers like Firefox where the Event object is no longer global.

    Please let me know if anyone has any questions or comments, thanks!
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    Hello!
    It's been some time since I last checked in and I've made a number of improvements to the gridTasks plugin.  I have attached a minimal configuration to get gridTasks up and running.  The main custom.js file includes utility functions for (un)loading javascript and css files, enabling/disabling debug mode and the calls for actually loading gridTasks.
    To load the plugin, custom.js loads the minified gridTaskMain-built.min.js to get the base gridTasks library and then custom.gridTasks.js for the actual customization:
    if (window.location.pathname.indexOf('/View/') > -1) {
      /*
        Custom Grid Tasks
      */
      app.custom.utils.getCachedScript('/CustomSpace/Scripts/grids/gridTaskMain-built.min.js');
      app.custom.utils.getCachedScript('/CustomSpace/custom.gridTasks.js');
    }
    custom.gridTasks.js is very similar to the previous examples I've provided.  I've just wrapped the calls in some logic for handling async loads and simplified the template strings to make them hopefully more readable.  With this setup you could also have multiple versions of files to serve users/analysts if you find that the customizations are getting a bit involved as well.

    For a more detailed view of my setup and changes you can check out the source files on my github repo here.  Still working on a lot of the documentation so if you have any questions feel free to let me know!
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited February 2019
    Sorry about the double post!  There were some oddities with file attachments yesterday and it looks like my draft was posted a couple of times without the attachment.  I do have another update based on feedback I've gotten from my users, I have updated the icon highlighting and click behaviors to make selecting them much easier.

    The dark green background in the images below represents the clickable area for links (not visible to end users).
    Previous icon-only hover+click:

    New hover+click:


    EDIT:
    I was walking through how Search grids are generated and they have a nice queryBuilderGridReady event published on searching for both saved searches and in the query builder that I was able to plug into custom.gridTasks.js:
    app.events.subscribe('queryBuilderGridReady', function (event) {
      populateGridTasks();
    }); 
    Added a small check for the AssignToAnalystByGroup task in the Assigned User column too and voilà!
    <img alt="" src="https://us.v-cdn.net/6026663/uploads/editor/tw/ypr2o718idse.png" title="Image: https://us.v-cdn.net/6026663/uploads/editor/tw/ypr2o718idse.png"><br>

  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    Hello!
    I have been going over my current code base for documentation and refactoring and have made a number of small improvements to gridTasks.

    - Style task now applies provided template (either function or string) to the column.attributes.style value instead of modifying the column's template string.


    - Simplified the anchor Underscore/Kendo template to remove the container div and content span thanks to using the column.attributes.class value.
    Old:
    <div class="ra-grid-task-container clearfix" style="<%= _style().replace(/ {4}/g,"") %>">
        <ul class="ra-grid-task-menu">
            <% _.forEach(_tasks, function (task) { %>
                <%= task.template(obj, task).replace(/ {4}/g,"") %>
            <% }) %>
        </ul>
        <span class="ra-grid-task-content">#: <%= field %> #</span>
    </div>
    New:
    <ul class="ra-grid-task-menu">
        <% _.forEach(_tasks, function (task) { %>
            <%= task.template(obj, task).replace(/ {4}/g,"") %>
        <% }) %>
    </ul>
    #: <%= field %> #
    - app.custom.gridTasks.updateGrid has been renamed to app.custom.gridTasks.apply and includes some minor modifications on when a column's template is updated.
    - New app.custom.gridTasks.ready function to provide a deferred callback when gridTasks is ready so code can be executed immediately after gridTasks is ready without needed to wait for an event trigger.
    The following is an excerpt from custom.gridTasks.js where I use app.custom.gridTasks.ready.  Just in case the code is loaded and executed before gridTaskMain-built.min.js is I also use a single event subscriber with $.one() to gridTasks.ready:
    /**
     * Initialize Custom Grid Tasks.
     */
    function initGridTasks() {
      if (!_.isUndefined(app.storage.custom) && app.storage.custom.get('DEBUG_ENABLED')) {
        app.custom.utils.log('custom.gridTasks:initGridTasks');
      }
      // Immediately attempt to populate grid tasks.
      populateGridTasks();
      // Subscribe to queryBuilderGridReady events to populate dynamics grids.
      app.events.subscribe('queryBuilderGridReady', populateGridTasks);
    }
    
    if (typeof app.custom.gridTasks !== 'undefined') {
      app.custom.gridTasks.ready(initGridTasks);
    } else {
      // Subscribe initGridTasks to gridTasks.Ready event once.
      $(app.events).one('gridTasks.Ready', initGridTasks);
    }<br>
    Please let me know if anyone has any questions or comments, thanks!
  • Mikkel_MadsenMikkel_Madsen Customer Advanced IT Monkey ✭✭✭
    I'm looking so much forward to getting time to play around with this :smiley:

  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    edited March 2019
    Thanks @Mikkel_Madsen
    Another small update.  Due to how Work Item grids handle hidden field attributes and state saving/loading, using a Kendo template for style on a hidden column will cause parsing errors on refresh.  To work around this I have created a new 'Class' option when adding a grid task.
    Priority colors are now handled via:
    <div>(custom.gridTasks.js:23):
    app.custom.gridTasks
      // Adding background colors to the Priority column based on value.
    <span style="background-color: transparent; color: inherit; font-size: inherit; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;">  .add(gridData, 'Priority', 'class', '', "#= 'ra-grid-priority-' + Priority #")</span></div><div>
    (custom.css:7):
     /*
      * Grid Priority Highlighting
      */
    .ra-grid-priority-1,
    .ra-grid-priority-High {
      background-color: rgba(255, 0, 0, 0.25);
    }
    
    .ra-grid-priority-2,
    .ra-grid-priority-Medium {
      background-color: rgba(255, 255, 0, 0.25);
    }
    
    .ra-grid-priority-3 {
      background-color: rgba(0, 255, 0, 0.25);
    }
    
    .ra-grid-priority-4,
    .ra-grid-priority-Low {
      /* background-color: rgba(255, 0, 0, 0.25); */
    }</div>
    Please let me know if you any questions or comments, thanks!
  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭
    Hello!
    Minor feature updates of the day:
    1. 'Class' task now has a check for previously created templates for Priority to remove deprecated templates.
        a.  This isn't needed if a user resets their cache or view, but does help sanitize things in the background.
    2. Added an optional title param for links and task templates.
    3. Updated app.custom.gridTasks.apply function to handle pre-rendered grids.
        a. Resolves loading masks not displaying properly ('No Results' shows while loading).

    1. To remove deprecated Priority field templates now that we use Class/Style I added the following check for a pre-existing definition of the template to set back to out of the box behavior:
    (gridTaskBuilder.js:119):


    2. Here's a sample use for the 'Assign To Analyst By Group' grid task using localizationHelper:
    (custom.gridTasks.js:67):

    Result:


    3. app.custom.gridTasks.apply function now only calls grid.refresh when data is already loaded and rendered, otherwise it calls the grid._updateCols internal function to update column templates before the pending render.  This means the loading mask (three green squares animation) displays properly instead of showing 'No Results' while waiting for a load to complete.

    Have a great day!
  • Justin_WorkmanJustin_Workman Cireson Support Super IT Monkey ✭✭✭✭✭

    @Ryan_Lane - TBH I've just now gotten around to implementing this. IT IS AMAZING! I'm swimming with ideas! I just have one question. Are your most recent updates reflected in the github repo?

  • Ryan_LaneRyan_Lane Cireson Support Advanced IT Monkey ✭✭✭

    @Justin_Workman Hey Justin, thanks! That's right, the most recent changes are located in the repo at https://github.com/ryanlanegit/CustomSpace/tree/master/Scripts/grids

    Haven't had the chance to create documentation on it's usage outside of my environment so it may be a task to get it up and running. Let me know if you have any questions on it.

    Here are a couple examples from our current evironment to show how we're using it.

    Work Item grid view tasks:

    Adding an Edit task to new comments in the Action Log:


Sign In or Register to comment.