Arc Forumnew | comments | leaders | submitlogin
Ask: html and javascript in arc
5 points by hasenj 5209 days ago | 5 comments
Complete newbie to lisp/arc here (coming from python).

I'm pretty happy with my python tools, except for html templating.

I'm using Jinja2 for templates, and while its great compared to django's templates, (and most other <% templates %>), one thing about it does annoy me.

If I want to create a macro, I'd have to type:

{% macro name(....) %} ... html stuff .... {% endmacro %}

This is too much boiler plate for me. Why can't the `endmacro` just be a closing brace/parenthesis?

That's where I started thinking that something like arc could be a natural fit with what I want to do with my day-to-day html templating.

Then I thought, well if I'm to do html in arc, why not do javascript in arc as well? (note: I'm not doing html in arc yet, I'm just thinking about it)

I saw the arc->javascript experiment, and the idea seems rather nice. I don't quite like the javascript syntax.

But in javascript, I need to do things like:

    jQuery("#content").hide();
How would something like that look like in arc?

I can try:

    (jQuery "#content")
and that would correctly produce `jQuery("#content")`, but how do I get at the `obj.method` syntax?

That's not really my question though, I mean I'm clearly thinking in javascript here, not in lisp.

In your experience, is writing javascript as arc a feasible idea? or even a good one?

If you were to do it, would you try to write javascript in arc syntax, or would you entirely ditch all the available javascript libraries (such as jQuery) and write pure arc that "compiles" to javascript as if javascript was some sort of an assembly language and you rarely (if ever) would look at its output?

I mean, clearly Javascript enteracts with the dom and the browser a lot, so you can't ignore everything.

So .. How would you do it? How would you approach it?

Right now, I'm trying to write a simple app in python using jinja2 templates, then I'll have a look at the template and try to come up with a more pleasant way to write it (with arc's syntax in mind). Addmittedly, that'd be like thinking in html/jinja/python while using, and in my experience, it's not a very good idea to approach a language and start working on it as if it was X, your X is your other favotire language that you're most familiar/comfortable with.



2 points by evanrmurphy 5209 days ago | link

but how do I get at the `obj.method` syntax?

  ((jQuery "#content") `hide)
Note that the compiler uses JavaScript bracket syntax rather than dot syntax, so your output will really be:

  jQuery('#content')['hide'];     // same as jQuery('#content').hide();
I walk through "hello world" in jQuery using Arc->JavaScript at http://arclanguage.org/item?id=12141 if you're interested.

In your experience, is writing javascript as arc a feasible idea? or even a good one?

It is feasible, but there are tradeoffs. If you generate your scripts and markup in Arc, you get the benefit of having more of your code in uniform syntax, and it's often more concise. However, your system will be more dependent on Arc, such that if you decide to port to another language, it will be more work. You also give up a lot of the handy editor tools designed to help you write JavaScript/HTML/CSS by hand.

jQuery already takes so much of the verbosity out of writing JavaScript that it can feel a bit excessive to try and metaprogram it away any further.

Recently I've been experimenting with a lot of different server-side languages and frameworks, so I have incentive to keep my scripts in their rawer but more portable form.

-----

2 points by hasenj 5207 days ago | link

> jQuery('#content')['hide']; // same as jQuery('#content').hide();

Not really the same, it's missing the () from the end to call the function.

Adding another level of parens seems to do the trick:

    (((jQuery "#content") `hide))
results in:

    jQuery('#content')['hide']();
But that style doesn't seem like something I'd want to do.

> jQuery already takes so much of the verbosity out of writing JavaScript that it can feel a bit excessive to try and metaprogram it away any further.

Agreed.

There are still annoyances though, like the anonymous function syntax when combined with function calls and json literals:

    jQuery(".item").each(function(){
              fn_that_takes_json({ id: $(this).id, obj: $(this) }); });
Too messy, and too many places to go wrong: brace in the wrong place, missing comma, missing semi colon, etc.

-----

1 point by evanrmurphy 5207 days ago | link

it's missing the () from the end to call the function.

You're right, thanks for catching that.

The `#` in that jQuery selector condemns the Arc version to look worse than the code it's attempting to repair. If the selector didn't have special characters, as in

  jQuery('content').hide();
then you could exploit Arc ssyntax and do

  (jQuery!content.`hide)
but I won't pretend like that's a great improvement either.

-----

1 point by hasenj 5206 days ago | link

perhaps a js macro could allow a better syntax?

I don't know if macros can do this,

    (js
        (jQuery "#content"
            (toggle))        ;; translates to: jQuery("#content").toggle();
        (jQuery ".cue"
            (html "done")
            (addClass "t"))) ;; translates to: jQuery(".cue").html("done").addClass("t");

-----

2 points by rocketnia 5206 days ago | link

Here's the start of an approach loosely influenced by that:

  (def jquery-dedot (symbol)
    (when (isa symbol 'sym)
      (zap string symbol)
      (when (begins symbol ".")
        (cut symbol 1))))
  
  (def parse-chain (chain)
    (whenlet (first-method-sym . rest) chain
      (iflet first-method jquery-dedot.first-method-sym
        (let numargs (or (pos jquery-dedot rest) len.rest)
          (let (first-args rest) (split rest numargs)
            (cons (cons first-method first-args) parse-chain.rest)))
        (err:+ "A chain given to 'parse-chain didn't start with a "
               "dot-prefixed method name."))))
  
  (mac jquery (selector . chain)
    (let result `(js-call js-var!jQuery ,selector)
      (each message parse-chain.chain
        (zap [do `(js-send ,_ ,@message)] result))
      result))
  
  (def js-var (name)
    (annotate 'rendered-js
      string.name))
  
  (def js-call (callee . args)
    (annotate 'rendered-js
      (+ "(" tojs.callee "(" (intersperse "," (map tojs args)) "))")))
  
  (def js-send (object method . args)
    (annotate 'rendered-js
      (+ "(" tojs.object "[" tojs.method "]("
         (intersperse "," (map tojs args)) "))")))
  
  (def tojs (value)
    (caselet type-value type.value
      rendered-js  rep.value
      int          string.value
      string       (do (zap [subst "\\\\" "\\" _] value)
                       (zap [subst "\\'" "'" _] value)
                       (zap [subst "<'+'/" "</" _] value)
                       (zap [subst "]]'+'>" "]]>" _] value)
                       (zap [+ "('" _ "')"] value)
                       (tostring:w/instring stream value
                         (whilet char readc.stream
                           (let code int.char
                             (if (<= 32 code 127)
                               pr.char
                               (let hex (coerce code 'string 16)
                                 (pr "\\u")
                                 (repeat (- 4 len.hex) (pr #\0))
                                 pr.hex))))))
        (err:+ "A value of type \"" type-value "\" can't be "
               "translated by 'tojs (yet).")))
In action:

  arc> (jquery "#content" .toggle)
  #(tagged rendered-js "((jQuery(('#content')))[('toggle')]())")
  arc> (jquery ".cue" .html "done" .addClass "t")
  #(tagged rendered-js "(((jQuery(('.cue')))[('html')](('done')))[('addClass')](('t')))")
This code isn't necessarily shorter than the JavaScript code, but it's set up so that you can conveniently compute parameters from the Arc side via things like (jquery "#blah" .height (- full-height top-height)) while also being able to compute them naturally from the JavaScript side via things like (jquery "#nav" .height (jquery "#content" .height)).

One downside is all the parentheses this leaves. But if that turns out to be a problem, the thing I'd do is to rewrite the JavaScript-generating utilities to return meaningful AST objects rather than just tagged strings. That way, the AST can be pretty-printed in a separate step, once the context of each and every JavaScript expression is known.

An AST could also be good for displaying as debug output, minifying, verifying, compiling, translating to multiple programming languages, and doing any other kind of inspection. The format of the AST could also be set up to make it easier for different generated parts to avoid tripping over each other; for instance, there could be scopes for element IDs or a library dependency resolver.

I just finished converting my website's static site generator to Arc and Penknife (a language I've been making), and I'm using this kind of AST approach, if only 'cause HTML ASTs are so simple. :-p I haven't had the need to generate JavaScript or PHP yet--and I don't even use any PHP right now, lol--but I expect to get around to that someday, and at that point things'll get kinda interesting.

-----