Skip to content

Latest commit

 

History

History
 
 

view

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

$mol_view

The base class for all visual components. It provides the infrastructure for reactive lazy rendering, handling exceptions. By default it finds or creates a div without child node changing and additional attributes, fields and event handler creation. You can customize it by inheritance or properties overriding at instantiating.

Properties

dom_name()' : string Returns name of the DOM-element creating for component, if the element with appropriate id is not presented at DOM yet.

dom_name_space() = 'http://www.w3.org/1999/xhtml'

Returns namespaceURI for the DOM element.

sub() : Array< $mol_view | Node | string | number | boolean > = null Returns list of child components/elements/primitives. If the list have not been set (by default), then the content of the DOM-element would not be changed in way, it's helpful for manual operating with DOM.

context( next? : $mol_ambient_context ) : $mol_ambient_context Some rendering context. Parent node injects context to all rendered child components.

minimal_height() = 0

Returns minimum possible height of the component. It's set by hand with constant or some expression.This property is used for lazy rendering.

dom_node() : Element

Returns DOM-element, to which the component is bounded to. At first the method tries to find the element by its id at DOM and only if it would have not been found - the method would create and remember a new one.

dom_tree() : Element

Same as dom_node, but its guarantee, that the content, attributes and properties of the DOM-element should be in actual state.

attr() : { [ key : string ] : string | number | boolean }

Returns the dictionary of the DOM-attributes, which values would be set while rendering. Passing null or false as the value to the attribute would lead to removing the attribute. Passing true is an equivalent to passing its name as value. undefined is just ignored.

field() : { [ key : string ] : any }

Returns dictionary of fields, which is necessary to set to the DOM-element after rendering.

style() : { [ key : string ] : string | number }

Returns dictionary of styles. Numbers will be convertes to string with "px" suffix.

event() : { [ key : string ] : ( event : Event )=> void }

Returns dictionary of event handlers. The event handlers are bind to the DOM-element one time, when the value is set to dom_node property. This handlers are synchronous and can be cancelled by preventDefault().

focused( next? : boolean ) : boolean

Determines, whether the component is focused or not at this time. If any inserted component would be focused, then its parent component would be focused also.

plugins() : Array< $mol_view > = null

It is an array of plugins. Plugin is a component which can be supplemented with the logic of the current components. For example

In the example we create a list with navigation (using $mol_nav)

<= Options $mol_list
    plugins / 
        <= Nav $mol_nav
            keys_y <= options /
    rows <= options /

*.view.tree

view.tree - is a declarative language of describing components, based on format tree. In a file could be plenty of components defined in series, but better way is put every component in a separate file, except very trivial cases. To create a new component it's enough to inherit this from any existing one. Names of the components should begin with $ and be unique globally accordance with principles presented on MAM. For example, let's declare the component $my_button extended from $mol_view:

$my_button $mol_view

It translates to (every *.view.tree code would be translated to *.view.tree.ts):

namespace $ { export class $my_button extends $mol_view {} }

While inheritance there is a possibility to declare additional properties or overload existing (but types of properties should match). For example lets overload a uri property with "https://example.org" string, and sub - with array of one string "Click me!", besides, lets declare a new property target with "_top" value by default. (it's important to mark that a value by default is necessary when declaring a property):

$my_example $mol_link
	uri \https://example.org
	sub /
		\Click me!
	target \_top
namespace $ { export class $my_example extends $mol_link {

	uri() {
		return "https://example.org"
	}

	sub() {
		return [ "Click me!" ]
	}

	target() {
		return "_top"
	}

} }

Nodes beginning with - - would be ignored, it allows to use them for commenting and temporary disable subtree. Nodes beginning with $ - is name of component. / - any list should begin with this symbol. \ - should be preceded any raw data, which can contain entirely any data until the end of the line, @ marks string for extraction to separate *.locale=en.json file. Numbers, booleans values and null is being wrote as it is, without any prefixes:

$my_values $mol_view
	title @ \Values example
	sub /
		0
		1.1
		true
		false
		null
		\I can contain any character! \("o")/
		- I
			am
				remark...
namespace $ { export class $my_values extends $mol_view {

	title() {
		return this.$.$mol_locale.text( '$my_values_title' )
	}

	sub() {
		return [ 0 , 1.1 , true , false , <any> null , "I can contain any character! \\(\"o\")/" ]
	}

} }

Dictionary (correspondence keys to their values) could be declared through a node * (you can use ^ to inherit pairs from superclass), through which are set values of attributes to DOM-element:

$my_number $mol_view
	dom_name \input
	attr *
		^
		type \number
		- attribute values must be a strings
		min \0
		max \20
namespace $ { export class $my_number extends $mol_view {

	dom_name() {
		return "input"
	}

	attr() {
		return { ...super.attr() ,
			"type" : "number" ,
			"min" : "0" ,
			"max" : "20" ,
		}
	}

} }

We could set value in the same way for fields of a DOM-element:

$my_scroll $mol_view
	field *
		^
		scrollTop 0
namespace $ { export class $my_scroll extends $mol_view {

	field() {
		return { ...super.field() ,
			"scrollTop" : 0 ,
		}
	}

} }

And styles too:

$my_rotate $mol_view
	style *
		^
		transform \rotate( 180deg )
namespace $ { export class $my_rotate extends $mol_view {

	style() {
		return { ...super.style() ,
			"transform" : "rotate( 180deg )" ,
		}
	}

} }

As a value we could bring not only constants, but also a content of another properties through one-way binding. For example, lets declare two text properties hint and text, and then use them for forming a dictionary field and a list sub:

$my_hint $mol_view
	hint \Default hint
	text \Default text
	field *
		^
		title <= hint -
	sub /
		<= text -
namespace $ { export class $my_hint extends $mol_view {

	hint() {
		return "Default hint"
	}

	text() {
		return "Default text"
	}

	field() {
		return { ...super.field() ,
			"title" : this.hint() ,
		}
	}

	sub() {
		return [ this.text() ]
	}

} }

Often it is convenient to combine declaration of property and usage of this one. The next example is equals to the previous completely:

$my_hint $mol_view
	field *
		^
		title <= hint \Default hint 
	sub /
		<= text \Default text

Reactions on DOM-events are required for two-way binding. For example, lets point out, that objects of click event is necessary to put in eventRemove property, which we declare right here and set it a default value null:

$my_remover $mol_view
	event *
		^
		click?val <=> remove?val null 
	sub /
		\Remove
namespace $ { export class $my_remover extends $mol_view {

	@ $mol_mem
	remove( next? : any ) {
		return ( next !== undefinded ) ? next : null as any
	}

	event() {
		return { ...super.event() ,
			"click" : ( next? : any )=> this.remove( next ) ,
		}
	}

	sub() {
		return [ "Remove" ]
	}

} }

We could declare as value an instance of another class directly. In the next example it is being declared a property List, and which value would be a component type of $mol_list_demo_tree, and then it put into a list of child components sub:

$my_app $mol_view
	List $mol_list_demo_tree
	sub /
		<= List -
namespace $ { export class $my_app extends $mol_view {

	@ $mol_mem
	List() {
		const obj = new $mol_list_demo_tree
		return obj
	}

	sub() {
		return [ this.List() ]
	}

} }

A property of a nested component could be overloaded also:

$my_name $mol_view
	sub /
		<= Info $mol_label
			title \Name
			content \Jin
namespace $ { export class $my_name extends $mol_view {

	@ $mol_mem
	Info() {
		const obj = new $mol_label
		obj.title = () => "Name"
		obj.content = () => "Jin"
		return obj
	}

	sub() {
		return [ this.Info() ]
	}

} }

Properties of parent and child component could be linked. At the following example we declare reactive property name, and we say to child component Input use property name as its own property value, we also say to a child component Output we want to property name to be outputted at inside of this one. In this way components Input and Output are become linked through parent's property name and changing value in Input would lead to updating output:

$my_greeter $mol_view
	sub /
		<= Input $mol_string
			hint \Name
			value?val <=> name?val \
		<= Output $mol_view
			sub /
				<= name?val \
namespace $ { export class $my_greeter extends $mol_view {

	@ $mol_mem
	name( next? : any ) {
		return ( next !== undefined ) ? next : ""
	}

	@ $mol_mem
	Input() {
		const obj = new $mol_string
		obj.hint = () => "Name"
		obj.value = ( next? : any ) => this.name( next )
		return obj
	}

	@ $mol_mem
	Output() {
		const obj = new $mol_view
		obj.sub = () => [ this.name() ]
		return obj
	}

	sub() {
		return [ this.Input() , this.Output() ]
	}

} }

=> - Right-side binding. It declares alias for property of subcomponent in declared component.

$my_app $mol_scroll
	sub /
 		<= Page $mol_page
			Title => Page_title -
			head /
				<= Back $mol_button_minor
					title \Back
				<= Page_title -
namespace $ { export class $my_app extends $mol_scroll {

	/// Title => Page_title -
	Page_title() {
		return this.Page().Title()
	}

	/// Back $mol_button_minor title \Back
	@ $mol_mem
	Back() {
		const obj = new $mol_button_minor
		obj.title = () => "Back"
		return obj
	}

	/// Page $mol_page 
	/// 	Title => Page_title 
	/// 	head / 
	/// 		<= Back 
	/// 		<= Page_title
	@ $mol_mem
	Page() {
		const obj = new $mol_page
		obj.head = () => [ this.Back() , this.Page_title() ]
		return obj
	}

	/// sub / <= Page
	sub() {
		return [ this.Page() ]
	}

} }

There are certain properties that depending on the key return different values. A typical example - a list of strings. Each line - a separate component that is available for the unique key. The listing of such properties is ! after the name of the key:

$my_tasks $mol_list
	sub <= task_rows /
	Task_row!key $mol_view
		sub /
			<= task_title!key <= task_title_default \
namespace $ { export class $my_tasks extends $mol_list {

	sub() {
		return this.task_rows()
	}

	task_rows() {
		return []
	}

	@ $mol_mem
	Task_row( key : any ) {
		const obj = new $mol_view
		obj.sub = () => [ this.task_title( key ) ]
		return obj
	}

	task_title( key : any ) {
		return this.task_title_default()
	}

	task_title_default() {
		return ""
	}

} }

Here we declared the property task_row, which takes on input some key and returns an unique instance of $mol_view for every key, with overloaded property sub, which outputs appropriate task_title for every task_row, and in its turn task_title returns the content of property default_title independently of the key, which is equal to empty string initially. Further overloading any of these properties, we could change any aspect of component behavior. You can override task_rows in subclass to generate rows as you want. In example:

task_rows() {
	const rows = [] as $mol_view[]
	for( let i = 0 ; i < 10 ; ++ i ) rows.push( this.Task_row( i ) )
	return rows
}

All special chars

  • - - remarks, ignored by code generation
  • $ - component name prefix
  • / - array
  • * - dictionary (string keys, any values)
  • ^ - return value of the same property from super class
  • \ - raw string
  • @ - localized string
  • <= - read only provide property from owner to sub component
  • => - read only provide property from sub component to owner
  • <=> - fully replace sub component property by owner's one
  • ! - property takes key as first argument
  • ? - property can be changed by provide additional optional argument

view.ts

In addition to declarative description of component, next to it could be created a file of the same name with view.ts extension, where a behavior could be described. Using a special construction, it could be inherited from realization obtained of view.tree and it would be overloaded automatically by heir:

For example we have following description into ./my/hello/hello.view.tree:

$my_hello $mol_view
	sub /
		<= Input $mol_string
			hint \Name
			value <=> name?val \
		<= message \

Here we declared 2 properties: name for getting value from Input and message for output the value. It would be translated into following file ./my/hello/-view.tree/hello.view.tree.ts:

namespace $ { export class $my_hello extends $mol_view {

	@ $mol_mem
	name( next? : any ) {
		return ( next !== undefined ) ? next : ""
	}

	@ $mol_mem
	Input( next? : any ) {
		const obj = $mol_string
		obj.hint = () => "Name"
		obj.value = ( next? : any ) => this.name( next )
		return obj
	}

	message() {
		return ""
	}

	sub() {
		return [ this.Input() , this.message() ]
	}

} }

For now we could "mix" into this class our behavior through ./my/hello/hello.view.ts:

namespace $.$$ {
	export class $my_hello extends $.$my_hello {
		
		message() {
			const name = this.name()
			return name && `Hello, ${name}!`
		}
		
	}
}

Here we linked our properties message and name through the expression. So now wherever we use $my_hello, the value message would be depend on name property entered by a user.

IDE Support

Articles