The Now Platform® Washington DC release is live. Watch now!

Help
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
DrewW
Mega Sage
Mega Sage

So this is a continuation of my last post on this subject.
https://community.servicenow.com/community?id=community_article&sys_id=b3192261db509c90f7fca851ca961...

So been learning new things and been relearning old lessons that I had forgotten about that came back to bite me.  One of these is that once I pushed my component to a Developer instance a few times I started to find that the browser was not showing the latest version.  After poking at it for sometime I realized that it was because ServiceNow was not letting the browser know that it did not have the latest version of the file and I needed to clear the browser cache.  That was the first knot on my forehead as I should have known better and have gotten used to not needing to do this for a while.

The second knot on my forehead came from a discrepancy in the documentation and the fact that the now-cli login command indicates it saved your the credentials once you use it.  Well unthinkingly I assumed (yes I know) that it was saving the information in the "now-cli.json" file, well it does not save it there, not sure where it saved it but it does save it because I do not have to login first to use the deploy command. So the second part was the Doc's site as of this writing shows that the proxy file should have a format of

Authorization: "Basic <username:password>"

and the developer site shows

"Authorization": "Basic <Base 64 encode username:password>"

Well I know that the two sites are not the same and did not think to check the dev site and spent a bunch of time wondering why I had an authentication error.  I am here to say the Dev site is correct, you have to make sure its a base64 encoded string.  I would have preferred it was encrypted but its not.  So a quick google and there is an easy way to do this.  Just open a browser and then open the dev tools for said browser and you can do the following using the console.

btoa("userName:password")
Result: dXNlck5hbWU6cGFzc3dvcmQ=

atob("dXNlck5hbWU6cGFzc3dvcmQ=")
Result: userName:password

So just toss the result into the Authorization line of the now-cli.json and you are off and running when it comes to getting data from the instance you specified.

After getting the proxy file straitened out getting data from an instance is supper easy.  Here is an example that works using a developer instance.

import {createCustomElement, actionTypes} from '@servicenow/ui-core';
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';
import {createHttpEffect} from '@servicenow/ui-effect-http';

const {
		COMPONENT_CONNECTED, 
		COMPONENT_ERROR_THROWN,
	} = actionTypes;

const GET_INCIDENTS_REQUESTED = 'GET_INCIDENTS_REQUESTED',
	GET_INCIDENTS_REQUEST_STARTED = 'GET_INCIDENTS_REQUEST_STARTED',
	GET_INCIDENTS_SUCCEEDED = 'GET_INCIDENTS_SUCCEEDED',
	GET_INCIDENTS_FAILED = 'GET_INCIDENTS_FAILED'
    ;

const getIncidents = createHttpEffect('/api/now/table/:table', {
    method: 'GET',
	pathParams: ['table'],
    queryParams: [
                    'sysparm_query',
                    'sysparm_display_value',
                    'sysparm_exclude_reference_link',
                    'sysparm_suppress_pagination_header',
                    'sysparm_fields',
                    'sysparm_limit',
                    'sysparm_view',
                    'sysparm_no_count'
                ],
    startActionType: GET_INCIDENTS_REQUEST_STARTED,
    successActionType: GET_INCIDENTS_SUCCEEDED,
    errorActionType: GET_INCIDENTS_FAILED
});

const view = (state, {updateState}) => {
	return (
		<div>
		</div>
	);
};

createCustomElement('x-8781-incident-viewer', {
	renderer: {type: snabbdom},
	view,
	styles,
	actionHandlers: {

		//Component actions **********************
		[COMPONENT_CONNECTED]: {
			effect({state, properties, action: {payload: {host}}, updateState, dispatch}){
				dispatch(GET_INCIDENTS_REQUESTED, {
					table: 'incident',
					sysparm_query: '^table_sys_id=',
					sysparm_fields: 'sys_id',
					sysparm_limit: 3,
					sysparm_display_value: 'true'
				});

			}
		},

        //Get Image action handlers **********************
		[GET_INCIDENTS_REQUESTED]: getIncidents,
		[GET_INCIDENTS_SUCCEEDED]: ({action}) => {
			if(action.payload.result && action.payload.result.length > 0){
				action.payload.result.forEach(element => {
					console.log(element.sys_id)
				});
			}
		},
		[GET_INCIDENTS_FAILED]: () => {

		},
	
		//Error Handeling **********************
		[COMPONENT_ERROR_THROWN]: ({action: {payload}}) => {
			console.log(payload)
		}
	},
	properties: {
		table: {
			default: null
		},
		sysId: {
			default: null
		}
	}
	
});

So the code is fairly simple but once you start getting into multiple components you need to pay attention to the action type strings you use.  As far as I can tell these are global, so when you do a dispatch if there is another component on the page that uses the same string then it will react, which is a good thing but if you are not paying attention you could have duplicate string being used and get undesired affects.  So make sure your constant's are as unique as you can make them.

Another item is there have been a number of times where I have wondered what parms do I need or are available for an action handler since they are not clearly documented.  Well I have found a relative easy way to find out.  Consider this code, it will error because dispatch is not defined.

		[COMPONENT_CONNECTED]: {
			effect({state, properties, action: {payload: {host}}, updateState}){
				dispatch(GET_INCIDENTS_REQUESTED, {
					table: 'incident',
					sysparm_query: '^table_sys_id=',
					sysparm_fields: 'sys_id',
					sysparm_limit: 3,
					sysparm_display_value: 'true'
				});

			}
		},

So if you are wondering were to get it. you can just do this

		[COMPONENT_CONNECTED]: {
			effect({...stuff}){
				console.log(stuff);
			}
		},

So what this will do is assign all of the parameters to the var stuff and once you dump it to the console you can then pick thru it and see whats there and what you need to pull out.

 

Next lets take a look at the properties that I added.  This is something that came out in some example components posted to the ServiceNowDevProgram on GitHub

https://github.com/ServiceNowDevProgram/now-experience-component-examples

This was something I was happy to see and so with this there is the question of are there more.  Well there are.  You can see them by going to an instance on Orlando then "Workspace Experience -> Contextual Side Panel" and then just pick any record and you will see a related list called "Action Model Fields".

find_real_file.png

So now we have a list of properties that we can add to our components to connect it to other things on the page.  One thing to keep in mind as far as I can tell is that they are case sensitive so if one does not look like its working check the case.

 

So whats next?  Well as I understand it bootstrap is not available and ServiceNow planes to create there own components that will take the place of the bootstrap items.  So it looks like we will see about 30+ components from them per family release but until then I need something to save my sanity.  So this is how you can install bootstrap.

npm install bootstrap

Docs
https://www.npmjs.com/package/bootstrap#quick-start

Not sure this will be a good idea but going to try it and see if it works and does not blowup.

 

Thoughts?  Questions? Comments? Please post below.

 

Comments
ashwin12
Tera Contributor

Thank you Drew. Very helpful. Finally I can do a createHttpEffect() successfully. I had my own @#$% moments tho. Proxy settings in now-cli.json were most challenging ones. But when I started looking at createHttpEffect() as a REST API call issued by REST Explorer in ServiceNow, things started making more sense to me especially the function parameters to createHttpEffect(). All is well when it ends well! Thanks for your post!

Last but not the least, thanks to that angel who posted the examples on git.

Revanth7
Kilo Contributor

Hi Drew,

Very detailed explanation and this helped me in fixing some of the errors i encountered. Moving on, i am also trying to use bootstrap but unable to do so. Please let me know if you are able to achieve this and if yes, help me with the steps.

Thanks,

Revanth

DrewW
Mega Sage
Mega Sage

This is what I did to use bootstrap in a component I was testing out.

npm install bootstrap
npm install jquery
npm install popper.js

 

In CSS file
@import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';

In js file
import 'bootstrap';

 

Revanth7
Kilo Contributor

I tried your suggestion, did not work for me. Can you please share a code sample of yours on how to use bootstrap? Another doubt i have is, how to access the DOM elements rendered inside the components? For example, i tried using a now card and tried to access the div element inside it using document.getElementById() in javascript but unable to do so. Do you have any idea of achieving this?

 

find_real_file.png

 

DrewW
Mega Sage
Mega Sage

What does your code look like?  What I posted installs the latest version of Bootstrap and I found that its a little different than the version Servicenow uses in portal.

DrewW
Mega Sage
Mega Sage

Also why are trying to do a document.getElementById?  Are you trying to setup the onClick of the button?  Get a value from a text box when its clicked?

 

Revanth7
Kilo Contributor

i just tried rendering a bootstrap primary button

<button type="button" class="btn btn-primary">Primary</button>. The buttons did not appear as per the bootstrap css
Revanth7
Kilo Contributor

I am working on to render the google maps but the google maps API needs a div tag before it returns the api payload. So, to do that i need to get an div element with an id and try to access it to render the map but could not do that. The google API call returns with an html element but adding to our component and rendering it is the issue i am facing now.

 

Google API sample code:

map = new google.maps.Map(document.getElementById('map'), {

  center: {lat: -34.397, lng: 150.644},
  zoom
: 8
});



Tommy Jensen
Mega Guru

Have a look in this sample component that use TUI calendar. Which does that same.

https://community.servicenow.com/community?id=community_article&sys_id=458c426d1b9c9010d01143f6fe4bc...

Rajat Gupta6
Tera Expert

Hi Drew,

Thanks for the wonderful article. Got to learn a lot from this. I have a doubt though, what is the below line of code doing? I could not find any doc related to it.

effect({state, properties, action: {payload: {host}}, updateState, dispatch})

 

Regards,

Rajat

DrewW
Mega Sage
Mega Sage

Its just another way to format an Lifecycle action handler.  See first code example at link below.

https://developer.servicenow.com/dev.do#!/reference/now-experience/orlando/ui-framework/now-experience-ui-framework-101/lifecycles

 

 

Rajat Gupta6
Tera Expert

Hi Drew, I really need your help as i am facing an issue. Would appreciate if you can take out some time help.

I want to create a component on Interaction form in agent workspace. The component will take the 'opened for' user on the interaction record and then it should fetch all the tasks for that particular user and show those tasks in a table (maybe in a pop-up). But as of now i am just trying to get the opened_for value so that i can use it to fetch tasks.

i created a component and i am able to query interaction table using createHttpEffect when i hardcode the sys_id as below and it gave me result when i run it locally.

	dispatch('FETCH_TASK_DATA', {
				sysparm_limit: '1',
				sysparm_query: 'sys_id = e672fd3fdb03101003522706ca9619a3'
			});
		},
		'FETCH_TASK_DATA': createHttpEffect('api/now/table/interaction', {
			method: 'GET',
			queryParams: ['sysparm_limit', 'sysparm_query'],
			successActionType: 'FETCH_TASK_DATA_SUCCEEDED'
		}),
 

but i am not able to pass sys_id of the interaction record to my component when i deployed this so that i can fetch the other details(opened_for, number etc) of interaction record by using httpEffect from my component. How can i pass sys_id of the currently opened interaction record to my component? when i deployed it is not returning anything. screenshots below.

find_real_file.png

I tried the property as suggested in this post but it is not working for me. I am not sure how to use it in my component. Can you please help me. PFB the complete code, i am a beginner in this area so there might be something i am missing/doing wrong.

 

import { createCustomElement, actionTypes } from '@servicenow/ui-core';
const { COMPONENT_CONNECTED } = actionTypes;
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';
import { createHttpEffect } from '@servicenow/ui-effect-http';


const view = (state, { updateState }) => {
	const { number = 'Loading number', opened_for = 'Loading opened for'} = state;
	const { sysId } = state.properties;
	return (
		<div>
        <h2>Interaction</h2>
	<p>{number} - {opened_for.value}</p>
    </div>
	);
};

createCustomElement('x-184699-now-experience-interaction', {
	renderer: { type: snabbdom },
	
	actionHandlers: {
		[COMPONENT_CONNECTED]: (coeffects) => {
			const { dispatch } = coeffects;

			dispatch('FETCH_TASK_DATA', {
				sysparm_limit: '1',
				sysparm_query: 'sys_id = {sysId}'
			});
		},
		'FETCH_TASK_DATA': createHttpEffect('api/now/table/interaction', {
			method: 'GET',
			queryParams: ['sysparm_limit', 'sysparm_query'],
			successActionType: 'FETCH_TASK_DATA_SUCCEEDED'
		}),
		'FETCH_TASK_DATA_SUCCEEDED': (coeffects) => {
			const { action, updateState } = coeffects;
			const { result } = action.payload;
			const { number, opened_for } = result[0];

			updateState({ number, opened_for });
		}
	},
	view,
	properties: {
        sysId: {
            default: 'e672fd3fdb03101003522706ca9619a3'
        }
    },
	styles
});

 

Regards,

Rajat

DrewW
Mega Sage
Mega Sage

sysId is not a global var of any kind so you have to get it from the state.

Here is an example of a functioning action handler that I used for my Attachment viewer component.

		[actionTypes.COMPONENT_CONNECTED]: {
			effect({properties, action: {payload: {host}}, dispatch}){

				console.log("COMPONENT_CONNECTED: " + host.attributes["component-id"].nodeValue);

				//Get the images for the passed record.
				if(properties.table && properties.sysId){
					dispatch(localActionTypes.GET_ATTACHMENTS_REQUESTED, {
						table: 'sys_attachment',
						sysparm_query: baseQuery + '^table_name=' + properties.table + '^table_sys_id=' + properties.sysId,
						sysparm_fields: 'file_name, content_type, size_bytes, sys_id',
						sysparm_display_value: 'true'
					});
				}
				
			},
			args: [],
			stopPropagation: true
		},

 

And the properties

	properties: {
		sysId: {
			default: null
		},
		table: {
			default: null
		}
	}
Rajat Gupta6
Tera Expert

Thanks Drew. i appreciate your help.

I was able to make the component read the sysId from properties and when i run it locally it is picking up the default sysId value provided. But when i deployed it on the instance forcefully using 'now-cli develop -force' (as per docs it should overwrite with the latest update in the component) the changes are not reflecting in the instance. I do not know if the instance is supplying the sys_id to my component of the current opened record or not.

How do we test the components in such cases? deploy them to instance every time when we make change?

find_real_file.png

 

find_real_file.png

DrewW
Mega Sage
Mega Sage

So I have found that you have to clear both your Browser cache and also the instance cache to get it to load you new component at times.

To flush the instance cache enter "cache.do" into the Filter navigator box.

Rajat Gupta6
Tera Expert

Thanks Drew, it is working now. You are awesome.

Rajat Gupta6
Tera Expert

Hi Drew,

Hope you are fine. I am facing more issue and need your help please.

I am using createHttpEffect and fetching the opened_for sys_id from interaction record. I am then passing it to a sub-component where i want to again use createHttpEffect and fetch the tasks from task table for that user.

issue is that when i am passing the sys_id of opened_by user, it is passing the default value if default value is present or null('properties.callerId' is null in lifecycle action handler) and not the one retrieved from database (i.e the actual sys_id). How do i wait till the data is retrieved from database and then pass it to the sub-component so that the next httpEffect call can read the sys_id and not the default value.

 

In the below code, in the console.log, properties.callerId is null. Also, i applied a log in the view function of sub-component, it first shows null and later shows the actual sys_id but in the lifecycle action handler, it is always null or default.

Hope i am able to explain the issue clearly.find_real_file.png

 [COMPONENT_BOOTSTRAPPED]: {
        effect({ properties, action: { payload: { host } }, dispatch }) {
            console.log('properties.callerId' + properties.callerId);
            const query = `sys_class_nameIN${taskTables.join(',')}`;
            const fields = columns.map((col) => {
                return col.field;
            }).join(',');

            if (properties.callerId) {
                dispatch('FETCH_TASK_DATA', {
                    // sysparm_query: query + 'sys_id =' + properties.callerId,
                    sysparm_query: query,
                    sysparm_display_value: 'all',
                    sysparm_exclude_reference_link: true,
                    sysparm_fields: fields
                });
            }
        }
    },
DrewW
Mega Sage
Mega Sage

When you call your subcomponent you have to pass the values as attributes in the HTML.  So something like this in your view where mahe2-bok-aig-section is your subcomponent.

<mahe2-bok-aig-section section={section} aig={aig.sys_id} selectedSection={selectedSection} selectedSubsection={selectedSubsection}></mahe2-bok-aig-section>
Rajat Gupta6
Tera Expert

Yes, i am passing values as attributes. But it is passing null or default values and not waiting for the server to retrieve the value and then pass it.

below is how i am doing it.

return (
		<div>
			<h2>Interaction</h2>
			<p>{number} - {user}</p>
			<x-184699-now-experience-interaction-task
				title="Task by Same Caller "
				callerId={user}
				dataColumns={displayColumns}>
			</x-184699-now-experience-interaction-task>

		</div>
	);
DrewW
Mega Sage
Mega Sage

Are you getting the user and displayColumns from the state?  In my example what I did not included was 

let {aig, sections, selectedSection, selectedSubsection} = state;
Rajat Gupta6
Tera Expert

Yes, i am getting user from state and displayColumn from another js file defaults.js.

Here is the entire code and the action handlers.

// COMMPONENT FETCHING INTERACTION DETAILS AND PASSING OPENED_FOR TO SUB COMPONENT
import { createCustomElement, actionTypes } from '@servicenow/ui-core';
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';
import '../x-184699-now-experience-interaction-task';
import { actionHandlers } from './interactionActionHandlers';
import { columns } from './defaults';


const view = (state, { updateState }) => {

	const { number = 'Loading number', user, dataRows } = state;
	const { properties } = state;

	const displayColumns = columns.filter((col) => {
		return col.field !== 'sys_id';
	});

	return (
		<div>
			<h2>Interaction</h2>
			<p>{number} - {user}</p>
			<x-184699-now-experience-interaction-task
				title="Task by Same Caller "
				callerId={user}
				dataColumns={displayColumns}>
			</x-184699-now-experience-interaction-task>

		</div>
	);
};

createCustomElement('x-184699-now-experience-interaction', {
	renderer: { type: snabbdom },

	actionHandlers: {
		...actionHandlers
	},
	view,
	properties: {
		sysId: {
			default: '12b93c83db13101003522706ca961932'
		}
	},
	styles
});

 

action handlers

 

 

 

import { actionTypes } from '@servicenow/ui-core';
const { COMPONENT_CONNECTED  , COMPONENT_BOOTSTRAPPED} = actionTypes;
import { createHttpEffect } from '@servicenow/ui-effect-http';

import { taskTables, columns } from '../x-184699-now-experience-interaction/defaults';

export const actionHandlers = {
    //[COMPONENT_CONNECTED]: (coeffects) => {
    //	const { dispatch } = coeffects;
    [COMPONENT_BOOTSTRAPPED]: {
        effect({ properties, action: { payload: { host } }, dispatch }) {
            console.log(properties.sysId);

            dispatch('FETCH_INT_DATA', {
                sysparm_limit: '1',
                sysparm_query: 'sys_id =' + properties.sysId
            });
        }
    },
    'FETCH_INT_DATA': createHttpEffect('api/now/table/interaction', {
        method: 'GET',
        queryParams: ['sysparm_limit', 'sysparm_query'],
        successActionType: 'FETCH_INT_DATA_SUCCEEDED'
    }),
    'FETCH_INT_DATA_SUCCEEDED': (coeffects) => {
        const { action, updateState } = coeffects;
        const { result } = action.payload;
        const { number, opened_for } = result[0];
        const user = opened_for.value;
        updateState({ number, user });
    }
    
}
​
DrewW
Mega Sage
Mega Sage

Then dump out the values using console.log and see what they are.

Revanth7
Kilo Contributor

Did you try using jquery in a component? if you have tried, let me know how to do it.

DrewW
Mega Sage
Mega Sage

I have not yet had a need to do so.  Its available by default in Workspace.  What is your use case for needing to use it?

If you are hoping to select something on another part of the page or in a subcomponent then you have to use querySelector, something like

<DOM_OBJECT>.shadowRoot
   .querySelector("app-drawer-layout > partial-panel-resolver > ha-panel-lovelace").shadowRoot
   .querySelector("hui-root").shadowRoot
   .querySelector("#layout > app-header > app-toolbar")
   .style.display = "none";

 

The few times I have thought I needed to use it I have found another way to go about it that was just as easy.

Version history
Last update:
‎05-04-2020 01:00 PM
Updated by: