Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fcfdb84
maint(pat-toggle): Add alias attribute for attr to toggle an attribute.
thet Oct 4, 2022
752036f
feat(pat-date-picker): Add placeholder support for styled behavior.
thet Oct 5, 2022
fe90c73
feat(pat-sortable): Optionally import the sortable styles.
thet Oct 5, 2022
0513102
feat(pat-sortable): Add the sortable-item class to each sortable elem…
thet Oct 5, 2022
5f3076c
feat(pat-sortable): Support dynamic sortable lists.
thet Oct 5, 2022
9ed77c8
maint(pat-validation): Rename log to logger for better naming.
thet Oct 5, 2022
c8c656a
maint(pat-validation): Use more debug messages.
thet Oct 5, 2022
831ee60
fix(pat-validation): Fallback error messages for not-before and not-a…
thet Oct 5, 2022
a8b7981
fix(pat-validation): Fix problem with multiple form validation runs.
thet Oct 5, 2022
8838da0
feat(pat-validation): Validate also newly added form elements.
thet Oct 5, 2022
a20a883
fix(pat-date-picker): Do not throw a blur event after selecting a date.
thet Oct 5, 2022
4ae9b79
Remove date-picker-bak.js file which is unused.
thet Oct 10, 2022
5546b7a
todo note - non-reachable code part. e.tagName is always undefined, i…
thet Oct 4, 2022
81c4e14
maint(pat-markdown): Modernize code.
thet Sep 29, 2022
3739935
feat(pat-markdown): Switch to marked as markdown library to support b…
thet Oct 3, 2022
8fd88c0
feat(pat-markdown): Initialize syntax highlight when parsing markup.
thet Oct 3, 2022
b8ecca4
pat-syntax-highlight: Switch to Prism syntax highlighting.
thet Oct 3, 2022
8fb23e1
maint(pat-syntax-highlight): Switch to class based pattern.
thet Oct 3, 2022
85212ba
feat(pat-syntax-highlight): Switch to highlight.js.
thet Oct 3, 2022
0f00d8c
fix(pat-syntax-highlight): Depend on highlight.js <11.
thet Oct 10, 2022
5c08b35
pat-clone-code: Add new pattern to clone code into a code tag for aut…
thet Oct 4, 2022
e1a044d
pat-clone-code: Add optional feature to format html output with prett…
thet Oct 5, 2022
76a83c5
fix(core registry): Do not scan TextNode content for patterns.
thet Oct 4, 2022
9f7f5ef
feat(core registry): Move clone-code Pattern to the beginning.
thet Oct 6, 2022
7fcfdc4
yarn install.
thet Oct 3, 2022
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
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@
"@fullcalendar/timegrid": "^5.11.3",
"@juggle/resize-observer": "^3.4.0",
"@stomp/stompjs": "^6.1.2",
"dompurify": "^2.4.0",
"google-code-prettify": "^1.0.5",
"highlight.js": "<11",
"imagesloaded": "^4.1.4",
"intersection-observer": "^0.12.2",
"jquery": "^3.6.1",
"jquery-jcrop": "^0.9.13",
"luxon": "2.4.0",
"marked": "^4.1.0",
"masonry-layout": "^4.2.2",
"moment": "^2.29.4",
"moment-timezone": "^0.5.37",
"photoswipe": "^4.1.3",
"pikaday": "^1.8.0",
"prettier": "^2.7.1",
"prismjs": "^1.29.0",
"promise-polyfill": "^8.2.3",
"screenfull": "^6.0.2",
"select2": "^3.5.1",
Expand Down
10 changes: 10 additions & 0 deletions src/core/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ const registry = {
patterns.splice(patterns.indexOf("validation"), 1);
patterns.unshift("validation");
}
// Add clone-code to the very beginning - we want to copy the markup
// before any other patterns changed the markup.
if (patterns.includes("clone-code")) {
patterns.splice(patterns.indexOf("clone-code"), 1);
patterns.unshift("clone-code");
}

return patterns;
},

Expand All @@ -150,6 +157,9 @@ const registry = {

if (typeof content === "string") {
content = document.querySelector(content);
} else if (content instanceof Text) {
// No need to scan a TextNode.
return;
} else if (content.jquery) {
content = content[0];
}
Expand Down
81 changes: 81 additions & 0 deletions src/pat/clone-code/clone-code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { BasePattern } from "../../core/basepattern";
import code_wrapper_template from "./templates/code-wrapper.html";
import dom from "../../core/dom";
import Parser from "../../core/parser";
import registry from "../../core/registry";
import utils from "../../core/utils";

export const parser = new Parser("clone-code");
parser.addArgument("source", ":first-child");
parser.addArgument("features", null, ["format"]);

class Pattern extends BasePattern {
static name = "clone-code";
static trigger = ".pat-clone-code";
parser = parser;

async init() {
// Source
if (this.options.source.lastIndexOf(":", 0) === 0) {
this.source = this.el.querySelector(this.options.source);
} else {
this.source = document.querySelector(this.options.source);
}
await this.clone();
}

async clone() {
// Clone the template.
let markup =
this.source.nodeName === "TEMPLATE"
? this.source.innerHTML
: this.source.outerHTML;

// Create temporary wrapper.
let tmp_wrapper;
if (this.source.nodeName === "HTML") {
// We have a full HTML document which we cannot wrap into a div.
tmp_wrapper = new DOMParser().parseFromString(markup, "text/html");
} else {
tmp_wrapper = document.createElement("div");
tmp_wrapper.innerHTML = markup;
}

// Remove elements with the class ``clone-ignore``.
const ignore = tmp_wrapper.querySelectorAll(".clone-ignore");
for (const _el of ignore) {
_el.remove();
}

// Get back the clone string depending of what the wrapper is.
markup =
tmp_wrapper instanceof HTMLDocument
? tmp_wrapper.documentElement.outerHTML
: tmp_wrapper.innerHTML;

if (this.options.features?.includes("format")) {
// Format the markup.
const prettier = (await import("prettier/standalone")).default;
const parser_html = (await import("prettier/parser-html")).default;
markup = prettier.format(markup, {
parser: "html",
plugins: [parser_html],
});
}

markup = utils.escape_html(markup);
const pre_code_markup = dom.template(code_wrapper_template, { markup: markup });

// Now we need to wrap the contents in any case in a div.
tmp_wrapper = document.createElement("div");
tmp_wrapper.innerHTML = pre_code_markup;
const pre_code_el = tmp_wrapper.children[0];

this.el.appendChild(pre_code_el);
registry.scan(pre_code_el);
}
}

registry.register(Pattern);
export default Pattern;
export { Pattern };
107 changes: 107 additions & 0 deletions src/pat/clone-code/clone-code.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import Pattern from "./clone-code";
import utils from "@patternslib/patternslib/src/core/utils";

describe("pat-clone-code", () => {
afterEach(() => {
document.body.innerHTML = "";
});

it("is initialized correctly", async () => {
document.body.innerHTML = `
<div class="pat-clone-code">
<p>hello world</p>
</div>
`;
const el = document.querySelector(".pat-clone-code");

new Pattern(el);
await utils.timeout(1); // wait a tick for async to settle.

const _el = document.body.querySelector(
".pat-clone-code pre code.language-html"
);
expect(_el).toBeTruthy();
expect(_el.innerHTML.trim()).toBe("&lt;p&gt;hello world&lt;/p&gt;");
expect(_el.textContent.trim()).toBe("<p>hello world</p>");
});

it("clones another source", async () => {
document.body.innerHTML = `
<div class="pat-clone-code"
data-pat-clone-code="source: html">
<p>hello world</p>
</div>
`;
const el = document.querySelector(".pat-clone-code");

new Pattern(el);
await utils.timeout(1); // wait a tick for async to settle.

console.log(document.body.innerHTML);
const _el = document.body.querySelector(
".pat-clone-code pre code.language-html"
);
expect(_el).toBeTruthy();

expect(_el.innerHTML.trim().indexOf("&lt;html&gt;")).toBe(0);
expect(_el.innerHTML.trim().indexOf("&lt;body") > 0).toBe(true);
});

it("ignores .clone-ignore", async () => {
document.body.innerHTML = `
<div class="pat-clone-code">
<div>
<div>1</div>
<div class="clone-ignore">2</div>
<div>3</div>
</div>
</div>
`;
const el = document.querySelector(".pat-clone-code");

new Pattern(el);
await utils.timeout(1); // wait a tick for async to settle.

const _el = document.body.querySelector(
".pat-clone-code pre code.language-html"
);
expect(_el).toBeTruthy();
console.log(document.body.innerHTML);
expect(_el.textContent.trim()).toBe(`<div>
<div>1</div>

<div>3</div>
</div>`);
});

it("pretty prints output", async () => {
document.body.innerHTML = `
<div class="pat-clone-code" data-pat-clone-code="features: format">
<div>
<div>1</div>
<div


>2</div>

<div>3</div>
</div>
</div>
`;
const el = document.querySelector(".pat-clone-code");

new Pattern(el);
await utils.timeout(1); // wait a tick for async to settle.

const _el = document.body.querySelector(
".pat-clone-code pre code.language-html"
);
expect(_el).toBeTruthy();
expect(_el.textContent.trim()).toBe(`<div>
<div>1</div>
<div>2</div>

<div>3</div>
</div>`);
});
});
112 changes: 112 additions & 0 deletions src/pat/clone-code/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
## Description

The clone pattern lets the website user clone elements in the page.

## Documentation

The clone pattern is typically used in case you want to create a form on which it is unknown how many instances the user will need of a certain field or group of fields.
For instance if you want to ask the user to fill out the name and birthdate of each family member.

### Usage

This pattern is enabled by adding the `pat-clone` class on a container element which contains the original element and any clones of it that may have beeen added.
The first element inside the .pat-clone container is by default assumed to be the original element may be cloned by the user.

Consider the following markup:

<h3>List of family members</h3>

<div class="pat-clone">
<!-- The first element inside the .pat-clone container is by default
assumed to be the original element which will be cloned.
-->
<fieldset class="clone"> <!-- By default, pat-clone will consider elements with the "clone" class to be clones. -->
<legend>Family member 1</legend>
<input name="name-1" type="text" placeholder="Name" />
<input name="date-1" type="date" placeholder="birthdate" /><br/>
<button type="button" class="remove-clone">Remove</button>
</fieldset>
<!-- Now comes the clone trigger, a button which when clicked will cause
a new clone of the above fieldset to be created.
-->
<button type="button" class="add-clone">Add an extra family member</button>
</div>

Each time the user clicks on the button saying 'Add an extra family member', the
pattern will make a copy of the first element inside the
`.pat-clone` element, unless the `template` property is used to configure a
different clone template. The `template` property takes a CSS selector as
value.

Typically when using a template element, such an element would be hidden from view.

The new clone is always appended at the end, inside the `.pat-clone` element.

When creating a `.pat-clone` element containing existing clones, it's
important that each existing clone either gets the `clone` CSS class or that you
pass in a unique CSS selector for each clone via the `clone-element`
property. This allows the pattern to properly determine how many existing
clones there are to start with.

#### Incrementation of values in clones

The pat-clone pattern will automatically add up any number in the values of name and value attributes.
For instance, if you have `name="item-1"` in your markup, then the first clone will be
`name="item-2"` and the one after that `name="item-3"` etc.. If you want to print a number
— for instance in a header of each clone — then you can use the syntax: `#{1}`. This string
will be replaced by an number that's also increased by 1 for each clone.

### Example with a hidden template

The markup below would have exactly the same effect as the first example, but using a hidden template. This might come in handy when the first instance shown should either contain different information, or if it will have pre-filled values by the server.

<h3>List of family members</h3>

<div class="pat-clone" data-pat-clone="template: #my-template">
<!-- First visible instance and also template -->
<fieldset class="clone">
<legend>Family member 1</legend>
<input name="Mary Johnson" type="text" placeholder="Name" />
<input name="1977-04-16" type="date" placeholder="birthdate" /><br/>
<button type="button" class="remove-clone">Remove</button>
</fieldset>

<!-- Template markup -->
<fieldset id="my-template" hidden>
<legend>Family member #{1}</legend>
<input name="name-1" type="text" placeholder="Name" />
<input name="date-1" type="date" placeholder="birthdate" /><br/>
<button type="button" class="remove-clone">Remove</button>
</fieldset>

<!-- Clone trigger -->
<button type="button" class="add-clone">Add an extra family member</button>
</div>

### Example with a hidden template which includes a pattern

Patterns in templates are initialized after cloning.
However, the patterns in the template itself are not initialized if the template has the attribute ``hidden`` or the class ``disable-patterns``.
This is to prevent double-initialization within the template and after being cloned.

<div id="template" class="disable-patterns" hidden>
<input name="date-1" type="date" class="pat-date-picker" />
</fieldset>

<div class="pat-clone" data-pat-clone="template: #template">
<button type="button" class="add-clone">Add a date</button>
</div>




### Option reference

| Property | Description | Default | Allowed Values | Type |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ----------------- | --------------------------------- |
| template | Selects the element that will be cloned each time. You might often want to refer to a piece of template markup for this that is hidden with though the CSS. | :first | | CSS Selector |
| max | Maximum number of clones that is allowed | | | Integer |
| trigger-element | Selector of the element that will add the clone when clicked upon. | .add-clone | | CSS Selector |
| remove-element | Selector of the element that will remove the clone when clicked upon. | .remove-clone | | CSS Selector |
| remove-behaviour or remove-behavior | What should happen when the user clicks on the element to remove a clone? Two choices exist currently. Show a confirmation dialog, or simply remove the clone immediately. | "confirm" | "confirm", "none" | One of the allowed string values. |
| clone-element | Selector of the individual clone element(s). | .clone | | CSS Selector |
24 changes: 24 additions & 0 deletions src/pat/clone-code/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>pat-clone demo page</title>
<meta charset="utf-8">
<link rel="stylesheet" href="/style/common.css" />
<script>
window.__patternslib_disable_modernizr = true;
</script>
<script src="/bundle.min.js"></script>
</head>
<body>

<h1>Example - clone-code</h1>

<p class="clone-ignore">Demo on how to use pat-clone together with pat-syntax-highlight to show the source of any html snippet</p>

<div class="pat-clone-code"
data-pat-clone-code="source: html">
<p>Test paragraph</p>
</div>

</body>
</html>
5 changes: 5 additions & 0 deletions src/pat/clone-code/templates/code-wrapper.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<pre class="pat-syntax-highlight">
<code class="language-html">
${this.markup}
</code>
</pre>
Loading