-
-
Save FCO/f38fa33d2aea5268f409f785fdcf1aae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<script src="./todo-complete.p6"></script> | |
</head> | |
<body> | |
<span id="todoapp">loading...</span> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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