0
votes

With knockout you can produce a row of bootstrap nav-tabs like this

<ul class="nav nav-tabs question-container" data-bind="foreach: Questions">
  <li role="presentation" data-bind="css: { active: $index() === 0 }">
    <a data-toggle="tab" 
       data-bind="attr: { href: '#q' + QuestionId() }">, text: Caption"></a>
  </li>
</ul>

With bootstrap-sortable you can make the tabs sortable simply by changing the binding. A limitation clearly documented is that you can't use the sortable binding with virtual elements because it depends on being defined on a single container element.

Suppose I wanted to add a tab "Add a new question" presenting various options for what to create, after the manner of the add-new tabs used in Chrome, Internet Explorer, Firefox and Microsoft Excel.

If I weren't trying to make the tabs sortable, I'd do this:

<ul class="nav nav-tabs question-container">
  <!-- ko foreach: Questions-->
  <li role="presentation" data-bind="css: { active: $index() === 0 }">
    <a data-toggle="tab" 
       data-bind="attr: { href: '#q' + QuestionId() }, text: Caption"></a>
  </li>
  <!-- /ko -->
  <li role="presentation" data-bind="css: { active: Questions().length === 0 }">
    <a data-toggle="tab" href="#addQuestion">
    <i class="fa fa-plus"></i>Add a new question</a>
  </li>
</ul>

By adding a marker class "sortable-item" to the <LI> in the template, we can trivially exclude the meta item by defining item: ".sortable-item" in the bootstrap-sortable options. But it still won't fly, because you can't use virtual elements with bootstrap-sortable.

Does anyone know how to add meta items to a collection managed by bootstrap-sortable?

There are four ways this problem can be approached.

  1. Don't use bootstrap-sortable, apply jQuery-UI sortable explicitly and ise sort update events to update the backing store collection.
  2. Use bootstrap and inject the meta items after binding completes.
  3. Modify bootstrap to support the notion of a meta-items template in addition to the binding template.
  4. Modify bootstrap to use the immediate parent of a virtual node. There is always a parent, even if it's BODY.

Personally I favour option four, because it doesn't change how bootstrap-sortable is applied, it simply removes a limitation. No breaking changes.

Option three is nearly as good but it increases the conceptual complexity and makes the learning curve steeper.

Option two sucks big rocks. It screws up separation of concerns.

Option one, which is what I actually did, sucks even bigger rocks. Not only does it pollute the view model with UI behaviour, it's complicated to do, depends on poorly documented and obscure knockout internal utilities and it's just plain ugly. Everything about it is a justification for the existence of bootstrap-sortable.


In answer to this comment:

The "add question" button is not a question, therefore it should not be part of the list of questions

It's not a list of questions, it's the UI for a set of operations that can be performed on a list of questions. Most of them are "display item X for editing" but one of them is "create a new item". Also present are controls for deleting items and for re-ordering them.

I find it hilariously ironic that anyone would claim this is not an obvious UI design while using a web browser - a ubiquitous example of this exact design

1
The "add question" button is not a question, therefore it should not be part of the list of questions. Once you no longer insist on cramming it into the same element that contains the questions your problem is solved. - Tomalak
I must agree with @Tomalak, it doesn't seem very obvious to add this button to the list. - Guilherme Rodrigues
Write to Google, Microsoft and Mozilla and tell them they've all got it wrong then. In the meantime perhaps we could focus on the question as asked? - Peter Wone
But why does it have to be part of the list? It's not because this special tab is visually similar; you could style it just the same and place it adjacent to the list. Those browsers you are talking about probably do it like that, eg check the source code for Chromium here. While _tabs is an array containing browser tabs, the "Add new tab" button is constructed w. a separate NewTabButton class and is outside the list - Tyblitz
@Tyblitz - The tab mechanism is a tidy way of managing limited UI space and is well understood by users, so it doesn't cause cognitive dissonance. In this case, each tab presents various things you can do to topics - create, edit, delete them. Having just one operation (create) totally outside this system does induce cognitive dissonance (I have run focus groups). Another tab is also tidier from an engineering standpoint. I've already got this working. I wish to use bootstrap-sortable so that I can do everything in the mark-up and restore separation of concerns. - Peter Wone

1 Answers

0
votes

It doesn't seem like there is currently a better story than the workaround I mention in the question. The ideal solution would be for me or someone else to write a knockout binding. Most of the code in bootstrap-sortable seems to have to do with templating the rather nifty table it generates, but I think the required lessons are there.