Source: enyo-x/source/views/documents_box.js

/*jshint bitwise:false, indent:2, curly:true, eqeqeq:true, immed:true,
latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
trailing:true, white:true*/
/*global XV:true, XM:true, _:true, window: true, Backbone:true, enyo:true, XT:true, async:true */

(function () {

  /**
    @name XV.DocumentListRelations
    @class A control that displays a list of scrolling rows.<br />
    Use to provide the list of related data to the {@link XV.DocumentsBox}.<br />
    @extends XV.ListRelations
   */
  enyo.kind(
    /** @lends XV.DocumentListRelations# */{
    name: "XV.DocumentListRelations",
    kind: "XV.ListRelations",
    parentKey: "account",
    published: {
      // the cache of asynchrously-fetched documently-associated info models
      resolvedModels: {}
    },
    events: {
      onError: ""
    },
    components: [
      {kind: "XV.ListItem", classes: "header", components: [
        {kind: "XV.ListColumn", name: "column", components: [
          {kind: "XV.ListAttr", attr: "targetType", formatter: "formatType", classes: "header"},
          {kind: "FittableColumns", components: [
            {kind: "XV.ListAttr", attr: "target", formatter: "formatNumber", name: "numberAttr", classes: "bold"},
            {kind: "XV.ListAttr", attr: "purpose", fit: true, formatter: "formatPurpose",
              classes: "right"}
          ]},
          {kind: "XV.ListAttr", formatter: "formatDescription",
            ontap: "openWindow", placeholder: "_noDescription".loc()}
        ]}
      ]}
    ],
    orderBy: [
      {attribute: "id"}
    ],
    /**
     @todo Document getInfoModel method.
     */
    getInfoModel: function (model) {
      return this.getResolvedModels()[model.id];
    },
    /**
      @param {Integer} Index
      @param {Boolean} Return InfoModel (default = true)
    */
    getModel: function (index, infoModel) {
      var model = this.inherited(arguments);
      if (infoModel !== false) {
        return this.getInfoModel(model);
      }
      return model;
    },
    /**
      To be able to render this list with the appropriate detail we need to have
      a cache of all of the info models that the document assignment models point
      to. Load up that cache, and then rerender the list when we have everything
    */
    lengthChanged: function () {
      var that = this;
      this.inherited(arguments);
      async.map(this.readyModels(), function (model, callback) {
        that.resolveModel(model, function (resolvedModel) {
          that.resolvedModels[model.id] = resolvedModel;
          callback();
        });
      }, function (err, results) {
        that.refresh();
      });
    },
    resolveModel: function (model, callback) {
      if (_.isObject(model.get("target"))) {
        callback(model.get("target"));
        return;
      }


      var recordType = XT.documentAssociations[model.get("targetType")].model;
      var resolvedModel = new (XT.getObjectByName(recordType))();
      resolvedModel.fetch({
        id: model.get("target"),
        queryOnPrimaryKey: true,
        success: callback
      });
    },
    /**
      Show the natural ID of the info model
     */
    formatNumber: function (value, view, model) {
      var resolvedModel = this.resolvedModels[model.id];
      if (resolvedModel) {
        return resolvedModel.get(resolvedModel.nameAttribute) || resolvedModel.id;
      }
      return value;
    },
    /**
      Show the description field of the info model
     */
    formatDescription: function (value, view, model) {
      var infoModel = this.getInfoModel(model),
        attr, recordType, isUrl;

      if (!infoModel) {
        return;
      }

      attr = infoModel.descriptionKey;
      recordType = infoModel.recordType;
      isUrl = recordType === 'XM.Url' || recordType === 'XM.FileRelation';

      view.addRemoveClass("hyperlink", isUrl);
      return infoModel.get(attr);
    },
    /**
      Show the purpose of the document assignment
     */
    formatPurpose: function (value) {
      var purpose;
      switch (value)
      {
      case 'S':
        purpose = "_related".loc();
        break;
      case 'A':
        purpose = "_child".loc();
        break;
      case 'C':
        purpose = "_parent".loc();
        break;
      case 'D':
        purpose = "_duplicate".loc();
        break;
      }
      return purpose;
    },
    /**
      Show the record type of the info model
     */
    formatType: function (value, view, model) {
      return XT.documentAssociations[value] ? XT.documentAssociations[value].label : value;
    },
    /**
     @todo Document the openWindow method.
     */
    openWindow: function (inSender, inEvent) {
      var model = this.getModel(inEvent.index),
        recordType = model.recordType,
        path,
        error;

      if (recordType === "XM.Url") {
        path = model.getValue('path');
      } else if (recordType === 'XM.FileRelation') {
        path = XT.getOrganizationPath() + '/file?recordType=XM.File&id=' + model.id;
      }
      if (path) {
        if (path.search(/^file/i) > -1) {
          error = XT.Error.clone('xt1011');
          this.doError({error: error});
          return true;
        }
        window.open(path);
        return true;
      }
    },
    setupItem: function () {
      this.inherited(arguments);
      // Hack: You _will_ conform!
      this.$.column.applyStyle("width", "100%");
    }
  });

  /**
    @name XV.DocumentsBox
	@class Provides a container in which its components are a vertically stacked group
    of horizontal rows.<br />
    Made up of a header, scroller, and a row of navigation buttons.<br />
    Use to implement a popup window for the user to define document relationships,
    for example: the purpose (related to, parent of, child of, duplicate of) and
    the type (account, contact, file, incident, item) of object to which the document is related.
    @extends XV.ListRelationsBox
   */
  enyo.kind(/** @lends XV.DocumentsBox# */{
    name: "XV.DocumentsBox",
    kind: "XV.ListRelationsBox",
    classes: "xv-documents-box",
    title: "_documents".loc(),
    listRelations: "XV.DocumentListRelations",
    searchList: "dummy",
    /**
     Creates a pop-up window for user to define the document relationships.
     For example, a file related to an account or a photograph related to an
     inventory item.
     */
    create: function () {
      this.inherited(arguments);
      var popup = {
        kind: "onyx.Popup",
        name: "selectionPopup",
        centered: true,
        modal: true,
        floating: true,
        scrim: true,
        onHide: "popupHidden",
        components: [
          {kind: "FittableColumns", components: [
            {content: "_purpose".loc() + ":", classes: "xv-documents-picker-label"},
            {kind: "onyx.PickerDecorator", components: [
              {classes: "xv-documents-picker"},
              {kind: "onyx.Picker", name: "purposePicker", style: "z-index: 999;",
                onChange: "purposeSelected", components: [
                {content: "_relatedTo".loc(), value: "S", active: true},
                {content: "_parentOf".loc(), value: "A" },
                {content: "_childOf".loc(), value: "C" },
                {content: "_duplicateOf".loc(), value: "D" }
              ]}
            ]}
          ]},
          {kind: "FittableColumns", components: [
            {content: "_type".loc() + ":", classes: "xv-documents-picker-label"},
            {kind: "onyx.PickerDecorator", components: [
              {classes: "xv-documents-picker"},
              {kind: "onyx.Picker", name: "typePicker",
                onChange: "typeSelected", style: "z-index: 999;"}
            ]}
          ]},
          {tag: "br"},
          {kind: "onyx.Button", content: "_ok".loc(), ontap: "popupOk",
            classes: "onyx-blue xv-popup-button"}
        ]
      };
      this.createComponent(popup);
    },
    /**
     @todo Document the attachDocument method.
     */
    attachDocument: function () {
      var searchList = XV.getList(this._type.infoModel),
        purpose = this._purpose,
        sourceKey = this._type.sourceKey,
        targetKey = this._type.targetKey,
        collection = this.$.list.getValue(),
        inEvent,

        // Callback to handle selection...
        callback = function (selectedModel) {

          // Create a new document assignment record
          var Klass = XT.getObjectByName("XM.DocumentAssociation"),
            model = new Klass(null, {isNew: true}),
            options = {
              silent: true,
              status: XM.Model.READY_CLEAN
            };

          model.set('purpose', purpose, {silent: true});
          model.set('sourceType', sourceKey, {silent: true});
          model.set('targetType', targetKey, {silent: true});
          model.set('target', selectedModel, options);
          collection.add(model);
        };

      // Open a search screen
      inEvent = {
        list: searchList,
        callback: callback
      };
      this.doSearch(inEvent);
    },
    /**
     @todo Document the attachItem method.
     */
    attachItem: function () {
      if (!this._initPicker) { this._buildList(); }
      this._popupDone = false;
      this._mode = "attach";
      this.$.selectionPopup.show();
    },
    /**
     @todo Document the detachItem method.
     */
    detachItem: function () {
      var list = this.$.list,
        index = list.getFirstSelected(),
        model = list.getModel(index, false);
      model.destroy();
      list.lengthChanged();
    },
    /**
     @todo Document the newDocument method.
     */
    newDocument: function () {
      var purpose = this._purpose,
        infoModel = this._type.infoModel,
        sourceKey = this._type.sourceKey,
        targetKey = this._type.targetKey,
        workspace = XV.getWorkspace(infoModel),
        collection = this.$.list.getValue(),
        inEvent,

        // Callback when new model is successfully committed
        callback = function (model) {
          if (!model) { return; }

          // First load up the info model version
          // of the model just created
          var Klass = XT.getObjectByName(infoModel),
            attrs = {},
            info,
            options = {};
          attrs[Klass.prototype.idAttribute] = model.id;
          info = Klass.findOrCreate(attrs);
          options.success = function () {

            // Now create a document assignment model
            var Klass = XT.getObjectByName("XM.DocumentAssociation"),
              model = new Klass(null, {isNew: true});

            model.set('purpose', purpose, {silent: true});
            model.set('sourceType', sourceKey, {silent: true});
            model.set('targetType', targetKey, {silent: true});
            model.set('target', info, {silent: true});
            collection.add(model);
          };

          // Fetch the info model
          info.fetch(options);
        };
      inEvent = {
        originator: this,
        workspace: workspace,
        callback: callback,
        allowNew: false
      };
      this.doWorkspace(inEvent);
    },
    /**
     @todo Document the newItem method.
     */
    newItem: function () {
      if (!this._initPicker) { this._buildList(); }
      this._popupDone = false;
      this._mode = "new";
      this.$.selectionPopup.show();
    },
    /**
     @todo Document the popupHidden method.
     */
    popupHidden: function (inSender, inEvent) {
      if (!this._popupDone &&
          inEvent.originator.name === "selectionPopup") {
        inEvent.originator.show();
      }
    },
    /**
     @todo Document the popupOK method.
     */
    popupOk: function () {
      this._popupDone = true;
      this.$.selectionPopup.hide();
      if (this._mode === "attach") {
        this.attachDocument();
      } else if (this._mode === "new") {
        this.newDocument();
      }
    },
    /**
     @todo Document the purposeSelected method.
     */
    purposeSelected: function (inSender, inEvent) {
      this._purpose = inEvent.selected.value;
    },
    /**
     @todo Document the selectionChanged method.
     */
    selectionChanged: function (inSender, inEvent) {
      var index = this.$.list.getFirstSelected(),
        model = index ? this.$.list.getModel(index) : null,
        couldNotRead = model ? !model.couldRead() : true;
      this.$.detachButton.setDisabled(index === undefined || this.getDisabled());
      this.$.openButton.setDisabled(couldNotRead || this.getDisabled());
    },
    /**
     @todo Document the typeSelected method.
     */
    typeSelected: function (inSender, inEvent) {
      this._type = inEvent.selected.value;
    },
    /**
     @todo Document the valueChanged method.
     */
    valueChanged: function () {
      this.inherited(arguments);
      this.$.newButton.setDisabled(this.getDisabled());
      this.$.attachButton.setDisabled(this.getDisabled());
    },
    /** private */
    _buildList: function () {
      var that = this,
        infoModel,
        hashes = [],
        InfoModel,
        parentRecordType = this.parent.parent.value.recordType,
        sourceKey,
        EditableModel;

      // figure out what the key is for the workspace that's hosting this doc box
      _.each(XT.documentAssociations, function (docAssoc, key) {
        if (docAssoc.model.indexOf(parentRecordType) === 0) {
          sourceKey = key;
        }
      });

      _.each(XT.documentAssociations, function (docAssoc, key) {
        infoModel = docAssoc.model;
        InfoModel = XT.getObjectByName(infoModel);
        EditableModel = XT.getObjectByName(InfoModel.prototype.editableModel);

        if (EditableModel.canCreate()) {
          hashes.push({
            content: docAssoc.label,
            value: {
              sourceKey: sourceKey,
              targetKey: key,
              infoModel: infoModel
            }
          });
        }
      });

      // Sort by the content
      hashes = _.sortBy(hashes, function (hash) {
        return hash.content;
      });

      // Default first one
      hashes[0].active = true;

      // Create the components
      _.each(hashes, function (hash) {
        that.$.typePicker.createComponent(hash);
      });

      // Don't come back here
      this._initPicker = true;
    }
  });

}());