here is how I use angular jquery file upload
var album = angular.module('album', ['restangular', 'blueimp.fileupload']),
.controller('somecontroller',function($scope,){
$scope.options = {
something
}
})
all I did was set the scope.options, change the controller ,and everything just magically works
setup the jquery file upload seems quite easy, but there are something really confuse me
how can I call the jquery file upload's callback function. for example, if the files uploaded successfully,I want to update the ui by calling fileuploaddone function ,it confuse me because there is no added file in my controller.
I'm new to angularJS, please help me to understand the workflow of angular jquery file upload
the blueimp.fileupload uses events that are fired via $emit to notify parent scopes:
on([
'fileuploadadd',
'fileuploadsubmit',
'fileuploadsend',
'fileuploaddone',
'fileuploadfail',
'fileuploadalways',
'fileuploadprogress',
'fileuploadprogressall',
'fileuploadstart',
'fileuploadstop',
'fileuploadchange',
'fileuploadpaste',
'fileuploaddrop',
'fileuploaddragover',
'fileuploadchunksend',
'fileuploadchunkdone',
'fileuploadchunkfail',
'fileuploadchunkalways',
'fileuploadprocessstart',
'fileuploadprocess',
'fileuploadprocessdone',
'fileuploadprocessfail',
'fileuploadprocessalways',
'fileuploadprocessstop'
].join(' '), function (e, data) {
if ($scope.$emit(e.type, data).defaultPrevented) {
e.preventDefault();
}
})
That means that you can simply add an event listener in one of the parent scope controllers, e.g.:
$scope.$on('fileuploadprocessdone', function(event, files){
$.each(files, function (index, file) {
//do what you want
});
});
You can also override the default handleResponse function in your config phase, e.g.:
angular.module('myApp', ['blueimp.fileupload']).
.config(['fileUploadProvider', function (fileUploadProvider){
fileUploadProvider.defaults.handleResponse = function (e,data){
var files = data.result && data.result.files;
if (files) {
data.scope().replace(data.files, files);
// do what you want...
} else if (data.errorThrown || data.textStatus === 'error') {
data.files[0].error = data.errorThrown ||
data.textStatus;
}
};
}]);
Related
I have a panel widget with a button. Clicking the button should execute some global actions related to all such widgets and after that execute some local actions related to this widget instance only. Global actions are binded in a separate javascript file by CSS class like this:
var App = function ()
{
var handleWidgetButton = function ()
{
$('.widgetBtn').on('click', function (e)
{
// do smth global
});
return {
init: function ()
{
handleWidgetButton();
}
};
}
}();
jQuery(document).ready(function()
{
App.init();
});
And in the html file local script is like this:
$("#widgetBtn1234").click(function (e)
{
// do smth local
});
Currently local script is executed first and global only after while I want it to be the opposite. I tried to wrap local one also with document.ready and have it run after global but that doesn't seem to change the execution order. Is there any decent way to arrange global and local jQuery bindings to the same element?
The problem you're having comes from using jQuery's .ready() function to initialize App, while you seem to have no such wrapper in your local code. Try the following instead:
var App = function ()
{
var handleWidgetButton = function ()
{
$('.widgetBtn').on('click', function (e)
{
// do smth global
});
return {
init: function ()
{
handleWidgetButton();
}
};
}
}();
$(function()
{
App.init();
});
Then in your local JS:
$(function() {
$("#widgetBtn1234").click(function (e)
{
// do smth local
});
});
Note that $(function(){}) can be used as shorthand for $(document).ready(function(){});. Also, make sure your JS file is located before your local JS, as javascript runs sequentially.
Alternatively, you can use setTimeout() to ensure everything's loaded properly:
(function executeOnReady() {
setTimeout(function() {
// Set App.isInitialized = true in your App.init() function
if (App.isInitialized) runLocalJs();
// App.init() hasn't been called yet, so re-run this function
else executeOnReady();
}, 500);
})();
function runLocalJs() {
$("#widgetBtn1234").click(function (e)
{
// do smth local
});
};
How about this instead:
var widget = $("#widgetBtn1234").get(0);//get the vanilla dom element
var globalHandler = widget.onclick; //save old click handler
// clobber the old handler with a new handler, that calls the old handler when it's done
widget.onclick = function(e){
//do smth global by calling stored handler
globalHandler(e);
//afterward do smth local
};
There might be a more jqueryish way to write this, but I hope the concept works for you.
-------VVVV----keeping old answer for posterity----VVVV--------
Why not something like this?
var App = function ()
{
var handleWidgetButton = function ()
{
$('.widgetBtn').on('click', function (e)
{
// do smth global
if(this.id === 'widgetBtn1234'){
//do specific things for this one
}
});
return {
init: function ()
{
handleWidgetButton();
}
};
}
}();
Please excuse any syntax errors I might have made as I haven't actually tested this code.
Check out my simple JQ extension I created on jsbin.
http://jsbin.com/telofesevo/edit?js,console,output
It allows to call consequentially all defined personal click handlers after a global one, handle missed handlers case if necessary and easily reset all personal handlers.
I have 2 controllers like below,
app.controller('ParentMenuController',
function ($scope,MenuService) {
$scope.contentLoaded = false;
$scope.showButton = false;
$scope.showButton = MenuService.getStatus();
});
Controller2:
app.controller('ChildMenuController',
function ($scope,MenuService) {
MenuService.setStatus(true);
});
Service:
app.factory('MenuService', function ($q,$http) {
var status= false;
var setStatus=function(newObj){
status=newObj;
};
var getStatus=function(){
return status;
};
return {
getStatus:getStatus,
setStatus:setStatus
};
});
I am not able to set the status to true, but the below line of coding is not at all executing, so the status is always false.
$scope.showButton = MenuService.getStatus();
On button click or any action from user i can trigger the event, but my requirement is while page load, the button should not be visible. When childMenu controller executes, then parent controller button should be visible. I dont want to use $broadcast which requires $rootscope.
Note: My controller and html has hundereds of lines. I just pasted here required code for this functionality. ChildMenuController(childMenu.html) has separate html and ParentMenuController(parentMenu.html) has separete html.
So $scope.showButton is not available in ChildMenucontroller. Both html is used as directive. Main html is index.html.
See this sample:
http://plnkr.co/edit/TepAZGOAZZzQgjUdSpVF?p=preview
You need to use a wrapper object so that it's properties are changed instead of the main object.
app.controller('ParentMenuController',
function ($scope,MenuService) {
$scope.contentLoaded = false;
$scope.showButton = MenuService.getStatus();
});
app.controller('ChildMenuController',
function ($scope,MenuService) {
MenuService.setStatus(true);
});
app.factory('MenuService', function ($q,$http) {
var status= {value:false};
var setStatus=function(newObj){
status.value=newObj;
};
var getStatus=function(){
return status;
};
return {
getStatus:getStatus,
setStatus:setStatus
};
});
It's exactly the same as your code, but state is now an object with a value property. The state object is always the same one so that when the value is changed in the service the changes are propagated to everyone that has ever requested that object.
Actually your service is getting called and this piece of line is executing
$scope.showButton = MenuService.getStatus();
but once your child controller got loaded you are only setting the status but in order to show button you should getStatus after setting it
Like this,
app.controller('ChildMenuController', function($scope, MenuService) {
MenuService.setStatus(true);
$scope.$parent.showButton = MenuService.getStatus();
});
this will set the button to true and it will be shown.
I have done a sample with your code please take a look into it
DEMO
I have a modal dialog where the user can select files to be uploaded. The actual file select/upload is handled by ng-file-upload. When the user selects one or more file, they are added to a list in the dialog, showing progress, completion and failure statuses for each element. The list of items are handled inside a custom directive, since it's used other places as well.
I need to prevent the user from dismissing the dialog while files are still uploading, and that's a challenge for me, cause the button for closing the dialog is in one controller, while the list of uploads is in another (the directive controller). I have solved that by giving and empty list to the directive like this:
//extract from directive:
var directive = {
...
scope: {
'files': '='
}
}
//extract from usage
<uploadFiles files="files" />
Now the outer controller and the inner controller shares the list of files uploading.
So when the user tries to dismiss the dialog by clicking the Close button, I first check if the list contains files still uploading, and if so, I disable the button and display a spinner and a 'please wait'-text.
//from the outer controller
function onModalOk() {
if (uploadInProgress()) {
waitForCompletionBeforeClosingDialog();
} else {
closeDialog();
}
}
the waitForCompletionBeforeClosingDialog() is implemented by setting up a deep watch on the files array. Each time the watch is triggered, I loop through to see if every file has completed. If so, I delete the watch and dismiss the dialog.
function waitForCompletionBeforeClosingDialog() {
$scope.showWaitText = true;
var unregisterWatchForCompletion = $scope.$watch('files', function(files) {
if (allCompleted(files)) {
unregisterWatchForCompletion();
closeDialog();
}
}, true);
}
Everything is working ok, except for one little thing...
In the console, I get this error:
TypeError: Illegal invocation
at equals (angular.js:931)
at equals (angular.js:916)
at Scope.$digest (angular.js:14302)
at Scope.$apply (angular.js:14571)
at angular.js:16308
at completeOutstandingRequest (angular.js:4924)
at angular.js:5312
and it's fired in a tight loop.
I have tried debugging this error, but with no luck..
Do anyone have any ideas?
Is there better ways of doing this all together?
What about using an $httpInterceptor to keep count of the amount of active requests?
something like:
angular.module('someModule').provider('httpStatus', ['$httpProvider', function ($httpProvider) {
var currentRequestCount = 0;
var interceptor = ['$q', function ($q) {
return {
request: function (config) {
currentRequestCount++;
return config;
},
response: function (response) {
currentRequestCount--;
return response;
},
responseError: function (rejection) {
currentRequestCount--;
return $q.reject(rejection);
}
}
}];
$httpProvider.interceptors.push(interceptor);
this.$get = function () {
return {
isWaiting: function () {
return currentRequestLength > 0;
}
}
};
}]);
You could inject the httpStatus service into your dialog and use it to disable the buttons if there are any active requests. May need to add the requestError handler also.
https://docs.angularjs.org/api/ng/service/$http
In my angular code, I load some data in ajax which fill my template, so far so good...
Then, once the data are loaded, I want to call a javascript function.
I couldn't find a way to have callback once the template is rendered. I have try several events but none are working.
My solution is to call the javascript method after a timeout:
$http.post('url').success(function (data) {
$timeout(function () {/* process data */ }, 200);
});
It seems to work, but that's a timeout, nothing guarantee me that at the end of the timeout everything is rendered. Maybe it is working only because my PC is fast...
Is there an event based solution? Or any solution better than this one...
The jsfiddle of my solution : http://jsfiddle.net/ZCT4K/5/
You need to $watch() your data. So you could try the following:
$http.post('url')
.success(function (data) {
$scope.postData = data;
});
//Watch the value of postData
$scope.$watch('postData', function () {
if ($scope.postData === undefined || $scope.postData === null || $scope.postData === "") {
return;
}
/*Else process your data*/
});
I have a problem with event object passed to the function in drop event. In my code, div#dropArea has it's drop event handled by firstDrop function which does some animations and then calls the proper function dropFromDesktop which handles the e.dataTransfer.files object. I need this approach in two separate functions because the latter is also used further by some other divs in the HTML document (no need to duplicate the code). First one is used only once, to hide some 'welcome' texts.
Generally, this mechanism lets you drag files from desktop and drop them into an area on my website.
Here's, how it looks (in a shortcut):
function firstDrop(ev) {
var $this = $(this);
//when I call the function here, it passes the event with files inside it
//dropFromDesktop.call($this, ev);
$this.children('.welcomeText').animate({
opacity: '0',
height: '0'
}, 700, function() {
$('#raw .menu').first().slideDown('fast', function() {
//when I call the function here, it passes the event, but 'files' object is empty
dropFromDesktop.call($this, ev);
});
});
}
function dropFromDesktop(ev) {
var files = ev.originalEvent.dataTransfer.files;
(...) //handling the files
}
$('#dropArea').one('drop', firstDrop);
$('some_other_div').on('drop', dropFromDesktop);
The problem is somewhere in jQuery.animation's callback - when I call my function inside it, the event object is passed correctly, but files object from dataTransfer is empty!
Whole script is put inside $(document).ready(function() { ... }); so the order of function declarations doesn't matter, I guess.
I suspect your problem is related with the lifetime of the Event object. Unfortunately, I have no clue about the cause of it. But, there is a way to workaround it that I can think of and it is keeping a reference to Event.dataTransfer.files instead.
var handleFileList = function(fn) {
return function(evt) {
evt.preventDefault();
return fn.call(this, evt.originalEvent.dataTransfer.files);
};
};
var firstDrop = function(fileList) { ... }
var dropFromDesktop = function(fileList) { ... }
$('#dropArea').one('drop', handleFileList(firstDrop));
$('some_other_div').on('drop', handleFileList(dropFromDesktop));