Git Product home page Git Product logo

reef's Introduction

Reef

A tiny utility library for building reactive state-based UI.

Reef is a simpler alternative to React, Vue, and other UI libraries. No build steps. No fancy syntax. Just vanilla JS and a few small utility functions.

Getting Started →

This code is free to use under the MIT license.

reef's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reef's Issues

Data driven reef components

Hi - reef looks really cool. I am just getting into it so this may already be possible. Be gentle...

I have a query / enhancement request around creation of complex HTML layouts.

My use case is a data-driven form. Lets say I get my form definitions from a server, and in the first case I have a title, four inputs and a submit button in the form, but the next form definition might have 5 inputs and 2 buttons.

Following your documentation for nested components, the initial reef object would need to reference the id's of each of the objects that the form definition requires me to produce. However, I do not know ahead of the execution what those id's will be.

As a parallel, in jquery, I would use .after(), .before(), .append() and .appendTo() to target new elements into the DOM in such a case.

I realise reef is intended to be kept skinny but I think maybe adding those 4 jquery-like functions, or at least .append() and .appendTo() would allow sophisticated component build-up via programmatic and therefore data-driven means.

A potential solution would be to parse the form definition and create speculative placeholder divs in the html for the initial reef object. However, my long term goal is to allow the form spec to be richer and allow nesting, in which case the optimum approach is the full append/appendTo method.

Sorry I am not skilled enough to fork the code and make my own attempt at a solution.

Regards.

Reef Not defined when attempting to import the router via ES Modules

I'm following the tutorial for using the ESM method cause I want to use snowpack and two things

https://codepen.io/k5nn/pen/oNjKYpM

  1. Importing Reef via ESM does not append to the window I need to add it manually

when you open up the inspect element the window object does not have Reef in it but when you add
window.Reef = Reef
before the console.log it appears

  1. Importing Reef Router via ESM returns the error Reef is not defined

Minor Issue with snowpack ( I don't know if I should bring it up here since it seems like the solution is for router.js to have it's own package.json but I might as well bring it up for future reference ) when attempting to do regular snowpack install

npx snowpack install
it only picks up reef not the reef router but when you need the router also you need to create your own config

snowpack-config.json
{ "install" : [ "reefjs" , "reefjs/router" ] }

then install with snowpack via
npx snowpack install --config ./snowpack-config.json

Doesn't render <td> and <tr> HTML elements

For some reason <td> and <tr> elements are not being rendered when returned from template function.

It happened when I tried to use Reef to render the content of <tbody> only. Once I tried to render the whole table structure, it worked well.

Server side/page functionality

Reef's got some awesome use cases already, and I'm willing to make the switch, but I'm not sure about it's compatibility with server side work or multiple pages. I'd like to be able to click a link and change to a new URL (displaying correctly in the address bar), while keeping the instant change/fast functionality.

I was thinking, maybe using the same page and main #app component, but for each page, attaching a different component and somehow changing the address URL for display/routing purposes. Makes me wonder if a fully fledged router (something cheesy like reef-router of course) could be considered.

Side question also, is there any sort of chat/discussion group that exists for Reef? Somewhere like Reddit or even Discord would be great.

Thanks for making something that can be picked up and used awesomely in just one day.

Using reef.polyfills.es causes "Proxy polyfill does not support trap 'deleteProperty'" error in IE 11

There is no codepen since it can't be tested in IE.

I was getting an error in IE when using the pollyfill version of Reef.

Unhandled promise rejection TypeError: Proxy polyfill does not support trap 'deleteProperty'

After some digging online it looks like most Proxy polyfills do not support deleting object properties. Only get and set. So for my situation I commented out line 166 through 170

deleteProperty: function (obj, prop) {
    delete obj[prop];
    debounceRender(instance);
    return true;
}

But then I was getting this error:

Unable to get property 'childNodes' of undefined or null reference

After debugging in IE, the error was on line 647, inside of the stringToHTML function. For reasons I couldn't figure out, the head element was returning a null value. For my situation I commented out the following if statement inside of the stringToHTML function.

if (support) {

  // Create document
  var parser = new DOMParser();
  var doc = parser.parseFromString(str, 'text/html');

  // If there are items in the head, move them to the body
  if (doc.head.childNodes.length > 0) {
    Array.prototype.slice.call(doc.head.childNodes).reverse().forEach(function (node) {
      doc.body.insertBefore(node, doc.body.firstChild);
    });
  }

  return doc.body;

}

Forcing Reef to use the old school method.

var dom = document.createElement('div');
dom.innerHTML = str;
return dom;

Update createDOMMap() to account for comments

/**
 * Create a DOM Tree Map for an element
 * @param  {Node}   element The element to map
 * @return {Array}          A DOM tree map
 */
var createDOMMap = function (element) {
	return Array.prototype.map.call(element.childNodes, (function (node) {
		return {
			content: node.childNodes && node.childNodes.length > 0 ? null : node.textContent,
			atts: node.nodeType !== 1 ? [] : getAttributes(node.attributes),
			type: node.nodeType === 3 ? 'text' : (node.nodeType === 8 ? 'comment' : node.tagName.toLowerCase()),
			children: createDOMMap(node),
			node: node
		};

	}));
};

And in makeElem():

// Create the element
var node = elem.type === 'text' ? document.createTextNode(elem.content) : (elem.type === 'comment' ? document.createComment(elem.content) : document.createElement(elem.type));

Reactive data (feature)

Reef looks good! I love anything that can do heavy lifting without loads of bloat, dependencies, and compiling.

I note that—and I think I'm correct in saying—an event is emitted when the render() function is used. But it would seem no event or 'reaction' occurs when the data itself is changed.

I read https://vuejs.org/v2/guide/reactivity.html and it appears Vue does this by converting the basic data object into getters and setters which emit events whenever the values change. I've been playing with something similar which just wraps assignment in functions to emit a customEvent: no need for setters or getters, and works with adding properties.

I solved this in just under 1K uncompressed: https://gist.github.com/Heydon/9de1a8b55dd1448281fad013503a5b7a. But I wonder if there is a better way. Have you considered adding this facility, so that authors could (conditionally) subscribe to changes in the data object?

Removal of properties that shouldn't be

Currently, dynamic form properties that are not defined in a template (like value or checked are removed if the state updates).

Look in to how to effectively "ignore" properties when diffing if they're not defined in the template.

var app = new Reef('#app', {
	data: {
		submitTxt: 'Submit'
	},
	template: function (props) {
		return `
			<form>
				<label for="username">Username</label>
				<input type="text" id="username">

				<label for="password">Password</label>
				<input type="password" id="password">

				<button>${props.submitTxt}</button>
			</form>`;
	}
});

app.render();

document.addEventListener('submit', function(event) {
	event.preventDefault();
	app.setData({submitTxt: 'Submitting...'});
});

In this example, the fields would get wiped out when the form is in submitting state...

For my own reference: search this page for if present to find out which fields this applies to.

Template literals

I like the basic ideas and line of thinking behind this library, but I'd like to suggest you consider using template literals.

The approach of internally serializing all values to HTML strings, and then parsing them and turning them back into HTML elements, isn't very efficient.

Not having access to the original data within the template isn't very practical - for example, if you have a Markdown string you want to render, you first need to unescape the HTML value, which didn't need to be escaped in the first place.

Having a single option allowHTML to disable the encoding of all of the data isn't very helpful either, since a single component could contain a mix of data types: raw strings, HTML, Markdown, numbers, booleans, objects, etc.

(It looks like you've gone back and forth on the idea of pre-escaping data between major releases of the library, so I guess you're aware of some of the problems with this approach.)

Also, the strings approach is limited to updating HTML serialized string-attributes, as opposed to updating properties of HTML element objects, e.g.:

template: ({disabled}) => '<input ' + (enabled ? "" : "disabled") + '>'

As opposed to e.g.:

template: ({disabled}) => html`<input disabled=${ !enabled }>`

In practical terms, you might consider using (or supporting or integrating) something like htm, which generates fast rendering functions.

Alternatively, you might consider writing a basic HTML5 parser, which is surprisingly easy and would allow you to completely bypass unnecessary encoding/decoding of HTML - while allowing natural things like inline event-handlers, style object literals, booleans, etc. like JSX allows, but using browser standards.

I do realize this precludes native IE support (without transpiling) but at some point 94% has to be good enough, right? 😉

In summary, the biggest problem with this library, as I see it, is the idea that everything must be turned into strings before it can get to the DOM - this isn't efficient or practical and, in a sense, the barrier between template functions and the browser object model currently is almost as high as it is between the browser and the server; in my opinion, with a client-side library, the template needs to be much closer to the DOM/BOM than this.

Also, per the README, "It doesn't have a Virtual DOM", which is true as far as the external API - but it does internally use the equivalent of a virtual DOM model.

In terms of the total round-trip from template to DOM, this is effectively more steps than a virtual DOM: from data values to HTML-escaped strings, to DOM objects via DOMParser, to your internal "details" model (the "virtual DOM") and then finally diffing to the live DOM objects.

Compare this with a traditional virtual DOM approach, which doesn't have the escaping or DOMParser steps - which makes me think, with something like htm in the mix, this would already be faster, simpler, and much closer to the DOM, in terms of templates mapping directly to HTML object properties, rather than taking the whole HTML escaping/parsing round-trip. (Also, htm can be optionally compiled away for even smaller footprint, if desired.)

If you want to truly avoid the virtual DOM step internally as well, as mentioned, you might consider a custom HTML5 parser - which might mean a bit more initialization overhead per template, but should yield substantially faster updates overall. (I'm happy to post a bit more information on this idea, if requested.)

Either of these approaches could also pave the way for proper support for components, possibly with life-cycles. (I know your stated mission is for this library to be an anti-framework, but have you tried manually managing the life-cycles of nested components? If every child component is a global instance as shown in the README, that's easy - but if you have something like a custom drop-down or date-picker, and have to manually manage the life-cycles of component instances, this quickly gets out of hand.)

I think you have some nice ideas, but the basic idea of treating everything as strings causes too many practical problems.

Anyhow, this is just my personal analysis after some quick testing. Cheers! 🙂

Needs npm package

Hi Chris, I'm following your work on reef and it looks really nice and lean. I'd like to npm install this but 'reef' is being used by another package. Curious if you could mention your plan here. Maybe scoping, @cferdinandi/reef?

Add redirect URLs

var router = new Reef.Router({
	routes: [
		{
			id: 'home',
			title: 'Home',
			url: '/'
		},
		{
			id: 'about',
			title: 'About',
			url: '/about'
		},
		{
			url: '/contact-us',
			redirect: '/contact'
		},
		{
			id: 'contact',
			title: 'Contact Us',
			url: '/contact'
		}
	]
});

Reef pollyfill version throws "Invalid operand to 'in': Object expected" error in IE11

Getting the following error in IE11 using the polyfill version of Reef.

Unhandled promise rejection TypeError: Invalid operand to 'in': Object expected
   "Unhandled promise rejection"
   {
      [functions]: ,
      __proto__: { },
      description: "Invalid operand to 'in': Object expected",
      message: "Invalid operand to 'in': Object expected",
      name: "TypeError",
      number: -2146823281,
      stack: "TypeError: Invalid operand to 'in': Object expected
   at stringToHTML (eval code:755:5)
   at Reef.prototype.render (eval code:814:3)
   at Anonymous function (eval code:738:28)
   at Anonymous function (eval code:32:7)
   at forEach (eval code:16:3)
   at renderPolyps (eval code:736:3)
   at Reef.prototype.render (eval code:818:3)
   at displayData (eval code:174:3)
   at _callee$ (eval code:63:13)
   at tryCatch (eval code:43:7)"
   }

For me at least, the str argument being passed into stringToHTML is empty.

In the diff method, empty node content and null content is being detected as a difference

if (templateMap[index].content !== domMap[index].content) {

If the content in the template is null and in the dom is '' it would get detected as a difference and would reassign the dom node textContent when it shouldn't, right?

Maybe the validation should check that at least one of those contents is meaningful?:

if (templateMap[index].content !== domMap[index].content && (templateMap[index].content || domMap[index].content))

Btw: awesome work with this library, it allowed me to understand a lot of "state + template" things!

Introducing.... Reefer ;)

Hey Chris Thoroughly having fun with ReefJS ... (just discovered it earlier this week! :))
.... and had a fun notion for making it declarative.

    <script type='reef/hello' rfp-greeting='Yo :) Yo'></script>
    <script type='reef/wizard' rfp-selected='neville'></script>
    <script type='reef/typer'></script>
    <script type='reef/nested'></script>
    <script type='reef/todos' rfn-target='#todos'></script>

Check out: https://codepen.io/sreekotay/project/editor/ZvkWPj# 

hello-world.html is just your simple hello world. 
hello-world-reefer.html is what it looks like with Reefer! (declarative reef).

Then next-world-reefer.html just shows your other examples with reefer.

I'll likely add store support and then release as a standalone project. Note that it works with vanilla Reefer -- no changes required (though I have a few suggestion)...

Not really an issue - but curious for thoughts/suggestions...

Test case: https://codepen.io/sreekotay/project/editor/ZvkWPj#

event-handlers are stripped from template

Using an event-handler inside a template doesn't work

<li class="list-item" onclick="clicked('${item}')">${item}</li>

where 'item' == 'image/test.jpg'
is transformed to:

<li class="list-item">image/test.jpg</li>

Aria attributes are sanitized out

Allow exception:

/**
 * Check if setAttribute() should be used for this attribute
 * @param  {String} att The attribute type
 * @return {Boolean}    Returns true if setAttribute() should be used
 */
var useSetAttribute = function (att) {
	return ['data-', 'aria-'].indexOf(att.slice(0, 5)) > -1 || attributeExceptions.indexOf(att) > -1;
};

Transition between states

Good day! The awesome plugin you made, thanks! What way do you recommend to implement transition (animation) between state changes?

Update DOMParser

The DOMParser() method lacks IE9 support for text/html. Use application/xml instead.

/**
 * Convert a template string into HTML DOM nodes
 * @param  {String} str The template string
 * @return {Node}       The template HTML
 */
var stringToHTML = function (str) {
	var parser = new DOMParser();
	var doc = parser.parseFromString('<dov>' + str + '</div>', 'application/xml');
	return doc.firstChild;
};

domParser performance

Consider switching from domParser to document.createRange().createContextualFragment() on modern browsers.

With small html strings the performance increases are quite large. The attached test shows createContextualFragment completing in around 10% of the time it takes domParser.

For large test strings (like an entire webpage) there is smaller but still significant gains (createContextualFragment completing in 65-85% of the of the time it takes domParser).

(all numbers from chrome 79).

https://jsperf.com/html-string-to-dom-node

Reef with reactive CSS background-image

Hi Chris!

First thanks for this lightweight tool for the toolbox! I run into a bit of trouble with updating elements with a background image. Reef does not seem to like inline CSS, even with allowHTML set to true.

Consider my test case: https://codepen.io/Reforced/pen/vYNaPeN

As you see it keeps "jumping". Using inline style="background-image: url('${data.images[0]}');" does not work; it seems to be getting escaped to just https, without anything of the URL.. Any ideas about how to approach this better?

Todo App demo - checkbox not ticked on first click

The demo for the Todo App (http://jsfiddle.net/cferdinandi/cm0qLyzu/2/) is not working properly on Chrome 69 on Mac OS.

  1. Add a new item, e.g. "Learn Reef"
  2. Learn Reef appears as an unchecked checkbox
  3. Click on checkbox
  4. Checkbox remains unchecked, but Learn Reef is crossed out
  5. Click on checkbox again
  6. Checkbox is now checked, and Learn Reef is not crossed out

So the checkbox and the crossed out effect (.completed) is out of sync with each other. I had a look at the fiddle but not sure what is wrong because you do add the 'checked' html attribute if needed.

BTW I love your daily newsletters.

Router : Entry points not valid on refresh

In the Examples particularly the routing section when the I try to refresh the page and I'm not on the Home it returns a 404 when I refresh it on the '/contact' and the '/about' route but when I refresh it on the '/' route it passes

onclick attributes sanitized out

First off, thanks for creating a frontend UI library I actually enjoy using :)

Picked it up in about 30min following the great documentation and it'll work for most of my projects.

The potential bug I found is here (onclick attributes being stripped on data updates): https://codepen.io/anon/pen/WPPZjB

To reproduce:

  1. Click the heading multiple times to see the timestamp update
  2. Uncomment the Reef.setSanitizer function
  3. Click the heading multiple times to see the timestamp only update once

Maybe it's similar to issues #12 & #9 ?

Can't set multiple data properties at once

The new method for updating data makes my code a lot smaller but there's a few instances where I would like to set multiple properties at once to avoid re-rendering the templates many times.

New (render template multiple times):

unsplashImageList.app.data.mode = 'list';
unsplashImageList.app.data.data = results;

Old (render template once):

unsplashImageList.app.setData(
    {
        mode: 'list',
        data: results,
    }
);

Another example is setting the default properties for the app. My app has the option to reset the app properties so I was previously using:

ninjaData.setData( { ...ninjaDataDefault } );

but switching to:

ninjaData.data = { ...ninjaDataDefault };

does not work, which suggests I will need to set each property individually, and this will render each component 7+ times.

How do you test your Reef app?

Hey there 👋,

I'm taking a look at Reef and so far I'm convinced by the approach. I wonder how you test your Reef app though, or what is the recommended way to do it? I think an entry in the documentation would be valuable.

Keep up,
David

Question : Fetching data from Remote API then append to DOM

I think I'm doing it wrong

https://codepen.io/k5nn/pen/oNjKYpM

so basically when ever I update the DOM with the fetch results via appendChild() it fetches the data again I'm thinking of a way to suppress the fetch when ever the DOM updates post fetch but I can't think of anything

sidenote : do you accept donations cause I want to support this project in some way shape or form and I'm not really confident in my coding skill to contribute in a project as big as this

Template String Conversion into HTML Nodes drops <style> Nodes

This is due to DOMParser.parseFromString(template) puts any <style> tags into the <head> of the returned document. When Reef's internal stringToHTML function returns doc.body the markup in the head is lost.

Test case: https://codepen.io/semmel-semmel/pen/LYYLPym
When clicking the button reefApp.setData will update the custom element's dom, however it looses the inline styles inside the web component, because stringToHTML drops all <style> nodes. Thus text looses it's colour.

A fixed in stringToHTML could be

// ...
// If DOMParser is supported, use it
if (support) {
	var parser = new DOMParser();
	var doc = parser.parseFromString(str, 'text/html');
	doc.body.prepend(...doc.head.childNodes); // <--- Added this line
	return doc.body;
}
//...

Does this make sense?

Feature: Efficient update adjacent nodes

Great project. Love the DomDiff.
One thing that I think may be improved is the updating of same-kind adjacent nodes. Right now, if I insert (for example) a new LI node at the beginning of a UL list, since the diff algorithm doesn't has any context, it doesn't knows that the list can be moved down instead of have to update every single LI element AND adding a new LI at the end.

On a library I'm working on (not related) I faced the same problem with objects, where I could not equate them using ==, so I had to resort to some identifier of the object (usually a property named id).

I think that maybe you can also use the same approach, by using a custom attribute/data attribute. What are your thoughts on this?

Home route fails if not listed last

Via @ThatTonybo: #39 (comment)

Might of found the issue, which seems to be route positions:

	{
		id: 'about',
		title: 'About',
		url: '/about'
	},
	{
		id: 'contact',
		title: 'Contact Us',
		url: '/contact'
	},
	{
		id: 'home',
		title: 'Home',
		url: '/'
	}

Something like this works as the / is at the bottom, but:

	{
		id: 'home',
		title: 'Home',
		url: '/'
	},
	{
		id: 'about',
		title: 'About',
		url: '/about'
	},
	{
		id: 'contact',
		title: 'Contact Us',
		url: '/contact'
	}

As / is at the top, it fails to get to any other routes. Tested on 7.1.5 as just released.

When some component's DOM element transitions from "empty" to "some content", it goes to the parent element instead

Something strange is happening when a DOM element inside a component transitions from "empty" to "some content": that "some content" is appended to the parent instead of being appended to the element.

Here's a simple CodePen: https://codepen.io/rdromao/pen/MWWPrGz

I think this line:

elem.appendChild(fragment);

... should be:
domMap[index].node.appendChild(fragment)

Right? 🤔

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.