Giter Club home page Giter Club logo

karax's People

Contributors

ajusa avatar alehander92 avatar araq avatar bluenote10 avatar bung87 avatar chancyk avatar choltreppe avatar darkmusic avatar dawkot avatar daylinmorgan avatar dom96 avatar geotre avatar gogolxdong avatar hitoshi44 avatar kidandcat avatar metagn avatar moigagoo avatar monyarm avatar not-lum avatar oskca avatar planetis-m avatar pnuzhdin avatar ringabout avatar ryukoposting avatar scattergory avatar timotheecour avatar untoreh avatar willyboar avatar xyb avatar zedeus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

karax's Issues

Graph without explanation

The graph showing performance comparison should really have some kind of explanation with it. Specifically the colours aren't documented anywhere and makes the whole thing rather confusing..

echartstest foreign node issue with click routing

type
  clickHandler* = proc():VNode

var view* :clickHandler 

proc click*(x : clickHandler): proc() = 
  result = proc() = view = x

proc echartSection():VNode = 
    buildHtml tdiv(id = "echartSection", style = style((width, kstring"600px"), (height, kstring"400px")))

proc createDom(data: RouterData): VNode =
  let hash = data.hashPart
  if hash == cstring"#/Products": currentView = Products
  else: currentView = Customers

  result = buildHtml(tdiv):
    ul(class = "tabs"):
      for v in low(Views)..high(Views):
        li:
          if v == Products:
              a(href = "#/" & $v, onclick = click echartSection):
                text kstring($v)
          else:
            a(href = "#/" & $v):
              text kstring($v)
    tdiv:
      text "other section"

setRenderer createDom, "ROOT", postRender
setForeignNodeId "echartSection"

got

Uncaught Error: Initialize failed: invalid dom.
    at Object.init (echarts.js:1778)
    at Object.post_render_256045 [as postRenderCallback] (echartstest.js:3454)
    at dodraw_249631 (echartstest.js:2870)
    at HEX3Aanonymous_251001 (echartstest.js:2913)

Allow to customize the ID of the root tag

Currently the ID is hard-coded to "ROOT", which can clash with existing IDs. Ideally the ID should be configurable.

Also, if the ID is not found, the error message could be more helpful (currently fails with a cryptic "x_84928 is null" error).

How to set and get cookie?

I used

        function setCookie(cname, cvalue, exdays) {
            var exdate = new Date();
            exdate.setDate(exdate.getDate() + exdays);
            document.cookie = cname + "=" + escape(cvalue) + ((exdays == null) ? "" : ";expires=" + exdate.toGMTString());
        }

        function getCookie(cname) {
            var name = cname + "=";
            var ca = document.cookie.split(';');
            for (var i = 0; i < ca.length; i++) {
                var c = ca[i];
                while (c.charAt(0) == ' ') {
                    c = c.substring(1);
                }
                if (c.indexOf(name) == 0) {
                    return c.substring(name.length, c.length);
                }
            }
            return "";
        }

in the html and import as

proc setCookie(cname:kstring, cvalue:kstring, exdays:int) {.importc.}
proc getCookie(cname:kstring):kstring {.importc.}

or

proc setCookie(cname:kstring, cvalue:kstring, exdays:int) = 
  if cname == kstring"":
    document.cookie = kstring""
  else:
    document.cookie = cname & "=" & cvalue & ";expires={exdays}"

proc getCookie(cname:kstring):cstring = document.cookie

Diffing performance issue

I can confirm that the event handler update problem (#35) is solved now for me. However it seems that this can trigger problems in the diffing. The problem is not visible in the small example attached to the other issue, but with a slightly more complex VDOM I'm now seeing update times >10 sec and "unresponsive scripts" warnings. The old diffing which did not update the event handlers was very quick.

I had some difficulties to reproduce the problem in smaller examples, so I'm attaching a more complete version. The problem should be reproducible via karun and clicking on one of the "x" elements.

include karaxprelude
import sequtils, future

type
  Model = ref object
    entries: seq[cstring]

proc postRenderCallback() =
  discard

proc onClick(ev: Event, n: VNode) =
  discard

proc onDragStart(ev: Event, n: VNode) =
  discard

proc dummy(): VNode =
  result = buildHtml():
    tdiv()

proc panel(title: string, body: VNode): VNode =
  result = buildHtml():
    tdiv(class="panel panel-default"):
      tdiv(class="panel-heading"):
        text title
      tdiv(class="panel-collapse"):
        tdiv(class="panel-body"):
          body

proc threePanels(viewL, viewC, viewR: VNode): VNode =
  result = buildHtml():
    tdiv(class="row"):
      tdiv(class="col-xs-2 sidebar sidebar-left"):
        viewL
      tdiv(class="col-xs-8 col-xs-offset-2"):
        viewC
      tdiv(class="col-xs-2 sidebar sidebar-right"):
        viewR

proc renderEntry(model: Model, entries: seq[cstring], elementIndex: int): VNode =
  proc onRemove(ev: Event, n: VNode) =
    model.entries.delete(elementIndex)
  result = buildHtml():
    span(id=entries[elementIndex], class="some-classes", draggable="true", ondragstart=onDragStart):
      span(class="label-float-left"):
        text entries[elementIndex]
      a(type="button", class="close label-float-right", onclick=onRemove):
        text "ร—"

proc dropzone(model: Model, entries: seq[cstring]): VNode =
  result = buildHtml():
    tdiv(class="dropzone"):
      for elementIndex, entry in entries.pairs():
        renderEntry(model, entries, elementIndex)

proc rowElement(model: Model, title: string, entries: seq[cstring], dropzoneId: cstring): VNode =
  result = buildHtml():
    tdiv(class="row vertical-align"):
      tdiv(class="col-xs-2"):
        text title
      tdiv(class="col-xs-10"):
        dropzone(model, entries)

proc viewCenter(model: Model): VNode =
  panel("Panel Header"):
    flatHtml():
      tdiv():
        rowElement(model, "Entries", model.entries, "rows".cstring)
        tdiv(class="form-group text-right"):
          tdiv(class="checkbox"):
            label():
              input(type="checkbox")
              text "Automatic update"
          button(type="button", class="btn btn-primary btn-sm", onclick=onClick):
            text "Update"

proc view(model: Model): VNode =
  result = buildHtml():
    tdiv():
      tdiv():
        text "Navbar here"
      tdiv(class="container-fluid below-navbar"):
        threePanels(
          dummy(),
          viewCenter(model),
          dummy(),
        )

proc runMain() =
  var model = Model(
    entries: toSeq(1..20).map(x => cstring($x))
  )
  proc renderer(): VNode =
    view(model)
  setRenderer(renderer, root="ROOT", clientPostRenderCallback=postRenderCallback)

runMain()

Color helpers

Hi I want to propose following color helpers:

type
  RGB = tuple
    R: int
    G: int
    B: int

proc `&`*(x: RGB): cstring =
  &x.R & cstring"," & &x.G & cstring"," & &x.B

proc rgb*(rgb: RGB): cstring =
  cstring"rgb(" & &rgb & cstring")"

proc rgba*(rgb: RGB, alfa: float): cstring =
  cstring"rgba(" & &rgb & cstring"," & &alfa & cstring")"

Usage:

const WhiteColor* = (R: 255, G: 255, B: 255)

tdiv(style=style(StyleAttr.color, rgba(WhiteColor, 0.5)):
  text "Test"

Example from karax readme: Error: cannot open 'vdom'

I wanted to give karax a try on my MacBook Pro with Nim 0.16.0 installed, starting with the very first example on the readme, but in iTerm I got:

$ cd examples/todoapp
$ nim js todoapp.nim
Hint: used config file '/usr/local/Cellar/nim/0.16.0/nim/config/nim.cfg' [Conf]
Hint: system [Processing]
Hint: todoapp [Processing]
todoapp.nim(2, 8) Error: cannot open 'vdom'

Is div still a keyword?

The readme refers to div as a keyword in nim. Is this still the case. Wouldn't using div be better?

Karax license?

Nimble says Karax is MIT licensed, but actually the repo does not contain a license, which probably means that both private and commercial use are not permitted ;).

nimble install: system module needs string

Hi there,
I wanted again to try Karax with Nim 0.17.0 and a "nimble install" at the project root gives me

$ nimble install                                                           *[master] 
     Info Hint: used config file '/etc/nim.cfg' [Conf]
     Error: Could not read package info file in /home/vince/bacasable/karax/karax.nimble;
        ...   Reading as ini file failed with: 
        ...     Invalid section: .
        ...   Evaluating as NimScript file failed with: 
        ...     Error: system module needs 'string'.

If it is not necessary to do that (as discussed in #2), then I have the same errors as discussed here #6 (comment)

meaning with nim js todoapp.nim:

/home/vince/bacasable/karax/src/components.nim(82, 13) Error: type mismatch: got (NimNode, proc (bl: typed, op: string): NimNode{.noSideEffect.}, void)
but expected one of: 
proc add(x: var cstring; y: cstring)
proc add[T](x: var seq[T]; y: openArray[T])
proc add(x: var string; y: cstring)
proc add[T](x: var seq[T]; y: T)
proc add(x: var string; y: string)
proc add(x: var string; y: char)
proc add(parent, kid: VNode)
proc add(c: ClassList; class: cstring)
proc add[A, B](t: var OrderedTable[A, B]; key: A; val: B)
proc add(father, child: NimNode): NimNode
proc add[A, B](t: TableRef[A, B]; key: A; val: B)
proc add(father: NimNode; children: varargs[NimNode]): NimNode
proc add[A, B](t: var Table[A, B]; key: A; val: B)
proc add[A, B](t: OrderedTableRef[A, B]; key: A; val: B)

Strange behaviour of focus/blur and VComponent.markDirty

Try to click in the input. Observe that border of the input has blinked to red but return back to the black because input has been replaced by markDirty which caused blur event right after the focus.

import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils

type TextInput* = ref object of VComponent
  value: cstring
  isActive: bool

proc render(x: VComponent): VNode =
  let self = TextInput(x)

  let style = style(
    (StyleAttr.position, cstring"relative"),
    (StyleAttr.paddingLeft, cstring"10px"),
    (StyleAttr.paddingRight, cstring"5px"),
    (StyleAttr.height, cstring"30px"),
    (StyleAttr.lineHeight, cstring"30px"),
    (StyleAttr.border, cstring"solid 1px " & (if self.isActive: cstring"red" else: cstring"black")),
    (StyleAttr.fontSize, cstring"12px"),
    (StyleAttr.fontWeight, cstring"600")
  ).merge(self.style)

  let inputStyle = style.merge(style(
    (StyleAttr.color, cstring"inherit"),
    (StyleAttr.fontSize, cstring"inherit"),
    (StyleAttr.fontWeight, cstring"inherit"),
    (StyleAttr.fontFamily, cstring"inherit"),
    (StyleAttr.position, cstring"absolute"),
    (StyleAttr.top, cstring"0"),
    (StyleAttr.left, cstring"0"),
    (StyleAttr.height, cstring"100%"),
    (StyleAttr.width, cstring"100%"),
    (StyleAttr.border, cstring"none"),
    (StyleAttr.backgroundColor, cstring"transparent"),
  ))

  proc onfocus(ev: Event; n: VNode) =
    if not self.isActive:
      kout cstring"focus"
      self.isActive = true
      markDirty(self)

  proc onblur(ev: Event; n: VNode) =
    if self.isActive:
      kout cstring"blur"
      self.isActive = false
      markDirty(self)

  result = buildHtml(tdiv(style=style)):
    input(style=inputStyle, value=self.value, onblur=onblur, onfocus=onfocus)

proc newTextInput*(style: VStyle = VStyle(); value: cstring = cstring""): TextInput =
  result = newComponent(TextInput, render)
  result.style = style
  result.value = value

proc createDom(): VNode =
  result = buildHtml(tdiv):
    newTextInput(value=cstring"test")

setRenderer createDom

Publishing helper modules seperately?

Have you considered publishing the jdict and jstrutils modules as separate nimble packages? I think they are very universal and I would like to import them in other projects.

Event handler doesn't update when DOM elements are deleted

I'm currently dealing with issues which come down to the following behavior. Small example with dynamic deletes from a closure event handler:

include karaxprelude

var elements = @[cstring"A", cstring"B", cstring"C", cstring"D", cstring"E"]

proc renderElement(text: cstring, index: int): VNode =

  kout(cstring"re-building closure for", text, index)
  proc remove(ev: Event, n: VNode) =
    kout(cstring"Deleting element from index: ", index)
    elements.delete(index)

  result = buildHtml():
    tdiv(onClick=remove):
      text text

proc buildDom(): VNode =
  result = buildHtml():
    tdiv:
      for index, text in elements.pairs:
        renderElement(text, index)

setRenderer buildDom

The initial render shows elements A B C D E. Clicking on C correctly says Deleting element from index: 2 and the sequence becomes A B D E. From the log output I can see that the VNodes + closures should have been updated correctly. However, clicking e.g. again on the item on index 2 (now the D) produces the log message Deleting element from index: 3, resulting in the wrong element to be deleted. Since the index 3 is D's old index, it looks like the event handler hasn't been updated.

Intervening elements are re-rendered if an element is added both before and after them

This example might explain better

include karax / prelude
import future
var page = 1

proc createDom(): VNode = buildHtml(tdiv):
  h1:
    text "Testing"
  if page == 3 or page == 4:
    tdiv: text "Some text"
  input(`type`="text")
  button(onclick=() => (page = 1)): text "1"
  button(onclick=() => (page = 2)): text "2"
  button(onclick=() => (page = 3)): text "3"
  button(onclick=() => (page = 4)): text "4"
  if page == 2 or page == 4:
    tdiv: text "Some text"
setRenderer createDom

Going from page 1 to page 2 or page 1 to page 3 works as expected. But page 1 to page 4 causes the input and buttons 1, 2 and 3 to be re-rendered.

Documentation or Tutorials?

This really is a neat little project. After looking at some of the examples, the TODO one seems to be the best one, but I'm having a little difficulty of trying to make sense of how this framework... well... works. I understand that this is still in early development and in an experimental phase, but something would be nice.

buildHtml to take multiple elements

Not sure if this is by design, but having multiple top level elements in buildHtml does not seem to work:

proc createDom(): VNode =
  result = buildHtml():
    tdiv():
      text "test"
    tdiv():
      text "test"

Leads to: Error: expression 'tmp94011' is of type 'VNode' and has to be discarded

Wrapping them into one top level element solve the problem.

Add support for "refs"

I'm considering to add a "refs" system to Karax at some point, similar to solutions in React/Vue. It's probably best to write down the idea for now, to see if this fits the general design.

The problem arises when an event handler needs to access arbitrary DOM elements, e.g., you click a button and the onclick needs to fetch the position of a bunch of divs. Currently I see two ways to implement this:

  1. Use DOM id tags in combination with getElementById(id) to fetch the elements. Here is an example of this in my transition groups demo. This has a few drawbacks:

    • Unnecessary pollution of DOM id namespace.
    • For a huge DOM, calling getElementById repeatedly is significantly slower than just storing a reference once.
    • List-like elements need to encode their index in the id.
  2. I have been experimenting with adding a "refs" system on user side:

    var vnodeMap = newJDict[cstring, (VNode, VNode)]()
    
    proc registerAs(n: VNode, name: cstring): VNode =
      if name in vnodeMap:
        # store new candidate node
        vnodeMap[name] = (vnodeMap[name][0], n)
      else:
        vnodeMap[name] = (n, n)
      result = n
    
    # Usage in the `buildHtml` vdom generation:
    buildsImportantDiv().registerAs("importantDiv")

    Storing a tuple of VNode is necessary, because not every generated VNode ends up being used. Thus, the tuple stores (old-vnode, candidate-vnode) and on retrieval there is additional logic to move the candidate into the old-vnode position if it has ended up with a non-nil node.dom. Overall, solving the refs problem on client side is a bit complicated and it would be nice to have some solution in Karax.


I haven't really thought it through yet how to implement it in Karax. Some ideas:

A syntactically naive approach would be:

var
  refDivA: Ref = nil # maybe just use VNode?
  refDivB: Ref = nil

proc view(): VNode =
  result = buildHtml():
    tdiv(ref=refDivA): text "A"
    tdiv(ref=refDivB): text "B"

Karax would have update the refs on rendering accordingly. But on first glance this approach becomes weird when dealing with a list of elements/references: The view would have to adjust the length of a seq[Ref] so that for i in 0..<N: tdiv(ref=refs[i]) would not lead to out-of-bound errors when Karax wants to update a reference. So probably that's too error prone and not the kind of responsibility for a view.

An alternative would be to use a callback for ref with signature Ref -> void (or VNode -> void if there is no need for an explicit Ref type). This would be similar to how it is solved in React. The usage with lists could look like:

var
  refs: newJDict[int, Ref]()

proc view(): VNode =
  result = buildHtml():
    tdiv:
      for i in 0..<N:
        tdiv(ref = (r: Ref) => refs[i] = r)

This on the other hand raises the question if the callback should rather have both a register and an unregister call, either by having a second argument in the callback or by having two optional callbacks refAdd and refRemove. This would enable other things that would fall into the "component lifecycle" of React/Vue, but I'm not sure if Karax needs them.

What do you guys think is best?

No support for Shadow DOMs

This would be a useful feature for my current project.

Seems like a lot of changes would be required to support it though? For a start setRenderer would need to take an element rather than a string for the root dom and the KaraxInstance would also need to contain the rootElement.

I'm not sure what other changes would also need to be made but hopefully it can be done without breaking backwards compatibility.

Add docs and tutorials

Karax looks like a cool project and I would like to use it, but there are barely any docs and no getting started guide.

Issue with case statement

There is a small issue with case statements in karaxdsl. For example

include karaxprelude

type
  TestEnum {.pure.} = enum
    ValA, ValB

var e = TestEnum.ValA

proc renderDom(): VNode =
  result = buildHtml():
    tdiv():
      case e
      of TestEnum.ValA:
        tdiv: text "A"
      of TestEnum.ValB:
        tdiv: text "B"

does not compile, because the macro generates an add call for the case identifier:

let tmp123076 = tree(VNodeKind.tdiv)
case add(tmp123076, e)
of TestEnum.ValA:
  ...

Will try to push a fix in a moment...

Uncaught TypeError: Converting circular structure to JSON while using toJson

It happens when you are using a copy of an object with a recursive type in the deep even if the real value is nil.

For example:

import karax, kajax, vdom, components, karaxdsl

type
  Column = object
    t: ref Column

  Test = object
    columns: seq[Column]

var test1 = Test(columns: @[Column(t: nil), Column(t: nil)])
let test2 = test1

kout toJSON(test1)
kout toJSON(test2)
{"columns":[{"t":null},{"t":null}]}
test.js:402 Uncaught TypeError: Converting circular structure to JSON
    at JSON.stringify (<anonymous>)
    at test.js:402

Of course, it will not work anyway in a case t will have real value. But user might have an ability to prepare data before marshaling e.g. set all known refs to nil.

One possible solution is to delete m_type field in toJson e.g.:

proc toJson*[T](data: T): cstring
  {.importc: """
    (function some(obj) {
      function clear(obj) {
          delete obj["m_type"];
          for (var i in obj) {
              if (obj[i] !== null && typeof(obj[i])=="object") {
                  clear(obj[i]);
              }
          }
      }
      clear(obj);
      return JSON.stringify(obj);
    })
  """.}

Empty page rendered if the VNode argument in render proc is not called `n`

Consider the following code:

include karax/prelude


const maxMessageLength = 10
var buttonEnabled = true


proc renderTweetBox*(): VNode =
  buildHtml(tdiv):
    textarea():
      proc onkeyup(event: Event, n: VNode) =
        buttonEnabled =
          if n.value.len < maxMessageLength: true
          else: false

    if buttonEnabled:
      button():
        text "Tweet"
    else:
      button(disabled = "disabled"):
        text "Tweet"


setRenderer renderTweetBox

It works fine (although nimsuggest raises a warning).

If I rename n to this, it stops working.

Compilation should not depend on variable names.

Unicode enum string representation issue

There seems to be an issue with using the $ macro on enum strings containing unicode values in karax.
You can use karun --run to run the following code, and there should be a javascript error thrown, which can be seen in the browser developer tools.

include karax / prelude

type 
    NavType = enum 
        Home = "Home", 
        Review = "[ๆผขๅญ— Review]"
         
var 
    currentNav = Review

doAssert(currentNav == Review, "Check 1 failed") # OK
doAssert($Review == "[ๆผขๅญ— Review]", "Check 2 failed") # OK
doAssert($currentNav == $Review, "Check 3 failed")  # Fails here

proc createDom(): VNode =
    result = buildHtml(tdiv()):
        text "Current page: " & $currentNav
                    
setRenderer createDom

The assertion failure message is the following, which indicates the unicode characters in the string are in their escaped form, which is why the final assertion fails.

Uncaught Error: Error: unhandled exception: $currentNav == "[\xE6\xBC\xA2\xE5\xAD\x97 Review]" Check 3 failed [AssertionError]

This same test (without the karax content) works fine as a nim c -r test.

kdom enhancements + refactoring

I would like to propose some enhancements/refactoring to the kdom (mostly event-centric). Ideally these should also be considered for addition to the Nim JS dom as well (in fact, some of these changes are already in the Nim JS dom). The reason I am suggesting considering these enhancements for karax is because karax provides a convenient framework for testing and proving these changes, but I am fine with moving this over to Nim instead if requested.

The need for these changes initially arose from the lack of drag event functionality in the kdom, and noticing the divergence from the standard JavaScript event model, though I understand this was a deliberate design decision and can respect the desire to keep it that way.

NOTE: Some of these changes are breaking changes, in particular removing certain fields from Event that belong to child events (x, y, keyCode, etc.). As far as karax is concerned, this would require one small change: the enterWrapper() proc in karax.nim will need to cast the Event to KeyboardEvent in order to retrieve the keyCode property.

The changes I am proposing are as follows:

  • Refactor Event model to include more specific events in addition to the base Event:
    • UIEvent, which derives from Event
    • KeyboardEvent, which derives from UIEvent
    • MouseEvent, which derives from UIEvent
    • DragEvent, which derives from MouseEvent
    • Modify existing TouchEvent to derive from UIEvent
  • Add EventPhase enum to assist with determining phase of Event.
  • Expose a DomEvent enum to assist with addEventListener calls.
  • Add KeyboardEventKey enum to assist with getModifierState() calls.
  • Add MouseButtons enum to assist with mouse events.
  • Add helper enums to assist with drag events:
    • DataTransferItemKind
    • DataTransferDropEffect
    • DataTransferEffectAllowed
    • DragEventTypes
  • Add overload to addEventListener that accepts AddEventListenerOptions parameter, as well as AddEventListenerOptions type.
  • Add JFile object to assist with retrieving properties of files dropped onto the browser window.
  • Add Element.clientWidth, Element.clientHeight properties.
  • Add Window.screen property.
  • Add Window.getComputedStyle().
  • Change clearTimeout to accept a ref TimeOut instead of TimeOut.
  • Change setTimeout to return ref Timeout instead of Timeout.
  • Add Element.focus().
  • Add Document.querySelector(), Document.querySelectorAll().
  • Add Event.stopImmediatePropagation().
  • Add KeyboardEvent.getModifierState(), MouseEvent.getModifierState().
  • Add DataTransfer methods:
    • clearData()
    • getData()
    • setData()
    • setDragImage()
  • Add DataTransferItem method:
    • getAsFile()

is there a way to pass argument to dom event callback?

hello

i want to pass fixed arguments to dom event callback.

i hope it would like this

for option in self.product.options:

button(onclick=onDelClick("A3333", ev, node)):

"A3333" is fixed.

do you have any suggestion?

JS Error when ascii escaped characters are used with text macro

Hi:

import karax / [vdom, karax, karaxdsl, jstrutils, compact, localstorage]

type
  Filter = enum
    all, active, completed

var
  selectedEntry = -1
  filter: Filter
  entriesLen: int

const
  contentSuffix = cstring"content"
  completedSuffix = cstring"completed"
  lenSuffix = cstring"entriesLen"

proc getEntryContent(pos: int): cstring =
  result = getItem(&pos & contentSuffix)
  if result == cstring"null":
    result = nil

proc isCompleted(pos: int): bool =
  var value = getItem(&pos & completedSuffix)
  result = value == cstring"true"

proc setEntryContent(pos: int, content: cstring) =
  setItem(&pos & contentSuffix, content)

proc markAsCompleted(pos: int, completed: bool) =
  setItem(&pos & completedSuffix, &completed)

proc addEntry(content: cstring, completed: bool) =
  setEntryContent(entriesLen, content)
  markAsCompleted(entriesLen, completed)
  inc entriesLen
  setItem(lenSuffix, &entriesLen)

proc updateEntry(pos: int, content: cstring, completed: bool) =
  setEntryContent(pos, content)
  markAsCompleted(pos, completed)

proc onTodoEnter(ev: Event; n: VNode) =
  addEntry(n.value, false)
  n.value = ""

proc removeHandler(ev: Event; n: VNode) =
  updateEntry(n.index, cstring(nil), false)

proc editHandler(ev: Event; n: VNode) =
  selectedEntry = n.index

proc focusLost(ev: Event; n: VNode) = selectedEntry = -1

proc editEntry(ev: Event; n: VNode) =
  setEntryContent(n.index, n.value)
  selectedEntry = -1

proc toggleEntry(ev: Event; n: VNode) =
  let id = n.index
  markAsCompleted(id, not isCompleted(id))

proc onAllDone(ev: Event; n: VNode) =
  clear()
  selectedEntry = -1

proc clearCompleted(ev: Event, n: VNode) =
  for i in 0..<entriesLen:
    if isCompleted(i): setEntryContent(i, nil)

proc toClass(completed: bool): cstring =
  (if completed: cstring"completed" else: cstring(nil))

proc selected(v: Filter): cstring =
  (if filter == v: cstring"selected" else: cstring(nil))

proc createEntry(id: int; d: cstring; completed, selected: bool): VNode {.compact.} =
  result = buildHtml(tr):
    li(class=toClass(completed)):
      if not selected:
        tdiv(class = "view"):
          input(class = "toggle", `type` = "checkbox", checked = toChecked(completed),
                onclick=toggleEntry, index=id)
          label(onDblClick=editHandler, index=id):
            text d
          button(class = "destroy", index=id, onclick=removeHandler)
      else:
        input(class = "edit", name = "title", index=id,
          onblur = focusLost,
          onkeyupenter = editEntry, value = d, setFocus=true)

proc makeFooter(entriesCount, completedCount: int): VNode {.compact.} =
  result = buildHtml(footer(class = "footer")):
    span(class = "todo-count"):
      strong:
        text(&entriesCount)
      text cstring" item" & &(if entriesCount != 1: "s left" else: " left")
    ul(class = "filters"):
      li:
        a(class = selected(all), href = "#/"):
          text "All"
      li:
        a(class = selected(active), href = "#/active"):
          text "Active"
      li:
        a(class = selected(completed), href = "#/completed"):
          text "Completed"
    button(class = "clear-completed", onclick = clearCompleted):
      text "Clear completed (" & &completedCount & ")"

proc makeHeader(): VNode {.compact.} =
  result = buildHtml(header(class = "header")):
    h1:
      text "todos"
    input(class = "new-todo", placeholder="What needs to be done?", name = "newTodo",
          onkeyupenter = onTodoEnter, setFocus)

proc createDom(): VNode =
  result = buildHtml(tdiv(class="todomvc-wrapper")):
    section(class = "todoapp"):
      makeHeader()
      section(class = "main"):
        input(class = "toggle-all", `type` = "checkbox", name = "toggle")
        label(`for` = "toggle-all", onclick = onAllDone):
          text "Mark all as complete"
          text "Testing latin character \243"
        var entriesCount = 0
        var completedCount = 0
        ul(class = "todo-list"):
          #for i, d in pairs(entries):
          for i in 0..entriesLen-1:
            var d0 = getEntryContent(i)
            var d1 = isCompleted(i)
            if d0 != nil:
              let b = case filter
                      of all: true
                      of active: not d1
                      of completed: d1
              if b:
                createEntry(i, d0, d1, i == selectedEntry)
              inc completedCount, ord(d1)
              inc entriesCount
      makeFooter(entriesCount, completedCount)

setOnHashChange(proc(hash: cstring) =
  if hash == "#/": filter = all
  elif hash == "#/completed": filter = completed
  elif hash == "#/active": filter = active
)

if hasItem(lenSuffix):
  entriesLen = parseInt getItem(lenSuffix)
else:
  entriesLen = 0
setRenderer createDom

This returns an error on this statement:

text "Testing latin character \243"

URIError: malformed URI sequence
toJSStr

SetConstr is not defined when using json

I assume the json mod is nim js friendly.

Reproduce using the following as source after compiling to js in the default html template:

include karaxprelude
import json
var jString = "{}"
var jNode = parseJson(jString)

Diff recursion explosion with nested tags

I have a problem in a larger karax application, which was hard to reproduce, but I think this artificial example should be sufficient:

import vdom, karax, karaxdsl

type
  User = object
    name: cstring

var users = @[@[User(name: cstring"a")], @[User(name: cstring"b")]]

const MAX = 250

for z in 0..<MAX:
  users.add(@[])
  for y in 0..<20:
    users[^1].add(User(name: cstring("c" & $z)))

var specialLine = -1

proc render: VNode =
  result = buildHtml(tdiv):
    for i, line in users:
      var special = ""
      if specialLine == i:
        special = "focus"

      tdiv(class="line " & special):
        for m in line:
          span:
            text(m.name)

proc ok: VNode =
  # render()
  result = buildHtml(tdiv):
    tdiv:
      tdiv:
        render()

proc top: VNode =
  result = buildHtml(tdiv):
    section(id="main"):
      ok()

setRenderer(top)

I start this with karun --run example.nim and stats enabled and the only modifications I have are that instead of if kxi.recursion > 100:stuff I have if kxi.recursion mod 1000 == 0:kout kxi.recursion,to more easily monitor the value itself

When I call redraw, normally it logs ~5000 kxi.recursion which is almost instant.

When I change specialLine in the console, (e.g. to 2), the first redraw after that logs ~1200000 kxi.recursion.

If we test with changing ok: if you just have render() there, you log ~40000 kxi.recursion, but for each nested tdiv in which render VNode goes, it logs 3 * more recursion.

As a whole, even the 5000 value is weird, because there is if kxi.recursion > 100: debug log, so I assume that may be karax is not supposed to go even to that number: I guess it has also something to do with the huge number of tags (but 250 * ~20 shoudn't be that much)

echartstest.nim doesn't work

/cc @Araq as reported on gitter:
nim js echartstest.nim
open echartstest.html

shows

Customers
Products

hyperlinks but nothing happens when I click them (and no errors are shown).

If I edit echartstest.nim with
currentView = Customers
=>
currentView = Products

then a chart is shown (but clicking on the links doesn't work either)

note: there's also another warning, but not error:

There is a chart instance already initialized on the dom
see https://translate.google.fr/translate?hl=en&sl=zh-CN&u=https://zhidao.baidu.com/question/813945019601723452.html&prev=search for details on that

EOL (\n) not being passed to textarea

The textarea character "\n" is being ignored when passed through the text() procedure.
It can be inserted by another procedure by using javascript calls but not by setting the variable and having it loaded during the renderDom process.
In short, everything comes out as a single continuous line.

This is very easily tested, but I can generate examples if needed.

Event handler with arguments raises error

This code works:

include karax/prelude


const maxMessageLength = 10
var buttonEnabled = true


proc renderTweetBox*(): VNode =
  buildHtml(tdiv):
    textarea():
      proc onkeyup(event: Event, n: VNode) =
        buttonEnabled =
          if n.value.len < maxMessageLength: true
          else: false

    if buttonEnabled:
      button():
        text "Tweet"
    else:
      button(disabled = "disabled"):
        text "Tweet"


setRenderer renderTweetBox

but line 11 gets underlined by nimsuggest in VSCode with the following error:

type mismatch: got <VNode, EventKind, proc (event: Event, n: VNode){.gcsafe, locks: 0.}, KaraxInstance>

but expected one of:
proc addEventHandler(n: VNode; k: EventKind; action: EventHandler;
kxi: KaraxInstance = kxi)
first type mismatch at position: 3
required type: EventHandler
but expression 'proc (event: Event; n: VNode) = buttonEnabled = if len(value(n)) < 10: true else: false' is of type: proc (event: Event, n: VNode){.gcsafe, locks: 0.}
proc addEventHandler(n: VNode; k: EventKind; action: proc (); kxi: KaraxInstance = kxi)
first type mismatch at position: 3
required type: proc (){.closure.}
but expression 'proc (event: Event; n: VNode) = buttonEnabled = if len(value(n)) < 10: true else: false' is of type: proc (event: Event, n: VNode){.gcsafe, locks: 0.}

expression: addEventHandler(tmp179021, onkeyup, proc (event: Event; n: VNode) = buttonEnabled = if len(
value(n)) < 10: true else: false, kxi)

There is no such problem with handlers without arguments.

No easy way to specify attributes without repetition

    # TODO: Surely there is a better way to handle this.
    let rankSelect = buildHtml():
      if isAdmin:
        select(class="form-select", value = $profile.user.rank):
          for r in Rank:
            option(text $r)
      else:
        select(class="form-select", value = $profile.user.rank, disabled=""):
          for r in Rank:
            option(text $r)

There should be an easy way to avoid this repetition.

regression with lastest Nim

Uncaught TypeError: Cannot set property 'renderId' of null
at init_215046
Uncaught TypeError: Cannot read property 'renderer' of null
at dodraw_213631 (todoapp.js:2780)
at HEX3Aanonymous_215049 (todoapp.js:3252)

Support for elements not managed by Karax, within Karax app

I'm running into some issues using Karax in a project. I have embedded the Monaco editor within my Karax app, and redraw operations fail. The same proc returns false when it detects the outside changes in the DOM. Is there an existing workaround to this issue? If not, I'm happy to add the functionality myself and submit a PR. All I need is some guidance on the best way to solve this problem. I can also provide more details if needed!

Nested VComponent Redraw is not working easily

I made a VComponent which has a child VComponent

when child vcomponent calls
markDirty(self)
redraw()

it doesn't redraw the child component.
I can see the change when the parent component gets update.

Thank you.

Component DOM siblings been re-rendered even if only one of them been changed

There is a placeholder which changes each time input gets focus. I expect input to be not re-rendered or even not to lose focus because placeholder and input are siblings but it actually has been re-rendered and lost focus.

import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils

type TextInput* = ref object of VComponent
  value: cstring
  placeholder: cstring

proc render(x: VComponent): VNode =
  let self = TextInput(x)

  let style = style(
    (StyleAttr.position, cstring"relative"),
    (StyleAttr.paddingLeft, cstring"10px"),
    (StyleAttr.paddingRight, cstring"5px"),
    (StyleAttr.height, cstring"30px"),
    (StyleAttr.lineHeight, cstring"30px"),
    (StyleAttr.border, cstring"solid 1px black"),
    (StyleAttr.fontSize, cstring"12px"),
    (StyleAttr.fontWeight, cstring"600")
  ).merge(self.style)

  let inputStyle = style.merge(style(
    (StyleAttr.color, cstring"inherit"),
    (StyleAttr.fontSize, cstring"inherit"),
    (StyleAttr.fontWeight, cstring"inherit"),
    (StyleAttr.fontFamily, cstring"inherit"),
    (StyleAttr.position, cstring"absolute"),
    (StyleAttr.top, cstring"0"),
    (StyleAttr.left, cstring"0"),
    (StyleAttr.height, cstring"100%"),
    (StyleAttr.width, cstring"100%"),
    (StyleAttr.border, cstring"none"),
    (StyleAttr.backgroundColor, cstring"transparent"),
  ))

  let placeholderStyle = style(
    (StyleAttr.opacity, cstring"0.35"),
    (StyleAttr.fontStyle, cstring"italic"),
    (StyleAttr.overflow, cstring"hidden")
  )

  proc onfocus(ev: Event; n: VNode) =
    self.placeholder = self.placeholder & cstring"1"
    kout cstring"focus"
    markDirty(self)

  proc onblur(ev: Event; n: VNode) =
    kout cstring"blur"

  result = buildHtml(tdiv(style=style)):
    if self.value == nil or len(self.value) == 0:
      tdiv(style=placeholderStyle):
        text self.placeholder
    input(style=inputStyle, value=self.value, onblur=onblur, onfocus=onfocus)

proc newTextInput*(style: VStyle = VStyle(); value: cstring = cstring""; placeholder: cstring = cstring""): TextInput =
  result = newComponent(TextInput, render)
  result.style = style
  result.value = value
  result.placeholder = placeholder

proc createDom(): VNode =
  result = buildHtml(tdiv):
    newTextInput(placeholder=cstring"test")

setRenderer createDom

Calling proc with module path raises Error: Expected a node of kind nnkIdent, got nnkDotExpr

Consider the following component code (likebutton.nim):

import future

include karax/prelude


var liked = false


proc render*(): VNode =
  buildHtml:
    if liked:
      text "You liked this"
    else:
      button(onclick = () => (liked = true)):
        text "Like"

and the root component (index.nim):

include karax/prelude

from likebutton import nil


proc render(): VNode =
  buildHtml(tdiv):
    h1:
      text "Hello Karax!"
    likebutton.render()


when isMainModule:
  setRenderer render

karun -r index.nim fails with index.nim(10, 15) Error: Expected a node of kind nnkIdent, got nnkDotExpr

From https://forum.nim-lang.org/t/4038

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.