yarn add clay-view
npm install clay-view
To use Clay you simply import the <clay-view>
component and bind your Schema
with v-model
.
<template>
<clay-view v-model="schema"/>
</template>
<script>
import {ClayView} from 'clay-view';
export default {
components: {ClayView},
data(){
return {
schema: {
namespace: 'someUniqueNamespace',
component: 'div'
},
}
}
}
</script>
This will render be rendered as :
<div></div>
The namespace
is a required unique key that must be present in every CNode. It is later used to get and identify the data of this CNode
{
namespace: string
}
The component
is a required key that tells the CNode witch HtmlTag
or VueComponent
it is representing.
For a simple HtmlTag
simply put the tag name in.
// Will render an <div>
const DivCNode={
namespace: 'key',
component: 'div',
}
// Will render an <button>
const ButtonCNode={
namespace: 'key',
component: 'button',
}
To use an Vue Component you have four different choices.
The first one is you simply use a global registered VueComponent
. In this case you put the component tag in the key.
Vue.component('my-global-component', {/* ... */});
// Will render the registered <my-global-component> Vue Component
const GlobalComponentCNode={
namespace: 'key',
component: 'my-global-component',
}
The second one is you tell the <clay-view>
which VueComponents
it should locally register.To do so add an object with all the VueComponents
to the components
prop at the <clay-view>
.
Now you can simply use the component tag name as you would with a global Component.
<template>
<clay-view v-model="schema" :components="components"/>
</template>
<script>
import {ClayView} from 'clay-view';
import MyLocalComponent from 'MyLocalComponent.vue';
export default {
components: {ClayView},
data(){
return {
schema: {
namespace: 'someUniqueKey',
component: 'MyLocalComponent'
},
components: {MyLocalComponent}
}
}
}
</script>
The third option is to use put your imported component directly into the CNode.
import MyLocalComponent from 'MyLocalComponent.vue';
// Will render MyLocalComponent Vue Component
const GlobalComponentCNode = {
namespace: 'key',
component: MyLocalComponent,
}
The last option is to use inlineComponents
. You can simply define your component inside the CNode
with an template.
For this to work you need an build of vue which includes the runtime compiler
// Will render <div>My inline component</div> Vue Component
const GlobalComponentCNode = {
namespace: 'key',
component: {
template: '<div>My inline component</div>'
/* all the other vue Component Stuff */
},
}
The children
lets you define which children an CNode has. It accepts ether an array or a single
CNode. If your
CNodeis representing an
componentwith
slotsyou can put them here as well with the
slot` key for named Slots.
// Will render <div><span></span></div>
const ChildrenCNode = {
namespace: 'key',
component: 'div',
children: {
namespace: 'childKey',
component: 'span'
}
}
The text
key is basically the v-text
directive from vue.
If the
CNode
has atext
andchildren
key at the same time thetext
will overwrite thechildren
// Will render <div>Some Text</div>
const TextCNode = {
namespace: 'key',
component: 'div',
text: 'Some Text'
}
The html
key is basically the v-html
directive from vue.
If the
CNode
has ahtml
,text
andchildren
key at the same time thehtml
will overwrite thetext
andchildren
// Will render <div><span>Some Text</span></div>
const HtmlCNode = {
namespace: 'key',
component: 'div',
html: '<span>Some Text</span>'
}
The if
key is basically the v-if
directive from vue.
// Will render nothing
const TextCNode = {
namespace: 'key',
component: 'div',
if: false
}
The show
key is basically the v-show
directive from vue.
// Will render <div style="display:none;"></div>
const TextCNode = {
namespace: 'key',
component: 'div',
show: false
}
The for
key is basically the v-for
directive from vue. To use it put an array or object in this key. The CNode will than
get looped for every item inside the array or object. You can access the value by binding it to <loopNamsepac>.$for.value
the current index by <loopNamsepac>.$for.index
and if you loop an object the current key by <loopNamsepac>.$for.key
Note that as with the normal v-for you should specify a key and you can not loop the root element
// Will render <div><span data-index="0">item_1</span><span data-index="1">item_2</span></div>
const loopableCNode = {
namespace: 'root',
component: 'div',
children: {
namespace: 'looping-child',
component: 'span',
for: { key1: 'item_1', key2: 'item_2' },
':key': 'looping-child.$for.key',
attrs: {
':data-index': 'looping-child.$for.index',
},
':text': 'looping-child.$for.value',
},
};
The class
key let you add css classes to an CNode. It follows the same syntax as a normal bound class in vue. So you can use
an string
, array
orobject
to define your classes
// Will render <div class="someClass"></div>
const ClassStringCNode = {
namespace: 'key',
component: 'div',
class: 'someClass'
}
// Will render <div class="someClass someMoreClass"></div>
const ClassArrayCNode = {
namespace: 'key',
component: 'div',
class: ['someClass','someMoreClass']
}
// Will render <div class="someClass"></div>
const ClassObjectCNode = {
namespace: 'key',
component: 'div',
class: {
'someClass': true,
'notPresentClass': false,
}
}
The style
key lets you add inline Styles with the Object Syntax from vue.
// Will render <div style="color:red"></div>
const StyledCNode = {
namespace: 'key',
component: 'div',
style: {
color: 'red'
}
}
The attrs
key lets you add normal Html attributes as object
// Will render <div id="foo"></div>
const StyledCNode = {
namespace: 'key',
component: 'div',
attrs: {
id: 'foo'
}
}
The props
key lets you add props to components
<!--PropComponent.vue-->
<template>
<div v-text="myProp" />
</template>
<script>
export default {
props: ['myProp']
}
</script>
import PropComponent from './PropComponent.vue'
// Will render <div>foo</div>
const StyledCNode = {
namespace: 'key',
component: PropComponent,
props: {
myProp: 'foo'
}
}
The domProps
key lets you add DOM Properties
to the CNode
.
// Will render <input value="foo" />
const DomPropsCNode = {
namespace: 'key',
component: 'input',
domProps: {
value: 'foo'
}
}
The slot
key lets you define a Slot name like v-slot
.
const SlotCNode = {
namespace: 'key',
component: 'div',
slot: 'slotName'
}
The key
key lets you define a key for a CNode. Important for v-for
loops.
const SlotCNode = {
namespace: 'key',
component: 'div',
key: 'someKey'
}
The ref
key is the same as the vue ref.
const SlotCNode = {
namespace: 'key',
component: 'div',
ref: 'someRef'
}
If you are applying the same ref name to multiple elements in the CNode. This will make $refs.myRef
become an array
const SlotCNode = {
namespace: 'key',
component: 'div',
refInFor: false
}
With the on
key you can specify event handlers. They work the same as here explained
const EventCNode = {
namespace: 'key',
component: 'button',
on: {
click: () => console.log('Hurray')
}
}
Same as the on
only for Native Events more
const EventCNode = {
namespace: 'key',
component: 'button',
nativeOn: {
click: () => console.log('Hurray')
}
}
So because Clay is meant to be used outside auf JavaScript as well it comes with its own Binding system that don't relay
on JavaScript functions, Objects or variables. Each CNode
can have its own data
,similar to the data
key in a Vue Component,
that is reactive and can be used inside of the CNode
.
To register the data
you add a data
key to your CNode
.
const DataCNode = {
namespace: 'key',
component: 'div',
data: {
myReactiveData: 'SomeData'
}
}
Now you can bind this data to our other keys. For example the text
:
const DataCNode = {
namespace: 'ownNamespace',
component: 'div',
':text': 'ownNamespace.myReactiveData',
data: {
myReactiveData: 'SomeData'
}
}
As you can see we tell clay, by prefixing the key with a :
, that we want to bind this key to an different value.
Then we specify the key of the value inside of our data
and prefix it with our namespace
separated by .
. The Rendered output will now be:
<div>SomeData</div>
If we want to get values from an more nested object we can use dot notation to get them:
const DataCNode = {
namespace: 'ownNamespace',
component: 'div',
':text': 'ownNamespace.myReactiveData.nested.inside',
data: {
myReactiveData: {
nested: {
inside: 'Foo'
}
}
}
}
<div>Foo</div>
Because of the namespace we can also access data from a parent inside of an child:
const DataCNode = {
namespace: 'parentNamespace',
component: 'div',
data: {
myReactiveData: 'Foo'
},
children: {
namespace: 'childNamespace',
component: 'span',
':text': 'parentNamespace.myReactiveData',
}
}
<div><span>Foo</span></div>
You can not use the child data inside of the parent!
You cant bind all of the Keys of an CNode but here is an list of all the keys that allow binding:
const DataCNode = {
namespace: 'namespace',
component: 'div',
':text': 'namespace.myReactiveData',
':html': 'namespace.myReactiveData',
':class': 'namespace.myReactiveData',
':if': 'namespace.myReactiveData',
':show': 'namespace.myReactiveData',
':key': 'namespace.myReactiveData',
':for': 'namespace.myReactiveData',
'attrs': {
':id' : 'namespace.myReactiveData'
},
'style': {
':color' : 'namespace.myReactiveData'
},
'props': {
':myProp' : 'namespace.myReactiveData'
},
'domProps': {
':myDomProp' : 'namespace.myReactiveData'
},
'on': {
':click': 'namespace.myReactiveFunction'
},
'nativeOn': {
':click': 'namespace.myReactiveFunction'
},
data: {
myReactiveData: 'data',
myReactiveFunction: () => {}
}
}
Clay allows you to use Scoped Slots which enables you to control and use pretty much everything from, for example your backend. If you don't know what Scoped Slots are read this So how does it work: First an normal Vue Example that we then translate to Clay.
<template>
<scoped-slot-compoent>
<template v-slot="props">
<button @click="props.scopedFunction">{{props.scopedValue}}</button>
</template>
</scoped-slot-compoent>
</template>
<script>
import ScopedSlotComponent from 'ScopedSlotComponent.vue';
export default {
components: {ScopedSlotComponent}
}
</script>
As you can see we are using a ScopedSlotComponent
that provides us with an function an a value. We than use it to bind
the function to an click event on a button and the value as an label.
Translated in a CNode it would look like this:
import ScopedSlotComponent from 'ScopedSlotComponent.vue';
const scopedSlotCNode = {
namespace: 'key',
component: ScopedSlotComponent,
scopedSlots: {
default: {
key: 'props',
content: { /** **/ }
}
}
}
First we concentrate on this syntax. To specify that a CNode has an Scoped Slot we use the scopedSlots
key.
This key except an Object of ClayScopedSlot
objects. The key of each ClayScopedSlot
object is the name of the Slot.
So in this case because we didn't specify any name we use the default
.
The key
inside of the ClayScopedSlot
is our unique identifier to get the data from our scoped slot. You will see how this
work in a minute.
The content
key inside of the ClayScopedSlot
except an CNode
again. This CNode has now access to the ScopedSlot data.
In action it looks like this.
import ScopedSlotComponent from 'ScopedSlotComponent.vue';
const scopedSlotCNode = {
namespace: 'slotNamespace',
component: ScopedSlotComponent,
scopedSlots: {
default: {
namespace: 'child',
component: 'button',
':text':'slotNamespace.$slot.default.scopedValue',
onNative: {
':click': 'slotNamespace.$slot.default.scopedFunction'
}
}
}
}
As you can see we tell clay to bind the text
and the click
to an value. But because we want to use the data
from our scoped slot
we prefix it with the namespace
from the CNode
with the scopedSlot
and add the $slot
and the name of the slot, in this case default
.
Now we can use out dot notation to get the data from the scopedSlot
.
We can even nest scoped slots deeply and every Child will have access to all of his parent Scoped Slots.
import ScopedSlotComponent from 'ScopedSlotComponent.vue';
import OtherScopedSlotComponent from 'OtherScopedSlotComponent.vue';
const scopedSlotCNode = {
namespace: 'rootSlot',
component: ScopedSlotComponent,
scopedSlots: {
default: {
namespace: 'childSlot',
component: OtherScopedSlotComponent,
':class': 'rootSlot.$slot.default.scopedValue',
scopedSlots: {
default: {
namespace: 'nestedChild',
component: 'button',
':text': 'childSlot.$slot.default.scopedValue',
onNative: {
':click': 'rootSlot.$slot.default.scopedFunction'
}
}
}
}
}
};
There are use cases where it is quit handy to do js logic inside of an template. For example to hide and show a dropdown
on a button click. Clay gives you the option to use JS code as a bound value. This Function is disabled by default because
of potential security issues if it is used carelessly. To activate pass a config as prop to the <clay-view>
with enableJsExecution: true
.
<clay-view :schema="schema" :config="{enableJsExecution: true}"></clay-view>
To use Js in the schema now you bind a value and prefix the value wit |>
the following string will be then interpreted as JS Code.
The Data that you can normally access with the normal bind syntax is also available in the scope.
const scopedSlotCNode = {
namespace: 'container',
component: 'div',
data: {
open: false,
},
children: [
{
namespace: 'container',
component: 'button',
text: 'Click me',
on: {
':click': '|> () => container.open = !container.open '
}
},
{
namespace: 'container',
component: 'div',
text: 'Hidden till click',
':if': 'container.open'
}
]
};