Merge pull request #269 from gwendall/master

Tweaked ionKeyboard, ionModal, ionPopup
This commit is contained in:
Nick Wientge 2015-08-10 08:50:42 -07:00
commit ea7f80a266
7 changed files with 162 additions and 78 deletions

View file

@ -0,0 +1,64 @@
Meteor.startup(function() {
if (Meteor.isCordova) {
var getScrollContainer = function($element) {
return $($element.parents('.content.overflow-scroll').get(0));
}
var focusPadding = 20;
var isBehindKeyboard = function($focused, keyboardHeight) {
var keyboardTop = $(window).innerHeight() - keyboardHeight;
var focusedBottom = $focused.offset().top + $focused.innerHeight();
var focusedIsBehindKeyboard = focusedBottom > keyboardTop - focusPadding;
return focusedIsBehindKeyboard;
}
var getScrollToPosition = function($focused, $container, keyboardHeight) {
var scrollTo = $container.scrollTop() + $focused.offset().top - $container.offset().top - focusPadding;
return scrollTo;
}
// Scroll to make input on top of the page
// #TODO Correct behavior should be: if the input is behind the keyboard, scroll to make it visible on top of the keyboard
scrollToFocusedElement = function($focused, keyboardHeight) {
$focused = $focused || $(':focus');
var $container = getScrollContainer($focused);
if (!$focused.length || !$container.length) return;
var focusedIsBehindKeyboard = isBehindKeyboard($focused, keyboardHeight);
if (!focusedIsBehindKeyboard) return;
var scrollTo = getScrollToPosition($focused, $container, keyboardHeight);
setTimeout(function() {
$container.animate({ scrollTop: scrollTo }, {
duration: 400,
complete: function() {
// Fix floating input cursor bug (https://github.com/twbs/bootstrap/issues/14708, https://github.com/cubiq/iscroll/issues/178)
var display = $focused.css('display');
$focused.css({ display: 'none' }).css({ display: display });
}
});
}, 0);
}
var $scrollContainer, scrollPosStart, scrollPosEnd, scrollDistance, scrollHappened, scrollThreshold = 10;
// Trigger focus on input through touchend for long taps
$(document).on('touchstart', function(event) {
$scrollContainer = getScrollContainer($(event.target));
scrollPosStart = $scrollContainer.scrollTop();
});
$(document).on('touchend', function(event) {
$scrollContainer = getScrollContainer($(event.target));
scrollPosEnd = $scrollContainer.scrollTop();
scrollDistance = Math.abs(scrollPosStart - scrollPosEnd);
scrollHappened = scrollDistance > scrollThreshold;
var $target = $(event.target);
var isInput = _.contains(['INPUT', 'TEXTAREA'], event.target.tagName);
var isFocused = $target.is(':focus');
if (isInput && !isFocused && !scrollHappened) $target.focus();
});
}
});

View file

@ -64,34 +64,19 @@ window.addEventListener('native.keyboardshow', function (event) {
$(el).css({bottom: keyboardHeight});
});
});
// Scroll to the focused element
scrollToFocusedElement(null, keyboardHeight);
Meteor.startup(function() {
if (Meteor.isCordova) {
// Scroll to make input on top of the page
// #TODO Correct behavior should be: if the input is behind the keyboard, scroll to make it visible on top of the keyboard
$(document).delegate('input, textarea', 'touchstart, focus', function(event) {
var $input = $(event.currentTarget);
var $container = $($(event.currentTarget).parents('.content.overflow-scroll').get(0));
var contentOffset = $container.offset().top;
var padding = 10;
var scrollTo = $container.scrollTop() + $input.offset().top - contentOffset - padding;
setTimeout(function() {
$container.scrollTop(scrollTo);
}, 0);
});
}
});
window.addEventListener('native.keyboardhide', function (event) {
// TODO: Android is having problems
if (Platform.isAndroid()) {
return;
}
$('input, textarea').blur();
// $('input, textarea').blur();
$('body').removeClass('keyboard-open');
// Detach any elements that were attached
@ -103,4 +88,5 @@ window.addEventListener('native.keyboardhide', function (event) {
$('.content.overflow-scroll').each(function (index, el) {
$(el).css({bottom: $(el).data('ionkeyboard.bottom')});
});
});

View file

@ -7,7 +7,7 @@ IonLoading = {
customTemplate: null,
backdrop: false
}, userOptions);
if (options.backdrop) {
IonBackdrop.retain();
$('.backdrop').addClass('backdrop-loading');
@ -42,7 +42,10 @@ IonLoading = {
$loadingEl.removeClass('visible');
Blaze.remove(this.view);
this.view = null;
}.bind(this), 400);
}.bind(this), 0);
}
Meteor.setTimeout(function() {
$('.loading-container').remove();
}, 0)
}
};

View file

@ -7,62 +7,79 @@ IonModal = {
leaveActiveClass: 'ng-leave-active',
view: {},
views: [],
open: function (templateName, data) {
this.template = Template[templateName];
var view = Blaze.renderWithData(this.template, data, $('body').get(0));
if (!this.view[templateName]) {
this.view[templateName] = [view];
} else {
this.view[templateName].push(view);
}
this.views.push(templateName);
var $modalBackdrop = $(view.firstNode());
if (this.views.length === 1) {
$modalBackdrop.addClass('active');
}
var $modalEl = $modalBackdrop.find('.modal').eq(0);
$modalEl.addClass(this.enterClasses.join(' '));
$modalEl.on(this.transitionEndEvent, function () {
$('body').addClass('modal-open');
$modalEl.removeClass(this.enterClasses.join(' ')).removeClass(this.enterActiveClass).off('webkitAnimationEnd');
}.bind(this));
Meteor.setTimeout(function () {
$modalEl.addClass(this.enterActiveClass);
}.bind(this), 10);
this.template = Template[templateName];
this.views.push(templateName);
if (!this.view[templateName]) this.view[templateName] = [];
var view = Blaze.renderWithData(this.template, data, $('.ionic-body').get(0));
this.view[templateName].push(view);
var $modalBackdrop = $(view.firstNode());
var $modal = $('.modal', $modalBackdrop);
if (this.views.length === 1) {
$modalBackdrop.addClass('active');
}
$modal.addClass(this.enterClasses.join(' '));
Meteor.setTimeout(function () {
$modal.addClass(this.enterActiveClass);
}.bind(this), 50);
}.bind(this), 0);
},
close: function (templateName) {
close: function () {
var templateName = this.views.pop();
var view = this.view[templateName].pop();
var $modalBackdrop = $(view.firstNode());
this.templateClosed = templateName;
Meteor.setTimeout(function () {
if (!this.views.length) {
$modalBackdrop.removeClass('active');
}
var templateName = this.templateClosed || this.views.slice(-1)[0];
delete this.templateClosed;
var $modalEl = $modalBackdrop.find('.modal').eq(0);
$modalEl.addClass(this.leaveClasses.join(' '));
var view = (this.view[templateName] || []).slice(-1)[0];
if (!view) return;
Meteor.setTimeout(function() {
$modalEl.addClass(this.leaveActiveClass);
}.bind(this), 10);
var $modalBackdrop = $(view.firstNode());
var $modal = $('.modal', $modalBackdrop);
$modal.addClass(this.leaveClasses.join(' '));
Meteor.setTimeout(function () {
$modal.addClass(this.leaveActiveClass);
}.bind(this), 50);
$modalBackdrop.fadeOut(500, function() {
$('body').removeClass('modal-open');
});
}.bind(this), 0);
$modalEl.on(this.transitionEndEvent, function () {
$('body').removeClass('modal-open');
Blaze.remove(view);
}.bind(this));
}
};
$(document).delegate('.modal', IonModal.transitionEndEvent, function(e) {
var $modal = $(e.currentTarget);
if ($modal.hasClass(IonModal.enterClasses.join(' ')) || $modal.hasClass(IonModal.enterActiveClasse)) {
$modal.removeClass(IonModal.enterClasses.join(' ')).removeClass(IonModal.enterActiveClass);
$('body').addClass('modal-open');
} else if ($modal.hasClass(IonModal.leaveClasses.join(' ')) || $modal.hasClass(IonModal.leaveActiveClasse)) {
var firstChild = $modal.children().first();
var templateName = getElementModalTemplateName(firstChild);
IonModal.views = _.without(IonModal.views, templateName);
var view = IonModal.view[templateName].pop();
var $modalBackdrop = $(view.firstNode());
$modalBackdrop.removeClass('active');
$modal.removeClass(IonModal.leaveClasses.join(' ')).removeClass(IonModal.leaveActiveClass).off(IonModal.transitionEndEvent);
$('body').removeClass('modal-open');
$(e.target).parents('.modal-backdrop').remove();
Blaze.remove(view);
}
});
Template.ionModal.created = function () {
this.data = this.data || {};
this.customTemplate = this.data.customTemplate || false;
@ -75,10 +92,10 @@ Template.ionModal.created = function () {
Template.ionModal.rendered = function () {
if (this.focusFirstInput) {
Meteor.setTimeout(function () {
if (!this._domrange) return;
this.$('input:first').focus();
}.bind(this), 600);
}
$(window).on('keyup.ionModal', function(event) {
event.stopImmediatePropagation();
if (event.which == 27) {
@ -119,11 +136,7 @@ Template.ionModal.helpers({
},
animation: function () {
if (this.animation) {
return this.animation;
} else {
return 'slide-in-up';
}
return this.animation || 'slide-in-up';
},
customTemplate: function () {
@ -139,6 +152,7 @@ Template.ionModal.helpers({
return classes.join(' ');
}
});
Template.ionModal.events({
@ -148,8 +162,16 @@ Template.ionModal.events({
IonModal.close();
}
},
'click [data-dismiss=modal]': function (event, template) {
IonModal.close();
var tplName = getElementModalTemplateName(event.currentTarget);
IonModal.close(tplName);
}
});
var getElementModalTemplateName = function(element) {
var modal = $(element).parents('.modal').get(0);
var modalView = Blaze.getView(modal);
var tplView = Meteor._get(modalView, 'parentView', 'parentView'); // Twice because the parent view is a #with block
var tplName = tplView.name.slice('Template.'.length, tplView.name.length);
return tplName;
};

View file

@ -121,12 +121,12 @@ IonPopup = {
},
close: function () {
var $backdrop = $(this.view.firstNode());
var $popup = $backdrop.find('.popup-container');
var $popup = this._domrange ? $(this.view.firstNode()).find('.popup-container') : $('.popup-container');
$popup.addClass('popup-hidden').removeClass('active');
setTimeout(function () {
$('body').removeClass('popup-open');
$('.backdrop').remove();
Blaze.remove(this.view);
}.bind(this), 100);
},

View file

@ -20,12 +20,12 @@ Template.ionSlideBox.rendered = function () {
return '<span class="slider-pager-page icon ion-record"></span>';
}
});
this.$('.ion-slide-box').on('afterChange', function (event, slick, currentSlide) {
$(this).trigger({type: 'onSlideChanged', index: currentSlide});
});
};
Template.ionSlideBox.destroyed = function () {
this.$('.ion-slide-box').slick('unslick');
var $slideBox = this.$('.ion-slide-box');
if ($slideBox.hasClass('slick-initialized')) $slideBox.slick('unslick');
};

View file

@ -11,7 +11,15 @@ Cordova.depends({
Package.onUse(function(api) {
api.versionsFrom("1.0");
api.use(["templating", "underscore", "fastclick", "iron:router@1.0.0", "tracker", "session"], "client");
api.use([
"templating",
"underscore",
"fastclick",
"iron:router@1.0.0",
"tracker",
"session",
"jquery"
], "client");
api.addFiles([
"vendor/snap.js",
@ -46,6 +54,7 @@ Package.onUse(function(api) {
"components/ionItem/ionItem.js",
"components/ionKeyboard/ionKeyboard.js",
"components/ionKeyboard/ionInputFocus.js",
"components/ionList/ionList.html",
"components/ionList/ionList.js",