Event when list of uploads has changed - angularjs

I'm integrating Fine Uploader into an existing workflow. The user needs to provide a bit of metadata and select a file, then click Upload.
The Upload button should be disabled until a file is selected. If the user deletes the file, it should go back to disabled. I have autoUpload:false.
I'm using AngularJS so I'm binding to a function which invokes $("#fine-uploader").fineUploaderS3('getUploads'), and seems like a nice way to handle it.
However, I need a way to get notified when the list has changed, so I can trigger the digest cycle and update the UI. I've tried registering callbacks for the submitted and cancel events, which works – except that cancel is called when the upload is still in the list.
Is there a cleaner way to get a callback when the user adds or removes a file from the list of uploads?

In your case, listening for the "cancel" event is not appropriate. Why? Because the file/upload is not actually cancelled when your cancel callback is invoked due to the fact that you are able to abort the cancel attempt via your callback handler's return value.
You can be sure that the file has been cancelled internally by listening for the statusChange event. When it has truly been cancelled, your statusChange event handler will be called with the file ID and a newStatus parameter of qq.status.CANCELED.
You can read more on status change handling in the docs.
UPDATE: Since you are using Fine Uploader UI, the file is not removed from the UI until just after this status is updated. This doesn't seem like an issue for you though, since you are utilizing the getUploads method to determine file status.

Here's my working solution, in case it's useful to someone else:
$scope.uploader = $('#fine-uploader').fineUploaderS3({
...
}).on('statusChange', function (event, id, oldStatus, newStatus) {
$timeout(function () {
$scope.$digest();
});
});
$scope.has_selected_file = function () {
if (!$scope.uploader) return false;
var uploads = $scope.uploader.fineUploaderS3('getUploads');
var result = _(uploads).some(function (item) {
return item.status !== qq.status.CANCELED;
});
return result;
};
(Uses Underscore.js)

Related

How to prevent browser window from closing in AngularJS until the promise is resolved

I have the following code:
$window.onbeforeunload = function (event) {
event.preventDefault();
// some asynchronous code
};
I want the onbeforeunload event to wait until the asynchronous code is executed. Not sure if this can be achieved across all browsers supporting AngularJS.
Regardless of whether you are using AngularJS, or any other framework, delaying window unload until an async action completes is not something that modern browsers support anymore, as it creates a bad user experience.
However, assuming that the async thing you want to do is make an API request, then the modern solution is to use the navigator.sendBeacon() method instead. It is guaranteed to be sent in a non-blocking fashion by the browser, even after the window has been unloaded. It is a nicer solution for everyone.
Note that beacon requests have to be sent as POST requests, so your API needs to support this method for any endpoints you wish to use in an unload handler.
One can create a handler that looks at a spinner flag:
var spinnerFlag = false;
$window.addEventListener('beforeunload',unloadHandler);
$scope.$on("$destroy", function() {
$window.removeEventListener('beforeunload',unloadHandler);
});
function unLoadHandler(e) {
if (spinnerFlag) {
// Cancel the event
e.preventDefault();
// Chrome requires returnValue to be set
e.returnValue = '';
};
});
Then one can set and clear the flag:
spinnerFlag = true;
var promise = promiseBasedAPI(arguments).finally(function() {spinnerFlag = false;});
This will set the flag before starting the asynchronous operation and clear the flag when the promise either succeeds or rejects.
The user will be prompted to confirm the page unload if the promise has not resolved.
For more information, see
MDN Web API Reference - onbeforeunload
Prevent a webpage from navigating away using JavaScript — this answer
How to show the "Are you sure you want to navigate away from this page?" when changes committed?
Can beforeunload/unload be used to send XmlHttpRequests reliably

AngularJS reloading multiple tab in the same time if any change

I have an add form that if the user file the form and submit, in the same time, user can see the data in the same page in table with ui-view and this is not an issue!!
Problem is: if i open more than one tab in the same browser, that post or change reflection doesn't show in other tab without refreshing or reloading. i want to see change in other tab of browser or other browser without reload
I want to see the change without refresh or reloading.
if i reload in other tab, it works but if i dont reload in other tab, the change doesn't effect
here you go for my post method:
$scope.formModel = {};
$scope.onSubmit = function () {
$http.post('http://127.0.0.1:8000/api/v1/contact/create/', $scope.formModel)
.then(function(response) { //if success, below fuction will execute
$scope.successPost = 'You have successfully submitted your Contact';
$timeout(function() {
$scope.successPost = '';
}, 4000);
//below $scope will push data in client site if the request is success
$scope.contacts.push(response.data);
//if any error occurs, below function will execute
}, function(response) {
// below variable will get predefined message that will explain exactly what happened
var errorData = response.data;
$scope.errorPost = Object.values(errorData)[0][0];
$timeout(function(){
$scope.errorPost = '';
}, 4000);
});
$scope.formModel = {}; //It means, after submit, the form field will clear
$scope.addContactForm.$setPristine();
};
I want to see the change in the other tab without refresh or reload
well browser tabs are per definition separated from each other and can't communicate directly between each other.
there are certain possibilities to handle that: for example you can have a separate external service and always poll that service for latest changes and if something changes automatically call reload.
or you could use a service that provide a websocket that sends you to the frontend an update notification once something changed in your data.
Just want to point you in the right direction, the topic is to big to provide a finished solution. I recommend to do some research on how to communicate between tabs / how to build a service with an update notification for new data
Edit: Just to add #Yftach point from the comments: Theoretically localstorage is shared between tabs, so theoretically you could have a variable there and watch in an interval for changes. However the solution with a separate server is (at least in my eyes) way better

angularjs fire promise and redirect to external URL before it resolves [duplicate]

I'm writing a small script to capture link clicks and save the link's URL into a database table in order to track how many times each link on a particular page is clicked. The links are to external sites.
So, I capture the click event in my JavaScript function, use jQuery to post to a PHP page that saves the data in MySQL, and then the JavaScript function redirects the user to the URL of the link they clicked on.
The problem I'm running into is that it seems like the post never completes because the of redirect. I've worked around this by calling the redirect inside the post callback, but that adds a few second delay because it doesn't get called until after the post completes. I'm looking for a way to just post the data and redirect the user immediately, regardless of whether or not the post succeeds or fails.
This is the current code with the workaround. It works fine, but adds the delay:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
},
function( response )
{
window.location = clicked;
}
);
event.preventDefault();
}
And this is what I'd like to do, but when I try it never completes:
function trackClicks(event)
{
var clicked = $(this).attr('href');
$.post
(
$('#track-click-post-url').attr('value'),
{
action: 'track_landing_page_clicks',
clicked_url: clicked,
nonce: $('#track-click-nonce').attr('value')
}
);
window.location = clicked;
event.preventDefault();
}
jQuery doesn't provide a callback for what you're looking for. Here are the available ready states:
Value State Description
0 UNSENT open()has not been called yet.
1 OPENED send()has not been called yet.
2 HEADERS_RECEIVED send() has been called, and headers and status are available.
3 LOADING Downloading; responseText holds partial data.
4 DONE The operation is complete.
You're looking for readystate 2, as that's the earliest you're aware of the fact that the server received the message.
This should get you off the ground:
var xhr = new XMLHttpRequest();
xhr.open("POST", clicked);
xhr.onreadystatechange = function() {
if (xhr.readyState >= 2) window.location = clicked;
};
xhr.send($('#track-click-post-url').attr('value'));
https://developer.mozilla.org/en/XMLHttpRequest for further reading.
Why do you post using Javascript when you are going to load a page any way?
Just update the db with the link clicked on the new page.
Perhaps using the referrer URL to track on what page the click was.
Or some other solution to get on what page the click was (e.g. url param) or some other way.
When you leave a page, all pending requests are killed, and the new page loads. The 1st way is the correct way. Yes, there will be a delay when a link is clicked, that's because the POST request is running.
You can't run a request in the background of a page, if the user is not on that page.
Maybe you can save the link URL in a cookie, and put it into the DB when the next page loads.

How to use $resource in AngularJS properly for building a client app?

I've been following this tutorial http://draptik.github.io/blog/2013/07/28/restful-crud-with-angularjs/. I implemented a Grails backend with it instead of the Java one in the tutorial.
I've got the data coming back and forth, with one issue. If I create/update/delete a user, I don't see the changes reflected on my user list when I am redirected back. I have to refresh the page to see the updates.
Looking at the network traffic for an edit, it looks like it does a PUT and fires off the GET before the PUT is complete. Assuming this is because $resource returns a promise so things can be done asynchronously. So how do I handle this so that when $location redirects me, my list is up to date?
I'm guessing the options are to wait for the PUT to complete before redirecting/querying for the list, or to somehow manually manage the $scope.users to match the request?
Or maybe this tutorial is just a bad example? Maybe there is a better way to do it (still using $resource)?
Note: I've seen Restangular out there, and I've seen $http with success callbacks, but I would like to understand the situation above.
One way to overcome this issue would be to not redirect to the list page, till you get a callback, and then do a redirect. You can show some busy indicator till that time. The resource call looks like this.
resource.update(config,data,function() { //gets called on success},
function(error) { //gets called on failure});
In real life scenario waiting for the response of update makes sense as you want to handle the error and success scenarios on the same page.
I don't see your code anywhere so i'm just assuming (based on what you wrote and your current problem)
You are probably doing a full (or partial) get each time you changed a user and (re)binding the result to your scope. Doing this in the callback of the resource should actually start the digest cycle angular does to update modified objects. If you had been doing the fetching outside $resource - for example with custom/jquery ajax you would need to execute $scope.$apply()
What i really don't understand you would need to wait for the callback. You already know you added/modified a user. Instead of 'detaching' that user from your scope, modify it, post it to your rest server, then wait for callback, and reinserting it into the scope - why not modify it directly in the list/array you put on your scope?
var users = Users.get(function () {
$scope.users = users.record; // bind the resulting records to the scope
});
$scope.updateUser = function (user) {
resource.update(...); //pseudo
};
Then in your html, you will keep a reference to the currentUser and the div-list will update automaticly.
<div ng-repeat="user in users" ng-click="currentUser=user">{{user.Name}}</div>
<input ng-model="currentUser.Name">
<button ng-click="updateUser(currentUser);">Update</button>
If you don't want to see the update in the list while you type, but only once your callback fires or when you hit the button, would would instead use another ng-model for your input like this:
<input ng-model="tempUser.Name">
And you would then copy the value other in either the updateUser method or in the resource callback like this:
$scope.updateUser = function (user) {
user.Name = $scope.tempUser.Name; // should update automaticly
resource.update(...) // pseudo
}
Hope it helped!

Fine-Uploader How to capture additional params for each file while still allowing multiple files

I have the requirement to allow a user to select a file to upload and then need to capture additional required information from the user that applies to that specific file that will be sent as params to the server when uploading.
Thus far, I have been doing this by working off of the fine-uploader onSubmit callback and displaying a modal with the inputs I need. If inputs pass validation, I store the file id and associated params, otherwise, if they close/cancel the modal I call the fine-uploader cancel api and cancel the file with the given id.
The user can do this multiple times, which queues the files into the standard fine-uploader list and then they would press "Upload" to actually start the uploads concurrently.
All of this works great, my problem is that because I have the "multiple" option enabled, the user can select or drag multiple files at the same time, but I need to present the modal to each of them individually. The onSubmit event is firing individually for each of the files so how can I prevent the second, third, etc, modal from opening until I either cancel or save the previous one?
For the record I am actually using Backbone/Marionette and triggering a "file:added" event for each onSubmit callback. This is where I am creating the modal view and displaying it, however, the solution doesn't need to be tied to backbone.
Also, if there is a better way to collect parameters for a file using fine-uploader, I am not necessarily tied to this approach - just need to make sure that each file has the additional data populated otherwise it can not be uploaded.
Here is the Fine Uploader onSubmit callback:
.on('submit', function(event, id, name) {
var args = {};
args.id = id;
args.fileName = name;
uploadDocsVent.trigger('file:added', args);
})
Here is my code that is listening for that event, creating a File Backbone model, and instantiating a view to be displayed in a Modal region.
uploadDocsVent.on("file:added", function(args){
var file = new UploadDocuments.File( args );
var addlInfoView = new UploadDocuments.Views.FileInfo({
model: file,
categories: App.uploadedFilesCategories
});
App.appLayout.modalConfirmRegion.show(addlInfoView);
});
In my view I have this code responding to the modal clicks:
onDialogPrimaryClicked: function () {
UploadDocuments.claimsUploadWidget.addFileWithInfo( this.model );
this.trigger("dialog:close");
},
onDialogSecondaryClicked: function () {
UploadDocuments.uploadView.uploader.fineUploader('cancel', this.model.get('id'));
this.trigger("dialog:close");
}
Thanks in advance for any ideas on how to approach/get around this.
Andrew - I think I know what you are getting at. It's clear that you simply want Fine Uploader to only process a file in a batch of selected files after the user has submitted information in the modal that your display for that file in your onSubmit handler. What you are looking for is a way to return a promise, or a "deferred" in your onSubmit handler. This would allow you to call Fine Uploader back (and say "go ahead, upload/submit this file and move on to the next") once your user has submitted the associated modal.
I have already created a qq.Promise "class" that implements the promise pattern. Fine Uploader observes a promise as a valid return value only for the onPasteReceived callback at this time. It might make sense to allow this as a valid return value for other callbacks, such as onSubmit. This would allow you to defer uploading/submittal of a file until the user has dealt with the associated modal. Setting the multiple option to "false" may or may not be a desirable option for you now, due to the way Fine Uploader functions in "single-file mode".
I think it may be valuable to allow promises to be returned in some callbacks, such as this one, to allow for user interaction. Could you please open up a feature request in the Github project? There, we can discuss a bit more and you can track progress on any associated changes I put into the library.

Resources