I am trying to create two directives: clonable and removable.
These will show a copy and a remove button and if clicked will be copied and/or removed from dom.
The problem I am facing, if copy it is getting wrong element!
If I copy the div, it actually copies the image!
app.directive('removable', function ($compile) {
return {
restrict: 'A',
link: function ($scope, $element, attrs) {
var removeBtn;
$element.on('mouseenter', function (e) {
e.preventDefault();
console.warn("mouseenter-remove");
console.warn($element)
removeBtn = angular.element("<button ng-click='remove($event)'>Delete</button>");
$element.append(removeBtn);
$compile(removeBtn)($scope);
});
$element.on('mouseleave', function (e) {
e.preventDefault();
console.warn("mouseleave-remove");
console.warn($element)
removeBtn.hide();
});
$scope.remove = function ($event) {
console.warn("remove");
console.warn($element)
$element.hide();
removeBtn.remove();
}
}
};
});
app.directive('clonable', function ($compile) {
return {
restrict: 'A',
link: function ($scope, $element, attrs) {
var copyBtn;
var copyTimeout;
$element.on('mouseenter', function (e) {
console.warn('mouseenter- copy');
console.warn($element);
e.preventDefault();
copyBtn = angular.element("<button ng-click='copy($event)'>Copy</div>");
$element.after(copyBtn);
$compile(copyBtn)($scope);
});
$element.on('mouseleave', function (e) {
console.warn('mouseleave- copy');
console.warn($element);
e.preventDefault();
copyBtn.fadeOut("slow");
});
$scope.copy = function ($event) {
console.warn('copy');
console.warn($element);
$element.after($element.clone());
copyBtn.remove();
}
}
};
});
Here is the jsfiddle. If copy "TEST TEST" it will actually copy the image
Related
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 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>
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);
});
}
<button-large color="green" click="createWorkstation()" busy="disableSave()" busyLabel="Saving...">Save</button-large>
I'm not able to watch changes to the output of disableSave(). The console.log()'s shown in my directive are never triggered when the output of .busy is changed. What am I doing wrong?
directive('buttonLarge', function () {
return {
scope: {
busy: '&',
click: '&'
},
replace: true,
restrict: 'E',
transclude: true,
template: '<button class="buttonL" ng-transclude/>',
link: function (scope, element, attrs) {
//when the button is busy, disable the button
if (angular.isDefined(scope.busy())) {
scope.$watch(scope.busy(), function () {
console.log('watched');
});
attrs.$observe(scope.busy(), function () {
console.log('observed');
});
}
//setup click event - https://groups.google.com/forum/#!topic/angular/-uVE5WJWwLA
if (angular.isDefined(scope.click)) {
element.bind('click', scope.click);
}
}
}
})
Controller
$scope.newWorkstationDialog = function (workflowProcess) {
var d = $dialog.
dialog({
resolve: {
workflowProcess: function () {
return workflowProcess;
}
}
}).
open('/partials/admin/'+workflowProcess.entity.slug+'/setup.htm', ['$scope', 'dialog', ..., function ($scope, dialog, ...) {
$scope.saving = false;
/* Create the workstation */
$scope.createWorkstation = function () {
console.log('saving');
$scope.saving = true;
$timeout(function () {
$scope.saving = false;
console.log('stopped saving');
}, 1000);
}
//Should the save button be disabled?
$scope.disableSave = function () {
return $scope.saving;//|| $scope.form.$valid;
}
$scope.cancel = function () {
dialog.close();
}
}]);
}
Your syntax of watching in not correct .You should be not using scope when doing watch because internally it use $parse service which internally attach scope . So you need to modify your code as below
1st option
scope.$watch(function(){
return scope.busy()
}, function (newvalue,oldvalue) {
console.log('watched');
});
2nd option
scope.$watch('busy()', function (newvalue,oldvalue) {
console.log('watched');
});