Search code examples
knockout.jsbootstrap-4bootstrap-accordion

Bootstrap editable accordion name


I'm trying to build a Bootstrap accordion using Knockout foreach with editable header. When double clicking on the accordion name, the name should become an input box (probably not the best idea to do this) that should save the change on enter click or click outside that area (changing the focus). My problem is that when a user double clicks on the name to edit it, the panel expand and collapses and some find it difficult to edit.

<div class="col-lg-12 panel m-t-5 panel-group" id="mAccordion">
    <div class="panel panel-default" data-bind="foreach:{ data: selectedItemM, as: 'mod'}">
        <div class="panel-heading ui-sortable-handle" style="border-color:black; background-color:black; margin-bottom: 2px" data-bind="visible:!IsDeleted()">
            <i class="fas fa-trash-alt pull-right" style="color:red; cursor:pointer" data-bind="event:{'click':$root.deleteM}"></i>
            <a class="accordion-toggle accordion-inner" style="color:#fff; text-decoration:none;cursor:pointer;" data-bind="attr:{href:'#mPanelBody_'+Id}" data-toggle="collapse" data-parent="#modAccordion">
                <h4 class="panel-title" data-bind="attr:{id:'header_'+Id}, text: Name, event:{'dblclick':$root.editM}" style="color:white;cursor: pointer; width:30%"></h4>
                <input type="text" class="panel-title" data-bind="attr:{id:'input_'+Id}, textInput: Name,valueUpdate: 'afterkeydown', event:{keypress:$root.renameM}" style="color:white;display:none; background-color:black; width:30%;" />
            </a>
        </div>
        <div class="panel-collapse accordion-sub collapse" data-bind="attr:{id:'mPanelBody_'+Id}, visible:!IsDeleted()"></div>
    </div>
</div>

It is possible to make the name of the accordion just to be editable on double click (or simple click) without doing anything to the accordion (expand or collapse) and expand or collapse it just when clicking outside the name area, on the empty space from the header?


Solution

  • You can use the hasFocus binding to detect and set focus on the input. You can use event.stopPropagation in your click handlers to prevent the click event from bubbling up to the accorion's toggle click target. Here's an example that shows:

    • Clicking on the text (green border) in the header starts editing it.
    • Removing focus on the edit input puts back the header
    • Clicking outside the text in the heading (blue border) toggles the accordion.

    const expanded = ko.observable(false);
    
    const focus = ko.observable(false);
    const title = ko.observable("Heading");
    
    
    const startEdit = function(data, event) {
      focus(true);
      event.stopPropagation();
    };
    
    const toggle = function() {
      if (!focus()) {
        expanded(!expanded());
      }
    };
    
    ko.applyBindings({ expanded, toggle, focus, title, startEdit });
    h2 {
     border: 1px solid blue;
     padding: .5rem;
    }
    
    span {
     border: 1px solid green;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <h2 data-bind="click: toggle">
      <span data-bind="text: title, click: startEdit, visible: !focus()"></span>
      <input type="text" data-bind="textInput: title, visible: focus, hasFocus: focus, clickBubble: false">
    </h2>
    
    <div data-bind="visible: expanded">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </div>