Skip to content
This repository has been archived by the owner on Feb 11, 2022. It is now read-only.

Commit

Permalink
v0.2.0
Browse files Browse the repository at this point in the history
Breaking changes ⚠:
- Move SSR implementation into separate package (`@isotope/server`).
- Change `map()` behavior to only rerender when new array is provided.
Other changes:
- Improve performance of classes configurator on Node creation.
- Emit lifecycle events only when they're listened to.
  • Loading branch information
areknawo committed Apr 13, 2020
1 parent 32902ab commit 3448966
Show file tree
Hide file tree
Showing 45 changed files with 477 additions and 1,054 deletions.
193 changes: 33 additions & 160 deletions dist/isotope.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*!
* @isotope/core v0.1.4
* @isotope/core v0.2.0
* (c) Arek Nawo <[email protected]> (areknawo.com)
* Released under the MIT License.
*/
Expand Down Expand Up @@ -124,7 +124,9 @@
* @returns - IsotopeNode.
*/
emit(event, data = {}) {
this.element.dispatchEvent(Object.assign(this.customDOM ? this.customDOM.createEvent(event) : new Event(event), { node: this }, data));
if (this.listenedEvents && this.listenedEvents.includes(event)) {
this.element.dispatchEvent(Object.assign(this.customDOM ? this.customDOM.createEvent(event) : new Event(event), data));
}
return this;
}
/**
Expand Down Expand Up @@ -196,6 +198,12 @@
/** @private */
on(event, handler, options) {
this.element.addEventListener(event, handler, options);
if (this.listenedEvents) {
this.listenedEvents.push(event);
}
else {
this.listenedEvents = [event];
}
return this;
}
/**
Expand Down Expand Up @@ -299,157 +307,6 @@
return new IsotopeNode(element, config);
};

/**
* Class implementing Isotope CustomElement API, allowing for easy server-side stringification.
*/
class StringElement {
/**
* Creates a new ServerElement instance.
*
* @param tag - Tag to be used for the element.
*/
constructor(tag) {
this.children = [];
this.classList = {
add: (...tokens) => {
this.classes.push(...tokens);
},
contains: (token) => {
return this.classes.includes(token);
},
remove: (...tokens) => {
tokens.forEach((token) => {
this.classes.splice(this.classes.indexOf(token), 1);
});
}
};
this.parentElement = null;
this.style = {};
this.attributes = {};
this.classes = [];
this.$textContent = "";
this.events = {};
this.tagName = tag;
}
/** @private */
addEventListener(type, listener) {
(this.events[type] || (this.events[type] = [])).push(listener);
}
/** @private */
appendChild(newChild) {
this.children.push(newChild);
newChild.parentElement = this;
return newChild;
}
/** @private */
dispatchEvent(event) {
(this.events[event.type] || []).slice().forEach((handler) => {
if (typeof handler === "function") {
handler(event);
}
else {
handler.handleEvent(event);
}
});
return true;
}
/** @private */
getAttribute(qualifiedName) {
return this.attributes[qualifiedName];
}
/** @private */
insertBefore(newChild, refChild) {
newChild.parentElement = this;
if (refChild) {
const index = this.children.indexOf(refChild);
if (index >= 0) {
this.children.splice(index + 1, 0, newChild);
return newChild;
}
}
this.children.push(newChild);
return newChild;
}
/** @private */
removeAttribute(qualifiedName) {
this.attributes[qualifiedName] = null;
}
/** @private */
removeChild(oldChild) {
oldChild.parentElement = null;
this.children.splice(this.children.indexOf(oldChild), 1);
return oldChild;
}
/** @private */
removeEventListener(type, listener) {
if (this.events[type]) {
this.events[type].splice(this.events[type].indexOf(listener) >>> 0, 1);
}
}
/** @private */
setAttribute(qualifiedName, value) {
this.attributes[qualifiedName] = value;
}
/** @private */
set textContent(textContent) {
this.children = [];
this.$textContent = textContent || "";
}
/** @private */
get textContent() {
return this.$textContent;
}
/**
* Stringifies the ServerElement.
*
* @returns - Stringified ServerElement.
*/
toString() {
const tag = this.tagName;
const content = this.textContent ||
this.children
.map((child) => {
return `${child}`;
})
.join("");
const classes = this.classes.join(" ");
const styles = Object.entries(this.style)
.map(([property, value]) => {
const propertyKebabCase = property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
return `${propertyKebabCase}:${value}`;
})
.join(";");
const attributes = Object.entries(this.attributes)
.map(([name, value]) => {
return `${name}="${value}"`;
})
.join(" ");
return `<${tag}${classes ? ` class="${classes}"` : ""}${styles ? `style="${styles}"` : ""}${attributes}>${content}</${tag}>`;
}
}

/**
* Creates a String View.
*
* @param tag - Tag for the top-level Node.
* @returns - The created top-level Node.
*/
const createStringView = (tag) => {
if (!IsotopeNode.prototype.customDOM) {
IsotopeNode.prototype.customDOM = {
/** @private */
createElement(tag) {
return new StringElement(tag);
},
/** @private */
createEvent(type) {
return { type };
}
};
}
return new IsotopeNode(tag);
};

if (!IsotopeNode.prototype.setAttribs) {
IsotopeNode.prototype.onCreate.push((node, config) => {
if (config.attribs) {
Expand Down Expand Up @@ -495,11 +352,21 @@
if (!IsotopeNode.prototype.setClasses) {
IsotopeNode.prototype.onCreate.push((node, config) => {
if (config.classes) {
let classes = "";
if (typeof config.classes === "function") {
node.classes = config.classes;
}
else if (Array.isArray(config.classes)) {
classes = config.classes.join(" ");
}
else {
node.setClasses(config.classes);
classes = Object.entries(config.classes)
.filter(([, apply]) => apply)
.map(([name]) => name)
.join(" ");
}
if (classes) {
node.element.setAttribute("class", classes);
}
}
});
Expand Down Expand Up @@ -867,12 +734,19 @@
if (node.mapData) {
const data = node.mapData;
const items = typeof data.items === "function" ? data.items(node) : node.getState(data.items);
if (node.linked) {
handleMapUpdate(node, items);
}
else {
handleMapCreation(node, items);
if (!items.isotopeMapped) {
if (items.length === 0) {
node.linked = [];
node.element.textContent = "";
}
else if (node.linked) {
handleMapUpdate(node, items);
}
else {
handleMapCreation(node, items);
}
}
items.isotopeMapped = true;
}
};
IsotopeNode.prototype.onClean.push((node) => {
Expand Down Expand Up @@ -1103,7 +977,6 @@

exports.IsotopeNode = IsotopeNode;
exports.createDOMView = createDOMView;
exports.createStringView = createStringView;

Object.defineProperty(exports, '__esModule', { value: true });

Expand Down
4 changes: 2 additions & 2 deletions dist/isotope.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/isotope.min.js.map

Large diffs are not rendered by default.

43 changes: 41 additions & 2 deletions docs/list-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ node.map([1, 2, 3, 4], (item, node) => {

## Reactivity

The `map()` method can be used to render both static and dynamic lists. If you want your list to be dynamic, you have to pass it in the form of either state property name or a function.
The `map()` method can be used to render both static and dynamic lists. If you want your list to be dynamic, you have to pass it in the form of either state property name or a function. If you're using a function, remember that it has access to the parent node through its first parameter.

```javascript
// ...
Expand All @@ -71,7 +71,46 @@ const node = view.child("div").map(
);
```

If you're using a function, remember that it has access to the parent node through its first parameter.
To update your list (i.e. move, remove or add an item), you have to provide the `map()` method with a completely new data array, e.g.

```javascript
// ...
const node = view
.child("div", {
state: {
list: [1, 2, 3, 4, 5]
}
})
.map("list", (item, node) => node.child("div", item));
const list = [5, 4, 3, 2, 1];

node.setState({ list }); // completely new array
node.setState({ list: [...list, 0] }); // create new array from the previous one and add an item
```

Keep in mind that a new array is only required when you need to change the ordering of items. If you only want to change their data, you can do that though simple combination of linking and state change (check out the [reactivity](./reactivity.md) section for more info).

```javascript
// ...
const list = [
{ id: 1, text: "Text" },
{ id: 2, text: "Text" },
{ id: 3, text: "Text" }
];
const node = view
.child("div", {
state: {
list
}
})
.map("list", (item, node) => {
return node.child("div").text(() => item.text); // Make text reactive
});
list[2].text = "Different text";
node.setState({}); // let Isotope know to update the node.
```

> INFO: If provided dynamic data, the `map()` method automatically links all of its items.
### Indexing

Expand Down
6 changes: 3 additions & 3 deletions docs/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

Although Isotope is really fast when used on the client-side, serving a pre-rendered, static HTML file to the client not only improves the performance even further but also helps you rank higher in search engine results, with better SEO.

Isotope supports **Server-Side Rendering** (SSR) through a special `createStringView()` method.
Isotope supports **Server-Side Rendering** (SSR) through an additional `@isotope/server` package and its `createStringView()` function.

## createStringView(tag)

`createStringView()` method allows you to create a view that will output its content to a string.

```javascript
import { createStringView } from "@isotope/core"; // or "@isotope/core/lib/views/string"
import { createStringView } from "@isotope/server";

const view = createStringView(tag);
```
Expand All @@ -27,7 +27,7 @@ const view = createStringView(tag);
After you set up your view together with its content, you can retrieve the rendered string by simply "stringifying" the view itself.

```javascript
import { createStringView } from "@isotope/core"; // or "@isotope/core/lib/views/string"
import { createStringView } from "@isotope/server";

const view = createStringView("body");
const node = view.child("div", "Text");
Expand Down
14 changes: 10 additions & 4 deletions docs/views.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# Views

In Isotope, **views** are simply top-level [nodes](./nodes.md), that are created using dedicated methods and attached to the specified DOM elements. The library provides two types of such views, created with either `createDOMView()` or `createStringView()` (see [SSR section](./ssr.md)) method.

When you've got your view set up, you can use it just like a usual node.
In Isotope, **views** are simply top-level [nodes](./nodes.md), that are created using dedicated functions. The library you with one of such functions - `createDOMView()`.

## createDOMView(element, config)

`createDOMView()` method allows you to create a view that will use DOM as its rendering output.

```javascript
import { createDOMView } from "@isotope/core"; // or "@isotope/core/lib/views/dom"
import { createDOMView } from "@isotope/core"; // or "@isotope/core/lib/view"

const view = createDOMView(element, config);
```
Expand All @@ -26,3 +24,11 @@ const view = createDOMView(element, config);
**Returns**:

- Created `IsotopeNode`.

## Usage

When you've got your view set up, you can use it just like a usual node.

## Server-Side Rendering

Isotope supports SSR rendering through a separate `@isotope/server` package. You can learn more about this [here](./ssr.md).
12 changes: 11 additions & 1 deletion lib/configurators/classes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/configurators/classes.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createDOMView, createStringView } from "./views";
import { IsotopeNode } from "./node";
import { createDOMView } from "./view";
import "./configurators";
import "./nodes";
export { IsotopeNode, createDOMView, createStringView };
export { IsotopeNode, createDOMView };
Loading

0 comments on commit 3448966

Please sign in to comment.