New/Edit Form in SP Modal Windows
by Steve McHargue
Summary
There are 2 challenges to opening Modal Windows with an iFrame to a SharePoint List New/Edit Form.
- Suppress the HS Framework Extension Header and Footer
- Closing the modal when the user saves or cancels the form.
Hide HS Header
The HS Framework Header will be visible when displaying a SharePoint Page in an iFrame. This is not desirable for a modal window. We can hide it easily with CSS if we know the page is being displayed in an iFrame. That will take a little JavaScript. This CSS and JS can go almost anywhere, but HS-SPO-HEADER is an appropriate place.
// if window.self and window.top are not the same, this code is running in an iframe
if (window.self !== window.top) {
document.body.classList.add('sp-in-iframe');
}
/* hide these elements when we are displaying a SP Page in an iFrame */
body.sp-in-iframe #reactClientFormHeader, /* hide the big redundant 'New Item' header (optional) */
body.sp-in-iframe .od-ListForm-breadcrumb, /* hide the little redundant 'new item' header (optional) */
body.sp-in-iframe #hs-spfx-extension-footer, /* hide the hs extension header and footer (required) */
body.sp-in-iframe #hs-spfx-extension-header {
display:none !important;
}
body.sp-in-iframe .od-ListItemFormRoot {
padding:0 1em !important; /* cosmetic improvement (optional) */
}
Close Window on Save/Cancel
Code running in the iFrame cannot directly close the modal window, since it exists in the parent context. To overcome this we can take advantage of the ?Source URL parameter that the New/Edit aspx form accepts. When the user selects save or cancel, the iframe will be redirected to the Source URL.
blank.html
Because of the difficulty in creating an .aspx page in SharePoint that will contain JavaScript, we will leverage the HS Web Server as the Source option. Create a file blank.html in the HandshakeWebService folder with the following HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Blank</title>
</head>
<body>
<script>
// Signal the parent that the form completed and redirected here,
// origin will be a url paramter passed to blank.html
try {
const params = new URLSearchParams(window.location.search);
var parentOrigin = params.get('origin');
if (parentOrigin) {
window.parent.postMessage({ type: 'sp-form-complete' }, parentOrigin);
} else {
console.error('Error retrieving *origin* from url parameters');
}
// Create and append the message div programmatically
} catch (e) {
// Swallow errors quietly
console.error('Unexpected error in blank.html',e);
}
</script>
<div>You may close this window</div>
</body>
</html>
To close the modal window (setting the display to none) we need to listen for the post message from blank.html. Here is the code to do that. This code can go in the skin that will trigger the modal action, or add it to the core JavaScript in your environment so that it is always available. Either way, you don’t need to add this listener more than once.
// Assumes your modal window has an ID of sp-modal, and the iFrame container within it has an ID of sp-modal-frame
window.addEventListener('message', (event) => {
if (event.origin === __HS.ApplicationOptions.HandshakeURL && event.data.type === 'sp-form-complete') {
const modal = document.getElementById('sp-modal');
if (!modal) {
return;
}
modal.style.display = 'none';
const formFrame = document.getElementById('sp-modal-frame');
if (!formFrame) {
return;
}
formFrame.src = '';
}
});
Putting it together
The JS/CSS code above will take care of cleaning up the SP Page in a modal and closing it when the user is done. Here is how it is put together in an “Add new Post” action in a skin.
iFrame Container
Add this DIV element to the skin, setting the appropriate list name and header; some modification will need to be made if the list is not in the same site URL as the current SP page.
<!-- Modal structure -->
<div id="sp-modal" class="sp-modal" data-sp-list-name="Firm News">
<div class="sp-modal-content">
<div class="sp-modal-header">New Item for Life At Irell</div>
<iframe id="sp-modal-frame" src=""></iframe>
</div>
</div>
Add a button or div with a click event that will execute the following function:
function OpenSPModal() {
const modal = document.getElementById('sp-modal');
const formFrame = document.getElementById('sp-modal-frame');
if (!modal || !formFrame) {
return;
}
const listName = modal.dataset.spListName;
if (!listName) {
return;
}
// Build the NewForm.aspx URL for your list. Assumes the page and list are in the current site
// The source is an HTML file on the HS Server that will message the parent window to close the window.
const source = `${__HS.ApplicationOptions.HandshakeURL}/HandshakeWebServices/blank.html`;
// Important that ?origin be part of the encodedURI as it needs to be consumed by blank.html, not newform.aspx
const origin = encodeURIComponent('?origin=' + window.location.origin);
const listFormUrl = `/Lists/${listName}/NewForm.aspx?Source=${source}${origin}`;
// Open modal
formFrame.src = listFormUrl;
modal.style.display = 'flex';
}
Finally, here is the css that supports a CSS native modal window:
.sp-modal {
/* javascirpt will change this to flex when the modal is to be displayed. */
display: none;
align-items: center;
justify-content: center;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.65);
}
/* Modal content box, child of sp-modal overlay */
.sp-modal-content {
background-color: #fff;
padding: 0;
border-radius: 6px;
width: 80%;
max-width: 1024px;
height: 85vh;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.3);
}
/* optional header, child of sp-modal-content */
.sp-modal-header {
flex-shrink: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 1em 1em;
background-color: var(--secondary);
color: var(--white);
}
/* required iFrame containg the SP List Url, child of sp-modal-contnet */
#sp-modal-frame {
width: 100%;
flex: 1;
border: none;
}
Notes and Caveats
- The modal does not have a close button in the header. This is intentional to prevent accidental closure of the window while adding data. It does mean the only way to close the modal is for the cancel/save in the form to work correctly. You may wish to add a close button with confirmation that any data entered will be discarded.
- Edit Form is not shown here, but it should be possible to pass the List Item ID to the EditForm.aspx page.
- As noted earlier additional code will be needed to support editing a list from a different subsite.
- Kendo popup window was not used, as the overhead seemed unneccessary. Certainly an option with some changes.