├── .github └── logo.png ├── .gitignore ├── LICENSE ├── README.md ├── ROADMAP.md ├── dist.sh ├── docs ├── README.md ├── api.md ├── getting_started.md ├── installation.md └── reactivity │ ├── README.md │ ├── computed.md │ ├── conditionals.md │ ├── directives.md │ ├── mark.md │ └── reactive_values.md ├── example └── 01 │ ├── README.md │ └── index.html ├── index.ts ├── package.json ├── postpub.sh ├── pubwebsite.sh ├── setup.sh ├── tests ├── index.html ├── templates.html ├── test.computed.html ├── test.conditionals.html ├── test.css ├── test.directives.html ├── test.html ├── test.js ├── test.loops.html └── test.templates.html ├── tsconfig.json ├── website ├── assets │ ├── banner.png │ ├── fonts │ │ ├── press-start-2p-latin-ext.woff2 │ │ ├── press-start-2p-latin.woff2 │ │ ├── vt323-latin-ext.woff2 │ │ └── vt323-latin.woff2 │ ├── grid.svg │ ├── heart.png │ ├── index.css │ ├── noise.png │ └── sb.png ├── index.html ├── index.js └── inventory.html └── yarn.lock /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18alantom/strawberry/488c9ba5d1b2a32d7d991f5d7454f9337e141df4/.github/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /index.js 3 | /index.html 4 | /perf.html 5 | /temp 6 | **/temp.* 7 | notes.md 8 | /dist 9 | 10 | **/*.min.js 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023-present, Alan Tom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > 3 | > After nearly 2 years of no commits. I've decided to archive Strawberry. 4 | > Should you be curious enough to wonder why, here's a post on [why it started, why it stopped, any why the decision was made](https://18alan.space/posts/archiving-strawberry.html). 5 | 6 |
7 | 8 | strawberry logo 9 | 10 | Zero-dependency, build-free framework for the artisanal web. 11 | 12 | [Website](https://18alan.space/strawberry/) · [How it works](https://18alan.space/posts/how-hard-is-it-to-build-a-frontend-framework.html) · [Docs](https://github.com/18alantom/strawberry/tree/main/docs) 13 | 14 |
15 | 16 | > **Warning** 17 | > 18 | > Strawberry is in an experimental phase. Everything stated below works, but I 19 | > am still figuring out the quickest and cleanest ways of doing things. 20 | 21 | --- 22 | 23 | > Seriously, another frontend framework? 24 | 25 | Yes, but, Strawberry is not like the usual frontend-framework. 26 | 27 | It doesn't have any dependencies. It doesn't need a build-step to run. It's 28 | tiny, less than 3KB when gzipped. Yet it does a lot of the core things the big, 29 | spangly frameworks can do. 30 | 31 | ```html 32 | 33 | 36 | 37 | 38 | 41 | 42 | 43 | A plum colored p element! 44 | 45 | 46 | 49 | ``` 50 | 51 | [Here's](https://18alan.space/strawberry/#inventory-example) a live example from 52 | the website. 53 | 54 | --- 55 | 56 |
57 | 58 | **Index** 59 | 60 | [Installation](#installation) · [Features](#features) · [Examples](#examples) · [Development](#development) 61 | 62 | [Docs](https://github.com/18alantom/strawberry/tree/main/docs) · [Roadmap](https://github.com/18alantom/strawberry/blob/main/ROADMAP.md) 63 | 64 |
65 | 66 | **Documentation Index** 67 | 68 | 1. [Installation](./docs/installation.md): explains how to pull Strawberry code into your web project. 69 | 2. [Getting Started](./docs/getting_started.md): describes a simple example with code walk through. 70 | 3. [Reactivity](./docs/reactivity/README.md): explains what is reactivity in Strawberry. 71 | 1. [Reactive Values](./docs/reactivity/reactive_values.md): explains keys and values of the reactive object. 72 | 2. [Mark (`sb-mark`)](./docs/reactivity/mark.md): explains how to mark an element to update along with data. 73 | 3. [Conditionals (`sb-if`, `sb-ifnot`)](./docs/reactivity/conditionals.md): explains how to render or hide an element when data changes to truthy or falsy. 74 | 4. [Computed](./docs/reactivity/computed.md): explains how to define reactive values that depend on other reactive values. 75 | 5. [Directives](./docs/reactivity/directives.md): explains how to extend Strawberry with custom directives. 76 | 4. Composability (to be added) 77 | 5. [API](./docs/api.md): lists all of Strawberry's defined directives and methods. 78 | 79 | ## Installation 80 | 81 | If you wanna try it out, then run this 👇 command to setup a simple _starter_ page. 82 | 83 | ```bash 84 | curl -so- https://raw.githubusercontent.com/18alantom/strawberry/main/setup.sh | bash 85 | ``` 86 | 87 | Or if you wanna just use it straight away, copy this 👇 script tag in the head of your html file: 88 | 89 | ```html 90 | 91 | ``` 92 | 93 | ## Features 94 | 95 | Here're are a few of its features: 96 | 97 | 1. **Reactivity**: change your data and the UI updates. 98 | 2. **Composability**: create and use components. 99 | 3. **Build-free**: doesn't require a build-step. Link or [copy the lib](https://unpkg.com/sberry@0.0.3-alpha.0/dist/sb.min.js) and you're ready to go. 100 | 4. **Zero Dependencies**: has no dependencies. Uses WebAPIs for everything. 101 | 5. **Tiny**: [source code](https://github.com/18alantom/strawberry/blob/main/index.ts) is under 1000 CLOC. 102 | 6. **No VDOM**: directly updates the DOM. 103 | 104 | Strawberry is and will be developed with these two hard constraints: 105 | 106 | 1. Zero dependencies. 107 | 2. No build step required to run it. 108 | 109 | Other than this, there is also a soft constraint of keeping the source code light. 110 | 111 | --- 112 | 113 | ## Examples 114 | 115 | Here are a couple of simple examples of a few things that Strawberry can do 116 | right now. 117 | 118 | **1. Basic Reactivity**: `innerText` is updated when `data.message` when 119 | is set. 120 | 121 | ```html 122 |

Placeholder

123 | 124 | 127 | ``` 128 | 129 | **2. Computed Values**: `innerText` is updated with the computed value 130 | `data.countMessage` when `data.count` is updated. 131 | 132 | ```html 133 |

Placeholder

134 | 135 | 141 | ``` 142 | 143 | **3. Conditional Rendering**: `p` is rendered only when `data.sayHi` is `true`. 144 | 145 | ```html 146 | 149 | 150 | 153 | ``` 154 | 155 | **4. Looping**: `ul` is populated with `li` when `data.list` is set. `innerText` 156 | of the `li` are set from the list items. 157 | 158 | ```html 159 | 162 | 163 | 166 | ``` 167 | 168 | **5. Templates**: On running `sb.register`, the `red-p` element is defined and can be used. 169 | 170 | ```html 171 | 174 | 175 | Hi! 176 | ``` 177 | 178 | **5. External Templates**: Templates can be defined in external files. They are loaded and registered using `sb.load`. 179 | 180 | ```html 181 | 184 | 185 | Hi! 186 | ``` 187 | 188 | **6. Nested Templates**: Templates can be nested, named slots can be used to 189 | 190 | ```html 191 | 192 | 195 | 196 | 197 | 200 | 201 | 202 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 223 | ``` 224 | 225 | --- 226 | 227 | ## Development 228 | 229 | The development of Strawberry does has a direction, but no deadlines as I work 230 | on this usually during the weekends. 231 | 232 | Here's a 233 | [road map](https://github.com/18alantom/strawberry/blob/main/ROADMAP.md). This 234 | isn't exactly a road map, but a list of problems and maybe their solutions that 235 | need to be implemented to further Strawberry. 236 | 237 | ### Running Dev Mode 238 | 239 | Strawberry has only two dev dependencies: `esbuild` and `typescript`. 240 | 241 | To run Strawberry in dev mode: 242 | 243 | ```bash 244 | # Clone the repo 245 | git clone https://github.com/18alantom/strawberry 246 | 247 | # Install esbuild and typescript 248 | cd strawberry 249 | yarn install 250 | 251 | # Run in Dev mode 252 | yarn dev 253 | ``` 254 | 255 | You can now create an HTML file in the repository and add `script:src` to link 256 | the generated `index.js` file. You can serve this using the Live Preview plugin. 257 | 258 | To view the rendered HTML you need to serve it locally for that you can use the [Live Preview](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) VSCode plugin, or run [this](https://docs.python.org/3/library/http.server.html#:~:text=python%20%2Dm%20http.server%20%2D%2Dbind%20127.0.0.1): 259 | 260 | ```bash 261 | # Cd into strawberry root 262 | cd strawberry 263 | 264 | # Run simple python server to serve files in strawberry 265 | python -m http.server 8080 --bind 127.0.0.1 266 | ``` 267 | 268 | ### Tests 269 | 270 | Right now tests just involve linking strawberry to an html file (`test.*.html`), 271 | and calling the `test` function to check whether a value is as expected. Success 272 | and Failures are printed in the console. 273 | 274 | See [`test.html`](https://github.com/18alantom/strawberry/blob/main/tests/test.html) for an example. 275 | 276 | To see all tests in one place, open `test/index.html`. 277 | 278 | ### Website 279 | 280 | The website has two dependencies required to run, `sb.min.js` and 281 | `highlight.min.js` for running strawberry in the example and highlighting all the code. 282 | 283 | You can run the [`pubwebsite.sh`](https://github.com/18alantom/strawberry/blob/main/pubwebsite.sh) script which downloads these files: 284 | 285 | ```bash 286 | ./pubwebsite.sh 287 | ``` 288 | 289 | And then respond with `"n"` when it asks to publish: 290 | 291 | ```bash 292 | $ Publish the website? [y/n]: n 293 | $ /Users/you/Desktop/projects/strawberry 294 | ``` 295 | 296 | After the script has run, [`website/index.html`](https://github.com/18alantom/strawberry/blob/main/website/index.html) should render as expected. 297 | 298 | --- 299 | 300 | ## Douglas Crockford on the XML of today 301 | 302 | These are excerpts from the CoRecursive podcast on [JSON vs XML](https://corecursive.com/json-vs-xml-douglas-crockford/). 303 | 304 | > It’s probably the JavaScript frameworks. They have gotten so big and so weird. 305 | > People seem to love them. I don’t understand why. 306 | 307 | And on web APIs. 308 | 309 | > ...the browsers have actually gotten pretty good. The web standards thing have 310 | > finally worked, and the web API is stable pretty much. Some of it’s still pretty 311 | > stupid, but it works and it’s reliable. 312 | 313 | Read the transcript on [CoRecursive](https://corecursive.com/json-vs-xml-douglas-crockford/#javascript-frameworks). 314 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | This project doesn't have a concrete road map. It's in the experimental stage 2 | right now. This `md` is just a list of problems whose solutions haven't been 3 | implemented or even figured out. 4 | 5 | For a few of these, I haven't decided whether they are problems Strawberry 6 | should be solving. In keeping with the theme of simplicity, there are ways 7 | around the problems, but they might feel convoluted. 8 | 9 | Ultimately this framework should feel intuitive and simple even if it is at the 10 | cost of not being able to do _everything_. 11 | 12 | **Terminology**: 13 | 14 | - Reactive Data Object (RDO): the object received when `sb.init` is called, when 15 | props of this object are updated, the UI updates. 16 | 17 | **List** 18 | 19 | - [ ] [Setting `sb-*` props for Strawberry inserted elements](#setting-sb--props-for-strawberry-inserted-elements) 20 | - [ ] [Directives for Strawberry Created Elements](#directives-for-strawberry-created-elements) 21 | - [ ] [Logic Encapsulation in Templates](#logic-encapsulation-in-templates) 22 | - [ ] [Interactive Elements in Templates](#interactive-elements-in-templates) 23 | - [ ] [Animating `sb-if`](#animating-sb-if) 24 | - [x] [Two-way Binding](#skip-two-way-binding) (SKIP) 25 | - [x] [Nested Components](#done-nested-components) 26 | - [x] [Defer Directives](#done-defer-ui-updates) 27 | 28 | # Setting `sb-*` props for Strawberry inserted elements 29 | 30 | **Problem** if I have a list of `a` elements which is added by strawberry 31 | 32 | ```html 33 | 34 |
35 | 38 | ``` 39 | 40 | there is no way to set the `href` attribute. 41 | 42 | A **Solution** to this is to have a directive that sets attributes. This along 43 | with the nested components is a solution. 44 | 45 | ```html 46 |
47 | 48 |
49 | 52 | ``` 53 | 54 | # Directives for Strawberry Created Elements 55 | 56 | **Problem** is when an element is created and inserted by strawberry, there is 57 | no way to init the properties of that element. 58 | 59 | This is more of a enhancement than an issue, should strawberry even do this. 60 | 61 | # Logic Encapsulation in Templates 62 | 63 | **Problem** is that scripts inside templates run in the global context. And if 64 | the template is external, they don't run. 65 | 66 | This means that templates can't have logic ascribed to them. 67 | 68 | # Interactive Elements in Templates 69 | 70 | **Problem** is that templates can have multiple interactive elements, inputs, 71 | buttons, etc. But accessing these elements requires traversing the Shadow DOM. 72 | 73 | # Animating `sb-if` 74 | 75 | **Problem** is that when `sb-if` causes a elements to be removed it's done using 76 | `el.replaceWith` which is not animatable. 77 | 78 | # `[DONE]` Defer UI updates 79 | 80 | **Problem** is that currently when an RDO prop is set in a regular `script` tag 81 | 82 | ```html 83 | 87 | ``` 88 | 89 | only the elements before the script with the apt `sb-mark` are updated. 90 | Downstream elements are not updated. 91 | 92 | **Desired behavior** is that all elements are updated. Irrespective of the 93 | where the script is loaded. 94 | 95 | A **solution** is that when RDO props are set inside the `head` tag, the updates 96 | are deferred until the ready state changes to `"interactive"`. This happens only 97 | after the DOM has been parsed but before all assets have been fetched. 98 | 99 | # `[DONE]` Nested Components 100 | 101 | [PR #9](https://github.com/18alantom/strawberry/pull/9) 102 | 103 | **Problem** is that currently `sb-mark` on plain elements don't support nesting. 104 | For instance say I have a nested list: 105 | 106 | ```javascript 107 | data.list = [ 108 | [1, 2], 109 | ['a', 'b', 'c'], 110 | ]; 111 | ``` 112 | 113 | Where I want the outer list items to be rendered in a `ul` and the inner ones 114 | in an `li`. 115 | 116 | ```html 117 |
118 | 122 | 127 |
128 | ``` 129 | 130 | if the number of items in the outer list or inner list were fixed, it is 131 | possible to create templates with digit slot names: 132 | 133 | ```html 134 | 135 | 145 | ``` 146 | 147 | or just by using `sb-child`: 148 | 149 | ```html 150 | 151 | 152 | 153 | ``` 154 | 155 | But for nested lists with a varying number of items it is not possible without 156 | using JavaScript. This is without even considering the nesting depth. 157 | 158 | A **solution** is to allow `sb-mark` items to have children that define the 159 | shape of the child: 160 | 161 | ```html 162 |
163 | 164 | 168 |
169 | ``` 170 | 171 | This has the added advantage that the markup can be filled with placeholder data 172 | until the actual data loads. And to indicate that these are `sb-child` elements, 173 | they can be marked like so: 174 | 175 | ```html 176 |
177 | 180 |
181 | ``` 182 | 183 | _Note: this prevents `"#"` from being used as a valid RDO prop name._ 184 | 185 | # `[SKIP]` Two-way Binding 186 | 187 | **Problem** is `sb-mark` can't be used on input elements to change their 188 | value when the RDO prop changes, or vice versa. 189 | 190 | **Solution** is writing a new directive for this: 191 | 192 | ```javascript 193 | const data = sb.init({ 194 | directives: { 195 | bind({ el, value, parent, prop }) { 196 | el.value = value; 197 | el.oninput ??= (e) => { 198 | parent[prop] = e.target.value; 199 | }; 200 | }, 201 | }, 202 | }); 203 | ``` 204 | 205 | This is trivial, but the question is 206 | 207 | 1. Should this be in strawberry? 208 | 2. Should this be incorporated into `sb-mark` when element is an input element? 209 | 210 | Probably not considering, two way binding can be: 211 | 212 | - lazy: i.e. using a `change` listener 213 | - delayed: assignment takes place only after a time 214 | 215 | So, instead of inserting the batteries—in the spirit of simplicity—this probably won't be added in. 216 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file runs on yarn build. It converts index.ts to 4 | # minified javascript sb.min.js and adds the copyright 5 | # header file. 6 | # 7 | # Finally it prints the normal and gzipped sizes of the file. 8 | 9 | rm -rf dist 10 | mkdir dist 11 | 12 | version=$(cat package.json | grep '"version"' | cut -d'"' -f4) 13 | file_path="dist/sb.min.js" 14 | file_header="/* Strawberry $version 15 | * Copyright (c) 2023-present, Alan Tom (18alantom) 16 | * MIT License 17 | * This is a generated file. Do not edit.*/" 18 | 19 | echo "$file_header" >> "$file_path" 20 | node_modules/.bin/esbuild ./index.ts --bundle --minify --format=iife --global-name=sb >> "$file_path" 21 | 22 | size=$(wc -c < $file_path) 23 | gzsize=$(gzip -c $file_path | wc -c) 24 | 25 | kb=$(echo "scale=3; $size / 1024" | bc) 26 | gzkb=$(echo "scale=3; $gzsize / 1024" | bc) 27 | 28 | echo "dist/sb.min.js is ${kb}KB and gzipped ${gzkb}KB" 29 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | 3 | Strawberry is a simple frontend framework that gives you reactivity and 4 | composability. 5 | 6 | Here's the documentation index: 7 | 8 | 1. [Installation](./installation.md): explains how to pull Strawberry code into your web project. 9 | 2. [Getting Started](./getting_started.md): describes a simple example with code walk through. 10 | 3. [Reactivity](./reactivity/README.md): explains what is reactivity in Strawberry. 11 | 1. [Reactive Values](./reactivity/reactive_values.md): explains keys and values of the reactive object. 12 | 2. [Mark (`sb-mark`)](./reactivity/mark.md): explains how to mark an element to update along with data. 13 | 3. [Conditionals (`sb-if`, `sb-ifnot`)](./reactivity/conditionals.md): explains how to render or hide an element when data changes to truthy or falsy. 14 | 4. [Computed](./reactivity/computed.md): explains how to define reactive values that depend on other reactive values. 15 | 5. [Directives](./reactivity/directives.md): explains how to extend Strawberry with custom directives. 16 | 4. Composability (to be added) 17 | 5. [API](./api.md): lists all of Strawberry's defined directives and methods. 18 | 19 | If you want to get a quick round up of Strawberry check the 20 | [Getting Started](./getting_started.md) page. The remaining pages dwell deeper 21 | into individual topics and will be more clear after you have familiarized yourself 22 | with the basics. 23 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # Strawberry API 2 | 3 | This page lists the available strawberry directives and exported methods. 4 | 5 | 1. **Directives**: set on elements `` 6 | 1. [`sb-mark`](#sb-mark): mark element as reactive. 7 | 2. [`sb-if`](#sb-if): render element if data is truthy. 8 | 3. [`sb-ifnot`](#sb-ifnot): render element if data is falsy. 9 | 2. **Methods**: called from the global `sb` object. 10 | 1. [`init`](#init): initialize strawberry. 11 | 2. [`directive`](#directive): register a custom directive. 12 | 3. [`watch`](#watch): watch data for changes. 13 | 4. [`unwatch`](#unwatch): stop watching data for changes. 14 | 5. [`register`](#register): register custom components. 15 | 6. [`load`](#load): load components from external files. 16 | 7. [`prefix`](#prefix): change prefix `"sb"` to a custom value. 17 | 18 | ## Directives 19 | 20 | Note this section is mentioned only completeness. for a more detailed 21 | information on the items in this section check out the documentation on 22 | [reactivity](./reactivity/README.md). 23 | 24 | ### `sb-mark` 25 | 26 | [Detailed documentation](./reactivity/mark.md) 27 | 28 | Used to mark an element with a key which will allow the element to be updated 29 | when the respective data changes. 30 | 31 | ```html 32 |

...

33 | 36 | ``` 37 | 38 | ### `sb-if` 39 | 40 | [Detailed documentation](./reactivity/conditionals.md) 41 | 42 | Used to render an element when the respective data value is truthy. 43 | 44 | ```html 45 |

Hello, World!

46 | 49 | ``` 50 | 51 | ### `sb-ifnot` 52 | 53 | [Detailed documentation](./reactivity/conditionals.md) 54 | 55 | Used to render an element when the respective data value is falsy. 56 | 57 | ```html 58 |

Hello, World!

59 | 62 | ``` 63 | 64 | ## Methods 65 | 66 | If strawberry has been loaded correctly then all the methods mentioned in this 67 | section should be available on the global `sb` object. 68 | 69 | ### `init` 70 | 71 | ```typescript 72 | function init(): ReactiveObject; 73 | ``` 74 | 75 | > **Info** 76 | > 77 | > `ReactiveObject` is not a specific type, it is an object that looks like a 78 | > regular JavaScript object but has reactive properties. For details check the 79 | > [Reactive Values](./reactivity/reactive_values.md) page. 80 | 81 | Used to initialize strawberry. Calling it does the following things: 82 | 83 | - Returns a reactive object which is used to store all [reactive data](./reactivity/README.md) in a Strawberry app. 84 | - Registers defined components. 85 | 86 | ```html 87 |

88 | 92 | ``` 93 | 94 | When `sb.init` is called all templates elements with a name attribute are 95 | registered. Strawberry checks for component templates once again after the 96 | document is done loading, so it's alright to call `sb.init` in the `` 97 | element of a document. 98 | 99 | > **Info** 100 | > 101 | > Calling `sb.init` will always return the same reactive object so there is no 102 | > point in calling it multiple times. If you want to register components after 103 | > loading you should instead use `sb.register` or `sb.load`. 104 | 105 | ### `directive` 106 | 107 | [Detailed documentation](./reactivity/directives.md) 108 | 109 | ```typescript 110 | function directive( 111 | name: string, 112 | cb: Directive, 113 | isParametric: value = false 114 | ): void; 115 | 116 | type Directive = (params: { 117 | el: Element; // The element to which the directive has been applied. 118 | value: unknown; // The updated value. 119 | key: string; // Period '.' delimited key that points to the value in the global data object. 120 | isDelete: boolean; // Whether the value was deleted `delete data.prop`. 121 | parent: Prefixed; // The parent object to which the value belongs (the proxied object, unless isDelete). 122 | prop: string; // Property of the parent which points to the value, `parent[prop] ≈ value` 123 | param: string | undefined; // If directive is a parametric directive, `param` is passed 124 | }) => void; 125 | ``` 126 | 127 | - `name`: the name of the directive being registered 128 | - `cb`: the directive callback function 129 | - `isParametric`: whether the directive is a parametric directive 130 | 131 | Used to register a custom directive. For example here is a two way bind directive: 132 | 133 | ```html 134 | 142 | 143 | ``` 144 | 145 | ### `watch` 146 | 147 | ```typescript 148 | function watch(key: string, watcher: Watcher): void; 149 | type Watcher = (newValue: unknown) => any; 150 | ``` 151 | 152 | - `key`: dot separated string for a value in the reactive object 153 | - `watcher`: function that is called when watched value changes, it receives the `newValue` that is set 154 | 155 | Used to set watcher function that is called when a watched value or its child 156 | value changes. For example: 157 | 158 | ```javascript 159 | data.a = { b: '' }; 160 | 161 | sb.watch('a.b', (v) => console.log(`b changed to: ${v}`)); 162 | sb.watch('a', (v) => console.log(`a changed to: ${v}`)); 163 | 164 | data.a.b = 'Hello, World'; 165 | 166 | // b changed to: Hello, World 167 | // a changed to: [object Object] 168 | ``` 169 | 170 | Note: in the above example even the second watcher is triggered because `b` is a 171 | property (child value) of `a`. 172 | 173 | > **Warning** 174 | > 175 | > Watchers should not alter the reactive object. If a dependent value is 176 | > required then a [`computed`](./reactivity/computed.md) value should be used. 177 | 178 | ### `unwatch` 179 | 180 | ```typescript 181 | function unwatch(key?: string, watcher?: Watcher): void; 182 | ``` 183 | 184 | - `key`: key from which watchers are to be removed. 185 | - `watcher`: specific watcher which is to be removed. 186 | 187 | Used to remove watchers. Watchers are removed depending on the args passed: 188 | 189 | - Only `key`: all watchers registered under the passed `key` are removed. 190 | - Only `watcher`: `watcher` is removed from all keys. 191 | - Both: `watcher` found registered with `key` is removed. 192 | - Neither: all watchers are removed. 193 | 194 | Example: 195 | 196 | ```javascript 197 | sb.unwatch('a.b'); 198 | ``` 199 | 200 | ### `register` 201 | 202 | ```typescript 203 | function register(): void; 204 | function register(parentElement: HTMLElement): void; 205 | function register(template: string): void; 206 | function register(templateString: string[], ...args: unknown): void; 207 | ``` 208 | 209 | Used to register custom components in Strawberry. These components can be 210 | defined dynamically after a document has completed loading. 211 | 212 | It can be called in multiple ways: 213 | 214 | 1. **Without args**: This will register all the components found in html 215 | document. 216 | 217 | ```javascript 218 | sb.register(); 219 | ``` 220 | 221 | 2. **Passing the parent element**: This will register all the components found 222 | inside the passed `parentElement`. 223 | 224 | ```javascript 225 | sb.register(parentElement); 226 | ``` 227 | 228 | 3. **As a tag function**: This allows for dynamically creating templates with 229 | interpolated values and expressions. 230 | 231 | ```javascript 232 | sb.register` 233 | `; 238 | ``` 239 | 240 | 4. **Passing the component string**: Functionally, it is the same as using it 241 | as a tagged function. 242 | ```javascript 243 | sb.register(``); 248 | ``` 249 | 250 | > **Warning** 251 | > 252 | > Once an element has been registered by a particular name, it cannot be 253 | > re-registered, `sb-register` will skip over registered elements and only 254 | > register the ones that have not been registered. 255 | 256 | > **Info** 257 | > 258 | > If your components have been defined statically before the document finished 259 | > loading (i.e. before `document.readyState` becomes `"interactive"`) then you 260 | > don't need to call `sb.register`. 261 | 262 | ### `load` 263 | 264 | ```typescript 265 | function load(files: string | string[]): Promise; 266 | ``` 267 | 268 | - `files`: Path to a single file from the calling folder or a list of files. 269 | 270 | Used to load components from external files. For example if you have the 271 | following folder structure: 272 | 273 | ``` 274 | . 275 | ├── index.html 276 | └── components.html 277 | ``` 278 | 279 | Where `index.html` is your main HTML file that will be served, and 280 | `components.html` contains your templates. You can load the components in 281 | `index.html` using `sb.load` like so: 282 | 283 | ```javascript 284 | sb.load('components.html'); 285 | ``` 286 | 287 | ### `prefix` 288 | 289 | ```typescript 290 | function prefix(value: string = 'sb'): void; 291 | ``` 292 | 293 | - `value`: value to use for prefix, defaults to `'sb'` 294 | 295 | Used to override the default (`'sb'`) prefix for directives, for example 296 | if you want to use data attributes to manage directives you can do this: 297 | 298 | ```html 299 | 302 |

303 | ``` 304 | -------------------------------------------------------------------------------- /docs/getting_started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Let's start with a simple example, I'll explain what's going on after I show you the code. 4 | 5 | **Index** 6 | 7 | 1. [Example](#example) 8 | 2. [Explanation](#explanation) 9 | 10 | ## Example 11 | 12 | The example code sets up a simple web page with a counter that increments on 13 | clicking a button and also displays an updating message. 14 | 15 | You can copy-paste it into a `.html` file and open it in a browser to see how it 16 | works. 17 | 18 | ```html 19 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | loading... 48 | 49 | 50 | 51 | 52 | 55 | ``` 56 | 57 | ## Explanation 58 | 59 | In the above example we are rendering a button component called `cool-button` 60 | which on clicking increments `counter` and which updates the button's 61 | `innerText`. 62 | 63 | I have added comments that to mark sections of the code above, I'll explain 64 | what's going on in each of them: 65 | 66 | ### 1. Link Strawberry 67 | 68 | ```html 69 | 70 | 71 | 72 | ``` 73 | 74 | This loads and runs the `sb.min.js` file from unpkg. On doing this, the `sb` 75 | object becomes available in the global scope. You can open the console, type 76 | `sb` and check that it is defined. 77 | 78 | **Documentation Link**: 79 | 80 | - [Installation](./installation.md) 81 | 82 | ### 2. Define Component 83 | 84 | ```html 85 | 97 | ``` 98 | 99 | This defines a component inside a `template` tag with the name attribute 100 | deciding what the component will be called (`"cool-button"`). The component 101 | defined is a button component with some styling added to it. 102 | 103 | **Documentation Link**: 104 | 105 | - [Register Components](./api.md#register) 106 | 107 | ### 3. Initialize Strawberry 108 | 109 | ```html 110 | 114 | ``` 115 | 116 | This calls `sb.init` to do two things: 117 | 118 | 1. Register our defined components (i.e. `cool-button`). 119 | 2. Returns the reactive object `data` which updates UI when its values change. 120 | 121 | Along with that we are also initializing `data.counter` with the value `0`. 122 | 123 | **Documentation Links**: 124 | 125 | - [Initialization using `init`](./api.md#init) 126 | - [Reactivity](./reactivity/README.md) 127 | 128 | ### 4. Use the Component 129 | 130 | ```html 131 | 132 | 133 | loading... 134 | 135 | 136 | ``` 137 | 138 | Use the `cool-button` component to display a button. Here two things are going on: 139 | 140 | 1. `sb-mark="message"`: this attribute means "whenever `data.message` changes, update the `innerText` of this component."" 141 | 2. `onclick="data.counter += 1"`: this is an inline function that increments `data.counter` that was defined in section 142 | 143 | > **Info** 144 | > 145 | > You can also use some other way of adding an event listener. 146 | > [For example, by using a directive](./reactivity/directives.md#example-event-listeners-using-a-directive). 147 | > I've used this for brevity. 148 | 149 | ### 5. Update the Component 150 | 151 | ```html 152 | 155 | ``` 156 | 157 | This finally just defines what the `message` from `sb-mark="message"` is. Here 158 | the message is a function that depends on `data.counter`, i.e. it is a _computed property_. 159 | 160 | Which means whenever `data.counter` changes the value of message changes too. 161 | 162 | Now whenever you click on the `cool-button`, the following things happen 163 | 164 | 1. `counter` increments. 165 | 2. `data.counter` is re-run because it depends on `counter`. 166 | 3. the value of `data.counter` is used to set the `innerText` of `cool-button`. 167 | 168 | > **Info** 169 | > 170 | > Since `data.message` is a computed property, the following is true. 171 | > 172 | > ```javascript 173 | > typeof data.message === 'string'; 174 | > ``` 175 | 176 | **Documentation Links**: 177 | 178 | - [Computed Values](./reactivity/computed.md) 179 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | There are a couple of ways you can setup Strawberry 4 | 5 | **Index** 6 | 7 | 1. [Strawberry Starter](#strawberry-starter) 8 | 2. [Self Setup](#self-setup) 9 | 10 | ## Strawberry Starter 11 | 12 | The quickest way to get started is to run this command: 13 | 14 | ```bash 15 | curl -so- https://raw.githubusercontent.com/18alantom/strawberry/main/setup.sh | bash 16 | ``` 17 | 18 | This will run a [tiny bash script](https://github.com/18alantom/strawberry/blob/main/setup.sh) that sets up a simple starter page using Strawberry. 19 | 20 | ## Self Setup 21 | 22 | If you want to add the functionality of Strawberry to an existing `html` document. You do two things: 23 | 24 | Add a link to Strawberry to you html's `head` tag: 25 | 26 | ```html 27 | 28 | ``` 29 | 30 | Or you can download [this file](https://raw.githubusercontent.com/18alantom/strawberry/main/dist/sb.min.js) and add a `script:src` that links to the copy. 31 | -------------------------------------------------------------------------------- /docs/reactivity/README.md: -------------------------------------------------------------------------------- 1 | # Reactivity 2 | 3 | Reactivity is the crux of Strawberry, for a quick brief on what reactivity is 4 | consider the following example: 5 | 6 | ```html 7 |

...

8 | 11 | ``` 12 | 13 | In the above example, when `data.message` is updated the inner text of the `

` 14 | element is also updated. This is due to Strawberry's reactivity, i.e. 15 | _when the data updates, update the UI_. 16 | 17 | Reactive depends on two things, what data is set/updated, and how the UI is to 18 | be updated. The reactivity section of the documentation has been divided as 19 | such: 20 | 21 | - **What data is set/updated** 22 | 1. [Reactive Values](./reactive_values.md) 23 | 2. [Computed](./computed.md) 24 | - **How the UI is to be updated** 25 | 1. [Mark (`sb-if`)](./mark.md) 26 | 2. [Conditionals (`sb-if`, `sb-ifnot`)](./mark.md) 27 | 3. [Directives](./directives.md) 28 | 29 | ## Setting and updating the data 30 | 31 | For a piece of data to be considered reactive, it should be set on Strawberry's 32 | reactive object: 33 | 34 | ```javascript 35 | const data = sb.init(); 36 | ``` 37 | 38 | The object `data` that is returned on initialization is a reactive object. This 39 | means that any value that is set on `data` will update the appropriate UI. 40 | 41 | Data works with the following types of values: 42 | 43 | 1. Regular objects. [documentation](reactive_values.md#objects-and-arrays) 44 | 2. Regular arrays for loops. [documentation](reactive_values.md#objects-and-arrays) 45 | 3. Functions used for computed values. [documentation](computed.md) 46 | 47 | ## Updating the UI 48 | 49 | There are a couple of ways Strawberry updates the UI on data change: 50 | 51 | - `sb-mark`: used for setting content of an element. [documentation](mark.md) 52 | - `sb-if`: used for adding or removing an element if the value is _truthy_. [documentation](conditionals.md) 53 | - `sb-ifnot`: used for adding or removing an element if the value is _falsy_. [documentation](conditionals.md) 54 | 55 | All three of the above are called _directives_, you can add custom directives to 56 | Strawberry, check the [documentation](directives.md). 57 | -------------------------------------------------------------------------------- /docs/reactivity/computed.md: -------------------------------------------------------------------------------- 1 | # Computed 2 | 3 | Computed values are values that have been calculated from other values. Consider this example: 4 | 5 | ```javascript 6 | let a = 10; 7 | let b = 20; 8 | 9 | const c = () => a + b; 10 | ``` 11 | 12 | Here, `c` is a function that returns a computed value. The function has two 13 | variable dependencies `a` and `b` i.e. when either of them change, the value of 14 | `c()` also changes. 15 | 16 | **Index** 17 | 18 | 1. [Computed values are functions](#computed-values-are-functions) 19 | 2. [Elements can be marked by computed values](#elements-can-be-marked-by-computed-values) 20 | 3. [Computed functions can use `this`](#computed-functions-can-use-this) 21 | 4. [Functions can be returned from computed functions](#function-can-be-returned-from-computed-functions) 22 | 5. [Computed functions can be async](#computed-functions-can-be-async) 23 | 6. [Additional points regarding computed](#additional-points-regarding-computed) 24 | 25 | ## Computed values are functions 26 | 27 | In Strawberry computed values are obtained by setting functions on the reactive object: 28 | 29 | ```javascript 30 | // data is the reactive object 31 | const data = sb.init(); 32 | 33 | data.a = 10; 34 | data.b = 20; 35 | 36 | data.c => () => data.a + data.b; 37 | ``` 38 | 39 | After setting a function for a computed value, you don't need to call it to get 40 | the value: 41 | 42 | ```javascript 43 | data.c === 30; // evaluates to true 44 | ``` 45 | 46 | You can update the function for the computed value by setting a new value to the 47 | property: 48 | 49 | ```javascript 50 | data.c => () => (data. a + data.b) / 10; 51 | 52 | data.c === 3; // evaluates to true 53 | ``` 54 | 55 | > **Warning** 56 | > 57 | > Dependencies of a computed value should be defined before the computed value 58 | > is defined. Else the computed value will not be evaluated properly and you 59 | > might end up with incorrect values. 60 | 61 | ## Elements can be marked by computed values 62 | 63 | Say you have an element marked by a computed value: 64 | 65 | ```html 66 |

0

67 | 68 | 72 | ``` 73 | 74 | The element is updated when you: 75 | 76 | 1. Set the computed value's function. 77 | 2. update a dependency of the computed value. 78 | 79 | In the above example setting `data.c` will update the inner text of the `

` 80 | element to `'20'`. Similarly if you update the dependency `data.a`, eg 81 | `data.a = 5` the inner text will again be updated, in this case to `"15"`. 82 | 83 | ## Computed functions can use `this` 84 | 85 | Up until now the computed functions have directly accessed the `data` object. You 86 | can instead use `this` to access the reactive object that contains the computed 87 | function: 88 | 89 | ```javascript 90 | data.a = 10; 91 | data.c = function () { 92 | return this.a + 10; 93 | }; 94 | ``` 95 | 96 | > **Warning** 97 | > 98 | > This is not possible using arrow functions (i.e `() => {}`) because they can't 99 | > be bound to their own `this` value. So you need to use regular functions for 100 | > `this`. 101 | 102 | The benefit of this is you can: 103 | 104 | 1. Reuse functions 105 | 2. Not have to rely on long value access expressions 106 | 107 | For example: 108 | 109 | ```javascript 110 | data.users = []; 111 | function isOldEnough() { 112 | return this.age > 30; 113 | } 114 | 115 | function addUser(name, age) { 116 | data.users.push({ name, age, isOldEnough }); 117 | } 118 | 119 | addUsers('Flo', 28); 120 | addUsers('Max', 55); 121 | 122 | data.users[0].isOldEnough; // false 123 | data.users[1].isOldEnough; // true 124 | ``` 125 | 126 | ## Function can be returned from computed functions 127 | 128 | If your computed function returns a function then it is returned as it is, i.e. 129 | without being called like a computed value. 130 | 131 | ```javascript 132 | function onClickHandler() { 133 | /* event listener logic*/ 134 | } 135 | 136 | data.handler = () => onClickHandler; 137 | 138 | typeof data.handler === 'function'; // evaluates to true 139 | ``` 140 | 141 | This is useful for associating a function with an element. For instance by 142 | creating a custom directive to assign event listeners to elements. 143 | 144 | ## Computed functions can be `async` 145 | 146 | If your computed function depends on pulling data from some async source you 147 | can use an `async` function instead: 148 | 149 | ```javascript 150 | data.item = 'Matchbox'; 151 | data.isInStock = async () => { 152 | const quantity = await getStockQuantity(data.item); 153 | return quantity > 0; 154 | }; 155 | ``` 156 | 157 | In the above example when `data.item` is updated, the function `isInStock` is 158 | called and when the returned `Promise` is resolved all the directives marked 159 | using `"isInStock"` are evaluated with the resolved value. 160 | 161 | > **Note** 162 | > 163 | > Accessing `data.isInStock` will still return a `Promise` that will have to be 164 | > awaited. 165 | 166 | ## Additional points regarding computed 167 | 168 | **Note**: you don't need to know this stuff, but if you find that computed is not 169 | functioning as expected then these points will help in debugging the issue. 170 | 171 | - Computed values won't update if computed dependencies are deleted. 172 | - Accessing computed values with missing or incorrect dependencies (e.g after 173 | deleting or after reassigning a dependency) may result in an error or 174 | unexpected values. 175 | - Computed functions are executed once when set to evaluate dependencies. 176 | - If a codeblock in a computed function has a dependency, and that block is not 177 | executed when the computed function called, the dependency is not counted. 178 | -------------------------------------------------------------------------------- /docs/reactivity/conditionals.md: -------------------------------------------------------------------------------- 1 | # Conditionals 2 | 3 | This page is about the details of Strawberry's conditional directives `sb-if` 4 | and `sb-ifnot`. If you haven't checked the [Getting Started](../getting_started.md) 5 | and the [Reactivity](./README.md) pages, I'd suggest doing so first. 6 | 7 | **Index** 8 | 9 | 1. [Conditional Rendering](#conditional-rendering) 10 | 2. [Hidden default state](#hidden-default-state) 11 | 3. [Conditionally Rendering Lists](#conditionally-rendering-lists) 12 | 13 | ## Conditional Rendering 14 | 15 | When you want to show or hide an element on the basis of some data value, you 16 | can use one of Strawberry's conditional directives `sb-if` and `sb-ifnot`. 17 | 18 | ```html 19 |

20 |

You have a message.

21 |
22 | 23 | 26 | ``` 27 | 28 | In the above example the `
` element will be rendered cause 29 | `data.hasMessage` is set to `true`. 30 | 31 | An element with an `sb-if` or `sb-ifnot` can have two default states: 32 | 33 | - **Visible default state**: element is visible until value is set. 34 | - **Hidden default state**: element is hidden until the value is set. 35 | 36 | _Note: in the example shown, the `
` element has a visible default state._ 37 | 38 | ## Hidden default state 39 | 40 | In the example below, the `
` element has a visible default state. 41 | 42 | ```html 43 |
44 |

You have a message.

45 |
46 | 47 | 50 | ``` 51 | 52 | Due to this, the `
` element will be rendered until the value of 53 | `data.hasMessage` has been evaluated. Which in this case is `false`, this may be 54 | undesirable behaviour and you can avoid this by wrapping the element in a 55 | `