thing.json
object, even if it only
contains the identifier
of the "thing".
bone.js/py/*
code file to config the
UX.
flesh.json
template file.SchemaOrg is a micro data schema. "Micro" because it was intended for webpages as an lightweight-overcoat weaved into common HTML tags by a couple of extra attributes.
But apps and websites can be micro as well.
This paper discusses the elioflesh pattern by comparing what TEW's doing with a more "classical" approach to application development. We go into a little more detail about the classical approach that would be required by the average developer - but TEW is for content creators as well - so I'm aiming this paper at technical minded non-devs - and therefore some waffle is required.
Before the phrase "I'm a full stack developer" became something you actually needed to say or write in a CV - the whole idea blew up
A word used a lot in app development is stack. "Stacks" refer to layers of computer resources we need to install and run for any application to work. Services in stacks are seen as sitting on top of eachother - each relying on the services of the resource below it. A web client service is stacked on top of a web server service which is dependent on a database service.
In modern application development there can be a lot of services in a stack - but broadly speaking, since the beginning of time doing app development, there have been 3 main "stacks"... in brief:
But let's face it... before the phrase "I'm a full stack developer" became something you actually needed to say or write in a CV - the whole idea blew up. Cloud hosting services reduced the need to do "stackiness" in your development.
A virtual machine, preloaded with the correct runtimes and database, can be spun up by literally touching a button. Who needs to know full stack? Not you, my modest webdev friend or content creator.
Forget stacks. For sure... we will need stacks of services if we're deploying a serious app, but it doesn't matter if they are set up TEW or not. It only matters that one service provides the thing, another the bone, and it is through the flesh where we find the UX (user experience).
The point here is not to say that Classical App stacks aren't important, they are! TEW is a cult, not a framework. This paper only describes how to use frameworks and stacks TEW.
NB Out of all the 4 main parts of TEW, elioflesh is the least developed. The following may change, but it describes the proposed solution for writing a TEW application on the client side of the stack.
This is not a tutorial. It is written like a tutorial because the internet is littere... I mean... blessed with countless articles offering:
Okay... We made the last one up but there is a stack for bootstrap+javascript. Just saying.
What follows, missing the specificities, is how a [TODO app, TEW stack] would be written for comparison.
Create a folder for your app.
Add three files. thing.json
bone.js
flesh.json
Optionally add a sin.scss
stylesheet settings file.
Edit files.
Tell the client it's finished.
These files lean more toward config than code... TEW is about bundling routine code into libraries and calling those standard endpoints "off-the-back" of configuration.
If your app is struggling to contain all your functionality in those three files, it's probably not a TEW solution you need. TEW doesn't pretend to be everything - we are very clear: YEW is best for micro apps running on top of a single, manageable chunk of data.
thing.json
This is covered by eliothing dogma detail here...
The file should be thing.json
(or thing.js
or
thing.py
or something else if you want to have code
dynamically build your thing (although an inflateT
endpoint
is better because it can be reused by other people as an endpoint) but it
should only return a single data package meeting the
design_pattern).
Your data may already be in a database somewhere. There may be no data. But we must insist you put inside the repo folder of your app at least a summary of the thing initializing the app.
thing.json
Examples# thing.json
{ "identifier": "honeymoonPictures" }
inflateT
The full thing from a Wikipedia article
# thing.json
{
"identifier": "List_of_birds_of_Colombia",
"mainEntityOfPage": "Collection",
"url": "https://en.wikipedia.org/wiki/List_of_birds_of_Colombia"
}
Because the app is all about getting the user to start adding content.
# thing.json
{
"identifier": "myNigerianStampCollection",
"name": "A Year Collecting Stamps in Nigerian",
"mainEntityOfPage": "Collection",
"potentialAction": "collect first stamp"
}
Because this (imaginary app the author just made up) "readonly" app is all about getting AMAZING content to a user.
# thing.json
{
"identifier": "myGuideToParis",
"alternateName": "You won't BELIEVE what I saw in Paris",
"name": "A Week In Paris",
"mainEntityOfPage": "TouristTrip",
"touristType": "I am the absolutely worse type period . period . period",
"description": "I was supposed to meet this girl, but she didn't show up...",
"ItemList": {
"itemListElement": [
{
"identifier": "Day1",
"name": "Eiffel Tower",
"mainEntityOfPage": "TouristDestination",
"disambiguatingDescription": "Can you BELIEVE it?! I'm here."
},
{
"identifier": "Day2",
"name": "Eiffel Tower",
"mainEntityOfPage": "TouristDestination",
"disambiguatingDescription": "Don't spit over the side, apparently."
},
{
"identifier": "Day3",
"name": "Eiffel Tower",
"mainEntityOfPage": "TouristDestination",
"disambiguatingDescription": "Finally got to the top!"
},
{
"identifier": "Day4",
"name": "Eiffel Tower",
"mainEntityOfPage": "TouristDestination",
"disambiguatingDescription": "I really like it here."
},
{
"identifier": "Day5",
"name": "Eiffel Tower",
"mainEntityOfPage": "TouristDestination",
"disambiguatingDescription": "Massive Queue at Louves."
},
{
"identifier": "Day6",
"name": "Eiffel Tower",
"mainEntityOfPage": "TouristDestination",
"disambiguatingDescription": "ticketboothgirl has a thing for me.",
"ItemList": {
"itemListElement": [
{
"identifier": "ticketboothgirl",
"Person": {
"telephone": "a secret. get your own ticketboothgirl"
}
}
],
"numberOfItems": 1
}
},
{
"identifier": "Day7",
"name": "Eiffel Tower",
"mainEntityOfPage": "TouristDestination",
"disambiguatingDescription": "Time to kill. Missed my flight."
}
],
"numberOfItems": 7
}
}
Again, it's incumbent upon me in this paper to emphasize that a "thing" is data. Only data. No logic.
bone.js
bone.js
or bone.py
or some other runtime.
While eliobones has all the endpoints,
elioflesh needs to know which endpoints to include in the
app. bone.js
can also be used to add "inline"
endpoints, filters, and to convert SchemaOrg properties
to dynamic fields.
bone.js
is about as close to Middleware as
we get. It's not a Server and it's not an
endpoint.
Think of bone.js
as a handy clientside script which will
allow you, as a developer, a quick way to inject logic into the app - for
when an eliobones endpoint is overkill.
When accessing data for the UX layer, elioflesh will
first check bone.js
. In this way, bone.js
gets
to override thing.json
.
thing.json
, in this example, has a
name
property, but in TEW's design_pattern it is
reserved for system use. But your app must show a different name to it.
# thing.json
{
"name": "Reserved by design_pattern",
"alternateName": function (thing) {
return thing.Collection.alternateName
},
"Collection": {
"name": "The Real Name of my Nigerian Stamp Collection"
},
"ItemList": {
"name": "A list of all my Nigerian Stamps",
"collectionSize": function (thing) {
return thing.ItemList.itemListElement.filter(
function (t) {
return t.about==="NigerianStamps"
}
).length
}
}
}
As the design_pattern explains, one option is to put the name in
a subtype, as above in Collection
. The app will have UX to
show all the properties and subtypes from subtypes by default. But if you
still want your app to use the Collection.name
,
instead of name
.
// bones.js
module.exports = {
{
name: function (thing) {
return thing.Collection.name
},
alternateName: function (thing) {
return thing.Collection.alternateName
}
}
Is the next example we override whatever static value was stored against
Collection.collectionSize
in the "thing" data, and
instead add a function to dynamically calculate the size.
// bones.js
module.exports = {
{
Collection: {
collectionSize: function (thing) {
return thing.ItemList.itemListElement.filter(
function (t) {
return t.about==="NigerianStamps"
}
).length
}
}
}
TEW insists that all related records are stored in the same place -
ItemList
. It's kinda our thing.
But we recognize many SchemaOrg fields are, in fact,
lists. One solution is to turn the field into a dynamic
list which filters what it needs from ItemList
-
contextualising the appropriate items:
// bones.js
module.exports = {
{
Recipe: {
recipeInstructions: function (thing) {
return.ItemList.itemListElement.filter(
function (t) {
return t.hasOwnProperty("Recipe")
}
)
}
}
}
You might challenge this approach by insisting the danger of doing it when
two different Recipe
s are listed... to which we would retort
about the danger of creating one app for two recipes when two apps for
each recipe is more TEW.
Since the thing's list contains everything the user
needs, we can also add SearchAction
things to the bone's
list that the app needs, like filters.
// bones.js
module.exports = {
ItemList: {
itemListElement: [
{
identifier: "onlyParrots",
mainEntityOfPage: "SearchAction",
query: { Taxon: { hasDefinedTerm: "Parrot" } }
},
{
identifier: "upComingBirdWatchingCourses",
mainEntityOfPage: "EducationEvent",
query: { EducationEvent: { teaches: "BirdWatching" } }
}
]
}
}
elioflesh will need to look for
SearchAction
s in bones.js
when the app is
rendered, and add these filter options to the
ListItem component.
Since the thing's list contains everything the user
needs, we can also add ItemListOrderType
things to the
bone's list that the app needs, like filters.
// bones.js
module.exports = {
ItemList: {
itemListElement: [
{
identifier: "theSoonerIGetOutOfHereTheBetter",
mainEntityOfPage: "ItemListOrderType",
itemListOrder: "ItemListOrderAscending",
query: "Flight.departureTime"
},
{
identifier: "theCheaperTheBetter",
mainEntityOfPage: "ItemListOrderType",
itemListOrder: "ItemListOrderAscending",
query: "Flight.offers"
}
]
}
}
elioflesh will need to look for
ItemListOrderType
s when the app is rendered, and add these
sort options to the ListItem component.
sort options and filter options can be nested TEW to produce groups:
// bones.js
module.exports = {
ItemList: {
itemListElement: [
{
identifier: "theSoonerAndMoreLuxuriousIsBetter",
mainEntityOfPage: "ItemListOrderType",
ItemList: {
itemListElement: [
{
identifier: "soonerIsBetter",
mainEntityOfPage: "ItemListOrderType",
itemListOrder: "ItemListOrderAscending",
query: "Flight.departureTime",
},
{
identifier: "moreLuxuriousIsBetter",
mainEntityOfPage: "ItemListOrderType",
itemListOrder: "ItemListOrderDescending",
query: "Flight.offers",
},
{
identifier: "onlyParrots",
mainEntityOfPage: "SearchAction",
query: { Flight: { offers: ">$3000" } }
}
]
}
}
]
}
}
These Permits will be copied to each new record. In TEW, Permissions can be customized at a record level.
// bones.js
module.exports = {
ItemList: {
itemListElement: [
{
identifier: "readT",
mainEntityOfPage: "Permit",
Permit: {
validUntil: new Date(Date.now() + DAY * 90),
validIn: "MusicEvent",
permitAudience: "ANON",
},
},
{
identifier: "readT",
mainEntityOfPage: "Permit",
Permit: {
validUntil: new Date(Date.now() + DAY * 90),
validIn: "Flight",
permitAudience: "AUTH",
},
},
]
}
}
These Permits will be copied to each new record. In TEW, Permissions can be customized at a record level.
// bones.js
module.exports = {
ItemList: {
itemListElement: [
{
identifier: "readT",
mainEntityOfPage: "Permit",
Permit: {
validUntil: new Date(Date.now() + DAY * 90),
validIn: "MusicEvent",
permitAudience: "ANON",
},
},
{
identifier: "readT",
mainEntityOfPage: "Permit",
Permit: {
validUntil: new Date(Date.now() + DAY * 90),
validIn: "Flight",
permitAudience: "AUTH",
},
},
]
}
}
Add all the startup scripts to your list. The list will be processed in order.
// bones.js
module.exports = {
ItemList: {
itemListElement: [
{
identifier: "inflateT",
mainEntityOfPage: "InstallAction",
}
]
}
}
Nuff said? You're probably bored of these examples by now... we're just iterate the same patterns to solve different problems.
flesh.json
Very simple: flesh.json
is to be used as a simple way to
template the output:
{
"name": { "believer": "h1" },
"description": { "believer": "p" },
"status": { "believer": "dl" },
"Itemlist": {
"believer": "dl"
"itemListElement": {
"name": { "believer": "dt" },
"disambiguatingDescription": { "believer": "dd" },
}
}
}
All we need, if we use eliosin, is the tag name it will use to wrap the value in an HTML element. Don't worry -
readT
mode;
input
tags in a
form
when doing an updateT
.
flesh.json
to override all that... to use a
code
tag for a DateTime field instead of the normal
p
, for instance.
Bringing all this together will vary depending on your runtimes, programming language, or framework.
Here is an example app in JS:
import { elioApp, ElioThing } from "theElioWay"
import thing from "./thing.json"
import bones from "./bones.js"
import flesh from "./flesh.json"
let { identifier } = envvars
let ChristmasList = new ElioThingComponent({
identifier: identifier,
mainEntityOfPage: "Event",
ItemList: { itemListElement: ["Person", "Product"] }
}
)
let app = elioApp(ChristmasList, thing, bones, flesh)
app.run("localhost", 5000)
In Rust:
rustRust { elioApp, ElioThing } brust "theElioWay"
rustRust thing brust "thing.json"
rustRust bones brust "bones.rust"
rustRust flesh brust "flesh.json"
rusty { identifier } = envvars
rusty ChristmasList = rust ElioThingComponent({
identifier: identifier,
mainEntityOfPage: "Event",
ItemList: { itemListElement: ["Person", "Product"] }
}
)
rustyRusty app = elioApp(ChristmasList, thing, bones, flesh)
app.run("localhost", 5000)
Just joking - We don't know Rust - didn't even look.
The point we're making is that TEW doesn't dictate how your particular TEW app will be pulled together - but given the "Three File" design_pattern it's likely to be as simple as this. We hope someone will do Rust TEW.
Nothing above is dogma per se. You can probably find an infinite number of
ways to combine flesh
, thing
and
bones
in a TEW app.
bones.js
flesh.json
and a
thing.json
.
bones.js
for
instance, but TEW apps are Micro Apps, and it is optimal if all the
config needed by your app is kept in these three files + endpoints.
bones.js
flesh.json
and a
thing.json
are objects which conform to TEW's
design_pattern - having only properties and subtypes from
SchemaOrg.