ministryofjustice/peoplefinder

View on GitHub
app/assets/javascripts/peoplefinder/photo_upload.js.erb

Summary

Maintainability
Test Coverage
/* global $, _ */
var PhotoUpload = (function (){
  'use strict';

  var PhotoUpload = {

    defaultImage:   "<%= asset_path('medium_no_photo.png') %>",
    imageSizeLimit: 10.0,

    enhance: function ( el ){
      var els = PhotoUpload.findElements(el);

      els.$input.change(PhotoUpload.photoSelected);
      els.$uploadButton.click(PhotoUpload.uploadPhoto);
      els.$cropButton.click(PhotoUpload.cropDone);
      els.$cropAgainButton.click(PhotoUpload.cropAgain);
      els.$resetButton.click(PhotoUpload.resetImage);
    },

    photoSelected: function ( event ){
      var els = PhotoUpload.findElements(event.target);
      PhotoUpload.clearError(els);
      PhotoUpload.showUploadButton(els, false);
      PhotoUpload.checkFileSize(this, PhotoUpload.imageSizeLimit, els);

      $('.save-cancel-actions input').hide();
    },

    checkFileSize: function ( fileInput, limit, els ) {
      // let backend validator handle files between 6 and 10MB for more consistent error handling.
      var el = fileInput;
      var fileSize = PhotoUpload.fileSize(el)
      if (fileSize > limit) {
        PhotoUpload.showError(els, 'File ' + el.files[0].name + ' is too large for upload (' + fileSize.toFixed(2) + 'MB)');
        els.$input.val('');
      } else {
        PhotoUpload.showUploadButton(els, true);
      }
    },

    fileSize: function ( el ) {
      // return 0 if File API (IE 11+) not available
      // NOTE: server will throw 413 for POST exceeding 20MB
      var fileSize = 0;
      if (window.FileReader) {
        if (el.files.length > 0) {
          fileSize = el.files[0].size / 1048576;
        }
      }
      return fileSize;
    },

    showUploadButton: function (els, show){
      var show = typeof show !== 'undefined' ? show : true;
      if (show) {
        els.$uploadButton.removeClass('hidden');
      } else {
        els.$uploadButton.addClass('hidden');
      }
    },

    uploadPhoto: function ( event ){
      event.preventDefault();
      var els = PhotoUpload.findElements(event.target);

      var iframeId = 'photo-upload-iframe-' + ~new Date();
      var iframe = $('<iframe id="' + iframeId + '" name="' + iframeId + '"></iframe>');
      iframe.hide();
      $(els.$el).append(iframe);
      iframe.load(PhotoUpload.photoUploaded);

      var form = $('<form target="' +
        iframeId +
        '" method="POST" action="/profile_photos" ' +
        'enctype="multipart/form-data"></form>');

      form.append(els.$input);
      form.append($('input[name="authenticity_token"]').clone());
      $(els.$el).append(form);
      PhotoUpload.setState(els, 'uploading');
      form.submit();
      els.$label.after(els.$input);
      form.remove();

      if( els.$preview.data('jcrop') ) {
        els.$preview.data('jcrop').destroy();
      }
    },

    photoUploaded: function ( event ){
      var $iframe = $(event.target);
      var jsonText = $iframe.contents().text();
      if(jsonText.length>1){
        var uploadData = $.parseJSON(jsonText);
        var els = PhotoUpload.findElements($iframe);
        if (uploadData.error) {
          // e.g. carrierwave allowlist errors
          PhotoUpload.showError(els, uploadData.error);
          PhotoUpload.setState(els, 'cropped');
        } else {
          els.$preview.attr('src', uploadData.image.croppable.url);
          els.$preview.css({clip: ''});
          PhotoUpload.setState(els, 'cropping');
          setTimeout(_.bind(function (){
            PhotoUpload.setupCrop(els);
          }, this), 1000);

          els.$photo_id.data('old-id', els.$photo_id.val());
          els.$photo_id.val(uploadData.id);
        }

        PhotoUpload.showUploadButton(els, false);
        $iframe.remove();
      }
    },

    showError: function ( els, error_messages ){
      els.$uploadError.removeClass('hidden');
      els.$uploadError.text(error_messages);
    },

    clearError: function ( els ) {
      els.$uploadError.addClass('hidden');
    },

    preview: function ( event ){
      if( !window.FileReader ) {
        return false;
      }

      var els = PhotoUpload.findElements(event.target);
      var file = els.$input[0].files[0];
      var reader = new FileReader();

      reader.onloadend = _.bind(function (){
        els.$preview[0].src = reader.result;
      }, this);

      reader.readAsDataURL(file);
    },

    togglePreviewUploading: function ( $preview, on ){
      var $parent = $preview.closest('.maginot');

      on = on || !$parent.hasClass('upload-in-progress');
      $parent[on ? 'addClass' : 'removeClass']('upload-in-progress');
    },

    setupCrop: function ( els ){
      var imgw = els.$preview.width();
      var trueSize = [
        els.$preview.naturalWidth(),
        els.$preview.naturalHeight()
      ];

      els.$preview.Jcrop({
        setSelect: [0,0,trueSize[0],trueSize[0]],
        trueSize: trueSize,
        // boxWidth: 450, boxHeight: 400 ,
        onSelect: function ( cropData ){
          els.$preview.data('new-crop-data', cropData);
        },
        aspectRatio: 1
      }, function (){
        els.$preview.data('jcrop', this);
      });
    },

    cropDone: function ( event ){
      event.preventDefault();

      var els = PhotoUpload.findElements(event.target);
      var cropData = els.$preview.data('new-crop-data');
      var previewBoxWidth  = els.$preview.closest('.media-object').width();
      var naturalWidth  = els.$preview.naturalWidth();

      var xaspect = previewBoxWidth  / naturalWidth;

      els.$crop_x.val(cropData.x);
      els.$crop_y.val(cropData.y);
      els.$crop_w.val(cropData.w);
      els.$crop_h.val(cropData.h);

      var zoomRatio = previewBoxWidth / cropData.w / xaspect;
      var transformOriginX = xaspect * cropData.x;
      var transformOriginY = xaspect * cropData.y;
      var transformOrigin = " " + Math.floor(transformOriginX) + "px " + Math.floor(transformOriginY) + "px ";

      els.$preview.data('jcrop').destroy();

      els.$preview.css({
        marginLeft: -1 * transformOriginX,
        marginTop:  -1 * transformOriginY,
        zoom: zoomRatio,
        '-moz-transform': 'scale('+ zoomRatio +')',
        '-moz-transform-origin': transformOrigin
      });

      PhotoUpload.setState(els, 'cropped');

      $('.save-cancel-actions input').show();
      $('.save-cancel-actions input').prop('disabled', false);
    },

    cropAgain: function ( event ){
      event.preventDefault();
      PhotoUpload.setupCrop(PhotoUpload.findElements(event.target));
    },

    setState: function ( els, state ){
      var states = ['initial', 'uploading', 'cropping', 'cropped'];

      if( _.contains(states, state) ){
        els.$el.removeClass(states.join(' '));
        els.$el.addClass(state);
      }
    },

    resetImage: function ( event ){
      event.preventDefault();

      var els = PhotoUpload.findElements(event.target);
      PhotoUpload.clearError(els);
      els.$uploadButton.addClass('hidden');

      els.$preview.attr('src', PhotoUpload.defaultImage);
      els.$preview.attr('style', '');
      els.$photo_id.val('');

      els.$crop_x.val('');
      els.$crop_y.val('');
      els.$crop_w.val('');
      els.$crop_h.val('');

      PhotoUpload.setState(els, 'initial');
    },

    findElements: _.memoize(function ( el ){
      var $el           = $(el).closest('.person-photo');
      var $label        = $el.find('label[for="person-image"]');
      var $input        = $el.find('#person-image');
      var $preview      = $el.find('.maginot > img.media-object');


      if( !$el.hasClass('person-photo') &&
          $input.length   === 0 &&
          $preview.length === 0 ){
        return new Error('PhotoUpload needs a .person-photo block.');
      }

      return {
        $el:      $el,
        $input:   $input,
        $label:   $label,
        $preview: $preview,

        $uploadError:     $el.find('.js-photo-upload-error'),
        $uploadButton:    $el.find('.upload-button-bar .photo-upload-button'),
        $cropButton:      $el.find('.upload-button-bar .crop-finished-button'),
        $cropAgainButton: $el.find('.crop-again-button'),
        $resetButton:     $el.find('.reset-image-button'),

        $photo_id:  $el.find('#person_profile_photo_id'),
        $crop_x:    $el.find('#person_crop_x'),
        $crop_y:    $el.find('#person_crop_y'),
        $crop_w:    $el.find('#person_crop_w'),
        $crop_h:    $el.find('#person_crop_h')
      };
    })

  };

  return PhotoUpload;
})();

(function(){
  var makeProp = function (natural, prop) {
    $.fn[natural] = (natural in new Image()) ?
    function () {
      return this[0][natural];
    } :
    function () {
      var
      node = this[0],
      img,
      value;
      if (node.tagName.toLowerCase() === 'img') {
        img = new Image();
        img.src = node.src,
        value = img[prop];
      }
      return value;
    };
  };
  makeProp('naturalWidth', 'width');
  makeProp('naturalHeight', 'height');
})();

$(function (){
  var photoBlocks = $('.person-photo');
  if(photoBlocks.length > 0){
    _.each(photoBlocks, PhotoUpload.enhance);

    var saveBtn = $('.save-cancel-actions input[name="commit"]'),
      cropButton = PhotoUpload.findElements(photoBlocks).$cropButton,
      editForm = $('form.edit_person, form.new_person');

    saveBtn.click(function(e){
      e.preventDefault();
      if(cropButton.is(':visible')){
        cropButton.trigger('click');
        editForm.submit();
      } else {
        editForm.submit();
      }
    });
  }
});