0.0.2 • Published 5 years ago

visiejs v0.0.2

Weekly downloads
-
License
ISC
Repository
github
Last release
5 years ago

Visie.js

Basic JS reactive view library

This library was created for the purpose of gaining a better understanding of how more established and much larger frameworks like Vue.js function internally. At only a few hundred lines of code, and with documentation high up on the priority list, it may be able to assist people interested in learning the very basics about things such as reactivity and virtual DOMs.

Using these two not so complicated, but at first somewhat unintuitive concepts, even though it is a very small library, Visie is able to alleviate much of the pain usually associated with building dynamic web application interfaces, enabling you to create reusable custom elements, commonly known as Components, complete with their own state and logic encapsulated away from the rest of the application, that will automatically rerender themselves when needed. Component templates use a combination of the Handlebars template engine (https://handlebarsjs.com/) and directives interpreted by Visie itself.

Visie's reactivity, unlike Vues, is based on ES6 Proxies. These, on one hand, provide more features, the lack of which Vue users have to work around in often times unintuitive ways, but on the other hand are impossible to provide polyfills for, which means Visie absolutely requires ES6 to function. If you're looking for a library to use for more than just educational or hobbyist purposes, you would be much better served by a framework such as Angular, React or Vue.


Getting Started

You can install Visie with npm install visiejs Visie is a CommonJS module, so make sure that your environment supplies a functional implementation of the CommonJS require function.

One way to achieve this would be with Browserify. Browserify bundles all your code, including that of the modules you import, into a single .js file, which can then be run in the browser. To set this up, install Browserify using npm install browserify --save-dev and then add the following script to your package.json: "build": "browserify src/main.js -o dist/bundle.js"

... where src/main.js is the entry point of your application, and dist/bundles.js is the file Browserify will output to, which can then be loaded from any HTML document.


The Basics

Visie applications consist of an Instance and Components. Components contain all the state and logic they need to function, as well as a template describing their structure in HTML. The instance needs to be initialized with a DOM element which will become the root of the application.

If you've worked with Vue before, the following should look very familiar.

index.html

<!DOCTYPE  html>
<html  lang="en">
<head>
  <meta  charset="UTF-8">
  <meta  name="viewport"  content="width=device-width, initial-scale=1.0">
  <title>Visie.js</title>
  <script  src="build/bundle.js"  type="module"></script>
</head>
<body>
  <div  id="root">
    <!-- The Visie app will go in here -->
  </div>
</body>
</html>

main.js

// First, we have to require Visie
const  Visie  =  require("visiejs")

// Now, we can create an instance
const  v  =  new  Visie.Instance();

// We will initialize this instance as soon as the page fully loads
document.body.onload  =  ()  =>  v.init("root",  /* ID of the root element*/  {
  // This object is a component description
  template:
    `<div>
       <h1>{{message}}</h1>
       <p>
         <input type="text" visie-model="messageInputField">
         <button @click="message = messageInputField; messageInputField = ''">Change Message</button>
       </p>
    </div>`,
  data:  {
    message:  "Hello World!",
    messageInputField:  ""
  }
})

Once you execute npm run build and open index.html in a browser, you will be greeted with the h1, the text field and the button from the "template" member of the second argument to v.init. Try entering a new message and watch the application automatically rerendering when you click the button. Changing a member of data which is referenced in the template of a component will trigger that component to rerender. Those members can also be other objects or arrays which will also be made reactive automatically. The visie-model directive on the input field creates a two-way data binding between the its own value and the messageInputField member of the data object, meaning that changing either will automatically update the other. This is why we can simply set message to messageInputField to update the message, and messageInputField to '' to clear the field in the click handler of the button.

Event handlers can be added to elements by prepending an @ to the event type and setting an attribute with this name to a piece of JavaScript code to be executed when the handler is called. To make all this look a bit nicer, we can make the following changes:

main.js

// First, we have to require Visie
const  Visie  =  require("visiejs")

// Now, we can create an instance
const  v  =  new  Visie.Instance();

// We will initialize this instance as soon as the page fully loads
document.body.onload  =  ()  => v.init("root",  /* ID of the root element*/  {
  // This object is a component description
  template:
    `<div>
       <h1>{{message}}</h1>
       <p>
         <input type="text" visie-model="messageInputField">
         <button @click="updateMessage()">Change Message</button>
       </p>
     </div>`,
  data:  {
    message:  "Hello World!",
    messageInputField:  ""
  },
  methods:  {
    updateMessage()  {
      this.message =  this.messageInputField
      this.messageInputField =  ""
    }
  }
})

As you might have guessed, the methods object contains functions which are then callable from the template and each other, as well as on a component instance directly.

Speaking of which...

Components


Components can be created using the Visie.createComponent function, which takes a component description object as its only argument and returns a function to create such a component. You should only have to call this function manually extremely rarely. Lets look at an example.

main.js

const Visie = require("visiejs")

const Counter = Visie.createComponent({
  // Name of the component
  name: "Counter",
  // Properties to be pulled from the HTML placeholder element
  props: ["count"],
  template: `
    <p id="counter">
      <span>Site has been open for {{countInSeconds}}</span>
    </p>
  `,
  // This method is called once the component is initialized, but before its rendered
  created() {
    // Properties pulled from the HTML placeholder can be accessed via the 'props' object
    this.count = this.props.count
    this.start()
  },
  data: {
    count: 0,
    intervalId: null
  },
  methods: {
    start() {
      if(this.intervalId == null)
        this.intervalId = setInterval(() => this.count++, 1000)
    },
    stop() {
      if(this.intervalId != null) {
        clearInterval(this.intervalId)
        this.intervalId = null
      }
    }
  },
  // Object containing computed methods to be used in the template. Work like they do in Vue
  computed: {
    countInSeconds() {
      return `${this.count}s`
    }
  }
})

const  v  =  new  Visie.Instance();
document.body.onload  =  ()  => v.init("root", {
  template:
    `<div>
       <h1>{{message}}</h1>
       <p>
         <input type="text" visie-model="messageInputField">
         <button @click="updateMessage()">Change Message</button>
       </p>
       <Counter :count="0" key="counter"></Counter>
     </div>`,
  components: {
    Counter
  },
  data:  {
    message:  "Hello World!",
    messageInputField:  ""
  },
  methods:  {
    updateMessage()  {
      this.message =  this.messageInputField
      this.messageInputField =  ""
    }
  }
})

Okay, a couple of new things here.

  • To be able to use a component from within another component, it has to be a member of the parent components components object.
  • To use components, use an HTML element with their name as its type in the parent template.
  • Attributes on this element also listed in the component descriptions props array will be pulled into the components props object.

Also, you might have noticed the colon before the Counter's count attribute. This means that the attributes value will not be treated as a normal HTML attribute, but evaluated as JavaScript within the template context.

To preserve component state between parent rerenders, the component needs a unique key attribute. In the future, elements outside of loops will be assigned such a key automatically, but for now, you will have to manually assign them.

That's all for now, I will keep updating and extending this document.

0.0.2

5 years ago

0.0.1

5 years ago