Skip to content

Commit 786103e

Browse files
authored
Bookmarklet to open a modal on sn form pages to call g_form setters without scripting (#1717)
* Create gFormModal.js * Create gFormModal.min.js * Create README.md * Add files via upload * Delete Specialized Areas/Browser Bookmarklets/Open g_form modal/gFormModal.min.js * Update gFormModal.js * Update and rename gFormModal.js to Open modal to use g_form.js * Update README.md
1 parent 6262b6c commit 786103e

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
javascript:!function(){if(document.querySelector("#gform-modal-overlay"))return;const e=window.gsft_main?.g_form||this.g_form||"undefined"!=typeof querySelectorShadowDom&&querySelectorShadowDom.querySelectorAllDeep("sn-form-data-connected")[0]?.nowRecordFormBlob?.gForm||angular.element("sp-variable-layout").scope().getGlideForm?.()||null;if(!e)return;const t=e.elements?e.elements:(e=>e.getFieldNames().map((e=>({fieldName:e}))))(e);((e,t)=>{const n=document.createElement("div");n.id="gform-modal-overlay",n.style.position="fixed",n.style.top=0,n.style.left=0,n.style.width="100vw",n.style.height="100vh",n.style.backgroundColor="rgba(0, 0, 0, 0.6)",n.style.zIndex=9999,n.style.display="flex",n.style.justifyContent="center",n.style.alignItems="center";const d=document.createElement("div");d.id="modal-container",d.style.background="#fff",d.style.padding="20px",d.style.borderRadius="8px",d.style.boxShadow="0 0 10px rgba(0,0,0,0.3)",d.style.maxWidth="1000px",d.style.textAlign="center",d.style.maxHeight="70vh";const o=document.createElement("div");o.style.overflowY="auto",o.style.overflowX="hidden",o.style.maxHeight="60vh",d.appendChild(o);let l="";t.forEach((t=>{const n=t.fieldName,d=e.isReadOnly(n),o=e.isMandatory(n),a=e.isVisible(n);l+=`\n <tr>\n <td>${n}</td>\n <td><input type="checkbox" ${o?"disabled":""} id="disabled-${n}" ${d?"":"checked"}></td>\n <td><input type="checkbox" id="mandatory-${n}" ${o?"checked":""}></td>\n <td><input type="checkbox" ${o?"disabled":""} id="visible-${n}" ${a?"checked":""}></td>\n <td><input type="text" id="value-${n}" value="${e.getValue(n)}"></td>\n <td><button id="change-value-${n}">Set</button></td> \n </tr>\n `})),o.innerHTML=`\n <h2>g_form modal</h2>\n <table>\n <thead>\n <tr>\n <th>Field</th>\n <th>ReadOnly</th>\n <th>Mandatory</th>\n <th>Display</th>\n <th>Value</th>\n <th></th>\n </tr>\n </thead>\n <tbody>${l}</tbody>\n </table>\n `;const a=[];n.addEventListener("click",(e=>{d.contains(e.target)||(a.forEach((e=>e())),document.body.removeChild(n))})),((e,t)=>{let n=!1,d=0,o=0;const l=document.createElement("div");l.style.cursor="move",l.style.height="20px",l.style.marginBottom="10px",l.style.background="rgba(100, 100, 100, 0.3)",l.style.borderRadius="8px",e.prepend(l);const a=t=>{n&&(e.style.left=t.clientX-d+"px",e.style.top=t.clientY-o+"px",e.style.position="absolute")},s=()=>{n=!1,document.body.style.userSelect=""};l.addEventListener("mousedown",(t=>{n=!0,d=t.clientX-e.offsetLeft,o=t.clientY-e.offsetTop,document.body.style.userSelect="none"})),document.addEventListener("mousemove",a),document.addEventListener("mouseup",s),t.push((()=>{document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",s)}))})(d,a),n.appendChild(d),document.body.appendChild(n),t.forEach((t=>{const n=t.fieldName,d=o.querySelector(`#disabled-${n}`),l=o.querySelector(`#mandatory-${n}`),a=o.querySelector(`#visible-${n}`),s=o.querySelector(`#value-${n}`),c=o.querySelector(`#change-value-${n}`);d&&d.addEventListener("change",(()=>{e?.setDisabled(n,d.checked)})),l&&l.addEventListener("change",(()=>{const t=l.checked,o=!e?.getValue(n),s=a.checked;e?.setMandatory(n,t),t&&(!o&&s?a.checked=!0:o&&!s?(a.checked=!0,d.checked=!1):o&&s&&(d.checked=!1)),d&&(d.disabled=t),a&&(a.disabled=t)})),a&&a.addEventListener("change",(()=>{e?.setDisplay(n,a.checked)})),c&&s&&c.addEventListener("click",(()=>{const t=s.value;e.setValue(n,t)}))}))})(e,t)}();
2+
3+
/*
4+
* full js of minified bookmarklet below inside comment
5+
*/
6+
7+
/*
8+
(function () {
9+
const makeDraggable = (element, cleanupHandlers) => {
10+
let isDragging = false;
11+
let offsetX = 0;
12+
let offsetY = 0;
13+
14+
const header = document.createElement('div');
15+
header.style.cursor = 'move';
16+
header.style.height = '20px';
17+
header.style.marginBottom = '10px';
18+
header.style.background = "rgba(100, 100, 100, 0.3)";
19+
header.style.borderRadius = "8px";
20+
element.prepend(header);
21+
22+
const mouseMoveHandler = (e) => {
23+
if (isDragging) {
24+
element.style.left = `${e.clientX - offsetX}px`;
25+
element.style.top = `${e.clientY - offsetY}px`;
26+
element.style.position = 'absolute';
27+
}
28+
};
29+
30+
const mouseUpHandler = () => {
31+
isDragging = false;
32+
document.body.style.userSelect = '';
33+
};
34+
35+
header.addEventListener('mousedown', (e) => {
36+
isDragging = true;
37+
offsetX = e.clientX - element.offsetLeft;
38+
offsetY = e.clientY - element.offsetTop;
39+
document.body.style.userSelect = 'none';
40+
});
41+
42+
document.addEventListener('mousemove', mouseMoveHandler);
43+
document.addEventListener('mouseup', mouseUpHandler);
44+
45+
cleanupHandlers.push(() => {
46+
document.removeEventListener('mousemove', mouseMoveHandler);
47+
document.removeEventListener('mouseup', mouseUpHandler);
48+
});
49+
};
50+
51+
const createOverlay = (g_form, fieldArray) => {
52+
const overlay = document.createElement('div');
53+
overlay.id = "gform-modal-overlay";
54+
overlay.style.position = 'fixed';
55+
overlay.style.top = 0;
56+
overlay.style.left = 0;
57+
overlay.style.width = '100vw';
58+
overlay.style.height = '100vh';
59+
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
60+
overlay.style.zIndex = 9999;
61+
overlay.style.display = 'flex';
62+
overlay.style.justifyContent = 'center';
63+
overlay.style.alignItems = 'center';
64+
65+
66+
const container = document.createElement('div');
67+
container.id = "modal-container";
68+
container.style.background = '#fff';
69+
container.style.padding = '20px';
70+
container.style.borderRadius = '8px';
71+
container.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
72+
container.style.maxWidth = '1000px';
73+
container.style.textAlign = 'center';
74+
container.style.maxHeight = '70vh';
75+
76+
const modal = document.createElement('div');
77+
modal.style.overflowY = 'auto';
78+
modal.style.overflowX = 'hidden';
79+
modal.style.maxHeight = '60vh';
80+
container.appendChild(modal)
81+
82+
let listItems = '';
83+
fieldArray.forEach(element => {
84+
const fieldName = element.fieldName;
85+
const isReadOnly = g_form.isReadOnly(fieldName);
86+
const isMandatory = g_form.isMandatory(fieldName);
87+
const isVisible = g_form.isVisible(fieldName);
88+
89+
listItems += `
90+
<tr>
91+
<td>${fieldName}</td>
92+
<td><input type="checkbox" ${isMandatory ? 'disabled' : ''} id="disabled-${fieldName}" ${!isReadOnly ? 'checked' : ''}></td>
93+
<td><input type="checkbox" id="mandatory-${fieldName}" ${isMandatory ? 'checked' : ''}></td>
94+
<td><input type="checkbox" ${isMandatory ? 'disabled' : ''} id="visible-${fieldName}" ${isVisible ? 'checked' : ''}></td>
95+
<td><input type="text" id="value-${fieldName}" value="${g_form.getValue(fieldName)}"></td>
96+
<td><button id="change-value-${fieldName}">Set</button></td>
97+
</tr>
98+
`;
99+
});
100+
101+
modal.innerHTML = `
102+
<h2>g_form modal</h2>
103+
<table>
104+
<thead>
105+
<tr>
106+
<th>Field</th>
107+
<th>ReadOnly</th>
108+
<th>Mandatory</th>
109+
<th>Display</th>
110+
<th>Value</th>
111+
<th></th>
112+
</tr>
113+
</thead>
114+
<tbody>${listItems}</tbody>
115+
</table>
116+
`;
117+
118+
const cleanupHandlers = [];
119+
120+
overlay.addEventListener('click', (event) => {
121+
if (!container.contains(event.target)) {
122+
cleanupHandlers.forEach(fn => fn());
123+
document.body.removeChild(overlay);
124+
}
125+
});
126+
127+
makeDraggable(container, cleanupHandlers);
128+
overlay.appendChild(container);
129+
document.body.appendChild(overlay);
130+
131+
fieldArray.forEach(element => {
132+
const fieldName = element.fieldName;
133+
const disabledCheckbox = modal.querySelector(`#disabled-${fieldName}`);
134+
const mandatoryCheckbox = modal.querySelector(`#mandatory-${fieldName}`);
135+
const visibleCheckbox = modal.querySelector(`#visible-${fieldName}`);
136+
const valueInput = modal.querySelector(`#value-${fieldName}`);
137+
const changeButton = modal.querySelector(`#change-value-${fieldName}`);
138+
139+
if (disabledCheckbox) {
140+
disabledCheckbox.addEventListener('change', () => {
141+
g_form?.setDisabled(fieldName, disabledCheckbox.checked);
142+
});
143+
}
144+
145+
if (mandatoryCheckbox) {
146+
mandatoryCheckbox.addEventListener('change', () => {
147+
const setToMandatory = mandatoryCheckbox.checked;
148+
const isEmpty = !g_form?.getValue(fieldName);
149+
const isDisplayed = visibleCheckbox.checked;
150+
g_form?.setMandatory(fieldName, setToMandatory);
151+
152+
if (setToMandatory) {
153+
if (!isEmpty && isDisplayed) {
154+
visibleCheckbox.checked = true;
155+
} else if (isEmpty && !isDisplayed) {
156+
visibleCheckbox.checked = true;
157+
disabledCheckbox.checked = false;
158+
} else if (isEmpty && isDisplayed) {
159+
disabledCheckbox.checked = false;
160+
}
161+
}
162+
if (disabledCheckbox) disabledCheckbox.disabled = setToMandatory;
163+
if (visibleCheckbox) visibleCheckbox.disabled = setToMandatory;
164+
});
165+
}
166+
167+
if (visibleCheckbox) {
168+
visibleCheckbox.addEventListener('change', () => {
169+
g_form?.setDisplay(fieldName, visibleCheckbox.checked);
170+
});
171+
}
172+
173+
if (changeButton && valueInput) {
174+
changeButton.addEventListener('click', () => {
175+
const newValue = valueInput.value;
176+
g_form.setValue(fieldName, newValue);
177+
});
178+
}
179+
});
180+
};
181+
182+
if (document.querySelector("#gform-modal-overlay")) return;
183+
184+
const getFieldNames = (g_form) => {
185+
return g_form.getFieldNames().map(name => ({ fieldName: name }));
186+
}
187+
188+
const g_form =
189+
window.gsft_main?.g_form ||
190+
this.g_form ||
191+
(typeof querySelectorShadowDom !== "undefined" &&
192+
querySelectorShadowDom.querySelectorAllDeep('sn-form-data-connected')[0]?.nowRecordFormBlob?.gForm) ||
193+
(angular.element("sp-variable-layout").scope().getGlideForm?.()) ||
194+
null;
195+
if (!g_form) {
196+
return;
197+
}
198+
const fieldArray = g_form.elements ? g_form.elements : getFieldNames(g_form);
199+
createOverlay(g_form, fieldArray);
200+
})();
201+
*/
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# g_form Bookmarklet Modal
2+
3+
This script provides a bookmarklet that injects a draggable modal into ServiceNow forms that allows the user to interact with fields on forms. Tested on next exp classic forms with or without top navigation, workspace(needs snutils installed) and portal.
4+
---
5+
6+
## 📸 Screenshots
7+
8+
### Modal Overview
9+
![Modal Overview](image.png)
10+
11+
---
12+
13+
## 🔧 How to Use
14+
15+
1. **Copy the minified script** on the first line of the *Open modal to use g_form.js* file
16+
2. **Create a bookmark** in your browser.
17+
3. Paste the script into the bookmark's URL field.
18+
4. Navigate to a ServiceNow form and click the bookmarklet.
191 KB
Loading

0 commit comments

Comments
 (0)