capejs/capejs

View on GitHub
lib/cape/resource_agent.js

Summary

Maintainability
B
5 hrs
Test Coverage
'use strict'

let Inflector = require('inflected')
let Cape = require('./utilities')
let checkStatus = require('./mixins/check_status')

// Cape.ResourceAgent
//
// public properties:
//   resourceName: the name of resource
//   id: the id of the resource
//   client: the object that utilizes this agent
//   basePath: the string that is added to the request path. Default value is '/'.
//   nestedIn: the string that is inserted between path prefix and the resource
//     name. Default value is ''.
//   shallow: a boolean value that controls whether the agent should omit
//     the `nestedIn` string from the member path. Default is `false`.
//   adapter: the name of adapter (e.g., 'rails'). Default is undefined.
//     Default value can be changed by setting Cape.defaultAgentAdapter property.
//   autoRefresh: a boolean value that controls unsafe Ajax requests trigger
//     this.refresh(). Default is `false`.
//   dataType: the type of data that you're expecting from the server.
//     The value must be 'json', text' or undefined.
//     When the `dataType` option is not defined, the type is detected automatically.
//   singular: a boolean value that specifies if the resource is singular or not.
//     Resources are called 'singular' when they have a URL without ID.
//     Default is `false`.
//   formName: the name of form with which the users edit the properties
//     of the resource. Default is `undefiend`.
//     When the `formName` option is not defined, the name is derived from the
//     `resourceName` property, e.g. `user` if the resource name is `user`.
//   paramName: the name of parameter to be used when request parameter is
//     constructed. Default is `undefiend`.
//     When the `paramName` option is not defined, the name is derived from the
//     `resourceName` property, e.g. `user` if the resource name is `user`.
//   object: the object that represents the resource.
//   data: the response data from the server. This property holds an object
//     if the response data is a valid JSON string. Otherwise, it holds the
//     original string value.
//   errors: the object that holds error messages.
//   headers: the HTTP headers for Ajax requests.
// private properties:
//   _: the object that holds internal methods and properties of this class.
//
// parameters for the constructor
//   client: the component object that use this agent.
//   options: an object that is used to initialize properties. The properties
//     which can be initialized by it are `resourceName`, `basePath`,
//     `nestedIn`, `adapter`, `dataType`, and `singular`.
class ResourceAgent {
  constructor(client, options) {
    let adapterName, adapter

    options = options || {}

    this._ = new _Internal(this)
    this.resourceName = options.resourceName
    this.client = client
    this.id = options.id
    this.basePath = options.basePath
    this.nestedIn = options.nestedIn
    this.shallow = options.shallow || false
    this.adapter = options.adapter
    this.autoRefresh = options.autoRefresh || false
    this.dataType = options.dataType
    this.singular = options.singular || false
    this.formName = options.formName
    this.paramName = options.paramName

    this.object = undefined
    this.data = undefined
    this.errors = {}
    this.headers = { 'Content-Type': 'application/json' }
  }

  init(afterInitialize, errorHandler) {
    let self = this, path

    if (this.singular) {
      path = this.singularPath()
    }
    else if (this.id) {
      path = this.memberPath()
    }
    else {
      path = this.newPath()
    }
    errorHandler = errorHandler || this.defaultErrorHandler

    this._.applyAdapter()

    fetch(path, {
      headers: this._.headers(),
      credentials: 'same-origin'
    })
    .then(checkStatus)
    .then(this._.responseHandler())
    .then(function(data) { self._.initialDataHandler(data, afterInitialize); })
    .catch(errorHandler)
  }

  refresh() {
    let self = this
    this.show(function(data) {
      self.data = data
      self.afterRefresh()
    })
  }

  // Called by the `refresh()` method after it updates the `data` property.
  //
  // Developers may override this method to let the agent do some
  // post-processing jobs.
  afterRefresh() {
    this.client.refresh()
  }

  show(callback, errorHandler) {
    this.ajax('GET', this.requestPath(), {}, callback, errorHandler)
  }

  create(afterCreate, errorHandler) {
    let path = this.singular ? this.singularPath() : this.collectionPath()
    return this._create_or_update('POST', path, afterCreate, errorHandler)
  }

  update(afterUpdate, errorHandler) {
    let path = this.singular ? this.singularPath() : this.memberPath()
    return this._create_or_update('PATCH', path, afterUpdate, errorHandler)
  }

  _create_or_update(verb, path, callback, errorHandler) {
    let params = this.client.paramsFor(
      this.formName || this.resourceName,
      { as: this.paramName || this.resourceName }
    )
    this.ajax(verb, path, params, callback, errorHandler)
    return false
  }

  destroy(afterDestroy, errorHandler) {
    let path = this.singular ? this.singularPath() : this.memberPath()
    this.ajax('DELETE', path, {}, afterDestroy, errorHandler)
    return false
  }

  get(actionName, params, callback, errorHandler) {
    this._.http_request('GET', actionName, params, callback, errorHandler)
  }

  head(actionName, params, callback, errorHandler) {
    this._.http_request('HEAD', actionName, params, callback, errorHandler)
  }

  post(actionName, params, callback, errorHandler) {
    this._.http_request('POST', actionName, params, callback, errorHandler)
  }

  patch(actionName, params, callback, errorHandler) {
    this._.http_request('PATCH', actionName, params, callback, errorHandler)
  }

  put(actionName, params, callback, errorHandler) {
    this._.http_request('PUT', actionName, params, callback, errorHandler)
  }

  delete(actionName, params, callback, errorHandler) {
    this._.http_request('DELETE', actionName, params, callback, errorHandler)
  }

  requestPath() {
    if (this.singular) {
      return this.singularPath()
    }
    else if (this.id === undefined) {
      return this.collectionPath()
    }
    else {
      return this.memberPath()
    }
  }

  collectionPath() {
    let resources = Inflector.pluralize(Inflector.underscore(this.resourceName))
    return this._.pathPrefix() + resources
  }

  newPath() {
    let resources = Inflector.pluralize(Inflector.underscore(this.resourceName))
    return this._.pathPrefix() + resources + '/new'
  }

  memberPath() {
    let resources = Inflector.pluralize(Inflector.underscore(this.resourceName))
    return this._.pathPrefix(this.shallow) + resources + '/' + this.id
  }

  singularPath() {
    let resource = Inflector.underscore(this.resourceName)
    return this._.pathPrefix() + resource
  }

  defaultErrorHandler(ex) {
    console.log(ex)
  }
}

Object.assign(ResourceAgent.prototype, require('./mixins/agent_common_methods'))

// Internal class of Cape.ResourceAgent
class _Internal {
  constructor(main) {
    this.main = main
  }

  http_request(verb, actionName, params, callback, errorHandler) {
    let path = this.main.requestPath()
    if (actionName !== '') path = path + '/' + actionName
    this.main.ajax(verb, path, params, callback, errorHandler)
  }

  initialDataHandler(data, afterInitialize) {
    this.parseData(data)
    this.postProcess(afterInitialize)
  }

  parseData(data) {
    let paramName = this.main.paramName || this.main.resourceName

    try {
      this.main.data = JSON.parse(data)
      this.main.object = this.main.data[paramName] || {}
    }
    catch (e) {
      console.log("Could not parse the response data as JSON.")
      this.main.data = data
    }
  }

  postProcess(afterInitialize) {
    let formName = this.main.formName || this.main.resourceName

    if (typeof afterInitialize === 'function') {
      afterInitialize.call(this.main.client, this.main)
    }
    else if (this.main.object) {
      this.main.client.setValues(formName, this.main.object)
      this.main.client.refresh()
    }
  }
}

Object.assign(_Internal.prototype, require('./mixins/agent_common_inner_methods'))

module.exports = ResourceAgent