From 9b2f69fd600f5dc610e23160e72928fbf9c36310 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 12 Dec 2013 10:46:04 -0600 Subject: [PATCH 1/2] list updates --- dist/css/ionic.css | 28 +- dist/js/ionic-angular.js | 327 +++++++------ js/ext/angular/src/directive/ionicList.js | 327 +++++++------ .../angular/test/directive/ionicList.unit.js | 436 ++++++++++++++---- js/ext/angular/test/list.html | 276 ++++++----- js/ext/angular/test/pullToRefresh.html | 134 ++++++ scss/_button.scss | 1 - scss/_items.scss | 24 +- scss/_list.scss | 11 +- 9 files changed, 1071 insertions(+), 493 deletions(-) create mode 100644 js/ext/angular/test/pullToRefresh.html diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 4012a3d141..9f093c99b5 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -4152,7 +4152,7 @@ button.item-button-right:after { line-height: 100%; } .item-edit .button { height: 100%; } - .item-edit .button .icon, .item-edit .button i { + .item-edit .button.icon { display: -webkit-box; display: -webkit-flex; display: -moz-box; @@ -4166,6 +4166,7 @@ button.item-button-right:after { align-items: center; position: absolute; top: 0; + left: 0; height: 100%; color: #ef4e3a; font-size: 24px; } @@ -4193,12 +4194,13 @@ button.item-button-right:after { top: 0; right: 0; z-index: 0; - height: 100%; } + width: 50px; + height: 100%; + background: inherit; } .item-drag .button { height: 100%; - border: none; - border-radius: 0; } - .item-drag .button .icon, .item-drag .button i { + min-width: 42px; } + .item-drag .button.icon:before { display: -webkit-box; display: -webkit-flex; display: -moz-box; @@ -4213,7 +4215,7 @@ button.item-button-right:after { position: absolute; top: 0; height: 100%; - font-size: 24px; } + font-size: 32px; } /** * The hidden right-side buttons that can be exposed under a list item @@ -4223,13 +4225,16 @@ button.item-button-right:after { position: absolute; top: 0; right: 0; - z-index: 1; + z-index: 0; height: 100%; } .item-options .button { height: 100%; border: none; border-radius: 0; } +.item-options-hide .item-options { + display: none; } + /** * Lists * -------------------------------------------------- @@ -4243,12 +4248,16 @@ button.item-button-right:after { /** * List editing styles. These trigger when the entire list goes into - * "edit mode" + * "edit mode" or reordering list items */ .list-editing .item-content { - margin-right: 50px; margin-left: 50px; } +.list-reordering .item-content { + margin-right: 50px; } +.list-reordering .item-drag { + z-index: 1; } + /** * List Header * -------------------------------------------------- @@ -5380,7 +5389,6 @@ input[type="range"] { padding: 0 6px; min-width: initial; border-color: transparent; - background: none; background: none; } .button-icon.button:active, .button-icon.button.active { border-color: transparent; diff --git a/dist/js/ionic-angular.js b/dist/js/ionic-angular.js index f1a3f5157a..7390fb1e4d 100644 --- a/dist/js/ionic-angular.js +++ b/dist/js/ionic-angular.js @@ -803,191 +803,222 @@ angular.module('ionic.ui.content', []) angular.module('ionic.ui.list', ['ngAnimate']) -.directive('linkItem', ['$timeout', function($timeout) { +.directive('item', ['$timeout', function($timeout) { return { restrict: 'E', - require: ['?^list'], + require: '?^list', replace: true, transclude: true, + scope: { item: '=', - onSelect: '&', - onDelete: '&', + itemType: '@', canDelete: '@', canReorder: '@', canSwipe: '@', - buttons: '=', - type: '@', + onSelect: '&', + onDelete: '&', + optionButtons: '&', + deleteIcon: '@', + reorderIcon: '@' + }, + + template: '
\ +
\ + \ +
\ +
\ +
\ + \ +
\ +
\ + \ +
\ +
', + + link: function($scope, $element, $attr, list) { + if(!list) return; + + var $parentScope = list.scope; + var $parentAttrs = list.attrs; + + // Set this item's class, first from the item directive attr, and then the list attr if item not set + $scope.itemClass = $scope.itemType || $parentScope.itemType; + + // Decide if this item can do stuff, and follow a certain priority + // depending on where the value comes from + if(($attr.canDelete ? $scope.canDelete : $parentScope.canDelete) !== "false") { + if($attr.onDelete || $parentAttrs.onDelete) { + + // only assign this method when we need to + // and use its existence to decide if the delete should show or not + $scope.deleteClick = function() { + if($attr.onDelete) { + // this item has an on-delete attribute + $scope.onDelete($scope.item); + } else if($parentAttrs.onDelete) { + // run the parent list's onDelete method + // if it doesn't exist nothing will happen + $parentScope.onDelete($scope.item); + } + }; + + // Set which icons to use for deleting + $scope.deleteIconClass = $scope.deleteIcon || $parentScope.deleteIcon || 'ion-minus-circled'; + } + } + + if($attr.onSelect || $parentAttrs.onSelect) { + // only assign this method when we need to + $scope.selectClick = function() { + if($attr.onSelect) { + // this item has an on-delete attribute + $scope.onSelect($scope.item); + } else if($parentAttrs.onSelect) { + // run the parent list's onDelete method + // if it doesn't exist nothing will happen + $parentScope.onSelect($scope.item); + } + }; + } + + // set the reorder Icon Class only if the item or list set can-reorder="true" + if(($attr.canReorder ? $scope.canReorder : $parentScope.canReorder) === "true") { + $scope.reorderIconClass = $scope.reorderIcon || $parentScope.reorderIcon || 'ion-navicon'; + } + + // Set the option buttons which can be revealed by swiping to the left + // if canSwipe was set to false don't even bother + if(($attr.canSwipe ? $scope.canSwipe : $parentScope.canSwipe) !== "false") { + $scope.itemOptionButtons = $scope.optionButtons(); + if(typeof $scope.itemOptionButtons === "undefined") { + $scope.itemOptionButtons = $parentScope.optionButtons(); + } + } + + } + }; +}]) + +.directive('linkItem', [function() { + return { + restrict: 'E', + require: '?^list', + replace: true, + transclude: true, + + scope: { + item: '=', + itemType: '@', + canDelete: '@', + canReorder: '@', + canSwipe: '@', + onSelect: '&', + onDelete: '&', + optionButtons: '&', + deleteIcon: '@', + reorderIcon: '@', href: '@' }, - template: '\ -
\ - \ + + template: '\ +
\ + \
\ -
\ +
\ +
\ + \
\ -
\ - \ -
\ -
\ - \ +
\ + \
\
', link: function($scope, $element, $attr, list) { - // Grab the parent list controller - if(list[0]) { - list = list[0]; - } else if(list[1]) { - list = list[1]; - } + if(!list) return; + + var $parentScope = list.scope; + var $parentAttrs = list.attrs; $attr.$observe('href', function(value) { - $scope.href = value; + if(value) $scope.href = value.trim(); }); - // Add the list item type class - $element.addClass($attr.type || 'item-complex'); + // Set this item's class, first from the item directive attr, and then the list attr if item not set + $scope.itemClass = $scope.itemType || $parentScope.itemType; - if($attr.type !== 'item-complex') { - $scope.canSwipe = false; + // Decide if this item can do stuff, and follow a certain priority + // depending on where the value comes from + if(($attr.canDelete ? $scope.canDelete : $parentScope.canDelete) !== "false") { + if($attr.onDelete || $parentAttrs.onDelete) { + + // only assign this method when we need to + // and use its existence to decide if the delete should show or not + $scope.deleteClick = function() { + if($attr.onDelete) { + // this item has an on-delete attribute + $scope.onDelete($scope.item); + } else if($parentAttrs.onDelete) { + // run the parent list's onDelete method + // if it doesn't exist nothing will happen + $parentScope.onDelete($scope.item); + } + }; + + // Set which icons to use for deleting + $scope.deleteIconClass = $scope.deleteIcon || $parentScope.deleteIcon || 'ion-minus-circled'; + } } - $scope.isEditing = false; - $scope.deleteIcon = list.scope.deleteIcon; - $scope.reorderIcon = list.scope.reorderIcon; - $scope.showOptions = true; + // set the reorder Icon Class only if the item or list set can-reorder="true" + if(($attr.canReorder ? $scope.canReorder : $parentScope.canReorder) === "true") { + $scope.reorderIconClass = $scope.reorderIcon || $parentScope.reorderIcon || 'ion-navicon'; + } - $scope.buttonClicked = function(button) { - button.onButtonClicked && button.onButtonClicked($scope.item, button); - }; - - var deregisterListWatch = list.scope.$watch('isEditing', function(v) { - $scope.isEditing = v; - - // Add a delay before we allow the options layer to show, to avoid any odd - // animation issues - if(!v) { - $timeout(function() { - $scope.showOptions = true; - }, 200); - } else { - $scope.showOptions = false; + // Set the option buttons which can be revealed by swiping to the left + // if canSwipe was set to false don't even bother + if(($attr.canSwipe ? $scope.canSwipe : $parentScope.canSwipe) !== "false") { + $scope.itemOptionButtons = $scope.optionButtons(); + if(typeof $scope.itemOptionButtons === "undefined") { + $scope.itemOptionButtons = $parentScope.optionButtons(); } - }); + } - $scope.$on('$destroy', function () { - deregisterListWatch(); - }); } }; }]) -.directive('item', ['$timeout', function($timeout) { +.directive('list', ['$timeout', function($timeout) { return { restrict: 'E', - require: ['?^list'], replace: true, transclude: true, + scope: { - item: '=', - onSelect: '&', - onDelete: '&', + itemType: '@', canDelete: '@', canReorder: '@', canSwipe: '@', - buttons: '=', - type: '@', - }, - template: '
  • \ -
    \ - \ -
    \ -
    \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
  • ', - - link: function($scope, $element, $attr, list) { - // Grab the parent list controller - if(list[0]) { - list = list[0]; - } else if(list[1]) { - list = list[1]; - } - - // Add the list item type class - $element.addClass($attr.type || 'item-complex'); - - if($attr.type !== 'item-complex') { - $scope.canSwipe = false; - } - - $scope.isEditing = false; - $scope.deleteIcon = list.scope.deleteIcon; - $scope.reorderIcon = list.scope.reorderIcon; - $scope.showOptions = true; - - $scope.buttonClicked = function(button) { - button.onButtonClicked && button.onButtonClicked($scope.item, button); - }; - - var deregisterListWatch = list.scope.$watch('isEditing', function(v) { - $scope.isEditing = v; - - // Add a delay before we allow the options layer to show, to avoid any odd - // animation issues - if(!v) { - $timeout(function() { - $scope.showOptions = true; - }, 200); - } else { - $scope.showOptions = false; - } - }); - - $scope.$on('$destroy', function () { - deregisterListWatch(); - }); - } - }; -}]) - -.directive('list', function() { - return { - restrict: 'E', - replace: true, - transclude: true, - - scope: { - isEditing: '=', - deleteIcon: '@', - reorderIcon: '@', + showDelete: '=', + showReorder: '=', hasPullToRefresh: '@', onRefresh: '&', onRefreshOpening: '&', - onReorder: '&', - refreshComplete: '=' + refreshComplete: '=', + onSelect: '&', + onDelete: '&', + optionButtons: '&', + deleteIcon: '@', + reorderIcon: '@' }, - controller: function($scope) { - var _this = this; + template: '
    ', + controller: function($scope, $attrs) { this.scope = $scope; - - $scope.$watch('isEditing', function(v) { - _this.isEditing = true; - }); + this.attrs = $attrs; }, - template: '
      \ -
    ', - link: function($scope, $element, $attr) { var lv = new ionic.views.ListView({ el: $element[0], @@ -1002,7 +1033,6 @@ angular.module('ionic.ui.list', ['ngAnimate']) $scope.$parent.$broadcast('scroll.onRefreshOpening', amt); }, onReorder: function(el, oldIndex, newIndex) { - console.log('Moved', el,oldIndex,newIndex); $scope.$apply(function() { $scope.onReorder({el: el, start: oldIndex, end: newIndex}); }); @@ -1019,11 +1049,28 @@ angular.module('ionic.ui.list', ['ngAnimate']) } if($attr.animation) { - $element.addClass($attr.animation); + $element[0].classList.add($attr.animation); } + + var destroyShowReorderWatch = $scope.$watch('showReorder', function(val) { + if(val) { + $element[0].classList.add('item-options-hide'); + } else if(val === false) { + // false checking is because it could be undefined + // if its undefined then we don't care to do anything + $timeout(function(){ + $element[0].classList.remove('item-options-hide'); + }, 250); + } + }); + + $scope.$on('$destroy', function () { + destroyShowReorderWatch(); + }); + } }; -}); +}]); })(); ; diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js index ae55443690..efabb83cd8 100644 --- a/js/ext/angular/src/directive/ionicList.js +++ b/js/ext/angular/src/directive/ionicList.js @@ -3,191 +3,222 @@ angular.module('ionic.ui.list', ['ngAnimate']) -.directive('linkItem', ['$timeout', function($timeout) { +.directive('item', ['$timeout', function($timeout) { return { restrict: 'E', - require: ['?^list'], + require: '?^list', replace: true, transclude: true, + scope: { item: '=', - onSelect: '&', - onDelete: '&', + itemType: '@', canDelete: '@', canReorder: '@', canSwipe: '@', - buttons: '=', - type: '@', + onSelect: '&', + onDelete: '&', + optionButtons: '&', + deleteIcon: '@', + reorderIcon: '@' + }, + + template: '
    \ +
    \ + \ +
    \ +
    \ +
    \ + \ +
    \ +
    \ + \ +
    \ +
    ', + + link: function($scope, $element, $attr, list) { + if(!list) return; + + var $parentScope = list.scope; + var $parentAttrs = list.attrs; + + // Set this item's class, first from the item directive attr, and then the list attr if item not set + $scope.itemClass = $scope.itemType || $parentScope.itemType; + + // Decide if this item can do stuff, and follow a certain priority + // depending on where the value comes from + if(($attr.canDelete ? $scope.canDelete : $parentScope.canDelete) !== "false") { + if($attr.onDelete || $parentAttrs.onDelete) { + + // only assign this method when we need to + // and use its existence to decide if the delete should show or not + $scope.deleteClick = function() { + if($attr.onDelete) { + // this item has an on-delete attribute + $scope.onDelete($scope.item); + } else if($parentAttrs.onDelete) { + // run the parent list's onDelete method + // if it doesn't exist nothing will happen + $parentScope.onDelete($scope.item); + } + }; + + // Set which icons to use for deleting + $scope.deleteIconClass = $scope.deleteIcon || $parentScope.deleteIcon || 'ion-minus-circled'; + } + } + + if($attr.onSelect || $parentAttrs.onSelect) { + // only assign this method when we need to + $scope.selectClick = function() { + if($attr.onSelect) { + // this item has an on-delete attribute + $scope.onSelect($scope.item); + } else if($parentAttrs.onSelect) { + // run the parent list's onDelete method + // if it doesn't exist nothing will happen + $parentScope.onSelect($scope.item); + } + }; + } + + // set the reorder Icon Class only if the item or list set can-reorder="true" + if(($attr.canReorder ? $scope.canReorder : $parentScope.canReorder) === "true") { + $scope.reorderIconClass = $scope.reorderIcon || $parentScope.reorderIcon || 'ion-navicon'; + } + + // Set the option buttons which can be revealed by swiping to the left + // if canSwipe was set to false don't even bother + if(($attr.canSwipe ? $scope.canSwipe : $parentScope.canSwipe) !== "false") { + $scope.itemOptionButtons = $scope.optionButtons(); + if(typeof $scope.itemOptionButtons === "undefined") { + $scope.itemOptionButtons = $parentScope.optionButtons(); + } + } + + } + }; +}]) + +.directive('linkItem', [function() { + return { + restrict: 'E', + require: '?^list', + replace: true, + transclude: true, + + scope: { + item: '=', + itemType: '@', + canDelete: '@', + canReorder: '@', + canSwipe: '@', + onSelect: '&', + onDelete: '&', + optionButtons: '&', + deleteIcon: '@', + reorderIcon: '@', href: '@' }, - template: '\ -
    \ - \ + + template: '\ +
    \ + \
    \ -
    \ +
    \ +
    \ + \
    \ -
    \ - \ -
    \ -
    \ - \ +
    \ + \
    \
    ', link: function($scope, $element, $attr, list) { - // Grab the parent list controller - if(list[0]) { - list = list[0]; - } else if(list[1]) { - list = list[1]; - } + if(!list) return; + + var $parentScope = list.scope; + var $parentAttrs = list.attrs; $attr.$observe('href', function(value) { - $scope.href = value; + if(value) $scope.href = value.trim(); }); - // Add the list item type class - $element.addClass($attr.type || 'item-complex'); + // Set this item's class, first from the item directive attr, and then the list attr if item not set + $scope.itemClass = $scope.itemType || $parentScope.itemType; - if($attr.type !== 'item-complex') { - $scope.canSwipe = false; + // Decide if this item can do stuff, and follow a certain priority + // depending on where the value comes from + if(($attr.canDelete ? $scope.canDelete : $parentScope.canDelete) !== "false") { + if($attr.onDelete || $parentAttrs.onDelete) { + + // only assign this method when we need to + // and use its existence to decide if the delete should show or not + $scope.deleteClick = function() { + if($attr.onDelete) { + // this item has an on-delete attribute + $scope.onDelete($scope.item); + } else if($parentAttrs.onDelete) { + // run the parent list's onDelete method + // if it doesn't exist nothing will happen + $parentScope.onDelete($scope.item); + } + }; + + // Set which icons to use for deleting + $scope.deleteIconClass = $scope.deleteIcon || $parentScope.deleteIcon || 'ion-minus-circled'; + } } - $scope.isEditing = false; - $scope.deleteIcon = list.scope.deleteIcon; - $scope.reorderIcon = list.scope.reorderIcon; - $scope.showOptions = true; + // set the reorder Icon Class only if the item or list set can-reorder="true" + if(($attr.canReorder ? $scope.canReorder : $parentScope.canReorder) === "true") { + $scope.reorderIconClass = $scope.reorderIcon || $parentScope.reorderIcon || 'ion-navicon'; + } - $scope.buttonClicked = function(button) { - button.onButtonClicked && button.onButtonClicked($scope.item, button); - }; - - var deregisterListWatch = list.scope.$watch('isEditing', function(v) { - $scope.isEditing = v; - - // Add a delay before we allow the options layer to show, to avoid any odd - // animation issues - if(!v) { - $timeout(function() { - $scope.showOptions = true; - }, 200); - } else { - $scope.showOptions = false; + // Set the option buttons which can be revealed by swiping to the left + // if canSwipe was set to false don't even bother + if(($attr.canSwipe ? $scope.canSwipe : $parentScope.canSwipe) !== "false") { + $scope.itemOptionButtons = $scope.optionButtons(); + if(typeof $scope.itemOptionButtons === "undefined") { + $scope.itemOptionButtons = $parentScope.optionButtons(); } - }); + } - $scope.$on('$destroy', function () { - deregisterListWatch(); - }); } }; }]) -.directive('item', ['$timeout', function($timeout) { +.directive('list', ['$timeout', function($timeout) { return { restrict: 'E', - require: ['?^list'], replace: true, transclude: true, + scope: { - item: '=', - onSelect: '&', - onDelete: '&', + itemType: '@', canDelete: '@', canReorder: '@', canSwipe: '@', - buttons: '=', - type: '@', - }, - template: '
  • \ -
    \ - \ -
    \ -
    \ -
    \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
  • ', - - link: function($scope, $element, $attr, list) { - // Grab the parent list controller - if(list[0]) { - list = list[0]; - } else if(list[1]) { - list = list[1]; - } - - // Add the list item type class - $element.addClass($attr.type || 'item-complex'); - - if($attr.type !== 'item-complex') { - $scope.canSwipe = false; - } - - $scope.isEditing = false; - $scope.deleteIcon = list.scope.deleteIcon; - $scope.reorderIcon = list.scope.reorderIcon; - $scope.showOptions = true; - - $scope.buttonClicked = function(button) { - button.onButtonClicked && button.onButtonClicked($scope.item, button); - }; - - var deregisterListWatch = list.scope.$watch('isEditing', function(v) { - $scope.isEditing = v; - - // Add a delay before we allow the options layer to show, to avoid any odd - // animation issues - if(!v) { - $timeout(function() { - $scope.showOptions = true; - }, 200); - } else { - $scope.showOptions = false; - } - }); - - $scope.$on('$destroy', function () { - deregisterListWatch(); - }); - } - }; -}]) - -.directive('list', function() { - return { - restrict: 'E', - replace: true, - transclude: true, - - scope: { - isEditing: '=', - deleteIcon: '@', - reorderIcon: '@', + showDelete: '=', + showReorder: '=', hasPullToRefresh: '@', onRefresh: '&', onRefreshOpening: '&', - onReorder: '&', - refreshComplete: '=' + refreshComplete: '=', + onSelect: '&', + onDelete: '&', + optionButtons: '&', + deleteIcon: '@', + reorderIcon: '@' }, - controller: function($scope) { - var _this = this; + template: '
    ', + controller: function($scope, $attrs) { this.scope = $scope; - - $scope.$watch('isEditing', function(v) { - _this.isEditing = true; - }); + this.attrs = $attrs; }, - template: '
      \ -
    ', - link: function($scope, $element, $attr) { var lv = new ionic.views.ListView({ el: $element[0], @@ -202,7 +233,6 @@ angular.module('ionic.ui.list', ['ngAnimate']) $scope.$parent.$broadcast('scroll.onRefreshOpening', amt); }, onReorder: function(el, oldIndex, newIndex) { - console.log('Moved', el,oldIndex,newIndex); $scope.$apply(function() { $scope.onReorder({el: el, start: oldIndex, end: newIndex}); }); @@ -219,10 +249,27 @@ angular.module('ionic.ui.list', ['ngAnimate']) } if($attr.animation) { - $element.addClass($attr.animation); + $element[0].classList.add($attr.animation); } + + var destroyShowReorderWatch = $scope.$watch('showReorder', function(val) { + if(val) { + $element[0].classList.add('item-options-hide'); + } else if(val === false) { + // false checking is because it could be undefined + // if its undefined then we don't care to do anything + $timeout(function(){ + $element[0].classList.remove('item-options-hide'); + }, 250); + } + }); + + $scope.$on('$destroy', function () { + destroyShowReorderWatch(); + }); + } }; -}); +}]); })(); diff --git a/js/ext/angular/test/directive/ionicList.unit.js b/js/ext/angular/test/directive/ionicList.unit.js index 08525c39e1..ed0a644e91 100644 --- a/js/ext/angular/test/directive/ionicList.unit.js +++ b/js/ext/angular/test/directive/ionicList.unit.js @@ -1,7 +1,7 @@ 'use strict'; describe('Ionic List', function() { - var compile, scope; + var compile, scope, listElement, listCtrl; beforeEach(module('ionic.ui.list')); @@ -10,116 +10,390 @@ describe('Ionic List', function() { scope = $rootScope; })); + beforeEach(inject(function (_$compile_, _$rootScope_) { + scope.showDelete = false; + scope.showReorder = false; + + listElement = angular.element(''); + listElement = _$compile_(listElement)(scope); + + listCtrl = listElement.controller('list'); + + scope.$digest(); + })); + it('Should init', function() { var element = compile('' + - '' + - '' + + '' + + '' + '')(scope); expect(element.children().length).toBe(2); }); -}); -describe('Ionic Link Item Directive', function () { - var $rootScope, element, listCtrl, options, scope; + it('Should add animation class', function() { + var element = compile('')(scope); + expect(element.hasClass('my-animation')).toBe(true); + }); - beforeEach(module('ionic.ui.list')); + it('Should add list-editing class', function() { + expect(listElement.hasClass('list-editing')).toBe(false); + scope.showDelete = true; + scope.$digest(); + expect(listElement.hasClass('list-editing')).toBe(true); + }); - beforeEach(inject(function (_$compile_, _$rootScope_) { - $rootScope = _$rootScope_; - $rootScope.isEditing = false; + it('Should add list-reordering class', function() { + expect(listElement.hasClass('list-reordering')).toBe(false); + scope.showReorder = true; + scope.$digest(); + expect(listElement.hasClass('list-reordering')).toBe(true); + }); - var list = angular.element(''); - list = _$compile_(list)($rootScope); - - listCtrl = list.controller('list'); - - $rootScope.buttons = []; - - element = angular.element('').appendTo(list); - element = _$compile_(element)($rootScope); - - $rootScope.$digest(); - scope = element.isolateScope(); - })); - - it('Should show options when the list is not in edit mode', inject(function ($timeout) { - scope.canSwipe = true; - $rootScope.$digest(); - $timeout.flush(); - - expect(scope.isEditing).toBe(false); - expect(element.find('.item-options').length).toBe(1); - })); - - it('Should hide options when the list is in edit mode', inject(function ($timeout) { - scope.canSwipe = true; - $rootScope.isEditing = true; - $rootScope.$digest(); - $timeout.flush(); - - expect(scope.isEditing).toBe(true); - expect(element.find('.item-options').length).toBe(0); - })); - - it('Should deregister watcher when scope destroyed', inject(function ($timeout) { - $rootScope.isEditing = true; - scope.$destroy(); - $rootScope.$digest(); - $timeout.flush(); - - expect(scope.isEditing).toBe(false); - })); + it('Should add item-options-hide class', function() { + expect(listElement.hasClass('item-options-hide')).toBe(false); + scope.showReorder = true; + scope.$digest(); + expect(listElement.hasClass('item-options-hide')).toBe(true); + }); }); describe('Ionic Item Directive', function () { - var $rootScope, element, listCtrl, options, scope; + var $rootScope, $compile, listCtrl, options, listScope, itemScope, listElement, itemElement; beforeEach(module('ionic.ui.list')); beforeEach(inject(function (_$compile_, _$rootScope_) { $rootScope = _$rootScope_; - $rootScope.isEditing = false; + $compile = _$compile_; + $rootScope.showDelete = false; - var list = angular.element(''); - list = _$compile_(list)($rootScope); + listElement = angular.element(''); + listElement = _$compile_(listElement)($rootScope); + listScope = listElement.isolateScope(); - listCtrl = list.controller('list'); + listCtrl = listElement.controller('list'); - $rootScope.buttons = []; - - element = angular.element('').appendTo(list); - element = _$compile_(element)($rootScope); + itemElement = angular.element('').appendTo(listElement); + itemElement = _$compile_(itemElement)($rootScope); $rootScope.$digest(); - scope = element.isolateScope(); + itemScope = itemElement.isolateScope(); })); - it('Should show options when the list is not in edit mode', inject(function ($timeout) { - scope.canSwipe = true; + it('Should set item type from item attribute', inject(function ($timeout) { + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); $rootScope.$digest(); - $timeout.flush(); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemClass).toBe(undefined); - expect(scope.isEditing).toBe(false); - expect(element.find('.item-options').length).toBe(1); - })); - - it('Should hide options when the list is in edit mode', inject(function ($timeout) { - scope.canSwipe = true; - $rootScope.isEditing = true; + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); $rootScope.$digest(); - $timeout.flush(); - - expect(scope.isEditing).toBe(true); - expect(element.find('.item-options').length).toBe(0); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemClass).toBe("item-type-test"); + expect(itemElement.hasClass('item-type-test')).toBe(true); })); - it('Should deregister watcher when scope destroyed', inject(function ($timeout) { - $rootScope.isEditing = true; - scope.$destroy(); + it('Should set item type from list attribute', inject(function ($timeout) { + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); $rootScope.$digest(); - $timeout.flush(); - - expect(scope.isEditing).toBe(false); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemClass).toBe('list-item-type-test'); + expect(itemElement.hasClass('list-item-type-test')).toBe(true); })); + + it('Should item option buttons from item attribute', inject(function ($timeout) { + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.optionButtons()).toBe(undefined); + expect(itemScope.itemOptionButtons).toBe(undefined); + expect(itemElement.find('.item-options').length).toBe(0); + + $rootScope.buttons = [ + { text: 'Edit' }, { text: 'Cancel' } + ]; + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.optionButtons().length).toBe(2); + expect(itemScope.itemOptionButtons.length).toBe(2); + expect(itemElement.find('.item-options').find('button').length).toBe(2); + })); + + it('Should item option buttons from list attribute', inject(function ($timeout) { + $rootScope.buttons = [ + { text: 'Edit' }, { text: 'Cancel' } + ]; + + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + expect(listScope.optionButtons().length ).toBe(2); + + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + + expect(itemScope.optionButtons()).toBe(undefined); + expect(itemScope.itemOptionButtons.length).toBe(2); + expect(itemElement.find('.item-options').find('button').length).toBe(2); + })); + + it('Should have no option buttons by disabling item canSwipe', inject(function ($timeout) { + $rootScope.buttons = [ + { text: 'Edit' }, { text: 'Cancel' } + ]; + + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + + expect(itemScope.itemOptionButtons).toBe(undefined); + })); + + it('Should have no option buttons by disabling list canSwipe', inject(function ($timeout) { + $rootScope.buttons = [ + { text: 'Edit' }, { text: 'Cancel' } + ]; + + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + expect(listScope.optionButtons().length ).toBe(2); + + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + + expect(itemScope.itemOptionButtons).toBe(undefined); + })); + + it('Should hide delete w/ item can-delete attribute true but no list or item onDelete', inject(function ($timeout) { + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.deleteClick).toBe(undefined); + expect(itemElement.find('.item-edit').length).toBe(0); + })); + + it('Should hide delete w/ item can-delete attribute false but with item onDelete', inject(function ($timeout) { + $rootScope.onDelete = function() {}; + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.deleteClick).toBe(undefined); + expect(itemElement.find('.item-edit').length).toBe(0); + })); + + it('Should show delete w/ no item can-delete attribute but with item onDelete', inject(function ($timeout) { + $rootScope.onDelete = function() {}; + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.deleteClick).not.toBe(undefined); + expect(itemElement.find('.item-edit').length).toBe(1); + expect(itemScope.deleteIconClass).toBe("test-icon"); + })); + + it('Should hide delete w/ list can-delete attribute true but no list or item onDelete', inject(function ($timeout) { + $rootScope.onDelete = function() {}; + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.deleteClick).toBe(undefined); + expect(itemElement.find('.item-edit').length).toBe(0); + })); + + it('Should hide delete w/ list can-delete attribute false but with list onDelete', inject(function ($timeout) { + $rootScope.onDelete = function() {}; + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.deleteClick).toBe(undefined); + expect(itemElement.find('.item-edit').length).toBe(0); + })); + + it('Should hide delete w/ list can-delete attribute false but with item onDelete', inject(function ($timeout) { + $rootScope.onDelete = function() {}; + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.deleteClick).toBe(undefined); + expect(itemElement.find('.item-edit').length).toBe(0); + })); + + it('Should show delete w/ no can-delete attribute but with list onDelete', inject(function ($timeout) { + $rootScope.onDelete = function() {}; + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.deleteClick).not.toBe(undefined); + expect(itemElement.find('.item-edit').length).toBe(1); + + expect(itemScope.deleteIconClass).toBe("test-icon"); + })); + + it('Should not be able to reorder cuz no item or list can-reorder attribute true', inject(function ($timeout) { + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.reorderIconClass).toBe(undefined); + expect(itemElement.find('.item-drag').length).toBe(0); + })); + + it('Should be able to reorder cuz item can-reorder attribute true', inject(function ($timeout) { + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.reorderIconClass).toBe('test-icon'); + expect(itemElement.find('.item-drag').length).toBe(1); + })); + + it('Should be able to reorder cuz list can-reorder attribute true', inject(function ($timeout) { + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.reorderIconClass).toBe('test-icon'); + expect(itemElement.find('.item-drag').length).toBe(1); + })); + + it('Should not have options cuz no optionButtons', inject(function ($timeout) { + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemOptionButtons).toBe(undefined); + expect(itemElement.find('.item-options').length).toBe(0); + })); + + it('Should be able to reorder cuz list can-reorder attribute false and item can-reorder true', inject(function ($timeout) { + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.reorderIconClass).toBe('ion-navicon'); + expect(itemElement.find('.item-drag').length).toBe(1); + })); + + it('Should not have options cuz item can-swipe false', inject(function ($timeout) { + $rootScope.optionButtons = [{text:'BUTTON'}]; + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemOptionButtons).toBe(undefined); + expect(itemElement.find('.item-options').length).toBe(0); + })); + + it('Should not have options cuz list can-swipe false', inject(function ($timeout) { + $rootScope.optionButtons = [{text:'BUTTON'}]; + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemOptionButtons).toBe(undefined); + expect(itemElement.find('.item-options').length).toBe(0); + })); + + it('Should have options cuz item option-buttons and no can-swipe false', inject(function ($timeout) { + $rootScope.optionButtons = [{text:'BUTTON'}]; + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemOptionButtons.length).toBe(1); + expect(itemElement.find('.item-options').find('button').length).toBe(1); + })); + + it('Should have options cuz list option-buttons and no can-swipe false', inject(function ($timeout) { + $rootScope.optionButtons = [{text:'BUTTON'}]; + listElement = angular.element(''); + listElement = $compile(listElement)($rootScope); + listScope = listElement.isolateScope(); + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.itemOptionButtons.length).toBe(1); + expect(itemElement.find('.item-options').find('button').length).toBe(1); + })); + +}); + +describe('Ionic Link Item Directive', function () { + var $rootScope, $compile, listCtrl, options, listScope, itemScope, listElement, itemElement; + + beforeEach(module('ionic.ui.list')); + + beforeEach(inject(function (_$compile_, _$rootScope_) { + $rootScope = _$rootScope_; + $compile = _$compile_; + + listElement = angular.element(''); + listElement = _$compile_(listElement)($rootScope); + listScope = listElement.isolateScope(); + + listCtrl = listElement.controller('list'); + + itemElement = angular.element('').appendTo(listElement); + itemElement = _$compile_(itemElement)($rootScope); + + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + })); + + it('Should set link-item href', inject(function ($timeout) { + itemElement = angular.element('').appendTo(listElement); + itemElement = $compile(itemElement)($rootScope); + $rootScope.$digest(); + itemScope = itemElement.isolateScope(); + expect(itemScope.href).toBe("http://drifty.com/"); + expect(itemElement.attr('href')).toBe("http://drifty.com/"); + })); + }); diff --git a/js/ext/angular/test/list.html b/js/ext/angular/test/list.html index 688b255332..889854f68f 100644 --- a/js/ext/angular/test/list.html +++ b/js/ext/angular/test/list.html @@ -7,86 +7,128 @@ - + -
    - - - +
    +
    + +
    +

    List Tests

    +
    + +
    +
    - + + + + + + + Repeat Item: {{ item.text }} + + + +
    + Me Divider, just plain ol' HTML nested in the list directive. +
    + + + + + Individual item directive, but can't do much. Overrides list attributes with its own left and right icons. + + + + + - - {{item.text}} - - -
    - -
    -
    + option-buttons="optionButtons2"> + + Individual item directive and overrides list attrs with item attributes +
    + + +
    + Below is NOT using the item directive, but just nested HTML +
    + + +
    + +

    Nic Cage

    +

    I am not a demon. I am a lizard, a shark, a heat-seeking panther. I want to be Bob Denver on acid playing the accordion.

    +
    + + + + {{ item.text }} + + +
    + + + @@ -97,58 +139,70 @@ diff --git a/js/ext/angular/test/pullToRefresh.html b/js/ext/angular/test/pullToRefresh.html new file mode 100644 index 0000000000..40f733e201 --- /dev/null +++ b/js/ext/angular/test/pullToRefresh.html @@ -0,0 +1,134 @@ + + + + Pull To Refresh + + + + + + + + + + + + +
    +
    + +
    +

    Pull To Refresh

    +
    + + + + + + + + + Item: {{ item.text }} + + + + + + +
    + + + + + + + + + + + + diff --git a/scss/_button.scss b/scss/_button.scss index 5712a544b4..5ed1a2f4dd 100644 --- a/scss/_button.scss +++ b/scss/_button.scss @@ -148,7 +148,6 @@ min-width: initial; border-color: transparent; background: none; - background: none; &.button:active, &.button.active { border-color: transparent; diff --git a/scss/_items.scss b/scss/_items.scss index 2b0bacf0db..cabeab5525 100644 --- a/scss/_items.scss +++ b/scss/_items.scss @@ -496,7 +496,7 @@ button.item-button-right:after { } -// Item Animations +// Item Editing // ------------------------------- .item-sliding { @@ -512,12 +512,12 @@ button.item-button-right:after { opacity: 0.7; } + /** * The left-side edit area of a complex list item. This area shows * whe the list item is in edit mode. */ .item-edit { - @include transition(left 0.2s ease-in-out, opacity 0.2s ease-in-out); position: absolute; top: 0; @@ -529,11 +529,13 @@ button.item-button-right:after { .button { height: 100%; - .icon, i { + + &.icon { @include display-flex(); @include align-items(center); position: absolute; top: 0; + left: 0; height: 100%; color: $assertive; font-size: 24px; @@ -565,19 +567,21 @@ button.item-button-right:after { top: 0; right: 0; z-index: 0; + width: 50px; height: 100%; + background: inherit; .button { height: 100%; - border: none; - border-radius: 0; - .icon, i { + min-width: 42px; + + &.icon:before { @include display-flex(); @include align-items(center); position: absolute; top: 0; height: 100%; - font-size: 24px; + font-size: 32px; } } } @@ -589,7 +593,7 @@ button.item-button-right:after { position: absolute; top: 0; right: 0; - z-index: 1; + z-index: 0; height: 100%; .button { @@ -598,3 +602,7 @@ button.item-button-right:after { border-radius: 0; } } + +.item-options-hide .item-options { + display: none; +} diff --git a/scss/_list.scss b/scss/_list.scss index eb61e61db9..6319aeb0b1 100644 --- a/scss/_list.scss +++ b/scss/_list.scss @@ -14,14 +14,21 @@ /** * List editing styles. These trigger when the entire list goes into - * "edit mode" + * "edit mode" or reordering list items */ .list-editing { .item-content { - margin-right: 50px; margin-left: 50px; } } +.list-reordering { + .item-content { + margin-right: 50px; + } + .item-drag { + z-index: 1; + } +} /** From 68433b5573fa8b6b2dfb71648ef40dea4e6973b0 Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Thu, 12 Dec 2013 12:23:33 -0600 Subject: [PATCH 2/2] Fixed reordering for #319 --- dist/js/ionic-angular.js | 1 + dist/js/ionic.js | 27 ++++++++++------------- js/ext/angular/src/directive/ionicList.js | 1 + js/views/listView.js | 27 ++++++++++------------- 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/dist/js/ionic-angular.js b/dist/js/ionic-angular.js index 7390fb1e4d..f8cd0e72d2 100644 --- a/dist/js/ionic-angular.js +++ b/dist/js/ionic-angular.js @@ -1007,6 +1007,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) refreshComplete: '=', onSelect: '&', onDelete: '&', + onReorder: '&', optionButtons: '&', deleteIcon: '@', reorderIcon: '@' diff --git a/dist/js/ionic.js b/dist/js/ionic.js index a5fd4984b8..2982342942 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -4416,9 +4416,12 @@ ionic.views.Scroll = ionic.views.View.inherit({ }, this.el); window.ionic.onGesture('release', function(e) { - _this._handleTouchRelease(e); + _this._handleEndDrag(e); }, this.el); + window.ionic.onGesture('drag', function(e) { + _this._handleDrag(e); + }, this.el); // Start the drag states this._initDrag(); }, @@ -4551,6 +4554,13 @@ ionic.views.Scroll = ionic.views.View.inherit({ return; } + // Cancel touch timeout + clearTimeout(this._touchTimeout); + var items = _this.el.querySelectorAll('.item'); + for(var i = 0, l = items.length; i < l; i++) { + items[i].classList.remove('active'); + } + this._dragOp.end(e, function() { _this._initDrag(); }); @@ -4581,7 +4591,7 @@ ionic.views.Scroll = ionic.views.View.inherit({ return; } - e.preventDefault(); + e.gesture.srcEvent.preventDefault(); this._dragOp.drag(e); }, @@ -4603,19 +4613,6 @@ ionic.views.Scroll = ionic.views.View.inherit({ }, 250); }, - /** - * Handle the release event to remove the active state on an item if necessary. - */ - _handleTouchRelease: function(e) { - var _this = this; - - // Cancel touch timeout - clearTimeout(this._touchTimeout); - var items = _this.el.querySelectorAll('.item'); - for(var i = 0, l = items.length; i < l; i++) { - items[i].classList.remove('active'); - } - } }); })(ionic); diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js index efabb83cd8..31ebccf6ad 100644 --- a/js/ext/angular/src/directive/ionicList.js +++ b/js/ext/angular/src/directive/ionicList.js @@ -207,6 +207,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) refreshComplete: '=', onSelect: '&', onDelete: '&', + onReorder: '&', optionButtons: '&', deleteIcon: '@', reorderIcon: '@' diff --git a/js/views/listView.js b/js/views/listView.js index 6b09c389b0..8a0962a2fc 100644 --- a/js/views/listView.js +++ b/js/views/listView.js @@ -285,9 +285,12 @@ }, this.el); window.ionic.onGesture('release', function(e) { - _this._handleTouchRelease(e); + _this._handleEndDrag(e); }, this.el); + window.ionic.onGesture('drag', function(e) { + _this._handleDrag(e); + }, this.el); // Start the drag states this._initDrag(); }, @@ -420,6 +423,13 @@ return; } + // Cancel touch timeout + clearTimeout(this._touchTimeout); + var items = _this.el.querySelectorAll('.item'); + for(var i = 0, l = items.length; i < l; i++) { + items[i].classList.remove('active'); + } + this._dragOp.end(e, function() { _this._initDrag(); }); @@ -450,7 +460,7 @@ return; } - e.preventDefault(); + e.gesture.srcEvent.preventDefault(); this._dragOp.drag(e); }, @@ -472,19 +482,6 @@ }, 250); }, - /** - * Handle the release event to remove the active state on an item if necessary. - */ - _handleTouchRelease: function(e) { - var _this = this; - - // Cancel touch timeout - clearTimeout(this._touchTimeout); - var items = _this.el.querySelectorAll('.item'); - for(var i = 0, l = items.length; i < l; i++) { - items[i].classList.remove('active'); - } - } }); })(ionic);