Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

component "afterrender" using binding events #2319

Merged
merged 8 commits into from
Nov 25, 2017

Conversation

mbest
Copy link
Member

@mbest mbest commented Nov 14, 2017

Adds descendantsComplete event that is used by components to notify of completion, waiting until descendant components are also complete.

This replaces #2303.

@mbest
Copy link
Member Author

mbest commented Nov 22, 2017

This supports a special callback method on components called koDescendantsComplete which is called once all descendant bindings (including async components) are complete.

@brianmhunt I hope you can take some time to look over these changes and provide feedback. Thanks.

@brianmhunt
Copy link
Member

Excellent 😀

@mbest mbest merged commit 34b4123 into master Nov 25, 2017
@mbest mbest deleted the 1533-component-afterrender-generic branch November 25, 2017 17:25
@siimnelis
Copy link

siimnelis commented Jan 27, 2018

This is excellent feature that I have been missing.
I have used it now in one of the projects we use knockout and it works great.

There is one issue I'm still having and maybe there is a solution for that also. In our project when we bind our component, everything in the viewmodels markup is wrapped inside a ko if statement.

<!-- ko if:isLoaded -->

business logic here
<span data-bind="text:example"></span>

<!-- /ko -->
<!-- ifnot:isLoaded -->
show loading mask
<!-- /ko -->

The viewmodels data is loaded async and when the data is received, the viewmodel is initialized in a callback and the isLoaded observable is marked true.

self.getViewModel(function(viewModel){

self.example=ko.observable(viewModel.exaple);

});

Now the viewModel has example obervable and it's safe to bind the markup.

The problem with the koDescendatsComplete binding event is that it fires the first time around and it doesn't know about the nested components inside if loaded(that's expeted).

<!-- ko if:isLoaded -->

business logic here
<span data-bind="text:example"></span>
<nested-async-component-here-which-loads-the-same-way>
<!-- /ko -->

What I'm trying to achieve is to mark the parent loaded only when all the nested children are loaded as well and then show the finished markup.

I tried to use the childrenComplete binding handler in this case but it doesn't work the way I hoped.

<!-- ko if:isLoaded -->
<div data-bind="childrenComplete:childrenComplete"> 
business logic here
<span data-bind="text:example"></span>
<nested-async-component-here-which-loads-the-same-way>
<!-- /ko -->

childrenComplete function runs before the nested-async-component-here-which-loads-the-same-way koDescendatsComplete binding event.

I also tried to subscribe ko.bindingEvent(div, "koDescendantsComplete",function(){}); It throws an error that it can't bind to that nodetype(this was expected as well, was just trying).

Would it be possible to achieve such a binding handler/event which notifies when a binding handlers such as if, nested components have completed. Maybe there already a solution to achieve similar results already today, that I'm just not aware right now.

Thank you is advance

@mbest
Copy link
Member Author

mbest commented Jan 28, 2018

@siimnelis Components are designed so that the viewmodel code and/or template can be loaded asynchronously. The component loader documentation describes how to do this. Would that work for you?

@siimnelis
Copy link

siimnelis commented Jan 28, 2018

The viewmodel code and template both are loaded asynchronously using requirejs as amd. I have looked into component loader documentation.
The problem comes from that the viewmodels data itself also is loaded asynchronously.

This example is simplification.

define(['knockout', 'text!templates/partialViews/example', 'partialViewPage'],
    function (ko, htmlString, partialViewPage) {
        function viewModel(params) {

            var self = partialViewPage("UrlToLoadDataFrom", params);

            //when component is loaded first time it initializes the async data loading from server
            self.getViewModel(function (viewModel) {
                self.example= ko.observable(viewModel.example);
            }, {Id:1}); //can pass query string arguments to server

            return self;
        }

        return {
            viewModel: {
                createViewModel: function (params, componentInfo) {
                    params.element = componentInfo.element;
                    return viewModel(params);
                }
            }, template: htmlString
        };
    });

The partialViewPage.js adds basic functionality to all asynchronously loaded viewModels.

The partialViewPage wraps components markup inside ko if statement.

params.element.innerHTML = "<!-- ko if:isLoaded() && !loadingFailed() -->\r<div data-bind='visible:ready, childrenComplete:childrenCompleteCallback'>" +params.element.innerHTML + "</div>\r<!-- /ko -->";

There is the method getViewModel and isLoaded observable and other functionality.

self.isLoaded = ko.observable(false);

self.getViewModel = function (callback, data) {
         
//loading data with jquery ajax call on success callback if everthing is okey callback will be invoked with the viewmodel loaded from server and isLoaded is marked true, which renders the actual html

            $.ajax(viewmodelUrl,
                {
                    dataType:'json',
                    data:data,
                    success:function(viewModel) {
                        aetService.resetSessionTimer();
                        
                       callback(viewModel);

                        self.isLoaded(true);

                    }
                });

}

I thought about maybe I could load data from server inside createViewModel function and maybe make a custom loader. But the createViewModel must return viewmodel instance synchronously.

viewModel: {
                createViewModel: function (params, componentInfo) {
                    params.element = componentInfo.element;
//cant load data async here and pass it to there viewModel instance as a parameter

                    return viewModel(params);
                }

I look more into maybe I can somehow still achieve this with custom loader but from the documentation I understand for now that the job of a component loader is to asynchronously supply a template/viewmodel pair for any given component name. Viewmodel being the javascript class, not the data itself.

I hoped I could write custom loading for loadViewModel(name, templateConfig, callback), but the documentation states it must return the viewModel synchronously and thats the problem.

I think if I could achieve something like this, would solve the problem the best way.

viewModel: {
                createViewModel: function (params, componentInfo, callback) {
                    params.element = componentInfo.element;
//load data async and when done invoke callback with the data
                callback(viewModel(viewModelAsData, params));
                }

I could use componentInfo.element to add the mask to the node etc.

@mbest
Copy link
Member Author

mbest commented Jan 29, 2018

This is an interesting approach. I think that rather then the if binding, which can be either true or false at any time, we need a binding that encapsulates the idea of async loading. I'm thinking to call it when, based on #2113.

@siimnelis
Copy link

It's a good idea to have a binding that encapsulates the idea of async loading.

From the #2113 I understand the idea of when is to only run it once. With async loading there is also a usecase for reloading the view. I can call

self.getViewModel(function(viewModel){}); //marks isLoading(false) and the loading mask shows up until loaded

any number of times, which reloads the view.

It's comes handy when a POST request changes server state and I don't have the information how it affected the UI state(some webservice requests).

Normally I update the UI from POST requests response.

@@ -255,9 +328,7 @@
bindingApplied = true;
}

if (bindingApplied) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like bindingApplied is no longer used and can be removed from this context.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

brianmhunt added a commit to knockout/tko that referenced this pull request May 14, 2018
Probably we’ll be ripping all this out since it duplicates what’s in TKO already in terms of async binding handlers (but implementation details aside, this should still pass the KO 3.5 tests)
@ShallomAkporuku
Copy link

I am new to KO. I need to know when a component has finished updating DOM as stated in #1475. The issue is stated to be resolved by the descendantsComplete event. There is currently no documentation on how to implement this. Please can I be provided with a JSFiddle example on how the descendantsComplete event works?

I currently get alot of flicker as each value gets rendered one by one on the webpage after applying bindings. I am looking for a way only render after all the values in the array has been computed.

brianmhunt added a commit to knockout/tko that referenced this pull request Jun 15, 2018
* yarn) Update the packages so rollup can be used in each package directory

* tko) Fix export of `when`

* knockout) Add knockout proper package, plus somewhat modified spec from 3.5

See packages/knockout/spec/README.md

* Fix `jasmine.Clock.useMockForTasks`

* Expand tests to all `spec/` subdirectories

Also allow package.json’s `karma.files` to overload the default (by e.g. making them watchable)

* Fix semicolon hoisting variables to global scope

* spec) Remove duplicate import

* Upgrade devDependencies to latest

* karma) Fix relative `__dirname` import

* rollup) Better re-use of the path replacement

* observableArray - fix `compareArrays` tests

* npm) Update to latest packages, add globals, use .es6 knockout for testing

If we use `dist/knockout.js` for testing the knockout package, then we have to recompile every single dependency.  If we attempt to link directly to the files `src/index.js` as we do for the .es6 variants then Typescript does not inject its tslib dependencies.

So we test off of `dist/knockout.es6.js`.  One still has to recompile knockout to re-test every change.

* expose `ko.selectExtensions`

* knockout/templating) Fix a variety of template-related tests

* Down to 76 failing tests; fixes for templating and expressionRewriting behaviors

* Fix tests with dependencyDetection, postJson, and $context.ko (62 fail)

See `testing.md` for details

* tko.utils.functionRewrite) Add a basic util for backwards compat with `funciton () {}` in binding strings

* Add Backers.md for Pateron support

* parser) Add basic function rewriter to the default parser

Adds option `bindingStringPreparsers`

* build) Respect a `—es6` option to `yarn build` to speed up compilation

* string) Remove legacy `stringifyJson`

* #56 ) Break out common elements of knockout/tko compilation to `tko.builder`

This may have caused a couple regressions in tests, but they should be easy to find.

* packages) Fix configs for tko.functionRewrite and tko.builder

* rollup) Fix renamed config option

* export `utils.cloneNodes`

* Support ES2015 object initializer short-hand `{name}`

* function-rewrite) Rewrite arguments passed to lambdas

Note: arguments are not yet respected by the parser/interpreter.

* knockout) Put the `options in the correct place

* dev) Ignore `.vscode`

* parser) Add tests for calling lambdas created by the parser

Some lambdas may be passed into bindings.

* Add monkeypatch to fix the broken test system by breaking it

Time consuming & frustrating to diagnose.

* knockout) Fix tests related to `task` `scheduler` and `onError`

* Expose `ko.computedContext` as alias of `ko.dependencyDetection`

* Fix test for .length

* Update npm dependencies to latest

* MultiProvider) Respect the antiquated mechanism for providing `preprocessNode`

There’s certainly code out there that performs a `ko.bindingProvider.instance.preprocessNode = …`

* ko 3.5) Add `childrenComplete`

Cross-link knockout/knockout#2310

* Add `expressionRewriting` to `ko` global for backwards compatibility

* Make `ko.getBindingHandler` an overloadable function

* Prevent infinite recursion with `MultiProvider.instance`

* ci) Fix test wrt tko.bind

* dom/data) Add `getOrSet` function for dom data

* Make the async/component binding completion more explicit

* Make sure the MultiProvider returns an overloaded instances’ new nodes

* Implement details from knockout/knockout#2319

Probably we’ll be ripping all this out since it duplicates what’s in TKO already in terms of async binding handlers (but implementation details aside, this should still pass the KO 3.5 tests)

* Foreach parity re. knockout/knockout#2324

* knockout/knockout#2380 - ko.contextFor after applyBindingAccessorsToNode

* knockout/knockout#2378 Add tests and comply with spec

* knockout/knockout#2379 Fix style binding / compare to prior value

* Fix `arrayFirst` returning `undefined` when found element is falsy

Fixes #63

* Fix `ko.when` for promises & callbacks

* Respect the `createChildContextWithAs` option in the `with` binding

* Respect `createChildContextWithAs` in the native `foreach` binding

* Fix export of extenders

* Fix html parsing regex

* tko.utils) Fix circular reference

* knockout/spec) Update to `master` in KO

* knockout/knockout#2341 Respect `cleanExternalData` overload

@mbest - what do you think of the `addCleaner` and `removeCleaner` API?  Or is that just overkill?

* Binding Provider) Better support for legacy `ko.bindingProvider.instance = …`

* template foreach) Fix the ko.bindingProvider being broke after if this test fails

* Add BindingResult class that exposes binding state

@mbest what do you think of this API?

Do you think `BindingResult` should include the node bound and binding context?  That would be useful for debugging, and open up other useful possibilities - but might be outside the clean, core API.

* apply bindings) Add `rootNode` and `bindingContext` to the BindingResult

* Fix missing comma

* optionsBehaviors) Modify the tests to support lack of `function` support

* applyBindings) call `descendantsComplete` as expected

* tko.bind) Add a special BindingHandler `DescendantBindingHandler`

This should be the superclass of every class that bindings descendants, e.g. `if`, `with`, `foreach`, `template`.

* tko.bind) Add async binding context

This patch should bring parity with #2364, subject to the individual bindings (to be a separate commit)

* tko.bind) Fix error w/ virtual element binding

* tko.bind) Make sure `extend` doesn’t call `valueAccessor(value)`

In tko, `valueAccessor` with a parameter sets the value of the underlying data/observable.  So we pass the value needed for extending an async context by binding the function’s `this`.

* Fix the builder failing to respect `ko.getBindingHandler` setter/getter

Also fix the upstream fix for a custom element tests misssing a `tick` and `template`.

* tko.bind/completion events) Use promises to verify that descendants completed binding

* backers) Update

* utils) Deprecate `proto` utilities, remove circular dependency

1. Fix the subscribable -> dependencyDetection -> … circular dependency
2. Always use `Object.setPrototypeOf`; this introduces a polyfill requirement/dependency for IE9.

* domNodeDisposal) Fix node cleaning test

Updated since merges from master.

* foreach) Fix `$ctx.$index` being overwritten on each update

It’s unusual & unexpected that `_contextExtensions` is being called multiple times; this may be a regression that needs investigation.

* tko.bind) Fix tests re. virtual element error

* tko.bind) Fix circular dependency for `BindingHandler` / `applyBindings`

* tko.bind) Reverting to `descendantsComplete` based on binding promises

@mbest - Note the failing tests and cross-linking #69 and #65.

* binding.template) Update double-unwrap test; disable “destroy” test

Note #69 re. foreachBehaviors.js:131

* util.parser) Skip argument exposure tests in lambdas

Noting for #65.
@Totati
Copy link

Totati commented Oct 26, 2018

I am new to KO. I need to know when a component has finished updating DOM as stated in #1475. The issue is stated to be resolved by the descendantsComplete event. There is currently no documentation on how to implement this. Please can I be provided with a JSFiddle example on how the descendantsComplete event works?

Hi, @ShallomAkporuku search for those in the repository. They have tests, so you can find out how to use them.

@phil-warner
Copy link

phil-warner commented Apr 26, 2022

Additional example of implementing koDescendantsComplete also shown in this test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants