dhoss/Kodiak

View on GitHub
app/assets/javascripts/image-picker.js.coffee

Summary

Maintainability
Test Coverage
#
# Image Picker source
# by Rodrigo Vera
# original limit-functionality added by Jason M. Batchelor
#
jQuery.fn.extend({
  imagepicker: (opts = {}) ->
    this.each () ->
      select = jQuery(this)
      select.data("picker").destroy() if select.data("picker")
      select.data "picker", new ImagePicker(this, sanitized_options(opts))
      opts.initialized.call(select.data("picker")) if opts.initialized?
})

sanitized_options = (opts) ->
  default_options = {
    hide_select:      true,
    show_label:       false,
    initialized:      undefined,
    changed:          undefined,
    clicked:          undefined,
    selected:         undefined,
    limit:            undefined,
    limit_reached:    undefined,
  }
  jQuery.extend(default_options, opts)

both_array_are_equal = (a,b) ->
  jQuery(a).not(b).length == 0 && jQuery(b).not(a).length == 0

class ImagePicker
  constructor: (select_element, @opts={}) ->
    @select         = jQuery(select_element)
    @multiple       = @select.attr("multiple") == "multiple"
    @opts.limit     = parseInt(@select.data("limit")) if @select.data("limit")?
    @build_and_append_picker()

  destroy: ->
    for option in @picker_options
      option.destroy()
    @picker.remove()
    @select.unbind("change")
    @select.removeData "picker"
    @select.show()

  build_and_append_picker: () ->
    @select.hide() if @opts.hide_select
    @select.change =>
      @sync_picker_with_select()
    @picker.remove() if @picker?
    @create_picker()
    @select.after(@picker)
    @sync_picker_with_select()

  sync_picker_with_select: () =>
    for option in @picker_options
      if option.is_selected()
        option.mark_as_selected()
      else
        option.unmark_as_selected()

  create_picker: () ->
    @picker         =  jQuery("<ul class='thumbnails image_picker_selector'></ul>")
    @picker_options = []
    @recursively_parse_option_groups(@select, @picker)
    @picker

  recursively_parse_option_groups: (scoped_dom, target_container) ->
    for option_group in scoped_dom.children("optgroup")
      option_group = jQuery(option_group)
      container    = jQuery("<ul></ul>")
      container.append jQuery("<li class='group_title'>#{option_group.attr("label")}</li>")
      target_container.append jQuery("<li>").append(container)
      @recursively_parse_option_groups(option_group, container)
    for option in (new ImagePickerOption(option, this, @opts) for option in scoped_dom.children("option"))
      @picker_options.push option
      continue if !option.has_image()
      target_container.append option.node


  has_implicit_blanks: () ->
    (option for option in @picker_options when (option.is_blank() && !option.has_image())).length > 0

  selected_values: () ->
    if @multiple
      @select.val() || []
    else
      [@select.val()]

  toggle: (imagepicker_option) ->
    old_values = @selected_values()
    selected_value = imagepicker_option.value().toString()
    if @multiple
      if selected_value in @selected_values()
        new_values = @selected_values()
        new_values.splice( jQuery.inArray(selected_value, old_values), 1)
        @select.val []
        @select.val new_values
      else
        if @opts.limit? && @selected_values().length >= @opts.limit
          if @opts.limit_reached?
            @opts.limit_reached.call(@select)
        else
          @select.val @selected_values().concat selected_value
    else
      if @has_implicit_blanks() && imagepicker_option.is_selected()
        @select.val("")
      else
        @select.val(selected_value)
    unless both_array_are_equal(old_values, @selected_values())
      @select.change()
      @opts.changed.call(@select, old_values, @selected_values()) if @opts.changed?


class ImagePickerOption
  constructor: (option_element, @picker, @opts={}) ->
    @option = jQuery(option_element)
    @create_node()

  destroy: ->
    @node.find(".thumbnail").unbind()

  has_image: () ->
    @option.data("img-src")?

  is_blank: () ->
    !(@value()? && @value() != "")

  is_selected: () ->
    select_value = @picker.select.val()
    if @picker.multiple
      jQuery.inArray(@value(), select_value) >= 0
    else
      @value() == select_value

  mark_as_selected: () ->
    @node.find(".thumbnail").addClass("selected")

  unmark_as_selected: () ->
    @node.find(".thumbnail").removeClass("selected")

  value: () ->
    @option.val()

  label: () ->
    if @option.data("img-label")
      @option.data("img-label")
    else
      @option.text()

  clicked: () =>
    @picker.toggle(this)
    @opts.clicked.call(@picker.select, this)  if @opts.clicked?
    @opts.selected.call(@picker.select, this) if @opts.selected? and @is_selected()

  create_node: () ->
    @node = jQuery("<li/>")
    image = jQuery("<img class='image_picker_image'/>")
    image.attr("src", @option.data("img-src"))
    thumbnail = jQuery("<div class='thumbnail'>")
    thumbnail.click {option: this}, (event) ->
      event.data.option.clicked()
    thumbnail.append(image)
    thumbnail.append(jQuery("<p/>").html(@label())) if @opts.show_label
    @node.append( thumbnail )
    @node