Pliant talk forum

Pliant talk forum

Feature request: Separating design and logic in UI code

I'd like to move to the new UI framework,
but before that template mechanism must be added which
supports both html and new ui instructions.
Message posted by maybe Boris Reitman on 2007/09/29 23:03:25
A template mechanism needs to be added to ui to separate design from logic.
For example, "para" and "eol" shouldn't be used in the logic part of the code.

As well, I would like to use HTML templates for users accessing the site 
through http_proxy.

I like the idea of tag_prototype and tag in the old page design
because it names arguments, and allows them to be of any type.
The modification that is needed is to pass content generating
functions as the values for the attributes.

Consider this user code:

--8<-------------------------------------

ui_design_prototype design1 
  content_attr search_box     # delayed_action that generates content
  content_attr item itemcode  # delayed_action with one argument
    arg Str itemcode
  content_attr search_button
  content_attr search_results
  attr first_name Str <- ""   # old style plain attributes
  attr last_name Str 

ui_function show_homepage
  ...
  ovar Str search departments
  ui_design "design1"
    define first_name person:first_name  
    define last_name person:last_name 

    define search_box
      input "search: " search          # will become delayed action
    define search_button
      button "go"
        section_replay "search_results"  
    define search_results
      section "search_results"
        generate_search_results search

    define item                        
      ui_design "item_design_red"    # nested design invocation
        define price
          text get_price:itemcode    # itemcode is an argument from design1
        define thumbnail
          image get_img:itemcode

  ui_design_function design1
    if client_software="http_proxy"

      # here goes old template code that I use with .page
      template slurp_text_file:"/my/templates/design1.html"
        template_section "last_name"
          text last_name
        template_section "search_results"
          search_results                 # executes delayed action
        template_section "item"
          item template_value            # executes delayed action with argument
        template_default
          ui_design_render template_keyword template_value # auto-mode
    else
      search_box
      style use "highlight"
        para
          text first_name+" "+last_name
      search_results               
      eol
      text "now on sale: "
      item:"ABC123"
----8<--------------------

Contents of design1.html are:
---8<-----
...
User {{first_name}} {{last_name}}
Buy this item: {{item:ABC123}} 
<div id=departments>
{{departments}}
</div>
...
--->8-----

It should be possible to recompile ui_design_function without restarting the
server.
Message posted by maybe Hubert Tonneau on 2007/10/01 20:55:49
The server side API of the UI is currently very simple and straightforward:
it's just a thin layer.
It's implemented in /pliant/graphic/ui/server/api.pli

Nothing prevents you to build a more sophisticated layer.
What is fixed in the Pliant UI is the set of instructions that the client
understands and the encoding (PML), not the server side API.

I very well understand your wish to split the content and the formating
specifications.
So a perfect design would be:
. the application server outputs datas with semantic tags only
. the application server exposes (publishes) the set of rules that
  enable to turn the semantic level output to a set of positioning+drawing
  instructions
. the client applies the rules and does the rendering

Now what I don't want is the application server to be responsible for
translating from semantic to positioning+drawing through applying the rules
on server side.

The the planned scenario that will be plugged in the UI at some point (it's
not implemente yet because there are a lot of hard details to deal with
before it can truely work) is:
. the application server will be allowed to declare new tags. A new tag
  defined through an ID, plus a world wide URL, plus the ID of a server that
  understands it.
. now, when a client receives such a tag, it just forwards the content to
  the specified server, or a closer one if available, and get's back the
  positioning+drawing translation.
Message posted by maybe Boris Reitman on 2007/10/04 15:18:11
So can your tags take multiple pieces of code as arguments ? I think having even as 
highlevel tags such as "box" in the middle of the code is not good. For this 
reason I do not like the .page tags system.  

The use of templates as I proposed are more general than the "box" tag,
because unlike the box tag which defines header code and footer code to wrap one 
argument, a template allows several placeholders. The box tag essentially takes
a block of code as its argument.  A template needs to take several blocks of code, 
one for each of its placeholders.

Example: I want to hide the layout for this code.

table columns 2
  cell
    box
      generate_contents_A     
  cell
    box
      generate_contents_B

How can I create a tag to do this ? If use my template proposal, I'll do:

---8<----------------------------
ui_design_prototype double_panel_1
  content_attr block_A block_B

ui_design_function double_panel_1
  table columns 2
    cell
      box
        block_A
    cell
      box
        block_B
----------->8--------------------

in the code:
  
---8<----------------------------
ui_design "double_panel_1"
  define block_A
    generate_contents_A
  define block_B
    generate_contents_B
----------->8--------------------
 
We can call double_panel_1 a tag, to give it usage syntax like this:
---8<----------------------------
double_panel_1
  define block_A
    generate_contents_A
  define block_B
    generate_contents_B
----------->8--------------------

This looks good, but using it as a tag, makes it difficult to change style at runtime. 
Suppose I have two looks for double_panel_1_general and double_panel_1_specific.  

By using "ui_design" version, I could do this

---8<----------------------------
ui_design "double_panel_1"+(shunt is_logged_in "general" "specific")
  define block_A
    generate_contents_A
  define block_B
    generate_contents_B
----------->8--------------------

So maybe, both of the methods should be available to select a template/tag.

So, to summarize this post, the new addition that is needed to tags, is ability to wrap
multiplie pieces of code, instead of one which is now possible with (tag_x_before/tag_x_after) 
abstraction.

A side note:
A related cool new feature is to allow regular functions
to be called by giving arguments by the name, rather than listing them in their
order in the signature. The compiler will reorder them. For example,

function standard_name  first last -> full
  arg Str first last
  full := last+", "+first

We can let it be called like this:

result := standard_name last "John" first "Galt"

Message posted by maybe Hubert Tonneau on 2007/10/04 21:48:51
It seems to me that you are mixing very different subjects here.

About your template idea.
The key issue is that the UI protocol let's you very easily send the content to
be displayed in the wrong order (thanks to the 'section' notion) whereas HTTP
requires is to be properly ordered on server side.

So, with the UI procotol, you can send the template, with an empty section
for each part that will be the result of a sub expression, then at any later
time provide the content of these.

Now, the API you choose on server side is a very different issue.
My previous sentence means that with the UI protocol you have no contrain on
the server side, as opposed to HTTP where you have to either store all the
content of the page on server side before sending (see early Pliant HTTP
server) so that you get scalability issues or have to use dirty javascript
based tricks.
But too much freedom can be hard to cope with because various people will
want to use it differently.

What I prepose you is to considere the existing server side API as the low
end one (thin layer on top of the UI protocol PML encoded stream) on top
of which several higher level API can take place thanks to Pliant meta
programming capabilities.

> A related cool new feature is to allow regular functions
> to be called by giving arguments by the name, rather than listing them in their
> order in the signature.

This is an old thing that I thought about in the Pliant early days.
Basically, we have the 'function' which is expecting a fixed number of arguments
on one side so is not flexible at all (see Win32 API where you often have to
provide tons of arguments with values that will just be ignored), and the
'meta' on the other side that is absolutely flexible because your code will do
the parser but is hard to write.
So, there is room for a middle one that would provide 80% of the meta
flexibility, with only 20% of it's difficulty to write.
Now, the problem is that I'm not sure that there is a sigle middle man.

So, a more flexible 'function' that would enable to have keywords followed
by sets of optional arguments is an interesting candidate...
... but a more macro like mechanism (a bit like Pliant 'compile_as' but easier
to use) would also be interesting, etc.
w
Message posted by maybe Boris Reitman on 2007/10/05 05:40:33
> The key issue is that the UI protocol let's you very easily send the content to
> be displayed in the wrong order (thanks to the 'section' notion) whereas HTTP
> requires is to be properly ordered on server side.
> So, with the UI procotol, you can send the template, with an empty section
> for each part that will be the result of a sub expression, then at any later
> time provide the content of these.

This is cool, and I haven't thought about. First thing that concerns me though,
is that sections are global, so I can't nest a template inside a template,
because that would create duplicate section names.  So, if I have
the construct "double_panel" I can't create an inner double panel inside 
a double panel, or even, simply, two double panels in a row.
As a workeraround, a suffix generated by compiler needs to be appended to the 
section name, to make the names unique.  But then, I can't easilly target the sections
from remote code point, because a compiler wouldn't know how to fill in the name.
So, even if I use sections which can be in theory populated much later
in the code, I still need to supply the code in one spot 
(like the "define" lines in my example).

> So, there is room for a middle one that would provide 80% of the meta
> flexibility, with only 20% of it's difficulty to write.
> Now, the problem is that I'm not sure that there is a sigle middle man.

This way of calling functions, by naming its arguments is a standard way in 
which many perl functions are implemented (particularly 'new').
Thats where I got the idea.
Message posted by maybe Boris Reitman on 2007/11/02 14:58:04
progress report: I was able to modify the template to support running inside .ui
The modified version is here: http://archimedes.hypervolume.com/~boris/template.pli

Here's an example,
-----8<---------
module "/boris.reitman/template/template.pli"

ui_path "/boris.reitman/template/t/test"
  window left
    void
  window bottom
    void
  window main
    template "<b>Hello {{a}}!</b>"
      template_section "a"
        text "World"
----->8---------

Displays "Hello World!".  This works only for http_proxy (html), not ui client,
and is a first step for a template driven website. 
In ui client this executes the template_section
blocks in the order they appear in the template.
Message posted by maybe Hubert Tonneau on 2007/11/04 10:10:25
The global answer at:
http://old.fullpliant.org/pliant/browse/forum/pliant.question/07FIKAE11/
also applies to this thread.
Message posted by maybe Boris Reitman on 2007/11/04 14:19:36
How can I arrange to send custom html header (<head></head> meta tags)
to http proxy ?

Also, can you please fix no frames mode in http proxy ? Its doesn't work
(even if i do little fixup to get it to compile, the buttons don't work).\

Thanks
Message posted by maybe Hubert Tonneau on 2007/11/04 22:11:04
I've fixed the use_frame=false case in http proxy code.
It's available in the just posted release 99.

For sending custom <html> I've just inserted two or three 'plugin' that will
enable you to change these parts.
One possibility for you would be to change both the 'html' plugin and the
'head' plugin so that html tag would be ignored when it's content beginning
is <head> and your custom head code would scan all the tree to seach for a
html node with a content starting by <head>
Message posted by maybe Boris Reitman on 2007/11/05 01:39:34
Thank you, its working. 

I don't quite understand what you said with using both "html" and "head" plugins.
My goal is to have different ui_paths generate different <title> and headers.

so: 
  ui_path "/x/y" 

will generate different title, and meta headers when requested 
as 
  "http://.../x/y", or 

  "http://.../x/y/z", or 
  "http://.../x/y/w".  

(the "z" and "w" will arrive as a subpath).

Can you suggest how can I organize my code to pass this kind of info 
to my plugin custom code, from ui_function code ?
Message posted by maybe Hubert Tonneau on 2007/11/05 02:48:40
In your application, when you are using 'html' instruction, the string
is passed unmodified to the HTTP proxy,
so with a lot of over simplification and no test at all, you could have
something like:

in your application

html "<title>foo</title>"

in the HTTP proxy

method l find_tag tag -> value
  oarg LayoutPrototype l ; arg Str tag value
  if (entry_type addressof:l)=LayoutHtml
    var Pointer:LayoutHtml h :> addressof:l map LayoutHtml
    if (h:html 0 tag:len+2)="<"+tag+">"
      value := h html
      return
  var Link:LayoutPrototype p :> l first
  while exists:p
    value := p find_tag tag
    if value:len<>0
      return
    p :> p next
  value := ""

custom head
  ...
  if exists:main and (exists main:root)
    answer writechars (main:root find_tag "title")
  ...

custom html
  if (h:html 0 6)<>"<title>" and ...
    http writechars h:html
Message posted by maybe Boris Reitman on 2007/11/05 03:00:38
use_frames=false continues to be broken, when buttons are pressed in this mode, 
nothing happens.
Message posted by maybe Hubert Tonneau on 2007/11/05 03:16:28
It worked for me.
Did you clear your browser caches ?
If not enough, try with FireFox instead of IE if you where using IE.
In any case, read the javascript console.
Message posted by maybe Boris Reitman on 2007/11/05 11:17:40
Do you have any idea about this problem: 

I am experiencing a problem with accessing /_/alive at the end of page load,
when I am proxying pliant site behind Apache. My setup is like this:

ProxyPass /_/ http://freedom.hypervolume.com:8082/_/

I monitor the access in FireBug, and it reports 404 error on url

http://www.freedom.flowersinvancouver.com/_/alive

However, I am able to access it without any problem, if I try to load this url
directly, without the 404. The 404 only happens when I load the full page.
I am running in non frames mode.

I am at loss why it happens, and my guess is that it has to do with proxy and keep
alive connections.  Perhaps proxy initiates another connection on the /_/alive 
url ?  How can I fix this up ?

Thanks.
Message posted by maybe Hubert Tonneau on 2007/11/05 11:51:26
With HTTP, should an error occure, you get an error code (let's say 404) plus
an error message.
If the error is generated by Pliant HTTP proxy, then searching for 404 in
http_proxy.pli will tel you all the places where I generate such an error,
so changing the associated error message in the Pliant code will enable you
to track down to where Pliant is not happy.
If the error message is not generated by Pliant, then the error is not raised
by Pliant, so you have to dig in the other tools you are using to discover why
they generate it.
Message posted by maybe Boris Reitman on 2007/11/05 17:30:45
I found the problem with "alive", it was because of the cookie handling.

First, I have unset the "domain=" on the cookie issued by http proxy,
because the domain is set automatically on the browser end. Not setting the domain
allows more flexibility to proxy pliant under a different domain name.

As well, more importantly, parsing of cookie value was wrong, because
the cookie header send by the browser to the server has many cookies, 
all on one line, separated by ';'. So non-related cookies will be sent
to pliant server, and pliant must ignore them. It could be possible
to extend the proxy code to rewrite the "path=/" on the cookies header 
send by pliant to the client, but I didn't do that. Instead I did this patch
which works,

http://archimedes.hypervolume.com/~boris/http_proxy.patch
Message posted by maybe Hubert Tonneau on 2007/11/05 18:41:40
Your 'extract_cookie' function looks hugly to me.
Please cut/paste an example of the modified cookie you received so that I
can properly understand what has to be filtered and try to prepose a simpler
version.
Message posted by maybe Boris Reitman on 2007/11/05 22:04:34
This is what my web-browser sends to the pliant server,
because I am using "google-analytics" which tracks user statistis 
(called urchinTracker, google bought them).

Cookie: __utmz=183321329.1193384503.1.1.utmccn=(direct)|utmcsr=(direct)|utmcmd=(none); __utma=183321329.284667872.1193384503.1193874109.1194050992.3; SID=07FVIXK1AuHf0dnljasTaVixqks7hlg

There's no specific ordering of cookies, as they appear on the cookie line.
Message posted by maybe Hubert Tonneau on 2007/11/05 22:13:21
So, from my point of view, you could replace your complex code with just:
-          if (value 0 4)="SID="
-            sid := value 4 value:len
+          value+";" parse any "SID=" any:sid ";" any
Message posted by maybe Boris Reitman on 2007/11/05 22:46:18
merci :)
Message posted by maybe Boris Reitman on 2007/11/05 23:08:58
What about the headers/title tag (different pages give different headers) ? 
Can you please comment on that ?
Message posted by maybe Boris Reitman on 2007/11/05 23:10:25
sorry, I just saw your answer. Thanks!
Message posted by maybe Boris Reitman on 2007/11/06 14:35:38
I am having trouble getting this to compile, because I can't figure
out the types of objects.

"main" in the context of head plugin is already the "root",
and is a Link:LayoutPrototype.  I get error:

Failed to compile main   ('find_tag'  ?  ?)

for the line (main find_tag "title" "")

----8<----------------

method l find_tag tag extra -> value
  oarg_rw LayoutPrototype l ; arg Str tag value extra
  if (entry_type addressof:l)=LayoutHtml
    var Pointer:LayoutHtml h :> addressof:l map LayoutHtml
    var Bool match := false
    if h:html:len > 0
      var Bool match := (h:html 0 tag:len+2)="<"+tag+">"    
      if extra<>""
        match := match and (h:html search extra -1) <> -1
    if match
      value := h html
      return
  var Link:LayoutPrototype tmp :> h
  var Link:LayoutPrototype p :> tmp first
  while exists:p
    value := p find_tag tag extra
    if value:len<>0
      return
    p :> p next
  value := ""

custom head
  answer writeline "<head>"
  answer writeline "<script src=[dq]"+url_header+"/pliant"+runid+".js[dq] language=[dq]JavaScript[dq]></script>"
  answer writeline "<link rel=[dq]stylesheet[dq] type=[dq]text/css[dq] href=[dq]"+url_header+"/pliant"+runid+".css[dq]>"
  if exists:main
    answer writechars (main find_tag "title" "")
    answer writechars (main find_tag "meta" "keywords")
    answer writechars (main find_tag "meta" "description")
  answer writeline "</head>"

custom html
  if h:html:len > 0
    if (h:html 0 6)<>"<title>" and not (h:html 0 5)<>"<meta"
      http writechars h:html

---8<----------------
Message posted by maybe Hubert Tonneau on 2007/11/06 16:11:57
Your 'find_tag' method does not exist (is ignored), because it is outside
the 'custom' bloc.
Try to put it at the head of the 'custom' bloc: I think that Pliant is
basically ok with definining a function in the middle of another one.
Message posted by maybe Boris Reitman on 2007/11/06 20:52:32
Thanks, that did it.
Sometimes, when I click something on the page in the middle of alive request,
i get a popup "Connection is closed".
Message posted by maybe Hubert Tonneau on 2007/11/06 21:24:35
> Sometimes, when I click something on the page in the middle of alive request,
> i get a popup "Connection is closed"

This is the real remaining issue with the HTTP proxy.

My understanding of it is that the AJAX GET request issued in 'alive' Javascript
function of the HTTP proxy is collapsing with the one issued in 'run'

'alive' is responsible to tel the server that the client is still connected, and
to enable a server side decided change of the page content to be forwarded to
the client,

'run' is executed when a button in the page is pressed by the user
Message posted by maybe Boris Reitman on 2007/11/06 22:48:30
I'm gonna keep half of my website working under old server, cause I need to move 
it over incrementally. How can arrange to keep state ? In the old server I used
svar which is a "page" method.  How could I arrange to get a hold of svar variables
in .ui ?
Message posted by maybe Boris Reitman on 2007/11/06 23:10:20
I don't think it has to do with "run" because I have no buttonns on my website,
only links. Just switching from one path to another casuses this (but not all the time),
so I suspect that it happens if I click a link while "alive" request is processing.
Message posted by maybe Hubert Tonneau on 2007/11/06 23:38:28
In the UI, a button and a link are the same thing. Only the styling make them
look different.
So, when you use the HTTP proxy and clic on a link, the 'run' Javascript
method will be called.

About your need for session variables, I have no final answer yet:
you could have all your site work in a single URL.
This is the way the text editor works for example, so that you can have
a data structure with all the state that is kept all along.
Also I'm not convienced it would be a good idea because for a shopping
application having many URLs is usefull.
Basically, what you want in facts is an automatic logging feature: it makes
sence but is not available yet, even if not very difficult to add the UI
client and server.
Message posted by maybe Boris Reitman on 2007/11/06 23:50:26
I generate standard raw html links, on my page. 
They are real links with no javascript actions.
The reason is that I want search engine robots to follow them.
Message posted by maybe Boris Reitman on 2007/11/07 00:12:04
I updated the custom http_proxy.pli code to a working version, at same url:
http://archimedes.hypervolume.com/~boris/http_proxy.pli
Message posted by maybe Boris Reitman on 2008/02/04 00:02:53
styling/layout idea:

Our "for" loops must generate data in xml-like grouping relationships.
The data must be transformed to layout elsewhere. Example:

in application code:
~~~~~~

layout namespace "myapp"

tag "x"
  for i (1..3)
    tag "y"
      for j (1..4)
       tag "z"
         tag "record"
           tag "title" r:title
           tag "description"
             if edit_mode
               tag "input_multiline" r:description
             else
               tag_content r:description
           tag "button"
             recalculate_record r
             section_replay parent_tag:"record"      

layout binding:
~~~~~~~~~~~~~

layout_bind "table" "x" "y" "z"
# so that,
#  - x starts a table
#  - y starts a row
#  - z starts a cell

layout_bind "myapp:record_layout" "record"

layout definitions:
~~~~~~~~~~~~~~~~~~~
layout "table" e
  if browser_client<>"http_proxy" 
    table
      for i (1..e:size)
        start_row
        for q (1..e:i:size)
          start_cell
            e:i:j render
  else
     template "html_table.html"
       template_section "body"
         for i (1..e:size)
           template "html_table_row.html"
             for j (1..e:i:size)
               template "html_table_row_cell.html"
                  template_section "body"
                     e:i:j render

layout "myapp:record_layout" e
  text "title: "+e:"title":content
  text "description: " ; e:"description":render
  e:"button":render
                
The layout declarations must be possible to do in external file which 
is dynamically recompiled.  

Notice the (section_replay parent_tag:"record") -- we can naturally 
tie sections to the data tree.

-----

Another issue that's unclear to me is sharing of "ovars", i.e. global state
between functions. I'd like to have somethig like ui_var, which is available
according to dynamic calls, and not lexically. 

so,

ui_path "a/b/c"
  ui_var Boolean edit_mode := true
  myfunc1

ui_function myfunc1
  if edit_mode
    ...

without this kind of thing, I am forced to pass state to functions,
or to try to keep as much code as I can in ui_path body (but it gets messy).