I have a directive that handles uploading file and shows it in a list. For this I have a custom button for opening up the explorer. And after user selects a file from the explorer system shows the file name twice in the list. After debugging I realized it's calling the "onClick" method twice, once when the user clicks it (duh) and some mysterious event invokes it again. I think it's the scope.$apply part but can't be sure. Here's my code snippet:
<div data-ng-click="addFile($event)">
<span class="icon-small icon-add"></span>
</div>
Angular JS:
scope.addFile = function (event) {
if (event.originalEvent == null || !(event.originalEvent instanceof MouseEvent)) {
return;
}
if (!hiddenInputElementNode) {
//inject the hidden HtmlInputFile element and bind to the click event
hiddenInputElementNode = angular.element(
"<input accept='application/pdf,audio/*' type='file' class='hidden' multiple />");
hiddenInputElementNode.insertAfter(event.target);
}
//bind to the inputElementNode change event
hiddenInputElementNode.bind('change', function () {
angular.forEach(hiddenInputElementNode[0].files, function (dataFile) {
scope.$apply(
scope.selectedFiles.push({
name: dataFile.name,
data: dataFile
}));
});
this.value = null;
hiddenInputElementNode.unbind('change');
});
$timeout(function () {
if (!!hiddenInputElementNode) {
hiddenInputElementNode.click();
}
}, 0, false);
};
Even weirder this.value = null doesn't nullify the value!
Try by changing your javascript code for this one:
scope.addFile = function (event) {
if (event.target.tagName.toUpperCase() === "DIV") {
if (!hiddenInputElementNode) {
//inject the hidden HtmlInputFile element and bind to the click event
hiddenInputElementNode = angular.element(
"<input accept='application/pdf,audio/*' type='file' class='hidden' multiple />");
hiddenInputElementNode.insertAfter(event.target);
}
//bind to the inputElementNode change event
hiddenInputElementNode.bind('change', function () {
angular.forEach(hiddenInputElementNode[0].files, function (dataFile) {
scope.$apply(
scope.selectedFiles.push({
name: dataFile.name,
data: dataFile
}));
});
this.value = null;
hiddenInputElementNode.unbind('change');
});
$timeout(function () {
if (!!hiddenInputElementNode) {
hiddenInputElementNode.click();
}
}, 0, false);
}
};
I believe that you ng-click event is been fired twice because of the span inside the div (i had a similar problem with IE too).
Related
I need to implement toggle functionality for the widget. When the user clicks on the minimization button then widget should shrink and expand when click on maximize button respectively.
I'm trying to achieve this functionality with below piece of code.
Functionality working as expected but it is registering the event multiple times(I'm emitting the event and catching in the filterTemplate directive).
How can we stop registering the event multiple times ?
Or
Is there anyway to like compiling once and on toggle button bind the template/directive to DOM and to make it work rest of the functionality .
So could you please help me to fix this.
function bindFilterTemplate(minimize) {
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) { // filter template is custom
// directive like this
// "<widget></widget>"
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope); // Compiling with
// current scope on every time when user click on
// the minimization button.
});
}
} else {
$timeout(function () {
element.find('.cls-filter-body').empty();
});
}
}
bindFilterTemplate();
// Directive
app.directive('widget', function () {
return {
restrict: 'E',
controller: 'widgetController',
link: function ($scope, elem) {
// Some code
}
};
});
// Controller
app.controller('widgetController', function ($scope) {
// This event emitting from parent directive
// On every compile, the event is registering with scope.
// So it is triggering multiple times.
$scope.$on('evt.filer', function ($evt) {
// Server call
});
});
I fixed this issue by creating new scope with $scope.$new().
When user minimizes the widget destroying the scope.
Please let me know if you have any other solution to fix this.
function bindFilterTemplate(minimize) {
// Creating the new scope.
$scope.newChildScope = $scope.$new();
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) {
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope.newChildScope);
});
}
} else {
$timeout(function () {
if ($scope.newChildScope) {
// Destroying the new scope
$scope.newChildScope.$destroy();
}
element.find('.cls-filter-body').empty();
});
}
}
I'm using the Angular UI bootstrap modal and I ran into a bit of a problem.
I want to call a function when the bootstrap modal dismiss animation is finished. The code block below will call the cancel() function as soon as the modal starts to be dismissed - and NOT when the modal dismiss animation has finished.
Angular UI does not use events, so there is no 'hidden.bs.modal' event being fired (at least, not to my knowledge).
var instance = $modal.open({...});
instance.result.then(function(data) {
return success(data);
}, function() {
return cancel();
})
The cancel() block immediately runs when the modal starts to close. I need code to execute when the closing animation for the Bootstrap modal finishes.
How can I achieve this with angular UI?
Component for reference:
https://angular-ui.github.io/bootstrap/#/modal
Thanks!
A little late but hope it still helps! You can hijack the uib-modal-window directive and check when its scope gets destroyed (it is an isolated scope directive). The scope is destroyed when the modal is finally removed from the document. I would also use a service to encapsulate the functionality:
Service
app.service('Modals', function ($uibModal, $q) {
var service = this,
// Unique class prefix
WINDOW_CLASS_PREFIX = 'modal-window-interceptor-',
// Map to save created modal instances (key is unique class)
openedWindows = {};
this.open = function (options) {
// create unique class
var windowClass = _.uniqueId(WINDOW_CLASS_PREFIX);
// check if we already have a defined class
if (options.windowClass) {
options.windowClass += ' ' + windowClass;
} else {
options.windowClass = windowClass;
}
// create new modal instance
var instance = $uibModal.open(options);
// attach a new promise which will be resolved when the modal is removed
var removedDeferred = $q.defer();
instance.removed = removedDeferred.promise;
// remember instance in internal map
openedWindows[windowClass] = {
instance: instance,
removedDeferred: removedDeferred
};
return instance;
};
this.afterRemove = function (modalElement) {
// get the unique window class assigned to the modal
var windowClass = _.find(_.keys(openedWindows), function (windowClass) {
return modalElement.hasClass(windowClass);
});
// check if we have found a valid class
if (!windowClass || !openedWindows[windowClass]) {
return;
}
// get the deferred object, resolve and clean up
var removedDeferred = openedWindows[windowClass].removedDeferred;
removedDeferred.resolve();
delete openedWindows[windowClass];
};
return this;
});
Directive
app.directive('uibModalWindow', function (Modals) {
return {
link: function (scope, element) {
scope.$on('$destroy', function () {
Modals.afterRemove(element);
});
}
}
});
And use it in your controller as follows:
app.controller('MainCtrl', function ($scope, Modals) {
$scope.openModal = function () {
var instance = Modals.open({
template: '<div class="modal-body">Close Me</div>' +
'<div class="modal-footer"><a class="btn btn-default" ng-click="$close()">Close</a></div>'
});
instance.result.finally(function () {
alert('result');
});
instance.removed.then(function () {
alert('closed');
});
};
});
I also wrote a blog post about it here.
I have a text box to enter some text to search records. I am using data-ng-model-options="{ debounce: 1000 }" with keyup event, data-ng-model-options" working fine, but I want to fire keyup event after the debounce time duration.
Currently keyup event fires instantly before debounce duration. May be I doing something wrong.
Here is my HTML
<input type="text" id="focusOnMe" placeholder="Search..."/ data-ng-keyup="loadSearchResult($event)" data-ng-model="searchText" ng-model-options="{ debounce: 1000 }">
And this is my Keyup event action
$scope.loadSearchResult = function(event) {
$rootScope.hideSearchResult = true;
$rootScope.showLoading = true;
var searchText = $scope.searchText.trim();
if (searchText.length > 0) {
$http({
method: 'POST',
url: '/secure/search',
data: {
searchText: searchText,
peopleFlag: checkboxValueForPeopleSearch,
colonyFlag: checkboxValueForColonySearch
}
}).success(function(data) {
console.log(data);
if (data !== undefined || data !== null) {
$timeout(function() {
$rootScope.hideSearchResult = false;
$rootScope.showLoading = false;
$scope.allSearchResult = {
"bookmarks": data.bookmarks,
"people": data.people,
"colonies": data.colonies
};
}, 300);
} else {
$rootScope.showLoading = false;
commonNotification($rootScope, false, true, true, 'something went wrong!');
$timeout(function() {
$rootScope.newStatus = false;
}, 2000);
}
}).error(function(err) {
});
} else {
$rootScope.hideSearchResult = true;
$rootScope.showLoading = false;
}
};
Sorry the code is dependent to more files, so that I don't have plunker example
Any suggestion will be helpful for me.
Thank You
Debounce doesn't affect the keyup event. It only delays the assigning of the model ($scope) variable. So your keyup event fires immediately and loadSearchResult runs before you want it to.
To solve it, you can add a watch tied to $scope.searchText.
$scope.$watch('searchText', function (newValue) {
loadSearchResult(newValue);
});
Super-simple and cleanly-coded because we're using the Single Source of Truth ($scope).
Note: I omitted the event object because you're not using it.
Once you specify debounce in ng-model-options, it will change the way model gets updated. But it won't change how key event works. For your case, use _.debounce would help:
var delay = 500;
$scope.loadSearchResult = _.debounce($scope.loadSearchResult, delay);
This is what I have, and it works (thankfully, angular supports jQuery out of the box if its loaded)....but I'm wondering what the "angular way" is to accomplish this.
I want to close the open menu if you click anywhere else on the page, but the menu:
<body ng-click="onClickPage($event)">
//app controller:
$scope.onClickPage = function(e){
$log.log(e);
$rootScope.$broadcast('click:page', e);
};
//navbar controller
$rootScope.$on('click:page', function(ev, e){
var $el = $(e.target);
if ( !$el.parents('.menu').length && !$el.hasClass('.menu') ) {
$log.log('hide dropdown');
$scope.hideDropdown();
}
});
That might depend on how your dropdown is implemented but a general idea is to bind/unbind click event handler to the $document when open/close the dropdown.
By doing this way,it doesn't polute global event listeners and $rootScope while the dropdown is not opened.
function onDocumentClicked(e) {
var dropdownEl = angular.element('.dropdown');
if (e && dropdownEl && dropdownEl[0].contains(e.target)) {
return; // do nothing if clicked inside the dropdown
}
closeDropdown();
$scope.$apply();
}
function openDropdown() {
if (!$scope.dropdown.isOpen) {
$scope.dropdown.isOpen = true;
$document.bind('click', onDocumentClicked);
}
}
function closeDropdown() {
if ($scope.dropdown.isOpen) {
$document.unbind('click', onDocumentClicked);
$scope.dropdown.isOpen = false;
}
}
For the full example see: http://plnkr.co/edit/mbx0sLnPetctlWNYdpJC?p=preview
I have such code inside directive :
$document.bind('keydown', function ($event) {
if ($event && $scope.visible && $event.which === escapeKey) {
$scope.toggle();
$scope.$apply();
}
});
I want to test if user click escape toggle will run. At moment I have such test:
it('should toggle window visibility to false when keypress escape', function () {
var doc,
$event;
$httpBackend.when(method, url)
.respond(template);
$event = {
event: 'keydown'
};
directive = createDirective();
$httpBackend.flush();
$isolateScope = directive.isolateScope();
$isolateScope.toggle();
$document.triggerHandler('keydown');
});
But how can I pass that certain key was pressed thought triggerHandler. Don't want to use any jQuery . Is there another way of testing this?
element.triggerHandler({type: 'keydown', which: escapeKey});