Announcement

Collapse
No announcement yet.

Editable/Updatable Fields in Lists

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Editable/Updatable Fields in Lists

    It would be super useful to be able to click on a field empty or data in a list and be able to update it inline. It was one of the most useful features of KarmaCRM (if you want to see how they did it).

    Really impressed with the approach in espo though - VERY powerful and cool.

  • #2
    I feel a need for this. Would be great

    Comment


    • #3
      I also think that this would be a great feature, Espo is incredibly powerful and cool.

      If this is not in the future plans, could you provide some guidelines for someone else to do a custom implementation ?

      Comment


      • #4
        We need this too.

        Comment


        • #5
          Unfortunately, not planned.

          Comment


          • #6
            Would it be possible to add an action button to each row/record in a list view ? and if so could you tell me how to go about it ?

            Comment


            • #7
              I managed to implement the existing inline edit functionality to fields in list display. Here are the steps and code for those interested:

              Step 1: Create a new field base view with generic inline edit functions extending the existing "views/fields/base" view:
              Code:
              Espo.define('custom:views/fields/inline-base', 'views/fields/base', function (Dep) {
              
                  return Dep.extend({
              
                      customInitInlineEdit: function () { // substitutes initInLineEdit() at base.js
                          var $targetEditEl = $('tr[data-id="'+this.model.id+'"] > td[data-name="'+this.name+'"] > a.inline-edit-link');
                          // if the edit link already exists, avoid duplicanting
                          if($targetEditEl.length != 0) {
                              return;
                          }            
                          var $targetCell = $('tr[data-id="'+this.model.id+'"] > td[data-name="'+this.name+'"]');
                          var $editLink = $('<a href="javascript:" class="pull-right inline-edit-link hidden"><span class="fas fa-pencil-alt fa-sm"></span></a>');
                          if ($targetCell.length == 0) {
                              // if the target cell has not been rendered, wait until it has been rendered to perform the function customInitInlineEdit
                              this.listenToOnce(this, 'after:render', this.customInitInlineEdit, this);
                              return;
                          }
                          $targetCell.prepend($editLink);
                          $editLink.on('click', function () {
                              this.customInlineEdit();
                          }.bind(this));
                          $targetCell.on('mouseenter', function (e) {
                              e.stopPropagation();
                              if (this.disabled || this.readOnly) {
                                  return;
                              }
                              $editLink.removeClass('hidden');
                          }.bind(this)).on('mouseleave', function (e) {
                              e.stopPropagation();
                              $editLink.addClass('hidden');
                          }.bind(this));
                      },
              
                      customInlineEdit: function () { // substitutes inlineEdit() at base.js
                          var self = this;
                          this.trigger('edit', this);
                          this.setMode('edit');
                          this.initialAttributes = this.model.getClonedAttributes();
                          this.once('after:render', function () {
                              this.customAddInlineEditLinks(); // add the edit links "Cancel and Update"
                          }, this);
                          this._isInlineEditMode = true;
                          this.reRender(true);
                          this.trigger('inline-edit-on');
                      },
              
                      customAddInlineEditLinks: function () { // substitutes addInlineEditLinks() at base.js
                          var $targetCell = $('tr[data-id="'+this.model.id+'"] > td[data-name="'+this.name+'"]');
                          var $saveLink = $('<a href="javascript:" class="pull-right inline-save-link">' + this.translate('Update') + '</a>');
                          var $cancelLink = $('<a href="javascript:" class="pull-right inline-cancel-link">' + this.translate('Cancel') + '</a>');
                          $targetCell.prepend($saveLink);
                          $targetCell.prepend($cancelLink);
                          $targetCell.find('.inline-edit-link').addClass('hidden');            
                          $saveLink.click(function () {
                              this.customInlineEditSave();
                          }.bind(this));            
                          $cancelLink.click(function () {
                              this.customInlineEditClose();
                          }.bind(this));
                      },
              
                      customInlineEditSave: function () {
                          var data = this.fetch();
                          var self = this;
                          var model = this.model;
                          var prev = this.initialAttributes;
                          model.set(data, {silent: true});
                          data = model.attributes;
                          var attrs = false;
                          for (var attr in data) {
                              if (_.isEqual(prev[attr], data[attr])) {
                                  continue;
                              }
                              (attrs || (attrs = {}))[attr] =    data[attr];
                          }
                          if (!attrs) {
                              this.customInlineEditClose();
                              return;
                          }
                          if (this.validate()) {
                              this.notify('Not valid', 'error');
                              model.set(prev, {silent: true});
                              return;
                          }
                          this.notify('Saving...');
                          model.save(attrs, {
                              success: function () {
                                  self.trigger('after:save');
                                  model.trigger('after:save');
                                  self.notify('Saved', 'success');
                                  // if a parent model was passed, refresh the parent model which will refresh the parent view as well
                                  if(self.options.parentModel) {
                                      self.options.parentModel.fetch();
                                  }
                              },
                              error: function () {
                                  self.notify('Error occured', 'error');
                                  model.set(prev, {silent: true});
                                  self.render()
                              },
                              patch: true
                          });
                          this.customInlineEditClose(true);
                      },
              
                      customInlineEditClose: function (dontReset) { // substitutes inlineEditClose @ base.js
                          this.trigger('inline-edit-off');
                          this._isInlineEditMode = false;
                          if (this.mode != 'edit') {
                              return;
                          }
                          this.setMode('list');            
                          if (!dontReset) {
                              this.model.set(this.initialAttributes);
                          }
                          this.reRender(true);
                          this.customInitInlineEdit();
                      }
              
                  });
              });
              Step 2: Extend the original field type view (eg: text) to include the list inline edit capability:
              Code:
              Espo.define('custom:views/fields/inline-text', 'custom:views/fields/inline-base', function (Dep) {
              
                  return Dep.extend({
              
                      init: function(){
                          Dep.prototype.init.call(this); // call the parent "init()" function
                          // if the current view mode is 'list' make the field editable after it has been rendered and when the model has been updated
                          if(this.mode==='list') {
                              this.listenToOnce(this, 'after:render', this.customInitInlineEdit(), this);       
                              this.listenTo(this.model, 'after:save', function () {
                                 this.customInitInlineEdit(); 
                              }, this);
                          }
                      },
              
              // FROM HERE ON JUST COPY THE CODE FROM THE ORIGINAL FIELD VIEW: "views/fields/text"
              
                      type: 'text',
                      listTemplate: 'fields/text/list',
                      detailTemplate: 'fields/text/detail',
                      editTemplate: 'fields/text/edit',
                      searchTemplate: 'fields/text/search',
                      seeMoreText: false,
                      rowsDefault: 10,
                      rowsMin: 2,
                      seeMoreDisabled: false,
                      cutHeight: 200,
                      searchTypeList: ['contains', 'startsWith', 'equals', 'endsWith', 'like', 'notContains', 'notLike', 'isEmpty', 'isNotEmpty'],
              ...
              Step 3: Specify the custom field view in entityDefs
              Code:
              {
                  "fields": {
                      "name": {
                          "type": "varchar",
                          "required": true,
                          "trim": true,
                          "audited": false,
                          "readOnly": true,
                          "tooltip": false
                      },
                      "description": {
                          "type": "text",
                          "view": "custom:views/fields/inline-text",
                          "audited": false,
                          "readOnly": false,
                          "isCustom": true
                      },
              ...
              I am not an experienced developer so it's likely that this is not an elegant solution but it's working for me, at least for text and varchar which I needed for our implementation. If anybody has any suggestions for improvement please share. Cheers

              Comment

              Working...
              X