Makes non-trivial UI components easy to read and maintain.
It has been made to abstract-away SolidJS' HyperScript to generate reactive code in GlueCodes Studio on the fly.
- It can be transformed in maintainable and scalable manner.
- Gradual learning curve, no need to learn another templating syntax (directives etc.).
- Reads sequentially as HTML while remaining readable and maintainable.
- Isn't a mix of HTML and JavaScript drawing a clear border between view and logic.
- Allows formatting texts without writing nested inline tags.
- Makes writing dynamic texts easier with no need for checking whether variables are non-empty.
- Problem
- Syntax Comparison
- Installation
- Basic Usage
- Advanced Usage
- GlueDOM Syntax
- Api
- Contributing
- License
The ideal syntax for rendering DOM should mimic HTML in a way it reads sequentially from top to bottom. For single logical UI unit, there shouldn't be a need for local variable declarations or using partial functions.
Consider the two most common ways of rendering DOM; JSX and HyperScript. They work well in simple demo scenarios with no nested conditional logic. When the nested conditionals are required, you end up using a mix of logical expressions, ternary and spread operators.
Consider the following example; Write a function which renders condition-based HTML.
There is someCondition prop which needs to be truthy to display a section which contains other nested conditionals.
firstProgrammer and secondProgrammer are both optional.
Since you can't use block statements, you're forced to use unreadable mix of logical and ternary operators.
({
firstProgrammer,
secondProgrammer,
someCondition
}) => (
<div>
{someCondition
&& (firstProgrammer && secondProgrammer
? <p><bold>{firstProgrammer}</bold>, you're going to do pair-programming with {secondProgrammer}.</p>
: (firstProgrammer && !secondProgrammer
? <p><bold>{firstProgrammer}</bold>, you'll code this task by yourself.</p>
: <p>Hey man! Can you tell us your name before we give you job to do?</p>))
}
</div>
)Similarly to JSX, yet to the mix of unreadable ternary operators you're forced to add spread operator.
({
firstProgrammer,
secondProgrammer,
someCondition
}) => h('div', {}, [
...(someCondition ? [h('p', {}, [
...(firstProgrammer && secondProgrammer ? [
h('strong', {}, [
firstProgrammer
]),
`, you're going to do pair-programming with ${secondProgrammer}.`,
] : []),
...(firstProgrammer && !secondProgrammer ? [
h('strong', {}, [
firstProgrammer
]),
', you\'ll code this task by yourself.',
] : []),
...(!firstProgrammer && !secondProgrammer ? [
'Hey man! Can you tell us your name before we give you job to do?',
] : [])
])] : [])
])Here you can use block statements. When calling text, all its arguments are checked whether they are truthy and only if they are, they will be concatenated and rendered.
There is also a concept of formatters which are configured when initializing the top-most tag, and they can wrap texts inside a chosen tag and apply CSS classes on it.
In this case bold is configured to wrap props inside <strong/> tag.
Nesting is possible by simply nesting objects e.g. { bold: { italic: 'some text' } }.
({
firstProgrammer,
secondProgrammer,
someCondition
}) => (
tag('div', (props, { tag }) => {
if (someCondition) {
tag('p', (props, { text }) => {
text({ bold: firstProgrammer }, ', you\'re going to do pair-programming with ', secondProgrammer, '.')
if (!secondProgrammer) {
text({ bold: { italic: firstProgrammer } }, ', you\'ll code this task by yourself.')
}
if (!firstProgrammer && !secondProgrammer) {
text('Hey man! Can you tell us your name before we give you job to do?')
}
})
}
})
)Run:
yarn add https://ide.glue.codes/repos/df67f7a82cbdc5efffcb31c519a48bf6/core/glueDom-1.0.1.tar.gzOr:
npm i https://ide.glue.codes/repos/df67f7a82cbdc5efffcb31c519a48bf6/core/glueDom-1.0.1.tar.gz --saverenderer.js:
import React from 'react'
import { createRenderer } from '@gluecodes/glueDom'
export default createRenderer({
createDomElement: React.createElement
})component.js:
import tag from './renderer'
export default () => tag('div', (props, { text }) => {
text('hello world!')
}) renderer.js:
import React from 'react'
import { createRenderer } from '@gluecodes/glueDom'
import styles from './textFormatters.css'
export default createRenderer({
createDomElement: React.createElement,
formatters: {
bold: () => ({
tag: 'strong',
props: { className: styles.bold }
}),
italic: () => ({
tag: 'span',
props: { className: styles.italic }
})
}
})component.js:
import tag from './renderer'
export default ({
email,
firstName,
interests,
surname
}) => (
tag('p', (props, { text }) => {
text(
'You can format an email like this: "', { bold: { italic: email } },
'" and the whole sentence will appear only if email: ', email, ', firstName: ', firstName, ' and surname: ', surname, 'aren\'t empty. ',
'It can be especially useful when generating documents from ', { bold: 'dynamic' }, ' fields coming from backend.'
)
})
)Nesting
tag(tagName, (props, { component, tag, text }) => {
props.className = 'outerMostTag'
tag(tagName, (props, { component, tag, text }) => {
props.className = 'innerTag'
tag(tagName, (props, { component, tag, text }) => {
props.className = 'innerMostTag'
...
})
})
})
Assigning props to an element containing child elements or text
tag(tagName, (props, { text }) => {
props.className = 'someClass'
props.title = 'some tooltip'
...
text('some text')
})
No child elements nor text
tag(tagName, {
[props]
})
No child elements nor props
tag(tagName)
Nested Tag
tag(tagName, ...)
Components
component(reusableUiPiece, props)
Text
text(...[textChunk,])
tagNameA string that specifies the type of element to be createdpropsAn object to assign element props/attributescomponentA function to render componenttagA function to create an elementtextA function to create textreusableUiPieceA function returning reusable DOMtextChunkEither a string or an object which uses text formatters. If any chunk is empty, the whole sentence won't be rendered
createRenderer(config)createRenderer()A function to create initialtag()function based on providedconfigconfig(optional) An object containing configurationconfig.createDomElement(optional) A function to create DOM element. When specified, it should implement HyperScript-like interface/APIconfig.formattersAn object of functions. They may be used intext()to wrap given string intotagwithpropsofconfig.formatters[formatterName]() => ({ tag, props })formatterNameA string identifying a formatter which may be used intext()liketext({ [formatterName]: 'given string' })tagA string that specifies the type of element to be used for text wrappingpropsAn object of props to be set on the wrapping element
Feel free to rise issues, open PRs or contact at hello@glue.codes about any ideas/criticism.
