npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fabricate.js

v3.5.0

Published

Tiny vanilla JS webapp framework with a fluent API and zero dependencies.

Downloads

137

Readme

fabricate

n. To create quickly and easily.

A tiny vanilla JS webapp framework with a fluent API and zero dependencies, intended for small apps with relatively simply layouts. Includes some pre-prepared components to get started quickly.

Introduction

The aim of fabricate.js is to allow a quick and expressive way to set up UI with a fluent API based on method chaining. This allows creating elements with styles, attributes, handlers, and child elements in an easy and predictable fashion.

For example, a text element in a padded container:

const Label = ({ text }) => fabricate('span')
  .setStyles({ fontSize: '0.9rem' })
  .setText(text);

// Column is one of many included basic components
const Container = () => fabricate('Column').setStyles({ padding: '10px' });

const App = () => Container()
  .setChildren([
    Label({ text: 'Hello, world!' }),
    Label({ text: 'Welcome to fabricate.js!' }),
  ]);

// Use as the root app element as a builder
fabricate.app(App);

Components created with fabricate.js can be extended after they are created, for example this button with a hover-based highlight effect:

const BasicButton = () => fabricate('div')
  .setStyles({
    padding: '8px 10px',
    color: 'white',
    backgroundColor: 'gray',
    cursor: 'pointer',
  })
  .setText('Click me!')
  .onClick(onButtonClicked);

This component can then be specialised for other uses:

const SubmitButton = () => BasicButton()
  .setStyles({ backgroundColor: 'green' })
  .setText('Submit')
  .onClick(() => alert('Success!'));

const CancelButton = () => BasicButton()
  .setStyles({ backgroundColor: 'red' })
  .setText('Cancel')
  .onClick(() => alert('Cancelled!'));

See the examples directory for more examples, including simple apps.

Some basic components are included to quickly build a UI, see below for more details.

Installation

Install from a CDN, such as unpkg:

<!-- Where x.y.z is a published version -->
<script src="https://unpkg.com/[email protected]/fabricate.js"></script>

or install from npm and copy or reference fabricate.js from node_modules:

<script type="text/javascript" src="./node_modules/fabricate.js/fabricate.js"></script>

Types

TypeScript users can import types from the types/fabricate.d.ts file:

import { Fabricate, FabricateComponent } from 'fabricate.js';

/** App's state type */
type AppState = {
  counter: string;
  page: 'LoginPage' | 'ListPage';
};

// Required when global is declared with <script> import
declare const fabricate: Fabricate<AppState>;

API

The API is split into two sections - component construction and app helpers.

Component construction

App helpers

Create components

To create a component, simply specify the tag or declared component name:

const EmptyDivComponent = () => fabricate('div');

The shorter convenience alias fab is also available.

.asFlex()

To quickly set basic display: flex and flexDirection styles:

const Column = () => fabricate('div').asFlex('column');
const Row = () => fabricate('div').asFlex('row');

The Row and Column basic components are included for this purpose.

.setStyles() / setAttributes()

Set element styles and tag attributes:

const BannerImage = ({ src }) => fabricate('img')
  .setStyles({ width: '800px', height: 'auto' })
  .setAttributes({ src });

setNarrowStyles() is also available to easy specify styles to be added if the app is on a narrow screen, since it is a common case:

fabricate('Text')
  .setStyles({ fontSize: '1.8rem' })
  .setNarrowStyles({ fontSize: '1rem' })

If the theme option was used, palette and styles can be used during this function:

const App = () => fabricate('Text')
  .setStyles(({ palette, styles }) => ({
    color: palette.customColor,
    boxShadow: styles.dropShadow,
  }));

const options = {
  theme: {
    palette: {
      customColor: '#444',
    },
    styles: {
      dropShadow: '2px 0px 4px black',
    },
  },
};

fabricate.app(App, initialState, options);

.setChildren() / .addChildren()

Set other components as children to a parent, replacing any existing ones:

const ButtonRow = () => fabricate('Row')
  .setChildren([
    fabricate('Button', { text: 'Submit'}),
    fabricate('Button', { text: 'Cancel'}),
  ]);

Later, add more children to the existing list:

buttonRow.addChildren([
  fabricate('Button', { text: 'Added Later'}),
]);

.onClick() / onHover() / .onChange()

Add click and hover behaviors, which are provided the self-same element to allow updating styles and attributes etc:

All component callbacks and handlers have the same signature: cb(element, state, ...others)

fabricate('Button', { text: 'Click me!' })
  .onClick(el => alert('Clicked!'))
  .onHover({
    start: el => console.log('may be clicked'),
    end: el => console.log('maybe not'),
  });

Hovering can also be implemented with just a callback if preferred:

fabricate('Button', { text: 'Click me!' })
  .onClick((el, state) => {
    alert(`Clicked ${state.counter} times!`);
    fabricate.update('counter', ({ counter }) => counter + 1);
  })
  .onHover((el, state, isHovered) => console.log(`isHovered: ${isHovered}`));

For inputs, the change even can also be used:

fabricate('TextInput', { placeholder: 'Email address' })
  .onChange((el, state, value) => console.log(`Entered ${value}`));

.setText() / .setHtml()

For simple elements, set their innerHTML or innerText:

fabricate('div')
  .setStyles({ backgroundColor: 'red' })
  .setText('I am a red <div>');

Or set inner HTML directly:

fabricate('div').setHtml('<span>I\'m just more HTML!</div>');

.onDestroy()

Simple method to do something immediately after a component has been removed from the DOM:

DevicePage()
  .onDestroy(unsubscribeWebsockets);

.onEvent()

Add an event listener for any other kind of event, such as 'load':

fabricate('Image', { src })
  .onEvent('load', (el, state, event) => console.log(event));

.displayWhen()

Conditionally show or hide a component (or tree of components) using the displayWhen method. The component is created immediately, but hidden until the state test returns true:

To conditionally create components, use the .conditional() helper.

const App = () => fabricate('Text')
  .setText('Now you see me!')
  .displayWhen(state => state.showText);

// Use as the root app element and provide first state values
fabricate.app(App, { showText: false });

// Later, show the text
setInterval(
  () => fabricate.update('showText', state => !state.showText),
  2000,
);

Note: The second callback can be used to know when the element is shown or hidden.

const App = () => fabricate('Text')
  .setText('Now you see me!')
  .displayWhen(
    state => state.showText,
    (el, state, isVisible) => console.log(`Am I visible now? ${isVisible}`),
  );

.empty()

For components such as lists that refresh data, use empty() to remove all child elements:

/**
 * A list of users, driven by app state.
 */
const UserList = () => fabricate('Column')
  .onUpdate((el, { userList }) => {
    el.empty();
    el.addChildren(userList.map(User));
  }, ['userList']);

/**
 * When new data is available, update state and hence the list.
 */
const refreshUserList = () => {
  const userList = await fetchUsers();

  fabricate.update({ userList });
};

Note: setChildren clears existing children automatically, so would be a better option for this scenario.

fabricate / fab helpers

The main exported object also has some helper methods to use:

.isNarrow()

// Detect a very narrow device, or mobile device
fabricate('Text')
  .setStyles({ fontSize: fabricate.isNarrow() ? '1rem' : '1.8rem' })

For convenience, setNarrowStyles() is available.

.app()

Use app() to start an app from the document.body. You can also specify an initial state and some extra options.

const App = () => fab('Column').setChildren([
  fabricate('NavBar', { title: 'My New App' }),
  MainContent().setChildren([
    HeroImage(),
    Title(),
    Article(),
  ]),
]);

const initialState = {
  article: {
    title: 'Using fabricate.js in web apps',
    description: 'Lorem ipsum...',
  },
};

// Log all state updates, and persist 'readingList' state across reloads
const options = {
  logStateUpdates: true,
  persistState: ['readingList'],
};

fabricate.app(App, initialState, options);

The options available are:

| Name | Type | Description | |------|------|-------------| | logStateUpdates | boolean | Log all state updates in the console. | | persistState | Array<string> | List of state keys to persist values in LocalStorage. | | theme | { palette, styles } | Provide a palette and common styles for use in setStyles |

.declare()

Declare a custom component that can be instantiated elsewhere in the app, with props, useful in apps with many files:

When using TypeScript, simply exporting components is usually preferable.

// Declare component name and builder function including props
fabricate.declare('ColorfulText', ({ color }) => fabricate('span').setStyles({ color }));

Then create the component where needed, supplying the required props:

fabricate('ColorfulText', { color: 'red' })
  .setText('Red custom component!');

.onKeyDown()

Listen globally for 'keydown' events. Useful for keyboard shortcuts.

fabricate.onKeyDown((state, key) => {
  if (key === 'Enter') {
    console.log('Enter was pressed');
  }
});

.update() / .onUpdate()

A few methods are available to make it easy to maintain some basic global state and to update components when those states change. A list of keys to watch must be provided.

// View can watch some state - specifically, 'state.counter' and initial update
const App = () => fabricate('Text')
  .onUpdate(
    (el, state, key) => el.setText(state.counter),
    [fabricate.StateKeys.Init, 'counter'],
  );

// Initialise first state
fabricate.app(App, { counter: 0 });

There are three ways to update state:

// Update the state using the previous state
setInterval(() => {
  fabricate.update('counter', prev => prev.counter + 1);
}, 1000);

// Or just the new data
fabricate.update('counter', 0);

// Or as a state slice
fabricate.update({ counter: 0 });

There are some special events that can be used:

  • fabricate.StateKeys.Init - Called when the application is first run.
  • fabricate.StateKeys.Created - Called for a particular component when it is first created.
  • fabricate.StateKeys.Route - Called when router changes route.

.buildKey()

When state keys cannot be known in advance (such as with lists of components representing variable data from an API), the buildKey() helper can be used to construct state keys in a predictable way, allowing them for state updates.

As much specificity can be used as required, just use more parameters.

const UserCard = ({ userId }) => fabricate('Column')
  // ...
  .onUpdate((el, state) => {
    // Use key to display state
    const isOnlineKey = fabricate.buildKey('UserCard', userId, 'isOnline');
    
    el.setStyles({ backgroundColor: state[isOnlineKey] ? 'green' : 'grey' });
  });

// Set state when data is fetched
API.fetchUsers()
  .then((users) => {
    users.forEach((user) => {
      if (user.online) {
        const isOnlineKey = fabricate.buildKey('UserCard', user.id, 'isOnline');
        fabricate.update(isOnlineKey, true);
      }
    });
  });

.conditional()

Allows creation of components when a state condition is met. The rendered component is contained in a persistent wrapper than be styled if required for parent layout.

const mainPage = fabricate.conditional(
  state => state.page === 'main',
  MainPage,
);

const settingsPage = fabricate.conditional(
  state => state.page === 'settings',
  SettingsPage,
);

fabricate('Column')
  .setChildren([
    mainPage,
    settingsPage,
  ]);

.router()

For a multi-page app, use router() to declare pages to be displayed when navigate() is used, usually at the top level.

const HomePage = () => fabricate('Column')
  .setChildren([
    fabricate('h1').setText('This is HomePage'),
    fabricate('Button', { text: 'Go to StatusPage' })
      .onClick(() => fabricate.navigate('/status')),
]);
const StatusPage = () => fabricate('Column')
  .setChildren([
    fabricate('h1').setText('This is StatusPage'),
    fabricate('Button', { text: 'Go to HomePage' })
      .onClick(() => fabricate.navigate('/')),
]);

const App = () => fabricate.router({
  '/': HomePage,
  '/status': StatusPage,
});

// Use as the root app element
fabricate.app(App);

After navigating, goBack() can be used to go back a step, and getRouteHistory() displays the current route history.

Built-in components

See examples/components for a page displaying all example components.

Row

A simple flex row:

fabricate('Row')
  .setChildren([
    fabricate('Button').setText('Confirm'),
    fabricate('Button').setText('Cancel'),
  ])

Column

A simple flex column:

fabricate('Column')
  .setChildren([
    fabricate('Image', { src: '/assets/images/gallery1.png' }),
    fabricate('Image', { src: '/assets/images/gallery2.png' }),
  ])

Text

Basic text component:

fabricate('Text').setText('Hello, world!')

Image

Basic image component:

fabricate('Image', { src: '/assets/images/gallery01.png' })

Button

A simple button component with optional hover highlight behavior:

fabricate('Button', {
  text: 'Click me!',
  color: 'white',
  backgroundColor: 'gold',
  highlight: true,
})

NavBar

NavBar component for app titles, etc. Can contain more components within itself:

fabricate('NavBar', {
  title: 'My Example App',
  color: 'white',
  backgroundColor: 'purple',
})
  .setChildren([
    fabricate('Button', { text: 'Home' }).onClick(goHome),
    fabricate('Button', { text: 'Gallery' }).onClick(goToGallery),
  ])

NavBar has the following extra methods:

  • setTitle - (string) Set the new title.

TextInput

A basic text input box with padding:

fabricate('TextInput', {
  placeholder: 'Enter email address',
  color: '#444',
  backgroundColor: 'white'
})
  .onChange((el, state, newVal) => console.log(`Email now ${newVal}`))

Loader

Customizable CSS-based spinner/loader:

fabricate('Loader', {
  size: 48,
  lineWidth: 5,
  color: 'red',
  backgroundColor: '#ddd',
})

Card

Simple Material-like card component for housing sections of other components:

fabricate('Card')
  .setChildren([
    fabricate('Image', { src: '/assets/images/gallery01.png' }),
  ])

Fader

Container that fades in upon creation to smoothly show other components inside:

fabricate('Fader', {
  durationS: 0.6,
  delayMs: 300,
})

Pill

Basic pill for category selection or tags etc:

fabricate('Pill', {
  text: 'Favourites',
  color: 'white',
  backgroundColor: 'orange',
})

FabricateAttribution

Logo with link for quick attribution to use of fabricate.js (optional but cool!).

FabricateAttribution()

Tabs

Tab bar and tabs components which can be used for horizontal navigation between a number of different views. Tabs are specified as key name and a function that builds the view. Additional customisation of the tab bar and tabs themselves is available.

fabricate('Tabs', {
  tabs: {
    Home: HomeTab,
    User: UserTab,
    Settings: SettingsTab,
  },
  tabStyles: {
    color: 'white',
    backgroundColor: 'green',
  },
  barStyles: { width: '400px' },
})

Select

Slightly styles select component that provides options.

fabricate('Select', {
  options: [
    { label: 'Apple', value: 'apple' },
    { label: 'Orange', value: 'orange' },
    { label: 'Lemon', value: 'lemon' },
  ],
})

Run tests

Run unit tests:

npm test