mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat($ionicBody): service to simplify body ele interaction
Many services/directives have to interact with the body element, and each one has to write the same long code. The $ionicBody service provides some useful methods to clean up and reduce redundant code.
This commit is contained in:
18
js/angular/controller/sideMenuController.js
vendored
18
js/angular/controller/sideMenuController.js
vendored
@@ -4,8 +4,8 @@ IonicModule
|
||||
'$attrs',
|
||||
'$ionicSideMenuDelegate',
|
||||
'$ionicPlatform',
|
||||
'$document',
|
||||
function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $document) {
|
||||
'$ionicBody',
|
||||
function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody) {
|
||||
var self = this;
|
||||
var rightShowing, leftShowing, isDragging;
|
||||
var startX, lastX, offsetX, isAsideExposed;
|
||||
@@ -132,11 +132,9 @@ function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $document) {
|
||||
self.openAmount(self.right.width * p);
|
||||
}
|
||||
|
||||
if(percentage !== 0) {
|
||||
$document[0].body.classList.add('menu-open');
|
||||
} else {
|
||||
$document[0].body.classList.remove('menu-open');
|
||||
}
|
||||
// add the CSS class "menu-open" if the percentage does not
|
||||
// equal 0, otherwise remove the class from the body element
|
||||
$ionicBody.enableClass( (percentage !== 0), 'menu-open');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -269,11 +267,7 @@ function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $document) {
|
||||
};
|
||||
|
||||
self.activeAsideResizing = function(isResizing) {
|
||||
if(isResizing) {
|
||||
$document[0].body.classList.add('aside-resizing');
|
||||
} else {
|
||||
$document[0].body.classList.remove('aside-resizing');
|
||||
}
|
||||
$ionicBody.enableClass(isResizing, 'aside-resizing');
|
||||
};
|
||||
|
||||
// End a drag with the given event
|
||||
|
||||
15
js/angular/directive/sideMenus.js
vendored
15
js/angular/directive/sideMenus.js
vendored
@@ -64,10 +64,7 @@ IonicModule
|
||||
* with {@link ionic.service:$ionicSideMenuDelegate}.
|
||||
*
|
||||
*/
|
||||
.directive('ionSideMenus', ['$document', function($document) {
|
||||
|
||||
var ASIDE_OPEN_CSS = 'aside-open';
|
||||
|
||||
.directive('ionSideMenus', ['$ionicBody', function($ionicBody) {
|
||||
return {
|
||||
restrict: 'ECA',
|
||||
controller: '$ionicSideMenus',
|
||||
@@ -76,21 +73,15 @@ IonicModule
|
||||
|
||||
return { pre: prelink };
|
||||
function prelink($scope) {
|
||||
var bodyClassList = $document[0].body.classList;
|
||||
|
||||
$scope.$on('$ionicExposeAside', function(evt, isAsideExposed){
|
||||
if(!$scope.$exposeAside) $scope.$exposeAside = {};
|
||||
$scope.$exposeAside.active = isAsideExposed;
|
||||
if(isAsideExposed) {
|
||||
bodyClassList.add(ASIDE_OPEN_CSS);
|
||||
} else {
|
||||
bodyClassList.remove(ASIDE_OPEN_CSS);
|
||||
}
|
||||
$ionicBody.enableClass(isAsideExposed, 'aside-open');
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function(){
|
||||
bodyClassList.remove('menu-open');
|
||||
bodyClassList.remove(ASIDE_OPEN_CSS);
|
||||
$ionicBody.removeClass('menu-open', 'aside-open');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
10
js/angular/service/actionSheet.js
vendored
10
js/angular/service/actionSheet.js
vendored
@@ -51,13 +51,13 @@
|
||||
IonicModule
|
||||
.factory('$ionicActionSheet', [
|
||||
'$rootScope',
|
||||
'$document',
|
||||
'$compile',
|
||||
'$animate',
|
||||
'$timeout',
|
||||
'$ionicTemplateLoader',
|
||||
'$ionicPlatform',
|
||||
function($rootScope, $document, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform) {
|
||||
'$ionicBody',
|
||||
function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody) {
|
||||
|
||||
return {
|
||||
show: actionSheet
|
||||
@@ -119,7 +119,7 @@ function($rootScope, $document, $compile, $animate, $timeout, $ionicTemplateLoad
|
||||
|
||||
scope.removed = true;
|
||||
sheetEl.removeClass('action-sheet-up');
|
||||
$document[0].body.classList.remove('action-sheet-open');
|
||||
$ionicBody.removeClass('action-sheet-open');
|
||||
scope.$deregisterBackButton();
|
||||
stateChangeListenDone();
|
||||
|
||||
@@ -135,8 +135,8 @@ function($rootScope, $document, $compile, $animate, $timeout, $ionicTemplateLoad
|
||||
scope.showSheet = function(done) {
|
||||
if (scope.removed) return;
|
||||
|
||||
$document[0].body.appendChild(element[0]);
|
||||
$document[0].body.classList.add('action-sheet-open');
|
||||
$ionicBody.append(element)
|
||||
.addClass('action-sheet-open');
|
||||
|
||||
$animate.addClass(element, 'active', function() {
|
||||
if (scope.removed) return;
|
||||
|
||||
80
js/angular/service/body.js
vendored
Normal file
80
js/angular/service/body.js
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name $ionicBody
|
||||
* @module ionic
|
||||
* @description An angular utility service to easily and efficiently
|
||||
* add and remove CSS classes from the document's body element.
|
||||
*/
|
||||
IonicModule
|
||||
.factory('$ionicBody', ['$document', function($document) {
|
||||
return {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $ionicBody#add
|
||||
* @description Add a class to the document's body element.
|
||||
* @param {string} class Each argument will be added to the body element.
|
||||
* @returns {$ionicBody} The $ionicBody service so methods can be chained.
|
||||
*/
|
||||
addClass: function() {
|
||||
for(var x=0; x<arguments.length; x++) {
|
||||
$document[0].body.classList.add(arguments[x]);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $ionicBody#removeClass
|
||||
* @description Remove a class from the document's body element.
|
||||
* @param {string} class Each argument will be removed from the body element.
|
||||
* @returns {$ionicBody} The $ionicBody service so methods can be chained.
|
||||
*/
|
||||
removeClass: function() {
|
||||
for(var x=0; x<arguments.length; x++) {
|
||||
$document[0].body.classList.remove(arguments[x]);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $ionicBody#enableClass
|
||||
* @description Similar to the `add` method, except the first parameter accepts a boolean
|
||||
* value determining if the class should be added or removed. Rather than writing user code,
|
||||
* such as "if true then add the class, else then remove the class", this method can be
|
||||
* given a true or false value which reduces redundant code.
|
||||
* @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.
|
||||
* @param {string} class Each remaining argument would be added or removed depending on
|
||||
* the first argument.
|
||||
* @returns {$ionicBody} The $ionicBody service so methods can be chained.
|
||||
*/
|
||||
enableClass: function(shouldEnableClass) {
|
||||
var args = Array.prototype.slice.call(arguments).slice(1);
|
||||
if(shouldEnableClass) {
|
||||
this.addClass.apply(this, args);
|
||||
} else {
|
||||
this.removeClass.apply(this, args);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $ionicBody#append
|
||||
* @description Append a child to the document's body.
|
||||
* @param {element} element The element to be appended to the body. The passed in element
|
||||
* can be either a jqLite element, or a DOM element.
|
||||
* @returns {$ionicBody} The $ionicBody service so methods can be chained.
|
||||
*/
|
||||
append: function(ele) {
|
||||
$document[0].body.appendChild( ele.length ? ele[0] : ele );
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $ionicBody#get
|
||||
* @description Get the document's body element.
|
||||
* @returns {element} Returns the document's body element.
|
||||
*/
|
||||
get: function() {
|
||||
return $document[0].body;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
14
js/angular/service/loading.js
vendored
14
js/angular/service/loading.js
vendored
@@ -58,7 +58,7 @@ IonicModule
|
||||
})
|
||||
.factory('$ionicLoading', [
|
||||
'$ionicLoadingConfig',
|
||||
'$document',
|
||||
'$ionicBody',
|
||||
'$ionicTemplateLoader',
|
||||
'$ionicBackdrop',
|
||||
'$timeout',
|
||||
@@ -66,7 +66,7 @@ IonicModule
|
||||
'$log',
|
||||
'$compile',
|
||||
'$ionicPlatform',
|
||||
function($ionicLoadingConfig, $document, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform) {
|
||||
function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform) {
|
||||
|
||||
var loaderInstance;
|
||||
//default values
|
||||
@@ -104,7 +104,7 @@ function($ionicLoadingConfig, $document, $ionicTemplateLoader, $ionicBackdrop, $
|
||||
if (!loaderInstance) {
|
||||
loaderInstance = $ionicTemplateLoader.compile({
|
||||
template: LOADING_TPL,
|
||||
appendTo: $document[0].body
|
||||
appendTo: $ionicBody.get()
|
||||
})
|
||||
.then(function(loader) {
|
||||
var self = loader;
|
||||
@@ -144,8 +144,10 @@ function($ionicLoadingConfig, $document, $ionicTemplateLoader, $ionicBackdrop, $
|
||||
if (self.isShown) {
|
||||
self.element.addClass('visible');
|
||||
ionic.requestAnimationFrame(function() {
|
||||
self.isShown && self.element.addClass('active');
|
||||
self.isShown && $document[0].body.classList.add('loading-active');
|
||||
if(self.isShown) {
|
||||
self.element.addClass('active');
|
||||
$ionicBody.addClass('loading-active');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -159,7 +161,7 @@ function($ionicLoadingConfig, $document, $ionicTemplateLoader, $ionicBackdrop, $
|
||||
$ionicBackdrop.getElement().removeClass('backdrop-loading');
|
||||
}
|
||||
self.element.removeClass('active');
|
||||
$document[0].body.classList.remove('loading-active');
|
||||
$ionicBody.removeClass('loading-active');
|
||||
setTimeout(function() {
|
||||
!self.isShown && self.element.removeClass('visible');
|
||||
}, 200);
|
||||
|
||||
10
js/angular/service/modal.js
vendored
10
js/angular/service/modal.js
vendored
@@ -61,14 +61,14 @@
|
||||
IonicModule
|
||||
.factory('$ionicModal', [
|
||||
'$rootScope',
|
||||
'$document',
|
||||
'$ionicBody',
|
||||
'$compile',
|
||||
'$timeout',
|
||||
'$ionicPlatform',
|
||||
'$ionicTemplateLoader',
|
||||
'$q',
|
||||
'$log',
|
||||
function($rootScope, $document, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log) {
|
||||
function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log) {
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
@@ -124,12 +124,12 @@ function($rootScope, $document, $compile, $timeout, $ionicPlatform, $ionicTempla
|
||||
|
||||
self.el.classList.remove('hide');
|
||||
$timeout(function(){
|
||||
$document[0].body.classList.add(self.viewType + '-open');
|
||||
$ionicBody.addClass(self.viewType + '-open');
|
||||
}, 400);
|
||||
|
||||
if(!self.el.parentElement) {
|
||||
modalEl.addClass(self.animation);
|
||||
$document[0].body.appendChild(self.el);
|
||||
$ionicBody.append(self.el);
|
||||
}
|
||||
|
||||
if(target && self.positionView) {
|
||||
@@ -192,7 +192,7 @@ function($rootScope, $document, $compile, $timeout, $ionicPlatform, $ionicTempla
|
||||
ionic.views.Modal.prototype.hide.call(self);
|
||||
|
||||
return $timeout(function(){
|
||||
$document[0].body.classList.remove(self.viewType + '-open');
|
||||
$ionicBody.removeClass(self.viewType + '-open');
|
||||
self.el.classList.add('hide');
|
||||
}, self.hideDelay || 500);
|
||||
},
|
||||
|
||||
10
js/angular/service/popup.js
vendored
10
js/angular/service/popup.js
vendored
@@ -110,10 +110,10 @@ IonicModule
|
||||
'$q',
|
||||
'$timeout',
|
||||
'$rootScope',
|
||||
'$document',
|
||||
'$ionicBody',
|
||||
'$compile',
|
||||
'$ionicPlatform',
|
||||
function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $document, $compile, $ionicPlatform) {
|
||||
function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform) {
|
||||
//TODO allow this to be configured
|
||||
var config = {
|
||||
stackPushDelay: 75
|
||||
@@ -278,7 +278,7 @@ function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $docume
|
||||
var popupPromise = $ionicTemplateLoader.compile({
|
||||
template: POPUP_TPL,
|
||||
scope: options.scope && options.scope.$new(),
|
||||
appendTo: $document[0].body
|
||||
appendTo: $ionicBody.get()
|
||||
});
|
||||
var contentPromise = options.templateUrl ?
|
||||
$ionicTemplateLoader.load(options.templateUrl) :
|
||||
@@ -370,7 +370,7 @@ function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $docume
|
||||
.then(function(popup) {
|
||||
if (!previousPopup) {
|
||||
//Add popup-open & backdrop if this is first popup
|
||||
document.body.classList.add('popup-open');
|
||||
$ionicBody.addClass('popup-open');
|
||||
$ionicBackdrop.retain();
|
||||
//only show the backdrop on the first popup
|
||||
$ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
|
||||
@@ -398,7 +398,7 @@ function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $docume
|
||||
previousPopup.show();
|
||||
} else {
|
||||
//Remove popup-open & backdrop if this is last popup
|
||||
document.body.classList.remove('popup-open');
|
||||
$ionicBody.removeClass('popup-open');
|
||||
$ionicBackdrop.release();
|
||||
($ionicPopup._backButtonActionDone || angular.noop)();
|
||||
}
|
||||
|
||||
@@ -84,6 +84,14 @@ describe('$ionicSideMenus controller', function() {
|
||||
expect(ctrl.getOpenPercentage()).toEqual(-50);
|
||||
});
|
||||
|
||||
it('should add/remove menu-open from the body class', inject(function($document) {
|
||||
expect($document[0].body.classList.contains('menu-open')).toEqual(false);
|
||||
ctrl.openPercentage(100);
|
||||
expect($document[0].body.classList.contains('menu-open')).toEqual(true);
|
||||
ctrl.openPercentage(0);
|
||||
expect($document[0].body.classList.contains('menu-open')).toEqual(false);
|
||||
}));
|
||||
|
||||
// Open
|
||||
it('should toggle left', function() {
|
||||
ctrl.toggleLeft();
|
||||
|
||||
@@ -40,6 +40,18 @@ describe('Ionic Angular Side Menu', function() {
|
||||
expect(sideMenuController.isAsideExposed()).toBe(false);
|
||||
}));
|
||||
|
||||
it('should add/remove "aside-resizing" from the body tag when using activeAsideResizing', inject(function($compile, $rootScope, $document) {
|
||||
var el = $compile('<ion-side-menus><ion-side-menu></><ion-side-menu-content></ion-side-menu-content></ion-side-menus>')($rootScope.$new());
|
||||
$rootScope.$apply();
|
||||
var sideMenuController = el.controller('ionSideMenus');
|
||||
|
||||
expect($document[0].body.classList.contains('aside-resizing')).toEqual(false);
|
||||
sideMenuController.activeAsideResizing(true);
|
||||
expect($document[0].body.classList.contains('aside-resizing')).toEqual(true);
|
||||
sideMenuController.activeAsideResizing(false);
|
||||
expect($document[0].body.classList.contains('aside-resizing')).toEqual(false);
|
||||
}));
|
||||
|
||||
it('should emit $ionicexposeAside', inject(function($compile, $rootScope) {
|
||||
var el = $compile('<ion-side-menus><ion-side-menu></><ion-side-menu-content></ion-side-menu-content></ion-side-menus>')($rootScope.$new());
|
||||
$rootScope.$apply();
|
||||
|
||||
71
test/unit/angular/service/body.unit.js
Normal file
71
test/unit/angular/service/body.unit.js
Normal file
@@ -0,0 +1,71 @@
|
||||
describe('Ionic Body Class Service', function() {
|
||||
var ionicBody, doc;
|
||||
|
||||
beforeEach(module('ionic'));
|
||||
|
||||
beforeEach(inject(function($ionicBody, $document) {
|
||||
ionicBody = $ionicBody;
|
||||
body = $document[0].body;
|
||||
}));
|
||||
|
||||
it('Should add/remove one class the body tag', function() {
|
||||
ionicBody.addClass('class1');
|
||||
expect(body.classList.contains('class1')).toEqual(true);
|
||||
ionicBody.removeClass('class1');
|
||||
expect(body.classList.contains('class1')).toEqual(false);
|
||||
});
|
||||
|
||||
it('Should add/remove multiple classes the body tag', function() {
|
||||
ionicBody.addClass('class1', 'class2', 'class3');
|
||||
expect(body.classList.contains('class1')).toEqual(true);
|
||||
expect(body.classList.contains('class2')).toEqual(true);
|
||||
expect(body.classList.contains('class3')).toEqual(true);
|
||||
ionicBody.removeClass('class1', 'class2', 'class3');
|
||||
expect(body.classList.contains('class1')).toEqual(false);
|
||||
expect(body.classList.contains('class2')).toEqual(false);
|
||||
expect(body.classList.contains('class3')).toEqual(false);
|
||||
});
|
||||
|
||||
it('Should add/remove classes by chaining methods', function() {
|
||||
ionicBody.addClass('class1').addClass('class2').addClass('class3');
|
||||
expect(body.classList.contains('class1')).toEqual(true);
|
||||
expect(body.classList.contains('class2')).toEqual(true);
|
||||
expect(body.classList.contains('class3')).toEqual(true);
|
||||
ionicBody.removeClass('class1').removeClass('class2').removeClass('class3');
|
||||
expect(body.classList.contains('class1')).toEqual(false);
|
||||
expect(body.classList.contains('class2')).toEqual(false);
|
||||
expect(body.classList.contains('class3')).toEqual(false);
|
||||
});
|
||||
|
||||
it('Should add/false one class using addTo and boolean first arg', function() {
|
||||
ionicBody.enableClass(true, 'class1');
|
||||
expect(body.classList.contains('class1')).toEqual(true);
|
||||
ionicBody.enableClass(false, 'class1');
|
||||
expect(body.classList.contains('class1')).toEqual(false);
|
||||
});
|
||||
|
||||
it('Should add/false multiple classes using addTo and boolean first arg', function() {
|
||||
ionicBody.enableClass(true, 'class1', 'class2', 'class3');
|
||||
expect(body.classList.contains('class1')).toEqual(true);
|
||||
expect(body.classList.contains('class2')).toEqual(true);
|
||||
expect(body.classList.contains('class3')).toEqual(true);
|
||||
ionicBody.enableClass(false, 'class1', 'class2', 'class3');
|
||||
expect(body.classList.contains('class1')).toEqual(false);
|
||||
expect(body.classList.contains('class2')).toEqual(false);
|
||||
expect(body.classList.contains('class3')).toEqual(false);
|
||||
});
|
||||
|
||||
it('Should append a jqLite element to the body', function() {
|
||||
var jqLiteElement = angular.element('<div>jqLite</div>');
|
||||
ionicBody.append(jqLiteElement);
|
||||
expect(body.lastChild.outerHTML).toEqual('<div>jqLite</div>');
|
||||
});
|
||||
|
||||
it('Should append a DOM element to the body', function() {
|
||||
var domElement = document.createElement('span');
|
||||
domElement.innerText = 'domElement'
|
||||
ionicBody.append(domElement);
|
||||
expect(body.lastChild.outerHTML).toEqual('<span>domElement</span>');
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user