1

Ok... so I'm using Twitter Bootstrap. I have an observable array which represents a set of groups. In the UI, this observableArray is being used to render a "tab" and "tab-pane" for each group. I can sort and display these groups by their name property, no probs, doddle right!

<ul data-bind="foreach: myArray().sort(function (l, r) { return l.name() > r.name() ? 1 : -1 })" class="nav nav-tabs" role="tablist">
    <li data-bind="text: name"></li>
</ul>

That's great... however, I have an 'All' object in that same array which needs to be at the beginning of the set of tabs being displayed.

Tabs currently looks like this... A | All | B | C | D

Need to look like this... All | A | B | C | D

Any ideas? :-/

2
  • 1
    A comes before All so your sorting is correct. If you want it to sort differently then you'll have to add a clause to handle All. But take the model logic out of your view and put it in a computed property, it'll be a lot cleaner. Commented Nov 19, 2014 at 13:53
  • Thanks for the feedback. I'm aware that 'A' comes before 'All' haha. Just wasn't sure how I'd add an additional conditional into the View but you have answered my question I think... I'll try and put in a computed property. Commented Nov 19, 2014 at 13:56

2 Answers 2

2

Tweak your sort function. In OO fashion, the "All" object would have a property that indicates that it should be ordered at the top. Alternatively, the quick and dirty way to go is to tweak your sort function to something like this:

function (l, r) {
    if (l.name === "All") return 1;
    if (r.name === "All") return -1;
    return l.name().localeCompare(r.name());
}

I think I got the +1/-1 logic right, but you've got unit tests to work out those details, right? ;)

As a side note, I'd use the localeCompare function to compare strings.


Seconding @MattBurland's comment, you should move the sorting logic to your View Model too (this would be required anyways to unit test it). In addition, note that sort will sort the array itself, and you can also call sort on the observable (without invoking it as a function to get the observable's value), to sort the contents of the observable.

Here's what that would look like:

function ViewModel(items) {
  var self = this;
  self.myArray = (items);
  self.myArray.sort(function (l, r) {
      if (l.name() === "All") return -1;
      if (r.name() === "All") return 1;
      return l.name().localeCompare(r.name());
  });
};

var vm = new ViewModel([
  { name: ko.observable("B") },
  { name: ko.observable("A") },
  { name: ko.observable("All") }
]);

ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<ul data-bind="foreach: myArray" class="nav nav-tabs" role="tablist">
    <li data-bind="text: name"></li>
</ul>

Or like this, if you use the OO approach:

function ViewModel(items) {
  var self = this;
  self.myArray = (items);
  self.myArray.sort(function (l, r) {
      if (!!l.isSpecialOption) return -1;
      if (!!r.isSpecialOption) return 1;
      return l.name().localeCompare(r.name());
  });
};

var vm = new ViewModel([
  { name: ko.observable("B") },
  { name: ko.observable("A") },
  { name: ko.observable("All"), isSpecialOption: true }
]);

ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<ul data-bind="foreach: myArray" class="nav nav-tabs" role="tablist">
    <li data-bind="text: name"></li>
</ul>

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the feedback, i'll get back to you ;-)
0

I'd handle all of this in the ViewModel, rather than doing it in the template. The ideal solution is that your allTab is already separate from your other tabs, but if not you might need to loop through the other tabs and find it.

vm.sortedTabs = ko.computed(function () {
    var allTab = ?;
    var otherTabs = ?; //I don't know enough about your VM to know where you get these

    return [allTab].concat(otherTabs.sort(sortFunction)); 
});

Then your template is just

<ul data-bind="foreach: sortedTabs" class="nav nav-tabs" role="tablist">
    <li data-bind="text: name"></li>
</ul>

But this sort of sorting logic is far easier to deal with in the VM than inline in the template.

And compared to Jeroen's answer, I think it's far clearer to handle the special tab or tabs THEN sort the rest, rather than trying to put logic for handling the special tabs into your sorting alogorithm.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.