Send an email with a workable link from NewForm.aspx when there is not yet an ID available

In my last post, How do I send an email in JavaScript? I described how to send an email in JavaScript, but what was left unanswered was how do we send an email with a workable link from NewForm.aspx when there is not yet an ID available?

What I have done is create a text field in our Routing and Staffing list called OpenGuid.  Hide the field using JavaScript – NOT by setting the field to Hidden in the content type because we want it to be available in the form, just not visible to the user.

During the PreSaveAction event (after we make sure that this is the NewForm so that it never changes on us later) we populate OpenGuid using the createGuid function is described in How do I show a user with presence with code?.  That OpenGuid value will be inserted into the link’s query string as the ?open= parameter.


"use strict";
function PreSaveAction() {
    if (window.location.href.toLowerCase().indexOf("newform.aspx") > -1) {
        // Make sure that the OpenGuid is assigned so that the initial email can go out
        $("[title='OpenGuid']").val(createGuid());
        
        MyORG.Link = "https://intelshare.intelink.gov/sites/yourcollection/apps/rs/Pages/RoutingStaffingHome.aspx?open=" + $("[title='OpenGuid']").val();
        MyORG.Body = "*****View in HTML*****<br /><br /><br />" + 
                    "A new Routing and Staffing item, \"" + $("[title='Subject Required Field']").val() + "\" is available for review and approval. " +
                    "<br /><br /><a href='" + MyORG.Link + "'>Click here to go to the Routing item</a>";
        if (!sendNotification(MyORG.From, MyORG.To, MyORG.CC, MyORG.BCC, MyORG.Body, MyORG.Subject, true)) {
            alert("Error Sending Mail!  Please notify the Requirement Owner(s) that this item is waiting for them.");
        }
    }
}

Now create a file to inject the code depicted below into your list view or in my case, a page.

The document.ready function looks for an ?open= parameter and if it finds one, it then calls the OpenRoutingItem function which does a quick, targeted REST call to find the ID of the item of interest.

OpenRoutingItem in turn calls the OpenItem function and passes the ID so that the item of interest can be opened in a modal dialog. Take note that there is a commented line in OpenItem that was my first attempt and for the callback I passed SP.UI.ModalDialog.RefreshPage(dialogResult) which I learned the hard way causes the popup to reopen every time it’s closed! As you can see, my second attempt directly sets window.location.href to prevent the undesirable loop.


"use strict";
$(document).ready(function() {
    var openGuid = QueryParameterByName("open");
    if (openGuid != null && openGuid != "")
        OpenRoutingItem(openGuid);
});

function OpenRoutingItem(openGuid) {
    $.ajax({
        url: "/sites/yourcollection/apps/rs/_api/web/lists/getByTitle('Routing_Staffing')/items/" +
             "?$filter=OpenGuid eq '" + openGuid + "'" +
             "&$select=ID",
        type: "GET",
        async: false,
        contentType: "application/json;odata=verbose",
        headers: {"accept": "application/json;odata=verbose"},
        success: function (data) {
            //$("#restResponse").append(JSON.stringify(data));
            try {
                $.each(data.d.results, function (i, item){
                    openItem(item.ID);
                });
            }
            catch(err) {console.log("loadItemArrays Inner: " + JSON.stringify(err))}
        },
        error: function (err) {console.log("loadItemArrays Outer: " + JSON.stringify(err))}
    });
}

function openItem(id) {
    var addressTarget = "/sites/yourcollection/apps/rs/lists/Routing_Staffing/DispForm.aspx?ID=" + id;
    var addressCallback = "https://intelshare.intelink.gov/sites/yourcollection/apps/rs/Pages/RoutingStaffingHome.aspx";
    //OpenPopUpPage(address, function(dialogResult){SP.UI.ModalDialog.RefreshPage(dialogResult)}, 1360, 1300);
    OpenPopUpPage(address, function(dialogResult){window.location.href = addressCallback}, 1360, 1300);
}

Finally, a word of caution: The PreSaveAction event fires BEFORE SharePoint’s field validations. This means that if you have a required field and the user left it empty, PreSaveAction will fire, the email will go out, field validations will fire, the save is refused, the user fixes his oversight, saves again and then another email goes out. There are two ways around this: 1) Set a global variable so that if PreSaveAction gets fired more than once, the email will check for the flag before going out again, or 2) do your own field validations in PreSaveAction before the email is ever sent.

Advertisements

How do I send an email in JavaScript?

Did you know that you can send an email in JavaScript and not rely on a workflow? It’s not only possible, but through the use of the PreSaveAction() function, I have all but eliminated the use of clunky and unreliable workflows.

But what about not having an id yet when saving a newform.aspx you ask? That’s for the next post. For now, here’s the actual code that resides in my JSUtils.js file.

The code comments pretty well explain everything and if you want your code to take action in case the email fails, pass responseRequired as true . All global variables are added to the namespace carved out to prevent collisions.


"use strict";
var MyORG = window.MyORG || {};
MyORG.Response = false;
MyORG.From = "do-not-reply@ugov.gov";
MyORG.To = [];
MyORG.CC = [];
MyORG.BCC = [];

function PreSaveAction() {
    if (!sendNotification(MyORG.From, MyORG.To, MyORG.CC, MyORG.BCC, MyORG.Body, MyORG.Subject, true)) {
        alert("Error Sending Mail!  Please notify the Requirement Owner(s) that this item is waiting for them.");
    }
}

///////////////////////////////////////////////////////////////////////////////////
// All addresses must be present in the User Information List.                   //
// From, To, Body and Subject are manadatory fields.                             //
// To must be an array even if only one value is included.                       //
// CC and BCC must be arrays even if empty.                                      //
// The code below attempts to correct deficiencies, but the onus is on the user. //
//                                                                               //
// If a response is required, pass responseRequired as true.  This introduces a  //
// one second delay to capture a response, so use it sparingly.  The function    //
// does set console.log to true or false for troubleshooting purposes regardless //
// of responseRequired setting.                                                  //
///////////////////////////////////////////////////////////////////////////////////
function sendNotification(from, to, cc, bcc, body, subject, responseRequired) {
    if (typeof(from) == "undefined" || from == null || from == "" ||
        typeof(to) == "undefined" || to == null || to == "" ||
        typeof(body) == "undefined" || body == null || body == "" ||
        typeof(subject) == "undefined" || subject == null || subject == "")
    {
        return false;
    }
    if (typeof(cc) == "undefined" || cc == null || cc == "") {cc = []}
    if (typeof(bcc) == "undefined" || bcc == null || bcc == "") {bcc = []}
    if (typeof(to) == "string") {to = [to]}
    if (typeof(cc) == "string") {cc = [cc]}
    if (typeof(bcc) == "string") {bcc = [bcc]}
    
    if (responseRequired) {
        // Like all async functions in SP.js, SP.SOD.executeFunc will not return a value,
        // so we will use a global variable and setTimeout to get around the problem.
        // It's butt-ugly, but it works...
        MyORG.Response = false;
        setTimeout(SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sendNotificationEmail(from, to, cc, bcc, body, subject)), 1000);
        return MyORG.Response;
    } else {
        SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sendNotificationEmail(from, to, cc, bcc, body, subject));
    }
}
function sendNotificationEmail(from, to, cc, bcc, body, subject) {
    var siteurl = _spPageContextInfo.siteServerRelativeUrl;
    $.ajax({
        contentType: 'application/json',
        url: "/sites/yoursitecollection/_api/SP.Utilities.Utility.SendEmail",
        type: "POST",
        async: false,
        data: JSON.stringify({
            'properties': {
                '__metadata': {
                    'type': 'SP.Utilities.EmailProperties'
                },
                'From': from,
                'To': {
                    'results': to
                },
                'CC': {
                    'results': cc
                },
                'BCC': {
                    'results': bcc
                },
                'Body': body,
                'Subject': subject,
                "AdditionalHeaders":
                {"__metadata":
                    {"type":"Collection(SP.KeyValue)"},
                    "results":
                    [ 
                        {
                            "__metadata": {"type": 'SP.KeyValue'},
                            "Key": "content-type",
                            "Value": "text/html",
                            "ValueType": "Edm.String"
                        }
                    ]
                }
            }
        }),
        headers: {
            "Accept": "application/json;odata=verbose",
            "content-type": "application/json;odata=verbose",
            "X-RequestDigest": $("#__REQUESTDIGEST").val()
        },
        success: function(data) {
            MyORG.Response = true;
            console.log('Email Sent Successfully');
        },
        error: function(err) {
            console.log('Error in sending Email: ' + JSON.stringify(err));
        }
    });
}