Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7b26f26
Minor pat-inject code cleanup.
thet Apr 11, 2023
411653d
maint(pat-inject): Remove obsolete hooks option.
thet Apr 11, 2023
ae01e20
maint(pat-inject): Remove obsolete raptor-ui trigger.
thet Apr 11, 2023
14af661
maint(pat inject): Use dom.find_scroll_container instead jQuery :scro…
thet Apr 12, 2023
12c980b
feat(core utils): debouncer - Add postpone option for callback to be …
thet Apr 11, 2023
52d9ecf
feat(core utils): Add threshold_list helper for intersection observers.
thet Apr 13, 2023
95c16b8
feat(core utils): add parseLength method for parsing px and % lengths.
thet Apr 14, 2023
a3ecf93
feat(core dom): Add get_scroll_x and get_scroll_y helper methods to g…
thet Apr 13, 2023
15c6bf9
core dom: Cleanup docstrings.
thet Apr 13, 2023
622d5e2
feat(core dom): Implement get_visible_ratio to calculate the visible …
thet Apr 14, 2023
e9a8f2f
feat(core base): Throw pre-init.PATTERNNAME.patterns event.
thet Apr 11, 2023
cacb743
feat(core basepattern): Throw pre-init.PATTERNNAME.patterns event.
thet Apr 11, 2023
eb66159
feat(core basepattern): Allow to specify parser options on the pattern.
thet Apr 14, 2023
a66a9f8
maint(pat scroll): Code cleanup.
thet Apr 12, 2023
e5a4b24
maint(pat scroll-box): Use dom.scroll_y instead of own implementation.
thet Apr 13, 2023
148f79a
maint(pat scroll-box): Cleanup code.
thet Apr 13, 2023
6483649
feat(pat scroll-marker): Add pattern to set navigation classes based …
thet Apr 12, 2023
5b0fc43
maint(pat navigation): Switch to class based pattern.
thet Apr 14, 2023
3c55864
maint(pat navigation): Don't do option grouping. There will some opti…
thet Apr 14, 2023
fb8eb82
feat(pat navigation): Add scroll-marker functionality.
thet Apr 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/core/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ const Base = async function ($el, options, trigger) {
this.$el = $el;
this.el = $el[0];
this.options = $.extend(true, {}, this.defaults || {}, options || {});

this.emit("pre-init");

await this.init($el, options, trigger);

// Store pattern instance on element
Expand Down
14 changes: 10 additions & 4 deletions src/core/base.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,21 @@ describe("pat-base: The Base class for patterns", function () {
const node = document.createElement("div");
node.setAttribute("class", "pat-example");
const event_list = [];
node.addEventListener("init_done", () => event_list.push("pat init"));
$(node).on("init.example.patterns", () => event_list.push("base init"));
$(node).on("pre-init.example.patterns", () =>
event_list.push("pre-init.example.patterns")
);
node.addEventListener("init_done", () => event_list.push("init_done"));
$(node).on("init.example.patterns", () =>
event_list.push("init.example.patterns")
);
new Tmp(node);

// await until all asyncs are settled. 1 event loop should be enough.
await utils.timeout(1);

expect(event_list[0]).toBe("pat init");
expect(event_list[1]).toBe("base init");
expect(event_list[0]).toBe("pre-init.example.patterns");
expect(event_list[1]).toBe("init_done");
expect(event_list[2]).toBe("init.example.patterns");
});

it("adds the pattern instance on the element when manually initialized", async () => {
Expand Down
22 changes: 21 additions & 1 deletion src/core/basepattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class BasePattern {
static trigger; // A CSS selector to match elements that should trigger the pattern instantiation.
static parser; // Options parser.

// Parser options
parser_group_options = true;
parser_multiple = undefined;
parser_inherit = true;

constructor(el, options = {}) {
// Make static variables available on instance.
this.name = this.constructor.name;
Expand All @@ -31,6 +36,14 @@ class BasePattern {
}
this.el = el;

// Notify pre-init
this.el.dispatchEvent(
new Event(`pre-init.${this.name}.patterns`, {
bubbles: true,
cancelable: true,
})
);

// Initialize asynchronously.
//
// 1) We need to call the concrete implementation of ``init``, but the
Expand Down Expand Up @@ -60,7 +73,14 @@ class BasePattern {

// Create the options object by parsing the element and using the
// optional options as default.
this.options = this.parser?.parse(this.el, options) ?? options;
this.options =
this.parser?.parse(
this.el,
options,
this.parser_multiple,
this.parser_inherit,
this.parser_group_options
) ?? options;

// Store pattern instance on element
this.el[`pattern-${this.name}`] = this;
Expand Down
78 changes: 73 additions & 5 deletions src/core/basepattern.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("Basepattern class tests", function () {
jest.restoreAllMocks();
});

it("1 - Trigger, name and parser are statically available on the class.", async function () {
it("1.1 - Trigger, name and parser are statically available on the class.", async function () {
class Pat extends BasePattern {
static name = "example";
static trigger = ".example";
Expand All @@ -37,6 +37,57 @@ describe("Basepattern class tests", function () {
expect(typeof pat.parser.parse).toBe("function");
});

it("1.2 - Options are created with grouping per default.", async function () {
const Parser = (await import("./parser")).default;

const parser = new Parser("example");
parser.addArgument("a", 1);
parser.addArgument("camel-b", 2);
parser.addArgument("test-a", 3);
parser.addArgument("test-b", 4);

class Pat extends BasePattern {
static name = "example";
static trigger = ".example";
static parser = parser;
}

const el = document.createElement("div");
const pat = new Pat(el);
await utils.timeout(1);

expect(pat.options.a).toBe(1);
expect(pat.options.camelB).toBe(2);
expect(pat.options.test.a).toBe(3);
expect(pat.options.test.b).toBe(4);
});

it("1.3 - Option grouping can be turned off.", async function () {
const Parser = (await import("./parser")).default;

const parser = new Parser("example");
parser.addArgument("a", 1);
parser.addArgument("camel-b", 2);
parser.addArgument("test-a", 3);
parser.addArgument("test-b", 4);

class Pat extends BasePattern {
static name = "example";
static trigger = ".example";
static parser = parser;

parser_group_options = false;
}

const el = document.createElement("div");
const pat = new Pat(el);
await utils.timeout(1);

expect(pat.options.a).toBe(1);
expect(pat.options["camel-b"]).toBe(2);
expect(pat.options["test-a"]).toBe(3);
expect(pat.options["test-b"]).toBe(4);
});
it("2 - Base pattern is class based and does inheritance, polymorphism, encapsulation, ... pt1", async function () {
class Pat1 extends BasePattern {
some = "thing";
Expand Down Expand Up @@ -210,20 +261,37 @@ describe("Basepattern class tests", function () {
expect(cnt).toBe(1);
});

it("6.2 - Throws a init event after asynchronous initialization has finished.", async function () {
it("6.2 - Throws bubbling initialization events.", async function () {
const events = (await import("./events")).default;
class Pat extends BasePattern {
static name = "example";
static trigger = ".example";

async init() {
this.el.dispatchEvent(new Event("initializing"), { bubbles: true });
}
}

const el = document.createElement("div");
document.body.innerHTML = "<div></div>";
const el = document.querySelector("div");

const event_list = [];
document.body.addEventListener("pre-init.example.patterns", () =>
event_list.push("pre-init.example.patterns")
);
document.body.addEventListener("pre-init.example.patterns", () =>
event_list.push("initializing")
);
document.body.addEventListener("pre-init.example.patterns", () =>
event_list.push("init.example.patterns")
);

const pat = new Pat(el);
await events.await_pattern_init(pat);

// If test reaches this expect statement, the init event catched.
expect(true).toBe(true);
expect(event_list[0]).toBe("pre-init.example.patterns");
expect(event_list[1]).toBe("initializing");
expect(event_list[2]).toBe("init.example.patterns");
});

it("6.3 - Throws a not-init event in case of an double initialization event which is handled by await_pattern_init.", async function () {
Expand Down
77 changes: 75 additions & 2 deletions src/core/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ const get_parents = (el) => {
/**
* Return the value of the first attribute found in the list of parents.
*
* @param {DOM element} el - The DOM element to start the acquisition search for the given attribute.
* @param {Node} el - The DOM element to start the acquisition search for the given attribute.
* @param {string} attribute - Name of the attribute to search for.
* @param {Boolean} include_empty - Also return empty values.
* @param {Boolean} include_all - Return a list of attribute values found in all parents.
Expand Down Expand Up @@ -245,7 +245,7 @@ function get_css_value(el, property, as_pixels = false, as_float = false) {
* @param {String} [direction=] - Not given: Search for any scrollable element up in the DOM tree.
* ``x``: Search for a horizontally scrollable element.
* ``y``: Search for a vertically scrollable element.
* @param {(DOM Node|null)} [fallback=document.body] - Fallback, if no scroll container can be found.
* @param {(Node|null)} [fallback=document.body] - Fallback, if no scroll container can be found.
* The default is to use document.body.
*
* @returns {Node} - Return the first scrollable element.
Expand All @@ -270,6 +270,38 @@ const find_scroll_container = (el, direction, fallback = document.body) => {
return fallback;
};

/**
* Get the horizontal scroll position.
*
* @param {Node} scroll_reference - The element to get the scroll position from.
*
* @returns {number} The horizontal scroll position.
*/
const get_scroll_x = (scroll_reference) => {
// scroll_listener == window: window.scrollX
// scroll_listener == html: html.scrollLeft == window.scrollX
// scroll_listener == DOM node: node.scrollLeft
return typeof scroll_reference.scrollLeft !== "undefined"
? scroll_reference.scrollLeft
: scroll_reference.scrollX;
};

/**
* Get the vertical scroll position.
*
* @param {Node} scroll_reference - The element to get the scroll position from.
*
* @returns {number} The vertical scroll position.
*/
const get_scroll_y = (scroll_reference) => {
// scroll_listener == window: window.scrollY
// scroll_listener == html: html.scrollTop == window.scrollY
// scroll_listener == DOM node: node.scrollTop
return typeof scroll_reference.scrollTop !== "undefined"
? scroll_reference.scrollTop
: scroll_reference.scrollY;
};

/**
* Get data stored directly on the node instance.
* We are using a prefix to make sure the data doesn't collide with other attributes.
Expand Down Expand Up @@ -336,6 +368,44 @@ const template = (template_string, template_variables = {}) => {
return new Function("return `" + template_string + "`;").call(template_variables);
};

/**
* Get the visible ratio of an element compared to container.
* If no container is given, the viewport is used.
*
* Note: currently only vertical ratio is supported.
*
* @param {Node} el - The element to get the visible ratio from.
* @param {Node} [container] - The container to compare the element to.
* @returns {number} - The visible ratio of the element.
* 0 means the element is not visible.
* 1 means the element is fully visible.
*/
const get_visible_ratio = (el, container) => {
if (!el) {
return 0;
}

const rect = el.getBoundingClientRect();
const container_rect =
container !== window
? container.getBoundingClientRect()
: {
top: 0,
bottom: window.innerHeight,
};

let visible_ratio = 0;
if (rect.top < container_rect.bottom && rect.bottom > container_rect.top) {
const rect_height = rect.bottom - rect.top;
const visible_height =
Math.min(rect.bottom, container_rect.bottom) -
Math.max(rect.top, container_rect.top);
visible_ratio = visible_height / rect_height;
}

return visible_ratio;
};

const dom = {
toNodeArray: toNodeArray,
querySelectorAllAndMe: querySelectorAllAndMe,
Expand All @@ -351,10 +421,13 @@ const dom = {
create_from_string: create_from_string,
get_css_value: get_css_value,
find_scroll_container: find_scroll_container,
get_scroll_x: get_scroll_x,
get_scroll_y: get_scroll_y,
get_data: get_data,
set_data: set_data,
delete_data: delete_data,
template: template,
get_visible_ratio: get_visible_ratio,
add_event_listener: events.add_event_listener, // BBB export. TODO: Remove in an upcoming version.
remove_event_listener: events.remove_event_listener, // BBB export. TODO: Remove in an upcoming version.
};
Expand Down
Loading