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

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.

Related

(Temporarily) Preventing angular from updating view when object changes?

I have an object that I'm sending over to a web server to store in the db. Between when I send it and when the operation completes I need to modify it slightly so the server can handle it.
In effect I have the following code:
$scope.o = {...};
$scope.send = function()
{
$scope.sanitize($scope.o);
SomeService.Save($scope.o).then( function() {
$scope.unsanitize($scope.o);
});
}
Sanitize just gets the object ready to send the server and unsanitize puts it back into a form that the view can handle more easily.
The issue I'm having is that between the call to sanitize and unsanitize, there's a few milliseconds of delay (or seconds, depending on how slow my network is at the time). During that time the view is updated and shows the changes that sanitize made. I don't want those changes showing up to the user while the server is doing its thing, though.
Is there a way of temporarily preventing angular from updating changes to a specific object?
I've created a jsfiddle that illustrates the problem here: http://jsfiddle.net/29ze4exp/5/
Sure! Just don't modify the object that is bound to the view. Instead, use a copy.
var copy = angular.copy($scope.o);
$scope.sanitize(copy);
SomeService.Save(copy).then(function() {

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!

How to Post Data before Pop-Up?

I have an application where the user can edit content, then preview that content. Before preview, I need to have the content saved ($http.post to the server). I'm using angular and because the save routine is run through a different controller the code to save looks like:
$scope.$broadcast('save');
var saveCompletedListener = $scope.$on('saveCompleted', function () {
// this call doesn't work (because it waits for data save):
window.open($scope.contentUrl);
});
// this call does work, but shows old data in the popup:
window.open($scope.contentUrl);
The problem is the window.open call is getting blocked in all browsers. If I just call the window.open without waiting for save, it opens fine (it just shows the content from the previous save). So, is there some way in angular to show the popup, but have it wait to load the url until save has completed?

Event when list of uploads has changed

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)

Webshims - Show invalid form fields on initial load

(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});

Resources