component Temperature < State{
  number celsius: 0
}

component TemperatureConverter < PageView{
  static any[] use = [Content]

  Temperature temperature: Temperature{ id: temperature }

  Content{
    H1`Temperature Converter`

    T`Celsius: `
    Input{
      type = "number" 
      placeholder = "Celsius" 
      value = temperature.celsius
      on input:() => {temperature.celsius = this.currentValue}
    }
    P{
      T{ text: `${temperature.celsius}°C = ` }
      T{ text: `${(temperature.celsius * 9/5) + 32}°F` }
    }
  }
}
  • Declarative design
  • Modular UI components
  • Seamless javascript/typescript logic integration
  • Intuitive state handling

WHY LIVE ELEMENTS?

A language extension to javascript and typescript that that lets you build front-end user interfaces without using XML-like languages.

Live Elements brings 2 big advantages:

Reducing Complexity

  • Blends seamlessly with javascript
  • Substitutes xml & html, you don't need to switch between js and xml-like syntax.
  • Language-level data binding and simplified state management
  • Less lines of code

Improving Modularity

  • Easy to create reusable components
  • Web packages come with a range of pre-built, customizable components that you can adapt to you needs
  • Front-end components can be assembled into entire pages in seconds

LEARNING LIVE ELEMENTS

Since Live Elements is build on top of javascript, you don't have to learn an entire programming language from scratch. Live Elements doesn't aim to rethink for loops or variable declarations, instead it aims to add concepts with significant impact.

You can start either by learning about the language, or by going through practical guides.

EXAMPLES

You can try all the examples below directly in the playground

Hello world example.

Live Elements as a markup language.

Creating a reusable card component.

A simple counter.

A nested state (Subscription) inside another (Service).

A simple dynamic to-do list.

UI components: Styled markup language.

import live-web.dom
import live-elements-web-server.view

// component Hello extends PageView
component Hello < PageView{
  // create <div>
  Div{ 
    // create <h1>Hello from Live Elements</h1>
    H1`Hello from Live Elements`
  }
}
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style

// component Markup extends PageView
component Markup < PageView{
  // link to index.css
  static any[] use = [ScopedStyle{ src: './index.css'}]

  // <article>
  Article{
    // <h1>This is a heading</h1>
    // similar to calling  "new H1('This is a heading')" in js
    H1`This is a heading`

    // <p>This is a paragraph</p> 
    P`This is a paragraph`

    P{  // call new T(...), new B(...), new T(...), ...
      T`This is a second paragraph, with `B`bold`T` and `I`italic`T` text.
        This paragraph is written on multiple lines.`
    }

    // <h2>Subheading</h2>
    H2`Subheading`

    // <ul>
    Ul{
      Li`First list item` // <li>
      Li`Second list item` // <li>
    }
    
  } // </article>
}
import live-web.dom
import live-elements-web-server.view
import live-elements-web-server.style

// reusable card component, extends Li
// Li is the same as the html <li> tag
component Card < Li{
  id: card
  classes: ['card']

  // define the 3 configurable properties
  string name: ''
  string description: ''
  string color: ''

  
  H2{ classes: ['name']  // add <h2> as child to <li>
    T{ text: card.name } 
  }
  P{ // add <p> as child to <li>
    T{ text: card.description } 
  }
  // add <section> as child to <li>
  Section{ classes: ['card-color', card.color] }
}

// page component displaying the cards
component ReusableComponents < PageView{
  
  // import index.css
  static any[] use = [ScopedStyle{ src: './index.css'}]
    

  Div{ classes: ['h-full', 'center']
    Ul{ classes: ['cards']
      // create 3 cards
      Card{ name: 'Purple' description: 'Description for purple card' color: 'purple' }
      Card{ name: 'Yellow' description: 'Description for yellow card' color: 'yellow' }
      Card{ name: 'Blue' description: 'Description for blue card' color: 'blue' }
    }
  }
}
body{
  font-family: sans-serif;
}
ul.cards {
  list-style: none;
  padding: 0;
  display: flex;
  gap: 20px;
}

ul.cards li{
  width: 250px;
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
  background: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
}

section.card-color {
  width: 150px;
  height: 150px;
  border-radius: 15px;
}

.purple { background-color: rgb(171, 27, 171); }
.yellow { background-color: rgb(249, 227, 31); }
.blue { background-color: rgb(20, 148, 245); }
import live-web.dom
import live-web.model
import live-elements-web-server.view

// define a Counter state component
component Counter < State{
  number counter : 0
}

component CounterView < PageView{
  id: counterView // id to reference inside the hierarchy

  // create the counter
  Counter state: Counter{}

  Div{ classes: ['center', 'full']
    Div{ classes: ['counter']
      P{ 
        // The text property will now be bound to the counterView.state.counter
        // Whenever counterView.stat.counter changes, the text will change as well
        T{ text: 'Counter value: ' + counterView.state.counter } 
      }
      // as the 2 buttons update the state, the views will update as well
      Button{ on click: () => {counterView.state.counter++} T`+` }
      Button{ on click: () => {counterView.state.counter--} T`-` }
    }
  }
}
import live-web.dom
import live-web.model
import live-elements-web-server.view

component Subscription < State{
  string name: ''
  number price: 0
}

// Service component with 2 subscription types
component Service < State{
  Subscription basic: Subscription{ name: 'Basic' price: 1.00 }
  Subscription advanced: Subscription{ name: 'Advanced' price: 5.00 }
}

component ServiceView < PageView{
  id: serviceView

  // main state, the service component with 2 subscriptions
  Service state: Service{}

  Div{ classes: ['center', 'full']
    Div{ 
      H2`Subscription prices`
      Ul{
        Li{ // basic subscription
          T{ text: serviceView.state.basic.name + ':' }
          B{ T{ text: serviceView.state.basic.price.toFixed(2) + '$' }} 
        }
        Li{ // advanced subscription
          T{ text: serviceView.state.advanced.name + ': ' }
          B{ T{ text: serviceView.state.advanced.price.toFixed(2) + '$' }} 
        }
      }
      P{
        Button{
          on click: () => {// make subscriptions more expensive
            serviceView.state.basic.price += 1
            serviceView.state.advanced.price += 1
          }
          T`Increment Prices`
        }
        Button{
          on click: () => { serviceView.state.advanced.name  = 'Premium' }
          T`Upgrade to premium`
        }
      }
    }
  }
}
import live-web.dom
import live-web.model
import live-elements-web-server.view
import live-elements-web-server.style

// Item contains a label and isChecked flag.
component TodoItem < State{
  string label: ''
  bool isChecked: false
}

// TodoList contains a list of TodoItem's.
component TodoList < State{
  Array<TodoItem> items: []

  fn addItem(label:string){ 
    // when a new item is added, the new list is assigned to the items
    // property, triggering a new update to the Ul view
    this.items = this.items.concat([TodoItem{ label: label }])
  }
}


component TodoListView < PageView{
  id: todoListView

  static any[] use = [ScopedStyle{ src: './index.css'}]
  
  TodoList state: TodoList{}

  Div{ 
    Div{ 
      Ul{
        // this will also bind to todoListView.state.items
        children: todoListView.state.items.map(item => Li{
          classes: item.isChecked 
            ? ['strike', 'pointer', 'li-square'] 
            : ['pointer', ['li-square']]
          on click: () => { item.isChecked = true }
          
          T{ text: item.label }
        })
      }
      P{
        Input{ id: newItem type: 'text' placeholder: 'item...' }
        Button{
          on click: () => { 
            // add new item
            todoListView.state.addItem(newItem.currentValue)
            newItem.resetValue()
          }
          T`Add`
        }
      }
    }
  }
}

input{
  font-family: sans-serif;
  font-size: 14px;
}
.strike{
  text-decoration: line-through;
}
.li-square{
  list-style-type: square;
}
.pointer{
  cursor: pointer;
}
import live-web.dom
import live-web-view.content
import live-web-view.layout
import live-elements-web-server.view
import live-elements-web-server.style

// component StyledMarkup extends PageView
component StyledMarkup < PageView{

  static any[] use = [
  // import styles from live-web-view.content.Content
  Content,
  // import styles from live-web-view.layout.ColLayout
  ColLayout,
  // import index.css
  ScopedStyle{ src: './index.css'}
  ]

  Content{
  // similar as calling "new H1('This is a heading')" in js
  H1`This is a heading`

  // the same as calling "new P('This is a heading')" in js
  P`This is a paragraph`

  P{  // call new T(...), new B(...), ...
    T`This is a second paragraph, with `B`bold`T` and `I`italic`T` text.
    This paragraph is written on multiple lines.`
  }

  Ul{
    Li`First list item`
    Li`Second list item`
  }

  ColLayout{
    Col{ P`Paragraph in column one` }
    Col{ P`Paragraph in column two` }
  }
  }
}

SOURCE

Live elements is split into 2 main repositories:

Live Elements Web

Repository with web packages for live elements, including:

  • live-elements-core
  • live-elements-web-cli
  • live-elements-web-server
  • live-web

Live Elements Js Compiler

The compiler package, which compiles live elements-code to javascript.

CONTRIBUTING

Previously, issues were managed privately on GitLab as it was easier to handle at the time. However, to simplify contributions and collaboration, everything has been moved to GitHub. From now on, all issues will be tracked on GitHub, so feel free to suggest features, report bugs, or share your ideas.