mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(tabs): allow badges on tabbar via bound badge attr
This commit is contained in:
@@ -122,7 +122,8 @@ ionic.controllers.TabBarController = ionic.controllers.ViewController.inherit({
|
||||
|
||||
this.tabBar.addItem({
|
||||
title: controller.title,
|
||||
icon: controller.icon
|
||||
icon: controller.icon,
|
||||
badge: controller.badge
|
||||
});
|
||||
|
||||
// If we don't have a selected controller yet, select the first one.
|
||||
|
||||
11
js/ext/angular/src/directive/ionicTabBar.js
vendored
11
js/ext/angular/src/directive/ionicTabBar.js
vendored
@@ -169,6 +169,11 @@ angular.module('ionic.ui.tabs', ['ionic.service.view'])
|
||||
// tell any parent nav controller to animate
|
||||
$scope.animate = $scope.$eval($attr.animate);
|
||||
|
||||
var badge = $parse($attr.badge);
|
||||
$scope.$watch(badge, function(value) {
|
||||
$scope.badge = value;
|
||||
});
|
||||
|
||||
var leftButtonsGet = $parse($attr.leftButtons);
|
||||
$scope.$watch(leftButtonsGet, function(value) {
|
||||
$scope.leftButtons = value;
|
||||
@@ -241,7 +246,7 @@ angular.module('ionic.ui.tabs', ['ionic.service.view'])
|
||||
replace: true,
|
||||
scope: true,
|
||||
template: '<div class="tabs">' +
|
||||
'<tab-controller-item icon-title="{{c.title}}" icon="{{c.icon}}" icon-on="{{c.iconOn}}" icon-off="{{c.iconOff}}" active="c.isVisible" index="$index" ng-repeat="c in controllers"></tab-controller-item>' +
|
||||
'<tab-controller-item icon-title="{{c.title}}" icon="{{c.icon}}" icon-on="{{c.iconOn}}" icon-off="{{c.iconOff}}" badge="c.badge" active="c.isVisible" index="$index" ng-repeat="c in controllers"></tab-controller-item>' +
|
||||
'</div>',
|
||||
link: function($scope, $element, $attr, tabsCtrl) {
|
||||
$element.addClass($scope.tabsType);
|
||||
@@ -260,6 +265,7 @@ angular.module('ionic.ui.tabs', ['ionic.service.view'])
|
||||
icon: '@',
|
||||
iconOn: '@',
|
||||
iconOff: '@',
|
||||
badge: '=',
|
||||
active: '=',
|
||||
tabSelected: '@',
|
||||
index: '='
|
||||
@@ -274,7 +280,8 @@ angular.module('ionic.ui.tabs', ['ionic.service.view'])
|
||||
};
|
||||
},
|
||||
template:
|
||||
'<a ng-class="{active:active}" ng-click="selectTab()" class="tab-item">' +
|
||||
'<a ng-class="{active:active, \'has-badge\':badge}" ng-click="selectTab()" class="tab-item">' +
|
||||
'<i class="badge" ng-if="badge">{{badge}}</i>' +
|
||||
'<i class="icon {{icon}}" ng-if="icon"></i>' +
|
||||
'<i class="{{iconOn}}" ng-if="active"></i>' +
|
||||
'<i class="{{iconOff}}" ng-if="!active"></i> {{iconTitle}}' +
|
||||
|
||||
@@ -152,23 +152,47 @@ describe('Tab Item directive', function() {
|
||||
compile = $compile;
|
||||
scope = $rootScope;
|
||||
|
||||
scope.badgeValue = 3;
|
||||
element = compile('<tabs>' +
|
||||
'<tab title="Item" icon="icon-default"></tab>' +
|
||||
'<tab title="Item" icon="icon-default" badge="badgeValue"></tab>' +
|
||||
'</tabs>')(scope);
|
||||
scope.$digest();
|
||||
$document[0].body.appendChild(element[0]);
|
||||
}));
|
||||
|
||||
it('Default text works', function() {
|
||||
expect(element.find('a').first().text().trim()).toEqual('Item');
|
||||
var title = '';
|
||||
var a = element.find('a')[0];
|
||||
for(i = 0, j = a.childNodes.length; i < j; i++) {
|
||||
child = a.childNodes[i];
|
||||
|
||||
if (child.nodeName === "#text") {
|
||||
title += child.nodeValue.trim();
|
||||
}
|
||||
}
|
||||
expect(title).toEqual('Item');
|
||||
});
|
||||
|
||||
it('Default icon works', function() {
|
||||
scope.$digest();
|
||||
var i = element[0].querySelector('i');
|
||||
var i = element[0].querySelectorAll('i')[1];
|
||||
expect(angular.element(i).hasClass('icon-default')).toEqual(true);
|
||||
});
|
||||
|
||||
it('Badge works', function() {
|
||||
scope.$digest();
|
||||
var i = element[0].querySelectorAll('i')[0];
|
||||
expect(angular.element(i).hasClass('badge')).toEqual(true);
|
||||
expect(i.innerHTML).toEqual('3');
|
||||
});
|
||||
|
||||
it('Badge updates', function() {
|
||||
scope.badgeValue = 10;
|
||||
scope.$digest();
|
||||
var i = element[0].querySelectorAll('i')[0];
|
||||
expect(i.innerHTML).toEqual('10');
|
||||
});
|
||||
|
||||
it('Click sets correct tab index', function() {
|
||||
var a = element.find('a:eq(0)');
|
||||
var itemScope = a.isolateScope();
|
||||
@@ -178,3 +202,62 @@ describe('Tab Item directive', function() {
|
||||
expect(itemScope.selectTab).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tab Controller Item directive', function() {
|
||||
var compile, element, scope, ctrl;
|
||||
|
||||
beforeEach(module('ionic.ui.tabs'));
|
||||
|
||||
beforeEach(inject(function($compile, $rootScope, $document, $controller) {
|
||||
compile = $compile;
|
||||
scope = $rootScope;
|
||||
|
||||
scope.badgeValue = 3;
|
||||
scope.isActive = false;
|
||||
element = compile('<tabs class="tabs">' +
|
||||
'<tab-controller-item icon-title="Icon title" icon="icon-class" icon-on="icon-on-class" icon-off="icon-off-class" badge="badgeValue" active="isActive" index="0"></tab-controller-item>' +
|
||||
'</tabs>')(scope);
|
||||
scope.$digest();
|
||||
$document[0].body.appendChild(element[0]);
|
||||
}));
|
||||
|
||||
it('Icon title works', function() {
|
||||
var title = '';
|
||||
var a = element.find('a')[0];
|
||||
for(var i = 0, j = a.childNodes.length; i < j; i++) {
|
||||
child = a.childNodes[i];
|
||||
|
||||
if (child.nodeName === "#text") {
|
||||
title += child.nodeValue.trim();
|
||||
}
|
||||
}
|
||||
expect(title).toEqual('Icon title');
|
||||
});
|
||||
|
||||
it('Icon classes works', function() {
|
||||
var title = '';
|
||||
var elements = element[0].querySelectorAll('.icon-class');
|
||||
expect(elements.length).toEqual(1);
|
||||
var elements = element[0].querySelectorAll('.icon-off-class');
|
||||
expect(elements.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('Active switch works', function() {
|
||||
var elements = element[0].querySelectorAll('.icon-on-class');
|
||||
expect(elements.length).toEqual(0);
|
||||
|
||||
scope.isActive = true;
|
||||
scope.$digest();
|
||||
|
||||
var elements = element[0].querySelectorAll('.icon-on-class');
|
||||
expect(elements.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('Badge updates', function() {
|
||||
scope.badgeValue = 10;
|
||||
scope.$digest();
|
||||
var i = element[0].querySelectorAll('i')[0];
|
||||
expect(i.innerHTML).toEqual('10');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
tabs-style="tabs-top tabs-positive"
|
||||
controller-changed="onControllerChanged(oldController, oldIndex, newController, newIndex)">
|
||||
|
||||
<tab title="Home" icon-on="icon ion-ios7-filing" icon-off="icon ion-ios7-filing-outline" ng-controller="HomeCtrl">
|
||||
<tab title="Tasks" icon-on="icon ion-ios7-filing" icon-off="icon ion-ios7-filing-outline" ng-controller="HomeCtrl">
|
||||
<header class="bar bar-header bar-positive">
|
||||
<button class="button button-icon icon ion-plus" ng-click="newTask()"></button>
|
||||
<h1 class="title">Tasks</h1>
|
||||
@@ -88,7 +88,7 @@
|
||||
</content>
|
||||
</tab>
|
||||
|
||||
<tab title="About" icon-on="icon ion-ios7-clock" icon-off="icon ion-ios7-clock-outline">
|
||||
<tab title="Deadlines" icon-on="icon ion-ios7-clock" icon-off="icon ion-ios7-clock-outline" badge="unreadDeadlines">
|
||||
<header class="bar bar-header bar-positive">
|
||||
<h1 class="title">Deadlines</h1>
|
||||
</header>
|
||||
@@ -132,15 +132,35 @@
|
||||
<script>
|
||||
angular.module('tabsTest', ['ionic'])
|
||||
|
||||
.controller('RootCtrl', function($scope) {
|
||||
$scope.onControllerChanged = function(oldController, oldIndex, newController, newIndex) {
|
||||
console.log('Controller changed', oldController, oldIndex, newController, newIndex);
|
||||
console.log(arguments);
|
||||
.controller('RootCtrl', function($scope, $timeout) {
|
||||
$scope.controllerChanged = function(event) {
|
||||
console.log('Controller changed', event);
|
||||
|
||||
// Tab badge demo
|
||||
if (event.newIndex == 1) {
|
||||
$scope.unreadDeadlines = false;
|
||||
} else {
|
||||
if (!$scope.unreadDeadlines)
|
||||
$scope.unreadDeadlines = 1;
|
||||
updateRandomDeadlines();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.scroll = function(scrollTop, scrollLeft) {
|
||||
console.log('Scroll', scrollTop, scrollLeft);
|
||||
};
|
||||
|
||||
// Tab badge demo
|
||||
$scope.unreadDeadlines = false;
|
||||
function updateRandomDeadlines() {
|
||||
$timeout(function() {
|
||||
if (!$scope.unreadDeadlines)
|
||||
return;
|
||||
$scope.unreadDeadlines += 1;
|
||||
updateRandomDeadlines();
|
||||
}, Math.random() * 10000);
|
||||
}
|
||||
updateRandomDeadlines();
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ ionic.views.TabBarItem = ionic.views.View.inherit({
|
||||
|
||||
this._buildItem();
|
||||
},
|
||||
|
||||
// Factory for creating an item from a given javascript object
|
||||
create: function(itemData) {
|
||||
var item = document.createElement('a');
|
||||
@@ -18,12 +19,21 @@ ionic.views.TabBarItem = ionic.views.View.inherit({
|
||||
icon.className = itemData.icon;
|
||||
item.appendChild(icon);
|
||||
}
|
||||
|
||||
// If there is a badge, add the badge element
|
||||
if(itemData.badge) {
|
||||
var badge = document.createElement('i');
|
||||
badge.className = 'badge';
|
||||
badge.innerHTML = itemData.badge;
|
||||
item.appendChild(badge);
|
||||
item.className = 'tab-item has-badge';
|
||||
}
|
||||
|
||||
item.appendChild(document.createTextNode(itemData.title));
|
||||
|
||||
return new ionic.views.TabBarItem(item);
|
||||
},
|
||||
|
||||
|
||||
_buildItem: function() {
|
||||
var _this = this, child, children = Array.prototype.slice.call(this.el.children);
|
||||
|
||||
@@ -34,13 +44,23 @@ ionic.views.TabBarItem = ionic.views.View.inherit({
|
||||
// TODO: This heuristic might not be sufficient
|
||||
if(child.tagName.toLowerCase() == 'i' && /icon/.test(child.className)) {
|
||||
this.icon = child.className;
|
||||
break;
|
||||
}
|
||||
|
||||
// Test if this is a "i" tag with badge in the class name
|
||||
// TODO: This heuristic might not be sufficient
|
||||
if(child.tagName.toLowerCase() == 'i' && /badge/.test(child.className)) {
|
||||
this.badge = child.textContent.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Set the title to the text content of the tab.
|
||||
this.title = this.el.textContent.trim();
|
||||
this.title = '';
|
||||
for(i = 0, j = this.el.childNodes.length; i < j; i++) {
|
||||
child = this.el.childNodes[i];
|
||||
|
||||
if (child.nodeName === "#text") {
|
||||
this.title += child.nodeValue.trim();
|
||||
}
|
||||
}
|
||||
|
||||
this._tapHandler = function(e) {
|
||||
_this.onTap && _this.onTap(e);
|
||||
@@ -64,6 +84,10 @@ ionic.views.TabBarItem = ionic.views.View.inherit({
|
||||
return this.title;
|
||||
},
|
||||
|
||||
getBadge: function() {
|
||||
return this.badge;
|
||||
},
|
||||
|
||||
setSelected: function(isSelected) {
|
||||
this.isSelected = isSelected;
|
||||
if(isSelected) {
|
||||
|
||||
@@ -79,6 +79,12 @@
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@mixin tab-badge-style($bg-color, $color) {
|
||||
.tab-item .badge {
|
||||
background-color: $bg-color;
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Item Mixins
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@include justify-content(center);
|
||||
|
||||
@include tab-style($tabs-default-bg, $tabs-default-border, $tabs-default-text);
|
||||
@include tab-badge-style($tabs-default-text, $tabs-default-bg);
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@@ -28,30 +29,39 @@
|
||||
|
||||
&.tabs-light {
|
||||
@include tab-style($tabs-light-bg, $tabs-light-border, $tabs-light-text);
|
||||
@include tab-badge-style($tabs-light-text, $tabs-light-bg);
|
||||
}
|
||||
&.tabs-stable {
|
||||
@include tab-style($tabs-stable-bg, $tabs-stable-border, $tabs-stable-text);
|
||||
@include tab-badge-style($tabs-stable-text, $tabs-stable-bg);
|
||||
}
|
||||
&.tabs-positive {
|
||||
@include tab-style($tabs-positive-bg, $tabs-positive-border, $tabs-positive-text);
|
||||
@include tab-badge-style($tabs-positive-text, $tabs-positive-bg);
|
||||
}
|
||||
&.tabs-calm {
|
||||
@include tab-style($tabs-calm-bg, $tabs-calm-border, $tabs-calm-text);
|
||||
@include tab-badge-style($tabs-calm-text, $tabs-calm-bg);
|
||||
}
|
||||
&.tabs-assertive {
|
||||
@include tab-style($tabs-assertive-bg, $tabs-assertive-border, $tabs-assertive-text);
|
||||
@include tab-badge-style($tabs-assertive-text, $tabs-assertive-bg);
|
||||
}
|
||||
&.tabs-balanced {
|
||||
@include tab-style($tabs-balanced-bg, $tabs-balanced-border, $tabs-balanced-text);
|
||||
@include tab-badge-style($tabs-balanced-text, $tabs-balanced-bg);
|
||||
}
|
||||
&.tabs-energized {
|
||||
@include tab-style($tabs-energized-bg, $tabs-energized-border, $tabs-energized-text);
|
||||
@include tab-badge-style($tabs-energized-text, $tabs-energized-bg);
|
||||
}
|
||||
&.tabs-royal {
|
||||
@include tab-style($tabs-royal-bg, $tabs-royal-border, $tabs-royal-text);
|
||||
@include tab-badge-style($tabs-royal-text, $tabs-royal-bg);
|
||||
}
|
||||
&.tabs-dark {
|
||||
@include tab-style($tabs-dark-bg, $tabs-dark-border, $tabs-dark-text);
|
||||
@include tab-badge-style($tabs-dark-text, $tabs-dark-bg);
|
||||
}
|
||||
@media (min--moz-device-pixel-ratio: 1.5),
|
||||
(-webkit-min-device-pixel-ratio: 1.5),
|
||||
@@ -135,6 +145,20 @@
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.tab-item.has-badge {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item .badge {
|
||||
position: absolute;
|
||||
padding: $tabs-badge-padding;
|
||||
top: 2%;
|
||||
right: 10%;
|
||||
font-size: $tabs-badge-font-size;
|
||||
height: auto;
|
||||
line-height: $tabs-badge-font-size + 4;
|
||||
}
|
||||
|
||||
/* Navigational tab */
|
||||
|
||||
/* Active state for tab */
|
||||
|
||||
@@ -231,6 +231,9 @@ $tabs-height: 49px !default;
|
||||
$tabs-text-font-size: 14px !default;
|
||||
$tabs-text-font-size-side-icon: 12px !default;
|
||||
$tabs-icon-size: 32px !default;
|
||||
$tabs-badge-padding: 2px 6px;
|
||||
$tabs-badge-font-size: 12px !default;
|
||||
$tabs-text-font-size: 14px !default;
|
||||
|
||||
$tabs-light-bg: $button-light-bg;
|
||||
$tabs-light-border: $button-light-border;
|
||||
|
||||
@@ -14,14 +14,17 @@ describe('TabBarController', function() {
|
||||
ctrl.addController({
|
||||
title: 'Item 1',
|
||||
icon: 'icon-home',
|
||||
badge: 'Badge 1'
|
||||
});
|
||||
|
||||
expect(ctrl.getController(0).title).toEqual('Item 1');
|
||||
expect(ctrl.getController(0).badge).toEqual('Badge 1');
|
||||
|
||||
var items = ctrl.tabBar.getItems();
|
||||
expect(items.length).toEqual(1);
|
||||
|
||||
expect(items[0].getTitle()).toEqual('Item 1');
|
||||
expect(items[0].getBadge()).toEqual('Badge 1');
|
||||
expect(items[0].getIcon()).toEqual('icon-home');
|
||||
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ describe('TabBar view', function() {
|
||||
beforeEach(function() {
|
||||
element = $('<div class="tabs">' +
|
||||
'<a href="#" class="tab-item"><i class="icon-home"></i> Tab 1</a>' +
|
||||
'<a href="#" class="tab-item">Tab 2</a>' +
|
||||
'<a href="#" class="tab-item has-badge"><i class="badge">Badge 1</i> Tab 2</a>' +
|
||||
'<a href="#" class="tab-item">Tab 3</a>');
|
||||
|
||||
tabBar = new ionic.views.TabBar({
|
||||
@@ -20,10 +20,8 @@ describe('TabBar view', function() {
|
||||
expect(items[2].getTitle()).toEqual('Tab 3');
|
||||
});
|
||||
|
||||
it('Should trim title', function() {
|
||||
expect(items[0].el.textContent.trim()).toEqual(items[0].getTitle());
|
||||
expect(items[1].el.textContent.trim()).toEqual(items[1].getTitle());
|
||||
expect(items[2].el.textContent.trim()).toEqual(items[2].getTitle());
|
||||
it('Should read badge', function() {
|
||||
expect(items[1].getBadge()).toEqual('Badge 1');
|
||||
});
|
||||
|
||||
it('Should select', function() {
|
||||
|
||||
@@ -41,8 +41,9 @@
|
||||
Simple
|
||||
<i class="icon ion-heart"></i>
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
<a class="tab-item has-badge">
|
||||
Light
|
||||
<i class="badge">3</i>
|
||||
<i class="icon ion-leaf"></i>
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
<i class="icon ion-heart"></i>
|
||||
Simple
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
<a class="tab-item has-badge">
|
||||
<i class="badge">3</i>
|
||||
<i class="icon ion-leaf"></i>
|
||||
Light
|
||||
</a>
|
||||
|
||||
@@ -41,8 +41,9 @@
|
||||
Simple
|
||||
<i class="icon ion-heart"></i>
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
<a class="tab-item has-badge">
|
||||
Light
|
||||
<i class="badge">3</i>
|
||||
<i class="icon ion-leaf"></i>
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
<a class="tab-item">
|
||||
Simple
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
<a class="tab-item has-badge">
|
||||
<i class="badge">3</i>
|
||||
Light
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
<a class="tab-item tab-item-danger">
|
||||
<i class="icon ion-heart"></i>
|
||||
</a>
|
||||
<a class="tab-item tab-item-danger">
|
||||
<a class="tab-item tab-item-danger has-badge">
|
||||
<i class="badge">3</i>
|
||||
<i class="icon ion-leaf"></i>
|
||||
</a>
|
||||
<a class="tab-item tab-item-danger">
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
<i class="icon ion-heart"></i>
|
||||
Simple
|
||||
</a>
|
||||
<a class="tab-item">
|
||||
<a class="tab-item has-badge">
|
||||
<i class="badge">3</i>
|
||||
<i class="icon ion-leaf"></i>
|
||||
Light
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user