1

Trying to take a foreach ordered list and shuffle the list items to be random. I created a function (randomOrder) and used it in the binding on the foreach item. Nothing seems to be working.

HTML:

<ol data-bind="foreach: { data: docs, randomOrder: true} ">
     <li class="result" data-bind="component: { name: 'physicianreferral.docresult', params: { doc: $data } }"></li>
</ol>

JS:

    ko.bindingHandlers.randomOrder = {
        init: function (elem, valueAccessor) {
            // Build an array of child elements
            var child = ko.virtualElements.firstChild(elem),
                childElems = [];
            while (child) {
                childElems.push(child);
                child = ko.virtualElements.nextSibling(child);
            }

            // Remove them all, then put them back in a random order
            ko.virtualElements.emptyNode(elem);
            while (childElems.length) {
                var randomIndex = Math.floor(Math.random() * childElems.length),
                    chosenChild = childElems.splice(randomIndex, 1);
                ko.virtualElements.prepend(elem, chosenChild[0]);
            }
        }
    };
    ko.virtualElements.allowedBindings.randomOrder = true;
Ryan Brackett
  • 1,022
  • 11
  • 13

2 Answers2

2

First your binding isn't part of foreach and so would have to be specified at the top level:

foreach: docs, randomOrder: true

But I suggest that you don't use this method at all. Simply randomize the array you pass to foreach:

foreach: randomize(docs)

js:

function randomize(arr) {
    var newArray = [];
    while (arr.length) {
        var randomIndex = Math.floor(Math.random() * arr.length);
        newArray.push(arr.splice(randomIndex, 1)[0]);
    }
    return newArray;
};
Michael Best
  • 16,623
  • 1
  • 37
  • 70
  • Michael, this is really helpful, sir. I think I implemented your idea correctly, but I'm getting this error for some reason: Error: Uncaught ReferenceError: Unable to process binding "foreach: function (){return randomize(docs) }" Message: randomize is not defined – Ryan Brackett Mar 17 '17 at 21:33
  • For `randomize` to be available in a binding, it either should be a global function (`window.randomize = ...`) or on the view model. – Michael Best Mar 18 '17 at 04:35
  • Makes sense! I'll give that a shot on Monday. Thanks again, Michael! – Ryan Brackett Mar 19 '17 at 03:21
  • How did it go?? – Michael Best Apr 04 '17 at 17:34
  • Unfortunately, I never could get the results to randomly display. We decided to go ahead and make use of another approach that was able to provide some very similar results, thankfully. I appreciate your help - I'm sure it would have worked if I was more familiar with Knockout.js – Ryan Brackett Apr 11 '17 at 16:26
0

Some problems: first you're trying to use a custom binding within the foreach binding. You can use multiple bindings in the same HTML element but they should be separated by a comma.

<ol data-bind="foreach: { data: docs }, randomOrder: true">

The custom binding 'init' method has the allBindingAccessor which provides a way to access the view model's properties that are being used in other bindings in the same element.

var array = allBindingsAccessor().foreach.data();

With the array available you can use any algorithm to shuffle it. I've added in the snippet the Fisher-Yates (aka Knuth) Shuffle, copied straight from this answer. Click on 'Run code snippet' to see it being shuffled each time.

ko.bindingHandlers.randomOrder = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var array = allBindingsAccessor().foreach.data();

        var currentIndex = array.length, temporaryValue, randomIndex;

        // while there remain elements to shuffle...
        while (0 !== currentIndex) {

            // Pick a remaining element...
            randomIndex = Math.floor(Math.random() * currentIndex);
            currentIndex -= 1;

            // And swap it with the current element.
            temporaryValue = array[currentIndex];
            array[currentIndex] = array[randomIndex];
            array[randomIndex] = temporaryValue;
        }

        allBindingsAccessor().foreach.data(array);
    }
};

var ViewModel = function() {
    var self = this; 

    self.docs = ko.observableArray([1, 2, 3, 4, 5, 6, 7, 8 ,9, 10]);
};

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

<ol data-bind="foreach: { data: docs }, randomOrder: true">
    <li data-bind="text: $data"></li>
</ol>
Community
  • 1
  • 1
Rafael Companhoni
  • 1,780
  • 1
  • 15
  • 31