Skip to content

Instantly share code, notes, and snippets.

@FCO

FCO/index.html Secret

Created December 2, 2018 22:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FCO/f38fa33d2aea5268f409f785fdcf1aae to your computer and use it in GitHub Desktop.
Save FCO/f38fa33d2aea5268f409f785fdcf1aae to your computer and use it in GitHub Desktop.
<html>
<head>
<script src="./todo-complete.p6"></script>
</head>
<body>
<span id="todoapp">loading...</span>
</body>
</html>
{
"name": "todo-app",
"version": "1.0.0",
"description": "todo app",
"main": "index.js",
"dependencies": {
"parcel": "^1.9.7",
"parcel-plugin-nqp": "0.20.0",
"rakudo": "^0.21.0",
"webpack": "^4.26.1"
},
"scripts": {
"start": "parcel index.html"
},
"devDependencies": {
"webpack-cli": "^3.1.2"
},
"author": "Fernando Correa",
"license": "Artistic-2.0"
}
my \document = EVAL :lang<JavaScript>, 'return document';
EVAL :lang<JavaScript>, 'HTMLElement.prototype.defined = function() { return true }';
sub h1(*@inner, *%pars) {
$*parent.element("h1", @inner, |%pars)
}
sub p(*@inner, *%pars) {
$*parent.element("p", @inner, |%pars)
}
sub ul(*@inner, *%pars) {
$*parent.element("ul", @inner, |%pars)
}
sub li(*@inner, *%pars) {
$*parent.element("li", @inner, |%pars)
}
sub form(*@inner, *%pars) {
$*parent.element("form", @inner, |%pars)
}
sub input(*@inner, *%pars) {
$*parent.element("input", @inner, |%pars)
}
role Tag { ... }
class Element {
has @.cache;
has Tag $.owner is required;
has $.tag;
has $.dom-element = document.createElement($!tag);
method TWEAK(|) {
say "creating DOM element: { $!tag // "???" }";
}
multi method add-event-listener(Str $event, &handler) {
$!dom-element.addEventListener: $event, &handler
}
multi method add-event-listener(Str $event, Str $handler) {
nextwith $event, $!owner.^lookup($handler).assuming: $!owner
}
method class-name is rw {
$!dom-element<class>
}
method input-value($val) {
$!dom-element<value> = $val
}
method set-type(Str $type) {
$!dom-element<type> = $type
}
method style(*%styles) {
for %styles.kv -> $name, $value {
$!dom-element<style>{$name} = $value
}
}
method set-content(*@content) {
while $!dom-element<firstChild> {
$!dom-element.removeChild: $!dom-element<firstChild>;
}
$!dom-element.appendChild: $_
for @content.map({ .?get-tag-data // $_ }).map({ .?dom-element // document.createTextNode: .Str })
}
method checked(Bool $checked) {
$!dom-element<checked> = $checked ?? "checked" !! ""
}
}
role Tag does Callable {
has @!cache;
has Element $.root;
method CALL-ME(|c) {
if @*cache.defined and $*counter.defined and @*cache[$*counter++]:exists {
my $cached = @*cache[$*counter-1];
for c.hash.kv -> $attr, $value {
try $cached."$attr"() = $value
}
return $cached
}
say "creating a new Tag";
my $tag = ::?CLASS.new: |c;
@*cache.push: $tag with @*cache;
$tag
}
method mount-on($root) {
$!root = Element.new: :dom-element($root), :owner(self);
self.call-render
}
method create-element(Str $tag) {
Element.new: :$tag, :owner(self)
}
multi method element(
$tag,
$inner? is copy,
:$class is copy,
:$style is copy,
:%event,
:$type,
:$value is copy,
:$checked is copy
) {
my &inner;
my @inner;
my @dyn-inner;
my &set-value;
my &class;
my &style;
my @styles;
my @callable;
my &checked;
given $inner {
when Callable {
&inner = $inner;
$inner = Nil;
}
when Positional {
@inner = @$inner;
}
default {
@inner = $inner
}
}
if @inner.any ~~ Callable {
@dyn-inner = @inner;
@inner = ()
}
if $value ~~ Callable {
&set-value = $value;
$value = Nil
}
if $class ~~ Callable {
&class = $class;
$class = Nil;
}
with $style {
when Callable {
&style = $style;
$style = Nil;
}
when Associative {
(.value ~~ Callable ?? @callable !! @styles).push: $_ for .pairs
}
}
if $checked ~~ Callable {
&checked = $checked;
$checked = Nil
}
my $el;
if @*cache[$*counter++]:exists {
$el = @*cache[$*counter - 1]
} else {
given $el = self.create-element: $tag {
$el.class-name = $_ with $class;
$el.set-content: @inner if @inner;
$el.input-value: $_ with $value;
$el.set-type: $_ with $type;
$el.style: |$_ for @styles;
$el.add-event-listener: .key, .value for %event;
$el.checked: $_ with $checked;
}
@*cache.push: $el;
}
{
my @*cache := $el.cache;
my $*counter = 0;
$el.set-content: .() with &inner;
$el.set-content: @dyn-inner.map: { $_ ~~ Callable ?? .() !! $_ } if @dyn-inner;
with &class {
$el.class-name = $_ with .()
}
$el.input-value: .() with &set-value;
$el.style: |.() with &style;
$el.style: |$_ for @callable.map: { .key => .value.() };
$el.checked: .() with &checked;
}
$el
}
method get-tag-data {
my $*counter = 0;
my @*cache := @!cache;
my $*parent = self;
self.render;
}
method call-render {
$!root.set-content: self.get-tag-data
}
method render { ... }
}
############################################################
class Todo does Tag {
has Str $.title;
has Bool $.done is rw;
has &.toggle is required;
method render {
li(
:style{
$!done
?? { :textDecoration<line-through>, :opacity<0.3> }
!! { :textDecoration<none> , :opacity<1> }
},
:event{
:click( &!toggle )
},
input(
:type<checkbox>,
:checked{ $!done },
),
$!title
)
}
}
class App does Tag {
has @.todos;
has Str $.new-title = "";
method render {
form(
:event{
:submit{
.preventDefault;
@!todos.push: {
:title($!new-title),
:!done
};
$!new-title = "";
self.call-render
}
},
ul({ do for @!todos -> (Str :$title!, Bool :$done! is rw) {
Todo(:$title, :$done, :toggle{ $done = !$done; self.call-render })
}}),
input(
:type<text>,
:event{
:keyup{ $!new-title = .<target><value> }
},
:value{ $!new-title }
),
input(
:type<submit>,
:value<OK>,
)
)
}
}
my $app = App(:todos[
{:title<bla>, :!done},
{:title<ble>, :done },
{:title<bli>, :!done},
]);
$app.mount-on: document.getElementById: 'todoapp';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment