I already have this code that I came up with:
In my outer controller:
$scope.key = function ($event) {
$scope.$broadcast('key', $event.keyCode)
}
In my inner controller (I have more than one like this)
$scope.$on('key', function (e, key) {
if (key == 13) {
if (ts.test.current) {
var btn = null;
if (ts.test.userTestId) {
btn = document.getElementById('viewQuestions');
} else {
btn = document.getElementById('acquireTest');
}
$timeout(function () {
btn.focus();
btn.click();
window.setTimeout(function () {
btn.blur();
}, 500);
})
}
}
});
Is there another way that I could simplify this using some features of AngularJS that I have not included here?
Please check this gist, https://gist.github.com/EpokK/5884263
You can simply create a directive ng-enter and pass your action as paramater
app.directive('ngEnter', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
HTML
<input ng-enter="myControllerFunction()" />
You may change the name ng-enter to something different, because ng-** is a reserved by Angular core team.
Also I see that your controller is dealing with DOM, and you should not. Move those logic to other directive or to HTML, and keep your controller lean.
if (ts.test.userTestId) {
btn = document.getElementById('viewQuestions'); //NOT in controller
} else {
btn = document.getElementById('acquireTest'); //NOT in controller
}
$timeout(function () {
btn.focus(); //NOT in controller
btn.click(); //NOT in controller
window.setTimeout(function () { // $timeout in $timeout, questionable
btn.blur(); //NOT in controller
}, 500);
})
What i've done in the past is a directive which just listens for enter key inputs and then executes a function that is provided to it similar to an ng-click. This makes the logic stay in the controller, and will allow for reuse across multiple elements.
//directive
angular.module('yourModule').directive('enterHandler', [function () {
return{
restrict:'A',
link: function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
var key = event.which ? event.which : event.keyCode;
if (key === 13) {
scope.$apply(function () {
scope.$eval(attrs.enterHandler);
});
event.preventDefault();
}
});
}
}
}]);
then your controller becomes
$scope.eventHandler = function(){
if (ts.test.current) {
var btn = ts.test.userTestId
? document.getElementById('viewQuestions')
: document.getElementById('acquireTest');
$timeout(function () {
btn.focus();
btn.click();
window.setTimeout(function () {
btn.blur();
}, 500);
})
}
}
and your markup can then be
<div enter-handler="eventHandler()" ></div>
Related
I can move up and down with the keyboard keys but i cannot get the enter event correct
**Controller**
$scope.myFunction = function()
{
alert('sadsad');
alert('hi');
}
**Also i added directive like this**
.directive('ngEnter', function ()
{
restrict:'use strict';
return {
link: function (scope, elements, attrs) {
elements.bind('keydown keypress', function (event) {
if (event.which === 13) {
scope.$apply(function () {
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
}
};
});
**Below is the view code on which i m calling directive and function**
<tr class="find"ng-repeat="busServices in " ng-click="setSelected(busServices.bus_travel_id,this.busServices,$event)" ng-class="{selectedsd:busServices.bus_travel_id === idSelected}" ng-mouseover="ShowHideBoarding($event,this.busServices,true)" ng-mouseleave="ShowHideBoarding($event,false)" ng-init="($first) ? setSelected(busServices.bus_travel_id,this.busServices) : ''" ng-enter="myFunction()"></tr>
ng-enter doesnt get called at all.
what can i do so that ng enter can work.
Try something like this
.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keypress", function (event) {
if(event.keyCode === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
The restrict option can be omitted.
EDIT: You will have to add an attribute to your <tr> so that they can be focussed and receive key* events:
<tr tabindex="0" ....>
Replace 0 with other numbers for more of your focussable <tr>
I have an AngularJS Directive defined in a Javascript file that looks like this:
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
var directive = {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope: true
};
return directive;
function link(scope, element, attrs) {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
}
}
})();
and in the template URL of the directive there is a button that calls a Javascript function which looks like this:
function upload(files) {
var formData = new FormData();
angular.forEach(files, function (file) {
formData.append(file.name, file);
});
return fileManagerClient.save(formData)
.$promise
.then(function (result) {
if (result && result.files) {
result.files.forEach(function (file) {
if (!fileExists(file.name)) {
service.files.push(file);
}
});
}
appInfo.setInfo({ message: "files uploaded successfully" });
return result.$promise;
},
function (result) {
appInfo.setInfo({ message: "something went wrong: " +
result.data.message });
return $q.reject(result);
})
['finally'](
function () {
appInfo.setInfo({ busy: false });
service.status.uploading = false;
});
}
Once I select files for upload and click the upload button I need to reload the directive or somehow get it back to it's initial state so I can upload additional files. I'm relatively new to AngularJS and I'm not quite sure how to do this. Any help is much appreciated.
Thanks,
Pete
You just need to create a reset method. Also, you may want to call the parent controller function.
Using answer from this
ngFileSelect.directive.js
...
.directive("onFileChange",function(){
return {
restrict: 'A',
link: function($scope,el){
var onChangeHandler = scope.$eval(attrs.onFileChange);
el.bind('change', onChangeHandler);
}
}
...
fileUploader.directive.js
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
return {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope:{
onSubmitCallback: '&',
onFileChange: '&'
}
};
function link(scope, element, attrs) {
scope.reset = reset;
scope.fileChange = fileChange;
reset();
function reset() {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
if(typeof scope.onSubmitCallback === 'function') {
scope.onSubmitCallback();
}
}
function fileChange(file) {
if(typeof scope.onFileChange === 'function'){
scope.onFileChange(file);
}
}
}
}
})();
UploadFile.html
<form>
<div>
...
</div>
<input type="submit" ng-click="reset()" file-on-change="fileChange($files)" />Upload
</form>
parent.html
<file-uploader on-submit-callback="onUpload" on-file-change="onFileChange" ng-controller="UploadCtrl" />
upload.controller.js
...
$scope.onUpload = function() {
console.log('onUpload clicked %o', arguments);
};
$scope.onFileChange = function(e) {
var imageFile = (e.srcElement || e.target).files[0];
}
...
I have the div element and I need catch event of one click and click and hold.
If happened one click of this div, i should call function 1 in scope, if click and hold (more of 5 seconds) i should call function 2 in scope.
Create a directive on-click-and-hold and use it.
Directive
directive('onClickAndHold', function ($parse, $timeout) {
return {
link: function (scope, element, attrs) {
var clickAndHoldFn = $parse(attrs.onClickAndHold);
var doNotTriggerClick;
var timeoutHandler;
element.on('mousedown', function () {
$timeout.cancel(timeoutHandler);
timeoutHandler = $timeout(function () {
clickAndHoldFn(scope, {$event: event})
}, 5000)
});
element.on('mouseup', function (event) {
$timeout.cancel(timeoutHandler);
});
if (attrs.onClick) {
var clickFn = $parse(attrs.onClick);
element.on('click', function (event) {
if (doNotTriggerClick) {
doNotTriggerClick = false;
return;
}
clickFn(scope, {$event: event});
scope.$apply();
});
}
}
}
})
Markup
<div on-click-and-hold="f2()" on-click="f1()"></div>
Controller
controller('AppCtrl', function ($scope) {
$scope.f1 = function () {
console.warn('inside f1');
}
$scope.f2 = function () {
console.warn('inside f2');
}
})
If you want to handle only the click event use ng-click instead of on-click-and-hold.
Working Plnkr
I've created the following directive:
.directive('onSectionBlur', function ($parse) {
return {
restrict: 'A',
controller: function ($scope, $element, $attrs) {
$element.focusout(function (event) {
if (!jQuery.contains($element[0], event.relatedTarget)) {
$scope.$apply($parse($attrs.onSectionBlur)($scope));
}
});
}
};
})
My goal here is if a user tabs out of a section of a form (or clicks elsewhere), I want to display a read-only version of that data: http://jsfiddle.net/uZBXw/3/
So this works from what I can tell, but I feel like I was just mashing buttons on this line:
$scope.$apply($parse($attrs.onSectionBlur)($scope));
Is this the correct way to run code and wire it into the angular lifecycle?
I think you should use an isolated scope with an attribute marked with &. This will give you access to a function that will run on the parent scope and is the exact use case of what you're trying to do.
app.directive('onSectionBlur', function () {
return {
restrict: 'A',
scope: {
'notify': '&onSectionBlur' // reuse the directive name for easier handling
},
link: function (scope, element) {
element.on('focusout', function (evt) {
if (!angular.element.contains(element[0], evt.relatedTarget)) {
scope.$apply(scope.notify); // let $apply call the notify-callback
}
});
}
};
});
demo: http://jsbin.com/diwetaje/1/
from the Developer Guide:
Best Practice: use &attr in the scope option when you want your directive to expose an API for binding to behaviors.
I was having issues with clicking on various items in the section (i.e. checkbox labels), so if anyone else runs across this issue I've added a potential enhancement to Yoshi's version:
.directive('onSectionBlur', function ($document) {
return {
restrict: 'A',
scope: {
'notify': '&onSectionBlur'
},
link: function (scope, element) {
var hasFocus = false;
element.on('focusin', function (evt) {
hasFocus = true;
});
$document.on('click focusin', function (evt) {
if (hasFocus && !angular.element.contains(element[0], evt.target)) {
hasFocus = false;
scope.$apply(scope.notify);
}
});
}
};
});
EDIT: Here's the butchered up version I ended up with, that takes into account buttons that weren't clickable (if they were outside the section and below it) as well as not firing the event if the user has a modal window open:
link: function (scope, element) {
var hasFocus = false;
var lostFocus = function () {
hasFocus = false;
scope.$apply(scope.notify);
};
element.on('focusin', function (evt) {
hasFocus = true;
});
element.on('keydown', function (evt) {
if (hasFocus && evt.keyCode == 9) {
//Using timeout to give the browser time to process what it should have been doing (i.e. focusing next item)
if (evt.shiftKey && element.find(':focusable:first').is(evt.target)) {
$timeout(lostFocus);
} else if (element.find(':focusable:last').is(evt.target)) {
$timeout(lostFocus);
}
}
});
var docHandler = function (evt) {
//If the click came from inside of a modal window, ignore it
if (angular.element(evt.target).closest('.modal').length == 0) {
if (hasFocus && !angular.element.contains(element[0], evt.target)) {
lostFocus();
}
}
};
$document.on('click', docHandler);
scope.$on('$destroy', function () {
$document.off('click', docHandler);
});
}
My goal is to autosave a form after is valid and update it with timeout.
I set up like:
(function(window, angular, undefined) {
'use strict';
angular.module('nodblog.api.article', ['restangular'])
.config(function (RestangularProvider) {
RestangularProvider.setBaseUrl('/api');
RestangularProvider.setRestangularFields({
id: "_id"
});
RestangularProvider.setRequestInterceptor(function(elem, operation, what) {
if (operation === 'put') {
elem._id = undefined;
return elem;
}
return elem;
});
})
.provider('Article', function() {
this.$get = function(Restangular) {
function ngArticle() {};
ngArticle.prototype.articles = Restangular.all('articles');
ngArticle.prototype.one = function(id) {
return Restangular.one('articles', id).get();
};
ngArticle.prototype.all = function() {
return this.articles.getList();
};
ngArticle.prototype.store = function(data) {
return this.articles.post(data);
};
ngArticle.prototype.copy = function(original) {
return Restangular.copy(original);
};
return new ngArticle;
}
})
})(window, angular);
angular.module('nodblog',['nodblog.route'])
.directive("autosaveForm", function($timeout,Article) {
return {
restrict: "A",
link: function (scope, element, attrs) {
var id = null;
scope.$watch('form.$valid', function(validity) {
if(validity){
Article.store(scope.article).then(
function(data) {
scope.article = Article.copy(data);
_autosave();
},
function error(reason) {
throw new Error(reason);
}
);
}
})
function _autosave(){
scope.article.put().then(
function() {
$timeout(_autosave, 5000);
},
function error(reason) {
throw new Error(reason);
}
);
}
}
}
})
.controller('CreateCtrl', function ($scope,$location,Article) {
$scope.article = {};
$scope.save = function(){
if(typeof $scope.article.put === 'function'){
$scope.article.put().then(function() {
return $location.path('/blog');
});
}
else{
Article.store($scope.article).then(
function(data) {
return $location.path('/blog');
},
function error(reason) {
throw new Error(reason);
}
);
}
};
})
I'm wondering if there is a best way.
Looking at the code I can see is that the $watch will not be re-fired if current input is valid and the user changes anything that is valid too. This is because watch functions are only executed if the value has changed.
You should also check the dirty state of the form and reset it when the form data has been persisted otherwise you'll get an endless persist loop.
And your not clearing any previous timeouts.
And the current code will save invalid data if a current timeout is in progress.
I've plunked a directive which does this all and has better SOC so it can be reused. Just provide it a callback expression and you're good to go.
See it in action in this plunker.
Demo Controller
myApp.controller('MyController', function($scope) {
$scope.form = {
state: {},
data: {}
};
$scope.saveForm = function() {
console.log('Saving form data ...', $scope.form.data);
};
});
Demo Html
<div ng-controller="MyController">
<form name="form.state" auto-save-form="saveForm()">
<div>
<label>Numbers only</label>
<input name="text"
ng-model="form.data.text"
ng-pattern="/^\d+$/"/>
</div>
<span ng-if="form.state.$dirty && form.state.$valid">Updating ...</span>
</form>
</div>
Directive
myApp.directive('autoSaveForm', function($timeout) {
return {
require: ['^form'],
link: function($scope, $element, $attrs, $ctrls) {
var $formCtrl = $ctrls[0];
var savePromise = null;
var expression = $attrs.autoSaveForm || 'true';
$scope.$watch(function() {
if($formCtrl.$valid && $formCtrl.$dirty) {
if(savePromise) {
$timeout.cancel(savePromise);
}
savePromise = $timeout(function() {
savePromise = null;
// Still valid?
if($formCtrl.$valid) {
if($scope.$eval(expression) !== false) {
console.log('Form data persisted -- setting prestine flag');
$formCtrl.$setPristine();
}
}
}, 500);
}
});
}
};
});
UPDATE:
to stopping timeout
all the logic in the directive
.directive("autosaveForm", function($timeout,$location,Post) {
var promise;
return {
restrict: "A",
controller:function($scope){
$scope.post = {};
$scope.save = function(){
console.log(promise);
$timeout.cancel(promise);
if(typeof $scope.post.put === 'function'){
$scope.post.put().then(function() {
return $location.path('/post');
});
}
else{
Post.store($scope.post).then(
function(data) {
return $location.path('/post');
},
function error(reason) {
throw new Error(reason);
}
);
}
};
},
link: function (scope, element, attrs) {
scope.$watch('form.$valid', function(validity) {
element.find('#status').removeClass('btn-success');
element.find('#status').addClass('btn-danger');
if(validity){
Post.store(scope.post).then(
function(data) {
element.find('#status').removeClass('btn-danger');
element.find('#status').addClass('btn-success');
scope.post = Post.copy(data);
_autosave();
},
function error(reason) {
throw new Error(reason);
}
);
}
})
function _autosave(){
scope.post.put().then(
function() {
promise = $timeout(_autosave, 2000);
},
function error(reason) {
throw new Error(reason);
}
);
}
}
}
})
Here's a variation of Null's directive, created because I started seeing "Infinite $digest Loop" errors. (I suspect something changed in Angular where cancelling/creating a $timeout() now triggers a digest.)
This variation uses a proper $watch expression - watching for the form to be dirty and valid - and then calls $setPristine() earlier so the watch will re-fire if the form transitions to dirty again. We then use an $interval to wait for a pause in those dirty notifications before saving the form.
app.directive('autoSaveForm', function ($log, $interval) {
return {
require: ['^form'],
link: function (scope, element, attrs, controllers) {
var $formCtrl = controllers[0];
var autoSaveExpression = attrs.autoSaveForm;
if (!autoSaveExpression) {
$log.error('autoSaveForm missing parameter');
}
var savePromise = null;
var formModified;
scope.$on('$destroy', function () {
$interval.cancel(savePromise);
});
scope.$watch(function () {
// note: formCtrl.$valid is undefined when this first runs, so we use !$formCtrl.$invalid instead
return !$formCtrl.$invalid && $formCtrl.$dirty;
}, function (newValue, oldVaue, scope) {
if (!newValue) {
// ignore, it's not "valid and dirty"
return;
}
// Mark pristine here - so we get notified again if the form is further changed, which would make it dirty again
$formCtrl.$setPristine();
if (savePromise) {
// yikes, note we've had more activity - which we interpret as ongoing changes to the form.
formModified = true;
return;
}
// initialize - for the new interval timer we're about to create, we haven't yet re-dirtied the form
formModified = false;
savePromise = $interval(function () {
if (formModified) {
// darn - we've got to wait another period for things to quiet down before we can save
formModified = false;
return;
}
$interval.cancel(savePromise);
savePromise = null;
// Still valid?
if ($formCtrl.$valid) {
$formCtrl.$saving = true;
$log.info('Form data persisting');
var autoSavePromise = scope.$eval(autoSaveExpression);
if (!autoSavePromise || !autoSavePromise.finally) {
$log.error('autoSaveForm not returning a promise');
}
autoSavePromise
.finally(function () {
$log.info('Form data persisted');
$formCtrl.$saving = undefined;
});
}
}, 500);
});
}
};
});