问题
Update: The problem I'm having is doing a combination of three things:
- Adding a header to an array when the $priority (set to date created) changes. This is so I can group tasks by week and day in an ng-repeat.
- Resorting that list when a task is checked. Checked tasks should go to the bottom.
- When creating new tasks, I need to add them to the top of the list instead of the bottom.
Here is a plnkr of all the code: http://plnkr.co/edit/A8lDKbNvhcSzbWVrysVm
I'm using a priorityChanged function to add a header based on comparing the dates on a task: //controller var last = null; $scope.priorityChanged = function(priority) { var current = moment(priority).startOf('day'); var changed = last === null || !last.isSame(current); last = current; return changed; };
//view
<li ng-repeat="task in list track by task.$id">
<h3 ng-show="priorityChanged(task.$priority)">{{getDayName(task.$priority)}}</h3>
and to move a task to the bottom of the list when a task is completed I am using a .sort function when I populate the task list:
var populateTasks = function(start, end) {
$scope.start = start;
$scope.end = end;
var ref = new Firebase('https://plnkr.firebaseio.com/tasks').startAt(start).endAt(end);
var list = $firebase(ref).$asArray();
list.sort(compare);
list.$watch(function() {
list.sort(compare);
});
function compare(a, b) {
return a.completeTime - b.completeTime;
}
$scope.list = list;
};
It seems as though these approaches will not work together. Is there a way of combining them so that when the list is re-sorted the ng-repeat will run through the tasks again and add the necessary headers? Is that the ideal solution? Can the header be separate?
Update: I moved the ng-init functionality directly into the h3 to try to get that to run again but it does not display the header in that case.
Update2: The header does seem to show up if at least two of the $priority dates are unique but I still have the problem of deleting or moving the associated list item removing the connected header.
回答1:
USING A DIRECTIVE
You can create a directive to simplify things by nesting your client contents. demo
app.directive('repeatByWeek', function($parse, $window) {
return {
// must be an element called <repeat-by-week />
restrict: 'E',
// replace the element with template's contents
replace: true,
templateUrl: 'repeat.html',
// create an isolate scope so we don't interfere with page
scope: {
// an attribute collection="nameOfScopeVariable" must exist
'master': '=collection'
},
link: function(scope, el, attrs) {
// get the global moment lib
var moment = $window.moment;
scope.weeks = [];
updateList();
// whenever the source collection changes, update our nested list
scope.master.$watch(updateList);
function updateList() {
scope.weeks = sortItems(parseItems(scope.master));
}
function sortItems(sets) {
var items = [];
// get a list of weeks and sort them
var weeks = sortDescending(Object.keys(sets));
for(var i=0, wlen=weeks.length; i < wlen; i++) {
var w = weeks[i];
// get a list of days and sort them
var days = sortDescending(Object.keys(sets[w]));
var weekEntry = {
time: w,
days: []
};
items.push(weekEntry);
// now iterate the days and add entries
for(var j=0, dlen=days.length; j < dlen; j++) {
var d = days[j];
weekEntry.days.push({
time: d,
// here is the list of tasks from parseItems
items: sets[w][d]
});
}
}
console.log('sortItems', items);
return items;
}
// take the array and nest it in an object by week and then day
function parseItems(master) {
var sets = {};
angular.forEach(master, function(item) {
var week = moment(item.$priority).startOf('week').valueOf()
var day = moment(item.$priority).startOf('day').valueOf();
if( !sets.hasOwnProperty(week) ) {
sets[week] = {};
}
if( !sets[week].hasOwnProperty(day) ) {
sets[week][day] = [];
}
sets[week][day].push(item);
});
console.log('parseItems', sets);
return sets;
}
function sortDescending(list) {
return list.sort().reverse();
}
}
}
});
The repeat.html template:
<ul>
<!--
it would actually be more elegant to put this content directly in index.html
so that the view can render it, rather than needing a new directive for
each variant on this layout; transclude should take care of this but I
left it out for simplicity (let's slay one dragon at a time)
-->
<li ng-repeat="week in weeks">
<h3>{{week.time | date:"MMMM dd'th'" }}</h3>
<ul>
<li ng-repeat="day in week.days">
<h4>{{day.time | date:"MMMM dd'th'" }}</h4>
<ul>
<li ng-repeat="task in day.items">
<input type="checkbox" ng-model="task.complete" ng-change="isCompleteTask(task)">
<input ng-model="task.title" ng-change="updateTask(task)">
<span ng-click="deleteTask(task)">x</span>
</li>
</ul>
</li>
</ul>
</li>
</ul>
OTHER IDEAS
Most likely, you just need to move your changed out of ng-init. I don't think that is re-run when elements move/resort.
<li ng-repeat="task in list">
<h3 ng-show="priorityChanged(task.$priority)">{{getDayName(task.$priority)}}</h3>
<!-- ... -->
</li>
Since your list may resort several times, you can probably also get a pretty significant speed boost by using track by
<li ng-repeat="task in list track by task.$id">
If that doesn't resolve the problem, it might be time to think about writing your own directive (these are more fun than they sound) and possibly to consider setting aside AngularFire and going right to the source.
You really want a more deeply nested data structure here you can iterate at multiple levels, and you may need to structure that yourself either on the client or the server, now that you have a sense of how you want them organized (essentially a group by week functionality).
回答2:
you could use "unshift" javascript function
var fruits = ["1", "2", "3", "4"];
fruits.unshift("5","6");
Result
[ '5', '6', '1', '2', '3', '4' ]
来源:https://stackoverflow.com/questions/26047958/is-it-possible-to-sortcompare-and-reverse-an-array-in-angularfire