Using the Angular UI Bootstrap tabset
, with just a sortable-tab
directive:
<tabset>
<tab sortable-tab ng-repeat="tab in tabs" heading="{{tab.title}}" active="tab.active" disabled="tab.disabled">
<p>{{tab.content}}</p>
</tab>
<tab disabled="true">
<tab-heading>
<i class="glyphicon glyphicon-plus"></i>
</tab-heading>
</tab>
</tabset>
First of all it needed a bit of a trick/hack to integrate with ngRepeat
, so it can then re-order the array. It (re)parses the ng-repeat
attribute, and fetching the array from the scope, just like ngRepeat
does
// Attempt to integrate with ngRepeat
// https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211
var match = attrs.ngRepeat.match(/^s*([sS]+?)s+ins+([sS]+?)(?:s+tracks+bys+([sS]+?))?s*$/);
var tabs;
scope.$watch(match[2], function(newTabs) {
tabs = newTabs;
});
You can then also watch the $index
variable on the scope, to make sure you alway have the latest index of the current element:
var index = scope.$index;
scope.$watch('$index', function(newIndex) {
index = newIndex;
});
And then use HTML5 drag and drop, passing the index of the element as its data via setData
and getData
attrs.$set('draggable', true);
// Wrapped in $apply so Angular reacts to changes
var wrappedListeners = {
// On item being dragged
dragstart: function(e) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.dropEffect = 'move';
e.dataTransfer.setData('application/json', index);
element.addClass('dragging');
},
dragend: function(e) {
e.stopPropagation();
element.removeClass('dragging');
},
dragleave: function(e) {
element.removeClass('hover');
},
drop: function(e) {
e.preventDefault();
e.stopPropagation();
var sourceIndex = e.dataTransfer.getData('application/json');
move(sourceIndex, index);
element.removeClass('hover');
}
};
// For performance purposes, do not
// call $apply for these
var unwrappedListeners = {
dragover: function(e) {
e.preventDefault();
element.addClass('hover');
},
/* Use .hover instead of :hover. :hover doesn't play well with
moving DOM from under mouse when hovered */
mouseenter: function() {
element.addClass('hover');
},
mouseleave: function() {
element.removeClass('hover');
}
};
angular.forEach(wrappedListeners, function(listener, event) {
element.on(event, wrap(listener));
});
angular.forEach(unwrappedListeners, function(listener, event) {
element.on(event, listener);
});
function wrap(fn) {
return function(e) {
scope.$apply(function() {
fn(e);
});
};
}
Note: there is a bit of a hack regarding using a hover
class, instead of :hover
for some of the hover effects. This is partially due to CSS :hover
styles not being removed on elements after they were re-arranged from out of under the mouse, at least in Chrome.
The function to actually move the tabs, takes the array that ngRepeat
uses, and re-orders it:
function move(fromIndex, toIndex) {
// http://stackoverflow.com/a/7180095/1319998
tabs.splice(toIndex, 0, tabs.splice(fromIndex, 1)[0]);
};
You can see all this in a Plunker