I have 2 lists in my application and the user is supposed to drag and drop items from one list to another.
When the user drops an element from one of the lists to the other list a request has to be made to the server side code to update a field in the database (SelectedForDiscussion).
This is the code in my controller:
$scope.$watch("questionsDiscuss", function (value) {
var question = $.Enumerable.From($scope.questionsDiscuss).Where(function (item) { return !item.SelectedForDiscussion }).FirstOrDefault()
if (question != undefined) {
questionSelectionService.UpdateQuestionSelectionStatus(question.Id, true)
.then(function (output) {
var question = $.Enumerable.From($scope.questionsDiscuss)
.Where(function (item) { return item.Id == output.data.questionId })
.FirstOrDefault();
var index = $.Enumerable.From($scope.questionsDiscuss).IndexOf(question);
if (question != undefined)
if (output.data.result != "success") {
$scope.questionsDiscuss.splice(index, 1);
$scope.questionsReceived.splice(0, 0, question);
}
else {
question.SelectedForDiscussion = true;
$scope.questionsDiscuss[index] = question;
}
});
}
else {
var question = $.Enumerable.From($scope.questionsReceived).Where(function (item) { return item.SelectedForDiscussion }).FirstOrDefault();
if (question != undefined) {
questionSelectionService.UpdateQuestionSelectionStatus(question.Id, false)
.then(function (output) {
var question = $.Enumerable.From($scope.questionsReceived)
.Where(function (item) { return item.Id == output.data.questionId })
.FirstOrDefault();
var index = $.Enumerable.From($scope.questionsReceived).IndexOf(question);
if (question != undefined)
if (output.data.result != "success") {
$scope.questionsReceived.splice(index, 1);
$scope.questionsDiscuss.splice(0, 0, question);
}
else {
question.SelectedForDiscussion = false;
$scope.questionsReceived[index] = question;
}
});
}
}
}, true);
I have 4 javascript breakpoint placed at the following lines within Firebug:
2 of them at the following lines:
if (question != undefined) {
One at:
var question = $.Enumerable.From($scope.questionsDiscuss)
.Where(function (item) {
return item.Id == output.data.questionId
})
.FirstOrDefault();
And the other at:
var question = $.Enumerable.From($scope.questionsReceived)
.Where(function (item) {
return item.Id == output.data.questionId
})
.FirstOrDefault();
The following happens:
The breakpoints at:
if (question != undefined) {
are always reached.
The breakpoint at
var question = $.Enumerable.From($scope.questionsDiscuss)
.Where(function (item) {
return item.Id == output.data.questionId
})
.FirstOrDefault();
is also reached.
The other is never reached.
Both responses are OK(response code 200).
Everything should work perfectly but the then clause in the second promise is never reached.
Can anyone tell me what I am doing wrong?
The serverside appplication is an ASP.NET MVC application written in C#.
Edit 1:
I figured out why this was happening and I have a work around for it. I am stil interested in an actual solution.
The problem is angularjs throws an error then swallows it when calling $http for the second time. The error is:
digest alredy in progress
I think this is because in my directive I have this code:
dndfunc = function (scope, element, attrs) {
// contains the args for this component
var args = attrs.dndBetweenList.split(',');
// contains the args for the target
var targetArgs = $('#' + args[1]).attr('dnd-between-list').split(',');
// variables used for dnd
var toUpdate;
var target;
var startIndex = -1;
// watch the model, so we always know what element
// is at a specific position
scope.$watch(args[0], function (value) {
toUpdate = value;
}, true);
// also watch for changes in the target list
scope.$watch(targetArgs[0], function (value) {
target = value;
}, true);
// use jquery to make the element sortable (dnd). This is called
// when the element is rendered
$(element[0]).sortable({
items: 'div',
start: function (event, ui) {
// on start we define where the item is dragged from
startIndex = ($(ui.item).index());
},
stop: function (event, ui) {
var newParent = ui.item[0].parentNode.id;
// on stop we determine the new index of the
// item and store it there
var newIndex = ($(ui.item).index());
var toMove = toUpdate[startIndex];
// we need to remove him from the configured model
toUpdate.splice(startIndex, 1);
if (newParent == args[1]) {
// and add it to the linked list
target.splice(newIndex, 0, toMove);
} else {
toUpdate.splice(newIndex, 0, toMove);
}
// we move items in the array, if we want
// to trigger an update in angular use $apply()
// since we're outside angulars lifecycle
scope.$apply(targetArgs[0]);
scope.$apply(args[0]);
},
connectWith: '#' + args[1]
})
}
And there are 2 calls to apply at the end which trigger a new digest cycle I think.
Anyway I fixed it by adding this call before the calls to apply:
if (scope.updateLists != undefined)
scope.updateLists();
And moved all the code from the watch into the updateLists function.
Also because people have mentioned the service as having something to do with it I am pasting the relevant code within it:
GetQuestionsReceived: function (eid, criteria, page, rows) {
var promise = this.GetQuestionsReceivedInternal(eid,criteria, page, rows).then(function (response) {
// The return value gets picked up by the then in the controller.
return response;
});
// Return the promise to the controller
return promise;
},
GetQuestionsReceivedInternal: function (eid, criteria, page, rows) {
return $http({ method: 'GET',
url: '../QuestionManagement/GetQuestions?eventId='+eid+'&page=1&rows=5&'+serialize(criteria)
}).
success(function (data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
results = data;
}).
error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
if (window.console && console.log) {
console.log("Could not obtain questions received. Error:" + data + "Status:" + status + "Headers:" + headers + "Config:" + config);
}
});
},
GetQuestionsDiscuss: function (eid,criteria, page, rows) {
var promise = this.GetQuestionsDiscussInternal(eid,criteria, page, rows).then(function (response) {
// The return value gets picked up by the then in the controller.
return response;
});
// Return the promise to the controller
return promise;
},
GetQuestionsDiscussInternal: function (eid,criteria, page, rows) {
return $http({ method: 'GET',
url: '../QuestionManagement/GetQuestions?eventId=' + eid + '&page=1&rows=5&' + serialize(criteria)
}).
success(function (data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
response = data;
}).
error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
if (window.console && console.log) {
console.log("Could not obtain questions received. Error:" + data + "Status:" + status + "Headers:" + headers + "Config:" + config);
}
});
},
You have two very similar blocks of code, which could be generalized and placed in a function wrapper, leaving behind a very simple calling function.
If you can get everything into that form, then I think you will find it easier to debug.
Here is an attempt to do so :
function updateSelectionStatus(qA, qB, bool) {
var en = $.Enumerable.From(qA);
var question = en.Where(function (item) {
return bool ? !item.SelectedForDiscussion : item.SelectedForDiscussion;
}).FirstOrDefault();
if(question) {
questionSelectionService.UpdateQuestionSelectionStatus(question.Id, bool).then(function (output) {
if (output.data.result == "success") {
question.SelectedForDiscussion = bool;
}
else {
qA.splice(en.IndexOf(question), 1);
qB.unshift(question);
}
});
}
return question;
}
$scope.$watch("questionsDiscuss", function (value) {
if (!updateSelectionStatus($scope.questionsDiscuss, $scope.questionsReceived, true) {
updateSelectionStatus($scope.questionsReceived, $scope.questionsDiscuss, false);
}
}, true);
I may have made some false assumptions and simplified too much (eg. purging the inner $.Enumerable.From, which appears to reselect the same question as the outer), so you may well need to rework my code.
I'm advocating a principle here, rather than offering a solution.