Skip to content

Commit 0369b49

Browse files
committed
Add before and after functions
Element and CharData both have a before & after function. Also, changed the existing functions that took a Node but should take a Node or Text, i.e append, prepend and replaceChildren. These functions, along with the new before/after all take a new NodeOrText union.
1 parent d9e5821 commit 0369b49

File tree

4 files changed

+167
-56
lines changed

4 files changed

+167
-56
lines changed

src/browser/dom/character_data.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ pub const CharacterData = struct {
112112

113113
return true;
114114
}
115+
116+
pub fn _before(self: *parser.CharacterData, nodes: []const Node.NodeOrText) !void {
117+
const ref_node = parser.characterDataToNode(self);
118+
return Node.before(ref_node, nodes);
119+
}
120+
121+
pub fn _after(self: *parser.CharacterData, nodes: []const Node.NodeOrText) !void {
122+
const ref_node = parser.characterDataToNode(self);
123+
return Node.after(ref_node, nodes);
124+
}
115125
};
116126

117127
// Tests

src/browser/dom/document.zig

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -227,28 +227,17 @@ pub const Document = struct {
227227
return css.querySelectorAll(allocator, parser.documentToNode(self), selector);
228228
}
229229

230-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
231-
// function must accept either node or string.
232-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
233-
pub fn _prepend(self: *parser.Document, nodes: []const *parser.Node) !void {
230+
pub fn _prepend(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
234231
return Node.prepend(parser.documentToNode(self), nodes);
235232
}
236233

237-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
238-
// function must accept either node or string.
239-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
240-
pub fn _append(self: *parser.Document, nodes: []const *parser.Node) !void {
234+
pub fn _append(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
241235
return Node.append(parser.documentToNode(self), nodes);
242236
}
243237

244-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
245-
// function must accept either node or string.
246-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
247-
pub fn _replaceChildren(self: *parser.Document, nodes: []const *parser.Node) !void {
238+
pub fn _replaceChildren(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
248239
return Node.replaceChildren(parser.documentToNode(self), nodes);
249240
}
250-
251-
pub fn deinit(_: *parser.Document, _: std.mem.Allocator) void {}
252241
};
253242

254243
const testing = @import("../../testing.zig");

src/browser/dom/element.zig

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -313,24 +313,25 @@ pub const Element = struct {
313313
return css.querySelectorAll(state.arena, parser.elementToNode(self), selector);
314314
}
315315

316-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
317-
// function must accept either node or string.
318-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
319-
pub fn _prepend(self: *parser.Element, nodes: []const *parser.Node) !void {
316+
pub fn _prepend(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
320317
return Node.prepend(parser.elementToNode(self), nodes);
321318
}
322319

323-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
324-
// function must accept either node or string.
325-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
326-
pub fn _append(self: *parser.Element, nodes: []const *parser.Node) !void {
320+
pub fn _append(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
327321
return Node.append(parser.elementToNode(self), nodes);
328322
}
329323

330-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
331-
// function must accept either node or string.
332-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
333-
pub fn _replaceChildren(self: *parser.Element, nodes: []const *parser.Node) !void {
324+
pub fn _before(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
325+
const ref_node = parser.elementToNode(self);
326+
return Node.before(ref_node, nodes);
327+
}
328+
329+
pub fn _after(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
330+
const ref_node = parser.elementToNode(self);
331+
return Node.after(ref_node, nodes);
332+
}
333+
334+
pub fn _replaceChildren(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
334335
return Node.replaceChildren(parser.elementToNode(self), nodes);
335336
}
336337

@@ -529,4 +530,28 @@ test "Browser.DOM.Element" {
529530
.{ "el.matches('#9000')", "false" },
530531
.{ "el.matches('.notok')", "false" },
531532
}, .{});
533+
534+
// before
535+
try runner.testCases(&.{
536+
.{ "const before_container = document.createElement('div');", "undefined" },
537+
.{ "document.append(before_container);", "undefined" },
538+
.{ "const b1 = document.createElement('div');", "undefined" },
539+
.{ "before_container.append(b1);", "undefined" },
540+
541+
.{ "const b1_a = document.createElement('p');", "undefined" },
542+
.{ "b1.before(b1_a, 'over 9000');", "undefined" },
543+
.{ "before_container.innerHTML", "<p></p>over 9000<div></div>" },
544+
}, .{});
545+
546+
// after
547+
try runner.testCases(&.{
548+
.{ "const after_container = document.createElement('div');", "undefined" },
549+
.{ "document.append(after_container);", "undefined" },
550+
.{ "const a1 = document.createElement('div');", "undefined" },
551+
.{ "after_container.append(a1);", "undefined" },
552+
553+
.{ "const a1_a = document.createElement('p');", "undefined" },
554+
.{ "a1.after('over 9000', a1_a);", "undefined" },
555+
.{ "after_container.innerHTML", "<div></div>over 9000<p></p>" },
556+
}, .{});
532557
}

src/browser/dom/node.zig

Lines changed: 117 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -337,65 +337,72 @@ pub const Node = struct {
337337
// For now, it checks only if new nodes are not self.
338338
// TODO implements the others contraints.
339339
// see https://dom.spec.whatwg.org/#concept-node-tree
340-
pub fn hierarchy(self: *parser.Node, nodes: []const *parser.Node) !bool {
341-
if (nodes.len == 0) return true;
342-
343-
for (nodes) |node| if (self == node) return false;
344-
340+
pub fn hierarchy(self: *parser.Node, nodes: []const NodeOrText) bool {
341+
for (nodes) |n| {
342+
if (n.is(self)) {
343+
return false;
344+
}
345+
}
345346
return true;
346347
}
347348

348-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
349-
// function must accept either node or string.
350-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
351-
pub fn prepend(self: *parser.Node, nodes: []const *parser.Node) !void {
352-
if (nodes.len == 0) return;
349+
pub fn prepend(self: *parser.Node, nodes: []const NodeOrText) !void {
350+
if (nodes.len == 0) {
351+
return;
352+
}
353353

354354
// check hierarchy
355-
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
355+
if (!hierarchy(self, nodes)) {
356+
return parser.DOMError.HierarchyRequest;
357+
}
358+
359+
const doc = (try parser.nodeOwnerDocument(self)) orelse return;
356360

357-
const first = try parser.nodeFirstChild(self);
358-
if (first == null) {
361+
if (try parser.nodeFirstChild(self)) |first| {
359362
for (nodes) |node| {
360-
_ = try parser.nodeAppendChild(self, node);
363+
_ = try parser.nodeInsertBefore(self, try node.toNode(doc), first);
361364
}
362365
return;
363366
}
364367

365368
for (nodes) |node| {
366-
_ = try parser.nodeInsertBefore(self, node, first.?);
369+
_ = try parser.nodeAppendChild(self, try node.toNode(doc));
367370
}
368371
}
369372

370-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
371-
// function must accept either node or string.
372-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
373-
pub fn append(self: *parser.Node, nodes: []const *parser.Node) !void {
374-
if (nodes.len == 0) return;
373+
pub fn append(self: *parser.Node, nodes: []const NodeOrText) !void {
374+
if (nodes.len == 0) {
375+
return;
376+
}
375377

376378
// check hierarchy
377-
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
379+
if (!hierarchy(self, nodes)) {
380+
return parser.DOMError.HierarchyRequest;
381+
}
378382

383+
const doc = (try parser.nodeOwnerDocument(self)) orelse return;
379384
for (nodes) |node| {
380-
_ = try parser.nodeAppendChild(self, node);
385+
_ = try parser.nodeAppendChild(self, try node.toNode(doc));
381386
}
382387
}
383388

384-
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
385-
// function must accept either node or string.
386-
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
387-
pub fn replaceChildren(self: *parser.Node, nodes: []const *parser.Node) !void {
388-
if (nodes.len == 0) return;
389+
pub fn replaceChildren(self: *parser.Node, nodes: []const NodeOrText) !void {
390+
if (nodes.len == 0) {
391+
return;
392+
}
389393

390394
// check hierarchy
391-
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
395+
if (!hierarchy(self, nodes)) {
396+
return parser.DOMError.HierarchyRequest;
397+
}
392398

393399
// remove existing children
394400
try removeChildren(self);
395401

402+
const doc = (try parser.nodeOwnerDocument(self)) orelse return;
396403
// add new children
397404
for (nodes) |node| {
398-
_ = try parser.nodeAppendChild(self, node);
405+
_ = try parser.nodeAppendChild(self, try node.toNode(doc));
399406
}
400407
}
401408

@@ -414,7 +421,87 @@ pub const Node = struct {
414421
}
415422
}
416423

417-
pub fn deinit(_: *parser.Node, _: std.mem.Allocator) void {}
424+
pub fn before(self: *parser.Node, nodes: []const NodeOrText) !void {
425+
const parent = try parser.nodeParentNode(self) orelse return;
426+
const doc = (try parser.nodeOwnerDocument(parent)) orelse return;
427+
428+
var sibling: ?*parser.Node = self;
429+
// have to find the first sibling that isn't in nodes
430+
CHECK: while (sibling) |s| {
431+
for (nodes) |n| {
432+
if (n.is(s)) {
433+
sibling = try parser.nodePreviousSibling(s);
434+
continue :CHECK;
435+
}
436+
}
437+
break;
438+
}
439+
440+
if (sibling == null) {
441+
sibling = try parser.nodeFirstChild(parent);
442+
}
443+
444+
if (sibling) |ref_node| {
445+
for (nodes) |node| {
446+
_ = try parser.nodeInsertBefore(parent, try node.toNode(doc), ref_node);
447+
}
448+
return;
449+
}
450+
451+
return Node.prepend(self, nodes);
452+
}
453+
454+
pub fn after(self: *parser.Node, nodes: []const NodeOrText) !void {
455+
const parent = try parser.nodeParentNode(self) orelse return;
456+
const doc = (try parser.nodeOwnerDocument(parent)) orelse return;
457+
458+
// have to find the first sibling that isn't in nodes
459+
var sibling = try parser.nodeNextSibling(self);
460+
CHECK: while (sibling) |s| {
461+
for (nodes) |n| {
462+
if (n.is(s)) {
463+
sibling = try parser.nodeNextSibling(s);
464+
continue :CHECK;
465+
}
466+
}
467+
break;
468+
}
469+
470+
if (sibling) |ref_node| {
471+
for (nodes) |node| {
472+
_ = try parser.nodeInsertBefore(parent, try node.toNode(doc), ref_node);
473+
}
474+
return;
475+
}
476+
477+
for (nodes) |node| {
478+
_ = try parser.nodeAppendChild(parent, try node.toNode(doc));
479+
}
480+
}
481+
482+
// A lot of functions take either a node or text input.
483+
// The text input is to be converted into a Text node.
484+
pub const NodeOrText = union(enum) {
485+
text: []const u8,
486+
node: *parser.Node,
487+
488+
fn toNode(self: NodeOrText, doc: *parser.Document) !*parser.Node {
489+
return switch (self) {
490+
.node => |n| n,
491+
.text => |txt| @ptrCast(try parser.documentCreateTextNode(doc, txt)),
492+
};
493+
}
494+
495+
// Whether the node represented by the NodeOrText is the same as the
496+
// given Node. Always false for text values as these represent as-of-yet
497+
// created Text nodes.
498+
fn is(self: NodeOrText, other: *parser.Node) bool {
499+
return switch (self) {
500+
.text => false,
501+
.node => |n| n == other,
502+
};
503+
}
504+
};
418505
};
419506

420507
const testing = @import("../../testing.zig");

0 commit comments

Comments
 (0)