0

Upon page load, I'm trying to use the JavaScript function below to look in a SharePoint list to see if there are any requests for which the current user is the manager, and if so, allow them to approve or reject these requests.

 //Logic to approve events if the current user is a manager.
function promptToApproveEvents(){   
    var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
    var hostContext = new SP.AppContextSite(currentContext, hostUrl);
    var hostweb = hostContext.get_web();
    var list = hostweb.get_lists().getByTitle(paidTimeOffListName);

    //A caml query to get records where manager is equal to current user.
    var queryXml = "<View><Query><Where><Eq><FieldRef Name='Manager' /><Value Type='Integer'><UserID /></Value></Eq></Where></Query></View>"
    var query = new SP.CamlQuery();
    query.set_viewXml(queryXml);

    var items = list.getItems(query);

    currentContext.load(items);
    currentContext.executeQueryAsync(
    function() //on success.
        {       
        for(var i = 0;i < items.get_count();i++) {
            var item = items.getItemAtIndex(i);

            //Is the item we're looking at in need of approval?
            if (item.get_item("Approved") === false)
            {
                //Yes, prompt for approval.     
                //$('#approvalModal').modal('show'); //modals don't seem to work in loops.
                confirm("here's something in need of approval.");
            }
            else
            {
                //No, do nothing.
                alert("skip it, it doesn't need approved.");
            }
        }
    }, 
    function(sender,args){
        alert("Failed to get manager's name. Make sure your Organization Contacts list is filled out! Error: " + args.get_message()); //On fail
    }); 
}

My issue is that I thought I'd be able to use a bootstrap modal to prompt the user for input for every unapproved request. For example, give them some info about the request then they can decide to click an accept or reject button to pass judgement using a prompt like the mock-up below. Once they've made a decision about this request, I'll run a bit of code to record their decision on this request, and then they should be prompted about the next request, until the for loop exits.

_________________________________________________________
|      Hey boss. What to you think of this request?     |
|            Name: xx                                   |
|            Reason: xx                                 |
|            Other info: xx                             |
|                                                       |
|         [ACCEPT]                  [REJECT]            |
|_______________________________________________________|

Unfortunately, only one bootstrap modal pops up, and it shows up after the for loop is finished--which doesn't fit the desired behavior of a prompt for each matching item found.

I'm basically not sure what would be the best method to accomplish this. It seems like alert and confirm boxes "pause" execution like I want, and behave as desired. (For example if I have 2 unapproved requests and 1 approved, the code above pops up two "confirm" boxes and an "alert," as expected.) But their styling won't fit with the rest of my application at all, and they also have a "X" button that I'd prefer not to have in my prompts (they should be forced to explicitly click accept or reject.)

Any suggestions on what approach I should take?

Ectropy
  • 1,533
  • 4
  • 20
  • 37

2 Answers2

1

Since the modal call on bootstrap is asynchronous, you'd need some control over the flow of asynchronous calls. This can be done by manually, but is usually a huge headache (at least for me).

There are a few libraries out there for that (like q, async, etc.). But I'd recommend https://github.com/caolan/async since it's one of the easier ones to understand and start using.

For your case, you'd probably use https://github.com/caolan/async#eachseriesarr-iterator-callback

Instead of the for loop, take this code snippet:

async.eachSeries(items, function( item, callback) {
            //Is the item we're looking at in need of approval?
            if (item.get_item("Approved") === false)
            {
                //Yes, prompt for approval.     
                //$('#approvalModal').modal('show'); //modals don't seem to work in loops.
                confirm("here's something in need of approval.", callback);
            }
            else
            {
                //No, do nothing.
                alert("skip it, it doesn't need approved.");
            }
});

Also you'll need to make your confirm function should call the callback function after the modal closes, so async will open the next modal for you (that's why I added the callback to the confirm call).

Khôi
  • 2,133
  • 11
  • 10
  • Ok, I've been trying to use this technique but I don't think I fully understand how it works. Here's a paste of what I'm trying. Is there something obviously wrong with what I'm doing? http://pastebin.com/xUj5dXkT (When I was debugging, it seems that the program won't even step inside of the eachSeries loop, so maybe something is wrong with how I added the async library?) – Ectropy Jun 19 '14 at 20:11
  • Looks fine to me - BUT I'd check whether `items` is not undefined in your function. It could be that `items` is not set (yet) in that function's scope... try to use your browsers debugger or `console.log(items)` in the function. – Khôi Jun 21 '14 at 18:01
0

I ended up using a recursive function instead of a for loop.

The important thing to see is that when a reply has been received from the modal (a Bootbox dialog in this instance) a callback function is run, which calls the recursive function again, with the index stepped up by one. In this way, the recursive function keeps going until it has gone through all the items in the list, waiting for a response each time, and behaves like an asynchronous for loop.

Here's what it looks like, in case it helps someone with a similar problem. There's some extra stuff in here for my prompts, but I decided to leave everything in for context.

//Recursive function that behaves like a for loop, stepping through all items and finding ones needing approval.
        function recursiveCheckRequests(i) {
            var item = items.getItemAtIndex(i);

            //Is the item we're looking at in need of approval?
            if (item.get_item("_ModerationStatus") === 2) {
                //Yes, prompt for approval. (code for prompt goes here.)

                //Fetch info about the request to be displayed in the dialog box.

                var title = item.get_item('Title');
                var location = item.get_item('Location');
                var description = item.get_item('Description');
                var requester = item.get_item('Requester').get_lookupValue();
                var start = item.get_item('EventDate').toString("h:mm tt, dddd, MMMM d, yyyy"); //formatting the datetime
                var end = item.get_item('EndDate').toString("h:mm tt, dddd, MMMM d, yyyy"); //formatting the datetime

                //Generate a nicely formatted message using this crazy-looking string of HTML
                var html="<p>We've found a pending request from a person you manage.</p>"
                html +="<p>Would you like to approve this request? </p>"
                html +="<br/>"              
                html +="<div class=\"form-horizontal\"><!-- Start of form !-->"
                html +="    <div class=\"form-group\">"
                html +="        <label class=\"col-sm-4 control-label\">Requester</label>"
                html +="        <div class=\"col-sm-7\">"
                html +="            <p style=\"text-align:left\" class=\"form-control-static\">" + requester + "</p>"
                html +="        </div>"
                html +="    </div>"
                html +="    <div class=\"form-group\">"
                html +="        <label class=\"col-sm-4 control-label\">Title</label>"
                html +="        <div class=\"col-sm-7\">"
                html +="            <p type=\"text-align:left\" class=\"form-control-static\">" + title + "</p>"
                html +="        </div>"
                html +="    </div>" 
                html +="    <div class=\"form-group\">"
                html +="        <label class=\"col-sm-4 control-label\">Location</label>"
                html +="        <div class=\"col-sm-7\">"
                html +="            <p type=\"text-align:left\" class=\"form-control-static\">" + location + "</p>"
                html +="        </div>"
                html +="    </div>"     
                html +="    <div class=\"form-group\">"
                html +="        <label class=\"col-sm-4 control-label\">Start</label>"
                html +="        <div class=\"col-sm-7\">"
                html +="            <p type=\"text-align:left\" class=\"form-control-static\">" + start + "</p>"
                html +="        </div>"
                html +="    </div>"     
                html +="    <div class=\"form-group\">"
                html +="        <label class=\"col-sm-4 control-label\">End</label>"
                html +="        <div class=\"col-sm-7\">"
                html +="            <p type=\"text-align:left\" class=\"form-control-static\">" + end + "</p>"
                html +="        </div>"
                html +="    </div>"
                html +="    <div class=\"form-group\">"
                html +="        <label class=\"col-sm-4 control-label\">Description</label>"
                html +="        <div class=\"col-sm-7\">"
                html +="            <p type=\"text-align:left\" class=\"form-control-static\">" + description + "</p>"
                html +="        </div>"
                html +="    </div>"
                html +="</div> <!-- End of Form !-->"

                bootbox.dialog({
                  message: html,                    
                  title: "A pending request needs your verdict!",
                  buttons: {
                    approve: {
                      label: "Approve",
                      className: "btn-success",
                      callback: function() {

                        //Set it to approved.   
                        item.set_item("_ModerationStatus", 0);
                        item.update();

                        currentContext.load(item);
                        currentContext.executeQueryAsync(
                            function(){
                                        bootbox.dialog({
                                            title: "Request Approved.",
                                            message: "This request has been approved.",
                                            buttons: {
                                                sucess:{
                                                    label: "Ok",
                                                    callback: callback
                                                }
                                            }       
                                        });
                                }, 
                            function(sender, args){
                                        bootbox.dialog({
                                            title: "Something went wrong!",
                                            message: "We failed to approve the request! Here's what we know about the problem: " + args.get_message() + '\n' + args.get_stackTrace(),
                                            buttons: {
                                                sucess:{
                                                    label: "Ok",
                                                    callback: callback
                                                }
                                            }       
                                        });
                            });

                            function callback(){
                            //Go on to next in list when we've received a reponse.
                            if (i + 1 < items.get_count()) {
                               recursiveCheckRequests(i + 1);
                            }
                            }
                      }
                    },
                    reject: {
                      label: "Reject",
                      className: "btn-danger",
                      callback: function() {

                        //Set it to rejected.
                        item.set_item("_ModerationStatus", 1);
                        item.update();

                        currentContext.load(item);
                        currentContext.executeQueryAsync(
                            function(){
                                        bootbox.dialog({
                                            title: "Request Rejected.",
                                            message: "This request has been rejected.",
                                            buttons: {
                                                sucess:{
                                                    label: "Ok",
                                                    callback: callback
                                                }
                                            }       
                                        });
                                }, 
                            function(sender, args){                     
                                        bootbox.dialog({
                                            title: "Something went wrong!",
                                            message: "We failed to reject the request! Here's what we know about the problem: " + args.get_message() + '\n' + args.get_stackTrace(),
                                            buttons: {
                                                sucess:{
                                                    label: "Ok",
                                                    callback: callback
                                                }
                                            }       
                                        });
                                });         


                            function callback(){
                            //Go on to next in list when we've received a reponse.
                            if (i + 1 < items.get_count()) {
                               recursiveCheckRequests(i + 1);
                            }       
                            }
                      }
                    },
                    Main: {
                      label: "Skip",
                      className: "btn-default",
                      callback: function() {
                        //Leave it as pending, which is equivalent to the line below
                        //item.set_item("_ModerationStatus", 2);

                            //Go on to next in list since we don't require a reponse.
                            if (i + 1 < items.get_count()) {
                               recursiveCheckRequests(i + 1);
                            }   
                      }
                    }
                  }
                });
            }
            else
            {
                //bootbox.alert("This request isn't pending.");

                //Go on to next in list since this one doesn't need a response.
                if (i + 1 < items.get_count()) {
                   recursiveCheckRequests(i + 1);
                }
            }


        }
        //Start the recursive function seen above.
        recursiveCheckRequests(0);
Ectropy
  • 1,533
  • 4
  • 20
  • 37