Set focus when another element is clicked or other event occurs - angularjs

I have a tab-like view on my page and I have a variety of events which need to set the focus to one of the tabs. (showing/hiding the div is easy since I just use a model variable.) The events that cause a div to be focused are clicking on the tab header, data load completion, and initial loading. I know where to intercept all these events, but I'm not sure how I tell the other element to set the focus (none of the intercept sites know about the other element, only about the model).
I've looked around but can't find a good reference for this. I assume I want to listen for some message and post it from the various other locations.
How does one setup this type of messaging event?
ANSWER: I built on the answer and came up with the below directive. It combines both a focus and show state for the div.
newsendApp.directive('showAndFocus', function() {
return {
link: function(scope, element, attr){
scope.$on('SetArticlesListFocus',function() {
if( scope.$eval( attr.showAndFocus ) ) {
$(element).focus();
}
})
scope.$watch(attr.showAndFocus, function(value) {
if( value ) {
$(element).show();
setTimeout( function() { $(element).focus(); }, 0 );
} else {
$(element).hide();
}
})
}
}
});
If an event occurs which may require resetting the focus then I do: $rootScope.$broadcast( 'SetArticlesListFocus' );

You can broadcast an event, and also have listeners that perform an action once such a broadcast has occured.
You can use $scope.$broadcast('changed-tab', objectSentWithBroadcast). The second parameter is an object you can optionally send, like the tab you want to focus on.
Then you would have a listener like this where you can select the tab wanted:
$scope.$on('changed-tab', function(e, objectSentWithBroadcast) {
// do something here to select the tab
});

Have a look at this issue raised in angularjs.
https://github.com/angular/angular.js/issues/1277#issuecomment-16012024
Here is the final plunk that creates a ng-focus directive that manages two way data binding between a variable and an element's focus state.
http://plnkr.co/edit/bntEsfngnJKuneg2raD1
This will allow you to bind a model to an elements focus state and then setting or unsetting this variable will make the element gain / lose focus. You will have to note that if you are using div's etc ( which are non-focusable by default! ) you will need to set tabIndex = -1 on them. Apart from that it should work fine and suit your need for manipulating models pretty well.

Related

Targeting keydown events to an angularJS custom directive

I am working on a problem wherein I am required to pick-up keydown events (specifically ctrl+p and then point to a print function which already exists) on a certain custom directive and under certain conditions (a certain tab should be selected). My current approach is to bind the keydown event on the document itself, broadcast it and then listen to it in the required custom directive. Following is the code I have placed in the app.run.. block -
angular.element($document).on('keydown', function(evt) {
if(evt.ctrlKey && evt.key==='p'){
$rootScope.$broadcast('printOnKeyPress');
}
});
This part is working as expected, the problem arises when I try to handle it in the required controller of the custom directive as follows:
$scope.$on('printOnKeyPress', function() {
//point to existing print function
}
This is where the problem arises. It goes into the print function but still the output is incorrect. I am missing something and I can't figure out what.
Also, this is not a good approach but I have searched and am unable to find a possible solution to just bind the keydown event on that custom directive itself (the component only appears if a document is selected).
(ng-keydown won't also work here)
Any help is appreciated!
You could put it in the directive and use the scope destroy event to remove the listener.
Within directive link function:
function keyHandler(evt) {
if (evt.ctrlKey && evt.key === 'p') {
// do your print
}
}
angular.element($document).on('keydown', keyHandler);
scope.$on('$destroy', function() {
angular.element($document).off('keydown', keyHandler);
});

mdbottomsheet disable drag down to close

I would like to disable the drag down to close gesture of mdbottomsheet. I've found a work around on scripts but I'm not sure where to put the code. Thanks for the help.
As you say that angular-material doesn't provide any option to disable it, obviously you will have to make changes in its source code.
Now, you haven't mentioned whether you want to disable it at specific places or turn drag-down-to-close for bottomSheets everywhere.
1) In case of latter, it would be quite straightforward, as the only thing you need to do is remove the event listeners for drag events.
If you use angular-material.js file, heres what you can do:
Find the function BottomSheet(element, parent). This function basically registers the drag events which close the sheet. We need make it not attach the listeners.
Reduce it to:
function BottomSheet(element, parent){
return {
element: element,
cleanup: angular.noop
};
}
The cleanup function basically de-registers the listeners on drag event.This function is called when the scope of the bottomSheet is destroyed. To make minimal changes, we have just reduced the cleanup function to do nothing.
2) If you want to be able to pass an option while creating the sheet in your controller, you do the same thing, but conditionally based on the option you pass. Wont write the code because I assume you know how angular works, but here are the steps:
=> Add a boolean variable along with other options(template,scope,etc. ). Lets call it dragDownToClose.
=> In the defaults injector function inside the provider function of MdbottomSheet , assign it a default value (true/false).
=>Pass this along with element and parent during instantiation of BottomSheet() inside the onShow function.
=> So BottomSheet() will now have three argument - dragDownToClose being the new one.
=> As we did in the former case, return the element without any handler attached when the value is false, and let the original function be when its true.
Of-course there are various ways in which you can actually implement this. However, I hope you get the idea.
First, inject $element into your controller. You known what AngularJS $element do, right?
Then we both known that the drag events are registered in BottomSheet
parent.on('$md.dragstart', onDragStart)
.on('$md.drag', onDrag)
.on('$md.dragend', onDragEnd);
So, the simple solution is: Remove those events, override those events... without override the function BottomSheet, right?
$element
.on('$md.dragstart', function (ev) {
return false;
})
.on('$md.drag', function (ev) {
return false;
})
.on('$md.dragend', function (ev) {
return false;
});
Something still wrong here! The backdrop still draggable! So, we do the same for backdrop
var parent = $element.parent();
var backdrop = parent.find('md-backdrop');
backdrop
.on(blah blah blah
These is code in case you are asking for
You can try
$mdBottomSheet.show({
template: *yourTemplate*,
clickOutsideToClose:false
})
this will not let the user close the bottom sheet even with drag or click outside.

UI Bootstrap control uib-carousel bind to next event

I am using the angular-bootstrap's (ui.bootstrap) uib-carousel component with custom template to change the style, text, and functionality of the back and next buttons. I have to perform other actions when the "next" button is touched, in addition to the component's original functionality. Following the method of "capturing" the select event in this answer: How do you Bind to Angular-UI's Carousel Slide Events?, I modified the answer to use the next event.
the html declaration
<uib-carousel template-url="/tpl.html" active="vm.wizardStep" no-wrap="true" on-carousel-next="vm.onNext()" style="height: 395px;">
and the directive like so
.directive('onCarouselNext', function($parse) {
return {
require: '^uibCarousel',
link: function(scope, element, attrs, carouselCtrl) {
var callBack = $parse(attrs.onCarouselNext);
var origNext = carouselCtrl.next;
carouselCtrl.next = function() {
callBack(scope);
return origNext.apply(this, arguments);
};
}
};
});
The uib-carousel's next event does get fired, but it is not calling it through my directive. Just to compare apples to apples, I tried using the code in the answer as-is (i.e. capturing the "select" event), and that does work perfectly and calls my callback function. The reason I need to capture the "next" and can't use the "select" is because I'm using this to set up a "wizard" type framework, and the final "next" is a "done" that needs to do different code.
Anyone done something like this and get it to work?
So seeing as how I was trying to "hijack" the carousel control to turn it in to a wizard control, I ended up just taking the carousel code and changing it completely to fit my needs. If anyone would like to see the code, let me know and I'll put it out on github.

Angular.js keypress events and factories

I'm building an app in Angular and I'd like to have keypress events.
However, I'd prefer not to litter my code with a keypress here and a keypress there, but rather to put all keypress events into a single factory (or service) and then import this factory into my controllers to use.
I HOPE that doing things this way will make it easier for me to manage the keypress events and make sure I don't have conflicts (two events tied to the same keypresses) or something like that.
Does anybody have any suggestions on how to manage this?
I'm using angular-ui-keypress.
As an example of how I'm hoping to use keypress events.
The user may have multiple tabs open and hits 'cmd+s' to save the files.
Rather than a 'save' method being triggered on each of the open files, I've got an OpenFilesFactory, the keypress would map to the OpenFilesFactory.saveFiles method.
Am I going about this all wrong? Is there a reason not to tie keyboard shortcuts to a factory rather than in a controller?
What I ended up doing worked surprisingly well, and I'll opensource it as a module after a bit more work.
I created a directive, which binds to the keypress events on $document
angular.module('keypress', []).directive('keypressEvents',
function($document, $rootScope) {
return {
restrict: 'A',
link: function() {
$document.bind('keypress', function(e) {
$rootScope.$broadcast('keypress',e , String.fromCharCode(e.which));
});
}
}
})
I then created a second directive for watching for keypresses on specific elements, basically giving the element a focus for key events.
angular.module('focus', []).directive('onFocus',
function() {
return {
restrict: 'C',
link: function(scope) {
scope.$on('keypress',function(e,parent_evt,key){
if(scope.keyBindings[key]){
scope.keyBindings[key](parent_evt, e);
// params reversed so user goes up the chain
}
});
}
}
});
In any controller where you want to use keyboard shortcuts, add a keybindings object
function keyedS(key, parent_evt, evt){
// key is the key that was pressed
// parent_evt is the keypress event
// evt is the focused element object
}
$scope.keyBindings = {
's': keyedS
}
Feedback?
I've actually put this together with multiple keybindings, so if the user selects 'ctrl-shift-s', that is what gets passed along the chain. Though I'm still struggling to find a really good way of getting all the press events. Eg. Tab doesn't work at the moment.
I get what you mean by having it be a separate resource. To me, it seems to go against thinking in Angular, as events really should be "controlled." If you want to have all keypress (or click) events centralized, maybe a switch/case is in order:
$scope.keypressHandler = function () {
switch ($event.keyCode)
{
case 13:
$scope.someEnterKeyFunction();
break;
default:
$scope.someDefaultFunction();
break;
}
}

Help wrap onClick toggle checkbox into a function

I have a page with 50 hidden checkboxes, and I want to be able to toggle each checkbox by clicking on a visible link. The actual checkboxes have to stay hidden...so... Is there a better way to do this, with a JS function so I don't have to include the entire onclick in each link? And I use mootools, not jQuery.
This works to activate a checkbox:
Select
But to toggle it, this works:
onclick="if (event.target.tagName != 'INPUT') document.getElementById('field_select_temp_professional_10').checked = !document.getElementById('field_select_temp_professional_10').checked"
None of what you posted is actually mootools code, you may as well not use mootools...
Markup:
Select
js in your domready:
document.getElements("a.add_app").addEvents({
click: function(e) {
if (e.target.get("tag") != 'input') {
var checkbox = document.id("field_select_p" + this.get("data-id"));
checkbox.set("checked", !checkbox.get("checked"));
}
}
});
If you have 100+ then I suggest you look at using event delegation from mootools-more and add just one event to the parent instead of creating 100 events and storing 100 functions that deal with it.
This is coding to patterns, and it involves changing your markup to make things work. You can also make the change based upon walking the DOM in relation to the clicked item, e.g. this.getParent().getElement("input[type=checkbox]"), or something can mean you don't need to store a relative id in the element itself.

Resources