0

I am still struggling with getting a chain of deferred promises working on a Visual Web Part. The end goal is to change the colour of some DIV tags to colours referenced in a SPList.

I originally had this code working with a single request to lookup data from a list, but I am now looking for a chain of requests. The first is finding name of the Sub Site from the window.location.href property. Although this code is retrieving the sub site name thinking about this as if type this might be my first mistake and perhaps I should be getting this from the SPWeb object instead. The second then uses this value in a where clause to retrieve the correct data from the second call to a different list.

So the code if have at the moment works, but is intermittent. I think I'm not calling .resolve() in the correct place, and it is just luck as to if the async code has completed before the calling thread uses the expected result. It is therefore in the lap of the processor gods and probably works 50:50. This at least proves my jQuery code is producing the desired result.

I have two functions doing almost exactly the same thing to two different DIV elements. This is what I have at the moment.

function alterMenuColour(id) {
getMenuItemfromURLValue(window.location.href).done(function (urlSelection) {

    var colkey = 0; //this is the key for the list collection array.  This needs to be uneque for each different call to retrieveListItems.

    var promise = retrieveListItems('/sites/OMGIntranet/OMGCentral/', 'MenuItemList', '<View><Query><Where><Eq><FieldRef Name=\'MenuItem\'/><Value Type=\'Text\'>' + urlSelection + '</Value></Eq></Where></Query><RowLimit>1</RowLimit></View>', 'Id,MenuColour,BarColour', colkey);

    var collMenuListItem = collListItem[colkey];

    promise.done(
        function (collMenuListItem) {

            var listItemEnumerator = collMenuListItem.getEnumerator();

            var oListItem;

            while (listItemEnumerator.moveNext()) {
                oListItem = listItemEnumerator.get_current();
            }

            var menus = getChildElementsByType(document, id, 'div');

            jQuery(menus).children("div").each(function () {
                jQuery(this).css("background", oListItem.get_item('MenuColour'));
            });

        },
        function (sender, args) {
            alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
        }
    );
});
}

function alterBarColour() {
getMenuItemfromURLValue(window.location.href).done(function (urlSelection) {

    var colkey = 1; //this is the key for the list collection array.  This needs to be uneque for each different call to retrieveListItems.

    var barpromise = retrieveListItems('/sites/OMGIntranet/OMGCentral/', 'MenuItemList', '<View><Query><Where><Eq><FieldRef Name=\'MenuItem\'/><Value Type=\'Text\'>' + urlSelection + '</Value></Eq></Where></Query><RowLimit>1</RowLimit></View>', 'Id,MenuColour,BarColour', colkey);

    var collBarListItem = collListItem[colkey];

    barpromise.done(
        function (collBarListItem) {

            var listItemEnumerator = collBarListItem.getEnumerator();

            var oListItem;

            while (listItemEnumerator.moveNext()) {
                oListItem = listItemEnumerator.get_current();
            }

            var bar = document.getElementsByClassName('SectionMenuBar');

            jQuery(bar).each(function () {
                jQuery(this).css("background", oListItem.get_item('BarColour'));
            });

        },
        function (sender, args) {
            alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
        }
    );
});
}

I have created a function getMenuItemfromURLValue(url) which is the method if described earlier to get the Sub Site name.

function getMenuItemfromURLValue(url) {

var colkey = 2; //this is the key for the list collection array.  This needs to be uneque for each different call to retrieveListItems.

var promise = retrieveListItems('/sites/OMGIntranet/OMGCentral/', 'SectionMenuAssignmentList', '<View><Query><OrderBy><FieldRef Name=\'ID\'/></OrderBy></Query></View>', 'MenuItemLookup', colkey);

var collSelectionMenuAssignemntListItem = collListItem[colkey];

return promise.then(
    function (collSelectionMenuAssignemntListItem) {

        var listItemEnumerator = collSelectionMenuAssignemntListItem.getEnumerator();

        var oListItem;

        while (listItemEnumerator.moveNext()) {
            oListItem = listItemEnumerator.get_current();
            if (isStringMatch(decodeURI(url), oListItem.get_item('MenuItemLookup').$2d_1)) {
                return oListItem.get_item('MenuItemLookup').$2d_1;
            }
        }
    },
    function (sender, args) {
        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
    }
);
}

I am trying to write reusable code but it strikes me that it makes it harder to comprehend when using the deferred methods like .then()

I have a function to get the list data from SharePoint SPList which again on the same theme is reusable:

//http://www.shillier.com/archive/2013/03/04/using-promises-with-the-javascript-client-object-model-in-sharepoint-2013.aspx
function retrieveListItems(siteUrl, list, calm, include, collkey) {
var deferred = $.Deferred();

var clientContext
if (siteUrl == null) {
    clientContext = sharePointCurrentClientContext();
} else {
    clientContext = new SP.ClientContext(siteUrl);
}

var oList = clientContext.get_web().get_lists().getByTitle(list);

var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml(calm);

if (typeof this.collListItem === 'undefined') {
    this.collListItem = [];
}

this.collListItem.add(collkey, oList.getItems(camlQuery));

if (include == null) {
    clientContext.load(collListItem[collkey]);
} else {
    clientContext.load(collListItem[collkey], 'Include(' + include + ')');
}

clientContext.executeQueryAsync(Function.createDelegate(this, function () { deferred.resolve(collListItem[collkey]) }), Function.createDelegate(this, function (sender, args) { deferred.reject(sender, args); }));

return deferred.promise();
}

Originally when I wrote this function the only way if could figure out how to return data successfully was in a global list collection object called collListItem. This is horrible I hate this and have been trying to remove it but so far unsuccessfully. It does work though, and having read and read and read, some people suggest that objects cannot be returned via deferred methods and that they have to be chained to pass on data. This again brings me back to being able to have reusable code.

Matthew R
  • 1,038
  • 11
  • 15
  • 1
    Second parameters of those `.done()` methods appear to be error handlers but that's not how `.done()` works. For sure you can specify additional handlers in that way but they will all be success handlers, not error handlers - and all will accept exactly the same parameter list - the values/objects with which the promise was resolved. – Beetroot-Beetroot Jan 13 '14 at 12:45
  • The statements `var collMenuListItem = collListItem[colkey];`, `var collBarListItem = collListItem[colkey];` and `var collSelectionMenuAssignemntListItem = collListItem[colkey];` are redundant. Whereas `collMenuListItem`, `collBarListItem` and `collSelectionMenuAssignemntListItem` are used later in thir respective outer functions, all are formal variables of an inner function, therefore not the same vars as those declared earlier. – Beetroot-Beetroot Jan 13 '14 at 13:08
  • Thanks Beetroot-Beetroot. Yes your right about the .done(), I was trying swapping them around, as you can see I have accidently got one .done() and one .then(). I still can't work out the difference having read and read again the definitions. I also read they are interchangeable which is wrong because like you said the official documentation gives them different arguments. – Matthew R Jan 13 '14 at 14:53
  • Yes I was struggling with concept of the inner function and passing variables around. My javascript understanding is still pretty basic. – Matthew R Jan 13 '14 at 14:53

1 Answers1

0

I have got it working 100% of the time now, which is the goal, but I'm still not happy with it it doesn't seem to be elegant code and I'm still strugling with the concept.

I have changed,

function alterMenuColour(id) {
-    getMenuItemfromURLValue(window.location.href).done(function (urlSelection) {
+    getMenuItemfromURLValue(window.location.href, 3).done(function (urlSelection) {

and

function alterBarColour() {
-    getMenuItemfromURLValue(window.location.href).done(function (urlSelection) {
+    getMenuItemfromURLValue(window.location.href, 4).done(function (urlSelection) {

and

-function getMenuItemfromURLValue(url) {
-
-    var colkey = 2; //this is the key for the list collection array.  This needs to be uneque     for each different call to retrieveListItems.
+function getMenuItemfromURLValue(url, colkey) {
Matthew R
  • 1,038
  • 11
  • 15
  • 1
    Matthew, looking at the code I think the best reusability available is to generalise the fetching of the enumerator object - ie write a general function `fetchEnumerator(colkey){...}`, which returns a promise of an emumerator. Exactly how (a) `fetchEnumerator` is called and (b) how the returned promise is handled, are specific to the three functions `alterMenuColour`, `alterBarColour` and `getMenuItemfromURLValue`. Further generalisation would be self-defeatingly complex. – Beetroot-Beetroot Jan 14 '14 at 06:27
  • I learnt something else today, I need to change $2d_1 with get_lookupValue() as the dynamic property name changed to $2e_1 without warning on the production system. – Matthew R Aug 21 '14 at 15:24