With KnockoutJS, how can I scroll a component after rendering it in foreach?

I have pending updates .

I have two components.

The first is a list that is simply implemented as a div with foreach data binding:

<div class="list-people" data-bind="foreach: { data: people, afterRender: afterRenderPeople }">
    <!-- ko component: { name: "listitem-person", params: { person: $data } } --><!-- /ko -->
</div>

Second list item:

<div class="listitem-person">
    <span data-bind="text: Name"></span>
</div>

afterRendercalled for each item in foreach.

My afterRenderPersonfunction is quite simple:

public afterRenderPerson = (elements: any[], data: Person) => {
    let top = $(element[0]).offset().top;

    scrollTo(top);
};

The problem is that when called, the afterRenderPersonsubcomponent listitem-personis not yet displayed.

Which means that the array of elements passed in afterRenderPersonhas 4 nodes:

  • Node text containing \ni.e. new line.
  • Comment node containing <!-- ko component: { name: "listitem-person", params: { person: $data } } -->.
  • Comment node containing <!-- /ko -->.
  • Node text containing \ni.e. new line.

top, , , , .

+4
2

. , init ( tasks.schedule, ). , .

, afterRender. , , .

ko.options.deferUpdates = true;

ko.bindingHandlers.notify = {
  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
    // Make it asynchronous, to allow Knockout to render the child component
    ko.tasks.schedule(() => {
      const onMounted = valueAccessor().onMounted;
      const data = valueAccessor().data;
      const elements = [];

      // Collect the real DOM nodes (ones with a tagName)
      for(let child=ko.virtualElements.firstChild(element);
          child;
          child=ko.virtualElements.nextSibling(child)) {
        if (child.tagName) { elements.push(child); }
      }
      onMounted(elements, data);
    });
  }
};

ko.virtualElements.allowedBindings.notify = true;

function ParentVM(params) {
  this.people = params.people;
  this.afterRenderPeople = (elements, data) => {
    console.log("Elements:", elements.map(e => e.tagName));
    if (data === this.people[0]) {
      console.log("Scroll to", elements[0].outerHTML);
      //let top = $(element[0]).offset().top;

      //scrollTo(top);
    }
  };
}

ko.components.register('parent-component', {
  viewModel: ParentVM,
  template: {
    element: 'parent-template'
  }
});

function ChildVM(params) {
  this.Name = params.person;
}

ko.components.register('listitem-person', {
  viewModel: ChildVM,
  template: {
    element: 'child-template'
  }
});

vm = {
  names: ['One', 'Two', 'Three']
};

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

<template id="parent-template">
  <div class="list-people" data-bind="foreach: people">
    <!-- ko component: { name: "listitem-person", params: { person: $data } }, notify: {onMounted: $parent.afterRenderPeople, data: $data} -->
    <!-- /ko -->
  </div>
</template>

<template id="child-template">
  <div class="listitem-person">
    <span data-bind="text: Name"></span>
  </div>
</template>

<parent-component params="{ people: names }">
</parent-component>
Hide result
+2

, , foreach .

DOM, afterRender/afterAdd/beforeRemove/beforeMove/afterMove, .

. , .

, , , , ?

user3297291 scrollTo, .

, , - scrollTo ... , . - user3297291

, .

, HTML DOM. , DOM HTML, .

, , .

, HTML DOM . , , - .

while , "" , setTimeout.

setTimeout - , , , .

private _scrollToOffset = -100;
private _detectScrollToDelayInMS = 200;
private _detectScrollToCountMax = 40;
private _detectScrollToCount = 0;

private _detectScrollTo = (scrollToContainerSelector: string, scrollToChildSelector: string) => {
    //AJ: If we've tried too many times then give up.
    if (this._detectScrollToCount >= this._detectScrollToCountMax)
        return;

    setTimeout(() => {
        let foundElements = $(scrollToChildSelector);

        if (foundElements.length > 0) {
            //AJ: Scroll to it
            $(scrollToContainerSelector).animate({ scrollTop: foundElements.offset().top + this._scrollToOffset });

            //AJ: Give it a highlight
            foundElements.addClass("highlight");
        } else {
            //AJ: Try again
            this._detectScrollTo(scrollToContainerSelector, scrollToChildSelector);
        }
    }, this._detectScrollToDelayInMS);

    this._detectScrollToCount++;
};

, , , - , .

, , , "" , TKO, AKA Knockout 4.

" ".

, DOM?

brianmhunt 20

/tko (ko 4 ) .

, applyBindings Promise, , ( ).

API , .

+2

Source: https://habr.com/ru/post/1683357/


All Articles