KB Category Picker not working with custom table

Eddie_N
Kilo Explorer

Hello,

I'm having issues with setting up the KB Category Picker to work with a custom lookup table / form that I've created. The desired KB Category Picker that I want is shown below; it is the same picker that is used when creating a new knowledge article ("kb_knowledge" table). It is different from a regular list in that it allows you to drill down through the different levels of categories for the selected KB.

find_real_file.png

 

My table ("u_kb_category_to_approval_group") is extended from "dl_matcher" (lookup) and has the following three user-columns:

find_real_file.png

 

These fields all have the "u_" prefix because they are custom user fields. I want to have the same KB Category Picker for my "u_kb_category" field. The default KB Category Picker is handled by the "kb_category_reference_lookup" UI macro and "kb_categories_dialog" UI page. However, they both work with the "kb_category" and "kb_knowledge_base" fields whereas my equivalent fields include the "u_" prefix. As such, I had to create a copy of both the UI macro ("kb_category_reference_lookup_2") and the UI page ("kb_categories_dialog_2") and change them accordingly to reference my prefixed fields. The KB Category Picker dialog opens correctly for my form except it doesn't behave correctly when I select a category and press the "Ok" button; it doesn't acknowledge the selected category so an empty value is copied to the parent form's category field. I've troubleshooted and I don't believe that the "columnview_select" code in "kb_categories_dialog_2" client script is running as it should. It should be setting the hidden control called "categoryId" (in the dialog window) to the selected value each time a category is selected but its not. Your help in solving this issue would be greatly appreciated. Thank you!

The relevant code and configurations are shown below.

u_kb_category field decorations and reference qualifiers:

find_real_file.png

 

"kb_category_reference_lookup_2" UI macro XML code:

 

<?xml version="1.0" encoding="utf-8" ?>
  <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <g:inline template="ie_checker.xml" />
    <g:requires name="scripts/lib/jquery_includes.js"/>
	<g2:evaluate var="jvar_table_name">
		var tableName = current.getTableName();
		tableName;
	</g2:evaluate>
	<g2:evaluate var="jvar_contribute_kb">
		var knowledgeBases = new SNC.KnowledgeHelper().getWritableKnowledgeBases();
		var answer = [];
		while (knowledgeBases.next())
				answer.push(knowledgeBases.getValue('sys_id'));
		answer;
	</g2:evaluate>
	<g2:evaluate var="jvar_timeout">
		gs.minutesAgo(gs.getProperty('glide.ui.session_timeout') || 40);
	</g2:evaluate>
	<g2:evaluate var="jvar_msg_lookup_picker">
		gs.getMessage("Lookup using picker");		
	</g2:evaluate>	  
<script>
	var canCreateCategory='false', oldKnowledgeBase, category_link_id;
	function getCompatibleColumnWidth() {
		if ((window.frameElement) $[AMP]$[AMP] ( window.frameElement.id == 'dialog_frame')){
			return (window.frameElement.getWidth() * 0.75);
		}

		// Resize if displayed from a dialog
		return 700; // Default value for content width
	}

	function canContribute(kbId) {
		return '$[jvar_contribute_kb]'.indexOf(kbId) > -1;
	}

	function openDialog() {
		var kb_knowledge_base = g_form.getValue('u_kb_knowledge_base');
		var kb_knowledge_base_gr = g_form.getReference('u_kb_knowledge_base');
		var table_name = g_form.getTableName();
		
		//Verify for canCreate access only if Knowledge Base is changed
		if(oldKnowledgeBase!=kb_knowledge_base){
			var ga = new GlideAjax('KBCategoryCrud');
			ga.addParam('sysparm_name','canCreate');
			ga.addParam('sysparm_id', kb_knowledge_base); // ID of parent Knowledge Base
			var responseXML = ga.getXMLWait();
			if (responseXML $[AMP]$[AMP] responseXML.documentElement) {
				canCreateCategory = responseXML.getElementsByTagName("result")[0].getAttribute("canCreateCategory");
				oldKnowledgeBase = kb_knowledge_base;
			}else{
				console.log("ERROR: Error in checking for canCreate access on category. Please check the server response below.");
				console.log(responseXML);
				canCreateCategory = 'false';
			}
		}
	
		if (!kb_knowledge_base || !kb_knowledge_base.length)
			return;
		if (kb_knowledge_base_gr.kb_version === '3') {
			//Allow only those KBs to which current user can contribute and are created newly by him
			if (!canContribute(kb_knowledge_base) $[AMP]$[AMP] !(kb_knowledge_base_gr.sys_created_by == g_user.userName $[AMP]$[AMP] kb_knowledge_base_gr.sys_created_on > '$[jvar_timeout]'))
				return ;
		}
		var disable_editing = 'false';
		if(canCreateCategory == 'false' || kb_knowledge_base_gr.disable_category_editing == 'true'){
			disable_editing = 'true';
		}
		var dialog = new GlideDialogWindow('kb_categories_dialog_2');
		dialog.setTitle('${gs.getMessage('Category picker')}');
		dialog.setSize(getCompatibleColumnWidth(), 250);
		dialog.setPreference('kb_knowledge_base', kb_knowledge_base);
		dialog.setPreference('disable_editing', disable_editing);
		dialog.hideCloseButton();
		dialog.on('beforeclose', function(){
			setTimeout(function(){
				if(category_link_id){		
					$j(category_link_id).focus();
				}});
		});
		dialog.render();
		var isQuirksMode = (document.compatMode !== 'CSS1Compat');
		if (!isQuirksMode) {
			// In modern browsers only: make the dialog box fixed to avoid it scrolling away while using its scrollable content.
			// Do dialog css adjustment only after whole dialog has been rendered
			dialog.on("bodyrendered", function() {
				var wndw = $j(window);
				var dlgSecPart = $j('.drag_section_part');
				dlgSecPart.css('width', 'auto').css('height', 'auto');
				dlgSecPart.css('position', 'fixed');
				dlgSecPart.css('top', Math.max(0, ((wndw.height() - dlgSecPart.outerHeight()) / 2) + wndw.scrollTop()) + 'px');
				dlgSecPart.css('left', Math.max(0, ((wndw.width() - dlgSecPart.outerWidth()) / 2) + wndw.scrollLeft()) + 'px');
	
			//Set focus to first element or add icon
				var firstListElm = $j("#body_kb_categories_dialog .colview-container .column .list-item");
				if(firstListElm.length)
					firstListElm.first().focus();
				else{
					$j("#body_kb_categories_dialog .colview-container .column .btn-add-cat").focus();
				}
			});
		}
	}

	$j(function($) {
		//doctype case in split layout
		var doctypeSplitView = '.vsplit.col-sm-6 > div';
		//doctype case in row (non split) layout
		var doctypeRowView = '.section-content > .row > .col-xs-12 > div';
		//non doctype case in split layout
		var nonDoctypeSplitView = '.vsplit_bottom_margin > tbody > tr';
		//non doctype case in row (non split) layout
		var nondoctypeRowView = '.wide > tbody > tr';

		var category_form_id = 'element.' + '$[jvar_table_name]' + '.u_kb_category';
		category_link_id = 'button[id="lookup.' + '$[jvar_table_name]' + '.u_kb_category"]';
		var init = function(selector) {
			$(selector).each(function() {
				if ($( this ).attr('id') == category_form_id){
					$( this ).find(category_link_id)
							.removeAttr("data-type" )
							.removeAttr("onclick" )
							.attr("type", "button")
							.click(openDialog)
							.removeAttr("title" )
							.attr("title", '$[jvar_msg_lookup_picker]' )
							.removeAttr("aria-label" )
							.attr("aria-label", '$[jvar_msg_lookup_picker]' );

				}
			});

		};
		//override of deafault onclick event will happen only for one selector that exixt on the page
		init(doctypeSplitView);
		init(doctypeRowView);
		init(nonDoctypeSplitView);
		init(nondoctypeRowView);
	});
</script>

</j:jelly>

 

"kb_categories_dialog_2" UI page HTML code:

<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:requires name="scripts/lib/jquery_includes.js"/>
<g:include_script src="jquery_columnview.jsdbx"/>

<g:evaluate var="jvar_json" copyToPhase2="true">
	var kb_knowledge_base = RP.getWindowProperties().get('kb_knowledge_base');
	var disable_editing = RP.getWindowProperties().get('disable_editing');
	var jsonStr = new SNC.KnowledgeHelper().getJSONCategories(kb_knowledge_base);
	
	var escape_text = gs.getProperty('glide.ui.escape_text');
	if (escape_text == 'false'){
		jsonStr = GlideStringUtil.escapeHTML(jsonStr);
	}
	jsonStr;
</g:evaluate>
	<p id="err_message" class="notification notification-error" style="display:none"></p>
<div style="padding: 6px">
	<div id="kbCategoriesJSON" style="display:none">$[jvar_json]</div>
	<input type="hidden" id="categoryId" value="" />
	<div id="dialog_buttons" align="left">
		<g:dialog_buttons_ok_cancel ok="setCategory()" ok_style_class="btn btn-primary category-ok-btn" ok_type="button" cancel_type="button" />
	</div>
</div>

<!-- placeholders to read the icon char code from by name. -->
<span class="icon-edit" style="visibility:hidden; position:absolute;"/>
<span class="icon-chevron-right" style="visibility:hidden; position:absolute;"/>
<span class="icon-chevron-left" style="visibility:hidden; position:absolute;"/>

</j:jelly>

 

"kb_categories_dialog_2" UI page client script code:

var catBeingCreated = 0, //flag
	closePickerClicked = 0; //flag

// Update the form
function setCategory() {
	if(!catBeingCreated){
		var id = $j('#categoryId').val();
		g_form.setValue('u_kb_category', id);
		GlideDialogWindow.get().destroy(); //Close the dialog window
	}
	else
		closePickerClicked = 1;
}

function getCompatibleColumnWidth() {
	if (window.frameElement && window.frameElement.id == "dialog_frame")
		return (window.frameElement.getWidth() * 0.75); // Resize if displayed from a dialog
	else
		return 700; // Default value for content width
}

$j(function() {
	var kbCategoriesJSON = $j('#kbCategoriesJSON');
	var kbCategoryId = g_form.getTableName() + '.u_kb_category';

	kbCategoriesJSON.columnview({
		jsonData: kbCategoriesJSON.html(),
		maxWidth: getCompatibleColumnWidth(),
		idValue: gel(kbCategoryId).value || false,
		blockEditing: $[disable_editing]
	}).bind({
		columnview_select: function (ev, obj) {
			alert("Here!"); // This code should run each time a row is clicked in the picker list; it behaves like that for the original picker.
			//$j("#categoryId").val(obj.id); // hidden field located at component level: gets destroyed with the component.
		},
		columnview_create: function(ev, obj) {
			// root nodes don't have a parent: use the KB article's knowledge base ID
			var kbElementVal = null;
			
			if(g_form.getControl("u_kb_knowledge_base")){
				kbElementVal = g_form.getValue("u_kb_knowledge_base");
			}else{
				return;
			}
			
			catBeingCreated = 1; //setting flag
			var id = (obj.id) ? obj.id : kbElementVal;
			var ga = new GlideAjax('KBCategoryCrud');
			ga.addParam('sysparm_name','create');
			ga.addParam('sysparm_label', obj.value);
			ga.addParam('sysparm_is_root', (obj.id ? 'false' : 'true'));
			ga.addParam('sysparm_id', id); // ID of parent
			ga.getXML(function(serverResponse) {
				if(serverResponse.status > 199 && serverResponse.status < 300){
					var result = serverResponse.responseXML.getElementsByTagName("result");
					var id = result[0].getAttribute("sysId");
					$j("#categoryId").val(id);
					if ($j.isFunction(obj.callback)) {
						obj.callback(id, obj);
					}
					catBeingCreated = 0;
					if(closePickerClicked) // in case 'OK' button was already clicked
						setCategory(); //set the new category and then close
				}
				else{
					console.log("ERROR: Error in creating the new category. Please check the server response below.");
					console.log(serverResponse);
					catBeingCreated = 0;
					if(closePickerClicked) // in case 'OK' button was already clicked
						setCategory(); //set the new category and then close
				}
			});
		},
		columnview_update: function(ev, obj) {
			var ga = new GlideAjax('KBCategoryCrud');
			ga.addParam('sysparm_name','update');
			ga.addParam('sysparm_id', obj.id); // ID of self
			ga.addParam('sysparm_label', obj.value);
			ga.getXML(function(serverResponse) {
				var result = serverResponse.responseXML.getElementsByTagName("result");
				$j("#categoryId").val(obj.id);
				var currentId = g_form.getValue('u_kb_category');
				if (currentId === obj.id) {
					// refresh the field when the user updates the label of same category.
					g_form.setValue('u_kb_category', obj.id);
				}
				if ($j.isFunction(obj.callback)) {
					obj.callback(obj.id, obj);
				}
			});
		}
	});
});

 

1 ACCEPTED SOLUTION

Sam S1
Giga Contributor

Had the same problem.
In UI Scripts clone "jquery_columnview" script and rename it.

In cloned UI Script search for "#body_kb_categories_dialog" and change it to "#body_<name of your dialog>" - in your case change it to #body_kb_categories_dialog_2

In UI Page HTML change include part to the changed name of the UI Script.

 

 

View solution in original post

4 REPLIES 4

Sam S1
Giga Contributor

Had the same problem.
In UI Scripts clone "jquery_columnview" script and rename it.

In cloned UI Script search for "#body_kb_categories_dialog" and change it to "#body_<name of your dialog>" - in your case change it to #body_kb_categories_dialog_2

In UI Page HTML change include part to the changed name of the UI Script.

 

 

Thanks so much for your help Sam, this was the missing piece!

Wow was banging my head. Thanks for the solution!

 

Jim

Rafał Rataj
Tera Contributor

I was doing exactly the same but the dialog was not initiated at all,

I see no errors in the console or anything 

event the decorations itself seems not to be triggered or its breaking on the beginning 

as it should  state on the loop icon "Lookup using picker"  but it says "Lookup using list" ans opens a standard reference list dialog .. 

what could be the reason for that ?