Giter Club home page Giter Club logo

funcy.js's Introduction

funcyjs logo

A functional web-components wrapper

example of code

Why 'funcy.js'?

Naming things is hard and 'funcyjs' (pronounced like funky) seemed like a fun, recognizable name.

What is it?

funcy.js seeks to provide a functional way of defining web components, very much akin to react functional components with hooks.

the hooks functionality in funcyjs is powered by: hookuspocus

Here's a simple TODO app implemented with funcyjs: https://codepen.io/michael-klein/pen/xmQZBx

Browser Compatibility

The library is published is not transpiled for browser compatibility and does not contain any polyfills. As such you can use it as an es6 module in the latest version of chrome and other browsers that implement the latest JavaScript features including web components v1, but it will fail horribly anywhere else, so you will have to provide polyfills/transpilation if you want to use this in more browsers.

Installation

Using npm:

npm install funcy-components
import {defineComponent} from "funcy-components"

As ES6 module via hotlinking from unpkg:

import {defineComponent} from "https://unpkg.com/funcy-components/dist/core.min.mjs"

or the full version with all hooks:

import {defineComponent} from "https://unpkg.com/funcy-components/dist/full.min.mjs"

Usage

The bare minimum:

import {defineComponent} from "https://unpkg.com/funcy-components/dist/full.min.mjs";

defineComponent("a-component", () => {
 const div = document.createElement("div");
 div.innerHTML = "Hello World!";
 return div;
});

✏️pen

What's happening here? defineComponent is a method with the signature:

function defineComponent(name:string, component:(props:any) => View, options:DefineComponentOptions = {}):void;

It will define a web component via customElements.define by internally creating a class that extends HTMLElement using the supplied name.

component is a function that accepts props and returns a View (just like functional components in react). It will be called whenever the component needs to (re-)render. A View is anything that can be consumed by a renderer (more on that in a bit). In the above example, the View is simply a div element. The default renderer will simply replace the current content of the shadowRoot with the view (unless you return the same nodes).

defineComponent also accepts an options object, that allows you to define observed attributes and pass options to attachShadow

interface DefineComponentOptions {
  observedAttributes:string[],
  shadowOptions:ShadowRootInit
}

Props

Normally, you can only pass data to custom elements via attributes, which only support string values. funcyjs enables you to pass prps between funcyjs components using thr prps method like this:

defineComponent(
  "prop-sender",
  () => {
    const html = usePreactHtm();
    return html`
      <prop-receiver ...${prps({greeting: "hello World"})}></prop-receiver>
    `;
  }
);
defineComponent(
  "prop-receiver",
  (props) => {
    const html = usePreactHtm();
    return html`
      <div>
        ${props.greeting}
      </div>
    `;
  }
);

✏️pen

prps will actually return an object like this:

{
  "data-props": propsId
}

You can spread it on the component with htm or just set a "data-props" attribute manually if you just use DOM. Internally, funcyjs listens to changes to the data-props argument and qeueus re-renders if the passed props change.

Hooks: The basics

Hooks are a way to use state or other internal features in your functional components. They were first popularized by react. Read more about the motivation and use of the basic hooks (useReducer, useState, useEffect) in the react docs: https://reactjs.org/docs/hooks-intro.html. The basic hooks that funcyjs has in common with react should work exactly the same. If they don't, pease submit an issue :)

In the following I will explain how to use some of the hooks which are specific to funcyjs.

Custom renderers

A custom renderer is a function that takes a View and a shadowRoot and knows how to render the View to the shadowRoot. It is called after a component renders with the generated View and the shadowRoot of the elment. For example, this is the default renderer:

export const defaultRenderer = (view, shadowRoot) => {
  if (
    !(view instanceof NodeList
      ? shadowRoot.contains(view[0])
      : shadowRoot.contains(view))
  ) {
    shadowRoot.innerHTML = "";
    if (view instanceof NodeList) {
      view.forEach(node => shadowRoot.appendChild(node));
    } else {
      shadowRoot.appendChild(view);
    }
  }
};

You can define your own custom renderer with the useRenderer hook. funcyjs exports a custom usePreactHtm hook that uses htm and preact in the full bundle:

import { createHook, useRenderer } from "../export_core.mjs";
import { html, render } from "../../node_modules/htm/preact/standalone.mjs";
export const usePreactHtm = createHook(() => {
  useRenderer((view, shadowRoot) => {
    render(view, shadowRoot);
  });
  return html;
});

It also returns a html template tag that can be used to construct the view which is consumed with the render call.

Attributes

CustomElements can have attributes, just like any other element. The useAttribute hook will enable you to access and modify these:

defineComponent(
  "attribute-example",
  () => {
    const html = usePreactHtm();
    const [name, setName] = useAttribute("name");
    return html`
        <input type="text" onInput=${e => setName(e.target.value)} value=${name}></input>
    `;
  },
  {
    observedAttributes: ["name"]
  }
);

✏️pen

The above example will reflect changes you make to the input back to the attribute on the component in the DOM. Note that we also supplied "name" as an observedAttribute, so that when an outside source changes the attribute, the component will re-render (the setter from useAttribute won't trigger a re-render).

CSS

You can render CSS directly to the view, if you which. You can also use the useCSS hook for that purpose. The hook can act as a normal function or a template tag and will render the CSS you pass it to the shadowRoot:

defineComponent(
  "css-example",
  () => {
    const html = usePreactHtm();
    useCSS('h1 {color:green;}');
    const css = useCSS;
    css`
      h2 {
        color:red;
      }
      `
    return html`
      <div>
        <h1>green</h1>
        <h2>red</h2>
      </div>
    `;
  }
);

✏️pen

Exposing an API

CustomElements can expose API methods for others to consume. In funcyjs, this is done through the useExposeMethod hook:

defineComponent(
  "expose-method",
  () => {
    const html = usePreactHtm();
    useExposeMethod("methodName", () => alert("you used this method!"));
    return html`<div>something</div>`;
  }
);

Note that wether you use an arrow function or a normal function, this will never be bound to the CustomElement instance.

others:

You can access the host element, the shadow root or get information on the connected state of the component with the useHostElement, useShadowRoot useConnectedState hooks. Use them sparingly if at all.

currently implemented hooks:

core hooks:

  • useReducer
  • useState
  • useEffect
  • useRenderer
  • useAttribute
  • useCSS
  • useExposeMethod
  • useConnectedState
  • useHostElement
  • useShadowRoot

other (only present in full bundles):

  • usePreactHtm

License

MIT License

Copyright (c) 2019 Michael Klein

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

funcy.js's People

Contributors

michael-klein avatar michaelklein-talkwalker avatar

Stargazers

Ethan Lew avatar Ivan Delaš avatar Libx avatar Alexey Duryagin avatar Michael Wang 汪東陽 avatar Natanael dos Santos Feitosa avatar  avatar Christoph Werner avatar Shuta Hirai avatar zhings avatar Antonio De Lucreziis avatar Valerii Baleiko avatar song wang avatar Keri Lynn avatar Fu AoQi avatar Lex Huang avatar Hamed Fathi avatar Fang Jin avatar Jeff Moore avatar Daniel Weck avatar Trần Bá Thanh Tùng avatar  avatar daoyuly avatar Nikolay Kost avatar branson avatar Chris Hart avatar Roman Hossain Shaon avatar  avatar cpprookie avatar Ezekiel Chentnik avatar rim avatar  avatar Acampbell avatar Shahrul Nizam Selamat avatar Mohammad Bagher Ehtemam avatar Andres Valderrama Barbosa avatar Victor avatar Rod Kisten (Costa) avatar 波比小金刚 avatar Yuji Sugiura avatar 伊撒尔 avatar  avatar Cristian Bote avatar PB avatar James Bell avatar John Ericson avatar Ray Yee avatar Wesley Luyten avatar Micro·J avatar Clayton Fidelis avatar Felipe Egas avatar Colton Colcleasure avatar Jan Daryl Galbo avatar Kim Bum June avatar Chad avatar Burak Sormageç avatar Haroldo de Oliveira Pinheiro avatar Neko Hz avatar Niklas Palm avatar  avatar Diel Duarte avatar Nikolas Evers avatar Denis Denisov avatar Jonas Winzen avatar Thomas Marrec avatar Richard Burk Orofeo avatar Radu Micu avatar nyz avatar Karan Ganesan avatar Luís Rodrigues avatar Martin Beierling-Mutz avatar Akshay Nair avatar Florian Goße avatar  avatar Joshua Alpuerto avatar Rick Wong avatar Will Urmston avatar Jake Burden avatar Gabriel Cueto avatar Marvin Hagemeister avatar Olivier Monnier avatar Thiago Brasil avatar Jon avatar Diogo Moretti avatar Nemanja Stojanovic avatar Vitor L Cavalcanti avatar Michael Geers avatar Oskar avatar d11n avatar Wonmin Jeon avatar Shubham Battoo avatar K.K. POON avatar Veit Lehmann avatar David Merrique avatar Lúcio Caetano avatar Julian David avatar Paweł Lesiecki avatar Andrew Chou avatar Sergey Petushkov avatar  avatar

Watchers

Daniel Weck avatar James Cloos avatar PB avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.