@bikky/koco
v0.0.2
Published
An edited version of the Knockout library.
Downloads
2
Readme
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-bind
ing 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.