Skip to content

exdestroyer/ef.js

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ef.js

GitHub license npm Build status FOSSA Status

(maybe) An elegant HTML template engine & basic framework

ef.js is a static template framework for browsers, with which you can write your UI without concerning about the logic, or writing logic without concerning about the UI.

ef.js also provides a simple template-engine which helps you create component modules with data binding at ease, but you can also use your favourite template-engine which is compatible with ef.js's AST.

Official Website (WIP)

Demo:

Related projects:

Community projects:

Implementation in other languages:

  • ef.qt Writing Qt applications using the concept of ef

CDN

CDNJS | jsDelivr | UNPKG

For dev versions:

CDNJS | jsDelivr | UNPKG

Usage

import { create, onNextRender, inform, exec, bundle, setParser, parseEft, t, version } from 'ef.js'
// or you can use import * as ef from 'ef.js'

version // Version string of ef.js

setParser(someparser) // Change the default parser for ef.js so you can use a different type of template
parseEft('Your awesome template') // Get ef.js ast using default parser

const templateString = 'Your awesome template'
const ast = [/* AST which supported by ef */]

const data = {
  $data: {/* Binding data */},
  $methods: {/* Binding methods */}
}

const template1 = create(template)
const template2 = create(ast)
const template3 = t`
>component1
>component2
.Your awesome template
`

const component1 = new template1() // Create a component without data
const component2 = new template2(data) // Create a component and then updates it's data
const component3 = new template3(data, {component1, component2}) // Use component1 and component2 as custom components in template3

onNextRender(callback) // Cache operations to execute on next render
inform() // Tell ef to cache operations **USE WITH CARE**
exec() // Tell ef to execute all cached operations **USE WITH CARE**
exec(true) // Force execute cached operations **USE WITH CARE**
bundle(callback) // Wrapper for inform() and exec()

component1.$data.something = 'something new' // Update the binding data 'something'
component2.$methods.someMethod = ({e, value, state}) => {
  state.$data.something = 'something new'
  console.log('Event target', e.target)
  console.log('Value passed', value)
} // Update binding method

const logData = val => console.log('Subscribed data updated:', val)
component1.$subscribe('info.data', logData) // Observe a value
component1.$unsubscribe('info.data', logData) // Stop observing a value

component1.$update(data) // Update the whole component state

component1.$refs // Get all referenced nodes

component1.mountingPoint = component2 // Mount component2 to 'mountingPoint' on component1
component1.mountingPoint = null // Detach the mounted component

component1.listMP.push(componet2) // Mount component2 to list 'listMP' mounting point on component1

component1.$mount(...) // Mount method called by ef when trying to mount
compinent1.$umount() // Unmount from parent
component1.$destroy() // Destroy the component when not needed for more memory

ef.js template language (EFML) format

EFML is a completely logic-free template language. Just like HTML, there you can do nothing about logic, but EFML provides a easy starting point for data binding and events handling.

Also EFML is the first language that can be parsed into the AST which ef supports.

Note: EFML is very strict to indents. Wrong indents could lead to a parsing error.

Here is an example.

Tree structure
Lines not started with >#%@.|+- are considered as comments
The escape character of EFML is '&', for prevention of conflicts with js escapes.
Except for changes of the characters, all the usage should remain the same on all versions.
this is a comment

Lines starting with '>' stand for a new tag
>div
  Lines with exactly one indent after a tag definition are considered to be all things belongs to the defined tag

  Lines starting with '#' stand for attributes
  Mustaches are used for binding data
  Contents inside mustaches after '=' stand for the default value for this binding
  Contents without mustaches stand for static data,
  which means that you can not modify them through ef.js
  #class = {{class = some class name}}
  #style = {{attr.style = background: #ECECEC}}
  #id = testdiv
  #some-attr = some text
  #content

  Lines starting with '%' stand for properties
  %title = Welcome, {{name}}
  %anotherProperty = text

  Lines starting with '@' stand for events
  Contents after ':' are considered as value to be passed to the handler
  @click = updateInfo:{{binding.value}} and static value
  modifier keys now can bind easily
  @mousedown.shift.alt.ctrl.meta = select
  bind to keys is also easy
  @keypress.13 = submit
  use '.prevent' to `preventDefault`, '.stop' to `stopPropagation`, '.stopImmediate' to `stopImmediatePropagation`
  @keydown.8.prevent.stop = stopbackspace
  use '.capture' to capture an event
  @submit.capture.stopImmediate = submit

  Lines starting with '.' stand for text nodes
  .Name: {{name}}&nJob: {{job}}
  >pre
    Lines starting with '|' stand for multiline text
    |Line 1
    |Line 2
    |Line 3
  >br

  Lines starting with '-' stand for single node mounting point
  -node1

  Lines starting with '+' stand for multi node mounting point
  +list1

  '.' after a tag name stand for class names for this tag
  >p.some.{{binding.class}}.class.names

    '#' at the end of a tag name stand for the reference name of the node
    Mustaches after a dot will bind to 'class' automatically
    >span.{{emergency = emergency}}#notice_box
      .Notice: {{notice}}
    .some text

For standalone eft parser see eft-parser.

Fragments

After version 0.9.0, ef.js now supports fragments, which requires eft-parser to be v0.9.0 and above. A normal template could only have one entry tag, while fragment templates can have multiple, even mounting poings can be put at root level:

>div
  .A root level tag
-rootLevelMountingPoint
>p
  .Another root level tag
+rootLevelListMountingPoint
.Root level text node

You can use them just like normal templates, behaviors are always the same. Also, a single text node will be treated as fragments as well.

Helpers

ef.js also provides some helpers for creating Fragments and EFTextFragments, or transform almost anything into an ef component.

// Creats a fragment containing given ef components, non ef components will be automatically transtormed into ef components.
new ef.Fragment(Array<Any>)
// Creats a single `TextFragment` which contains only the given text. Text on `EFTextFragment` components can be modified with `.text` property.
new ef.EFTextFragment(string)
// Converts almost anything into an ef component
ef.toEFComponent(Any)

Attribute Mapping

Data on ef.js components are not always that easy to access, so since v0.10.4, a stable version of attribute mapping helper is bundled with ef.js. For documents, please refer to the comments for now. It would be extremely useful when using with custom components and JSX.

Custom Components

ef.js can handle custom components in templates since v0.10.4. Demo on writing logic within ef template using custom component

Scope

Scoping is not done in templates. You can write your template as normal, using whatever tag name you desire for your custom component, like:

App.eft
>div#root
  >MyComponent#myComponent
  >MyOtherComponent

Then you may pass the scope in your script:

import App from 'App.eft'
import MyComponent from 'MyComponent.eft'
import MyOtherComponent from 'MyOtherComponent.eft'

const scope = {MyComponent, MyOtherComponent}
const app = new App(null, scope)

If scope is not given when initializing the component, ef will treat these custom tags as normal HTML tags.

Note that if you reference a custom component, you'll get the component instance instead of the component's DOM object:

app.$refs.root // DOM object
app.$refs.myComponent // ef component

Attributes

Attributes on custom components are mapped to component[key], single way:

App.eft
>MyComponent#myComponent
  #myAttribute = {{customAttr}}
app.$data.customAttr = 'Lorem ipsum...' // This will actually set app.$refs.myComponent.myAttribute

Properties

Properties on custom components are mappde to component.$data[key], single way:

App.eft
>MyComponent#myComponent
  %my.Property = {{customProperty}}
app.$data.customProperty = 'Lorem ipsum...' // This will actually set app.$refs.myComponent.$data.my.Property

Events

Event handling only works on custom emitted events on custom component:

App.eft
>MyComponent#myComponent
  @myEvent = handleMyEvent
app.$refs.myComponent.$emit('myEvent') // This will trigger `handleMyEvent`

Note that modifier keys are no longer able to present on custom emitted events, so dont attach modifier key on them.

Automatic Two Way Binding

Just like what ef requires HTML elements to do to get custom two way binding, a value or checked property should present on a custom component, together with an input or keyup or change event been emitted when value has been changed. When binding checked, only change event should be emitted.

App.eft
>MyComponent
  %value = {{value}}
MyComponent.etf
>input
  #type = text
  @input = handleInput
import {mapAttrs} from 'ef.js'
import App from 'App.eft'
import _MyComponent from 'MyComponent.eft'

const MyComponent = class extends _MyComponent {
  constructor(...args) {
    super(...args)
    this.$methods.handleInput = ({state}) => {
      state.$emit('input')
    }
  }
}

const app = new App(null, {MyComponent}) // $data.value will automatically updats with what was changed in MyComponent

Children

You can write custom components with children just like what you do with normal HTML elements:

>MyComponent
  >div
  >MyOtherComponent
  -mountingPoint
  +listMountingPoint

but with one requirement: the custom component that handles children should have a list mounting point or an attribute named children:

MyComponent.eft
>div.my-field-set
  >span
    .{{legend}}
  +children

JSX

ef.js now comes with JSX support since v0.9.0. Demo here.

JSX Fragments

ef.js supports JSX fragments. You can create fragments just like what you do in React:

<>
  <h1>Hello JSX!</h1>
  <MyCustomComponent>Now ef.js comes with JSX fragment support!</MyCustomComponent>
</>

Note that JSX fragments are not always the same from ef fragments. No ef bindings can be set on JSX fragments in the meantime.

With Transpilers

Babel: As documented here, you can customize your jsx pragma when using babel. For example:

{
  "presets": [
    [
      "@babel/preset-react",
      {
        "pragma": "ef.createElement", // default pragma is React.createElement
        "pragmaFrag": "ef.Fragment", // default is React.Fragment
        "throwIfNamespace": false // defaults to true
      }
    ]
  ]
}

Buble: A pull request on custom Fragment pragma has been merged but not yet properly documented. Below is a correct example:

var output = buble.transform( input, {
  ...

  // custom JSX pragma
  jsx: 'ef.createElement',
  jsxFragment: 'ef.Fragment',

  ...
}

Typing Support

HELP WANTED

ef.js now has partial experimental typing support using TypeScript flavored JSDoc, which should be compatible with TypeScript. See ef-core and ef.js.

Run a test

git clone https://github.com/ClassicOldSong/ef.js.git
cd ef.js
npm install
npm start

Then you can test it out in the opening browser window.

Build from source

git clone https://github.com/ClassicOldSong/ef.js.git
cd ef.js
npm install
npm run build && npm run prod

Then you can get the fresh-built ef.min.js in the dist folder.

Note: All debugging messages are disabled in the production version.

License

MIT

FOSSA Status

About

(maybe) An elegant HTML template engine & basic framework

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 96.7%
  • HTML 3.3%