Hide an Element when another element is clicked ServiceNow UI Framework Custom Component

Mark Endsley
Tera Guru

Hi Everyone,

I've been working with creating my own custom components for Agent Workspace using the UI Framework. 

I've gotten my application to do almost everything I want, I have a <now-card> with a <now-button> inside, like this:

<div><now-card  id="myCard" style={{width:"250px"}}><now-card-header tagline={{"label":"Incident","icon":"tree-view-short-outline"}} heading={{label: number}} /><br/>{short_description}<br/><br/><now-button on-click={buttonClicked}>Assign to Me</now-button></now-card>
 
I want it to be when the button is clicked it will fire the buttonClicked function, and that will hide the entire card.
 
The issue is when I try to use getElementbyID("myCard") it returns NULL. I figure this may be because of the Shadow Dom.
 
I've also tried importing jQuery and I can hide the entire body, but I can't target the elements.
 
I'm at a loss on how to accomplish this. Any ideas?
1 ACCEPTED SOLUTION

DrewW
Mega Sage
Mega Sage

You need to read up on the shadowdom.  It basically keeps everything separate so that jquery and the like will never be able to return anything.  You can access it like this though, snagged it from something I did in Home Assistant.

const home_assistant_main = document
   .querySelector("body > home-assistant").shadowRoot
   .querySelector("home-assistant-main");

var header = home_assistant_main.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";

 

Ultimately what you need to do is when the button click event is triggered have the container that the card is in set a style for the DIV its in to hide it or set a var and use an IF statement to have it excluded from the HTML.  Something like the below

//If attachments length is greater than 0 then render one thing and if it is 0 render something else.
attachments.length > 0 ? attachments.map(attachment => (
	<li className="sn-card">
		<div className="sn-attachments"
			data={attachment.sys_id}
			on-click={attachmentsClicked}
			>
			<div className="sn-attachments-content -static has-media">
				<now-icon icon="document-fill" size="xl"></now-icon>
			</div>
			<div className="sn-attachments-content">
				<span className="sn-attachments-content-title">
					<span>{attachment.file_name}</span>
				</span>
				<span>{(attachment.size_bytes / 1024 / 1024).toFixed(2)} MB</span>
			</div>
		</div>
	</li>
)) : 
	<li className="sn-card">
		<div className="sn-attachments">
			<div className="sn-attachments-content">
				<span className="sn-attachments-content-title">
					<span>No PDF's found.</span>
				</span>
			</div>
		</div>
	</li>

View solution in original post

4 REPLIES 4

DrewW
Mega Sage
Mega Sage

You need to read up on the shadowdom.  It basically keeps everything separate so that jquery and the like will never be able to return anything.  You can access it like this though, snagged it from something I did in Home Assistant.

const home_assistant_main = document
   .querySelector("body > home-assistant").shadowRoot
   .querySelector("home-assistant-main");

var header = home_assistant_main.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";

 

Ultimately what you need to do is when the button click event is triggered have the container that the card is in set a style for the DIV its in to hide it or set a var and use an IF statement to have it excluded from the HTML.  Something like the below

//If attachments length is greater than 0 then render one thing and if it is 0 render something else.
attachments.length > 0 ? attachments.map(attachment => (
	<li className="sn-card">
		<div className="sn-attachments"
			data={attachment.sys_id}
			on-click={attachmentsClicked}
			>
			<div className="sn-attachments-content -static has-media">
				<now-icon icon="document-fill" size="xl"></now-icon>
			</div>
			<div className="sn-attachments-content">
				<span className="sn-attachments-content-title">
					<span>{attachment.file_name}</span>
				</span>
				<span>{(attachment.size_bytes / 1024 / 1024).toFixed(2)} MB</span>
			</div>
		</div>
	</li>
)) : 
	<li className="sn-card">
		<div className="sn-attachments">
			<div className="sn-attachments-content">
				<span className="sn-attachments-content-title">
					<span>No PDF's found.</span>
				</span>
			</div>
		</div>
	</li>

Hi Drew,

a few questions.

For the first solution, will this allow me to select these elements with jQuery? If so, that is the way I want to go for this as I want to hide the element slowly, like a fade out effect.

I'm having issues getting it to work. It would appear to me the first statement:

document
   .querySelector("body > home-assistant").shadowRoot

it seems as though home-assistant should be replaced with my app name, which I have done. However when I do this I get. 

TypeError: Cannot read property 'shadowRoot' of null

this happens when I try to console.log it.

I also get that if I attempt to return the shadowRoot of just "body".

Is there something I should be importing to make this work?

I have tried doing this both inside and outside the view declaration.

 

Is there any way to dig into the structure using the inspector? 

 

Also, I am confident the second option would work for simply hiding it, but I'd like to apply jQuery's fade effect.

Actually home-assistant is a tag so you would replace it with the tag name.  The issue with doing this is you have to crawl down the tree of tags to get to the one you want.  Which can get messy and will break if ServiceNow changes the structure of the page.

If you want it to fade then you may want to consider assigning a class to the DIV that will do this. 

So something like

//In the event script set the fadeClass var to the class name that will kickoff the fading
//HTML
<div class="{fadeClass}">
      <now-card  id="myCard" style={{width:"250px"}}><now-card-header tagline={{"label":"Incident","icon":"tree-view-short-outline"}} heading={{label: number}} />
      <br/>
      {short_description}
      <br/>
      <br/>
   <now-button on-click={buttonClicked}>Assign to Me</now-button>
   </now-card>
</div>

 

There maybe a better way to trigger it using the parms passed into the button event but I would have to mess with it again.

Thanks Drew,

I think for now I'm going to go with your original second solution and leave the fading for later.

It sounds like jQuery isn't the way to go for this type of thing anymore. I need to study up on this more before I try the more advanced stuff.