feat(tabs): allow badges on tabbar via bound badge attr

This commit is contained in:
UniRing
2014-01-31 17:36:36 +01:00
committed by Andy Joslin
parent dc45b9ca50
commit bc927e57e7
16 changed files with 202 additions and 27 deletions

View File

@@ -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.

View File

@@ -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}}' +

View File

@@ -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');
});
});

View File

@@ -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();
})

View File

@@ -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) {

View File

@@ -79,6 +79,12 @@
color: $color;
}
@mixin tab-badge-style($bg-color, $color) {
.tab-item .badge {
background-color: $bg-color;
color: $color;
}
}
// Item Mixins
// --------------------------------------------------

View File

@@ -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 */

View File

@@ -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;

View File

@@ -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');
});

View File

@@ -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() {

View File

@@ -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">

View File

@@ -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>

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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>