Skip to content

View Formats

nin-jin edited this page Feb 14, 2021 · 6 revisions

Comparison of component description formats. We use view.tree because it is compact and powerful.

Требования

  • Компактность. Чем больше кода, тем больше времени тратится на его поддержку, больше точек отказа, сложнее ориентироваться.
  • Наглядность. Синтаксис должен недвусмысленно говорить о том, что происходит. По возможности в мозгу должны вырабатываться мнемоники.
  • Простота. Чем меньше идиом и исключений, тем проще и быстрее освоить язык.
  • Сложные свойства. Любое свойство может содержать любые JSON данные вперемешку со вложенными компонентами.
  • Связывания свойств. Вложенные компоненты через свойства связываются с корневым. Связи бывают: левосторонними, правосторонними, двусторонними. Свойства бывают мутабельные и немутабельные. А так же они бывают синглтонами и мультитонами. Кроме того они могут быть локализуемыми и не локализуемыми.
  • Объявления свойств. Свойства объявляются как отдельно, так и инлайново. При объявлении, у свойства тут обязательно же задаётся значение по умолчанию.
  • Декларативность. Необходима для стат анализа кода компонент. Например, необходимо для экстракции локализованных строк, автоматического формирования конфигуратора и тд.
  • Типизация. Тайпскрипт должен получать максимум информации о типах, чтобы проверить корректность использования компонент.
  • Эффективность. pull семантика, минимум телодвижений в рантайме для сборки дерева компонент.

HTML шаблоны

  1. HTML многословный. Много визуального шума мешает понимать суть.
  2. Даже со всеми наворотами IDE HTML не очень удобно редактировать.
  3. Во view.tree значением любого свойства может быть что угодно и даже целые поддеревья компонент. В $mol это активно используется. В HTML же значениями именованных свойств могут быть только строки, а дерево может быть только одно. JSX в принципе позволяет и деревья в атрибуты запихивать, но это уже совсем далеко от HTML получается.
  4. В HTML основной акцент делается на тип узла, а не его уникальное имя (которого обычно вообще нет). view.tree наоборот, ставит акцент на уникальном имени (и у каждого узла оно обязано быть), а тип - второстепенная вариативная информация.
  5. view.tree наглядно показывает направление движения данных (стрелочками). В HTML приходится использовать малонаглядные костыли типа "бананы в ящике".

view.tree

$mol_app_users $mol_page
	head /
		<= Filter $mol_string
			hint <= filter_hint @ \Search users on GitHub
			value?val <=> filter_query?val \
	body /
		<= List $mol_list
			rows <= user_rows /
	Foot $mol_row
		sub /
			<= Reload $mol_button_minor
				title <= reload_title @ \Reload
				event_click?val <=> event_reload?val null
			<= Add $mol_button_minor
				enabled <= loaded false 
				title <= add_title @ \Add
				event_click?val <=> event_add?val null
			<= Save $mol_button_major
				enabled <= changed false 
				title <= save_title @ \Save
				event_click?val <=> event_save?val null
			<= Message $mol_status
				status <= users_master /

$mol_app_users_item $mol_row
	sub /
		<= Title $mol_string
			value?val <=> title?val \
		<= Drop $mol_button_minor
			title <= drop_title @ \Drop
			event_click?val <=> event_drop?val null

view.ts

namespace $ { export class $mol_app_users extends $mol_page {

	head() {
		return [].concat( this.Filter() )
	}

	@ $mol_mem
	Filter() {
		const obj = new this.$.$mol_string
		obj.hint = () => this.filter_hint()
		obj.value = ( val? : any ) => this.query( val )
		return obj
	}

	filter_hint() {
		return this.$.$mol_locale.text( "$mol_app_users_filter_hint" )
	}

	@ $mol_mem
	query( val? : any , force? : $mol_atom_force ) {
		return ( val !== void 0 ) ? val : ""
	}

	body() {
		return [].concat( this.List() )
	}

	@ $mol_mem
	List() {
		const obj = new this.$.$mol_list
		obj.rows = () => this.user_rows()
		return obj
	}

	user_rows() {
		return [] as any[]
	}

	@ $mol_mem
	Foot() {
		const obj = new this.$.$mol_row
		obj.sub = () => [].concat( this.Reload() , this.Add() , this.Save() , this.Message() )
		return obj
	}

	@ $mol_mem
	Reload() {
		const obj = new this.$.$mol_button_minor
		obj.title = () => this.reload_title()
		obj.event_click = ( val? : any ) => this.event_reload( val )
		return obj
	}

	reload_title() {
		return this.$.$mol_locale.text( "$mol_app_users_reload_title" )
	}

	@ $mol_mem
	event_reload( val? : any , force? : $mol_atom_force ) {
		return ( val !== void 0 ) ? val : null as any
	}

	@ $mol_mem
	Add() {
		const obj = new this.$.$mol_button_minor
		obj.enabled = () => this.loaded()
		obj.title = () => this.add_title()
		obj.event_click = ( val? : any ) => this.event_add( val )
		return obj
	}

	loaded() {
		return false
	}

	add_title() {
		return this.$.$mol_locale.text( "$mol_app_users_add_title" )
	}

	@ $mol_mem
	event_add( val? : any , force? : $mol_atom_force ) {
		return ( val !== void 0 ) ? val : null as any
	}

	@ $mol_mem
	Save() {
		const obj = new this.$.$mol_button_major
		obj.enabled = () => this.changed()
		obj.title = () => this.save_title()
		obj.event_click = ( val? : any ) => this.event_save( val )
		return obj
	}

	changed() {
		return false
	}

	save_title() {
		return this.$.$mol_locale.text( "$mol_app_users_save_title" )
	}

	@ $mol_mem
	event_save( val? : any , force? : $mol_atom_force ) {
		return ( val !== void 0 ) ? val : null as any
	}

	@ $mol_mem
	Message() {
		const obj = new this.$.$mol_status
		obj.status = () => this.users_master()
		return obj
	}

	users_master() {
		return [] as any[]
	}

} }

namespace $ { export class $mol_app_users_item extends $mol_row {

	sub() {
		return [].concat( this.Title() , this.Drop() )
	}

	@ $mol_mem
	Title() {
		const obj = new this.$.$mol_string
		obj.value = ( val? : any ) => this.title( val )
		return obj
	}

	@ $mol_mem
	title( val? : any , force? : $mol_atom_force ) {
		return ( val !== void 0 ) ? val : ""
	}

	@ $mol_mem
	Drop() {
		const obj = new this.$.$mol_button_minor
		obj.title = () => this.drop_title()
		obj.event_click = ( val? : any ) => this.event_drop( val )
		return obj
	}

	drop_title() {
		return this.$.$mol_locale.text( "$mol_app_users_item_drop_title" )
	}

	@ $mol_mem
	event_drop( val? : any , force? : $mol_atom_force ) {
		return ( val !== void 0 ) ? val : null as any
	}

} }

view.ts-like

export class $mol_app_users extends $mol_page {
	
	this.head() = ()=> [
		this.Filter() = ()=> $mol_string({
			hint : ()=> this.filter_hint() = ()=> $mol_locale.text( this , 'Search users on GitHub' ),
			value : (next) => this.filter_query( next ) = ()=> '',
		})
	]
	
	this.body() = ()=> [
		this.List() = ()=> $mol_list({
			rows : ()=> this.user_rows() = ()=> []
		})
	]
	
	this.Foot() = ()=> $mol_row({
		sub = ()=> [
			
			this.Reload() = ()=> $mol_button_minor({
				title : ()=> this.reload_title() = ()=> $mol_locale.text( this , 'Reload' ),
				event_click : (val)=> this.event_reload(val) = (val)=> null,
			}),
			
			this.Add() = ()=> $mol_button_minor({
				enabled : ()=> this.loaded() = ()=> false,
				title : ()=> this.add_title() = ()=> $mol_locale.text( this , 'Add' ),
				event_click : (val)=> this.event_add(val) = (val)=> null,
			}),

			this.Save() = ()=> $mol_button_major({
				enabled : ()=> this.changed() = ()=> false 
				title : ()=> this.save_title() = ()=> $mol_locale.text( this , 'Save' )
				event_click : (val)=> this.event_save(val) = (val)=> null
			}),

			this.Message() = ()=> $mol_status({
				status : ()=> this.users_master() = ()=> []
			}),

		]
	})

}

export class $mol_app_users_item extends $mol_row {
	
	this.sub() = ()=> [

		this.Title() = ()=> $mol_string({
			value : (val)=> this.title(val) = ()=> ''
		}),

		this.Drop() = ()=> $mol_button_minor({
			title : ()=> this.drop_title() = ()=> $mol_locale.text( this , 'Drop' ),
			event_click : (val)=> this.event_drop(val) = (val)=> null,
		}),

	]

}

view.html

<mol_page bind=mol_app_users>
	
	<head>
		
		<mol_string bind=Filter>
			<hint bind=filter_hint locale="Search users on GitHub" />
			<value bidi=filter_query value="" />
		</mol_string>
		
	</head>
	
	<body>
		
		<mol_list bind=List>
			<rows bind=user_rows></rows>
		</mol_list>
		
	</body>
		
	<mol_row bind=Foot>
		<sub>
			
			<mol_button_minor bind=Reload>
				<title bind=reload_title locale="Reload" />
				<event_click bidi=event_reload value=null />
			</mol_button_minor>

			<mol_button_minor bind=Add>
				<title bind=add_title locale="Add" />
				<enabled bind=loaded value=false />
				<event_click bidi=event_add value=null />
			</mol_button_minor>

			<mol_button_major bind=Save>
				<title bind=save_title locale="Save" />
				<enabled bind=changed value=false />
				<event_click bidi=event_save value=null />
			</mol_button_major>
			
			<mol_status bind=Message>
				<status bind=users_master></status>
			</mol_status>

		</sub>
	</mol_row>
		
</mol_page>

<mol_row bind=mol_app_users_item>
	<sub>
		
		<mol_string bind=Title>
			<value bidi=title value="" />
		</mol_string>
		
		<mol_button_minor bind=Drop>
			<sub bind=drop_title locale="Drop" />
			<event_click bidi=event_drop value=null />
		</mol_button_minor>
		
	</sub>
</mol_row>

view.xml

<defs:defs xmlns:defs="mol_defs"  xmlns:def="mol_def" xmlns:prop="mol_prop" xmlns="mol_arg">
	
	<def:mol_app_users extends="mol_page">
		
		<prop:head>
			
			<prop:Filter extends="mol_string">
				<hint>
					<prop:filter_hint locale="Search users on GitHub" />
				</hint>
				<value way="two">
					<prop:filter_query string="" />
				</value>
			</prop:Filter>
			
		</prop:head>	
		
		<prop:body>
			
			<prop:List extends="mol_list">
				<rows>
					<prop:user_rows />
				</rows>
			</prop:List>
			
		</prop:body>
			
		<prop:Foot extends="mol_row">
			<sub>
				
				<prop:Reload extends="mol_button_minor">
					<title>
						<prop:reload_title locale="Reload" />
					</title>
					<event_click bidi="event_reload" value="null" />
				</prop:Reload>

				<prop:Add extends="mol_button_minor">
					<title>
						<prop:add_title locale="Add" />
					</title>
					<enabled>
						<prop:loaded value="false" />
					</enabled>
					<event_click bidi="event_add" value="null" />
				</prop:Add>

				<prop:Save extends="mol_button_major">
					<title>
						<prop:save_title locale="Save" />
					</title>
					<enabled>
						<prop:changed value="false" />
					</enabled>
					<event_click bidi="event_save" value="null" />
				</prop:Save>
				
				<prop:Message extends="mol_status">
					<status>
						<prop:users_master value="null" />
					</status>
				</prop:Message>

			</sub>
		</prop:Foot>
			
	</def:mol_app_users>
	
	<def:mol_app_users_item extends="mol_row">
		<sub>
			
			<prop:Title extends="mol_string">
				<value bidi="title" string="" />
			</prop:Title>
			
			<prop:Drop extends="mol_button_minor">
				<title>
					<prop:drop_title locale="Drop" />
				</title>
				<event_click bidi="event_drop" value="null" />
			</prop:Drop>
			
		</sub>
	</def:mol_app_users_item>

</defs:defs>

view.json

{
	"mol_app_users": {
		"_extends": "mol_view",
		"head": [
			{
				"_prop": "Filter",
				"_extends": "mol_string",
				"hint": {
					"_prop": "filter_hint",
					"_locale": "Search users on GitHub"
				},
				"value": {
					"_bidi": "filter_query",
					"_value": ""
				}
			},
		],
		"body": [
			{
				"_prop": "List",
				"_extends": "mol_list",
				"rows": {
					"_prop": "user_rows",
					"_value": []
				}
			}
		],
		"Foot":	{
			"_extends": "mol_row",
			"sub": [
				{
					"_prop": "Reload",
					"_extends": "mol_button_minor",
					"title": {
						"_prop": "reload_title",
						"_locale": "Reload"
					},
					"event_click": {
						"_bidi": "event_reload",
						"_value": null
					}
				},
				{
					"_prop": "Add",
					"_extends": "mol_button_minor",
					"title": {
						"_prop": "add_title",
						"_locale": "Add"
					},
					"enabled": {
						"_prop": "loaded",
						"_value": false
					},
					"event_click": {
						"_bidi": "event_add",
						"_value": null,
					}
				},
				{
					"_prop": "Save",
					"_extends": "mol_button_major",
					"title": {
						"_prop": "save_title",
						"_locale": "Save"
					},
					"enabled": {
						"_prop": "changed",
						"_value": false
					},
					"event_click": {
						"_bidi": "event_save",
						"_value": null
					}
				},
				{
					"_prop": "Message",
					"_extends": "mol_status",
					"status": [
						{
							"_prop": "users_master",
							"_value": null
						}
					]
				}
			]
		}
	},
	"mol_app_users_item": {
		"_extends": "mol_row",
		"sub": [
			{
				"_prop": "Title",
				"_extends": "mol_string",
				"value": {
					"_bidi": "title",
					"_value": "",
				}
			},
			{
				"_prop": "Drop",
				"_extends": "mol_button_minor",
				"title": {
					"_prop": "drop_title",
					"_locale": "Drop"
				},
				"event_click": {
					"_bidi": "event_drop",
					"_value": null,
				}
			}
		]
	}
}