4

SOLUTION FOUND

As it turns out the simplest solution ever was to use simply forgo any of the following code and do this

var posts = /*All the queries that will update your list*/
$q.all(posts).then(function(x) {
    requeryData();
});

The reason it wasn't working for me was that the add requests were missing a return statement causing them to be undefined and allowing the .then() to fire before the response was actually received.

I am going to leave all my code snippets below just in cause anyone can learn from my mistake or how to use the getchanges endpoiunt.

DO NOT USE THE SOLUTION BELOW

I have build an angular web application in SP2013 which loads data in from a list and displays it nicely in a table and then allows users to manipulate that table through the app and then submit the changes after they are finished. Once they are done with the changes we don't want to close the app and want absolute confirmation that the items have been affected so we re-query the list and build the table again... Just like starting over. The problem is if they make more than 3 or 4 changes some of the changes don't get pulled in the requery. My guess is the query happens faster than the server can actually MAKE the changes.. If you wait a second and refresh the page it queries the changes just fine.. So I suppose its a timing issue. The server returns a status 200 when it receives the POST properly but by the nature of HTTP requests, there is no further communication after that so you can't tell the app when the server is finished its queue. (as far as I know)

I've been trying to devise some way to use SP.ChangeTokens.. But I honestly can't figure out quite how they work. Here is my function so far.

//Inside a listService angular service I wrote
this.getNewToken = function(guid) {
    var ticks = ((new Date().getTime() * 10000) + 621355968000000000);
    return {'__metadata':{'type':'SP.ChangeToken'},'StringValue':"1;3;"+guid+";"+ticks+";-1"}; 
}
this.tokenize = function(list, token) {
    if(token.length==0){alert("SET TOKEN BEFORE CALLING TOKENIZE");return;}
    var payload = {}
    payload = {'query':{'__metadata':{'type':'SP.ChangeQuery'},'Add':'True','Item':'True','Update':'True',"DeleteObject": 'True','ChangeTokenStart':token}}
    var promise = authService.setFormDigest()
    .then(function(formDigest) {
        var URLString = list.URL + "_api/web/lists/GetByTitle('" + list.Name + "')/getchanges";
        var innerPromise = $http({
            method: "POST",
            url: URLString,
            headers:
            {
                "Accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose",
                "X-RequestDigest": formDigest,
            },
            data: payload
        })
        .error(function(data, status, headers, config) {
            alert("Error " + data.error.code + " - " + data.error.message.value);
        })
        .then(function(response) {
            return response.data.d.results;
        });
        return innerPromise;
    });
    return promise;
}

Usage:

//Inside of testing controller I made
var list =
{
    "Name":"EwadData",
    "URL":"/bpa/eWAD/"
}

var token = ":)";

this.setToken = function()
{
    var ticks = ((new Date().getTime() * 10000) + 621355968000000000);
    token = {'__metadata':{'type':'SP.ChangeToken'},'StringValue':"1;3;ba8dbc6e-8b4e-4f2a-b6cc-a21e913a8c68;"+ticks+";-1"};
}

this.checkToken = function()
{
    console.log(token);
}

this.getChanges = function()
{
    console.log("getting changes...");
    listService.tokenize(list, token).then(function(response){
        console.log(response);
        var test = {
            'total':response.length,
            'adds':response.filter(function(x){return x.ChangeType==1}).length,
            'editss':response.filter(function(x){return x.ChangeType==2}).length,
            'removes':response.filter(function(x){return x.ChangeType==3}).length
        }
        console.log(test);
    });
}

HTML:

<!--Inside a testing view I made-->
<button type="button" ng-click="testing.setToken()">Set Token</button>
<button type="button" ng-click="testing.checkToken()">Check Token</button>
<button type="button" ng-click="testing.getChanges()">Get Changes</button>

OUTPUT:

//From setToken() response//

//From checkToken() response//
1;3;ba8dbc6e-****-4f2a-b6cc-a21e913a8c68;636065283318500000;240880

//From testing.getChanges() response immediately//
[]

//From testing.getChanges() response after adding 1 item, removing 1 item, editing 1 item
[Object, Object]
0:
    ChangeType: 1
    ItemId: 443
    ListId: "ba8dbc6e-****-4f2a-b6cc-a21e913a8c68"
    SiteId: "5ad6a2a2-****-40b1-9a1c-0b7e1cd91e1b"
    Time: "2016-08-11T19:17:52Z"
    WebId: "03aa3980-849a-4dd9-8ca9-3a73438d0e12"
    __metadata: Object
1:
    ChangeType:2
    ItemId:99
    ListId:"ba8dbc6e-8b4e-4f2a-b6cc-a21e913a8c68"
    SiteId:"5ad6a2a2-d3b4-40b1-9a1c-0b7e1cd91e1b"
    Time:"2016-08-11T19:32:33Z"
    WebId:"03aa3980-849a-4dd9-8ca9-3a73438d0e12"
2:
    ChangeType: 3
    ItemId: 442
    ListId: "ba8dbc6e-8b4e-4f2a-b6cc-a21e913a8c68"
    SiteId: "5ad6a2a2-d3b4-40b1-9a1c-0b7e1cd91e1b"
    Time: "2016-08-11T19:18:28Z"
    WebId: "03aa3980-849a-4dd9-8ca9-3a73438d0e12"
    __metadata: Object

add(1), edit(2), delete(3)

I figure this function needs to do a few things.. first get the most recent change token when you first open the program which is what I do with the first URLString.

Next you have to store all the changes you make in the UI. which I am doing outside of this function.

Then once the user submits you have to continuously call this function and pass it the stored token keep rechecking the changes since the you opened the app and compare them to your stored changes by comparing the ChangeType field..

Once the stored changes equal the response to the /getchanges query you can THEN move on and requery the whole table..

Lets say I queue up 10 POSTs using angular $q service like so..

var posts = {}
for(i in tenThings)
{
    posts.push(/*Some POST Query here like a list update*/);
}
$q.all(posts).then(function(responses) {
    console.log(responses);
    var validator = $interval(function() {
        if(/*Do your compare here*/)
            $interval.cancel(validator);
            requeryData();
        }
    }, 100);
});

it just seems unelegant to just keep re-querying..? How you do gracefully exit out of the infinite loop if something goes wrong? What if this get changes query takes more time than the server? It just seems... Messy!

I also tried tinkering with the...

/getlistitemchangessincetoken

endpoint with no success and much messier XML repsonses...

I would really appreciate any help solving this issue. Even just a push in the right direction.

  • Using the responses from your item update requests would probably work better while still ensuring the updates were actually made on the server - is there any particular reason you want to use change tokens here? – John-M Aug 12 '16 at 14:31
  • @John-M You know what...? I think I might have made a grievous error..... Some of the POSTs were missing a return statement for the promise.. Meaning the query still happened but the .then() was firing while there were still undefined responses... Which created the illusions of some of them not updating in time.... I think I might be the biggest dunce alive. I spent almost week developing this change token strategy because of this. I just now fixed the promise returns and it works immediately.... :O – Riggy Aug 12 '16 at 17:30
2

As it turns out the simplest solution ever was to use simply forgo any of the change token code and do this instead...

var posts = /*All the queries that will update your list*/
$q.all(posts).then(function(x) {
    requeryData();
});

Thank you @John-M for making me go back and skim through the code that was generating my POSTs. The reason it wasn't working for me was that the add requests were missing a return statement causing them to be undefined and allowing the .then() to fire before the response was actually received.

I am going to leave all my code snippets just in cause anyone can learn from my mistake or how to use the getchanges endpoint.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.