Data injection - How to update a view by remote data - craftkit best practice
Tags: craftkit, javascript, shadowdom, webdev
Published: 2020-11-02 14:51:14

Data will always come from outside of application asynchronously. So we are always plagued by way of loading data, and making its view. If you don't care about lag of data load, your interface easily stack. 

## Best practice: Data injection

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vl6wmpy9l4ogyoeo080b.png)

The best practice for this problem is "load data then make its view" pattern. (called as "Data injection" pattern) 

It is best to implement controller class to load remote data, that makes its factorized view (Factorize pattern) after loaded data. 

In other word, this is a kind of GoF Facade pattern, or minimized MVC structure. Usually, this controller represents reusable package. 

If factorized view classes need to load remote data, the request should be delegated to the controller class. (Delegate pattern)

This will make complexity aggregated in the controller. 

## Example

Here is a sample remote data same as [craftkit best practice - How to manage sub views for array data](https://dev.to/craftkitjs/craftkit-best-practice-how-to-manage-sub-views-for-array-data-29b2). 

```javascript 
https://craftkit.dev/craftkit-playground/sampledata/simple_products.json

[
	{ name: 'Apple', price: 10 },
	{ name: 'Orange', price: 13 },
	{ name: 'Strawberry', price: 20 },
	{ name: 'Pear', price: 9 }
]
``` 

Here is a sample implementation to load and show remote product list.

```javascript 
class ProductListController extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { products:[] };
		this.views = { current:null };
	}
	viewDidLoad(callback){
		this.loadData();
		this.renderReloadBtn();
		if( callback ){ callback(); }
	}
	loadData(){
		this.renderLoading();
		
		let xhr = new XMLHttpRequest();
		xhr.onload = (e) => {
			this.data.products = JSON.parse(xhr.response);
			this.renderProductList();
		};
		xhr.open('GET','https://craftkit.dev/craftkit-playground/sampledata/simple_products.json');
		xhr.send();
	}
	renderLoading(){
		let view = new Loading();
		this.replaceView({id:'list',component:view});
		this.views.current?.unloadView(); // for chrome
		this.views.current = view;
	}
	renderProductList(){
		let view = new ProductList({delegate:this});
		this.replaceView({id:'list',component:view});
		this.views.current?.unloadView(); // for chrome
		this.views.current = view;
	}
	renderReloadBtn(){
		this.appendView({
			id: 'btn',
			component: new ReloadBtn({delegate:this})
		});
	}
	style(componentId){
		return `
			#btn { background-color:#eee; }
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<div id="list"></div>
				<div id="btn"></div>
			</div>
		`;
	}
}

class Loading extends Craft.UI.View {
	template(componentId){
		return `
			<div>Loading...</div>
		`;
	}
}
class ReloadBtn extends Craft.UI.View {
	constructor(options){
		super(options);
		this.delegate = options.delegate;
	}
	reloadData(){
		this.delegate.loadData();
	}
	style(componentId){
		return `
			.root { cursor:pointer; }
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root" 
				onclick="${componentId}.reloadData();">
				Reload data
			</div>
		`;
	}
}

class ProductList extends Craft.UI.View {
	constructor(options){
		super(options);
		this.delegate = options.delegate;
		this.views = { products:[] }
	}
	viewDidLoad(callback){
		this.delegate.data.products.forEach( p => {
			let view = new Product(p);
			this.appendView(view);
			this.views.products.push(view);
		});
	}
}
class Product extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = options;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				${this.data.name} : ${this.data.price}
			</div>
		`;
	}
}
``` 

Sometimes, controller is represented as Page object to be shown in navigation stack. Sometimes, controller is represented as micro-fronend UI component. 


## NOTE

Above examples are runnable on playground.

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

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