4
\$\begingroup\$

I wrote a simple class that substitutes all file inputs in an HTML for a more complex template and set some actions on it. The code is rather small, but I'm not used to write JavaScript.

I wonder if there is a design pattern or extensively used way to set the callbacks? Of course, overall improvements are welcome as well.

The full code is here:

$(document).ready ->
  $('input[type="file"]').each ->
    new PhotoInput(this)

class PhotoInput
  constructor: (originalInput) ->
    @renderTemplate(originalInput)
    @setClickActions()
    @setPreviewRender()

  renderTemplate: (originalInput) ->
    @template = ich.image_file_input_template # this is ICanHazJS call - it loads a mustache template there.
      fileInput: $(originalInput)[0].outerHTML
    @fileInput = @template.find('input[type=file]').first()
    @previewImage = @template.find('.file-input-preview img')
    $(originalInput).replaceWith(@template)

  setClickActions: ->
    @template.find('#select-file').click =>
      @fileInput.focus().click()
    @template.find('.file-input-preview').click =>
      @fileInput.focus().click()

  setPreviewRender: ->
    @fileInput.change =>
      @previewImage.attr('alt', @fileInput.val())
      if(@fileInput[0].files && @fileInput[0].files[0])
        reader = new FileReader()
        reader.onload = (e) =>
          @previewImage.attr('src', e.target.result)
        reader.readAsDataURL(@fileInput[0].files[0])
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

You're instantiating a PhotoInput object but never assigning it to a variable. This implies that there's no need for a class at all. You're only instantiating it for its side-effects; you're not interested in the instance itself.

This works, but it's bad form.

Even if you stored the instance somewhere, you still can't really use it for anything. Sure, you could call its methods, like, say, setPreviewRender, but there'd be no point since it's already been called - in fact, it'd probably be a bad idea to call it again.

So don't use a class, just write a function and use closures instead of instance variables:

createPhotoInput = (originalInput) ->
  originalInput = $ originalInput

  # replace original input
  template = ich.image_file_input_template
    fileInput: originalInput.get(0).outerHTML

  originalInput.eq(0).replaceWith template

  # set click handlers
  input = template.find 'input[type=file]:first'
  template.find('#select-file, .file-input-preview').click (event) ->
    input.focus().click()

  # set preview handling
  preview = template.find '.file-input-preview img'
  input.change (event) ->
    preview.attr 'alt', input.val()
    file = input.get(0).files?[0]
    if file?
      reader = new FileReader
      reader.onload = (event) -> previewImage.attr 'src', e.target.result
      result.readAsDataURL file

  # return the template, just in case
  template

$ ->
  $('input[type="file"]').each ->
    createPhotoInput this

You'll note that it also does away with all the scope-handling and fat arrows (=>), since it's just closures.

\$\endgroup\$
4
  • \$\begingroup\$ Thank you very much for re-writing the full code! This is very similar what I had yesterday before trying to refactorate it, so maybe was a bad move trying to change it :P \$\endgroup\$
    – fotanus
    Commented May 26, 2014 at 13:50
  • \$\begingroup\$ @fotanus Well, refactoring is almost always good. But in this case, if you don't need a class, don't write a class :) \$\endgroup\$
    – Flambino
    Commented May 26, 2014 at 14:07
  • \$\begingroup\$ I have the same question but this didn't really answer it assuming you do have a good reason for having a class, so I asked a new question here: codereview.stackexchange.com/questions/55284/… I'm also not sure I agree that having a class is bad form here. A class has additional benefits - you can break up long methods for better organization and make the code more readable/understandable. Classes also have the benefit of being easier to test. \$\endgroup\$
    – Jared
    Commented Jun 25, 2014 at 22:53
  • \$\begingroup\$ @Jared You can use a class, sure, and sometimes you have to. The question is, do you actually have to? In this case, using a class is not the right way to go. Why make a class if you can't use the instances for anything? Basically, if you write new Foo() without ever storing the returned instance, or chaining off of it, you should not use a class. It's just a needlessly complex way of calling a function: The constructor. So instead, just call a function. \$\endgroup\$
    – Flambino
    Commented Jun 26, 2014 at 9:46
1
\$\begingroup\$

I think a class is fine to use here. I think it's more readable and easier to test.

Though I reserve my right to change my answer after I get some feedback on a related question, I like to initialize all instance variables and event handling in the constructor so it's easy to see at a glance what the variables and events are:

class PhotoInput
  constructor: (originalInput) ->  
    # Setup all instance variables
    @template = renderTemplate(originalInput)
    @fileSelect = @template.find('#select-file')
    @fileInputPreview = @template.find('.file-input-preview')
    @previewImage = @template.find('.file-input-preview img')
    @fileInput = @template.find('input[type=file]').first()

    # Setup events
    @fileSelect.click(@focusFileInput)
    @fileInputPreview.click(@focusFileInput)
    @fileInput.change(@setPreviewRender)

  focusFileInput: =>
    @fileInput.focus().click()

  # this is ICanHazJS call - it loads a mustache template there.
  renderTemplate: (originalInput) ->
    template = ich.image_file_input_template(fileInput: $(originalInput)[0].outerHTML)
    $(originalInput).replaceWith(@template)
    template

  setPreviewRender: =>
    @previewImage.attr('alt', @fileInput.val())
    if(@fileInput[0].files && @fileInput[0].files[0])
      reader = new FileReader()
      reader.onload = (e) => @previewImage.attr('src', e.target.result)
      reader.readAsDataURL(@fileInput[0].files[0])

$ ->
  $('input[type="file"]').each ->
    new PhotoInput(this)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.