How can I use the new JS component viewer as a callback?

According to the knockout documentation, here the viewModel component is created only upon request "on demand", it is considered that it is declared as:

<div data-bind='component: { name: componentNameObservable, params: { mode: "detailed-list", items: productsList } }'></div> 

Consider a one-page application script with a routing mechanism such as SammyJS, Crossroads.js, or any other.

When a route change request matches a route pattern, it is usually required that the routing library handler for an event mapped to a route set a new value for the NameObservable component. This will cause the injection of a new component into the related element. In addition, also inside this routed handler, let's say I want to execute the function declared inside the associated viewModel component, update / configure model model data, responding to an event with a changed route. How is this possible? Since the viewModel instance of the component in these cases is controlled by the mechanism for binding internal Knockout components, I cannot access its functions so that I can refer to them as callbacks that will be executed. If it were possible, I would pass the viewModel function of the component as a callback parameter to the routed library routing handler, and then execute the callback function, everything would be nice ... But it doesn't seem like that ...

I register a component using CommonJS to work with Browserify, for example, described here.

http://knockoutjs.com/documentation/component-loaders.html#note-integrating-with-browserify

In this case, module.exports in the component view model must expose all its non-constant constructor function, so everything that is required ('myViewModel') is translated into, gets the view model that needs to be updated correctly "when binding to the element.

Any suggestions?

+5
source share
1 answer

let's say I want to execute a function declared inside the associated viewModel component, update / configure model model data, responding to an event with a changed route. How is this possible?

In the documentation for registering components , you have 4 different options for presenting a presentation model:

  • 1st: constructor function
  • 2nd: shared object instance
  • 3rd: createViewModel factory function
  • 4th: AMD module whose value is described by the view model

In fact, option 4 uses the AMD module to return one of the other three options. Thus, there are only 3 possible options: with or without require .

If your SPA will use only one instance of your component, you can use the second solution: create a view model, save it in a variable that is always in scope (for example, in the global scope or inside the module) and register the component to use it. Thus, when a component is initialized by a routing event, you can access the view model through a variable to invoke the required functionality.

If your SPA may have several different instances of your component, or you simply do not want to use the previous solution, you should use the 1st or 3rd option (it does not matter which one is relevant to this issue). In this case, you can pass a callback function to your component, which will be available in the constructor parameters (option 1) or factory (option 3). A constructor (or factory) can call this callback to show its functionality. You can implement something like this, but not necessarily exactly like this:

In the main area of ​​your application

 // Here you'll store the component APIs to access them: var childrenComponentsApi = {}; // This will be passed as a callback, so that the child component // can register the API var registerChildComponentApi = function(api) { childrenComponentsApi.componentX = api; }; 

NOTE: it is important to have an object where you can register functionality so that you do not lose the link

In view:

 <div data-bind='component: { name: 'componentX', params: { registerApi: registerChildComponentApi, /*other params*/ } }'></div> 

In the constructor of the component view model (or factory):

 params.registerApi({ // The callback is available in thereceived params func1: func1, // register the desired functions func2: func2}); 

Later, in the main area, you can access component functions such as this:

childrenComponentsApi.componentX.fun1 (/ * params * /);

This is not really working code, but I hope that it will give you an idea of ​​how to implement what you need.

This solution works fine if the API is not called immediately. I used this implementation when the functionality is called by the user action, so I'm sure that the component is already installed.

But in your case, creating the component is asynchronous, so you need to change the implementation. There are at least two possible ways:

1) The easiest way is to change the implementation of registerApi and use it for initialization. Something like that:

In the viewmodels constructor:

 params.registerApi({ init: init, // initialization function func1: func1, // other exposed functionality func2: func2}); 

In the main area:

 var registerChildComponentApi = function(api) { childrenComponentsApi.componentX = api; childrenComponentsApi.componentx.init(/* params*/) } 

In this implementation, init is called after the callback has been launched by the client component, so you can be sure that this component is available.

2) a more complex solution involves the use of promises. If your component needs to perform asynchronous operations for preparation (for example, for AJAX calls), you can force it to return the promise, in addition to all open APIs, so that the component resolves the promise when it is really ready, and the main area starts the API only when the promise has been resolved . Something like that:

In the view model constructor:

 params.registerApi({ ready: ready, // promise created and solved by the component func1: func1, // exposed functionality func2: func2}); 

In the main area:

 var registerChildComponentApi = function(api) { childrenComponentsApi.componentX = api; } childrenComponentsApi.componentx.ready().then( childrenComponentsApi.componentx.func1; ); 

These are some implementation examples, but there can be many. For example, if you only need to run the init function for the component representation model, you can specify the component, for example provideInit , to the component and run it in a constant, parsing the init component. Something like that:

 <div data-bind='component: { name: 'componentX', params: { provideInit: provideInit, /*other params*/ } }'></div> var proviedInit: function(init) { init(/* params */); }; 

And do not forget that you can also initialize the component representation model by passing all the necessary parameters to the constructor. Or even passing observables as parameters and changing them from the main area.

The last best advice I can give you is to standardize and properly document registerApi functionality so that all components are implemented and used the same way.

As I said at the beginning, any of these solutions can be implemented directly or using AMD modules. That is, you can register the component that provides the constructor of the view model directly (option 1), or define the constructor as an AMD module and use option 4.

+3
source

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


All Articles