Discover our modules

At Robicue we have created various modules to optimize the development of modern applications. Most of them are free to use under the Creative Commons Attribution-NoDerivatives 4.0 International License.

If you need help implementing the modules in your project? Contact us at support@robicue.com

An architecture of hooks

28 January 2022


In the products we develop, we often use a hook function architecture. They are inspired by the React hooks, but the concept has been made more abstract and usable for both backend and frontend development.

What is a hook?

A hook is a simple function that provides some features. If for example we need functions to perform mathematical calculations then a hook is not the function that will do the calculations, but it is a function that will provide other functions to perform the calculations.

Example:

export const useMath = (context: Context) => {
  const { rand } = useRandom(context);

  return {
    pi: Math.pi,
    add(x: number, y: number) {
      return x + y;
    },
    randomInt(max: number) {
      return Math.round(rand() * max);
    },
  };
};

The advantage of using functionalities through hooks instead of using them directly is that most hooks rely on dependencies coming from the context parameter. The context can be easily changed and mocked which makes hooks modular, loosely coupled and much easier to test.

Conventions

If you start developing hooks there are some rules you can follow to keep consistency:

  1. Almost all features should be provided by a hook.

  2. A hook should start with use, e.g.: useContext, useFile, ...

  3. Every hook should have a single-responsibility (single-responsibility principle).

  4. Every hook should be in a separate file with the same name as the hook.

  5. Place your hooks together under a common folder, for example hooks.

  6. If you have a bunch of hooks that are related to each other, you can bundle them in a subfolder, but it is not required. Keep in mind that having too many nested folders might make it hard to keep an overview.

  7. If possible, try to write your hooks in TypeScript. This makes it easier to discover mistakes during development-time before running the application.

  8. If your hooks become too large and complex, refactor your code and split functionalities apart in separate hooks.

  9. Write tests for your hooks (make them testable). If you like test-driven, go for it!

  10. Hook functions must be fast. Heavy calculations should not happen inside the body of the hook function, but in functions returned from the hook. This makes them easy to include into other hooks without too much overhead.

  11. Avoid depending on global states. Make use of a useContext hook to store your state in a contextual object and pass this context object to other hooks.

  12. Try not to depend your hooks directly on external resources like files, databases, network requests, device inputs, ... If you need access to an external resource, then make a separate hook for it, that can be mocked for testing.

  13. Your hook files can export constants or other functions, but if possible try to make other functions part of your hook function.

  14. Take your time to think about the names you give to your hooks, functions or properties. Avoid abbreviations, and make sure that names are self explanatory.

  15. All names of hooks, functions or properties should be in camel case

  16. If possible try to include other hooks at the top of your hook. This makes it easier to find on which other hooks it depends. E.g.:

    export const useMyHook = (context: Context) => {
        const { select } = useDBQuery(context);
        const { organizations } = useModelOrganizations(context);
    
        ....
    }
    
  17. If possible, try to include other hooks directly into the body of your hook, so as to avoid placing it in sub-functions, loops or conditional statements when not required. This makes it easier to understand on which other hooks there are dependencies.

Context

The context is a special object that provides, as the name suggests, contextual information to hooks. Below are some reasons why a context is useful.

Prevents prop drilling

Contexts prevent that when deeply nested functions suddenly need some dependency, you don't have to drill that dependency through many functions until it finally lands into the function you need it for. This would otherwise create dependency on functions that do not really need it, and it forces you to change many parts of your code.

For example, contexts can be created on the level of an HTTP request. If your application is very complex and suddenly there is somewhere deeply in your application a function that needs the request object, you can simple use a hook like useHTTPRequest which will immediately give the request object at the right place.

Easy to mock and test

Suppose you want to create a test scenario for your code that applies within a specific time period. If all your hooks rely on some time provider hook, you can write that hook in such a way that the current time can be overruled. Because that rule would be applied on context level, it is easy to make an individual unit test that does not interfere with the time period of other concurrent running tests.

The useContext hook

This hook is maybe the most crucial hook in the whole architecture. It provides a way to create a new context object and to communicate with that context. We encourage never to access the context object directly. Instead you should rely on other hooks to read or wite the correct information in the context. This gives you a nice separation of concerns.

There are different ways to implement a context hook. But if you want you can use the official version below which we use for our own projects.

Install as NPM package:

npm install @robicue/use-context

Copy the full code:

const MAGIC_DATA_TOKEN = "__context_container__";
const VERSION = "1.0.0";

/**
 * All the data of our context is stored inside a property with a
 * long name that starts and ends with two underscores.
 * This is to indicate that the context data should never be accessed directly.
 */
export type Context = { [MAGIC_DATA_TOKEN]: unknown };

/**
 * We deliberately did not make the key of type 'string', to encourage the
 * use of the createKey. The createKey function ensures a better naming
 * convention by splitting the key into 3 parts: namespace, hook name, and
 * a name for the state.
 */
export type Key = unknown;

/**
 * Use this function to create a distinguishable
 * key for a contextual state.
 */
export const createKey = (
  namespace: string,
  hook: string,
  name: string
): Key => {
  return `${namespace}/${hook}/${name}`;
};

/**
 * Checks if the provided value is a valid context object.
 */
export const isContext = (value: unknown) => {
  return !!(value as Context)[MAGIC_DATA_TOKEN];
};

/**
 * Major hook that can be used to:
 * - create a new context
 * - create a fork of a context
 * - get or initialize a contextual state
 */
export const useContext = (context?: Context) => {
  let map: Map<Key, unknown>;

  if (context) {
    map = context[MAGIC_DATA_TOKEN] as Map<Key, unknown>;
    if (!map) {
      throw "An invalid context object has been provided";
    }
  } else {
    map = new Map();
    context = {
      [MAGIC_DATA_TOKEN]: map,
      __context_version__: VERSION,
    } as Context;
  }

  return {
    /**
     * The current context object.
     */
    context,

    /**
     * Returns TRUE if the specified key is not used in the context yet.
     */
    isAvailable(key: Key) {
      return map.has(key);
    },

    /**
     * Accesses the contextual state related to the specified key.
     * If the state does not exist yet, the result of the initializer
     * will be set and returned.
     */
    use<T>(key: Key, initializer?: () => T) {
      if (map.has(key)) {
        return map.get(key) as T;
      }

      if (!initializer) {
        throw `No context data available for '${key}'`;
      }

      const value = initializer();
      map.set(key, value);
      return value;
    },

    /**
     * Makes a fork of the current context.
     * The forked context allows you to keep access to the states
     * of the current context, but newly initialized states, created
     * in the forked context, are not seen by hooks using the current context.
     */
    fork() {
      return {
        ...context,
        [MAGIC_DATA_TOKEN]: new Map(map),
      } as Context;
    },
  };
};

How to store states into the context?

The state of your hook should live either inside your hook or inside the context, but not globally.

If you make use of the useContext hook, then creating a contextual object can be done like this:

import { Context, createKey, useContext } from "@robicue/use-context";

const counterStateKey = createKey("myApp", "useCounterHook", "counterState");

export const useCounterHook = (context: Context) => {
  const { use } = useContext(context);

  // The second parameter of the 'use' function is an
  // initializer that can return an object or value.
  // It will only be invoked if the value for the
  // specified key has not been initialized before
  // in the context.

  const counterState = use(counterStateKey, () => ({
    value: 0;
  }));

  return {
    increase() {
      counterState.value++;
    },
    decrease() {
      counterState.value--;
    },
  };
};

How to create a new context?

Most contexts are just created at very specific places in the application and then passed from one hook to another. If you are using the useContext example code above, a new context can be created by using the hook without specifying any parameter:

const { context: yourNewContext } = useContext();

Dealing with wrong abstractions

28 January 2022


As a developer you have to deal with abstractions daily. It leads to less duplicate code, more centralization, better testing, and it often hides away complexity. Although adding abstraction into your application has its own challenges. Below is a simple scenario of wrong abstraction, but when tackled in an early stage it can avoid many bugs.

Suppose we have an application with an object representing a Dog, that provides the two functions eat and walk.

class Dog {
  eat() {
    /** Do eating behavior */
  }
  walk() {
    /** Do walking behavior */
  }
}

Then later on during the development we need a second object representing another animal, where something is happening related to the interface of our Dog.

class Snake {
  eat() {
    /** Do eating behavior */
  }
  slide() {
    /** Do sliding behavior */
  }
}

It might feel right that we can start abstracting this into a single interface:

interface Animal {
  eat(): void;
  goForward(): void;
}

class Dog implements Animal {
  eat() {
    /** Do eating behavior */
  }
  goForward(): void {
    /** Do walking behavior */
  }
}

class Snake implements Animal {
  eat() {
    /** Do eating behavior */
  }
  goForward(): void {
    /** Do sliding behavior */
  }
}

This looks like a useful abstraction because now we can use it for our new function moveTheAnimal.

function moveTheAnimal(animal: Animal) {
  animal.goForward();
}

But sometimes this might lead to wrong abstractions. For example, at some point you might have this situation:

class Coral implements Animal {
  eat() {
    /** Do eating behavior */
  }
  goForward(): void {
    throw new Error("A coral cannot move forward");
  }
}

Because of the way we abstracted this, the development environment has now no way to tell you that this is incorrect, unless you start executing the application and see the runtime error as mentioned in the code above.

moveTheAnimal(new Coral());

The issue we have here is creating an abstraction by a wrong conclusion.

If some animals are able to go forward any animal is able to go forward

It is sometimes better to only start abstracting the moment there is really a need for it, avoiding premature abstraction. But, you can also abstract too late. We don't want this situation either:

function moveTheAnimal(animal: Dog | Snake) {
  if (animal instanceof Dog) {
    animal.walk();
  }
  if (animal instanceof Snake) {
    animal.slide();
  }
  // ...
}

A better solution may be to split up the interface:

interface MovableAnimal {
  goForward(): void;
}

interface EatingAnimal {
  eat(): void;
}

class Dog implements MovableAnimal, EatingAnimal {
  eat() {
    /** Do eating behavior */
  }
  goForward() {
    /** Do walking behavior */
  }
}

class Snake implements MovableAnimal, EatingAnimal {
  eat() {
    /** Do eating behavior */
  }
  goForward() {
    /** Do sliding behavior */
  }
}

class Coral implements EatingAnimal {
  eat() {
    /** Do eating behavior */
  }
}

Now we can keep our 'move' function clean.

function moveTheAnimal(animal: MovableAnimal) {
  animal.goForward();
}

And if we pass an animal object that is not movable, an error should be shown immediately in the development environment.

moveTheAnimal(new Coral());

The example may seem very obvious, but these kinds of abstraction errors are very common. This is often because we want to write code that is prepared for unexpected future scenarios. However, practice shows that it is very difficult to predict the future and that the users of our application may suddenly have very different needs than those we had foreseen. Your code is like a living being and will continuously change. Sometimes it is better to write simple code that is very strictly related to the requested use case, than to write code that is already prepared for other use cases but in reality never occur.

Music Composer

Module: com.robicue.audio.composer
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.audio.composer-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

Compose music through coding.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.audio.composer-latest.js"></script>
    <title>Song</title>
</head>
<body>
    <script>
        const { compose } = com.robicue.audio.composer;

        const song = compose(function({$$, pitch, bpm, staccato, volume}) {
            staccato(true);
            volume(.5);
            bpm(240);

            $$(3, 1, 3, 2, 6);
            pitch(.4);
            $$(3, 1, 3, 2, 6);
            pitch(.3);
            $$(3, 1, 3, 2, 6);
            pitch(.25);
            $$(3, 1, 3, 2, 6);

            pitch(1);
            $$(3, 1, 3, 2, 6);
            pitch(.4);
            $$(3, 1, 3, 2, 6);
            pitch(.3);
            $$(3, 1, 3, 2, 6);
            pitch(.25);
            $$(3, 1, 3, 2, 6);
        });
    </script>
    <main>
        <button onclick="song.play()">Play</button>
    </main>
</body>
</html>

Wave Generator

Module: com.robicue.audio.wave
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.audio.wave-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

Generate advanced sound waves.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.audio.wave-latest.js"></script>
    <title>Song</title>
</head>
<body>
    <script>
        const { generate, sine, tween, multiply } = com.robicue.audio.wave;

        const tone = function(pitch) {
            generate(.2, [
                sine(10 * pitch),
                tween(.2, [0, 1, 0]),
                multiply(.5)
            ]).play();
        };
    </script>
    <main>
        <button onclick="tone(16.35)">C</button>
        <button onclick="tone(17.32)">C#</button>
        <button onclick="tone(18.35)">D</button>
        <button onclick="tone(19.45)">D#</button>
        <button onclick="tone(20.60)">E</button>
        <button onclick="tone(21.83)">F</button>
        <button onclick="tone(23.12)">F#</button>
        <button onclick="tone(24.50)">G</button>
        <button onclick="tone(25.96)">G#</button>
        <button onclick="tone(27.50)">A</button>
        <button onclick="tone(29.14)">A#</button>
        <button onclick="tone(30.87)">B</button>
    </main>
</body>
</html>

Word to emoji

Module: com.robicue.emoji
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.emoji-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

Automatically convert text to emojis with the Robicue word to emoji converter.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.frameworks.frontend-latest.js"></script>
    <script src="https://cdn.robicue.com/com.robicue.emoji-latest.js"></script>
    <title>My emojis</title>
    <style>
        main {
            display: flex;
            align-items: center;
        }

        .emoji {
            font-size: 2em;
            margin-right: 10px
        }

        .input {
            width: 100%;
        }
    </style>
</head>
<body>
    <script>
        const wordToEmoji = com.robicue.emoji.byWord;
    </script>
    <main rbq rbq-init="store.text=''">
        <span class="emoji" rbq-watch="store.$text">${wordToEmoji(store.text)}</span>
        <input class="input" rbq-bind="store.$text" placeholder="<-- enter here a word (eg. man, dog, ...) and see the emoji change">
    </main>
</body>
</html>

The Robicue Frontend Framework (TRFF)

Module: com.robicue.frameworks.frontend
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.frameworks.frontend-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

Build complex web applications faster by combing the Robicue state manager with special attributes parsed by TRFF.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/frontend.js"></script>
    <title>My application</title>
</head>
<body rbq>
    <main rbq-init="store.counter=1">
        <h1>Welcome to my application</h1>
        <button rbq-onclick="store.counter++">Increase counter</button>
        <p>Counter: ${store.$counter}</p>
    </main>
</body>
</html>

The basics of TRFF

The Robicue Frontend Framework (TRFF) is a collection of modules that allows you to build web applications faster.

With this framework we strive to stick to the following rules:

  • Compatibility with other tools and frameworks
  • Focus on building your product not writing code
  • Everything as modular as possible
  • Keep it simple

In this tutorial you will learn how to make the following simple counter application:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/frontend.js"></script>
    <title>My application</title>
</head>
<body rbq>
    <main rbq-init="store.counter=1">
        <h1>Welcome to my application</h1>
        <button rbq-onclick="store.counter++">Increase counter</button>
        <p>Counter: ${store.$counter}</p>
    </main>
</body>
</html>

Design first

To get started you do not need the framework. You can easily begin with a simple HTML project:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My application</title>
</head>
<body>
    <h1>Welcome to my application</h1>
</body>
</html>

You can already start designing your application to show to your stakeholders and make it interactive through the framework later.

Let's create a simple counter:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My application</title>
</head>
<body>
    <main>
        <h1>Welcome to my application</h1>
        <button>Increase counter</button>
        <p>Counter: 1</p>
    </main>
</body>
</html>

Make it interactive

Now we will make it interactive through the framework. First we specify which elements should be made interactive. We do this by adding the rbq attribute to all elements we want to be interactive. Because in this example the full document must be interactive we added it to the body element.

TRFF has a built-in state manger, where most of the states are located in com.robicue.store. We can easily access this from within the components through the store variable. For our example counter application we will create a new counter variable in the store and assign it the value 1:

<main rbq-init="store.counter=1">

Now we want our application to show us the value of the counter. For this we make use of the ${} syntax to evaluate a JavaScript expression and render the result.

<p>Counter: ${store.counter}</p>

Our simple application is almost finished, but it is still not interactive so we add a button to increase the counter.

Normally in HTML we would use the onclick attribute to handle a click event on the button. But because this will not give us access to the local variables assigned by TRFF components we can solve this by prefixing every event attribute by rbq-.

<button rbq-onclick="store.counter++">Increase counter</button>

We now have the below code. Let's see what happens if we press the button.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/frontend.js"></script>
    <title>My application</title>
</head>
<body rbq>
    <main rbq-init="store.counter=1">
        <h1>Welcome to my application</h1>
        <button rbq-onclick="store.counter++">Increase counter</button>
        <p>Counter: ${store.counter}</p>
    </main>
</body>
</html>

Handling states

Nothing is happening when we click the button? Seems obvious, because how would the text in the 'p'-tag be aware of the changes in the counter variable?

In pure javascript we would have to do something like this:

const store = com.robicue.store;

let pTag = document.createElement('p');
document.body.append(pTag);

function updateCounter() {
    pTag.textContent = `Counter: ${store.counter}`;
}

store.counter = 1;
updateCounter();

store.counter = 2;
updateCounter();

But here comes the ease of TRFF. From every entry in the store we can also get it's state equivalent.

For example:

const store = com.robicue.store;

store.counter = 3;

// This returns the value of the counter
console.log(store.counter)

// This return the state object of the counter
console.log(store.$counter)

// This also returns the value of the counter
console.log(store.$counter.get())

A cool thing about a state is that it can be observed for changes.

const {store, state} = com.robicue;

store.counter = 3;

state.observe(store.$counter, value => {
    console.log('The counter has changed to value: ' + value);
});

store.counter = 4;

Ok, but how do we implement this in our example so that the text in the p-tag changes?

It is very simple. We just have to use the dollar sign in front of the variable and the framework sees it is returning a state and automatically inject a tag that changes when the state of our counter is mutating:

This: <p>Counter: ${store.counter}</p>

Becomes this: <p>Counter: ${store.$counter}</p>

And our counter is now working!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/frontend.js"></script>
    <title>My application</title>
</head>
<body rbq>
    <main rbq-init="store.counter=1">
        <h1>Welcome to my application</h1>
        <button rbq-onclick="store.counter++">Increase counter</button>
        <p>Counter: ${store.$counter}</p>
    </main>
</body>
</html>

Dynamic attributes

Can the framework detect changes from states in attributes too? Certainly! Just don't forget to prefix your attributes by rbq-.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/frontend.js"></script>
    <title>My application</title>
</head>
<body rbq>
    <main rbq-init="store.counter=1">
        <h1>Welcome to my application</h1>
        <button rbq-onclick="store.counter++">Increase counter</button>
        <p>Counter: </p><input rbq-value="${store.$counter}">
    </main>
</body>
</html>

Local storage persistency

Module: com.robicue.persistency
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.persistency-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

This module is an extension of the com.robicue.store module and keeps items in the store persistent by using the local storage.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.frameworks.frontend-latest.js"></script>
    <title>My application</title>
    <style>
        .input {
            width: 100%;
        }
    </style>
</head>
<body>
    <script>
        const { persist } = com.robicue.persistency;
        persist(com.robicue.store.$text);
    </script>
    <main rbq>
        <p>
            Everything that you type in the input box below will be kept persistent. You can check this by refreshing your page.
        </p>
        <input class="input" rbq-bind="store.$text" placeholder="Your persistent message">
    </main>
</body>
</html>

Hash location router

Module: com.robicue.router
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.router-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

Sometimes users want to have a url that gives them direct access to a specific page or state inside your application. The Robicue router makes this easy by using the hash component in the url. The module also allows you to assign parameters to the hash location for more advanced applications.

Also for this documentation website the router module has been used. Note how the hash location in the url bar changes when you navigate to the documentation of other modules.

Need help implementing it in your project? Contact us at support@robicue.com

const router = com.robicue.router;
const state = com.robicue.state;

state.observe('router/location', () => {
    console.log('Path: ' + router.path);
    console.log('Params: ' + JSON.stringify(router.params));
});

location.hash = '#/page-1?id=56&message=hello';

A powerful state manager

Module: com.robicue.state
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.sate-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

Manage states throughout your application and keep the business logic clear.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.state-latest.js"></script>
    <title>My state</title>
    <style>
        main {
            padding: 10px 0;
        }
        #status {
            color: #888;
        }
    </style>
</head>
<body>
    <input id="input">
    <button onclick="post()">Post</button>

    <main>
        Your status message: "<span id="status"></span>"
    </main>

    <script>
        const state = com.robicue.state;

        const inputElement = document.getElementById('input');
        const statusElement = document.getElementById('status');
        
        state.implement('my-app', {
            message: 'I have nothing to say',
            update(msg) {
                this.set('message', msg)
            }
        });

        state.attach('my-app/message', value => statusElement.textContent = value);

        function post() {
            state.dispatch('my-app/update', inputElement.value);
        }
    </script>
</body>
</html>

An innovative way to manage states

Module: com.robicue.store
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.sate-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

The store is an extension of the com.robicue.state module but offers more powerful ways to manage states throughout your application. With this module it is possible to work on JavaScript objects and arrays while keeping track on their changes. In combination with the com.robicue.persistency module you can also automatically keep the states persistent in the local storage or session storage.

The following example is similar with the one in the state module com.robicue.state but shows how to implement it through the store.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.store-latest.js"></script>
    <title>My store</title>
    <style>
        main {
            padding: 10px 0;
        }
        #status {
            color: #888;
        }
    </style>
</head>
<body>
    <input id="input">
    <button onclick="post()">Post</button>

    <main>
        Your status message: "<span id="status"></span>"
    </main>

    <script>
        const state = com.robicue.state;
        const store = com.robicue.store;

        const inputElement = document.getElementById('input');
        const statusElement = document.getElementById('status');
        
        store.myApp = {
            message: 'I have nothing to say',
            update(msg) {
                this.message = msg;
            }
        };

        // We can use the $-sign before variables to get their state object:
        state.attach(store.myApp.$message, value => statusElement.textContent = value);

        function post() {
            store.myApp.update(inputElement.value);
        }
    </script>
</body>
</html>

Fast styling through classes

Module: com.robicue.style
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.style-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

This module offers various pre-made classes to allow quick styling on HTML elements for responsive websites.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.style-latest.js"></script>
    <title>My website</title>
</head>
<body class="rbq-p-4">
    <header class="rbq-flex-row">
        <div class="rbq-flex-1">
        <a data-rbq-toggle="#menu" class="rbq-cursor-pointer" href="#">Open / close menu</a>
    </header>
    <nav id="menu" class="rbq-on-display-none">
        <ul>
            <li><a class="rbq-flex-1" href="#info">Info</a></li>
            <li><a class="rbq-flex-1" href="#about">About</a></li>
            <li><a class="rbq-flex-1" href="#contact">Contact</a></li>
        </ul>
    </nav>
    <main>
        <h1>Welcome to my application</h1>
    </main>
    <footer>
       Copyright &copy; Robicue
    </footer>
</body>
</html>

Window Manager

Module: com.robicue.windowManager
Version: 2021-01-12.001-alpha
Import: <script src="https://cdn.robicue.com/com.robicue.windowManager-latest.js"></script>
License: Creative Commons Attribution-NoDerivatives 4.0 International License

Example

Create, move and resize dockable windows within your web application with the Robicue Window Manager.

Need help implementing it in your project? Contact us at support@robicue.com

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.robicue.com/com.robicue.windowManager-latest.js"></script>
    <title>Window Manager</title>
    <style>
        body {
            height: 400px;
        }

        main {
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
        }

        .window {
            width: 250px;
            height: 250px;
        }
    </style>
</head>
<body>
    <main>
    </main>
    <script>
        const windowManager = com.robicue.windowManager;
        const Window = windowManager.window.Window;
        const Surface = windowManager.surface.Surface;

        let surface = new Surface("main");

        let window1 = new Window(surface);
        window1.createPage('Page 1', '<p>Hello world!</p>');
        window1.createPage('Page 2', '<p>This is another page</p>');

        let window2 = new Window();
        surface.dock('right', window2);
        window2.createPage('Page 3', '<p>How are you?</p>');
    </script>
</body>
</html>
navigate('/');
Copyright © ${new Date().getFullYear()} Robicue. All rights reserved.
document.body.scrollTop = 0; document.documentElement.scrollTop = 0; renderPreviews()