Language overview

This page offers a quick overview of the language together with some examples.

Language & files

Live Elements files end with the .lv extension. lv files support both javascript and live elements code, the only restriction is that lv files can only export components or component instances. Live elements code is used only for declaring and creating components, everything else inside components, like functions, property assignments, event listeners and other type of logic is written in javascript.

component X{
  fn printX(){ // a function with js code
    console.log('This is component X')
  }

  // a function assigned to a property
  y : () => { console.log('Called X property y') }
}

Files, modules and packages

Live Elements files are grouped into modules, and one or more modules forms a package. A module in Live elements is a folder with one or more lv files. All exports from .lv files are available to each other inside the same folder. For example, if A.lv and B.lv are in the same folder, and A.lv exports component A, in B.lv we can access A directly:

A.lv file:

component A{}

B.lv file:

component B{
  fn getA(){ return A } // return component A
}

To access components outside the folder we're in, we need to import that folder. Imports can be of 2 types: relative, and absolute. Relative imports start with a dot (.), and they import from the same package. Absolute imports start with the package name:

// relative import
import .a  // : will import module 'a' from the root of the current package

// absolute import
import live-web.dom  // will import 'dom' module from 'live-dom' package

Components

Note:

You can experiment with these concepts inside the playground, which you can install locally:

npm i live-elements-web-cli 
npx lvweb init --template playground
npx lvweb serve

The playground should be running on localhost:8080. In the top left corner, click on the Open icon, and select: Console: Application

The following application provides a run function where you can experiment with live elements and javascript code: If you're having trouble setting it up, you can checkout the quick-start-language section for more information.

Components are similar to javascript classes, but with extra functionality. A component is declared like this:

component X{}

A component that inherits another component is declared using this < symbol:

component Y < X{}

All components inherit from BaseElement, similarly to how all classes in javascript inherit from the Object class.

To create a component, use curly braces after the component name:

let a = X{}
let b = Y{}

Component Properties

Components declare properties like this:

component Point{
  number x: 0
  number y: 0
}

Properties can be declared without assignning them:

component Point{
  number x
  number y
}.

Properties can be assigned when inheriting a component that declared a property:

component OriginPoint < Point{
  x: 0
  y: 0
}

They can also be assigned when creating a new component instance:

const a = Point{
  x: 10
  y: 10
}

Properties automatically bind to properties they're assigned to:

const a = Point{
  x: 10
  y: this.x // x is now bound to y
}

console.log(a.y) // 10
a.x = 20
console.log(a.y) // 20, as x is now 20 and y is bound to x

To avoid bindings, the equal sign can be used intead of the colon:

const a = Point{
  x : 10
  y = this.x // will not bind y to x
}
a.x = 20
console.log(a.y) // 10

Component constructors

Constructors use the constructor keyword:

component Point{
  number x
  number y

  constructor(x:number, y:number){
    super()
    this{ x = x; y = y }
  }
}

All constructor arguments must be succeeded by a type. All types are typescript types. Whithin the constructor body, the call to super() is required and this{} initializer is also required and can be used to initialize declared properties: this{ x = value; y = value }.

The following notation is used to create such a component:

const point = Point.(10, 10){}

Component Quick Text Constructors

Quick text constructors are shortcuts to initialize a component with a single string constructor:

component Text{
  string data

  constructor(data:string){
    data()
    this{ data = data }
  }
}

const t = Text`Welcome` Same as Text.('Welcome'){}

Lines are concatenated into a single one, and spaces are trimmed:

const t = Text`
  this text spans on
  multiple lines, but
  will be merged into one
`

Tripple backticks can be used to preserve spaces and new lines:

const t = Text```
   This text will actually be on
   multiple lines
```

Children

Components can accept children by declaring the default children property:

component TreeNode{
  default children
}

const hierarchy = TreeNode{
  TreeNode{}
  TreeNode{
    TreeNode{}
    TreeNode{}
  }
}

Ids

Inside hierarchies, component instances can be accessed using an id:

component TreeNode{
  default children
  string data
}

const hierarchy = TreeNode{
  id: root // can now be accessed as root
  data: 'shared-data'

  TreeNode{
    data: root.data // this will now be 'shared-data'

    TreeNode{
      data: root.data // this will also be data
    }
  }
}

Functions

Just like classes, components can have functions:

component A{
  fn sayHello(){
    console.log('hello')
  }
}

const a = A{}
a.sayHello()

Argument types are required, while the return type is optional:

component A{
  fn say(message:string){
    console.log(message)
  }
}

const a = A{}
a.say('hello')

Static functions use the static keyword:

compoent A{
  static fn say(message:string){
    console.log(message)
  }
}

A.say('hello')

Events and Listeners

Components can declare events. Events can be 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 assign a function to be executed when an event was triggered:

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

    on event1: () => {
        console.log("Event 1 triggered")
    }
    on event2: (x, y) => {
        console.log("Event 2 triggered with: x=" + x + " and y=" + y)
    }
}

All properties automatically add an event named <property_name>Changed, which is triggered when the property changes:

component A{
    number x: 0
    on xChanged: () => {
        console.log("x changed to " + this.x)
    }
}

const a = A{}
a.x = 10 // will log: x changed to 10

Events and listeners can be declared when creating a component as well:

const a = A{
  event event1()
  on event1: () => { console.log('Event 1 triggered') }
}

More Information

The following 4 pages cover all the language features in depth: