mirror of
https://github.com/donl/meteor-ionic.git
synced 2026-05-26 22:06:41 -06:00
Merge pull request #269 from gwendall/master
Tweaked ionKeyboard, ionModal, ionPopup
This commit is contained in:
commit
ea7f80a266
7 changed files with 162 additions and 78 deletions
64
components/ionKeyboard/ionInputFocus.js
Normal file
64
components/ionKeyboard/ionInputFocus.js
Normal 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();
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
|
@ -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')});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
};
|
||||
|
|
|
|||
11
package.js
11
package.js
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue