jQuery UI Sortable with AngularJS Multiple Columns - angularjs

While using jQuery UI Sortable (multiple columns) I want to save the dropped item data which is a json object into an existing array, so I can have a live update for the view. The problem is when I log $('.selector').sortable('toArray') within 'sortupdate' it always returns 2 arrays. I want to splice the dropped item data like this $scope.items.splice(index, 1, newData). As the sortable returns 2 arrays, I cannot find the right index of the dropped item in the array. In HTML I have something like this:
<div id="ticket-{{status | lowercase | removeWhiteSpace}}" class="col-xs-12 col-sm-6 col-md-4 col-lg-3" data-ng-repeat="status in ticketStatus">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title white-text-shadow ticket-{{status | lowercase | removeWhiteSpace}}">{{status}}</h4>
</div>
<div class="panel-body overview-thumb-container" id="ticket-panel-{{$index}}">
<div data-ng-class="getBorderClass(item.deadline)" class="panel panel-default overview-thumbnail ngRepeatAnimation" id="item-{{item.id}}" data-ng-repeat="item in items | filter: searchItem | filter: {status: status}:true | orderBy:'id':true">
<div class="panel-heading">
<h6 class="panel-title">
<a data-ng-href="#/item/{{item.id}}" data-ng-click="viewItemDetail()" data-tooltip="{{item.title}}" data-tooltip-placement="bottom">{{item.title | limitTo: 30}}</a>
</h6>
</div>
<div class="panel-body">{{item.description | limitTo: 80}}</div>
<div class="panel-footer white-text-shadow">
<div class="pull-left time" data-ng-class="getTextClass(item.deadline)">{{item.deadline | limitTo: 10}}</div>
<div class="pull-right text-right link">
<ul class="list-inline">
<li><a data-ng-href="#/item" data-ng-click='deleteItem(item.id)'><i class="fa fa-trash-o"></i></a></li>
<li><a data-ng-href="#/item/{{item.id}}" data-ng-click="viewIssueDetail()">Details</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
In Controller:
app.controller('TicketCrtrl', function($scope, $http, $timeout, TicketService) {
// this service is defined
TicketService.getData().then(function(tickets) {
$scope.items = tickets;
dnd();
});
function dnd() {
var container = angular.element('.overview-thumb-container');
container.sortable({
placeholder: "ui-state-highlight",
opacity: 0.6,
cursor: 'move',
connectWith: '.overview-thumb-container'
});
var from, to,
id = -1,
index = -1,
currentData = new Object();
container.on('sortstart', function(e, ui) {
from = $(e.target).parent().find('h4').text();
var cid = $(ui.item).attr('id'); // current dragged item id
id = parseInt( cid.substr(cid.lastIndexOf('-') + 1) );
angular.forEach($scope.items, function(item, key) {
if (item.id === id) {
index = key;
angular.extend(currentData, item);
return;
}
});
});
container.on('sortstop', function(e, ui) {
to = ui.item.parent().parent().find('h4').text();
// dropped in a different column
if ( from !== to ) {
// remove the status attribute from current object
delete currentData.status;
// extend the new status to this data
angular.extend(currentData, {'status': to});
} else
return; // dropped in the same column
});
container.on('sortupdate', function(e, ui) {
console.log($(this).sortable('toArray')); // it returns 2 arrays with item ids
// update this data
$http.put(root.path + '/' + id, currentData).success(function() {
ui.item.animate({backgroundColor: '#f5fff5', borderColor: '#239169'}, 300).animate({backgroundColor: '', borderColor: '#ddd'}, 200);
$timeout(function() {
// replace the old data with the new one
$scope.items.splice(index, 1, currentData); // this doesn't work as the indexs are changed. It causes duplicates in ng-repeat
}, 500);
}).error(function(data, status) {
root.showHideWarningInfo($scope.errorConnectServer + status);
// put item back to its original position
container.sortable('cancel');
});
});
};
}
The array looks like this;
[{"description":"test 01","title":"test 01","deadline":"05/19/2014 00:00","status":"normal","id":1},{"description":"test 02","title":"test 02","deadline":"05/19/2014 00:00","status":"high","id":2},{"description":"test 03","title":"test 03","deadline":"05/20/2014 00:00","status":"low","id":3}]
Could someone help, how I can find the right index of the dropped item in the array, so the view can be updated. Thanks in advance.

The AngularUI Sortable directive's Github page, under the 'Developing Notes' header, suggests that the:
ui-sortable element should only contain one ng-repeat and not any other elements (above or below).
Otherwise the index matching of the generated DOM elements and the ng-model's items will break.
In other words: The items of ng-model must match the indexes of the generated DOM elements.
You may either 1. want to use the AngularUI Sortable directive, or 2. call 'sortupdate' on the same element that contains the ng-repeat, rather than on its container div, to see if that causes the ng-repeat $index to match the indexes of the generated DOM elements.

Related

Grabbing value from nested json array with Angular

I'm having trouble accessing the artists array within the items array here to be able to render the name field:
I'm currently able to grab other values at the same level as the artists that are simple objects. How can I loop through the array of the nested array?
Controller
$scope.search = "";
$scope.listLimit = "10";
$scope.selectedSongs = [];
$scope.addItem = function(song){
$scope.selectedSongs.push(song);
}
function fetch() {
$http.get("https://api.spotify.com/v1/search?q=" + $scope.search + "&type=track&limit=50")
.then(function(response) {
console.log(response.data.tracks.items);
$scope.isTheDataLoaded = true;
$scope.details = response.data.tracks.items;
});
}
Template
<div class="col-md-4">
<div class="playlist">
<h3>Top 10 Playlist</h3>
<ul class="songs" ng-repeat="song in selectedSongs track by $index | limitTo: listLimit">
<li><b>{{song.name}}</b></li>
<li>{{song.artists.name}}</li>
<li contenteditable='true'>Click to add note</li>
<li contenteditable='true'>Click to add url for image</li>
</ul>
<div id="result"></div>
</div>
</div>
You should do another ng-repeat to access the artists,
<div class="songs" ng-repeat="song in selectedSongs track by $index | limitTo: listLimit">
<ul ng-repeat="artist in song.artists">
<li><b>{{song.name}}</b></li>
<li>{{artist.name}}</li>
<li contenteditable='true'>Click to add note</li>
<li contenteditable='true'>Click to add url for image</li>
</ul>
</div>

how get the list of selected items in angular.js

Here I am using angular.js to show a list of people
<div class="recipient" ng-repeat="person in people">
<img src="{{person.img}}" /> person.name
<div class="email">person.email</div>
</div>
$scope.people = [{id:1}, {id:2}, {id:3}, {id:4}];
The looks is like below
What I want to do is I can select multiple items and by click a OK button, I can get a list of selected items. so If I select id 1 and id 2, then I want to get return a list of [{id:1},{id:2}]
How could I implement it in angular.js
Well I guess that if you're looping through a collection of people using a ng-repeat, you could add the ng-click directive on each item to toggle a property of you're object, let's say selected.
Then on the click on your OK button, you can filter all the people that have the selected property set to true.
Here's the code snippet of the implementation :
<div class="recipient" ng-repeat="person in people" ng-click="selectPeople(person)">
<img src="{{person.img}}" /> person.name
<div class="email">person.email</div>
</div>
<button ng-click="result()">OK</button>
function demo($scope) {
$scope.ui = {};
$scope.people = [{
name: 'Janis',
selected: false
}, {
name: 'Danyl',
selected: false
}, {
name: 'tymeJV',
selected: false
}];
$scope.selectPeople = function(people) {
people.selected = !people.selected;
};
$scope.result = function() {
$scope.ui.result = [];
angular.forEach($scope.people, function(value) {
if (value.selected) {
$scope.ui.result.push(value);
}
});
};
}
.recipient {
cursor: pointer;
}
.select {
color:green;
}
.recipient:hover {
background-color:blue;
}
<script src="https://code.angularjs.org/1.2.25/angular.js"></script>
<div ng-app ng-controller="demo">
<div class="recipient" ng-repeat="person in people" ng-click="selectPeople(person)" ng-class="{ select: person.selected }">
<div class="name">{{ person.name }}</div>
</div>
<button ng-click="result()">OK</button>
Result :
<ul>
<li ng-repeat="item in ui.result">{{ item.name }}</li>
</ul>
</div>
If you only want to show checked or unchecked you could just apply a filter, but you would need to toggle the filter value from undefined to true if you didn't wan't to get stuck not being able to show all again.
HTML:
<button ng-click="filterChecked()">Filter checked: {{ checked }}</button>
<div class="recipient" ng-repeat="person in people | filter:checked">
<input type='checkbox' ng-model="person.isChecked" />
<img ng-src="{{person.img}}" />{{ person.name }}
<div class="email">{{ person.email }}</div>
</div>
Controller:
// Apply a filter that shows either checked or all
$scope.filterChecked = function () {
// if set to true or false it will show checked or not checked
// you would need a reset filter button or something to get all again
$scope.checked = ($scope.checked) ? undefined : true;
}
If you want to get all that have been checked and submit as form data you could simply loop through the array:
Controller:
// Get a list of who is checked or not
$scope.getChecked = function () {
var peopleChkd = [];
for (var i = 0, l = $scope.people.length; i < l; i++) {
if ($scope.people[i].isChecked) {
peopleChkd.push(angular.copy($scope.people[i]));
// Remove the 'isChecked' so we don't have any DB conflicts
delete peopleChkd[i].isChecked;
}
}
// Do whatever with those checked
// while leaving the initial array alone
console.log('peopleChkd', peopleChkd);
};
Check out my fiddle here
Notice that person.isChecked is only added in the HTML.

How to detect my position in an ng-repeat loop?

I want to output a list of <li> elements using ng-repeat="obj in links", where links is an array of objects with href and text properties:
$scope.links = [
{ href: '/asdf', text: 'asdf'},
{ href: '/qwer', text: 'qwer'},
/* etc. */
{ href: '/zxcv', text: 'zxcv'}
];
But I want the ng-repeat loop to change what it does when it reaches a certain object in that array. Specifically, I want the loop to create hyperlinks for every object until obj.href==location.path() -- and after that, I just want to write out the text inside a <span>.
Currently, I'm solving this by creating both links and spans each time in the loop:
<ul>
<li ng-repeat="obj in links" ng-class="{active: location.path()==obj.href}">
<a ng-href="{{obj.href}}">{{obj.text}}</a>
<span>{{obj.text}}</span>
</li>
</ul>
plunkr
I then use CSS to hide all hyperlinks after the active class and hide all spans before it. But I don't want to just hide the links after the condition matches -- I want them to be completely removed from the DOM.
So there are two things you must do.
Find the index of the active element
Only show links up to the active index, and after that only show spans
What about this:
In your controller
$scope.lastIndex = 0;
$scope.$watch('links', function(newVal, oldVal){
for(var i=0; i< newVal.length; i++){
if (newVal[i].href == location.path()){
$scope.lastIndex = i
break;
}
}
}
In your HTML :
<ul>
<li ng-repeat="obj in links">
<a ng-if="$index <= {{lastIndex}}" ng-href="{{obj.href}}">{{obj.text}}</a>
<span ng-if="$index > {{lastIndex}}">{{obj.text}}</span>
</li>
</ul>
please see that example http://jsbin.com/cifef/1/edit
for your solution you need to replace $scope.location.href by location.path()
$scope.isLast = false;
$scope.getValue = function(obj)
{
if( obj.href==$location.path() || $scope.isLast )
{
$scope.isLast = true;
obj.isLast = true;
}
};
HTML:
<ul>
<li ng-repeat="obj in links" ng-class="{active: location.href==obj.href}" ng-init="getValue(obj)">
<a ng-href="{{obj.href}}" ng-hide="obj.isLast">{{obj.text}}</a>
<span ng-show="obj.isLast">{{obj.text}}</span>
</li>
</ul>

using part of a list in angularJS ng-repeat

I'm doing an angularJS data-binding as follows:
<div class="timeSlotWrapper">
<div class="timeSlotItem" ng-repeat="t in timeSlots" time-slot-obj="t" id ="{{t.id}}"
ng-click="timeSlotClick(cardId, $index)">{{ t.signalingTimeSlot}}</div>
</div>
the collection timeslots contain some 60 items, 30 of each belonging to one category. Lets say typeId is 0 for 30, and 1 for the other 30. I want to use ng-repeat for the first 30 only. Is it possible to do within ng-repeat or do I have to create the collection according to my need in code behind?
<div class="timeSlotWrapper">
<div class="timeSlotItem" ng-repeat="t in timeSlots | filter:{typeId:0}" time-slot-obj="t" id ="{{t.id}}"
ng-click="timeSlotClick(cardId, $index)">{{ t.signalingTimeSlot}}</div>
</div>
You can make use of angular filter. Example for same
myApp.filter('filterList', function () {
return function(id) {
if(id==1)
return id;
}
});
And in your html markup
<div class="timeSlotItem" ng-repeat="t in timeSlots | filterList:t.id" time-slot-obj="t" id ="{{t.id}}"
ng-click="timeSlotClick(cardId, $index)">{{ t.signalingTimeSlot}}</div>
UPDATE:
If 1 need not be hardcoded then a $scope object can be used in the filter:
myApp.filter('filterList', function () {
return function($scope) {
$scope.Objs.forEach(function(Obj){
if(id==$scope.Obj.id) {
return id;
}
});
}
});
and in html markup pass this object
<div class="timeSlotItem" ng-repeat="t in timeSlots | filterList:this" time-slot-obj="t" id ="{{t.id}}"
ng-click="timeSlotClick(cardId, $index)">{{ t.signalingTimeSlot}}</div>
Documentation on Angular Filters

How to perform an action when a filtered collection changes

I have 3 connected lists that need to work a bit like cascading dropdown lists, that is, selecting an item in the first list filters the second which filters the third.
To achieve this I am using angular filters like so:
<div ng-app="" ng-controller="DemoCtrl">
<h3>Categories</h3>
<ul>
<li ng-repeat="cat in model.categories"
ng-click="selectCategory(cat)"
ng-class="{ active: cat === selectedCategory }">{{ cat }}</li>
</ul>
<h3>Sub Categories</h3>
<ul>
<li
ng-repeat="cat in model.subCategories | filter: { parent:selectedCategory }"
ng-click="selectSubCategory(cat)"
ng-class="{ active: cat.name === selectedSubCategory }">{{ cat.name }}</li>
</ul>
<h3>Products</h3>
<ul>
<li ng-repeat="product in model.products | filter:{ category:selectedSubCategory }">{{ product.name }}</li>
</ul>
</div>
When the top level category changes (selectCategory) I need to ensure that the first sub category is also selected:
$scope.selectCategory = function (cat) {
$scope.selectedCategory = cat;
// how to select the first sub category?
};
Since setting $scope.selectedCategory updates the filtered sub categories, is there anyway I can be notified when the filtered collection changes so I can select the first item ($scope.selectSubCategory)?
http://jsfiddle.net/benfosterdev/dWqhV/
You could set up a watcher on $scope.selectedCategory and manually run $filter to get the first sub-category.
In the end I opted for just performing the filtering in my controller and binding to a "filtered" object on my $scope. When the primary category changed we re-filter the subcategories and select the first item:
$scope.selectCategory = function (cat) {
$scope.model.selectedCategory = cat;
var filtered = $scope.getSubCategoriesOf(cat);
$scope.model.filteredSubCategories = filtered;
$scope.selectSubCategory(filtered[0]);
}
$scope.getSubCategoriesOf = function (cat) {
return $filter('filter')($scope.model.subCategories, { parent: cat }, true);
}

Resources