Is it possible to handle swipe gestures in PWA application? - progressive-web-apps

I'm creating a PWA, ASP server side and JS client side.
Users interact nicely with it using buttons.
The boss ask me if we can implement something like "scroll between app screen" or "perform some operation (edit, delete..) on elements" using the swipe gesture, as many native apps do.
Is there an easy way? or any way anyhow?
Thanks!

There are a couple of libraries that can help implement touch gestures for PWAs - have a look at Hammerjs:
https://hammerjs.github.io/
Your question is fairly generic though, there might be better solutions but that's just one off the top of my head

Have a drawer component in a PWA that can swipe to close using similar JS as below. Made an example that logs out the direction of a swipe / gesture here: https://jsfiddle.net/jamiesmith/e9gndqpc/3/
Below checks a function called ignoreSwipe so we can add a class of ignoreSwipe to some element(s) we specifically want to ignore gestures from.
Some standards below makes use of:
TouchEvent.touches - https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/touches
TouchEvent.touchstart & touchmove - https://developer.mozilla.org/en-US/docs/Web/API/Touch_events
let _xDown, _yDown;
document.querySelector('div.app')
.addEventListener(
'touchstart',
handleTouchStart,
false
);
document.querySelector('div.app')
.addEventListener(
'touchmove',
handleTouchMove,
false
);
function ignoreSwipe(event) {
// if some touches come from elements with ignoreswipe class > ignore
return Array.from(event.touches).some((t) =>
t.target.classList.contains('noswipe')
);
}
function handleTouchStart(event) {
if (ignoreSwipe(event)) {
_xDown = undefined;
_yDown = undefined;
return;
}
const firstTouch = event.touches[0];
_xDown = firstTouch.clientX;
_yDown = firstTouch.clientY;
}
function handleTouchMove(event) {
if (!_xDown || !_yDown) {
return;
}
const xUp = event.touches[0].clientX;
const yUp = event.touches[0].clientY;
const xDiff = _xDown - xUp;
const yDiff = _yDown - yUp;
if (Math.abs(xDiff) > Math.abs(yDiff)) {
/*most significant*/
if (xDiff > 0) {
/* left swipe */
console.log('app: left swipe ', true);
} else {
/* right swipe */
console.log('app: right swipe ', true);
}
} else {
if (yDiff > 0) {
/* up swipe */
console.log('app: up swipe ', true);
} else {
/* down swipe */
console.log('app: down swipe ', true);
}
}
/* reset values */
_xDown = null;
_yDown = null;
}

Related

i am using Cross app navigation in my app I want to navigate directly to Object details

i am using Cross app navigation in my app
I want to navigate directly to Object details
#Object-Display&/Details/000000910500453026
First time it navigates properly , but when i try to repeat the flow , it automatically navigates to root view
**#Object-Display
I just call the cross app navigator and call the app
oCrossAppNavigator.hrefForExternal({
target: {
semanticObject: "Object",
action: `Display&/Details/${sProductCode}`,
},
})) ||
"";
oCrossAppNavigator.toExternal({
target: {
shellHash: sHash,
},
});
Hi All, i am using Cross app navigation in my app
I want to navigate directly to Object details
**<URL>#Object-Display&/Details/000000910500453026**
First time it navigates properly , but when i try to repeat the flow , it automatically navigates to root view
****<URL>#Object-Display**
I just call the cross app navigator and call the app
oCrossAppNavigator.hrefForExternal({
target: {
semanticObject: "Object",
action: `Display&/Details/${sProductCode}`,
},
})) ||
"";
oCrossAppNavigator.toExternal({
target: {
shellHash: sHash,
},
});
I had the same issue once. Are you using the cross app navigation in combination with the Admin template in SAP Portal? If so... check if the navigation item is in a multi-level hierarchy. When you use any form of navigation to an application that is located in the lower levels of the menu hierarchy then the Shell.controller.js is automatically routing you back to the root.
To solve this, I used a SAP Portal plugin and hacked the Shell.controller.js.
Code snippet:
/**
* OVERRIDE! sap.ushell.services.cp.designtime.renderers.cp.admin.Shell -> _selectControlByHash
* Default SAP Portal behaviour is changed to support deep-link navigation.
* #param {navigation intent} h
*/
customSelectControlByHash(h) {
const a = this.arrRefControls;
const f = this.getView().fixedItemsList;
let b;
let l;
h = h.replace("#", "");
if (a) {
b = a[h] || a[h.split("?")[0]];
if (!b) {
const s = sap.ushell.Container.getService("SiteService").getSiteModel();
const p = s.getAllPagesAndApps();
const d = s.getPageByAlias(p, h);
if (d) {
b = a[d.getIntentAction()];
}
}
if (b) {
if (f) {
f.setSelectedItem();
}
l = b.getParent();
/** Replacing ORIGINAL CODE **/
// if (l && l.setSelectedItem) {
// l.setSelectedItem(b);
// } else if (l && l.setExpanded) {
// l.setExpanded(true);
// if (this.siteService.siteModel.isAdminSite() && !this.siteService.isDesignTime()) {
// const o = new sap.ui.core.CustomData({
// key: "refresh",
// value: true,
// });
// b.addCustomData(o);
// }
// if (b._selectItem) {
// b._selectItem();
// }
// }
/** End ORIGINAL CODE **/
/** Start CUSTOM CODE **/
// prevent using b._selectItem() as this is triggering a re-routing event.
if (l && l.setExpanded && !l.setSelectedItem) {
//Collapse all others
l.getNavigationList().getItems().forEach(e => e.getProperty("expanded") && e.setProperty("expanded", false));
// Expand target
l.setExpanded(true);
if (b._selectItem && this.siteService.isDesignTime()) {
b._selectItem(); // Keep behaviour only when in DesignTime mode (Administration site)
} else if (l && l.getNavigationList) {
l.getNavigationList().setSelectedItem(b); // Instead of using _selectItem(), use setSelectedItem() using the parent navigation list.
}
} else if (l && l.setSelectedItem) {
l.getItems().forEach(e => e.getProperty("expanded") && e.setProperty("expanded", false));
l.setSelectedItem(b);
}
/** End CUSTOM CODE **/
}
}
}
/* Hacking the SideNavigation via "byId()" and via DOM Manipulation below.
* It is impossibility to extend or redefine the SideNavigation in the adminTemplate.
* This plugin is too late.
*/
enhanceNavigation() {
try {
// Load the custom icons
//ToDo: Can we remove IconFontLoader here and move it to library.
new IconFontLoader().loadCustomIcons().then(function () {
if (this._applyCrossAppNavigationFix) {
// Inject custom logic to override: sap.ushell.services.cp.designtime.renderers.cp.admin.Shell -> _selectControlByHash
// This must fix the CrossApplicationNavigation logic when deep-linking.
const oMainShellController = sap.ui.getCore().byId("mainShell").getController();
if (oMainShellController && oMainShellController._selectControlByHash) {
oMainShellController._selectControlByHash = this.customSelectControlByHash.bind(oMainShellController);
}
}....

ol-cesium: Is there a way to select a feature in ol-cesium and track it back to openlayers features?

I have a geojson that was added as a feature vector to openlayer. All the geojson in their properties section have some additional information that I want to display on clicking a feature (eg a popup) in 3d space. I used the ol-cesium overlay example to create a pop but there isn't a way to get the feature-geojson properties just with 3d(ol-cesium) enabled.
Are there any approach to get the feature details on clicking a feature in 3d space?
figured it out I think I had to do some think like this .
private getOlFeatureFromMouseLocationInOLCS(cesiumMouseEvent: any): OlFeature | undefined {
if (cesiumMouseEvent.position.x === 0 && cesiumMouseEvent.position.y === 0) {
return;
}
/**
//hoping the below two lines have bee defined early on .
this._ol3d = new OLCesium({ map: this._currentMap });
this.scene = this._ol3d.getCesiumScene();
**/
const pickedFeature = this.scene.pick(cesiumMouseEvent.position);
let olFeature: OlFeature;
if (pickedFeature.primitive) {
olFeature = (pickedFeature.primitive.olFeature)?pickedFeature.primitive.olFeature as OlFeature : undefined;
} else {
olFeature = undefined;
}
return olFeature;
}

Ionic 3 - How to prevent the user from clicking slide pager from slide 0 to slide 2 if slide 1 is not answered

I am working on a Ionic 3 questionnaire application where the user cannot go from one slide to another until the question is answered on a slide. I got everything working except pager (this dots) is allowing the user to go from one slide to another even though question# 2 is not answered.
I added the following logic but its not working as expected. Its still allowing me to jump from slide 0 to slide 2. Adding this.slideTo(this.currentIndex) changed the dot to be highlighted for slide 0 however its showing the contents of Slide# 2.
onSlideWillChange(event) {
let answerNotSelected: boolean = false;
for (let i: number = 0; i < this.slides.getActiveIndex(); i++) {
answerNotSelected = this.questionnaire.questions[i].selectedAnswer === undefined;
if (answerNotSelected) {
console.log('Returning from newQuestionIndex ' + this.slides.getActiveIndex() + ' to current slide:' + this.currentQuestionIndex);
this.slideTo(this.currentQuestionIndex);
}
} }
You could just lock the slides, and only enable back when the question has an answer. That way, the pager won't change the current slide (It would just be there to give the user some feedback about how many other questions are there in the questionnaire):
import { Component, ViewChild } from '#angular/core';
import { NavController, Content, Slides } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'app/home.page.html'
})
export class HomePage {
#ViewChild(Slides) slides: Slides;
ngOnInit() {
this.slides.lockSwipes(true); // Lock the slides
}
public showPrevious(): void {
this.slides.lockSwipes(false); // Unlock the slides
this.slides.slidePrev(500); // Move the the previous
this.slides.lockSwipes(true); // Lock the slides again
}
public showNext(): void {
this.slides.lockSwipes(false); // Unlock the slides
this.slides.slideNext(500); // Move the the next
this.slides.lockSwipes(true); // Lock the slides again
}
}
Please take a look at this working plunker for a demo.
I had the same problem - for anyone in the future I solved it in a albeit slightly hacky way (since _paginationContainer is an internal variable). You could probably also as easily just find elements in the DOM.
I called this on
ionSlideWillChange
, but it could probably be called on any of the swipe events.
ion-slide is very poorly documented, but it is based off of http://idangero.us/swiper/api/ and has all the same methods so you can look here for better documentation.
lockAll() {
var current = this.slider.getActiveIndex();
let array = this.slider._paginationContainer.childNodes
for (let index = 0; index < array.length; index++) {
var button : any = array[index];
if (this.inRange(index, current -1, current +1)) {
button.disabled = false;
}
else {
button.disabled = true
}
}
}
inRange(x, min, max) {
return ((x-min)*(x-max) <= 0);
}

Make Webview's auto links visible

A Webview will display links in the content HTML as having blue underlines. So if you have something in the HTML like
blah blah
... it is clearly visible as a link.
The Webview also allows you to click on phone numbers and addresses (even if those are just text in the HTML, not links) to launch the Dialer or Maps.
How can one get Webview to display those (Linkify, probably) links with underlines etc? It's easy enough in a TextView since one can get the spans from a TextView and style them, but Webview doesn't expose any way to retrieve that data... at least not that I can see looking through the docs.
Here is some JS code which can be injected to linkify phone numbers, emails and urls:
function linkify() {
linkifyTexts(linkifyPhoneNumbers);
linkifyTexts(linkifyEmails);
linkifyTexts(linkifyWebAddresses1);
linkifyTexts(linkifyWebAddresses2);
}
function linkifyPhoneNumbers(text) {
text = text.replace(/\b\+?[0-9\-]+\*?\b/g, '$&');
return text;
}
function linkifyEmails(text) {
text = text.replace(/(\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,6})/gim, '$1');
return text;
}
function linkifyWebAddresses1(text) {
text = text.replace(/(\b(https?|ftp):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/gim, '$1');
return text;
}
function linkifyWebAddresses2(text) {
text = text.replace(/(^|[^\/])(www\.[\S]+(\b|$))/gim, '$1$2');
return text;
}
var linkifyTexts = function(replaceFunc)
{
var tNodes = [];
getTextNodes(document.body,false,tNodes,false);
var l = tNodes.length;
while(l--)
{
wrapNode(tNodes[l], replaceFunc);
}
}
function getTextNodes(node, includeWhitespaceNodes,textNodes,match) {
if (node.nodeType == 3) {
if (includeWhitespaceNodes || !/^\s*$/.test(node.nodeValue)) {
if(match){
if(match.test(node.nodeValue))
textNodes.push(node);
}
else {
textNodes.push(node);
}
}
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
var subnode = node.childNodes[i];
if (subnode.nodeName != "A") {
getTextNodes(subnode,includeWhitespaceNodes,textNodes,match);
}
}
}
}
function wrapNode(n, replaceFunc) {
var temp = document.createElement('div');
if(n.data)
temp.innerHTML = replaceFunc(n.data);
else{
//whatever
}
while (temp.firstChild) {
n.parentNode.insertBefore(temp.firstChild,n);
}
n.parentNode.removeChild(n);
}
Given this:
http://code.google.com/p/android/issues/detail?id=742
it still doesn't seem to be a way to do this from Java directly. One thing that might work is to write some JavaScript code and run it after page is loaded, e.g. as given here:
In Android Webview, am I able to modify a webpage's DOM?
Here's an example of a similar thing:
Disabling links in android WebView
where the idea is to disable links. You may be able to use a similar approach to add some CSS, including underlining. A couple of other SOqs / links that might help:
Android: Injecting Javascript into a Webview outside the onPageFinished Event
Android: Injecting Javascript into a Webview outside the onPageFinished Event
http://iphoneincubator.com/blog/windows-views/how-to-inject-javascript-functions-into-a-uiwebview
Injecting Javascript into a Webview outside the onPageFinished Event (Using DatePicker to set a date on an input of a WebView)
Hope this helps.

Bing map AJAX Control V7 - How does one detect clicks but not drags?

I have a Bing map on my web page, and I want to detect when the user clicks in the window. However, I do not wish to detect when the user drags the map (this also generates a "click" event) . What is the best way to get only "pure" click events?
My solution ended up beeing a manual check if the click position was close to the position where the mouse was pushed down.
Microsoft.Maps.Events.addHandler(map, "click", clickHandler);
Microsoft.Maps.Events.addHandler(map, "mousedown", function(me) { lastMouseDownPoint = new Microsoft.Maps.Point(me.getX(), me.getY());});
function clickHandler(mouseEventArgs){
var point = new Microsoft.Maps.Point(mouseEventArgs.getX(), mouseEventArgs.getY());
//Drag detection
// Edited since the comma is incorrect, should be a plus as per pythagorean theorem
var dist = Math.sqrt(Math.pow(point.x-lastMouseDownPoint.x,2) + Math.pow(point.y-lastMouseDownPoint.y,2));
if(dist > 5) {
// We call this a drag
return;
}
// We have a "pure" click and can process it
}
Very simple:
Microsoft.Maps.Events.addHandler(map, 'click', onClick);
function onClick(e) {
if (e.mouseMoved === false && e.isPrimary === true) {
// Left click not being a drag
...
}
}
mouseMoved is true with a drag and drop and false otherwise.
The MouseEventArgs documentation http://msdn.microsoft.com/en-us/library/gg406731.aspx does not reference mouseMoved :/