Pergunta

Hi I have a problem with $bind, I am binding a model and outputting the models via a ng-repeat. The ng-repeat outputs the stored data and also offers some fields for adding/changing data. The changes are reflected in the scope but are not being synced to Firebase.

Is this a problem with my implementation of $bind?

The HTML:

<iframe id="fpframe" style="border: 0; width: 100%; height: 410px;" ng-if="isLoaded"></iframe>
<form>
  <ul>
    <li ng-repeat="asset in upload_folder" ng-class="{selected: asset.selected}">
      <div class="asset-select"><input type="checkbox" name="selected" ng-model="asset.selected"></div>
      <div class="asset-thumb"></div>
      <div class="asset-details">
        <h2>{{asset.filename}}</h2>
        <p><span class="asset-filesize" ng-if="asset.size">Filesize: <strong><span ng-bind-html="asset.size | formatFilesize"></span></strong></span> <span class="asset-filetype" ng-if="asset.filetype">Filetype: <strong>{{asset.filetype}}</strong></span> <span class="asset-dimensions" ng-if="asset.width && asset.height">Dimensions: <strong>{{asset.width}}x{{asset.height}}px</strong></span> <span class="asset-type" ng-if="asset.type">Asset Type: <strong>{{asset.type}}</strong></span></p>
        <label>Asset Description</label>
        <textarea ng-model="asset.desc" cols="10" rows="4"></textarea>
        <label>Creator</label>
        <input type="text" ng-model="asset.creator" maxlength="4000">
        <label>Release Date</label>
        <input type="text" ng-model="asset.release">
        <label for="CAT_Category">Tags</label>
        <input type="text" ng-model="asset.tags" maxlength="255">
      </div>
    </li>
  </ul>
</form>

The Controller: (fpKey is a constant that stores the Filepicker API key)

.controller('AddCtrl',
  ['$rootScope', '$scope', '$firebase', 'FBURL', 'fpKey', 'uploadFiles',
  function($rootScope, $scope, $firebase, FBURL, fpKey, uploadFiles) {
    // load filepicker.js if it isn't loaded yet, non blocking.
    (function(a){if(window.filepicker){return}var b=a.createElement("script");b.type="text/javascript";b.async=!0;b.src=("https:"===a.location.protocol?"https:":"http:")+"//api.filepicker.io/v1/filepicker.js";var c=a.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);var d={};d._queue=[];var e="pick,pickMultiple,pickAndStore,read,write,writeUrl,export,convert,store,storeUrl,remove,stat,setKey,constructWidget,makeDropPane".split(",");var f=function(a,b){return function(){b.push([a,arguments])}};for(var g=0;g<e.length;g++){d[e[g]]=f(e[g],d._queue)}window.filepicker=d})(document);
    $scope.isLoaded = false;
    // Bind upload folder data to user account on firebase
    var refUploadFolder = new Firebase(FBURL.FBREF).child("/users/" + $rootScope.auth.user.uid + "/upload_folder");
    $scope.upload_folder = $firebase(refUploadFolder);
    $scope.upload_folder.$bind($scope,'upload_folder');
    // default file picker options
    $scope.defaults = {
      mimetype: 'image/*',
      multiple: true,
      container: 'fpframe'
    };
    // make sure filepicker script is loaded before doing anything
    // i.e. $scope.isLoaded can be used to display controls when true
    (function chkFP() {
      if ( window.filepicker ) {
        filepicker.setKey(fpKey);
        $scope.isLoaded = true;
        $scope.err = null;
        // additional picker only options
        var pickerOptions = {
          services:['COMPUTER', 'FACEBOOK', 'GMAIL']
        };
        var storeOptions = {
          location: 'S3',
          container: 'imagegrid'
        };
        var options = $.extend( true, $scope.defaults, pickerOptions );
        // launch picker dialog
        filepicker.pickAndStore(options, storeOptions,
          function(InkBlobs){
            uploadFiles.process(InkBlobs, $scope.upload_folder);
          },
          function(FPError){
            $scope.err = FPError.toString();
          }
        );
      } else {
        setTimeout( chkFP, 500 );
      }
    })();
  }])

I also have a service handling the input from Filepicker, this creates new entries in the firebase at the reference that is bound (using Firebase methods rather than AngularFire maybe this breaks the binding?)

.service('uploadFiles', ['$rootScope', 'FBURL', function($rootScope, FBURL) {
  return {
    process: function(InkBlobs, upload_folder) {
      var self = this;
      var countUpload = 0;
      // write each blob to firebase
      angular.forEach(InkBlobs, function(value, i){
        var asset = {blob: value};
        // add InkBlob to firebase one it is uploaded
        upload_folder.$add(asset).then( function(ref){
          self.getDetails(ref);
          countUpload++;
        });
      });
      // wait for all uploads to complete before initiating next step
      (function waitForUploads() {
        if ( countUpload === InkBlobs.length ) {
          self.createThumbs(upload_folder, { multi: true, update: false, location: 'uploads' });
        } else {
          setTimeout( waitForUploads, 500 );
        }
      })();
    },
    getDetails: function(ref) {
      // after InkBlob is safely stored we will get additional asset data from it
      ref.once('value', function(snap){
        filepicker.stat(snap.val().blob, {size: true, mimetype: true, filename: true, width: true, height: true},
          function(asset) {
            // get asset type and filetype from mimetype
            var mimetype = asset.mimetype.split('/');
            asset.type = mimetype[0].replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
            asset.filetype = mimetype[1];
            // add metadata to asset in upload folder
            ref.update(asset);
          });
      });
    },
    createThumbs: function(ref, options) {
      var self = this;
      // default options
      options.multi = options.multi || false;
      options.update = options.update || false;
      options.location = options.location || 'asset';
      // if pathbase is not provided generate it based on provided location
      if (!options.pathbase) {
        if (options.location === 'assets') {
          options.pathbase = FBURL.LIBRARY + "/assets/";
        } else if (options.location === 'uploads') {
          options.pathbase = "/users/" + $rootScope.auth.user.uid + "/upload_folder/";
        } else {
          throw new Error('SERVICE uploadFiles.createThumbs: options.location is not valid must be assets or uploads');
        }
      }
      var generateThumb = function(blob, path) {
        filepicker.convert( blob,
          { width: 200, height: 150, fit: 'crop' },
          { location: 'S3', access: 'public', container: 'imagegrid', path: '/thumbs/' },
          function(tnInkBlob){
            var refThumbBlob = new Firebase(FBURL.FBREF).child(path);
            refThumbBlob.set(tnInkBlob);
          },
          function(FPError){
            alert(FPError);
          },
          function(percentage){
            // can use to create progress bar
          }
        );
      };
      if (options.multi) {
        // look at all assets in provided ref, if thumbnail is mission or update options is true generate new thumb
        angular.forEach(ref, function(value, key){
          if (typeof value !== 'function' && (!value.tnblob || options.update)) {
            // thumb doesn't exist, generate it
            var blob = value.blob;
            var path = options.pathbase + key + '/tnblob';
            generateThumb(blob, path);
          }
        });
      } else {
        // getting thumbnail for a single asset
        var refAsset = new Firebase(FBURL.FBREF).child(options.pathbase + ref);
        var blob = refAsset.val().blob;
        var path = options.pathbase + ref + '/tnblob';
        generateThumb(blob, path);
      }
    }
  };
}]);

So to recap, data is being saved to /users/$rootScope.auth.user.uid/upload_folder and this is being rendered in the HTML. Changes in the HTML form are reflected in the scope but not in Firebase, despite the binding:

var refUploadFolder = new Firebase(FBURL.FBREF).child("/users/" + $rootScope.auth.user.uid + "/upload_folder");
$scope.upload_folder = $firebase(refUploadFolder);
$scope.upload_folder.$bind($scope,'upload_folder');

Any ideas as to why this is? Is my implementation incorrect or am I somehow breaking the binding? Is $bind even supposed to work with ng-repeat in this manner?

Thanks

Foi útil?

Solução

Shooting myself for how simple this is, the error was in how I defined the binding. You can't set the binding on itself, you need two separate objects in the scope...

The firebase reference $scope.syncdata loads the initial data and all modifications made to $scope.upload_folder will be synced to firebase.

var refUploadFolder = new Firebase(FBURL.FBREF).child("/users/" + $rootScope.auth.user.uid + "/upload_folder");
$scope.syncdata = $firebase(refUploadFolder);
$scope.syncdata.$bind($scope,'upload_folder');
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top