mfpiccolo/kindred

View on GitHub
app/assets/javascripts/models/base.coffee

Summary

Maintainability
Test Coverage
class App.Base extends App.VirtualClass App.ActivePage, App.Setup

  @set_template: (template) ->
    @template = template

  # This method is a template of how a save all would work.   If your implementation
  # is basic then this logic might pass but most apps will have complex batch
  # persistence logic.  In that case you should overwrite this method in your model
  # or create a javascript service that handles batch persistence
  @save_all: (opts) ->
    data = {}

    # TODO fix the naive inflection
    collection = @collection_from_page(@snake_name)
    added_attrs = []

    $.each collection, (i, attrs) ->
      added_attrs.push($.extend attrs, opts.add_data_to_each)

    data[@snake_name + "s"] = added_attrs

    url_match = new RegExp(/{{([^{}]+)}}/g)
    @route = @route.replace(url_match, (match, p1) =>
      attribute = match.slice(2, - 2)
      opts.add_data_to_each[attribute]
    )

    path = @route + "/save_all.json"
    method = 'PUT'

    $.ajax
      type: method
      url: App.BaseUrl + "/" + path
      data: data

      success: (data, textStatus, xhr) =>
        @after_save_all(data, textStatus, xhr)
      error: (xhr) =>
        @after_save_all_error(xhr)

  # Example:
  # li = new App.LineItem()
  # li.set("foo", "bar")
  # li.attributes # => {"foo": "bar"}
  set: (attr_name, val) ->
    @attributes[attr_name] = val

  # Example:
  # li = new App.LineItem({foo: "bar"})
  # li.get("foo") # => "bar"
  get: (attr_name) ->
    @attributes[attr_name]

  # Example:
  # li = new App.LineItem({foo: "bar"})
  # li.remove("foo")
  # li.attributes # => {}
  remove: (attr_name) ->
    delete @attributes[attr_name]

  # Example:
  # li = new App.LineItem()
  # li.set_meta("foo", "bar")
  # li.meta # => {foo: "bar"}
  set_meta: (attr_name, val) ->
    @meta[attr_name] = val

  # Example:
  # li = new App.LineItem()
  # li.set_meta("foo", "bar")
  # li.remove_meta("foo")
  # li.meta # => {}
  remove_meta: (attr_name) ->
    delete @meta[attr_name]

  # POST or PUT will be used for the ajax request depending on the value of id.
  # TODO: meta data is not submitted with this request
  save: ->
    if !isNaN(parseFloat(@id)) && isFinite(@id)
      path = @route + "/" + @id + ".json"
      method = 'PUT'
    else
      path = @route + ".json"
      method = "POST"

    params = {}
    params[@snake_name] = @attributes

    response = $.ajax
      type: method
      url: App.BaseUrl + "/" + path
      dataType: "json"
      data: params
      global: false
      async: false
      success: (data, textStatus, xhr) =>
        @after_save(data, textStatus, xhr)
      error: (xhr) =>
        @after_save_error(xhr)

  # This method will send an ajax request if id is a number.  Other wise it will
  # just call the after destroy callback.
  destroy: ->
    @route ||= @snake_name + "s"
    path = @route + "/" + @id + ".json"
    method = "DELETE"

    if !isNaN(parseFloat(@id)) && isFinite(@id)
      $.ajax
        type: method
        url: App.BaseUrl + "/" + path
        dataType: "json"
        global: false
        async: false
        success: (data, textStatus, xhr) =>
          @after_destroy(data, textStatus, xhr)
        error: (xhr) =>
          @after_destroy_error(xhr)
    else
      @after_destroy()

  # accepts an object and assigns the key value pairs to the attributes hash
  assign_attributes: (attrs) ->
    $.each attrs, (attr, val) =>
      if attr == "id" && !isNaN(parseFloat(val)) && isFinite(val)
        @id = val
      @set(attr, val)

  #overridable hook
  after_save: (data, textStatus, xhr) ->
    @assign_attributes(data)
    @_clear_errors()
    @_update_data_vals_on_page()
    @update_vals_on_page()
    @_setup_interpolated_vars()
    @update_displays_on_page()

  #overridable hook
  after_save_error: (xhr) ->
    errors = JSON.parse(xhr.responseText)
    @_handle_errors(errors)

  #overridable hook
  after_destroy: (data, textStatus, xhr) ->
    @remove_errors_from_page()

  #overridable hook
  after_destroy_error: (xhr) ->

  #overridable hook
  @after_save_all: (data, textStatus, xhr) ->
    $(data).each (i, response_object) =>
      attrs = response_object[@snake_name]
      model = new App[@class_name]({uuid: attrs["uuid"]})
      model.assign_attributes(attrs)
      model._clear_errors()
      model._update_data_vals_on_page()

  #overridable hook
  @after_save_all_error: (xhr) ->
    data = JSON.parse(xhr.responseText)
    $(data).each (i, response_object) =>
      unless $.isEmptyObject(response_object["errors"])
        uuid = response_object[@snake_name].uuid
        model = new App[@class_name](response_object[@snake_name])
        model.assign_attributes_from_page()
        model._handle_errors(response_object["errors"])

  # Finds the attributes from a json response that do not have attached errors and
  # will hide the errors that have been resolved.
  # Then this method will iterate over the error responses and add the proper messaging
  # and show the error tags
  _handle_errors: (errors_obj, uuid) ->
    hideable_error_inputs = $(Object.keys(@attributes)).not(Object.keys(errors_obj)).get()
    $.each hideable_error_inputs, (i, attr) =>
      $("[data-error][data-attr='" + attr + "'][data-k-uuid='" + @uuid + "']").hide()

    $.each errors_obj, (attr, messages) =>
      error_tag = $("[data-error][data-attr='" + attr + "'][data-k-uuid='" + @uuid + "']")
      error_tag.html("")

      $.each messages, (i, message) ->
        error_tag.append("<span>" + message + "</span><br>")

      error_tag.show()

  # hides all the errors
  _clear_errors: () ->
    $.each @attributes, (attr, val) =>
      $("[data-error][data-attr='" + attr + "'][data-k-uuid='" + @uuid + "']").hide()