How to implement Swipe Gesture in IonicFramework? - javascript

I want to attach swipe left & swipe right on an image using IonicFramework.
From the documentation, I only got these, but no example yet:
http://ionicframework.com/docs/api/service/$ionicGesture/
http://ionicframework.com/docs/api/utility/ionic.EventController/#onGesture
Can anyone help provide sample HTML & JS to listen to gesture event?
P.S.: Previously, I managed to implement it using angularjs SwipeLeft and SwipeRight directive: https://docs.angularjs.org/api/ngTouch/service/$swipe . But now I wish to use the functions provided by ionicframework.

Ionic has a set of directives that you can use to manage various gestures and events. This will attach a listener to an element and fire the event when the particular event is detected. There are events for holding, tapping, swiping, dragging, and more. Drag and swipe also have specific directives to only fire when the element is dragged/swiped in a direction (such as on-swipe-left).
Ionic docs: http://ionicframework.com/docs/api/directive/onSwipe/
Markup
<img src="image.jpg" on-swipe-left="swipeLeft()" />
Controller
$scope.swipeLeft = function () {
// Do whatever here to manage swipe left
};

You can see some of sample which you can do with ionic from this site. One of the drawback is that the gesture will fire multiple instances during drag. If you catch it with a counter you can check how much the instances being fired per gesture. This is my hack method within the firing mechanism of of drag gesture you might need to change the dragCount integer to see which one is suite for your instance.
var dragCount = 0;
var element = angular.element(document.querySelector('#eventPlaceholder'));
var events = [{
event: 'dragup',
text: 'You dragged me UP!'
},{
event: 'dragdown',
text: 'You dragged me Down!'
},{
event: 'dragleft',
text: 'You dragged me Left!'
},{
event: 'dragright',
text: 'You dragged me Right!'
}];
angular.forEach(events, function(obj){
var dragGesture = $ionicGesture.on(obj.event, function (event) {
$scope.$apply(function () {
$scope.lastEventCalled = obj.text;
//console.log(obj.event)
if (obj.event == 'dragleft'){
if (dragCount == 5){
// do what you want here
}
dragCount++;
if (dragCount > 10){
dragCount = 0;
}
//console.log(dragCount)
}
if (obj.event == 'dragright'){
if (dragCount == 5){
// do what you want here
}
dragCount++;
if (dragCount > 10){
dragCount = 0;
}
//console.log(dragCount)
}
});
}, element);
});
add this line in your html template
<ion-content id="eventPlaceholder" has-bouncing="false">{{lastEventCalled}}</ion-content>

Related

Best way to hide 10000 dropdown menus

Context -
I have a chat component and each individual chat message has a dropdown.
And the dropdown menu is opened by clicking the "More Options icon"(3 dots).
Each individual chat message is a "backbone item view"
One solution is to listen to click on "body", loop through all the menus and then close the dropdown by removing a class on it.
$("body").on("click", function() {
$(".drop-down-menu").each(function(idx, item) {
$(item).removeClass("open"); // open class indicated it is open via CSS
});
});
The CSS -
.drop-down-menu {
visibility: hidden;
opacity: 0;
&.open {
opacity: 1;
visibility: visible;
}
}
Will there be any performance impact if there are 10,000 messages or more?
Hence, I am looking for the best solution to hide the drop down if user clicks anywhere on the screen.
Thanks.
You can make some trivial changes that should improve the performance of your code. The first thing is that there's no reason to loop like you are doing. jQuery objects are collections and jQuery operations usually loop over the elements of a jQuery object. So:
$("body").on("click", function() {
$(".drop-down-menu").removeClass("open");
});
This will automatically remove the class open from all elements matched by the selector ".drop-down-menu". jQuery will still go over a loop internally, but it is faster to let jQuery iterate by itself than to have .each call your own callback and then inside the callback create a new jQuery object on which to call .removeClass.
Furthermore, you logically know that removing the open class from elements that do not have this class is pointless. So you can narrow the operation to only those elements where removing open makes sense:
$("body").on("click", function() {
$(".drop-down-menu.open").removeClass("open");
});
These are principles that are widely applicable and that have trivial cost to implement. Anything more than this runs into the realm of optimizations that may have downsides, and should be supported by actually profiling your code. You could replace the jQuery code with code that only uses stock DOM calls but then if you need support for old browsers the cost of dealing with this and that quirk may not be worth it. And if you are using stock DOM methods, there are different approaches that may yield different performance increases, at the cost of code complexity.
Louis is offering a quick fix with efficient jQuery selectors.
For the long run, I would suggest making each message a MessageView component which has a ContextMenuView component. That way, each view only has one menu to take care of.
Catching clicks outside of an element
Then, use the following ClickOutside view as the context menu base view. It looks complicated, but it only wraps the blur and focus DOM events to know if you clicked outside the view.
It offers a simple onClickOutside callback for the view itself and a click:outside event which is triggered on the element.
The menu view now only has to implement the following:
var ContextMenuView = ClickOutside.extend({
toggle: function(val) {
this.$el.toggleClass("open", val);
this.focus(); // little requirement
},
// here's where the magic happens!
onClickOutside: function() {
this.$el.removeClass("open");
}
});
See the demo
var app = {};
(function() {
var $body = Backbone.$(document.body);
/**
* Backbone view mixin that enables the view to catch simulated
* "click:outside" events (or simple callback) by tracking the
* mouse and focusing the element.
*
* Additional information: Since the blur event is triggered on a mouse
* button pressed and the click is triggered on mouse button released, the
* blur callback gets called first which then listen for click event on the
* body to trigger the simulated outside click.
*/
var ClickOutside = app.ClickOutside = Backbone.View.extend({
events: {
"mouseleave": "_onMouseLeave",
"mouseenter": "_onMouseEnter",
"blur": "_onBlur",
},
/**
* Overwrite the default constructor to extends events.
*/
constructor: function() {
this.mouseInside = false;
var proto = ClickOutside.prototype;
this.events = _.extend({}, proto.events, this.events);
ClickOutside.__super__.constructor.apply(this, arguments);
this.clickOnceEventName = 'click.once' + this.cid;
},
/**
* Hijack this private method to ensure the element has
* the tabindex attribute and is ready to be used.
*/
_setElement: function(el) {
ClickOutside.__super__._setElement.apply(this, arguments);
var focusEl = this.focusEl;
if (focusEl && !this.$focusElem) {
this.$focusElem = focusEl;
if (!(focusEl instanceof Backbone.$)) {
this.$focusElem = Backbone.$(focusEl);
}
} else {
this.$focusElem = this.$el;
}
this.$focusElem.attr('tabindex', -1);
},
focus: function() {
this.$focusElem.focus();
},
unfocus: function() {
this.$focusElem.blur();
$body.off(this.clickOnceEventName);
},
isMouseInside: function() {
return this.mouseInside;
},
////////////////////////////
// private Event handlers //
////////////////////////////
onClickOutside: _.noop,
_onClickOutside: function(e) {
this.onClickOutside(e);
this.$focusElem.trigger("click:outside", e);
},
_onBlur: function(e) {
var $focusElem = this.$focusElem;
if (!this.isMouseInside() && $focusElem.is(':visible')) {
$body.one(this.clickOnceEventName, this._onClickOutside.bind(this));
} else {
$focusElem.focus(); // refocus on inside click
}
},
_onMouseEnter: function(e) {
this.mouseInside = true;
},
_onMouseLeave: function(e) {
this.mouseInside = false;
},
});
var DropdownView = app.Dropdown = ClickOutside.extend({
toggle: function(val) {
this.$el.toggle(val);
this.focus();
},
onClickOutside: function() {
this.$el.hide();
}
});
})();
var DemoView = Backbone.View.extend({
className: "demo-view",
template: $("#demo-template").html(),
events: {
"click .toggle": "onToggleClick",
},
initialize: function() {
this.dropdown = new app.Dropdown();
},
render: function() {
this.$el.html(this.template);
this.dropdown.setElement(this.$(".dropdown"));
return this;
},
onToggleClick: function() {
this.dropdown.toggle(true);
},
});
$("#app")
.append(new DemoView().render().el)
.append(new DemoView().render().el);
html,
body {
height: 100%;
width: 100%;
}
.demo-view {
position: relative;
margin-bottom: 10px;
}
.dropdown {
z-index: 2;
position: absolute;
top: 100%;
background-color: gray;
padding: 10px;
outline: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<div id="app"></div>
<script type="text/template" id="demo-template">
<button type="button" class="toggle">Toggle</button>
<div class="dropdown" style="display:none;">
This is a drop down menu.
</div>
</script>
Alternatives to detect a click outside an element
If you don't want, or can't use blur and focus events, take a look at How do I detect a click outside an element? for alternative techniques.
Lazy initialization of views
Another way to make an SPA more efficient is to delay the creation of new view to the very moment you need it. Instead a creating 10k context menu views, wait for the first time the user clicks on the toggle button and create a new view if it doesn't exist yet.
toggleMenu: function(){
var menuView = this.menuView;
if (!menuView) {
menuView = this.menuView = new ContextMenuView();
this.$('.dropdown').html(menuView.render().el);
}
menuView.toggle();
}
Pagination
Passed a certain threshold of HTML inside a webpage, the browser starts to lag and it impedes the user experience. Instead of dumping 10k views into a div, only show like a 100, or the minimum to cover the visible space.
Then, when scrolling to an edge (top or bottom), append or prepend new views on demand. Like the message list in any web-based chat app, like messenger.com.
Since you will only have one drop down menu open at a time, maybe you can keep a pointer to the element or index of the element it is attached to, instead of looping through all the menus.

Selecting zones for touch events with jQuery

I'm using https://github.com/n33/jquery.touch for creating touch events.
Wanting to use some gestures around all the body but the only map object with id "map".
I'm trying with:
$('body').not('#map');
and other variations, but these gestures still work in the map.
My code:
var touch = $('#body').not('#map');
touch.enableTouch({useMouse: true});
touch.on('doubleTap', function() { gestures("double"); });
If you are trying to add an event handler you'd need to do something like:
var touch = $('#body'),
exclude = $('#map');
touch.enableTouch({useMouse: true});
touch.on('doubleTap', function(e) {
var $touchedElement = $(e.target);
if (!$touchedElement.is(exclude) {
gestures("double");
}
});
You probably have to set this in the selector itself.
$('body:not(#map)').touchFunction();

Javascript events on top-of-window enter and leave?

I'm seeking a mouse event to detect when the mouse enter the top of the window, and leaves the top of the window. I don't mean the top of the webpage, but the top of the window.
There's no pre-existing "element" on the page i'm trying to attach the event to, but i think programmatically adding an invisible, fixed html element to the top of the page might be ok.
I like the clientY method with onmousemove, but that will fire repeatedly, which i don't want-- only want firing on enter and leave. Don't want my code to have to handle multiple firings.
This should work with ANY webpage-- i do not have any control over the HTML on the page (except for elements i add to the page programmatically).
Need only support modern browsers, simplest method possible, no jquery.
This method works great! But it prevents clicking elements behind it, which is not ok.
(function (){
var oBanana = document.createElement("div");
oBanana.style.position = "fixed"
oBanana.style.top = "0"
oBanana.style.left = "0"
oBanana.style.height= "100px"
oBanana.style.width = "100%"
oBanana.addEventListener("mouseover", function(event) {alert('in');});
oBanana.addEventListener("mouseout", function(event) {alert('out');});
document.body.appendChild(oBanana);
})();
Next i tried this, which inserts a small hotzone at the top of the page. I realized that, due to my scenario, i DON'T want mouse-out on the hotzone-- rather i want mouseover on everything BELOW the hotzone. Here's my first attempt at that, but fails because the hotzone gets the body event, plus the body event fires repeatedly:
(function (){
var oHotzone = document.createElement("div");
oHotzone.id = "fullscreen-hotzone"
oHotzone.style.position = "fixed"
oHotzone.style.top = "0"
oHotzone.style.left = "0"
oHotzone.style.height= "10px"
oHotzone.style.width = "100%"
oHotzone.addEventListener("mouseover", function(event) {alert('hotzone');});
document.body.appendChild(oHotzone);
document.body.style.marginTop = "10px"
document.body.addEventListener("mouseover", function(event) {alert('body');});
})();
Appreciate any help!
Thx!
It's the simplest you can do with vanilla javascript.
Function:
// create a one-time event
function onetime(node, type, callback) {
// create event
node.addEventListener(type, function(e) {
// remove event
e.target.removeEventListener(e.type, arguments.callee);
// call handler
return callback(e);
});
}
Implementation:
// one-time event
onetime(document.getElementById("hiddenTopElement"), "mouseenter", handler);
onetime(document.getElementById("hiddenTopElement"), "mouseleave" , hanlder);
// handler function
function handler(e) {
alert("You'll only see this once!");
}
my OP could have probably been stated better, but i'm happy with this solution. The fixed div blocked hover events on the body below, so the body hover event does not happen until the mouse leaves the hotzone. Perfect.
// create hotzone and add event
var oHotzone = document.createElement("div");
oHotzone.id = "fullscreen-hotzone"
oHotzone.style.position = "fixed"
oHotzone.style.top = "0"
oHotzone.style.left = "0"
oHotzone.style.height= "10px"
oHotzone.style.width = "100%"
oHotzone.addEventListener("mouseenter", function(event) {alert('hotzone');});
document.body.appendChild(oHotzone);
document.body.addEventListener("mouseenter", function(event) {alert('body');});

Touch events in JavaScript perform two different actions

Since I experienced a very strange issue with different touch event libraries (like hammer.js and quo.js) I decided to develop the events I need on my own. The Issue I'm talking about is that a touch is recognized twice if a hyperlink appears on the spot where I touched the screen. This happens on iOS as well as Android.
Imagine an element on a web page that visually changes the content of the screen if you touch it. And if the new page shows a hyperlink (<a href="">) at the same spot where I touched the screen before that new hyperlink gets triggered as well.
Now I developed my own implementation and I noticed: I'm having the same problem! Now is the question: Why?
What I do is the following (yes, I'm using jQuery):
(see source code below #1)
This function is only used with some special elements, not hyperlinks. So hyperlinks still have the default behavior.
The problem only affects hyperlinks. It doesn't occur on other elements that use the event methods showed above.
So I can imagine that not a click event is fired on the same element I touch but a click 'action' is performed at the same spot where I touched the screen after the touch event was processed. At least this is what it feels like. And since I only catch the click event on the element I actually touch I don't catch the click event on the hyperlink - and actually that shouldn't be necessary.
Does anyone know what causes this behavior?
Full source codes
#1 - attatch event to elements
$elements is a jQuery object returned by $( selector );
callback is the function that should be called if a tap is detected
helper.registerTapEvent = function($elements, callback) {
var touchInfo = {
maxTouches: 0
};
function evaluate(oe) {
var isSingleTouch = touchInfo.maxTouches === 1,
positionDifferenceX = Math.abs(touchInfo.startX - touchInfo.endX),
positionDifferenceY = Math.abs(touchInfo.startY - touchInfo.endY),
isAlmostSamePosition = positionDifferenceX < 15 && positionDifferenceY < 15,
timeDifference = touchInfo.endTime - touchInfo.startTime,
isShortTap = timeDifference < 350;
if (isSingleTouch && isAlmostSamePosition && isShortTap) {
if (typeof callback === 'function') callback(oe);
}
}
$elements
.on('touchstart', function(e) {
var oe = e.originalEvent;
touchInfo.startTime = oe.timeStamp;
touchInfo.startX = oe.changedTouches.item(0).clientX;
touchInfo.startY = oe.changedTouches.item(0).clientY;
touchInfo.maxTouches = oe.changedTouches.length > touchInfo.maxTouches ? oe.touches.length : touchInfo.maxTouches;
})
.on('touchend', function(e) {
var oe = e.originalEvent;
oe.preventDefault();
touchInfo.endTime = oe.timeStamp;
touchInfo.endX = oe.changedTouches.item(0).clientX;
touchInfo.endY = oe.changedTouches.item(0).clientY;
if (oe.touches.length === 0) {
evaluate(oe);
}
})
.on('click', function(e) {
var oe = e.originalEvent;
oe.preventDefault();
});
}
#2 - the part how the page transition is done
$subPageElem is a jQuery object of the page that should be displayed
$subPageElem.prev() returns the element of the current page that hides (temporarily) when a new page shows up.
$subPageElem.css({
webkitTransform: 'translate3d(0,0,0)',
transform: 'translate3d(0,0,0)'
}).prev().css({
webkitTransform: 'translate3d(-5%,0,-100px)',
transform: 'translate3d(-5%,0,-100px)'
});
I should also mention that the new page $subPageElem is generated dynamically and inserted into the DOM. I. e. that link that gets triggered (but shouldn't) doesn't even exist in the DOM when I touch/release the screen.

Webkit transitionEnd event grouping

I have a HTML element to which I have attached a webkitTransitionEnd event.
function transEnd(event) {
alert( "Finished transition!" );
}
var node = document.getElementById('node');
node.addEventListener( 'webkitTransitionEnd', transEnd, false );
Then I proceed to change its CSS left and top properties like:
node.style.left = '400px';
node.style.top = '400px';
This causes the DIV to move smoothly to the new position. But, when it finishes, 2 alert boxes show up, while I was expecting just one at the end of the animation. When I changed just the CSS left property, I get one alert box - so this means that the two changes to the style are being registered as two separate events. I want to specify them as one event, how do I do that?
I can't use a CSS class to apply both the styles at the same time because the left and top CSS properties are variables which I will only know at run time.
Check the propertyName event:
function transEnd(event) {
if (event.propertyName === "left") {
alert( "Finished transition!" );
}
}
var node = document.getElementById('node');
node.addEventListener( 'webkitTransitionEnd', transEnd, false );
That way, it will only fire when the "left" property is finished. This would probably work best if both properties are set to the same duration and delay. Also, this will work if you change only "left", or both, but not if you change only "top".
Alternatively, you could use some timer trickery:
var transEnd = function anon(event) {
if (!anon.delay) {
anon.delay = true;
clearTimeout(anon.timer);
anon.timer = setTimeout(function () {
anon.delay = false;
}, 100);
alert( "Finished transition!" );
}
};
var node = document.getElementById('node');
node.addEventListener( 'webkitTransitionEnd', transEnd, false );
This should ensure that your code will run at most 1 time every 100ms. You can change the setTimeout delay to suit your needs.
just remove the event:
var transEnd = function(event) {
event.target.removeEventListener("webkitTransitionEnd",transEnd);
};
it will fire for the first property and not for the others.
If you prefer it in JQuery, try this out.
Note there is an event param to store the event object and use within the corresponding function.
$("#divId").bind('oTransitionEnd transitionEnd webkitTransitionEnd', event, function() {
alert(event.propertyName)
});
from my point of view the expected behaviour of the code would be to
trigger an alert only when the last transition has completed
support transitions on any property
support 1, 2, many transitions seamlessly
Lately I've been working on something similar for a page transition manager driven by CSS timings.
This is the idea
// Returs the computed value of a CSS property on a DOM element
// el: DOM element
// styleName: CSS property name
function getStyleValue(el, styleName) {
// Not cross browser!
return window.getComputedStyle(el, null).getPropertyValue(styleName);
}
// The DOM element
var el = document.getElementById('el');
// Applies the transition
el.className = 'transition';
// Retrieves the number of transitions applied to the element
var transitionProperties = getStyleValue(el, '-webkit-transition-property');
var transitionCount = transitionProperties.split(',').length;
// Listener for the transitionEnd event
function eventListener(e) {
if (--transitionCount === 0) {
alert('Transition ended!');
el.removeEventListener('webkitTransitionEnd', eventListener);
}
}
el.addEventListener('webkitTransitionEnd', eventListener, false);
You can test here this implementation or the (easier) jQuery version, both working on Webkit only
If you are using webkit I assume you are mobilizing a web-application for cross platform access.
If so have you considered abstracting the cross platform access at the web-app presentation layer ?
Webkit does not provide native look-and-feel on mobile devices but this is where a new technology can help.

Categories

Resources