Locohost
Locohost

Reputation: 1702

AngularJS how to delay bind an element

Have some 'status' elements on the page that I want to set delayed, meaning after a number of file uploads complete. Of course this is very easy with a jQuery selector, but how do I do this in the "Angular way"?

Here's the trick: the number of files uploading can be 1 to X, so these 'status' spans (to be filled with text after uploads complete) will each need to reference an item in an array.

I've tried a few things and I can't make it work.

Does my question make sense?

Upvotes: 2

Views: 2874

Answers (2)

Locohost
Locohost

Reputation: 1702

Here is my solution. To implement you will need to visit Github and download and install angular-file-upload.js. Thank you danialfarid for this great Angular file upload tool!

Here is my view html...

<div>
    <b>Select one or many files by holding [Ctrl] when clicking name:</b> 
    <br/>

    <input type="file" ng-file-select="onFileSelect($files)" multiple>
    <br/><br/>

    <b>Selected File(s):</b>
    <ol ng-bind-html-unsafe="filesSelected()"></ol>
    <br/>

</div>

Here is my (the relevant part) controller....

angular.module('myApp.controllers', [])
    .controller('PhotoCtrl', ['$scope','$http', function($scope,$http) {

        $scope.selectedFiles = [];

        $scope.onFileSelect = function ($files) {
            $scope.selectedFiles = $files;
            for (var i = 0; i < $files.length; i++) {
                var $file = $files[i]; 
                $http.uploadFile({
                    url: 'server/PhotoUpload.php',
                    data: {"fileIdx": i},
                    file: $file
                }).success(function (data, status, headers, config) {
                    var selector = ".status-" + config.data.fileIdx;
                    var elem = angular.element(document.querySelector(selector));
                    var status = "";
                    if (data.ok == "true") {
                        elem.addClass('bold green'); 
                        status = "[ OK ]";
                    } else {
                        elem.addClass('bold red');
                        status = "[ " + data.error + " ]";
                    }
                    elem.html("&nbsp;" + status);
                    // to fix IE not refreshing the model
                    if (!$scope.$$phase) $scope.$apply();
                });
            }
        };

        $scope.filesSelected = function() {
            var html = "";
            for (var x = 0; x < $scope.selectedFiles.length; x++) {
                var file = $scope.selectedFiles[x];
                status = '<span class="status-' + x + '"></span>';
                html += "<li><span>" + file.name + " / size: " + file.size + "B / type: " + file.type + "</span>" + status + "</li>";
            }
            return html;
        };
    }]);

I'm linking the controller in the routeprovider. Also note the line url: 'server/PhotoUpload.php'. You'll need to Google for a PHP (or you can use node.js if you know how, PHP was easier for me right now) script to actually receive the files uploaded and write them to disk.

Don't forget to put the bold, green and red classes in your css file.

The kinda neat trick in this is in the $http promise success code. It receives the status for each file upload and then sets a green/red status message next to each numbered selected file. It works pretty well. I'll probably do two enhancements soon: 1) Show status icons with the status text and... 2) I'm going upload the files as CouchDB attachments rather than write a disk folder. I'm starting on #2 right now!

And "yes" I do know that updating the DOM from the controller isn't the "Angular way", however there is no other way to do this delayed file upload status display. Not that I've figured out. Please correct me if I'm wrong ;-) If you can suggest any improvements I would be very happy if you posted them :-)

Upvotes: 0

shaunhusain
shaunhusain

Reputation: 19748

You can still use jQuery selectors in Angular the only real rule is you don't do DOM manipulation anywhere but custom directives. It's not entirely clear what you're trying to do but if you need to directly manipulate the DOM and can't achieve it with ng-class or ng-show/hide or some other built in directive then you probably need a custom directive.

Write them like

angular.module("myModule", []).directive("myAwesomeDirective", [function(){
    return {
       restrict:'E', //could be E = element, C = class, A = attribute
       scope: {incomingData:"="},
       link:function(scope,iElem,iAttrs) {
          //this function called for each instance of the directive
          //do your DOM manipulation here
           scope.$watch(function() {return scope.incomingData}, function(newVal,oldVal) {
              console.log(newVal);
          }, true)
       }
    }
}]).controller("MyCtrl", ["$scope", function($scope) {
    $scope.someArray = [1,2,3,4];
    $scope.addElement = function() {
        $scope.someArray.push(Math.floor(10*Math.random()));
    }
}]);

Usage like

<div ng-app="myModule" ng-controller="MyCtrl">
    <button ng-click="addElement()">Add random</button>
    test
    {{someArray}}
    <my-awesome-directive incoming-data="someArray"></my-awesome-directive>
</div>

See more here: http://docs.angularjs.org/guide/directive or http://www.egghead.io

If this doesn't help try showing some code or just explain more about what you have tried and what isn't working as expected.

EDIT

Updated to include some stuff for binding to an incoming parameter, will try to setup a fiddle for it.

http://jsfiddle.net/uBaSa/1/

Upvotes: 3

Related Questions