cahir v0.0.9
Cahir is a function proxy that allows you interchangibly use tagged templates and method calls. You can use it as an imperative framework with declerative bits. You define the routines and you define the shortcuts.
The famous TODO wars:
Cahir is an imperative library, but still if you are wondering, this is how it works:
The characters you use for method calls are completely customizible:
__init__: Cahir.tagify({
strTransform: str => str
.trim()
.replace(/^\/>\s*/,"")
.replace(/^\|>/, "pipe")
...
.replace(/^👊/gi, "runtime")
.replace(/^👈/gi, "appendTo")Since Ch is a Proxy, you get to define how it reacts to keys that it does not have:
ch.div //logs a div DOM object
ch.li //logs a lig objectCan even do:
ch[`li{
"attr":[["data-x", ${15}], ["data-y", ${0}]],
"prop":[["a",3],["b",2],["innerHTML", ${
`"<span>Hello World!</span>"`
}]]
}`] //logs a li with data-x, data-y attributes with span as childWhat are the advantages?:
- Create reusable
tagged templates viach.pickle - Allow arbitrary method names to be intercepted
- Use tagged templates to their full potential by allowing marking/spreading
values like namespaced variables - Interchange between tagged templates and method calls:
`method1 ${arg1}`.method2(arg2)`method3 arg3 ...${rest}`- Create shortcuts to your methods and use them as operators like
->,|>,+->in your templates. You can use any character as you want
Browser/Installation
regular script
include the base script and your custom method collections (or the ones included in the collections folder of the repo):
<script src="https://cdn.jsdelivr.net/npm/cahir@0.0.9/dist/cahir.0.0.9.evergreen.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cahir@0.0.9/collections/DOM/ch.0.0.9.js"></script>es module browser
for components
<script type="module" src="./components/simpleChat/simple-chat.0.0.9.es.js"></script>or for DOM library only
<script type="module" src="./collections/DOM/ch.0.0.9.es.js"></script>or for just Cahir to construct your own method chaining/tagged template library
<script type="module" src="./dist/cahir.0.0.9.evergreen.es.js"></script>npm module
npm i cahirUsage
common js
const ch = require("cahir/collections/DOM");
const Cahir = require("cahir");es module
import ch from "cahir/collections/DOM";
import Cahir from "cahir";Web Components
For a set of native web components, see components
Node/Bun
npm i cahirpnpm add cahirExample
//prepare your function
const ch = new Cahir({
__init__: Cahir.tagify({
strTransform: str => str
.trim()
.replace(/^\+\s*/,"add")
.replace(/^x\s*/, "multiply")
.replace(/^\|>/, "pipe")
})(function (...args) {
if (args.length <= 1) {
this.currentNumber = args[0];
return this;
}
return this[args[0]](...args.slice(1))
}),
add: function(a) {
return this(this.currentNumber + a)
},
multiply: function(a) {
return this(this.currentNumber * a)
},
pipe: function(command, ...args) {
this[command].apply(this, [...args]);
return this;
}
})
//usage
ch(1)`
|> add two:${2}
x ${({values}) => values.two}
+ ${-10}
`Logging ch.curentNumber will result in -4. You can save the template and run it later with other parameters:
const pickle = ch.pickle`
|> add two:${2}
x ${({values}) => values.two}
+ ${-10}
`
ch(5)(pickle); //ch2.currentNumber === 4
ch(7)(pickle); //ch2.currentNumber === 8Usage
1) Start with a base function, this will be your primary method. Below function sets an arbitrary property selected on itself if given a single argument, otherwise do a method call:
function base (...args) {
if (args.length <= 1) {
this.selected = args[0];
return this;
}
return this[args[0]](...args.slice(1))
}2) Use the static tagify property on Cahir to convert the function for dual use (regular method calls + tagged templates)
const
tagger = Cahir.tagify(),
taggified = tagger(base);3) Provide the resulting function inside a configuration object with __init__ key to Cahir
const ch = new Cahir({
__init__: taggified,
method1: function(){...}
method2: function(){...}
...
})4) Now you can interchange between tagged templates and normal method calls:
ch(some_var).method1()`
method1 ${arg1}
method2 string-arg1 ${arg2}
`method2(arg1, arg2)(some_other_var)`
...
`5) You can use spread sytax within tagged templates:
ch`
method ...${some_array}}
...
`6) You can mark values within literals to be used later:
ch`
method my_field:${some_variable}
method2 string-arg1 ${({values}) => /*do something with values['my_field']*/}
`Values in literals can be anything, with a special case for functions. If you pass a function object, it will be given the arguments:
{
thisArg, // Proxy: the proxy `ch` object
self, //Any: current value in literal
index, //Number: current index in literal in order of appereance
values, //Array: all the values in the literal
strings, //Array: all the strings in the liteal
stringsTransformed //Array: all the strings after transformations applied
}7) If you want to pass a function to a method, use a function that returns a function:
ch(...)`
method1 ${({thisArg, values, ...}) => () => /*this function is passed to method1*/}
`If you want to label a function, use an object:
ch`
some_method some_label: ${await import("/path/to/some_script.js")}
`ch`
method1 comparer:${{ en: new Intl.Collator("en").compare }}
method2 ${({values}) => values.comparer.en(a, b)}
...
`8) Cahir.tagify static method accepts optional arguments:
delim(string): used for splitting strings to seprate methods and string argumentsstrTransform(function): transform the literal strings before evaluation during parsingvalTransform(function): transform the literal values before evaluation during parsing
Default values are reasonable, using a custom strTransform allows you to define method shortcuts:
const ch = new Cahir({
__init__: Cahir.tagify({
strTransform: str => str
.trim()
.replace(/^->/gi,"method1")
.replace(/^=>/,"method2")
.replace(/^\|>/, "method_pipe")
})(function (...args) {
//your function
}),
method1: function(...args) {
...
},
method2: function(...args) {
...
},
...then:
ch`
-> ${arg1}
=> second:${arg2} |> method1 ...${[arg3, arg4]}
=> ${({values}) => ++values.second}
...
`9) Cahir accepts __intercepApply__ and __interceptGet__ properties on the configuration object. These 2 functions are called with:
- the
chproxy asthis nextto be called with no arguments, if you do NOT want to interceptpropwhich is the requested propertyreceiver, same as the get handler for Proxies
const ch = new Cahir({
__init__: taggified,
method: function(){...}
__interceptGet__: function (next, prop, receiver) {
switch (true) {
case (!(prop in this)):
return this.method.bind(this, prop)
}
next();
}
})In above, any method that is not defined on ch will call method with prop. So ch.some_prop_string(arg1) would be the same as:
ch.method("some_prop_string", arg1)Webcomponents
Inspect app code on card game example where each game card is a webcomponent:
ch`
<game-card ${{ data: {values, d} }}/>
+< ${values.cont}`.selectedAbove creates a web component <game-card/>, appends it to values.cont and returns the component (via .selected). The string <game-card... is converted to a web component using strTransform at ch:
...
strTransform: str => str
.trim()
.replace(/^<((?:[a-z]+-+[a-z]*)+)/,"wc $1")
...Above calls wc (webcomponent) method on ch, which in turn calls game-card method that is adopted earlier:
adopt ...${[
"game-card",
(await import(
"./component-game-card.js"
)).render
]}component-game-card passes the user defined data to other downstream functions such as render-card
When creating webcomponents, you can provide several properties
ch`<game-card ${{
attrs = [["data-x", "y"], ...],
styles = [["width", "auto"], ...],
props = [["x", x], ["y", y], ...],
data = "Any arbitrary value",
innerHTML = Function|String,
select = truthy|falsey
}}/>`All these are passed to method game-card or whatever name you assigned during adopting.
wc implementation of ch uses Symbols to detect and call connectedCallback only ONCE. To detect multiple additions/removals from DOM, you can use custom functions or MutationObserver. Or you can define your custom wc.
Credits
Card data for the website was taken from gwent.one.
Contributions
Ch implementations
You can help documenting different Ch implementations under collections using JSDoc syntax
PRs
Reasonable PRs are always welcome:
For Cahir itself:
- state the bug/feature clearly
- state what the proposition does
- submit few test cases
For method collections:
- state what the collection is aimed at (DOM manipulation, data visualization, calculations etc.)
- explain method briefly in
JSDocsyntax - include examples if possible
