DEV Community

Cover image for Deno: Learn how to use Ogone
Rudy Alula
Rudy Alula

Posted on • Edited on

Deno: Learn how to use Ogone

Intro

Hey! I'm Rudy Alula, and I work as Front-end Developer for Stanley Robotics, which is solding the first outdoor robot parking vallet in automobile industry.

with this article, you're about to learn more on Ogone but first you need to understand what's happening and to README

Those who are familiar with Front-End tools will recognize some of the next features.

Installation

First you need to download Deno. And then run: deno --version

at this moment Ogone is built under deno 1.1.3
so please deno upgrade if your version is under.

for this tutorial we will follow this architecture:

- components
  |__root-component.o3
  |__greeting-component.o3
- deps.ts
- mod.ts

this is the moment to load Ogone. in your deps.ts start by importing Ogone:

// ./deps.ts
export { default as o3 } from 'https://x.nest.land/Ogone@0.18.0-rc.0-4/mod.ts';

// ./mod.ts
import { o3 } from './deps.ts';

o3.run({
  entrypoint: '/root-component.o3',
  modules: '/modules',
  port: 8080, // dev port
});

this file will configure the compilation of your application.


Getting Started

In the root-component, import another component like this with the use ... as statement.

// ./components/root-component.o3
use ./greeting-component.o3 as 'greeting-component';
<greeting-component />

let's see what we can do in the greeting-component.

// ./components/greeting-component.o3
<p>Hello World</p>

write the hello world inside an element
unwrapped textnodes throw an error

yup ok, not so cool but this is like every tools has to present a Hello world example.

Require data from the parent component

hmm, let's say Hello Deno instead but through the root-component. open your greeting-component.o3 and start using the require ... as statement.

// ./components/greeting-component.o3
require name as String;
<p>Hello ${name}!</p>

As you can see the first part of the component is the configuration of the component. you can see the first statement require <property> as <constructors>

if empty, name will throw an error. so you need to provide the Deno to the greeting-component.

// ./components/root-component.o3
use ./greeting-component.o3 as 'greeting-component';
<greeting-component :name="'Deno'" />

List of name

now you have seen two statements use ... as and require ... as.

what if our component has to present a list of random names ?

First, define the names, by using protocol element and def statement, follow this example:

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
   index: 0
</proto>
<p>Hello ${name}!</p>

here to understand the protocol element.
So we got now an index and a list, but nothing change. hmm let's use the mounted/onMounted feature of Ogone, we will use setInterval to see all the list.
inside the protocol element, use default statement like following:

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
    index: 0
    actual: null
  default:
    setInterval(() => {
      this.actual = this.people[this.index];
      this.index++;
      if (this.index > this.people.length) this.index = 0;
    }, 1000);
</proto>
<p>Hello ${actual || name}!</p>

default comes from the switch statement. It's the initialization case used by Ogone. this default statement is used when the component is mounted.

Now that the presentations are made, we have one problem. one loop is running until the user exit the window.

let's save this interval.

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
    index: 0
    actual: null
    interval: null
  case 'destroy':
    clearInterval(this.interval);
  break;
  default:
    this.interval = setInterval(() => {
      this.actual = this.people[this.index];
      this.index++;
      if (this.index > this.people.length) this.index = 0;
    }, 1000);
</proto>
<p>Hello ${actual || name}!</p>

as you can see all properties has to be defined in def
setting an property during the runtime will throw an error

ok that's better like this. you can spot the case case 'destroy'. this case is selected when the component is removed.

what if I want to use user events instead of an interval ?

You can use the flag --click. to test it, write a button element, set the flag attribute '--click:caseName', this will say to Ogone, select this case when there is a click.
example:

<proto>
case 'click:caseName':
  // do things
break;
</proto>
<button --click:caseName />

apply this inside the greeting-component. btw you can also use before-each statement:

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
    index: 0
    actual: null
  before-each:
     const update = () => {
       this.actual = this.people[this.index];
      if (this.index > this.people.length) this.index = 0;
     };
  case 'click:prev':
    this.index--;
    update();
  break;
  case 'click:next':
    this.index++;
    update(); 
  break;
</proto>
<p>Hello ${actual || name}!</p>
<button --click:prev>-</button>
<button --click:next>+</button>

before-each is selected before all cases, including the default case. this allows you to define vars that you can use in every cases.

use Reflections

Ogone comes out with few new features. one of those is the reflection. this act like a computed: {...} in Vue or the $: in Svelte, for those who knows.
the syntax this.reflect => value; or

this.reflect => {
  // do things
  return value;
}

how can I use it inside the greeting-component ?

good question. you can write it under the before-each statement like this:

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
    index: 0
    actual: null
  before-each:
     this.actual => {
        if (this.index > this.people.length) this.index = 0;
        return this.people[this.index];
     };
  case 'click:prev': this.index--; break;
  case 'click:next': this.index++; break;
</proto>
<p>Hello ${actual || name}!</p>
<button --click:prev>-</button>
<button --click:next>+</button>

reflections outside the before-each statement are not parsed

For and If flags

what if I want to see all the names ?

there is the flag --for="array as (value, key)" where array is a property or a new array.
erase the useless statements. and insert the paragraph element with the flag.

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
</proto>
<p --for="people as (person)">Hello ${person || name}!</p>

I don't want to see Mary Grey in the list, what can I do ?

there is two solutions. using .filter() inside the flag --for

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
</proto>
<p --for="people.filter(n => n!== 'Mary Grey') as (person)">Hello ${person || name}!</p>

or using the --if flag:

// ./components/greeting-component.o3
require name as String;
<proto>
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
</proto>
<p --for="people as (person)" --if="person !== 'Mary Grey'">Hello ${person || name}!</p>

sometimes you will have a long list of name to write, this is annoying inside a component that we want to keep small. Ogone lets you download ressources inside the protocol. assuming that we have a file names.yaml inside a folder sources.

// ./components/greeting-component.o3
require name as String;
<proto def="./sources/names.yaml" />
<p --for="people as (person)" --if="person !== 'Mary Grey'">Hello ${person || name}!</p>

inside ./sources/names.yaml

  people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso

Store

- components
  |__stores
     |__names.stores.o3
  |__root-component.o3
  |__greeting-component.o3
- deps.ts
- mod.ts

in Ogone, everything is a component. To create a store, start by creating a names.store.o3 file.

names is for our example and '.store' is not required.

this component will looks like this:

// ./components/stores/names.store.o3
<proto type="store" namespace="names">
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
</proto>

inject this store component inside your greeting-component

// ./components/greeting-component.o3
use ./stores/names.store.o3 as 'store-names';
require name as String;
<store-names namespace="names" />
<proto>
  def:
     people: []
</proto>
<p --for="people as (person)" --if="person !== 'Mary Grey'">Hello ${person || name}!</p>

the type of the component has to start the name of the component, if it's not starting you will get an error

how can I add one name to the store

you can use the object Store, that you can only access if the component uses a store component.
First create an action inside your store component:

// ./components/stores/names.store.o3
<proto type="store" namespace="names">
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
  case 'action:setName':
    this.people.push(ctx.name);
  break;
</proto>

all actions case have to start with 'action:'

now inside the component use the Store object. like following:

// ./components/greeting-component.o3
use ./stores/names.store.o3 as 'store-names';
require name as String;
<store-names namespace="names" />
<proto>
  def:
     people: []
  default:
    Store.dispatch('names/setName', {name: 'Ogone is Cool'});
</proto>
<p --for="people as (person)" --if="person !== 'Mary Grey'">Hello ${person || name}!</p>

now if you want to update this name, start writing a mutation inside the store:

// ./components/stores/names.store.o3
<proto type="store" namespace="names">
  def:
    people:
      - Mary Grey
      - Sam Lee
      - Christopher Van Sperso
  case 'action:setName':
    this.people.push(ctx.name);
  break;
  case 'mutation:UPDATE_NAME':
    this.people[ctx.id] = ctx.name;
  break;
</proto>

as the actions, the mutations have to start with 'mutation:'

Now, use it inside the component. For this, you need to use the flag --bind on an input. like <input --bind="property" /> but in our greeting-component we are going to use the property name.

// ./components/greeting-component.o3
use ./stores/names.store.o3 as 'store-names';
require name as String;
<store-names namespace="names" />
<proto>
  def:
     people: []
     name: Ogone
  case 'update:name':
    Store.commit('names/UPDATE_NAME', {
      id: this.people.length -1,
      name: this.name,
    });
  break;
  default:
    Store.dispatch('names/setName', {name: 'Ogone is Cool'});
</proto>
<p --for="people as (person)" --if="person !== 'Mary Grey'">Hello ${person || name}!</p>
<input --bind="name" />

Conclusion

with this article you learnt few features from Ogone:

  • use ... as 'component-name'
  • require <props> as <constructors>
  • <proto [type="store"[ >
  • def: <yaml>
  • default:
  • before-each:
  • case 'destroy'
  • case 'update:property'
  • --click:...
  • --for="array as (item, key)"
  • --if
  • --bind="property"
  • Store.dispatch('namespace/action')
  • Store.commit('namespace/mutation')

that's a good list for a start.
keep playing with it and mind that Ogone is still open for Pull requests or issues, and it still under development.

Top comments (0)