From 986dbac8936f7472c1fe7237c02789a5a37dce65 Mon Sep 17 00:00:00 2001 From: Andy Joslin Date: Wed, 2 Apr 2014 15:16:38 -0600 Subject: [PATCH] refactor(ionList): more expressive, declarative (breaking change) Closes #1024. BREAKING CHANGE: ion-list syntax has changed in favor of simplicity & flexibility. Relevant documentation: [ionList](http://ionicframework.com/docs/api/directive/ionList), [ionItem](http://ionicframework.com/docs/api/directive/ionItem), [ionOptionButton](http://ionicframework.com/docs/api/directive/ionOptionButton), [ionReorderButton](http://ionicframework.com/docs/api/directive/ionReorderButton), [ionDeleteButton](http://ionicframework.com/docs/api/directive/ionDeleteButton), [$ionicListDelegate](http://ionicframework.com/docs/api/service/$ionicListDelegate). To migrate, change your code from this: ```html {{item}} ``` To this: ```html {{item}} Hello ``` --- docs/templates/api_menu_version.template.html | 23 + .../src/controller/ionicScrollController.js | 2 +- js/ext/angular/src/directive/ionicList.js | 661 ++++++++++++------ js/ext/angular/test/anchorScroll.html | 1 - .../angular/test/directive/ionicList.unit.js | 613 +++++++--------- js/ext/angular/test/list.html | 96 +-- js/ext/angular/test/sideMenu.html | 7 +- js/ext/angular/test/tabs.html | 17 +- js/ext/angular/test/viewState.html | 3 +- js/views/listView.js | 55 +- scss/_items.scss | 21 +- 11 files changed, 804 insertions(+), 695 deletions(-) diff --git a/docs/templates/api_menu_version.template.html b/docs/templates/api_menu_version.template.html index 67175d2622..a75a48191c 100644 --- a/docs/templates/api_menu_version.template.html +++ b/docs/templates/api_menu_version.template.html @@ -220,6 +220,29 @@ +
  • + + ion-delete-button + +
  • + +
  • + + ion-reorder-button + +
  • + +
  • + + ion-option-button + +
  • + +
  • + + $ionicListDelegate + +
  • diff --git a/js/ext/angular/src/controller/ionicScrollController.js b/js/ext/angular/src/controller/ionicScrollController.js index 947ccededf..39d704c65e 100644 --- a/js/ext/angular/src/controller/ionicScrollController.js +++ b/js/ext/angular/src/controller/ionicScrollController.js @@ -126,7 +126,7 @@ angular.module('ionic.ui.scroll') * *
    * - * {{i}} + * {% raw %}{{i}}{% endraw %} * *
    *
    diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js index 7e13023496..c55fe656be 100644 --- a/js/ext/angular/src/directive/ionicList.js +++ b/js/ext/angular/src/directive/ionicList.js @@ -1,263 +1,504 @@ (function() { -'use strict'; + 'use strict'; -angular.module('ionic.ui.list', ['ngAnimate']) + var TPL_CONTENT_ANCHOR = + ''; + var TPL_CONTENT = + '
    '; + var TPL_DELETE_BUTTON = + '
    ' + + '
    '; + var TPL_REORDER_BUTTON = + '
    ' + + '
    '; + var TPL_OPTION_BUTTONS = + ''; + + angular.module('ionic.ui.list', ['ngAnimate']) + + /** + * @ngdoc service + * @name $ionicListDelegate + * @module ionic + * + * @description + * Delegate for controlling the {@link ionic.directive:ionList} directive. + * + * Methods called directly on the $ionicListDelegate service will control all lists. + * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle} + * method to control specific ionList instances. + * + * @usage + * + * ````html + * + * + * + * > + * {% raw %}Hello, {{i}}!{% endraw %} + * + * + * + * + * ``` + * ```js + * function MyCtrl($scope, $ionicListDelegate) { + * $scope.showDeleteButtons = function() { + * $ionicListDelegate.showDelete(true); + * }; + * } + * ``` + */ + .service('$ionicListDelegate', delegateService([ + /** + * @ngdoc method + * @name $ionicListDelegate#showReorder + * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons. + * @returns {boolean} Whether the reorder buttons are shown. + */ + 'showReorder', + /** + * @ngdoc method + * @name $ionicListDelegate#showDelete + * @param {boolean=} showReorder Set whether or not this list is showing its delete buttons. + * @returns {boolean} Whether the delete buttons are shown. + */ + 'showDelete', + /** + * @ngdoc method + * @name $ionicListDelegate#canSwipeItems + * @param {boolean=} showReorder Set whether or not this list is able to swipe to show + * option buttons. + * @returns {boolean} Whether the list is able to swipe to show option buttons. + */ + 'canSwipeItems', + /** + * @ngdoc method + * @name $ionicListDelegate#closeOptionButtons + * @description Closes any option buttons on the list that are swiped open. + */ + 'closeOptionButtons', + /** + * @ngdoc method + * @name $ionicListDelegate#$getByHandle + * @param {string} handle + * @returns `delegateInstance` A delegate instance that controls only the + * {@link ionic.directive:ionList} directives with `delegate-handle` matching + * the given handle. + * + * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);` + */ + ])) + + .controller('$ionicList', [ + '$scope', + '$attrs', + '$parse', + '$ionicListDelegate', + function($scope, $attrs, $parse, $ionicListDelegate) { + + var isSwipeable = true; + var isReorderShown = false; + var isDeleteShown = false; + + var deregisterInstance = $ionicListDelegate._registerInstance(this, $attrs.delegateHandle); + $scope.$on('$destroy', deregisterInstance); + + this.showReorder = function(show) { + if (arguments.length) { + isReorderShown = !!show; + } + return isReorderShown; + }; + + this.showDelete = function(show) { + if (arguments.length) { + isDeleteShown = !!show; + } + return isDeleteShown; + }; + + this.canSwipeItems = function(can) { + if (arguments.length) { + isSwipeable = !!can; + } + return isSwipeable; + }; + + this.closeOptionButtons = function() { + this.listView && this.listView.clearDragEffects(); + }; + }]) /** * @ngdoc directive - * @name ionItem + * @name ionList * @module ionic + * @delegate ionic.service:$ionicListDelegate + * @codepen JsHjf * @restrict E - * @parent ionic.directive:ionList - * * @description - * The ionItem directive creates a list-item that can easily be swiped, - * deleted, reordered, edited, and more. + * The List is a widely used interface element in almost any mobile app, and can include + * content ranging from basic text all the way to buttons, toggles, icons, and thumbnails. + * + * Both the list, which contains items, and the list items themselves can be any HTML + * element. The containing element requires the `list` class and each list item requires + * the `item` class. + * + * However, using the ionList and ionItem directives make it easy to support various + * interaction modes such as swipe to edit, drag to reorder, and removing items. + * + * Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton} + * {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list). * * @usage + * + * Basic Usage: + * * ```html * - * + * + * {% raw %}Hello, {{item}}!{% endraw %} * * * ``` * - * @param {string=} item-type The type of this item. See [the list CSS page](/docs/components/#list) for available item types. - * @param {expression=} option-buttons The option buttons to show when swiping the item to the left (if swiping is enabled). Defaults to the ionList parent's option-buttons setting. The format of each button object is: - * ```js - * $scope.itemButtons = [ - * { - * text: 'Edit', - * type: 'Button', - * onTap: function(item) { - * alert('Edit Item: ' + item.id); - * } - * }, - * { - * text: 'Share', - * type: 'Button', - * onTap: function(item) { - * alert('Share Item: ' + item.id); - * } - * } - * ]; - * ``` + * Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping * - * @param {expression=} item The 'object' representing this item, to be passed in to swipe, delete, and reorder callbacks. - * @param {boolean=} can-swipe Whether or not this item can be swiped. Defaults ot the ionList parent's can-swipe setting. - * @param {boolean=} can-delete Whether or not this item can be deleted. Defaults to the ionList parent's can-delete setting. - * @param {boolean=} can-reorder Whether or not this item can be reordered. Defaults to the ionList parent's can-reorder setting. - * @param {expression=} on-delete The expression to call when this item is deleted. - * @param {string=} delete-icon The class name of the icon to show on this item while deleting. Defaults to the ionList parent's delete-icon setting. - * @param {string=} reorder-icon The class name of the icon to show on this item while reordering. Defaults to the ionList parent's reorder-icon setting. + * ```html + * + * + * + * {% raw %} + *

    {{item.title}}

    + *

    {{item.description}}

    {% endraw %} + * + * Share + * + * + * Edit + * + * + * + * + * + * + *
    + *
    + * ``` + * + * @param {string=} delegate-handle The handle used to identify this list with + * {@link ionic.service:$ionicListDelegate}. + * @param show-delete {boolean=} Whether the delete buttons for the items in the list are + * currently shown or hidden. + * @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are + * currently shown or hidden. + * @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal + * option buttons. Default: true. */ -.directive('ionItem', ['$timeout', '$parse', function($timeout, $parse) { +.directive('ionList', [ + '$animate', + '$timeout', +function($animate, $timeout) { return { restrict: 'E', - require: '?^ionList', - replace: true, - transclude: true, + require: ['ionList', '^?$ionicScroll'], + controller: '$ionicList', + compile: function($element, $attr) { + var listEl = angular.element('
    ') + .append( $element.contents() ); + $element.append(listEl); - scope: { - item: '=', - itemType: '@', - canDelete: '@', - canReorder: '@', - canSwipe: '@', - onDelete: '&', - optionButtons: '&', - deleteIcon: '@', - reorderIcon: '@' - }, + return function($scope, $element, $attrs, ctrls) { + var listCtrl = ctrls[0]; + var scrollCtrl = ctrls[1]; - template: '
    \ -
    \ - \ -
    \ - \ -
    \ - \ -
    \ -
    \ - \ -
    \ -
    ', + //Wait for child elements to render... + $timeout(init); - link: function($scope, $element, $attr, list) { - if(!list) return; - - var $parentScope = list.scope; - var $parentAttrs = list.attrs; - - $attr.$observe('href', function(value) { - if(value) $scope.href = value.trim(); - }); - - if(!$scope.itemType) { - $scope.itemType = $parentScope.itemType; - } - - // Set this item's class, first from the item directive attr, and then the list attr if item not set - $element.addClass($scope.itemType || $parentScope.itemType); - - $scope.itemClass = $scope.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({ item: $scope.item, index: $scope.$parent.$index }); - } else if($parentAttrs.onDelete) { - // run the parent list's onDelete method - // if it doesn't exist nothing will happen - $parentScope.onDelete({ item: $scope.item, index: $scope.$parent.$index }); + function init() { + var listView = listCtrl.listView = new ionic.views.ListView({ + el: $element[0], + listEl: $element.children()[0], + scrollEl: scrollCtrl && scrollCtrl.element, + scrollView: scrollCtrl && scrollCtrl.scrollView, + onReorder: function(el, oldIndex, newIndex) { + var itemScope = angular.element(el).scope(); + if (itemScope && itemScope.$onReorder) { + itemScope.$onReorder(oldIndex, newIndex); + } + }, + canSwipe: function() { + return listCtrl.canSwipeItems(); } - }; + }); - // Set which icons to use for deleting - $scope.deleteIconClass = $scope.deleteIcon || $parentScope.deleteIcon || 'ion-minus-circled'; - $element.addClass('item-left-editable'); + if (angular.isDefined($attr.canSwipe)) { + $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) { + listCtrl.canSwipeItems(value); + }); + } + + $scope.$watch('!!(' + $attr.showDelete + ')', function(value, oldValue) { + //Don't care about first false value + if (!value && !angular.isDefined(oldValue)) return; + + if (value) listCtrl.closeOptionButtons(); + listCtrl.showDelete(value); + + $element.children().toggleClass('list-left-editing', value); + toggleNgHide('.item-delete.item-left-edit', value); + }); + $scope.$watch('!!(' + $attr.showReorder + ')', function(value, oldValue) { + //Don't care about first false value + if (!value && !angular.isDefined(oldValue)) return; + + if (value) listCtrl.closeOptionButtons(); + listCtrl.showReorder(value); + + $element.children().toggleClass('list-right-editing', value); + toggleNgHide('.item-reorder.item-right-edit', value); + }); + + function toggleNgHide(selector, shouldShow) { + angular.forEach($element[0].querySelectorAll(selector), function(node) { + if (shouldShow) $animate.removeClass(angular.element(node), 'ng-hide'); + else $animate.addClass(angular.element(node), 'ng-hide'); + }); + } } + + }; + } + }; +}]) + +.controller('$ionicItem', [ + '$scope', + '$element', +function($scope, $element) { + this.$element = $element; + this.$scope = $scope; +}]) + +/** + * @ngdoc directive + * @name ionItem + * @parent ionic.directive:ionList + * @module ionic + * @restrict E + * Creates a list-item that can easily be swiped, + * deleted, reordered, edited, and more. + * + * See {@link ionic.directive:ionList} for a complete example & explanation. + * + * Can be assigned any item class name. See the + * [list CSS documentation](/docs/components/#list). + * + * @usage + * + * ```html + * + * Hello! + * + * ``` + */ +.directive('ionItem', ['$animate', '$compile', function($animate, $compile) { + return { + restrict: 'E', + controller: '$ionicItem', + priorty: Number.MAX_VALUE, + require: ['ionItem', '^ionList'], + scope: true, + compile: function($element, $attrs) { + var isAnchor = angular.isDefined($attrs.href) || angular.isDefined($attrs.ngHref); + var isComplexItem = isAnchor || + //Lame way of testing, but we have to know at compile what to do with the element + /ion-(delete|option|reorder)-button/.test($element.html()); + + if (isComplexItem) { + var innerElement = angular.element(isAnchor ? TPL_CONTENT_ANCHOR : TPL_CONTENT); + innerElement.append($element.contents()); + + $element.append(innerElement); + $element.addClass('item item-complex'); + } else { + $element.addClass('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'; - $element.addClass('item-right-editable'); - } - - // 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(); - } - $element.addClass('item-swipeable'); - } - + return function link($scope, $element, $attrs) { + $scope.$href = function() { + return $attrs.href || $attrs.ngHref; + }; + }; } }; }]) /** * @ngdoc directive - * @name ionList + * @name ionDeleteButton + * @parent ionic.directive:ionItem * @module ionic * @restrict E - * @codepen jsHjf + * Creates a delete button inside a list item, that is visible when the + * {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or + * `$ionicListDelegate.showDelete(true)` is called. * - * @description - * The List is a widely used interface element in almost any mobile app, - * and can include content ranging from basic text all the way to buttons, - * toggles, icons, and thumbnails. + * Takes any ionicon as a class. * - * Both the list, which contains items, and the list items themselves can be - * any HTML element. The containing element requires the list class and each - * list item requires the item class. Ionic also comes with pre-built Angular - * directives to make it easier to create a complex list. - * - * Using the ionList and {@link ionic.directive:ionItem} directives - * make it easy to support various interaction modes such as swipe to edit, - * drag to reorder, and removing items. - * - * However, if you need just a simple list you won't be required to use the - * directives, but rather just use the classnames. - * This demo is a simple list without using the directives. - * - * See the {@link ionic.directive:ionItem} documentation for more information on list items. + * See {@link ionic.directive:ionList} for a complete example & explanation. * * @usage + * * ```html - * - * + * + * + * + * Hello, list item! + * + * + * + * Show Delete? + * + * ``` + */ +.directive('ionDeleteButton', [function() { + return { + restrict: 'E', + require: '^ionItem', + //Run before anything else, so we can move it before other directives process + //its location (eg ngIf relies on the location of the directive in the dom) + priority: Number.MAX_VALUE, + compile: function($element, $attr) { + //Add the classes we need during the compile phase, so that they stay + //even if something else like ngIf removes the element and re-addss it + $attr.$set('class', ($attr.class || '') + ' button icon button-icon', true); + return function($scope, $element, $attr, itemCtrl) { + var container = angular.element(TPL_DELETE_BUTTON); + container.append($element); + itemCtrl.$element.append(container).addClass('item-left-editable'); + }; + } + }; +}]) + +/** + * @ngdoc directive + * @name ionReorderButton + * @parent ionic.directive:ionItem + * @module ionic + * @restrict E + * Creates a reorder button inside a list item, that is visible when the + * {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or + * `$ionicListDelegate.showReorder(true)` is called. + * + * Can be dragged to reorder items in the list. Takes any ionicon class. + * + * When an item reorder is complete, the `on-reorder` callback given in the attribute is called + * (see below). + * + * See {@link ionic.directive:ionList} for a complete example. + * + * @usage + * + * ```html + * + * + * Item {{$index}} + * + * * * * ``` + * ```js + * function MyCtrl($scope) { + * $scope.items = [1, 2, 3, 4]; + * $scope.moveItem = function(item, fromIndex, toIndex) { + * //Move the item in the array + * $scope.items.splice(fromIndex, 1); + * $scope.items.splice(toIndex, 0, item); + * }; + * } + * ``` * - * @param {string=} item-type The type of this item. See [the list CSS page](/docs/components/#list) for available item types. - * @param {expression=} on-delete Called when a child item is deleted. - * @param {expression=} on-reorder Called when a child item is reordered. - * @param {boolean=} show-delete Whether to show each item delete button. - * @param {boolean=} show-reoder Whether to show each item's reorder button. - * @param {boolean=} can-delete Whether child items are able to be deleted or not. - * @param {boolean=} can-reorder Whether child items can be reordered or not. - * @param {boolean=} can-swipe Whether child items can be swiped to reveal option buttons. - * @param {string=} delete-icon The class name of the icon to show on child items while deleting. Defaults to `ion-minus-circled`. - * @param {string=} reorder-icon The class name to show on child items while reordering. Defaults to `ion-navicon`. - * @param {string=} animation An animation class to apply to the list for animating when child items enter or exit the list. See [the animation CSS page](/docs/components/#animations) for available animation classes. + * @param {expression=} on-reorder Expression to call when an item is reordered. + * Parameters given: $fromIndex, $toIndex. */ -.directive('ionList', ['$timeout', function($timeout) { +.directive('ionReorderButton', [function() { return { restrict: 'E', - replace: true, - transclude: true, - require: '^?$ionicScroll', - scope: { - itemType: '@', - canDelete: '@', - canReorder: '@', - canSwipe: '@', - showDelete: '=', - showReorder: '=', - onDelete: '&', - onReorder: '&', - optionButtons: '&', - deleteIcon: '@', - reorderIcon: '@' - }, - - template: '
    ', - - controller: ['$scope', '$attrs', function($scope, $attrs) { - this.scope = $scope; - this.attrs = $attrs; - }], - - link: function($scope, $element, $attr, ionicScrollCtrl) { - $scope.listView = new ionic.views.ListView({ - canSwipe: $scope.canSwipe !== "false" && !!$scope.optionButtons(), - el: $element[0], - listEl: $element[0].children[0], - scrollEl: ionicScrollCtrl && ionicScrollCtrl.element, - scrollView: ionicScrollCtrl && ionicScrollCtrl.scrollView, - onReorder: function(el, oldIndex, newIndex) { - $scope.$apply(function() { - $scope.onReorder({el: el, start: oldIndex, end: newIndex}); + require: '^ionItem', + priority: Number.MAX_VALUE, + compile: function($element, $attr) { + $attr.$set('class', ($attr.class || '') + ' button icon button-icon', true); + $element[0].setAttribute('data-prevent-scroll', true); + return function($scope, $element, $attr, itemCtrl) { + $scope.$onReorder = function(oldIndex, newIndex) { + $scope.$eval($attr.onReorder, { + $fromIndex: oldIndex, + $toIndex: newIndex }); + }; + + var container = angular.element(TPL_REORDER_BUTTON); + container.append($element); + itemCtrl.$element.append(container).addClass('item-right-editable'); + }; + } + }; +}]) + +/** + * @ngdoc directive + * @name ionOptionButton + * @parent ionic.directive:ionItem + * @module ionic + * @restrict E + * Creates an option button inside a list item, that is visible when the item is swiped + * to the left by the user. Swiped open option buttons can be hidden with + * {@link ionic.directive:$ionicListDelegate#closeOptionButtons $ionicListDelegate#closeOptionButtons}. + * + * Can be assigned any button class. + * + * See {@link ionic.directive:ionList} for a complete example & explanation. + * + * @usage + * + * ```html + * + * + * I love kittens! + * Share + * Edit + * + * + * ``` + */ +.directive('ionOptionButton', ['$compile', function($compile) { + return { + restrict: 'E', + require: '^ionItem', + priority: Number.MAX_VALUE, + compile: function($element, $attr) { + $attr.$set('class', ($attr.class || '') + ' button', true); + return function($scope, $element, $attr, itemCtrl) { + if (!itemCtrl.optionsContainer) { + itemCtrl.optionsContainer = angular.element(TPL_OPTION_BUTTONS); + itemCtrl.$element.append(itemCtrl.optionsContainer); } - }); - - if($attr.animation) { - $element[0].classList.add($attr.animation); - } - - var destroyShowReorderWatch = $scope.$watch('showReorder', function(val) { - if(val) { - $element[0].classList.add('item-options-hide'); - $scope.listView && $scope.listView.clearDragEffects(); - } 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(); - }); - + itemCtrl.optionsContainer.append($element); + }; } }; }]); diff --git a/js/ext/angular/test/anchorScroll.html b/js/ext/angular/test/anchorScroll.html index ce1823525c..819d0d00eb 100644 --- a/js/ext/angular/test/anchorScroll.html +++ b/js/ext/angular/test/anchorScroll.html @@ -24,7 +24,6 @@ Item {{ item.id }} diff --git a/js/ext/angular/test/directive/ionicList.unit.js b/js/ext/angular/test/directive/ionicList.unit.js index 7cf22dd159..082457adcd 100644 --- a/js/ext/angular/test/directive/ionicList.unit.js +++ b/js/ext/angular/test/directive/ionicList.unit.js @@ -1,389 +1,290 @@ 'use strict'; +describe('$ionicList controller', function() { + beforeEach(module('ionic')); + function setup(attrs) { + var ctrl; + inject(function($controller, $rootScope) { + var scope = $rootScope.$new(); + ctrl = $controller('$ionicList', { + $scope: scope, + $attrs: attrs || {}, + }); + ctrl.$scope = scope; + }); + return ctrl; + } -describe('Ionic List', function() { - var compile, scope, listElement, listCtrl; - - beforeEach(module('ionic.ui.list')); - - beforeEach(inject(function($compile, $rootScope, $controller) { - compile = $compile; - scope = $rootScope; + it('should register with handle', inject(function($ionicListDelegate) { + spyOn($ionicListDelegate, '_registerInstance'); + var ctrl = setup({delegateHandle: 'foobar'}); + expect($ionicListDelegate._registerInstance) + .toHaveBeenCalledWith(ctrl, 'foobar'); })); - beforeEach(inject(function (_$compile_, _$rootScope_) { - compile = _$compile_; - scope.showDelete = false; - scope.showReorder = false; + it('should register with given handle and deregister on destroy', inject(function($ionicListDelegate) { + var deregisterSpy = jasmine.createSpy('deregister'); + spyOn($ionicListDelegate, '_registerInstance').andCallFake(function() { + return deregisterSpy; + }); + var ctrl = setup({ + delegateHandle: 'something' + }); + expect($ionicListDelegate._registerInstance) + .toHaveBeenCalledWith(ctrl, 'something'); - listElement = angular.element(''); - listElement = _$compile_(listElement)(scope); - - listCtrl = listElement.controller('list'); - - scope.$digest(); + expect(deregisterSpy).not.toHaveBeenCalled(); + ctrl.$scope.$destroy(); + expect(deregisterSpy).toHaveBeenCalled(); })); - it('Should init', function() { - var element = compile('' + - '' + - '' + - '')(scope); - - expect(element.children().length).toBe(2); + it('.showReorder sets/gets', function() { + var ctrl = setup(); + expect(ctrl.showReorder()).toBe(false); + ctrl.showReorder(true); + expect(ctrl.showReorder()).toBe(true); }); - it('Should add animation class', function() { - var element = compile('')(scope); - expect(element.hasClass('my-animation')).toBe(true); + it('.showDelete sets/gets', function() { + var ctrl = setup(); + expect(ctrl.showDelete()).toBe(false); + ctrl.showDelete(true); + expect(ctrl.showDelete()).toBe(true); }); - it('Should add list-left-editing class because of showDelete', function() { - expect(listElement.hasClass('list-left-editing')).toBe(false); - scope.showDelete = true; - scope.$digest(); - expect(listElement.hasClass('list-left-editing')).toBe(true); + it('.canSwipeItems sets/gets', function() { + var ctrl = setup(); + expect(ctrl.canSwipeItems()).toBe(true); + ctrl.canSwipeItems(false); + expect(ctrl.canSwipeItems()).toBe(false); }); - it('Should add list-right-editing class because of showReorder', function() { - expect(listElement.hasClass('list-right-editing')).toBe(false); - scope.showReorder = true; - scope.$digest(); - expect(listElement.hasClass('list-right-editing')).toBe(true); - }); - - 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); - }); - - it('Should reorder', function() { - scope.onReorder = function(el, start, end) { - }; - - listElement = angular.element(''); - listElement = compile(listElement)(scope); - - var lv = listElement.isolateScope().listView; - - spyOn(scope, 'onReorder'); - - lv.onReorder({}, 0, 1); - - expect(scope.onReorder).toHaveBeenCalledWith({}, 0, 1); + it('.closeOptionButtons closes calls clearDragEffects', function() { + var ctrl = setup(); + ctrl.listView = { clearDragEffects: jasmine.createSpy('clearDragEffects') }; + ctrl.closeOptionButtons(); + expect(ctrl.listView.clearDragEffects).toHaveBeenCalled(); }); }); -describe('Ionic Item Directive', function () { - var $rootScope, $compile, listCtrl, options, listScope, itemScope, listElement, itemElement; +describe('ionList directive', function() { + beforeEach(module('ionic')); - beforeEach(module('ionic.ui.list')); + function setup(attrs, content) { + var el; + inject(function($compile, $rootScope) { + var scope = $rootScope.$new(); + el = angular.element(''+(content||'')+''); + el.data('$$ionicScrollController', { + scrollView: {}, + element: angular.element('
    ') + }); + $compile(el)(scope); + scope.$apply(); + }); + return el; + } + function flush() { + inject(function($timeout) { $timeout.flush(); }); + } - beforeEach(inject(function (_$compile_, _$rootScope_) { - $rootScope = _$rootScope_; - $compile = _$compile_; - $rootScope.showDelete = false; + it('should put contents in a list wrapper', function() { + var el = setup('', ''); + expect(el.children().hasClass('list')).toBe(true); + expect(el.children().html()).toBe(''); + }); - listElement = angular.element(''); - listElement = _$compile_(listElement)($rootScope); - listScope = listElement.isolateScope(); + it('should give options to listView after init', function() { + var options; + spyOn(ionic.views, 'ListView').andCallFake(function(o) { + options = o; + }); + var el = setup(); + flush(); + expect(ionic.views.ListView).toHaveBeenCalled(); + expect(options.el).toBe(el[0]); + expect(options.listEl).toBe(el.children()[0]); + expect(options.scrollEl).toBe(el.controller('$ionicScroll').element); + expect(options.scrollView).toBe(el.controller('$ionicScroll').scrollView); - listCtrl = listElement.controller('list'); + spyOn(el.controller('ionList'), 'canSwipeItems').andReturn('swipey'); + expect(options.canSwipe()).toBe('swipey'); - itemElement = angular.element('').appendTo(listElement); - itemElement = _$compile_(itemElement)($rootScope); + el.scope().$onReorder = jasmine.createSpy('$onReorder'); + options.onReorder(el, 2, 3); + expect(el.scope().$onReorder).toHaveBeenCalledWith(2,3); + }); - $rootScope.$digest(); - itemScope = itemElement.isolateScope(); + it('should watch canSwipe if given', function() { + var el = setup('can-swipe="shouldSwipe"'); + flush(); + expect(el.controller('ionList').canSwipeItems()).toBe(false); + el.scope().$apply('shouldSwipe = true'); + expect(el.controller('ionList').canSwipeItems()).toBe(true); + }); + + it('should showDelete true', inject(function($animate) { + var el = setup('show-delete="shouldDelete"', '
    '); + flush(); + + spyOn(el.controller('ionList'), 'closeOptionButtons'); + spyOn(el.controller('ionList'), 'showDelete'); + spyOn($animate, 'removeClass'); + + el.scope().$apply('shouldDelete = true'); + + expect(el.controller('ionList').closeOptionButtons).toHaveBeenCalled(); + expect(el.controller('ionList').showDelete).toHaveBeenCalledWith(true); + expect(el.children().hasClass('list-left-editing')).toBe(true); + expect($animate.removeClass).toHaveBeenCalled(); + expect($animate.removeClass.mostRecentCall.args[0][0]) + .toBe(el[0].querySelector('.item-delete.item-left-edit')); + expect($animate.removeClass.mostRecentCall.args[1]).toBe('ng-hide'); })); - it('Should set item type from item attribute', inject(function ($timeout) { - itemElement = angular.element('').appendTo(listElement); - itemElement = $compile(itemElement)($rootScope); - $rootScope.$digest(); - itemScope = itemElement.isolateScope(); - expect(itemScope.itemClass).toBe(undefined); + it('should showDelete false', inject(function($animate) { + var el = setup('show-delete="shouldDelete"', '
    '); + flush(); - itemElement = angular.element('').appendTo(listElement); - itemElement = $compile(itemElement)($rootScope); - $rootScope.$digest(); - itemScope = itemElement.isolateScope(); - expect(itemScope.itemType).toBe("item-type-test"); - expect(itemElement.hasClass('item-type-test')).toBe(true); + el.scope().$apply('shouldDelete = true'); + + spyOn(el.controller('ionList'), 'closeOptionButtons'); + spyOn(el.controller('ionList'), 'showDelete'); + spyOn($animate, 'addClass'); + + el.scope().$apply('shouldDelete = false'); + + expect(el.controller('ionList').closeOptionButtons).not.toHaveBeenCalled(); + expect(el.controller('ionList').showDelete).toHaveBeenCalledWith(false); + expect(el.children().hasClass('list-left-editing')).toBe(false); + expect($animate.addClass).toHaveBeenCalled(); + expect($animate.addClass.mostRecentCall.args[0][0]) + .toBe(el[0].querySelector('.item-delete.item-left-edit')); + expect($animate.addClass.mostRecentCall.args[1]).toBe('ng-hide'); })); - 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(); - itemScope = itemElement.isolateScope(); - expect(itemElement.hasClass('list-item-type-test')).toBe(true); + it('should showReorder true', inject(function($animate) { + var el = setup('show-reorder="shouldReorder"', '
    '); + flush(); + + spyOn(el.controller('ionList'), 'closeOptionButtons'); + spyOn(el.controller('ionList'), 'showReorder'); + spyOn($animate, 'removeClass'); + + el.scope().$apply('shouldReorder = true'); + + expect(el.controller('ionList').closeOptionButtons).toHaveBeenCalled(); + expect(el.controller('ionList').showReorder).toHaveBeenCalledWith(true); + expect(el.children().hasClass('list-right-editing')).toBe(true); + expect($animate.removeClass).toHaveBeenCalled(); + expect($animate.removeClass.mostRecentCall.args[0][0]) + .toBe(el[0].querySelector('.item-reorder.item-right-edit')); + expect($animate.removeClass.mostRecentCall.args[1]).toBe('ng-hide'); })); - 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); + it('should showReorder false', inject(function($animate) { + var el = setup('show-reorder="shouldReorder"', '
    '); + flush(); - $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); + el.scope().$apply('shouldReorder = true'); + + spyOn(el.controller('ionList'), 'closeOptionButtons'); + spyOn(el.controller('ionList'), 'showReorder'); + spyOn($animate, 'addClass'); + + el.scope().$apply('shouldReorder = false'); + + expect(el.controller('ionList').closeOptionButtons).not.toHaveBeenCalled(); + expect(el.controller('ionList').showReorder).toHaveBeenCalledWith(false); + expect(el.children().hasClass('list-right-editing')).toBe(false); + expect($animate.addClass).toHaveBeenCalled(); + expect($animate.addClass.mostRecentCall.args[0][0]) + .toBe(el[0].querySelector('.item-reorder.item-right-edit')); + expect($animate.addClass.mostRecentCall.args[1]).toBe('ng-hide'); })); - - 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-left-edit').length).toBe(1); - expect(itemElement.find('.item-delete').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-left-edit').length).toBe(1); - expect(itemElement.find('.item-delete').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-reorder').length).toBe(0); - expect(itemElement.find('.item-right-edit').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-reorder').length).toBe(1); - expect(itemElement.find('.item-right-edit').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-reorder').length).toBe(1); - expect(itemElement.find('.item-right-edit').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-reorder').length).toBe(1); - expect(itemElement.find('.item-right-edit').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('ionItem directive', function() { + beforeEach(module('ionic')); + function setup(attrs, content) { + var el; + inject(function($rootScope, $compile) { + el = angular.element('').html(content || ''); + el.data('$ionListController', {}); + $compile(el)($rootScope); + $rootScope.$apply(); + }); + return el; + } + + it('should be simple item by default', function() { + var el = setup('', '{{1}} 2'); + expect(el.html()).toBe('1 2'); + expect(el.hasClass('item')).toBe(true); + expect(el.hasClass('item-complex')).toBe(false); + }); + ['href', 'ng-href'].forEach(function(attr) { + it('should be complex anchor if '+attr+' is defined', function() { + var el = setup(attr+'="something"'); + expect(el.hasClass('item item-complex')).toBe(true); + expect(el.children()[0].tagName).toBe('A'); + expect(el.children().hasClass('item-content')).toBe(true); + expect(el.children().attr('ng-href')).toBe('something'); + }); + }); + ['ion-option-button','ion-reorder-button','ion-delete-button'].forEach(function(tag) { + it('should be complex div if '+tag+' is found', function() { + var el = setup('', tag); + expect(el.hasClass('item item-complex')).toBe(true); + expect(el.children()[0].tagName).toBe('DIV'); + expect(el.children().hasClass('item-content')).toBe(true); + expect(el.children().html()).toBe(tag); + }); + }); +}); + +describe('ionDeleteButton directive', function() { + beforeEach(module('ionic')); + it('should have delete button', inject(function($compile, $rootScope) { + var setSpy = jasmine.createSpy('setDeleteButton') + var el = angular.element(''); + el.data('$ionListController', {}); + $compile(el)($rootScope.$new()); + $rootScope.$apply(); + + var deleteContainer = angular.element(el[0].querySelector('.item-left-edit.item-delete')); + expect(deleteContainer.length).toBe(1); + expect(deleteContainer.children().hasClass('button icon button-icon')).toBe(true); + })); +}); + +describe('ionReorderButton directive', function() { + beforeEach(module('ionic')); + it('should have reorder button', inject(function($compile, $rootScope) { + var setSpy = jasmine.createSpy('setReorderButton') + var el = angular.element(''); + el.data('$ionListController', {}); + $compile(el)($rootScope.$new()); + $rootScope.$apply(); + + var reorderContainer = angular.element(el[0].querySelector('.item-right-edit.item-reorder')); + expect(reorderContainer.length).toBe(1); + expect(reorderContainer.children().hasClass('button icon button-icon')).toBe(true); + expect(reorderContainer.attr('data-prevent-scroll')).toBe('true'); + expect(reorderContainer.children().attr('data-prevent-scroll')).toBe('true'); + })); +}); + +describe('ionOptionButton directive', function() { + beforeEach(module('ionic')); + it('should have option button', inject(function($compile, $rootScope) { + var setSpy = jasmine.createSpy('setOptionButton') + var el = angular.element(''); + el.data('$ionListController', {}); + $compile(el)($rootScope.$new()); + $rootScope.$apply(); + + var optionContainer = angular.element(el[0].querySelector('.item-options')); + expect(optionContainer.length).toBe(1); + expect(optionContainer.children().hasClass('button')).toBe(true); + })); +}); diff --git a/js/ext/angular/test/list.html b/js/ext/angular/test/list.html index ece83d86d5..16742227df 100644 --- a/js/ext/angular/test/list.html +++ b/js/ext/angular/test/list.html @@ -47,7 +47,6 @@
    -

    List Tests

    @@ -57,91 +56,29 @@ - - - - + +

    {{item.from}}

    {{item.text}}

    + Hello! + + +
    + + +

    + Show Option? +

    + Hello!
    - Me Divider, just plain ol' HTML nested in the list directive. + 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. - - - - - - - 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 }} - - -
    - - -
    -

    BLUE {{slideBox.slideIndex}}

    -
    -
    - -
    -

    YELLOW {{slideBox.slideIndex}}

    -
    -
    - -

    PINK {{slideBox.slideIndex}}

    -
    -
    -
    -
    @@ -232,8 +169,11 @@ console.log('onDelete from the "list" on-delete attribute', item, index); $scope.items.splice(index, 1); }; - $scope.onReorder = function(el, start, end) { - console.log('On reorder', el, start, end); + $scope.onReorder = function(item, fromIndex, toIndex) { + console.log('On reorder', item, fromIndex, toIndex); + $scope.items.splice(fromIndex, 1); + $scope.items.splice(toIndex, 0, item); + console.log($scope.items); }; $scope.optionButtons1 = [ diff --git a/js/ext/angular/test/sideMenu.html b/js/ext/angular/test/sideMenu.html index f499b39933..84c5fc8dd1 100644 --- a/js/ext/angular/test/sideMenu.html +++ b/js/ext/angular/test/sideMenu.html @@ -22,8 +22,11 @@ Hello

    Content

    - - Sup + + + Sup + Hello + diff --git a/js/ext/angular/test/tabs.html b/js/ext/angular/test/tabs.html index 83a90b1e34..b749a68f56 100644 --- a/js/ext/angular/test/tabs.html +++ b/js/ext/angular/test/tabs.html @@ -54,27 +54,14 @@
    -

    Tasks

    -
    - - - + + {{item.title}} diff --git a/js/ext/angular/test/viewState.html b/js/ext/angular/test/viewState.html index 75a3fe16c3..5be9c0778b 100644 --- a/js/ext/angular/test/viewState.html +++ b/js/ext/angular/test/viewState.html @@ -156,7 +156,8 @@ - + {{ auto.year }} {{ auto.make }} {{ auto.model }} diff --git a/js/views/listView.js b/js/views/listView.js index 9613b43761..34ce84a803 100644 --- a/js/views/listView.js +++ b/js/views/listView.js @@ -22,11 +22,10 @@ } }; - - var SlideDrag = function(opts) { this.dragThresholdX = opts.dragThresholdX || 10; this.el = opts.el; + this.canSwipe = opts.canSwipe; }; SlideDrag.prototype = new DragOp(); @@ -34,6 +33,10 @@ SlideDrag.prototype.start = function(e) { var content, buttons, offsetX, buttonsWidth; + if (!this.canSwipe()) { + return; + } + if(e.target.classList.contains(ITEM_CONTENT_CLASS)) { content = e.target; } else if(e.target.classList.contains(ITEM_CLASS)) { @@ -58,10 +61,12 @@ if(!buttons) { return; } + buttons.classList.remove('invisible'); buttonsWidth = buttons.offsetWidth; this._currentDrag = { + buttons: buttons, buttonsWidth: buttonsWidth, content: content, startOffsetX: offsetX @@ -85,7 +90,10 @@ ionic.requestAnimationFrame(function() { lastDrag.content.style[ionic.CSS.TRANSITION] = ''; - lastDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(0, 0, 0)'; + lastDrag.content.style[ionic.CSS.TRANSFORM] = ''; + setTimeout(function() { + lastDrag.buttons && lastDrag.buttons.classList.add('invisible'); + }, 250); }); }; @@ -152,6 +160,10 @@ ionic.requestAnimationFrame(function() { if(restingPoint === 0) { _this._currentDrag.content.style[ionic.CSS.TRANSFORM] = ''; + var buttons = _this._currentDrag.buttons; + setTimeout(function() { + buttons && buttons.classList.add('invisible'); + }, 250); } else { _this._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + restingPoint + 'px, 0, 0)'; } @@ -178,40 +190,29 @@ ReorderDrag.prototype = new DragOp(); ReorderDrag.prototype._moveElement = function(e) { - var y = (e.gesture.center.pageY - this._currentDrag.elementHeight/2); + var y = e.gesture.center.pageY - this._currentDrag.elementHeight + this._currentDrag.scrollDelta; this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, '+y+'px, 0)'; }; ReorderDrag.prototype.start = function(e) { var content; - - // Grab the starting Y point for the item - var offsetY = this.el.offsetTop;//parseFloat(this.el.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[1]) || 0; - var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase()); - var elementHeight = this.el.offsetHeight; + var elementHeight = this.el.scrollHeight; var placeholder = this.el.cloneNode(true); - // If we have a scroll pane, move our draggable element outside of it - // We do this because when we drag our element down below the edge of the page - // and scroll the scroll-pane, if the element is *part* of the scroll-pane, - // it will scroll 'with' the scroll-pane's contents and change position. - var appendToElement = (this.scrollEl || this.el).parentNode; - placeholder.classList.add(ITEM_PLACEHOLDER_CLASS); this.el.parentNode.insertBefore(placeholder, this.el); this.el.classList.add(ITEM_REORDERING_CLASS); - appendToElement.parentNode.appendChild(this.el); - this._currentDrag = { elementHeight: elementHeight, startIndex: startIndex, placeholder: placeholder, scrollHeight: scroll, - list: placeholder.parentNode + list: placeholder.parentNode, + scrollDelta: 0 }; this._moveElement(e); @@ -238,10 +239,12 @@ if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) { this.scrollView.scrollBy(null, -pixelsPastTop); + this._currentDrag.scrollDelta -= pixelsPastTop; } if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) { if (scrollY < this.scrollView.getScrollMax().top) { this.scrollView.scrollBy(null, pixelsPastBottom); + this._currentDrag.scrollDelta += pixelsPastBottom; } } } @@ -263,8 +266,12 @@ // When an item is dragged, we need to reorder any items for sorting purposes ReorderDrag.prototype._reorderItems = function() { + var self = this; var placeholder = this._currentDrag.placeholder; - var siblings = Array.prototype.slice.call(this._currentDrag.placeholder.parentNode.children); + var siblings = Array.prototype.slice.call(this._currentDrag.placeholder.parentNode.children) + .filter(function(el) { + return el !== self.el; + }); var index = siblings.indexOf(this._currentDrag.placeholder); var topSibling = siblings[Math.max(0, index - 1)]; @@ -316,7 +323,9 @@ onReorder: function(el, oldIndex, newIndex) {}, virtualRemoveThreshold: -200, virtualAddThreshold: 200, - canSwipe: false + canSwipe: function() { + return true; + } }, opts); ionic.extend(this, opts); @@ -472,7 +481,7 @@ // Make sure this is an item with buttons var item = this._getItem(e.target); if(item && item.querySelector('.item-options')) { - this._dragOp = new SlideDrag({ el: this.el }); + this._dragOp = new SlideDrag({ el: this.el, canSwipe: this.canSwipe }); this._dragOp.start(e); e.preventDefault(); } @@ -506,10 +515,6 @@ _handleDrag: function(e) { var _this = this, content, buttons; - if (!this.canSwipe) { - return; - } - if(Math.abs(e.gesture.deltaY) > 5) { this._didDragUpOrDown = true; } diff --git a/scss/_items.scss b/scss/_items.scss index 87f382ad10..e9ff690a6c 100644 --- a/scss/_items.scss +++ b/scss/_items.scss @@ -601,7 +601,6 @@ button.item-button-right:after, width: $item-left-edit-open-width; height: 100%; line-height: 100%; - opacity: 0; .button { height: 100%; @@ -619,7 +618,6 @@ button.item-button-right:after, .list-left-editing .item-left-edit, .item-left-editing .item-left-edit { @include translate3d($item-left-edit-left, 0, 0); - opacity: 1; } @@ -640,6 +638,7 @@ button.item-button-right:after, // ------------------------------- .item-right-edit { + @include transition(all $item-edit-transition-function $item-edit-transition-duration); position: absolute; top: 0; right: 0; @@ -675,6 +674,8 @@ button.item-button-right:after, .item-reordering { // item is actively being reordered position: absolute; + left: 0; + top: 0; z-index: $z-index-item-reordering; width: 100%; box-shadow: 0px 0px 10px 0px #aaa; @@ -689,6 +690,18 @@ button.item-button-right:after, opacity: 0.7; } +.item-left-edit.item-delete, +.item-right-edit.item-reorder { + opacity: 1; + &.ng-hide { + opacity: 0; + } + &.ng-hide-add, + &.ng-hide-remove { + display: block !important; + } +} + /** * The hidden right-side buttons that can be exposed under a list item @@ -707,7 +720,3 @@ button.item-button-right:after, border-radius: 0; } } - -.item-options-hide .item-options { - display: none; -}