angular2: how to control the whole app with keyboard only? - javascript

What approach would you recommend to control the whole app with keyboard only? I found Spotlight lib, but it depends on enyojs which seems to have its own component model and overall enyojs seems to be an overkill.
So far I'm listening on keydown events in app.component.ts:
#HostListener('window:keydown', ['$event']) onKeydown(event: any) {
event.preventDefault();
this.mainKeydownHandler.handle(event.keyCode);
}
Each component which want to handle keydown events registers to MainKeydownHandler's handlers and when keydown event occures MainKeydownHandler delegates an event to the registered handler. The appropriate handler is determined by document.activeElement's class (I did not find more "angular2-like" way to get currently focused elem) in MainKeydownHandler's handle method. So now I have to add appropriate class name to a group of elements I want to be able to focus.
Furthermore, component's handler gets all selectable elements with:
#ViewChildren('selectable') elements: QueryList<ElementRef>;
so I need to add #selectable and class with ID of handler to each element I want to focus. Component's handler then receives keyCode and determines which element to select next, or return to previouslly selected elem from another component and so on.
This approach seems awkward, involves quite a lot of code when new component want to handle keydown events and there are situations when a component completely loses focus: when I delete an item from some list and then this component is rerendered (some service or MainKeydownHandler could remember, but this could lead to quite a lot of elements to remember along the way navigating user through components).
Is there a better, simpler, more generic and more declarative way to control entire angular2 app with keyboard-only (keydown/keyup events)?
Alternatively, is it reasonable to use Spotlight (with enyojs dep.) with angular2 for this use-case? And can you provide working plunker with angular2+spotlight. I could not get these two to work together.

If you want to enable basic keyboard navigation, you can do it with tabindex which is a standard way of doing it. See the specification
document.getElementById('mylist').focus();
:focus{
border: 1px solid red;
}
Press TAB to navigate.
<ul id="mylist" tabindex="-1">
<li tabindex="1"> One </li>
<li tabindex="2"> Two </li>
<li tabindex="4"> Four </li>
<li tabindex="3"> Three </li>
</ul>
If you want to do it with arrow keys, you will have to listen to keydown event on the document and manually focus() the elements using javascript. It will be easier to navigate back and forth, not that direct to find the element that is above and below current element in layout. You will have to compare x,y coordinates or offsets.
document.getElementById('mylist').focus();
window.onkeydown = function(e) {
var curr = document.activeElement;
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
{ acceptNode: function(node) { return (node.tabIndex === -1)?NodeFilter.FILTER_SKIP: NodeFilter.FILTER_ACCEPT} },
false
);
treeWalker.currentNode = curr;
if (e.keyCode == 39) {
treeWalker.nextNode().focus();
}
if (e.keyCode == 37) {
treeWalker.previousNode().focus();
}
};
:focus {
border: 1px solid red;
}
Press Right/Left arrow keys to navigate
<ul id="mylist" tabindex="-1">
<li tabindex="1">One</li>
<li tabindex="2">Two</li>
<li tabindex="4">Four</li>
<li tabindex="3">Three</li>
<li>Not selectable</li>
<li tabindex="5">Five</li>
</ul>

You can take a look at https://github.com/luke-chang/js-spatial-navigation It's a generic spatial navigation library. It should be able to be integrated easily with Angular.
You may also want to check out this related SO: Navigate the UI using only keyboard
And this Angular TV app which purports to have spatial navigation: https://github.com/ahmednuaman/angular-tv-app

Related

How to setup Jquery Mobile navigation using OnClick Events

I am building a hybrid application out of Jquery Mobile, Cordova and WordPress. My current question is regarding my navigation between "pages" in index.html that have the data-role="page" attribute.
Current Setup: I am using data-role="navbar" inside data-role="header" and EACH page has the following header:
<div data-role="navbar">
<ul>
<li><a class="blue-button home-button" href="#" data-transition="slidefade">HOME</a></li>
<li><a class="blue-button artist-refresh artist-button" href="#" data-transition="slidefade">ARTIST</a></li>
<li><a class="blue-button show-button" href="#" data-transition="slidefade">SHOW INFO</a></li>
</ul>
</div><!-- /navbar -->
main.js file I am trying to add event listeners to each of the navigation elements by class name and .ui-page-active class I am also bundling in a few other unique elements that have clickEvents but I reference them by ID:
function setupMainNav() {
console.log("Settign Up SUB NAV");
$('.ui-page-active .show-button').on('click', function () {
console.log("In the show info click");
$.mobile.changePage("#show-info", {
transition: "slide"
});
});
$('.ui-page-active .home-button').on('click', function () {
console.log("In the show info click");
$.mobile.changePage("#home", {
transition: "slide"
});
});
$('#artistContactButton').on('click', function () {
console.log("Show Contact Form");
$.mobile.changePage("#artist-contactpage", {
transition: "slide"
});
});
$('div.ui-page-active a.artist-button').on('click', function () {
console.log("artist button click");
$.mobile.changePage("#cyan-home", {
transition: "slide"
});
});
$('#show_link').on('click', function () {
$.mobile.changePage("#cyan-home", {
transition: "slide"
});
});
$('#shop_link').on('click', function () {
$.mobile.changePage("#shop-home", {
transition: "slide"
});
});
}
What I do is try to all the setupMainNav() function every-time a page changes using the .on('pagecreate') but only the first page that is loaded which has the #show_link and #shop_link elements with those ID's and of course those are the only two.
What are best practices for setting up navigation that is controlled via the JS and not the <a href>
Disclaimer: these are a few of what I think of as "best practices." Others may disagree; YMMV. Also this is assuming you don't want to use libraries or frameworks like Vue.js or React.js, which in general will do things quite differently. Depending on circumstances these libraries can have both advantages and drawbacks.
But within those limits, the general idea is this:
Keep the event handler generic, so that one function can do multiple things.
Pass in stuff that differs between links as attributes. This keeps things related to the activity together at the link.
I like to attach the event listener higher up in the DOM and then handle the events as they bubble. In this case we're attaching the event to the ul tag, and catching any click events that bubble up from a tags. IMHO this has a few advantages:
if you mutate the list, new links will automatically use the current event handler.
you only have one event handler attached to the DOM, instead of 3 (however many a tags you have)
this also gives you the chance to add other event listeners directly to specific a tags if you want to do something special before (or instead of) the default action. Because events attached directly happen first, and then the event bubbles. If you want it to happen instead of, you would just call e.stopPropagation() to prevent the event from bubbling.
Also what I've done sometimes in the past is to have a single generic page with header and navbar, and then load the main content div via ajax. This has the very visually pleasing effect that when you go to a different page the navbar stays put, and doesn't reload. You could easily do this in the example code below, if changePage was doing an XHR/fetch, and then loading the contents into a main content div.
In this greatly simplified example, I show how we can use the href, innerText, and a data attribute to do different things depending on which link is clicked. Of course you can do as much (or as little) as you want/need in this regard.
$('ul.navbar').on('click', 'a', function(e) {
var t = e.target;
var info = t.dataset.info || '';
console.log("click " + t.innerText + ' ' + info);
$.mobile.changePage(t.href, {
transition: "slide"
});
e.preventDefault();
return false;
});
// stub of $.mobile.changePage
$.mobile = {
changePage: function(href, opts) {
console.log('changePage', href);
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class='navbar'>
<li>HOME</li>
<li>ARTIST</li>
<li>SHOW INFO</li>
</ul>

How do I setup my JQuery Mobile MultiPage Navigation in my hybrid application [duplicate]

I am building a hybrid application out of Jquery Mobile, Cordova and WordPress. My current question is regarding my navigation between "pages" in index.html that have the data-role="page" attribute.
Current Setup: I am using data-role="navbar" inside data-role="header" and EACH page has the following header:
<div data-role="navbar">
<ul>
<li><a class="blue-button home-button" href="#" data-transition="slidefade">HOME</a></li>
<li><a class="blue-button artist-refresh artist-button" href="#" data-transition="slidefade">ARTIST</a></li>
<li><a class="blue-button show-button" href="#" data-transition="slidefade">SHOW INFO</a></li>
</ul>
</div><!-- /navbar -->
main.js file I am trying to add event listeners to each of the navigation elements by class name and .ui-page-active class I am also bundling in a few other unique elements that have clickEvents but I reference them by ID:
function setupMainNav() {
console.log("Settign Up SUB NAV");
$('.ui-page-active .show-button').on('click', function () {
console.log("In the show info click");
$.mobile.changePage("#show-info", {
transition: "slide"
});
});
$('.ui-page-active .home-button').on('click', function () {
console.log("In the show info click");
$.mobile.changePage("#home", {
transition: "slide"
});
});
$('#artistContactButton').on('click', function () {
console.log("Show Contact Form");
$.mobile.changePage("#artist-contactpage", {
transition: "slide"
});
});
$('div.ui-page-active a.artist-button').on('click', function () {
console.log("artist button click");
$.mobile.changePage("#cyan-home", {
transition: "slide"
});
});
$('#show_link').on('click', function () {
$.mobile.changePage("#cyan-home", {
transition: "slide"
});
});
$('#shop_link').on('click', function () {
$.mobile.changePage("#shop-home", {
transition: "slide"
});
});
}
What I do is try to all the setupMainNav() function every-time a page changes using the .on('pagecreate') but only the first page that is loaded which has the #show_link and #shop_link elements with those ID's and of course those are the only two.
What are best practices for setting up navigation that is controlled via the JS and not the <a href>
Disclaimer: these are a few of what I think of as "best practices." Others may disagree; YMMV. Also this is assuming you don't want to use libraries or frameworks like Vue.js or React.js, which in general will do things quite differently. Depending on circumstances these libraries can have both advantages and drawbacks.
But within those limits, the general idea is this:
Keep the event handler generic, so that one function can do multiple things.
Pass in stuff that differs between links as attributes. This keeps things related to the activity together at the link.
I like to attach the event listener higher up in the DOM and then handle the events as they bubble. In this case we're attaching the event to the ul tag, and catching any click events that bubble up from a tags. IMHO this has a few advantages:
if you mutate the list, new links will automatically use the current event handler.
you only have one event handler attached to the DOM, instead of 3 (however many a tags you have)
this also gives you the chance to add other event listeners directly to specific a tags if you want to do something special before (or instead of) the default action. Because events attached directly happen first, and then the event bubbles. If you want it to happen instead of, you would just call e.stopPropagation() to prevent the event from bubbling.
Also what I've done sometimes in the past is to have a single generic page with header and navbar, and then load the main content div via ajax. This has the very visually pleasing effect that when you go to a different page the navbar stays put, and doesn't reload. You could easily do this in the example code below, if changePage was doing an XHR/fetch, and then loading the contents into a main content div.
In this greatly simplified example, I show how we can use the href, innerText, and a data attribute to do different things depending on which link is clicked. Of course you can do as much (or as little) as you want/need in this regard.
$('ul.navbar').on('click', 'a', function(e) {
var t = e.target;
var info = t.dataset.info || '';
console.log("click " + t.innerText + ' ' + info);
$.mobile.changePage(t.href, {
transition: "slide"
});
e.preventDefault();
return false;
});
// stub of $.mobile.changePage
$.mobile = {
changePage: function(href, opts) {
console.log('changePage', href);
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class='navbar'>
<li>HOME</li>
<li>ARTIST</li>
<li>SHOW INFO</li>
</ul>

iPad - dropdown navigation better practice?

I have a web application that is expected to run on iPad. I do not wish to create a "dropdown" HTML element for navigation (ie. a select element) since I have plenty of real estate. I want to use my existing desktop-style "hover over then select" type navigation, but replace the hover event with a tap.
I only need to support mobile Safari for iPad, though other browser support could be a plus.
Here's how the nav is structured:
<nav id="main_navigation">
<ul>
<li>
<a id="nav_home" href="/index.jsp">home</a>
</li>
<li class="menuHasDrop" id="selected">
<a id="parent" href="/parent.jsp">parent</a>
<span class="navArrow"></span>
<ul>
<li><a id="foo" href="/foo.jsp">foo</a></li>
<li><a id="bar" href="/bar.jsp">bar</a></li>
</ul>
</li>
<li>
<a id="baz" href="baz.jsp">baz</a>
</li>
</ul>
</nav>
And here's how I'm detecting and enabling touch:
fc.enableTouch = function() {
(function() {
try {
document.createEvent('TouchEvent');
fc.touchEnabled = true;
} catch(e) {}
})();
if (fc.touchEnabled) {
$(document).ready(function() {
// disable clicks on parents
var $menus = $('.menuHasDrop');
$menus.on('click', '>a', function(e){
return false;
})
// listen for clicks on others
$('#main_navigation').on('click', $menus, function(e) {
e.stopImmediatePropagation();
var $this = $(this);
if ($this.parent().children('ul').length > 0) {
$menus.find('ul').hide();
$this.toggleClass('expanded');
if($this.hasClass('expanded')) {
$this.find('ul').show();
}
}
});
});
} else {
// console.log('not detecting touch');
}
}();
The "expanded" class is just a flag. I could set a flag another way, but it shouldn't matter much.
So, this all "works"... but there are two things bugging me:
There's a flicker of a shadow of some sort over the parent nav when I click it. I'm sure it's an OS "clicked a link" indicator, but it's not explicitly provided by CSS or the JavaScript. If anybody has seen such a thing, do you know how to disable it?
Every now and then on a fluke it seems, the UL representing the sub-menu seems to appear, go away, and appear again. Again, I don't THINK the logic itself would be able to cause this, so it might be browser-specific.
Other useful background information: there are always one or more content areas being refreshed via ajax calls, but the page itself is not being refreshed and the navigation area does not have any functions acting on it during the request.
I guess what I'm asking more than a code review is: "Are these just iOS quirks that I have to live with?" and "is there a definitive better practice for touch-enabling dropdowns?" Secondarily, all "I see a problem with your code" advice will be welcomed and appreciated. :)
To get rid of tap colour
-webkit-tap-highlight-color:rgba(0,0,0,0);
For binding, use touchstart/touchend what I have done in my current project is to detect touch, if touch enabled bind only touchstart and end, if not then bind click
I would also add a class to the anchor that you are targeting using ">a", that's really slow
$(#el).on('touchstart', function() {
// if class exist
// go to link
// else
// add class and open dropdown
});
For my current project we set up a virtual event handler to detect if the touch is a touchmove or just a regular touchstart
You can also do something like this
$(#el).on('touchstart', function() {
$(this).trigger('click');
// or you can use prevent default and stopPropagation
return false;
});
or bind on touchend, whatever works better for you

Ipad hover event jQuery

Im trying to create a false hover event for my site using jQuery...
I have created the following only all the child elements in my list now return false also as opposed to linking to the correct page...
if((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/iPad/i))) {
$("ul.sf-menu li.i").click(function(e) {
e.preventDefault();
});
}
Has anybody an idea on an alternative method that could work?
HTML
<ul class="sf-menu"> <li>Home<li class="i">Weddings
<ul>
<li>Peel Suite</li>
<li>The Hall</li>
<li>The Grounds</li>
<li>Food & Drink</li>
<li>Pricing</li>
</ul>
</li>
well. without HTML its kind of hard to tell. But you are stopping the default behavior of the browser when the user clicks on the li (and therefor also its children, which I suppose is an anchor/link).
you could check if its a link or an anchor on click and handle it differently;
$("ul.sf-menu li.i").click(function(e) {
if (e.target.nodeName!=="A"){
e.preventDefault();
//do your hover code
}
else{
//do nothing, because the user wants the link to load
}
});
change youre selector so it only matches the first level of listpoints, also i dont see a class i so you might need to drop that from the selector aswell.
$("ul.sf-menu > li")

How to hook into the contextmenu event of a browser

I keep getting puzzled, can't find a contextmenu that will work for me. Maybe someone can help?
Here's to what I need contextMenu to be added to:
<ul id="list_{id}" class="list">
<li id="Item_{id}"><a ondblclick=""><span>{title}</span></a></li>
</ul>
This is dynamic list, so it will add many more of them on the page and differ them by giving different ID's. So I need a contextMenu which will be added to every list but for each list an unique contextMenu. Basically different instances of contextMenu in every list, by adding dynamic {id} tag to the ID of contextMenu or something like that.
Thanks
It's kind of hard to tell what you're asking, but if you want to hook into the "context menu" event of a browser, you hook the contextmenu event and then do whatever you're going to do (which could include creating a div, for instance, with options on it — e.g., your own context menu). You can either do that on the lists themselves, individually, via getElementById as you indicated in your question, or you can do it by hooking the event on some container that holds all of the lists, and then figuring out when the event is triggered which list it was triggered on ("event delegation").
See the end of this answer for the event delegation approach. But assuming you have a way of knowing the actual IDs used and you want to hook each list specifically for some reason:
HTML:
<ul id='list_1'>
<li>List 1 item 1</li>
<li>List 1 item 2</li>
</ul>
<ul id='list_2'>
<li>List 2 item 1</li>
<li>List 2 item 2</li>
</ul>
JavaScript:
hookEvent(document.getElementById('list_1'), 'contextmenu', function(event) {
event = event || window.event;
if (event.preventDefault) {
event.preventDefault();
}
display("List 1 context menu");
return false;
});
hookEvent(document.getElementById('list_2'), 'contextmenu', function(event) {
event = event || window.event;
if (event.preventDefault) {
event.preventDefault();
}
display("List 2 context menu");
return false;
});
function hookEvent(element, event, handler) {
if (element.addEventListener) {
element.addEventListener( event, handler, false);
}
else if (element.attachEvent) {
element.attachEvent('on' + event, handler);
}
else {
element['on' + event] = handler;
}
}
Live example
Note that only some (most) browsers let you cancel the default context menu.
Update: Re your "but what if the ID is bindable?" question below: I'm afraid I don't know what you mean by "bindable" — none of the tags on your question indicates a specific templating technology. You haven't even mentioned whether the templating is happening server-side or client-side, which makes it hard to help. But basically, by the time the JavaScript is running, there will be real IDs on real elements in the document. You'll have to know what those IDs are in order to use getElementById.
Server-side templating:
If those IDs are going to be completely dynamic and the template is being handled on the server, you can include a small bit of script that passes those IDs on to JavaScript. For instance, near the top of your document you might have:
<script type='text/javascript'>
var mySpecialListIDs = [];
</script>
...and then update your template to add a small script tag each time it's expanded:
<ul id="list_{id}" class="list">
<li id="Item_{id}"><a ondblclick=""><span>{title}</span></a></li>
</ul>
<script type='text/javascript'>
mySpecialListIDs.push("{id}");
</script>
Then your client-side code can loop through mySpecialLitIDs and use each ID when hooking up the handler.
Client-side templating:
If the templating is being done client-side, this gets a bit simpler: Just set up your mySpecialListIDs list at some convenient place in your client-side script, and the append to it each time you call the templating engine.
Event Delegation: Whether you're doing server- or client-side templating, if you're going to have dynamic lists like this, sometimes event delegation is the best way to handle it. The contextmenu event (like most, but not all, events) bubbles up the DOM. So if you hook it on an ancestor element (something that contains all of your lists, like the document body itself or some such), you can then see which actual list was clicked by examining the event object. Like this:
HTML:
<div id='list_container'>
<ul id='list_1'>
<li>List 1 item 1</li>
<li>List 1 item 2</li>
</ul>
<ul id='list_2'>
<li>List 2 item 1</li>
<li>List 2 item 2</li>
</ul>
</div>
JavaScript (using the hookEvent function from above):
// Hook up the contextmenu event on the container, not
// on each list:
hookEvent(document.getElementById('list_container'),
'contextmenu',
handleListContextMenu);
// Our handler function
function handleListContextMenu(event) {
var target;
// Handle IE-vs-the-world difference
event = event || window.event;
// Find out what the actual target element clicked was
target = event.target || event.srcElement;
// See if it or an ancestor of it is one of our lists
while (target &&
(target.tagName !== "UL" || !target.id || target.id.substring(0, 5) !== "list_")) {
target = target.parentNode;
}
// Did we find a list?
if (target) {
// Yes, handle this.
if (event.preventDefault) {
event.preventDefault();
}
display("List '" + target.id + "' context menu");
return false;
}
}
Live example

Categories

Resources