cssknockout.jsfluid-layoutcss-tablesknockout-templating

Turn cell content to editable input box


When creating a fluid layout, where content can be dragged around and edited inside a table I ran into a problem.

After clicking on any of the <a></a> hyperlinks the cell content should be replaced by an editable input box.

This gets done, but the cell changes its size and wrecks the original layout.

The cell size should not change after click. It should be possible to achieve this by editing the CSS and adding Bootstrap classes.

var viewModel = function() {
  var self = this;
  self.gridItems = ko.observableArray(
    [{
      "rowItems": [{
        "name": "Item 1"
      }, {
        "name": "Item 2"
      }, {
        "name": "Item 3"
      }]
    }, {
      "rowItems": [{
        "name": "Item 4"
      }, {
        "name": "Item 5"
      }]
    }]
  );
  self.selectedRowItem = ko.observable();
};

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
  init: function(element, valueAccessor, allBindingsAccessor, context) {
    $(element).data("sortList", valueAccessor()); //attach meta-data
    $(element).sortable({
      update: function(event, ui) {
        var item = ui.item.data("sortItem");
        if (item) {
          //identify parents
          var originalParent = ui.item.data("parentList");
          var newParent = ui.item.parent().data("sortList");
          //figure out its new position
          var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
          if (position >= 0) {
            originalParent.remove(item);
            newParent.splice(position, 0, item);
          }

          ui.item.remove();
        }
      },
      connectWith: '.sortable-container'
    });
  }
};

//attach meta-data
ko.bindingHandlers.sortableItem = {
  init: function(element, valueAccessor) {
    var options = valueAccessor();
    $(element).data("sortItem", options.item);
    $(element).data("parentList", options.parentList);
  }
};

//control visibility, give element focus, and select the contents (in order)
ko.bindingHandlers.visibleAndSelect = {
  update: function(element, valueAccessor) {
    ko.bindingHandlers.visible.update(element, valueAccessor);
    if (valueAccessor()) {
      setTimeout(function() {
        $(element).focus().select();
      }, 0); //new RowItems are not in DOM yet
    }
  }
}

ko.applyBindings(new viewModel());

//$(".sortable").sortable({});
.sortable {
  list-style-type: none;
  margin: 0;
  padding: 0;
  width:100%;
}

.sortable li {
  margin: 0 3px 3px 3px;
  padding: 0.4em;
  padding-left: 1.5em;
  font-size: 1.4em;
  height: 18px;
  cursor: move;
}

.sortable li span {
  position: absolute;
  margin-left: -1.3em;
}

.sortable li.fixed {
  cursor: default;
  color: #959595;
  opacity: 0.5;
}

.sortable-grid {
  width: 100% !important;
}

.sortable-row {
  height: 100% !important;
  padding: 0 !important;
  margin: 0 !important;
  display: block !important;
}

.sortable-item {
  border: 1px solid black;
  margin: 0 !important;
}

.sortable-item > a {
  display: block;
  margin: 0 !important;
}

.sortable-item input {
  display: block;
  margin: 0 !important;
}

.sortable-container {
  margin: 0 !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://code.jquery.com/ui/1.12.0-beta.1/themes/smoothness/jquery-ui.css" rel="stylesheet" />
<script src="https://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" rel="stylesheet" />
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<ul class="sortable sortable-grid" data-bind="template: { name: 'gridTmpl', foreach: gridItems, templateOptions: { parentList: gridItems} }, sortableList: gridItems">
</ul>

<script id="gridTmpl" type="text/html">
  <li class="sortable-row">
    <table style="width:100%">
      <tbody>
        <tr class="sortable sortable-container" data-bind="template: { name: 'rowTmpl', foreach: rowItems, templateOptions: { parentList: rowItems} }, sortableList: rowItems">
        </tr>
      </tbody>
    </table>
  </li>
</script>

<script id="rowTmpl" type="text/html">
  <td class="sortable-item" data-bind="sortableItem: { item: $data, parentList: $data.parentList }">
    <a href="#" data-bind="text: name, click: $root.selectedRowItem, visible: $data !== $root.selectedRowItem()"></a>
    <input data-bind="value: name, visibleAndSelect: $data === $root.selectedRowItem()" />
  </td>
</script>


Solution

  • On your table, set table-layout to fixed. Another improvement would be to make the inputs take up the entire space of the cell.

    Here are the css changes to make:

    .sortable-item input {
      display: block;
      margin: 0 !important;
      width: 100%;  /* Added this property */
    }
    
    /* Added this rule */
    .sortable-row > table {
      table-layout: fixed;
    }