如果您不想使用 Angular UI,比如出于尺寸原因,您可以推出自己的基本版本。演示地址为http://plnkr.co/edit/WnvZETQlxurhgcm1k6Hd?p=preview。
数据
您说您不需要标签是动态的,但这可能会使它们更易于重用。所以在包装范围内,你可以有:
$scope.tabs = [{
header: 'Tab A',
content: 'Content of Tab A'
},{
header: 'Tab B',
content: 'Content of Tab B'
}, {
header: 'Tab C',
content: 'Content of Tab C'
}];
标签 HTML
设计 HTML 结构,您可以在上面的列表中重复按钮和内容
<tabs>
<tab-buttons>
<tab-button ng-repeat="tab in tabs">{{tab.header}}</tab-button>
</tab-buttons>
<tab-contents>
<tab-content ng-repeat="tab in tabs">{{tab.content}}</tab-body>
</tab-contents>
</tabs>
标签指令
有很多方法可以做到这一点,但一种方法是在单个按钮指令上注册点击处理程序,然后将它们传达给父 tabs 控制器。这可以使用require 属性来完成,在父控制器上公开一个方法,在本例中为show,并通过传递ngRepeat 添加到范围的变量$index 来传递按钮的当前索引.
app.directive('tabs', function($timeout) {
return {
restrict: 'E',
controller: function($element, $scope) {
var self = this;
this.show = function(index) {
// Show only current tab
var contents = $element.find('tab-content');
contents.removeClass('current');
angular.element(contents[index]).addClass('current');
// Mark correct header as current
var buttons = $element.find('tab-button');
buttons.removeClass('current');
angular.element(buttons[index]).addClass('current');
};
$timeout(function() {
self.show('0');
});
}
};
});
app.directive('tabButton', function() {
return {
restrict: 'E',
require: '^tabs',
link: function(scope, element, attr, tabs) {
element.on('click', function() {
tabs.show(scope.$index);
});
}
};
});
假设您在页面中有正确的 CSS,特别是 .current 类的样式,如 http://plnkr.co/edit/WnvZETQlxurhgcm1k6Hd?p=preview ,此时有一组工作标签。
可排序
使用 HTML5 拖放 API,您可以进行一些基本的拖放操作,而无需担心鼠标位置等问题。首先要做的是设计使其工作所需的属性。在这种情况下,父项上的 sortable 属性引用列表,sortable-item 属性包含对当前项索引的引用。
<tabs sortable="tabs">
<tab-buttons>
<tab-button ng-repeat="tab in list" sortable-item="$index">{{tab.header}}</tab-button>
</tab-buttons>
<tab-contents>
<tab-content ng-repeat="tab in list">{{tab.content}}</tab-body>
</tab-contents>
</tabs>
sortable 和 sortableItem 指令可以如下所示(更多详细信息可以在 http://www.html5rocks.com/en/tutorials/dnd/basics/ 找到)
app.directive('sortable', function() {
return {
controller: function($scope, $attrs) {
var listModel = null;
$scope.$watch($attrs.sortable, function(sortable) {
listModel = sortable;
});
this.move = function(fromIndex, toIndex) {
// http://stackoverflow.com/a/7180095/1319998
listModel.splice(toIndex, 0, listModel.splice(fromIndex, 1)[0]);
};
}
};
});
app.directive('sortableItem', function($window) {
return {
require: '^sortable',
link: function(scope, element, attrs, sortableController) {
var index = null;
scope.$watch(attrs.sortableItem, function(newIndex) {
index = newIndex;
});
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');
},
// On item being dragged over / dropped onto
dragenter: function(e) {
element.addClass('hover');
},
dragleave: function(e) {
element.removeClass('hover');
},
drop: function(e) {
e.preventDefault();
e.stopPropagation();
element.removeClass('hover');
var sourceIndex = e.dataTransfer.getData('application/json');
sortableController.move(sourceIndex, index);
}
};
// For performance purposes, do not
// call $apply for these
var unwrappedListeners = {
dragover: function(e) {
e.preventDefault();
}
};
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);
});
};
}
}
};
});
主要需要注意的是每个sortableItem只需要知道它当前的索引即可。如果它检测到另一个项目已被丢弃,它会调用sortable 控制器上的函数,然后在外部范围内重新排序数组。 ngRepeat 然后做它平常的事情并移动标签。
虽然我怀疑有更简单的解决方案,但这个解决方案的可排序行为和选项卡行为完全解耦。您可以在不是tabs 的元素上使用sortable,也可以在没有可排序行为的情况下使用tabs。