How to update view - craftkit code pattern
Tags: craftkit, javascript, shadowdom, webdev
Published: 2020-10-26 14:45:56

Currently, Craft.UI.View mainly provides UI classes to encapsulate its method, data and DOM structure. View component is automatically rendered by ViewController according to view's lifycycle method implementation.

After you append view on the screen, you have to update its view (=DOM) by your self. (Therefore this is named as **craft** kit)

## Shadow access pattern

Most primitive pattern is finding DOM element via `this.shadow`.

```javascript 
class CounterView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { count:0 };
	}
	countup(){
		this.data.count++;
		this.shadow.getElementById('counter').innerHTML = this.data.count;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<span id="counter">${this.data.count}</span>
			</div>
		`;
	}
}
``` 

In the latest chrome, you can use `replaceChildren` instead of `innerHTML`.

## renderView pattern

Most basic pattern is using Craft.UI.View#renderView method.

This example is just replacing above `this.shadow.getElementById` to `renderView`. 

```javascript 
class CounterView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { count:0 };
	}
	countup(){
		this.data.count++;
		this.renderView();
	}
	template(componentId){
		return `
			<div id="root" class="root">
				${this.data.count}
			</div>
		`;
	}
}
``` 

Every time you call countup() method, template is rendered again. This pattern is useful while your data is simple. 

If your view does not have this.data at the time it is instantiated, your template will be defined by optional chain somthing like this: `${this.data.YourDataSet?.count}`.

## Swap and renderView pattern

In more complex application, you might swap template dynamically.

```javascript 
class CounterView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { count:0 };
		this.templates = {
			even : `
				<div id="root" class="root">
					Some complex even view: ${this.data.count}
				</div>`,
			odd  : `
				<div id="root" class="root">
					Some complex odd view: ${this.data.count}
				</div>`,
		};
		this.crr_template = this.templates.even;
	}
	countup(){
		this.data.count++;
		if( this.data.count % 2 == 0 ){
			this.crr_template = this.templates.even;
		}else{
			this.crr_template = this.templates.odd;
		}
		this.renderView();
	}
	template(componentId){
		return this.crr_template;
	}
}
``` 
renderView() renders template defined by this.crr_template.

## Factorize pattern

This pattern defines more macro structure.

Also described in the post "[How to manage sub views for array data](https://dev.to/craftkitjs/craftkit-best-practice-how-to-manage-sub-views-for-array-data-29b2)", let's break down your template structure until it is almost atomically resolved.

```javascript 
class CounterView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { count:0 };
		this.views = { numbox:null };
	}
	countup(){
		this.data.count++;
	}
	updateNumBoxView(){
		let view = new NumBox({num:this.data.count});
		this.replaceView({
			id        : 'container',
			component : view,
		});
		this.views.numbox.unloadView();
		this.views.numbox = view;
	}
	viewDidLoad(callback){
		this.updateNumBoxView();
		if(callback){ callback(); }
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<div id="container"></div>
			</div>
		`;
	}
}

class NumBoxView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = options;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				${this.data.count}
			</div>
		`;
	}
}
``` 

Every time you update count, new view component will be created and placed in the `#id:container`. Call unloadView if you have to expire its memory. 

## Combined

Actual web application will load its data from remote api, or will be generated by user in run time, or so on. So you may have to combine several patterns. 

```javascript 
class CounterView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { count:0 };
		this.views = { 
			even : new EvenNumBoxView({ delegate:this }),
			odd  : new OddNumBoxView({ delegate:this })
		};
	}
	viewDidLoad(callback){
		this.updateNumBoxView();
		if(callback){ callback(); }
	}
	countup(){
		this.data.count++;
		this.updateNumBoxView();
	}
	updateNumBoxView(){
		if( this.data.count % 2 == 0 ){
			this.views.even.renderView();
			this.replaceView({
				id        : 'container',
				component : this.views.even,
			});
		}else{
			this.views.odd.renderView();
			this.replaceView({
				id        : 'container',
				component : this.views.odd,
			});
		}
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<div id="container"></div>
			</div>
		`;
	}
}

class EvenNumBoxView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.delegate = options.delegate;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				Some complex even view structure: 
				${this.delegate.data.count}
			</div>
		`;
	}
}

class OddNumBoxView extends Craft.UI.View {
	constructor(options){
		super(options);
		this.delegate = options.delegate;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				Some complex odd view structure: 
				${this.delegate.data.count}
			</div>
		`;
	}
}
``` 

In this example, EvenNumBoxView and OddNumBoxView delegate data management to CounterView. (This is called as "delegate pattern")


## NOTE

Above examples are runnable on playground.

```javascript 
var view = new CounterView();
view.loadView();
Craft.Core.Context.getRootViewController().appendSubView(view);
view.countup();
``` 

🛺 Try CraftKit Playground:
https://github.com/craftkit/craftkit-playground