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

/*jshint indent:2, curly:true, eqeqeq:true, immed:true, latedef:true,
newcap:true, noarg:true, regexp:true, undef:true, trailing:true */
/*global enyo:true, XT:true, XV:true, XM:true, _: true, Globalize:true */

(function () {

  /**
    @name XV.CommentTypePicker
    @class A picker control used for selecting from the menu of comment types
      in a comment box.<br />
    A component of {@link XV.CommentBoxItem}.
    @extends XV.PickerWidget
   */
  enyo.kind(/** @lends XV.CommentTypePicker# */{
    name: "XV.CommentTypePicker",
    kind: "XV.PickerWidget",
    collection: "XM.commentTypes",
    orderBy: [
      {attribute: 'order'},
      {attribute: 'name'}
    ]
  });

  /**
    @name XV.CommentBoxItem
    @class Represents an individual comment within an {@link XV.CommentBox}.
   */
  enyo.kind(/** @lends XV.CommentBoxItem# */{
    name: "XV.CommentBoxItem",
    classes: "xv-comment-box",
    published: {
      value: null
    },
    events: {
      onCommentBoxItemEdit: ""
    },
    handlers: {
      ontap: "openEditableArea",
      onValueChange: "controlValueChanged",
      onCommentBoxItemEdit: "commentBoxItemEdit"
    },
    components: [
      {name: "header", formatter: "formatHeader",
        classes: "xv-comment-box-label"},
      {attr: "text", name: "textBlock", formatter: "formatText", allowHtml: true,
        classes: "xv-comment-box-textblock"},
      // Editing widgets
      {kind: "XV.CommentTypePicker", name: "commentType",
        attr: "commentType", showing: false},
      {kind: "XV.TextArea", name: 'textArea', attr: "text",
        showing: false, showBorder: true}
    ],
    /**
      When a box gets opened, we want to close unless that box is us.
      This will get hit on the bubble up and the waterfall down, but
      the waterfall down is the only time it will do anything, if it
      was actually originated by someone else.
     */
    commentBoxItemEdit: function (inSender, inEvent) {
      if (this.id !== inEvent.originator.id) {
        this.hideEditableArea();
      }
    },
    /**
      Catch events from constituent widgets and update the model
    */
    controlValueChanged: function (inSender, inEvent) {
      var attr = inSender.getAttr(),
        value = inSender.getValue(),
        attributes = {},
        model = this.getValue();
      attributes[attr] = value;
      model.set(attributes);
      return true;
    },
    /**
      Formats the text that goes at the top of each comment
    */
    formatHeader: function (value, view, model) {
      var values = [
        Globalize.format(model.get('created'), 'd'),
        Globalize.format(model.get('created'), 't'),
        model.get('createdBy'),
        model.getValue('commentType.name')
      ];
      return values.join(' ');
    },
    /**
    @todo Document the formatText method.
    */
    formatText: function (value, view, model) {
      var text = value ? value.replace(/</g, "<").replace(/\r?\n/g, "<br>\n") : value;
      view.addRemoveClass("disabled", model.isReadOnly());
      return "\n<blockquote>" + text + "</pre></blockquote><hr>";
    },
    /**
      Transition from editable mode to read-only mode
    */
    hideEditableArea: function (inSender, inEvent) {
      var value = this.getValue(),
        text = this.formatText(value.get('text'), this, value);
      this.$.textBlock.setContent(text);
      this.$.textBlock.show();
      this.$.textArea.hide();
      this.$.commentType.hide();
    },
    /**
      When the user wants to make a new comment, we open it up for them
      and initialize it.
     */
    initializeNew: function () {
      if (this.value.getStatus() === XM.Model.READY_NEW) {
        this.setCommentTypeFilter();
        this.openEditableArea();
      }
    },
    /**
      Transition from read-only mode to editable mode
    */
    openEditableArea: function () {
      var that = this,
        model = this.getValue(),
        header = this.$.header,
        textInput = this.$.textArea.$.input,
        typeChanged = function () {
          var headerText = that.formatHeader(null, that, model);
          textInput.setDisabled(false);
          textInput.focus();
          header.setContent(headerText);
          model.off('change:commentType', typeChanged);
        };
      if (model.isReadOnly()) { return; }
      this.$.textBlock.hide();
      this.$.textArea.show();
      this.$.commentType.show();
      if (this.value.isReadOnly("commentType")) {
        this.$.commentType.setDisabled(true);
      }
      if (model.get('commentType')) {
        textInput.focus();
      } else {
        textInput.setDisabled(true);
        model.on('change:commentType', typeChanged);
      }
      this.doCommentBoxItemEdit();
    },
    /**
      Apply the filter that dictates which comment types
      are pertinent to this business object.
    */
    setCommentTypeFilter: function () {
      var value = this.getValue(),
        commentType = this.$.commentType;
      commentType.filter = function (models) {
        return _.filter(models, function (model) {
          var sourcesModels,
            sourcesAttrs,
            sources,
            attrs,
            sourceNames;
          sourcesModels = model.get('sources').models;
          sourcesAttrs = _.pluck(sourcesModels, 'attributes');
          sources = _.pluck(sourcesAttrs, 'source');
          attrs = _.pluck(sources, 'attributes');
          sourceNames = _.pluck(attrs, 'name');
          return _.find(sourceNames, function (name) {
            return name === value.sourceName;
          });
        });
      };
      commentType.buildList();
    },
    /**
      We disable this view by disabling all its subcomponents.
    */
    setDisabled: function (isDisabled) {
      var i,
        components = this.getComponents(),
        comp;

      for (i = 0; i < components.length; i++) {
        comp = components[i];
        if (comp.setDisabled) {
          comp.setDisabled(isDisabled);
        }
      }
    },
    /**
    @todo Document the valueChanged method.
    */
    valueChanged: function () {
      var i,
        model = this.getValue(),
        status = model ? model.getStatus() : null,
        K = XM.Model,
        controls = _.filter(this.$, function (obj) {
          return obj.attr || obj.formatter;
        }),
        control,
        label,
        attr,
        value;
      for (i = 0; i < controls.length; i++) {
        control = controls[i];
        attr = control.attr;
        label = ("_" + attr).loc();
        value = attr ? model.getValue(attr) : null;
        value = control.formatter ?
          this[control.formatter](value, control, model) : value;
        if (control.setValue) {
          control.setValue(value, {silent: true});
        } else {
          control.setContent(value);
        }
        if (model.isReadOnly() || !model.canUpdate()) {
          this.setDisabled(true);
        }
      }
    }
  });

  /**
    @name XV.CommentBox
    @class Provides a container in which its components are a vertically stacked group
    of horizontal rows.<br />
	Made up of a header (which contains the title),
	a scroller (a scrolling list of comment box items),
	and navigation buttons (such as New, Attach, Detach, Open).<br />
	Use to implement a comment box that contains and manages multiple comments,
	represented as {@link XV.CommentBoxItem}s.
	@extends XV.Groupbox
   */
  enyo.kind(/** @lends XV.CommentBox# */{
    name: "XV.CommentBox",
    kind: "XV.Groupbox",
    classes: "panel xv-comment-box",
    published: {
      attr: null,
      model: null,
      disabled: false,
      title: "_comments".loc()
    },
    handlers: {
      onCommentBoxItemEdit: "commentBoxItemEdit"
    },
    components: [
      {kind: "onyx.GroupboxHeader", name: "header"},
      {
        kind: "XV.Scroller",
        horizontal: 'hidden',
        fit: true,
        components: [{
          kind: "Repeater",
          name: "repeater",
          count: 0,
          onSetupItem: "setupItem",
          classes: "xv-comment-box-repeater",
          components: [
            {kind: "XV.CommentBoxItem", name: "repeaterItem" }
          ]
        }]
      },
      {kind: 'FittableColumns', classes: "xv-buttons", components: [
        {kind: "onyx.Button", name: "newButton", onclick: "newItem", classes: "icon-plus"}
      ]}
    ],
    /**
     Builds the comment box comprised of a header, scroller to contain comment box items, and navigation buttons.
     */
    create: function () {
      this.inherited(arguments);
      this.$.header.setContent(this.getTitle());
    },
    /**
      When one comment box item opens, we want to tell everyone so that
      the others can close.
     */
    commentBoxItemEdit: function (inSender, inEvent) {
      this.waterfallDown("onCommentBoxItemEdit", inEvent);
      return true;
    },
    /**
      We disable the new button if we're not allowed to create a comment, or if we've been told
      that the whole kind is to be disabled.
     */
    disabledChanged: function () {
      var canCreate = this._collection && this._collection.model.canCreate();
      this.$.newButton.setDisabled(!canCreate || this.getDisabled());
    },
    /**
      The "New" button has been clicked. Open a new comment by adding a new
      model to the collection.
     */
    newItem: function () {
      var Klass = XT.getObjectByName(this.getModel()),
        model = new Klass(null, { isNew: true });
      this._collection.add(model);
      this.$.repeater.setCount(this._collection.length);
    },
    /**
      Renders the CommentBoxItem repeater
     */
    setupItem: function (inSender, inEvent) {
      var row = inEvent.item.$.repeaterItem,
        model = this._collection.at(inEvent.index);
      row.setValue(model);
      if (inEvent.index === 0) {
        row.initializeNew();
      }
      return true;
    },
    /**
     @todo Document the setValue method.
     */
    setValue: function (value, options) {
      if (value) {
        value.comparator = this.sort;
        value.sort();
      }
      this._collection = value;
      this.$.repeater.setCount(this._collection.length);

      // these three lines are basically duplicated in disabledChanged and could be refactored
      if (!this._collection.model.canCreate()) {
        this.$.newItemButton.setDisabled(true);
      }
    },
    /**
      We sort comments by the date they were created, descending.
     */
    sort: function (a, b) {
      var aval = a.get('created'),
        bval = b.get('created');
      return XT.date.compare(bval, aval);
    }
  });
}());