0

I have this small project I'm working at, and it's my first time ever using KnockoutJS (and a long while since I used javascript).

Any javascript carousel works with foreach, until the array is updated. I have already tried using Glider, Slick and Owl plugins and they all end up doing the same thing:

  1. Document starts, foreach initiates, populates the carousel with cards fetched from URL. OK
  2. Using the <select>, I change the option which will return a different API URL based on it. OK
  3. The foreach is restarted, the new content is thrown in the data-bind, but the last carousel remains in HTML, working (?)
  4. The new content doesn't work well with the carousel, as the items show stacked on top of each other.

The fourth step actually happened long before I understood what happened with carousels and lifecycle from KO; I had to use a handleBinding to start the plugin function after the foreach was made. Problem is, when the foreach is updated, KO won't restart the whole view, just what's inside of it, so the handleBinding is ignored.

Also, I can't explain why, in the 3rd step, the last carousel keeps there.

Code:

Select

<div id="select-regiao" data-bind="with: localizacoes" class="form-group mb-0">
            <select id="selected-option" data-bind="options: planosPorLocalizacao, value: planosSelecionados" class="custom-select select-oi mr-2">
          </select>
        </div>

Carousel, foreach, card...

<div class="owl-carousel owl-theme" data-bind="foreach: planos, carousel">
        <!-- #region card do plano -->
        <div class="mt-4 m-2">
          <div class="card card-plano">
          [... card content... ]
          </div>
        </div>
        <!-- #endregion -->
      </div>

Before </body>

<script src="assets/js/jquery-3.3.1.slim.min.js" type="text/javascript"></script>
<script src="assets/js/popper.min.js" type="text/javascript"></script>
<script src="assets/js/bootstrap.min.js" type="text/javascript"></script>
<script src='assets/bower_components/knockout/dist/knockout.js' type='text/javascript'></script>
<script src="assets/js/ko-models.js" type="text/javascript"></script>
<script src="assets/js/owl-2-2.3.4/dist/owl.carousel.min.js"></script>

My ko-models.js:

$(document).ready(function() {
    apiUrl = "link";
    localizacoes = {
        planosPorLocalizacao: ["Todos", "Rio de Janeiro, RJ", "São Paulo, SP"],
        planosSelecionados: ["Todos"]
    };
    planos = [];
    var vm = null;

    var select = document.getElementById("selected-option");
    select.addEventListener("change", function() {
        if (select.value == "Rio de Janeiro, RJ") {
            apiUrl = "other link";
        } else if (select.value == "São Paulo, SP") {
            apiUrl = "other link";
        } else if (select.value == "Todos") {
            apiUrl = "link";
        }
        console.log(select.value);
        MostraPlanos();
    });

    function MostraPlanos() {
        fetch(apiUrl).then(function(next) {
            next.json().then(function(res) {
                // ko.cleanNode({planos: res});
                res.forEach(el => {
                    el.dependentePreco = el["dependente-preco"];
                    el.precoReal = el["preco"].split(",")[0];
                    el.precoCentavo = el["preco"].split(",")[1];
                });         
                planos = res;
                if(vm == null) {
                    vm = {
                        planos: ko.observable(planos),
                        localizacoes
                    };
                    ko.applyBindings(vm);
                }
                else{
                    vm.planos(planos);
                    vm.gliderCarousel();
                }
            });
        });
    }
    MostraPlanos();

    ko.bindingHandlers.carousel = {
        update: function() {
            $(".owl-carousel").owlCarousel({
                loop:true,
                margin:10,
                nav:true,
                responsive:{
                    0:{
                        items:1
                    },
                    600:{
                        items:3
                    },
                    1000:{
                        items:5
                    }
                }
            });
        }
    }
});
Major Sam
  • 547
  • 8
  • 16
  • Some observations: **1)** You only need to call `ko.applyBindings(vm)` once. After that you need the update the same instance of view model. [So I suggest converting using a constructor function as viewmodel and using the `new` keyword](https://stackoverflow.com/questions/9589419). So, it's easier for you to `fetch` and update within the view model. You can just do `this.planos(planos)`. **2)** `planos` should be an `observableArray()`. **3)** Adding `addEventListener` is a strict **no no**. You should either use a `subscribe` or `event` binding for `change`. – adiga Feb 03 '19 at 07:12
  • @adiga **1)** This completely misses the point, and anyway, `ko.applyBindings(vm)` is only called once. This is why I have an `if..else` in the main function. **2)** `planos` doesn't need to be an observable, only `vm.planos`. **3)** Why is it a "no no"? Where did you get this? – Major Sam Feb 04 '19 at 00:43
  • Sorry I missed the `if(vm == null)` bit. Regarding "planos", I meant converting it to `observableArray` like this: `vm = { planos: ko.observableArray(planos) }`. Regarding the select, use "change" [`event` binding](https://knockoutjs.com/documentation/event-binding.html) – adiga Feb 04 '19 at 18:24
  • An observable object is created when I do `vm = {planos: ko.observable(planos)}`, right inside the `if`. Otherwise it wouldn't work (and it does - what didn't was the carousel part solved in my answer). On the select, I tried searching for a "knockout" way to do it, but I thought it ended more complex than doing a simple `addEventListener`. Anyway, I just wanted to understand why is it a no, since it's not a bad practice... – Major Sam Feb 05 '19 at 00:32

1 Answers1

0

I found out the problem was the way the plugins used to track the carousel. Glider, for instance, was the last one I tried and spent most time at.

From the possible options in Glider.js:

addTrack

Type: Boolean

Default: true

Whether or not Glider.js should wrap it's children with a 'glider-track' .

NOTE: If false, Glider.js will assume that the 'glider-track' element has been added manually. All slides must be children of the track element.

Adding .glider-track to the outer div (parent of cards) and setting addTrack: false in JS solved the issue!

Major Sam
  • 547
  • 8
  • 16