From 82ef03b346f938b18672d5a2823f0f60202c9825 Mon Sep 17 00:00:00 2001 From: jbavari Date: Fri, 2 Oct 2015 12:40:22 -0600 Subject: [PATCH 1/8] fix(list): Fix for sticky titles in Chrome/Android. Currently broken --- ionic/components/item/item-group.ts | 126 ++++++++++++++++++++++++++- ionic/components/item/modes/ios.scss | 12 +-- ionic/util/dom.ts | 67 ++++++++++++++ ionic/util/util.ts | 32 +++++++ 4 files changed, 230 insertions(+), 7 deletions(-) diff --git a/ionic/components/item/item-group.ts b/ionic/components/item/item-group.ts index c3b8e80244..83abbd93c6 100644 --- a/ionic/components/item/item-group.ts +++ b/ionic/components/item/item-group.ts @@ -1,4 +1,8 @@ -import {Directive, ElementRef} from 'angular2/angular2'; +import {Directive, ElementRef, Host, Optional} from 'angular2/angular2'; +import {Content} from '../content/content'; +import {throttle} from '../../util/util'; +import {position, offset, CSS} from '../../util/dom'; +import {IonicConfig} from '../../config/config'; /** * TODO @@ -34,8 +38,126 @@ export class ItemGroupTitle { * TODO * @param {ElementRef} elementRef TODO */ - constructor(elementRef: ElementRef) { + constructor(elementRef: ElementRef, config: IonicConfig, @Host() content: Content) { this.isSticky = true; + this.content = content; this.ele = elementRef.nativeElement; + this.parent = this.ele.parentNode; + } + + onInit() { + + this.scrollContent = this.content.elementRef.nativeElement.children[0]; + + this.scrollMin = 0; + this.scrollMax = 0; + this.scrollTransition = 0; + this.isSticking = false; + + + this.scrollContent.addEventListener('scroll', event => this.scrollEvent(event)); + + this.calculateScrollLimits = scrollTop => { + var containerPosition = position(this.parent); + var elementOffset = offset(this.ele); + + var containerTop = containerPosition.top; + var containerHeight = containerPosition.height; + + var affixHeight = elementOffset.height; + + this.scrollMin = containerTop; + this.scrollMax = this.scrollMin + containerHeight; + this.scrollTransition = this.scrollMax - affixHeight; + }; + + // throttled version of the same calculation + let CALCULATION_THROTTLE_MS = 500; + this.throttledCalculateScrollLimits = throttle( + this.calculateScrollLimits, + CALCULATION_THROTTLE_MS, + {trailing: false} + ); + } + + applyTransform(element, transformString) { + // do not apply the transformation if it is already applied + if (element.style[CSS.transform] == transformString) { + } + else { + element.style[CSS.transform] = transformString; + } + } + + translateUp(element, dy, executeImmediately) { + var translateDyPixelsUp = dy == 0 ? 'translate3d(0px, 0px, 0px)' : 'translate3d(0px, -' + dy + 'px, 0px)'; + // if immediate execution is requested, then just execute immediately + // if not, execute in the animation frame. + if (executeImmediately) { + this.applyTransform(element, translateDyPixelsUp); + } + else { + // see http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + // see http://ionicframework.com/docs/api/utility/ionic.DomUtil/ + requestAnimationFrame( a => this.applyTransform(element, translateDyPixelsUp) ); + } + } + + createAffixClone() { + var clone = this.ele.cloneNode(true); + clone.style.position = 'absolute'; + clone.style.top = 0; + clone.style.left = 0; + clone.style.right = 0; + + this.scrollContent.parentNode.appendChild(clone); + return clone; + } + + scrollEvent(event) { + var scrollTop = event.target.scrollTop; + + // when scroll to top, we should always execute the immediate calculation. + // this is because of some weird problem which is hard to describe. + // if you want to experiment, always use the throttled one and just click on the page + // you will see all affix elements stacked on top + if (scrollTop == 0) { + this.calculateScrollLimits(scrollTop); + } + else { + this.throttledCalculateScrollLimits(scrollTop); + } + + // when we scrolled to the container, create the clone of element and place it on top + if (scrollTop >= this.scrollMin && scrollTop <= this.scrollMax) { + // we need to track if we created the clone just now + // that is important since normally we apply the transforms in the animation frame + // but, we need to apply the transform immediately when we add the element for the first time. otherwise it is too late! + var cloneCreatedJustNow = false; + + if (!this.affixClone) { + this.affixClone = this.createAffixClone(); + cloneCreatedJustNow = true; + this.isSticking = true; + } + + // if we're reaching towards the end of the container, apply some nice translation to move up/down the clone + // but if we're reached already to the container and we're far away than the end, move clone to top + if (scrollTop > this.scrollTransition) { + this.translateUp(this.affixClone, Math.floor(scrollTop - this.scrollTransition), cloneCreatedJustNow); + } else { + this.translateUp(this.affixClone, 0, cloneCreatedJustNow); + } + } else { + this.removeAffixClone(); + this.isSticking = false; + } + } + + removeAffixClone() { + if (this.affixClone) { + this.scrollContent.parentNode.removeChild(this.affixClone); + this.affixClone = null; + } } } diff --git a/ionic/components/item/modes/ios.scss b/ionic/components/item/modes/ios.scss index 1c102a7e4d..357731011e 100644 --- a/ionic/components/item/modes/ios.scss +++ b/ionic/components/item/modes/ios.scss @@ -26,14 +26,16 @@ $item-ios-divider-bg: #f5f5f5 !default; $item-ios-divider-color: #222 !default; $item-ios-divider-padding: 5px 15px !default; +.item-group-title { + padding: $item-ios-padding-top $item-ios-padding-right $item-ios-padding-bottom $item-ios-padding-left; + background-color: $item-ios-divider-bg; + color: $item-ios-divider-color; +} + .list { - .item-group-title { - padding: $item-ios-padding-top $item-ios-padding-right $item-ios-padding-bottom $item-ios-padding-left; - background-color: $item-ios-divider-bg; - color: $item-ios-divider-color; - } + .item-group { // Make sure the first and last items don't have borders > .item:first-of-type:before { diff --git a/ionic/util/dom.ts b/ionic/util/dom.ts index 2980fd0c0f..a5efd96d92 100644 --- a/ionic/util/dom.ts +++ b/ionic/util/dom.ts @@ -268,3 +268,70 @@ export function flushDimensionCache() { let dimensionCache = {}; let dimensionIds = 0; + +function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if (window.getComputedStyle) { + return window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; +} + +function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static') === 'static'; +} + +/** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ +export function parentOffsetEl(element) { + var offsetParent = element.offsetParent || document; + while (offsetParent && offsetParent !== document && isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || document; +}; + +/** + * Get the current coordinates of the element, relative to the offset parent. + * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/). + * @param {element} element The element to get the position of. + * @returns {object} Returns an object containing the properties top, left, width and height. + */ +export function position(element) { + var elBCR = offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element); + if (offsetParentEl != document) { + offsetParentBCR = offset(offsetParentEl); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element.getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; +} + +/** +* Get the current coordinates of the element, relative to the document. +* Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/). +* @param {element} element The element to get the offset of. +* @returns {object} Returns an object containing the properties top, left, width and height. +*/ +export function offset(element) { + var boundingClientRect = element.getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + (window.pageYOffset || document.documentElement.scrollTop), + left: boundingClientRect.left + (window.pageXOffset || document.documentElement.scrollLeft) + }; +} diff --git a/ionic/util/util.ts b/ionic/util/util.ts index 3e5c396109..d0ee00d697 100644 --- a/ionic/util/util.ts +++ b/ionic/util/util.ts @@ -180,3 +180,35 @@ export function getQuerystring(url, key) { } return queryParams; } + +/** + * Throttle the given fun, only allowing it to be + * called at most every `wait` ms. + */ +export function throttle(func, wait, options) { + var context, args, result; + var timeout = null; + var previous = 0; + options || (options = {}); + var later = function() { + previous = options.leading === false ? 0 : Date.now(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = Date.now(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; +} From 33dc40aad145f60eb93ae696cbf451af1722921f Mon Sep 17 00:00:00 2001 From: jbavari Date: Fri, 2 Oct 2015 13:12:04 -0600 Subject: [PATCH 2/8] Fix(demo): Fix infinite list demo, remove ion-view --- ionic/components/list/test/infinite/main.html | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/ionic/components/list/test/infinite/main.html b/ionic/components/list/test/infinite/main.html index f7ce1dc08b..d6f233e191 100644 --- a/ionic/components/list/test/infinite/main.html +++ b/ionic/components/list/test/infinite/main.html @@ -1,15 +1,10 @@ + - + + + {{item.title}} + + + - - - - - {{item.title}} - - - - - - - + From 46fc56fb95c411e9194e227b79b56e89d12aee2c Mon Sep 17 00:00:00 2001 From: jbavari Date: Fri, 2 Oct 2015 14:10:05 -0600 Subject: [PATCH 3/8] Fix(demo): Remove ion-view from demos for scoll (basic, pull-to-refresh) and tabs (tab-bar-bottom) --- ionic/components/scroll/test/basic/main.html | 4 ++-- .../scroll/test/pull-to-refresh/main.html | 4 ++-- .../tabs/test/tab-bar-bottom/main.html | 18 +++++++----------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/ionic/components/scroll/test/basic/main.html b/ionic/components/scroll/test/basic/main.html index 849a3fa850..3637a85aae 100644 --- a/ionic/components/scroll/test/basic/main.html +++ b/ionic/components/scroll/test/basic/main.html @@ -1,4 +1,4 @@ - + Scroll @@ -20,7 +20,7 @@ - +