Search code examples
javascriptangularjsfile-ioevent-bubbling

Allow for modal dialog before file dialog in Angular


I'm wanting to extend the directive outlined in this popular question.

.directive("fileread", [function () {
    return {
        scope: {
            fileread: "="
        },
        link: function (scope, element, attributes) {
            element.bind("change", function (changeEvent) {
                scope.$apply(function () {
                    scope.fileread = changeEvent.target.files[0];
                    // or all selected files:
                    // scope.fileread = changeEvent.target.files;
                });
            });
        }
    }
}]);

Essentially - what I want to do is when the user presses the 'open file' file input, it will display a 'this will abandon changes' modal dialog.

If the user presses 'cancel', the file dialog shouldn't be displayed, but if 'proceed without saving' or 'save changes and proceed' are pressed, only then should the select file dialog be displayed.

I want to be able to pass the modal creation function as a directive parameter - so I can use different modals before opening a file dialog.

Example usage:

<label class="btn btn-default" for="fileOpen">
    Open File
</label>
<input 
      style="display: none" 
      type="file"
      fileread="onFileRead" 
      id="fileOpen" 
      name='file' 
      prefn="openModal"
 />

Here's a jsfiddle that demonstrates what I'm trying to do, and the example issue: http://jsfiddle.net/hcyj3q6q/55/

Here's the full directive I've written:

app.directive("fileread", [function() {
  return {
    scope: {
      fileread: "=",
      prefn: "="},
    link: function(scope, element, attributes) {

            element.bind("click", function(event){
                if (scope.prefn){           // prefn is optional
                    //event.preventDefault();

                    scope.$apply(function(){
                        scope.prefn().then(
                            function() {
                                $timeout(function(){
                                 //Resume file dialog                       
                                }); 
                            }, 
                            function() {
                                //Don't open file dialog
                            }
                        );                                      
                    });
                }        
            });


      element.bind("change", function(changeEvent) {

                    scope.$apply(function() {
          var file = changeEvent.target.files[0];

          var reader = new FileReader();
          reader.readAsText(file);

          reader.onload = function(e) {
            scope.$apply(function() {
              var contents = e.target.result;
              scope.fileread(contents);
            });
          };

        });

      });

    }
  }
}]);

As is - the problem will be that the modal appears at the same time as the open file dialog.

We can prevent the file dialog opening with event.preventDefault(); (uncomment it), but then I don't know how to resume the 'change' event.

Is it possible to trigger a change event manually?


Solution

  • You want to click on a different button, not the file input label. User would not see the file input or it's label.

    Then in confirm of the modal ... trigger a click on the file input.

    Following uses an angular event broadcast from modal controller to directive


    In directive replace element.bind('click... with:

     scope.$on('init-file-select', function(){          
          element[0].click();
     });
    

    In modal controller modify beginning to look like:

    app.controller("AbandonChangesModalCtrl", function($scope,  $modalInstance, $rootScope){
    
        $scope.yes = function(){
           // this could also be done from main controller in `result` promise callback
           $rootScope.$broadcast('init-file-select')
            $modalInstance.close('yes');
        }
    

    DEMO