0.0.2 • Published 7 months ago

@bikky/koco v0.0.2

Weekly downloads
-
License
-
Repository
-
Last release
7 months ago

Bikky/koco

A wrapper around Knockout that extends it to add some new functionality.

Koco adds a bunch of new features, including features that exist in a number of other well-known frameworks. Here's a list of how these properties differ to the well-known versions:

  • Double handlebars can work like knockout virtual elements (<!-- ko -->).
  • CSS is not bound specifically to the HTML in the same file like some other frameworks do. Instead you probably want to include the html tag in the css selector.
  • HTML tags are not checked for conflicts. However you will get errors in the console if you try to register a tag that already exists.
  • All of knockout's functions are available as normal, they are exported on the ko object: import {ko} from "@bikky/koco";

Features:

  • Allows you to specify bindings in code for new elements rather than requiring all bindings be specified in an html data-bind attribute.
  • "Inner" and "Outer" contexts.
  • "Inner" and "Outer" bindings.
  • Relative importing of HTML and CSS files.
  • Ability to specify HTML and CSS in the same file.
  • Events for keeping track of what files are loading for loading screens.
  • Handlebars in HTML.
  • Easy virtual elements.
  • New and improved bindings.

Getting Started / Example:

Usage of koco is very easy, simply create a new .html file which contains the html for a knockout template along with desired css (as below), and then register it using koco.load.

Each top-level tag in the html file will be registered with koco as it's own template. Any top-level <style> tags will be automatically added to the <head> of the document.

<quick-bar-slot inner-data-bind="css: {'highlighted': highlighted }">
	<p>{{name}}</p>
	{{ with: slot }}
	<sprite></sprite>
	{{ end with }}
</quick-bar-slot>

<quick-bar>
	{{ foreach: slots }}
	<quick-bar-slot>
	</quick-bar-slot>
	{{ end foreach }}
</quick-bar>


<style>
	quick-bar {
		display: flex;
		flex-direction: row;
	}

		quick-bar quick-bar-slot {
			width: 72px;
			height: 72px;
			border: 2px solid cornflowerblue;
		}

	quick-bar quick-bar-slot.highlighted {
		border-color: deepskyblue;
		background-color: blue;
	}

	movable-panel.quick-bar {
		height: 1in;
	}

</style>
import {koco} from "bikky/koco";
import {Quickbar} from "./Quickbar";

koco.html.load("./Quickbar.html", (params: koco.Params) => {
    return new Quickbar(params.component.element, params.context.$data);
});

Usage:

VMFactory

HTML load and register functions in koco allow providing a VMFactory parameter. This factory allows the user to change the viewmodel that is bound to the template, it is called each time the template is instantiated, and the returned value is bound to the template's children and inner bindings (see below).

The only parameter to the factory function is a Params object. This object contains the following properties:

interface koco.Params {
    params: any,
    component: { 
        element: Node;
        templateNodes: Node[];
    },
    context: ko.KnockoutBindingContext
}

Inner and outer context & bindings.

When using koco you have two opportunities to supply bindings to an element, when the element is defined in the HTML file you can supply bindings on the top-level element. These bindings will use the inner context which is the data that the VMFactory parameter returns (see above).

So when you're working in an html file where you want to import the template you can supply outer bindings which use the context of the parent element:

<game-screen>
 <canvas></canvas>
 <quick-bar data-bind="visible: showQuickbar"></quick-bar>
</game-screen>

In this case showQuickbar is a property of the game-screen's viewmodel.

When you're working in the template file you can supply inner bindings which use the context of the template's viewmodel:

 <quick-bar data-bind="css.width: (numSlots() * 72) + 'px'">
 {{ foreach: slots }}
 <quick-bar-slot>
 </quick-bar-slot>
 {{ end foreach }}
</quick-bar>

In this case the numSlots function is a property of the quick-bar's viewmodel.

You can get the inner context by calling koco.innerContextFor(element), and the outer context by calling koco.contextFor(element) or ko.contextFor(element).

Handlebars

Adds support for handlebars in the tag content. E.g.:

<tag>{{{ htmlContent }}}</tag>
<tag>{{ textContent }}</tag>
<tag>{{ text }} and some more: {{ text2 }}</tag>

Handlebars can also be used in place of paired virtual elements, when used this way instead of using /ko to end the scope, you use end keyword:

<quick-bar>
	{{ foreach: slots }}
	<quick-bar-slot>
	</quick-bar-slot>
	{{ end foreach }}
</quick-bar>

API

koco.innerContextFor()

Get the context for the elements within a template. To get the context for the outer element use ko.contextFor.

koco.innerContextFor(element: Node): any;

koco.applyBindingsTo()

Applies bindings to a node and all of it's children. The same as the knockout applyBindings function, but forces the node to be present and swaps the parameters for this purpose (to stop me from trying to remember which of the 5 knockout functions is the one I want).

koco.applyBindingsTo(node: Node, bindings: any): void;

koco.addBindingTo()

Adds a data-bind binding to a node programmatically. A wrapper for the internal addProgrammaticBinding function.

koco.addBindingTo(node: Node, bindingName: string, bindingValue: any): void;

koco.html

koco.html.load()

Loads a segment of HTML from a file and registers it as a knockout component. The HTML is registered with the names of each tag at the top level of the supplied HTML snippet. The names of each component that is registered are returned, and are also available through the onEvent function.

Will also automatically register any top-level <style> elements as though the file was loaded with css.load.

url - The full path to the HTML file to be registered as a component. The parent element must be the custom element that will be used to identify the component. vmFactory - The viewmodel function, receives a Params and should return the viewmodel that will be bound to the element. options - Any additional knockout options that you want to provide to the registration function.

async function load(url: string | URL, vmFactory?: VMFactory, components?: string[], options?: Options): Promise<string[]>;
async function load(url: string | URL, vmFactory?: VMFactory, options?: Options): Promise<string[]>;

koco.html.register()

Registers a string of HTML as a knockout component. The HTML is registered with the names of each tag at the top level of the supplied HTML snippet. The names of each component that is registered are returned, and are also available through the onEvent function.

Will also automatically register any top-level <style> elements as though the file was loaded with css.regiser.

text - The HTML to be registered as a component. The parent element must be the custom element that will be used to identify the component. vmFactory - The viewmodel function, receives a Params and should return the viewmodel that will be bound to the element. options - Any additional knockout options that you want to provide to the registration function.

function register(text: string, vmFactory: VMFactory, components?: string[], options?: Options): string[];
function register(text: string, vmFactory: VMFactory, options?: Options): string[];

koco.html.onEvent()

Register a function to be called when a koco event occurs (e.g. on request, load or registration of a resource). Designed for use in loading screen managers.

event - The name of the event to register for. component is called for every component that is registered, request is called for every file load request that is made, load is called every time a file load request has been completed. callback - The function to be called when the event is triggered.

function onEvent(event: "component", callback: (name: string, path: string) => void): void;
function onEvent(event: "request", callback: (path: string) => void): void;
function onEvent(event: "load", callback: (path: string) => void): void;

css

koco.css.load()

Loads a segment of CSS as a link in the document head. Will also return the names of the html components that use this style sheet when the css is included in the same file that an html element registered with html.load.

url - The full path to the CSS file to be registered as a component.

function load(url: string | URL): void;

koco.css.register()

Registers a string of CSS as with the document.

text - The CSS to be added as a style sheet.

function register(text: string): Promise<Event>;

koco.css.onEvent()

Register a function to be called when a koco event occurs (e.g. on request, load or registration of a resource). Designed for use in loading screen managers.

event - The name of the event to register for. component is called for every component that is registered, request is called for every file load request that is made, load is called every time a file load request has been completed. callback - The function to be called when the event is triggered.

function onEvent(event: "component", callback: (name: string, path: string) => void): void;
function onEvent(event: "request", callback: (path: string) => void): void;
function onEvent(event: "load", callback: (path: string) => void): void;

New & Improved Bindings:

Initialise

Calls the given function when the HTML element is instantiated:

<div data-bind="initialise: functionToCall"></div>

EditableContent

Sets up two-way databinding on the content of the element, requires isContentEditable to be true on the element.

<div data-bind="editableContent: property" isContentEditable="true"></div>

ForEachProp

Calls the given function when the HTML element is instantiated:

<div data-bind="foreachprop: object">
    {{ $key }}: {{ $value }}
</div>

Notes:

foreach: someExpression is equivalent to template: { foreach: someExpression }.

foreach: { data: someExpression, afterAdd: myfn } is equivalent to template: { foreach: someExpression, afterAdd: myfn }

Children and Child

A new binding handler that allows child nodes to be inserted without them being bound (for example if you've already bound them to another knockout context). Works with knockout arrays and subobjects :)

Can also be used in virtual elements! Woo!

This does mean you will need to call koco.applyBindingsTo on each child node before they will show automatic dom updates.

<!-- ko children: property --> <!-- /ko -->
<tag data-bind="children: property"></tag>
<!-- ko child: property --> <!-- /ko -->
<tag data-bind="child: property"></tag>

HTML

Extends the html binding to be able to occur on virtual elements (comments and handlebars):

<!-- ko html: property --> <!-- /ko -->
<tag>{{{ htmlContent }}}</tag>

Internal API

Warning: This library replaces a significant amount of the knockout internals by replacing certain internal functions with new ones. This means that it may not work with future versions of knockout. Also the knockout files for the correct version are included in this repository.

AddNodePreprocessor

Adds a function that gets called just before a node is initialised. Allows you to replace that node with another node or even a list of nodes.

addNodePreprocessor(callback: (node) => (NodeList | Node[] | null | void));

AddNodeBindingPreprocessor

Add a function that gets called just before a node is initialised. Allows you to change the inner or outer bindings (data-binding attributes) before they get processed.

type BindingAddFunction = (binding: { inner?: string, outer?: string }) => void;

addNodeBindingPreprocessor(
    callback: (node, addBinding: BindingAddFunction) => (NodeList | Node[] | null | void);
);

AddNodeBinding

A helper function for adding inner and/or outer bindings in the addNodeBindingPreprocessor callback.

addNodeBinding(node: Node, additionalBindings: { inner?: string, outer?: string }): void

AddProgrammaticBinding

Allows you to add inner or outer bindings to a node at runtime (in code). The binding doesn't use the normal node's viewmodel, instead it uses value directly as its' value.

addProgrammaticBinding(element: Node, bindingName: string, value: any): void;

SetupCustomBindings

Like ko.applyBindings but allows you to specify the exact context and bindings (as a string) that should be applied to an element. Can be called multiple times on the same node. This can be used to (for example) create a new attribute that supplies bindings and works along-side data-bind (e.g. global-bind) that then gets it's own context (e.g. global-bind might get a global viewmodel instead of the local one).

setupCustomBindings(element: HTMLElement, context: ko.KnockoutBindingContext, bindingsString: string): void;

Licence:

Knockout and its' files are licenced under the MIT licence.

The rest of this library currently has no licence. If you wish to use this code in your own project feel free to contact the author, they may provide you with a profit/non-profit licence at their discretion.

0.0.2

7 months ago

0.0.1

7 months ago