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

frampton-dom

v0.1.8

Published

A virtual-dom module for frampton.js

Downloads

6

Readme

Frampton-DOM

Build Status

A virtual DOM library for use with frampton.js

Install

npm install --save frampton
npm install --save frampton-dom

Include

<script src="frampton.js"></script>
<script src="frampton-dom.js"></script>

Building DOM

A virtual DOM is a tree. A valid virtual DOM must be rooted to a single node. Nodes are created with functions corresponding to HTML elements.

const { div, text } = Frampton.DOM.Html;

// Create a div
const el = div({ class : 'test' }, [ text('hello') ]);

Each function, with the exception of text, takes two parameters. The first parameter is an object of attributes to apply to the element. The second is an array of children. The text function creates text nodes and only takes the string to display.

Transitions

You can declaratively apply transitions to elements.

const { div, text } = Frampton.DOM.Html;

// A transition can be classes to add, or styles to apply
const insertTransition = {
  class : 'attachment-preview-in'
};

const removeTransition = {
  class : 'attachment-preview-out'
};

const attributes = {
  transitionIn : insertTransition,
  transitionOut : removeTransition
};

const el = div(attributes, [ text('hello') ]);

Transitions can take several forms. In the above code the given classes are added at the appropriate time, when adding or removing the elements form the DOM. You can describe more exact transitions.

const transition = {
  from : {
    class : {
      remove : ['class-to-remove'],
      add : ['class-to-add']
    },
    style : {
      height : '100px'
    }
  },
  to : {
    class : {
      add : ['more-to-add']
    },
    style : {
      height : '0px',
      duration : '300ms'
    }
  }
}

If you pass a string to the 'class' key it is assumed to be class(es) to add. The 'from' block is used to reset the element before the transition is run. The 'from' and 'to' blocks are optional. If you don't use the 'from' and 'to' blocks the transition you describe is essentially a 'to' block without a 'from' block.

Rendering

To apply your virtual DOM to the actual DOM use the update function.

const update = Frampton.DOM.update;
const { div, text } = Frampton.DOM.Html;
const rootElement = document.body;

const newTree = div({ class : 'test' }, [ text('hello') ]);

// The update function takes a root element to attach your virtual DOM to, the
// old virtual DOM to diff against and the new virtual DOM to apply.
// Initially we don't have an old tree.
update({
  rootNode : rootElement,
  oldTree : null,
  newTree : newTree
});

Scenes

Usually you'll want to use the scene function to schedule your updates. A virtual DOM scene is an abstraction above the update function. You give the scene function a root node to attach the scene to and it returns a function to schedule updates of that scene. It will hold a reference to the existing virtual DOM tree in order to perform diffs. The scheduler function returned schedules updates to the DOM based on requestAnimationFrame. If you request more than one update before the next animation frame it will only run one update, the last one requested. This essentially throttles your updates to only run the needed updates so you don't have to worry about optimizing your code.

const scene = Frampton.DOM.scene;
const { div, text } = Frampton.DOM.Html;
const rootElement = document.body;

const scheduler = scene(rootElement);

const tree = div({ class : 'test' }, [ text('hello') ]);

scheduler(tree);

const newTree = div({ class : 'test' }, [ text('hello world') ]);

scheduler(newTree);

Events

Event handling in Frampton is usually done with event delegation. You attach event handlers to selectors and Frampton delegates for you.

const onSelector = Frampton.Events.onSelector;

// clicks :: Signal DomEvent
const clicks = onSelector('click', '.submit-button');

This will obviously still work with the Virtual DOM. No changes needed. You can also apply event handlers directly to the Virtual DOM. When applying event handlers to the Virtual DOM what you are really doing is applying a mapping function to the event object. The value returned from the mapping function is returned to you in a global callback for the Virtual DOM.

This global callback is given as a parameter to the update or scene function.

const scene = Frampton.DOM.scene;
const { div, text } = Frampton.DOM.Html;
const rootElement = document.body;

const globalCallback = (node) => {
  console.log('an event happened on: ', node);
};

const scheduler = scene(rootElement, globalCallback);

const clickHandler = (evt) => {
  evt.preventDefault();
  evt.stopPropagation();
  return evt.target;
}

const tree =
  div({ class : 'test', onClick : clickHandler }, [
    text('hello')
  ]);

scheduler(tree);

This becomes more useful when you handlers map the events to semantic messages that mean something to your application.

const scene = Frampton.DOM.scene;
const { div, input } = Frampton.DOM.Html;
const rootElement = document.body;

const globalCallback = (message) => {
  switch(message.type) {
    case 'RunSearch':
      const url = `http://fake.com/api/search/${message.data}`;
      $.get(url).then(handleSuccess, handleError);
      break;

    default:
      console.log(`Unknown message received: ${message.type}`);
  }
};

const scheduler = scene(rootElement, globalCallback);

const inputHandler = (evt) => ({
  type : 'RunSearch',
  data : evt.target.value
});

const tree =
  div({ class : 'search-wrapper' }, [
    input({ type : 'text', class : 'search-box', onInput : inputHandler })
  ]);

scheduler(tree);

Frampton.DOM.map

You can also map values before they are passed to the global event handler. Say you have a child view whose events you want to tag with some indicator of what parent view they belong to.

const scene = Frampton.DOM.scene;
const { div, header, input } = Frampton.DOM.Html;
const rootElement = document.body;

const mapping = (message) => {
  return {
    type : 'HeaderSearch',
    data : message.data
  };
};

const globalCallback = (message) => {
  switch(message.type) {
    case 'RunSearch':
      const url = `http://fake.com/api/search/${message.data}`;
      $.get(url).then(handleSuccess, handleError);
      break;

    case 'HeaderSearch':
      cosnt url = `http://fake.com/api/header-search/${message.data}`;
      $.get(url).then(handleSuccess, handleError);
      break;

    default:
      console.log(`Unknown message received: ${message.type}`);
  }
};

const scheduler = scene(rootElement, globalCallback);

const inputHandler = (evt) => ({
  type : 'RunSearch',
  data : evt.target.value
});

const search =
  div({ class : 'search-wrapper' }, [
    input({ type : 'text', class : 'search-box', onInput : inputHandler })
  ]);

const tree =
  header({ class : 'main-header' }, [
    div({ class : 'header-search' }, [
      map(mapping, search)
    ])
  ]);

scheduler(tree);

Finding the Difference in Trees

Finding the diff between objects can be expensive. We try not to run a diff if we don't have to. If two nodes are the same reference they are assumed to be the same and no changes are made. In fact we only go through the expense of diffing all the keys in a node if we are alerted that they are intended to be the same node. If two nodes are different references and there is no indication that they are meant to represent the same DOM node we replace the old node with the new node. To indicate that nodes in two virtual DOM trees are intended to be the same they must have the same id or the same key. Id is the usual DOM attribute and will be applied to the resulting DOM node. Key is a special parameter that will not be applied to the result DOM node. It is only used for diffing purposes.

const update = Frampton.DOM.update;
const { div, text } = Frampton.DOM.Html;
const rootElement = document.body;

// By using the same keys for each tree the nodes will just be updated.
// If the keys were different the DOM nodes would be replaced.
const treeOne =
  div({ key : 1, class : 'test' }, [
    div({ key : 2}, [ text('hello') ]
  ]);

const treeTwo =
  div({ key : 1, class : 'test' }, [
    div({ key : 2}, [ text('hello world') ]
  ]);

// The update function takes a root element to attach your virtual DOM to,
// the old virtual DOM to diff against and the new virtual DOM to apply.
// Initially we don't have an old tree.
update(rootElement, treeOne, treeTwo);