Creating Components

Live Elements uses a declarative syntax to create components. There are a few variations of this syntax, depending on context.

The common way to create a component is by using braces after a component type:

component A{}
const a1 = A{} // creates a new object of type A

The javascript equivalent for the above, which also works (but shouldn't be used), would be:

component A{}

const a1 = new A()
BaseElement.complete(a1)

Components that have a constructor that takes a set of arguments can be created by using the dot (.) after the constructor, and following with a list of arguments in paranthesis, just before the braces:

component A{
    constructor(arg1:any, arg2:any){
        super()
        this{}
        console.log("Created with args: " + arg1 + "," + arg2)
    }
}

const a1 = A.(1, 2){} // Created with args: 1,2

1. Members when creating a component

Inside the braces, we can add members like properties, functions, events, and some of the functionality similar to when declaring a component:

component A{
    number x: 10
    number y: 20
}

const a1 = A{
    x: 11 // assign property
    number z: 33 // add new property 
}
console.log(a1.x) // 11
console.log(a1.y) // 20
console.log(a1.z) // 33

The code below shows an example of each of the possible declarations:

component A{}

let a = A{
  number x: 10 // property declaration
  y: 10 // property assignment

  fn toString() string { return ""; } // function
  id: a // scope identifier

  event triggered()
  on triggered: () => {} // listener

  B{} // default child 0
  C{} // default child 1
}

The following sections will go through each declaration in detail.

1.1. Property Declarations

The expression below declares a property named x with a value of 10:

component A{}

const a = A{ 
    number x: 10 
}
console.log(a.x) // 10

The assignmet is optional:

component A{}

const a = A{
    number x
}
console.log(a.x) // undefined

Bindings and binding expressions work the same as they would in a component declaration:

component A{}

const a = A{
    number x: 10
    number y: 20
    number z: this.x + this.y // simple binding expression
    number t: { 
        if ( this.x === 20 )
            return true
        return false
     } // complex binding expression
}
a.x = 20
console.log(a.z) // 40
console.log(a.t) // true

2.2. Property assignments

Properties that are part of the object can be assigned directly:

component A{
    number x: 10
}
const a = A{
    x: 30
}
console.log(b.x) // 30

2.3. Component Ids

Each component when created can have an id assigned, which can then be used to reference that component from other components:

component A{
    id: a
    number x: 10
    
    number x1: B{ 
        id: b
        number y: a.x // a is accessible from here
    }
    number x2: C{ number z: b.y } // b is accessible from here
}

See also component declaration ids.

2.4. Methods

Methods can be added using the fn keyword. Method parameter types are required, and return type is optional:

component A{}

const a = A{
    fn sum(a:number, b:number) number{
        return a + b
    }
    fn toString() string{
        return "A{}"
    }
}

console.log(a.toString()) // A{}
console.log(a.sum(2, 3)) // 5

2.5. Events and Listeners

Events are declared using the event keyword, and, similar to functions, can have arguments. Events are triggered using the emit() function:

component A{
    event event1()
    event event2(x:number, y:number)

    fn triggerEvent1(){
        this.event1.emit()
    }
    fn triggerEvent2(){
        this.event2.emit(10, 20)
    }
}

Listeners are preceeded by the on keyword. They assign a function to be executed when the event was triggered:

component A{
    event event1(x:number, y:number)
    on event1: () => {
        console.log("Event 1 triggered")
    }
}

2.6. Default children

Objects of a component that enable the default children property can nest other objects. See also component default children:

component A{
    default children
}

const a = A{
    B{} // child 0
    C{} // child 1
}
console.log(a.children.length) // 2

2.7. Constructors

Components that declare constructors with arguments can be created using the dot notation followed by the arguments in paranthesis and braces:

component A{
    constructor(arg:number){
        super()
        this{}
        console.log("Created component with: " + arg)
    }
}
const a1 = A.(1){} // Created component with: 1

The example below shows how to create 2 Label objects that assign their text property through their constructor:

component A{
    default children
}

component Label{
    constructor(arg:number){
        super()
        this{}
        this.text = arg
    }

    string text: ''
}

const a = A{
    Label.("Label 1"){}
    Label.("Label 2"){}
}

2.7.1 Text Constructors

In user interfaces, a lot of visual components have to work with text, and most of the time, different parts of the text need to be formated differently. These annotations can become quite hard to follow, it's why Live Elements provides a shortcut to creating these visual components. The previous example, can be simplified as such:

const a = A{
    Label`Label 1`
    Label`Label 2`
}

Using this string notation makes it easy to format text in Live Elements. We can, for example, write the following:

Paragraph{
    T`I am `B`bold`T` and `I`italic.`
}

This creates a paragraph component, with 4 children.T, which stands for plain text, B which is bold text, and I, which is italic text. A detailed version from above would be written like this:

Paragraph{
    T{ text: "I am " }
    B{ text: "bold" }
    T{ text: " and " }
    I{ text: "italic." }
}

Text using this notation can be split on multiple lines. All spaces are trimmed to a single space:

const p = Paragraph{
    T`
        This is a 
        paragraph
        spread on
        multiple lines.
    `
}

console.log(p.children[0].text) // This is a paragraph spread on multiple lines

To add spaces or new lines, we can use the \s and \n symbols:

const p = Paragraph{
    T`
        This is a paragraph spread on \n
        multiple\s\s lines.
    `
}

console.log(p.children[0].text) // This is a paragraph spread on 
                                // multiple   lines

Another option to separate lines, is to use tripple quoted strings, which store the raw string (similar to the <pre> element in html):

const p = Paragraph{
    T```
This is a paragraph spread on
multiple\s\s lines.
    ```
}

console.log(p.children[0].text) // This is a paragraph spread on 
                                // multiple   lines

2.8 Component Completed Hook

Sometimes you might want to do operations after all assignments and connections have been made to an object. There's a function called completed which you can override when creating the object:

component A{}

const a = A{
    number x: 10
    number y: 20

    fn completed(){
        console.log("x and y are assigned: " + x + "," + y)
    }
}

3. Component Instances

Components can be created at the root of each lv file in order to be exported for that module. To create a component at the root of the file, the instance keyword is required together with the object name to be exported before the actual component creation:

// mymodule/myobject.lv

instance myobject A{} // will export an instance of A under the name of myobject

So now, when importing mymodule myobject will be available:

import .mymodule

component B{
    fn completed(){
        console.log(myobject) // object of type A
    }
}

The next page goes through working with web components.