Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
forked fromkaraxnim/karax

Karax. Single page applications for Nim.

License

NotificationsYou must be signed in to change notification settings

planety/karax

 
 

Repository files navigation

Karax is a framework for developing single page applications in Nim.

To try it out, run

cd ~/projects # Insert your favourite directory for projectsnimble develop karax # This will clone Karax and create a link to it in ~/.nimblecd karaxcd examples/todoappnim js todoapp.nimopen todoapp.htmlcd ../..cd examples/mediaplayernim js playerapp.nimopen playerapp.html

It uses a virtual DOM like React, but is much smaller than the existingframeworks plus of course it's written in Nim for Nim. No externaldependencies! And thanks to Nim's whole program optimization only whatis used ends up in the generated JavaScript code.

Goals

  • Leverage Nim's macro system to produce a framework that allowsfor the development of applications that are boilerplate free.
  • Keep it small, keep it fast, keep it flexible.

Hello World

The simplest Karax program looks like this:

import karax/ preludeproccreateDom():VNode=result=buildHtml(tdiv):text"Hello World!"setRenderer createDom

Sincediv is a keyword in Nim, karax choose to usetdiv insteadhere.tdiv produces a<div> virtual DOM node.

As you can see, karax comes with its ownbuildHtml DSL for convenientconstruction of (virtual) DOM trees (of typeVNode). Karax providesa tiny build tool calledkarun that generates the HTML boilerplate code thatembeds and invokes the generated JavaScript code

nim c karax/tools/karunkarax/tools/karun -r helloworld.nim

Via-d:debugKaraxDsl we can have a look at the produced Nim code bybuildHtml:

let tmp1=tree(VNodeKind.tdiv)add(tmp1,text"Hello World!")tmp1

(I shortened the IDs for better readability.)

Ok, sobuildHtml introduces temporaries and callsadd for the treeconstruction so that it composes with all of Nim's control flow constructs:

import karax/ preludeimport randproccreateDom():VNode=result=buildHtml(tdiv):ifrandom(100)<=50:text"Hello World!"else:text"Hello Universe"randomize()setRenderer createDom

Produces:

let tmp1=tree(VNodeKind.tdiv)ifrandom(100)<=50:add(tmp1,text"Hello World!")else:add(tmp1,text"Hello Universe")tmp1

Event model

Karax does not change the DOM's event model much, here is a programthat writes "Hello simulated universe" on a button click:

import karax/ prelude# alternatively: import karax / [kbase, vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils, jjson]var lines:seq[kstring]=@[]proccreateDom():VNode=result=buildHtml(tdiv):    button:text"Say hello!"proconclick(ev:Event; n:VNode)=        lines.add"Hello simulated universe"for xin lines:      tdiv:text xsetRenderer createDom

kstring is Karax's alias forcstring (which stands for "compatiblestring"; for the JS target that is an immutable JavaScript string) whichis preferred for efficiency on the JS target. However, on the native targetskstring is mapped tostring for efficiency. The DSL for HTMLconstruction is also avaible for the native targets (!) and thekstringabstraction helps to deal with these conflicting requirements.

Karax's DSL is quite flexible when it comes to event handlers, so thefollowing syntax is also supported:

import karax/ preludefrom sugarimport `=>`var lines:seq[kstring]=@[]proccreateDom():VNode=result=buildHtml(tdiv):button(onclick= ()=> lines.add"Hello simulated universe"):text"Say hello!"for xin lines:      tdiv:text xsetRenderer createDom

ThebuildHtml macro produces this code for us:

let tmp2=tree(VNodeKind.tdiv)let tmp3=tree(VNodeKind.button)addEventHandler(tmp3,EventKind.onclick,                  ()=> lines.add"Hello simulated universe", kxi)add(tmp3,text"Say hello!")add(tmp2, tmp3)for xin lines:let tmp4=tree(VNodeKind.tdiv)add(tmp4,text x)add(tmp2, tmp4)  tmp2

As the examples grow larger it becomes more and more visible of whata DSL that composes with the builtin Nim control flow constructs buys us.Once you have tasted this power there is no going back and languageswithout AST based macro system simply don't cut it anymore.

Attaching data to an event handler

Since the type of an event handler is(ev: Event; n: VNode) or() anyadditional data that should be passed to the event handler needs to bedone via Nim's closures. In general this means a pattern like this:

procmenuAction(menuEntry: kstring):proc()=result=proc()=echo"clicked", menuEntryprocbuildMenu(menu:seq[kstring]):VNode=result=buildHtml(tdiv):for min menu:nav(class="navbar is-primary"):tdiv(class="navbar-brand"):a(class="navbar-item", onclick=menuAction(m))

DOM diffing

Ok, so now we have seen DOM creation and event handlers. But how doesKarax actually keep the DOM up to date? The trick is that every eventhandler is wrapped in a helper proc that triggers aredraw operationthat calls therenderer that you initially passed tosetRenderer.So a new virtual DOM is created and compared against the previousvirtual DOM. This comparison produces a patch set that is then appliedto the real DOM the browser uses internally. This process is called"virtual DOM diffing" and other frameworks, most notably Facebook'sReact, do quite similar things. The virtual DOM is faster to createand manipulate than the real DOM so this approach is quite efficient.

Form validation

Most applications these days have some "login"mechanism consisting ofusername andpassword andalogin button. The login button should only be clickableifusername andpassword are not empty. An errormessage should be shown as long as one input field is empty.

To create new UI elements we write aloginField proc thatreturns aVNode:

procloginField(desc, field, class: kstring;                validator:proc (field: kstring):proc ()):VNode=result=buildHtml(tdiv):label(`for`= field):text descinput(class= class, id= field, onchange=validator(field))

We use thekarax / errors module to help with this errorlogic. Theerrors module is mostly a mapping from strings tostrings but it turned out that the logic is tricky enough to warranta library solution.validateNotEmpty returns a closure thatcaptures thefield parameter:

procvalidateNotEmpty(field: kstring):proc ()=result=proc ()=let x=getVNodeById(field)if x.text.isNilor x.text=="":      errors.setError(field, field&" must not be empty")else:      errors.setError(field,"")

This indirection is required becauseevent handlers in Karax need to have the typeproc ()orproc (ev: Event; n: VNode). The errors module alsogives us a handydisableOnError helper. It returns"disabled" if there are errors. Now we have all thepieces together to write our login dialog:

# some consts in order to prevent typos:const  username=kstring"username"  password=kstring"password"var loggedIn:boolprocloginDialog():VNode=result=buildHtml(tdiv):ifnot loggedIn:loginField("Name :", username,"input", validateNotEmpty)loginField("Password:", password,"password", validateNotEmpty)button(onclick= ()=> (loggedIn=true), disabled= errors.disableOnError()):text"Login"      p:text errors.getError(username)      p:text errors.getError(password)else:      p:text"You are now logged in."setRenderer loginDialog

Full examplehere

This code still has a bug though, when you run it, thelogin button is notdisabled until some input fields are validated! This is easily fixed,at initialization we have to do:

setError username, username&" must not be empty"setError password, password&" must not be empty"

There are likely more elegant solutions to this problem.

Routing

For routingsetRenderer can be called with a callback that takes a parameter oftypeRouterData. Here is the relevant excerpt from the famous "Todo App" example:

proccreateDom(data:RouterData):VNode=if data.hashPart=="#/": filter= allelif data.hashPart=="#/completed": filter= completedelif data.hashPart=="#/active": filter= activeresult=buildHtml(tdiv(class="todomvc-wrapper")):section(class="todoapp"):...setRenderer createDom

Full examplehere

Server Side HTML Rendering

Karax can also be used to render HTML on the server. Only a subset ofmodules can be used since there is no JS interpreter.

import karax/ [karaxdsl, vdom]const places=@["boston","cleveland","los angeles","new orleans"]procrender*():string=let vnode=buildHtml(tdiv(class="mt-3")):    h1:text"My Web Page"    p:text"Hello world"    ul:for placein places:        li:text place    dl:      dt:text"Can I use Karax for client side single page apps?"      dd:text"Yes"      dt:text"Can I use Karax for server side HTML rendering?"      dd:text"Yes"result=$vnodeechorender()

About

Karax. Single page applications for Nim.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Nim86.5%
  • SCSS8.2%
  • CSS5.3%

[8]ページ先頭

©2009-2025 Movatter.jp