Skip to content

Latest commit

 

History

History
220 lines (158 loc) · 8.51 KB

12.api.md

File metadata and controls

220 lines (158 loc) · 8.51 KB

Api

Introduction

There are some coding features you may wish to take note of.
This document will describe some of those features, how and why to use them.

Create a bindable context

A bindable context is the core mechanism for the binding engine. It creates an object and events data structure. You can use this structure to for binding and notifications.

const contextId = crsbinding.data.addObject(name);

Then name attribute helps you identify the context in the data for debugging purposes.
The addObject function will create all the required internals and send you back a context id used for all other calls.
All calls on the crsbinding.data path requires the context id as the first parameter.
These functions will also accept as the first parameter a object with a field _dataId.

Here are two examples function calls, one with the id and the other with an object that has a _dataId field.

this._dataId = crsbinding.data.addObject(name);
crsbinding.data.setProperty(this._dataId, "propertyName", "propertyValue");
crsbinding.data.setProperty(this, "propertyName", "propertyValue");

To remove the context once you are done with it you can call removeObject.

crsbinding.data.removeObject(contextId);

If however you have bound elements to that context it might be better to just use a brute force clean function that get's rid of all traces in the dom and data.

crsbinding.utils.forceClean(contextId);

Set property value

crsbinding.data.setProperty(contextId, propertyName, value);

This function can be run from anywhere at any time.
The only requirement is that the context exists at the time you call it.
There are no limitations around what property you set on what context.

In viewbase and bindable element there are shorthand functions setProperty and getProperty but you don't need to define the context id.
Under the hood they call the crsbinding.data functions and fill in the context id on your behalf. When you want to interact with properties on the globals context you can use the context id crsbinding.$globals.

crsbinding.data.setProperty(crsbinding.$globals, "messagesCount", 2);

Get property value

const value = crsbinding.data.getProperty(contextId, propertyName);

Get the value of any property on any context at any time as long as the context and property exists. See the above documenation on setProperty, the same applies here.

Listen to changes on path

crsbinding.events.listenOnPath(context, property, callback)
This function allows you to be notified if a property changes.
The path is the object path relative to the context.

Consider the following object structure.

this.setProperty("model", {
    person: {
        firstName: "John",
        lastName: "Doe"
    }
})

Let's declare a path to listen on.

crsbinding.events.listenOnPath(this, "model.person.firstName", callback);

If anything changes that affects that path, the callback function will be called.

  1. model is replaced with a different object
  2. person is replaced with a different object
  3. first name changes to a different value

The property parameter can also be an array of paths.

crsbinding.events.listenOnPath(this, ["model.person.firstName", "model.person.lastName"], callback);

Listen to changes on binding context

Any binding context can be notified about changes.
If you want to be notified about a spesific property on the context you can create a function where the name is a composite between the property name and "Changed".
For example, if I have a property called "firstName" the function name would be "firstNameChanged".

The second way of doing this is having a function called "propertyChanged" on the binding context.
All property changes starting on the binding context regardless of depth will be notified in this function.

propertyChanged(property, value) {
    if (property.indexOf("data.person") !== -1 ) {
        doSomething();
    }
}

Note that if a property has it's own function "trap" it will not be notified in propertyChanged.
So if I update firstName then only firstNameChagned will fire.
propertyChanged will not be called.
For all other property changes, propertyChanged will be called.

Bypassing binding events

What if you want to make changes to the binding data but not have any of the UI update or triggers fire.
You can access the binding data at crsbinding.data._data.
This is a map where the key is the context id.

So you can get the binding context data using.

const data = crsbinding.data._data.get(1).data; // 1 is the context id

Working directly on the data object will allow you to make silent changes, but generally this is not reconmended unless you really know what you are doing.
The other reason why you may want to know about this is for debugging.
You can investigate your binding data in the console this way.

Update UI

crsbinding.data.updateUI(contextId, property);

Update UI starts the process of flushing the data in the binding context data to the screen.
You often want to use it when working with the for binding expression and arrays.
This allows you to make batch changes to your data but only trigger the updates once.
This is a bit more of an advanced strategy but can buy a bit more performance.

If you debug your data structure, and the data in the store is correct, but not on the UI, try giving updateUI a go to repaint those changes.

makeShared

crsbinding.data.makeShared(this, "selectedPerson", ["firstName", "lastName"]);

This one is more difficult to explain so let's use an example.
Go to the master detail example
Here we have a list on the left and input on the right.

For a selected item in the list, if I make changes to the firstName or lastName, the selected list item must also be updated.
The property the form binds to is on the view model. The list item has it's own context. So now I am working basically with two different contexts.
I know that the view model has a property called selectedPerson and when an item in the list is selected, selectedPerson is set to that array item.

<ul click.setValue="selectedPerson = $data($event.target.dataset.uid)"></ul>

The uid in the example above is the contextId of the array item.

When I make changes to the first name using the input box, the binding engine is not ware of the fact that there is a difference context who may want its UI updated when you make changes to selectedPerson. We can mark the property selectedPerson as shared on the view model.
This tells the binding engine that any object that sets selectedPerson shares its fate with selectedPerson. Changes made to selected person will notify changes to the UI of the shared object.
When changes are made to the sharedObject, update the UI of the list item.

The parameters for makeShared are:

  1. contextId or a object that contains a field called _dataId (ViewBase and BindableElement)
  2. the property on the binding context that will be set by the array
  3. the fields that will trigger a refresh fo the array item's UI

As you can see in the master detail example, when you update the firstName and lastName, the selected array item also updates with the same changes.

Relative path

There are two functions you need to take note of when working working with relative paths.

  1. getPathOfFile
  2. relativePathFrom

You can find these functions under crsbinding.utils.

getPathOfFile gives you the folder of a given file.

const result = crsbinding.utils.getPathOfFile("https://folder/subfolder/index.js");
expect(result).toBe("https://folder/subfolder/");

relativePathFrom will give you the path given a source folder or file and a relative path to calculate form that.

const result = crsbinding.utils.relativePathFrom("https://folder/subfolder/index.js", "./../../test.js");
expect(result).toBe("https://test.js");

Here is a practical example where we are using a relative path to fetch HTML to be used in a crs-widget with id "cw-header".

export async function loadTools(context) {
    const url = crsbinding.utils.relativePathFrom(context.html, "./tools.html");
    const html = await fetch(url).then(result => result.text());
    crsbinding.events.emitter.postMessage("#cw-header", {context: context, html: html});
}

These functions are used internally for html fragments