In my AngularJS application I created a ui-sortable with a list. When I try to order the list different, it is acting stange. Without a ng-model I do not have this problem, but ui-sortable requires to have a ng-model, otherwise it will print an error in the console. Why does the ng-model create this problem? Do I have created my model object in a wrong?
I reproduces the error in this jsFiddle.
To reproduce: drag Laurent to the bottom of the list, and Laurent will appear in the middle, and not on the bottom where it should be.
I hope someone can help me with this problem.
It appears to be a bug in ui-sortable, you're using an extremely oudated version of the both libraries.
check out the source code at
https://github.com/angular-ui/ui-sortable/blob/v0.0.1/src/sortable.js
and compare it with your version
https://cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.js
after some debuging you might notice there is
onStop = function(e, ui) {
// digest all prepared changes
if (ui.item.sortable.resort && !ui.item.sortable.relocate) {
// Fetch saved and current position of dropped element
var end, start;
start = ui.item.sortable.index;
end = ui.item.index();
if (start < end)
end--;
// Reorder array and apply change to scope
ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]);
}
if (ui.item.sortable.resort || ui.item.sortable.relocate) {
scope.$apply();
}
};
remove that if statement start < end then end-- and it will work. onStop should look like this after
onStop = function(e, ui) {
// digest all prepared changes
if (ui.item.sortable.resort && !ui.item.sortable.relocate) {
// Fetch saved and current position of dropped element
var end, start;
start = ui.item.sortable.index;
end = ui.item.index();
// Reorder array and apply change to scope
ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]);
}
if (ui.item.sortable.resort || ui.item.sortable.relocate) {
scope.$apply();
}
};
I do not recommend manual changes to library code, rather upgrade to new version of the code, there might be more features and more bug fixes.
Related
EDIT:
ng-dbclick does not work for me or it would be used here.
Original:
I have a button. I click it once, it works as expected. I click it again, it does not work. I wait maybe a minute more, and then it does work. My function associated with the button does have lots of returns and if loops which call other functions, but the thing is, it does work perfectly each time it does. Here, I have implemented a double click function (hence the counter is 1 and 2, and it does work) - the project is marked as complete when double clicked, otherwise on a single click it is paused or started depending on its status.
I know I probably don't understand the digest or eval cycles here, or may need to use $scope.apply()...I basically don't understand those concepts at all or where to use them. I do have $http get and post requests in my nested functions.
Code from the HTML file:
<button ng-click="resumeorpauseproject(project, project.id)">Click me</button>
Code from the controller file:
$scope.resumeorpausecounter = 0;
$scope.resumeorpauseproject = function(value1, value2){
$scope.resumeorpausecounter = $scope.resumeorpausecounter + 1;
$timeout(function () {
if ($scope.resumeorpausecounter == 1) {
$scope.resumeorpauseproject1(value1, value2);
return;
}
if ($scope.resumeorpausecounter == 2) {
/*$scope.resumeorpausecounter = false;*/
$scope.markprojectascomplete(value1, value2);
return;
}
}, 300);
};
http://plnkr.co/edit/FNrP5Q9ivQR55zuLTa6U?p=preview
What you are trying to do is crazy imo.
Just model it properly and use ng-dblclick. You don't actually need to apply $digest nor $apply if understand your case correctly.
If we just think in terms of functionality, what you have is a project, and it should be marked as completed when doubleclicked, and should be toggled between started and stopped on single click:
Here is the relevant js:
project.state = {
started: false,
completed: false,
toggleStarted: function toggleStarted(){
this.started = !this.started;
},
markCompleted: function markCompleted(){
this.completed = true;
}
};
And here is the relevant html:
<button
data-ng-click="project.state.toggleStarted()"
data-ng-dblclick="project.state.markCompleted()"
>
Click me
</button>
The code in this Plunker works as it should (just the first 3 links do something). But as the list started to grow I realized I needed a for loop. So within the switch function I transformed this:
if(obj == "SP") {
$scope.discipline ='Link2'
return $scope.files = spFiles;
}
else if(obj == "LSM"){
$scope.discipline = "Link3"
return $scope.files = lsmFiles;
}
else if(obj == "AR"){
$scope.discipline = "Link1"
return $scope.files = arFiles;
}
Into this:
for(i=0;i<disciplines.length;i++){
if(obj == disciplines[i].initial){
return $scope.files = disciplines[i].array;
}
}
The disciplines array is located at the top of the script.js file.
Problem is, now suddenly I am getting a dupes error when I click on one of the links. I haven't a clue what is duplicating. I read the documentation for this error and added the track by $index to the ng-repeat located on the bottom of index.html. Take a look at this Plunker to see what it does now. Certainly there is a simple explanation for this. I've never encountered this though.
The only other answer I could find on here that seems to pertain to my question is this one, but, the object that is being used by the ng-repeat isn't a JSON object I'm pretty sure:
spFiles.push({
floor:floors[c].name, initial:floors[c].initial, status:spFloorStatus[i]
});
Plus, it was working before I created the for loop causing the trouble.
I apologize if anything is unclear. Please, if you need me to clarify anything just let me know, I tried to be as thorough and as least confusing as possible.
That's because disciplines[i].array return just string, not your variable, named arFiles.
You can initialize these variables as:
var tempFiles={};
and then add:
tempFiles.arFiles={
floor:floors[c].name, initial:floors[c].initial, status:arFloorStatus[i]
};
and get :
return $scope.files = tempFiles[disciplines[i].array];
Main idea:
I want to combine bootstrap grid system with angular ng-repeat
Method:
I used filter to reformat the json data (an array with a lot of objects), like the codepen project:
http://codepen.io/maggiben/pen/sfCnq
```
filter('listToMatrix', function() {
return function listToMatrix(list, elementsPerSubArray) {
var matrix = [], i, k;
console.log("hellowrld")
for (i = 0, k = -1; i < list.length; i++) {
if (i % elementsPerSubArray === 0) {
k++;
matrix[k] = [];
}
matrix[k].push(list[i]);
}
return matrix;
};
});
And here is my jade page code:
My controller code: To get the news.
The problem is I got much more items on the page:
This error means angular doesn't manage to finish a digest cycle. The reason for it comes from your filter: every time angular applies the filter to your list, a new matrix is created, so angular will keep invoking the filter until what it returns is the same as the previous iteration (which never happens).
To fix it, you could either track the items of your matrix using ng-repeat ... track by items.someProperty, so after two consecutives calls to your filter, angular will detect that this someProperty has not changed and will finish the cycle.
Another way to fix it would be to cache the result of your filter for a given list, so the next time angular invoyes your filter with the same list, you would return a pointer to the same matrix.
For more lecture you can refer to this question (same problem): Angular filter works but causes "10 $digest iterations reached"
I dissected ng-repeat and extracted the code blocks attached, seeing that these comprise the logic that handles the repeating algorithm (which I want to understand how it works).
I have quite a few questions, but since they are all about the internals of ng-repeat I chose to ask them all here. I don't see any reason to separate them into different SO questions. I have marked inline to which line(s) of code each question refers to.
Why do they need to make sure that trackById is not the native hasOwnProperty function? (that's what that assertNotHasOwnProperty function does, part of Angular's internal API)
As far as my intuition go, this code executes on items already in the repeater, when it has to update the collection - it just picks them up and pushes them into the list for processing, right?
This code block obviously looks for duplicates in the repeater collection. But how exactly does it do that is beyond me. Please explain.
Why does Angular have to store the block object both nextBlockMap and in nextBlockOrder?
What are block.endNode and block.startNode?
I assume the answer to the above question will clarify how this algorithm work, but please explain why it has to check if the nextNode has (been) '$$NG_REMOVED'?
What happens here? Again, I assume question 6 will already provide an answer to this one. But still pointing that out.
Like I said, I dug through ng-repeat to find the code I deemed relevant to the repeating mechanism. Plus, I do understand the rest of the directive. So without further ado, here is the code (from v1.2.0):
length = nextBlockOrder.length = collectionKeys.length;
for (index = 0; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
trackById = trackByIdFn(key, value, index);
// question #1
assertNotHasOwnProperty(trackById, '`track by` id');
// question #2
if (lastBlockMap.hasOwnProperty(trackById)) {
block = lastBlockMap[trackById];
delete lastBlockMap[trackById];
nextBlockMap[trackById] = block;
nextBlockOrder[index] = block;
// question #3
} else if (nextBlockMap.hasOwnProperty(trackById)) {
// restore lastBlockMap
forEach(nextBlockOrder, function(block) {
if (block && block.startNode) lastBlockMap[block.id] = block;
});
// This is a duplicate and we need to throw an error
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
expression, trackById);
// question #4
} else {
// new never before seen block
nextBlockOrder[index] = { id: trackById };
nextBlockMap[trackById] = false;
}
}
for (index = 0, length = collectionKeys.length; index < length; index++) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
block = nextBlockOrder[index];
// question #5
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
if (block.startNode) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = block.scope;
// question #6
nextNode = previousNode;
do {
nextNode = nextNode.nextSibling;
} while(nextNode && nextNode[NG_REMOVED]);
if (block.startNode != nextNode) {
// existing item which got moved
$animate.move(getBlockElements(block), null, jqLite(previousNode));
}
previousNode = block.endNode;
} else {
// new item which we don't know about
childScope = $scope.$new();
}
// question #7
if (!block.startNode) {
linker(childScope, function(clone) {
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
$animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
block.scope = childScope;
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
block.endNode = clone[clone.length - 1];
nextBlockMap[block.id] = block;
});
}
}
lastBlockMap = nextBlockMap;
After some tinkering with the directive, I became familiar with ng-repeaters code, and managed to answer some of my questions. I highlighted in bold the things I couldn't yet figure out on my own, and would appreciate if anyone could shed some light on the bold parts:
The ID is tested for hasOwnProperty, because they use that method to check whether the ID is present in the iteration objects (lastBlockMap, nextBlockMap) (this process explained below). I couldn't find out on what scenario this can actually happen, however.
I was correct in my assumption. nextBlockMap contains all items that will be transcluded on the current model change. lastBlockMap contains everything from the previous model update. It used for finding duplicates in the collection.
OK, this one is pretty straightforward actually. In this for loop, ng-repeat fills up nextBlockMap with items from lastBlockMap. Looking at the order of ifs, it's easy to see that if the item cannot be found in lastBlockMap, but it is already present in nextBlockMap (meaning, it was already copied there from lastBlockMap, and therefore its trackById appears twice in the collection) - it's a duplicate. What the forEach does is simply run through all initialized items in nextBlockMap (blocks that have a startNode property) and push their ID back into lastBlockMap. I cannot however understand why this is necessary.
The only reason I could find for separating nextBlockOrder (all trackByIds in an array) from nextBlockMap (all block objects in a trackById hash), is this line, which working with an array makes it an easy and simple operation: if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;. It is explained in the answers to question 5 and 6:
block.startNode and block.endNode are the first and last DOM nodes in the block belonging to an item in the collected being repeated. Therefore, this line here sets previousNode to reference the last DOM node of the previous item in the repeater.
previousNode is then used as the first node, in a loop that checks how the DOM changed when items have been moved around or removed from the repeater collection - again, only in case we are not working with the first block in the array.
This is easy - it initializes the block - assigning the $scope and startNode and endNode for later reference, and saves everything in nextBlockMap. The comment created right after the cloned element, is there to guarantee we always have an endNode.
I'm working with the new face tracking SDK of Kinect (Microsoft Official), and I noticed that there's difference in detection between c++ and c#-wpf example: the first one is way faster in recognition than the second (the one I want to use, actually). In the c++ version the face tracking is almost on the fly, while in the wpf one it starts ONLY when I put my entire body (so the entire skeleton) in the FOV of Kinect.
Did anyone found out why? I noticed that the skeletonframe provided shows the property "Trackingmode = default", even though I set the kinect skeleton stream on seated.
colorImageFrame.CopyPixelDataTo(this.colorImage);
depthImageFrame.CopyPixelDataTo(this.depthImage);
skeletonFrame.CopySkeletonDataTo(this.skeletonData);
// Update the list of trackers and the trackers with the current frame information
foreach (Skeleton skeleton in this.skeletonData)
{
if (skeleton.TrackingState == SkeletonTrackingState.Tracked
|| skeleton.TrackingState == SkeletonTrackingState.PositionOnly)
{
// We want keep a record of any skeleton, tracked or untracked.
if (!this.trackedSkeletons.ContainsKey(skeleton.TrackingId))
{
this.trackedSkeletons.Add(skeleton.TrackingId, new SkeletonFaceTracker());
}
// Give each tracker the upated frame.
SkeletonFaceTracker skeletonFaceTracker;
if (this.trackedSkeletons.TryGetValue(skeleton.TrackingId,
out skeletonFaceTracker))
{
skeletonFaceTracker.OnFrameReady(this.Kinect,
colorImageFormat,
colorImage,
depthImageFormat,
depthImage,
skeleton);
skeletonFaceTracker.LastTrackedFrame = skeletonFrame.FrameNumber;
}
}
}
The code is the one provide my microsoft with the 1.5 SDK.
I had some information in other forums, specifically here (Thanks to this guy (blog)):
MSDN forum link
Basically, in the c++ example all the methods to track the face are used, both color+depth and color+depth+skeleton, while in the c# only the latter is used. So it only starts when you stand up.
I did some tests, but the other method is still not working for me, I did some modification to the code but with no luck. Here is my modification:
internal void OnFrameReady(KinectSensor kinectSensor, ColorImageFormat colorImageFormat, byte[] colorImage, DepthImageFormat depthImageFormat, short[] depthImage)
{
if (this.faceTracker == null)
{
try
{
this.faceTracker = new Microsoft.Kinect.Toolkit.FaceTracking.FaceTracker(kinectSensor);
}
catch (InvalidOperationException)
{
// During some shutdown scenarios the FaceTracker
// is unable to be instantiated. Catch that exception
// and don't track a face.
//Debug.WriteLine("AllFramesReady - creating a new FaceTracker threw an InvalidOperationException");
this.faceTracker = null;
}
}
if (this.faceTracker != null)
{
FaceTrackFrame frame = this.faceTracker.Track(
colorImageFormat,
colorImage,
depthImageFormat,
depthImage,
Microsoft.Kinect.Toolkit.FaceTracking.Rect.Empty);
//new Microsoft.Kinect.Toolkit.FaceTracking.Rect(100,100,500,400));
this.lastFaceTrackSucceeded = frame.TrackSuccessful;
if (this.lastFaceTrackSucceeded)
{
if (faceTriangles == null)
{
// only need to get this once. It doesn't change.
faceTriangles = frame.GetTriangles();
}
this.facePointsProjected = frame.GetProjected3DShape();
this.rotationVector = frame.Rotation;
this.translationVector = frame.Translation;
this.faceRect = frame.FaceRect;
this.facepoints3D = frame.Get3DShape();
}
}
}
frame.TrackSuccessful is always false. Any idea?
I finally figured it out and made a post on MSDN forums regarding what else needs to be done to get this working.
It's here.
Hope that helps!