Forum OpenACS Q&A: double deference in template

Request notifications

Collapse
Posted by hsin wang on
Hello,

I'm trying to create a component template to which I would pass different multirow references to generate view-specific elements.

The problem I'm running into is that multirow column names are variable, and rather than aliasing query columns, I'd prefer to normalize those column names in the component template.

I was hoping since there was an easy way to dereference dynamically-generated variables since references to multirows and lists can be easily done.

I've tried several ways, but can't seem to figure it out. Below is an example of what I'm trying to do.

Example:
-------------------------------------------------

code:
# get users
db_multirow users nx {
  select user_id, username from users;
}

# get jobs
db_multirow jobs nx {
  select job_id, job_titlel from jobs;
}

-------------------------------------------------

templates:
<include src="../select-widget" &datasrc="users" combo_value="user_id" combo_label="username" />

<include src="../select-widget" &datasrc="jobs" combo_value="job_id" combo_label="job_title" />

-------------------------------------------------
included template:

<select>
        <multiple name="datasrc">
              <option value="@datasrc.@combo_value@@">
                    @datasrc.@combo_label@@
              </option>
        </multiple>
</select>

Collapse
Posted by Gustaf Neumann on
A conceptually simple approach is to use SQL aliases as shown here (untested):

Code:

# get users
db_multirow users nx {
  select user_id as id, username as label from users
}

# get jobs
db_multirow jobs nx {
  select job_id as id, job_title as label from jobs
}
Templates:
<include src="../select-widget" &datasrc="users" />

<include src="../select-widget" &datasrc="jobs" />
Included template:
<select>
        <multiple name="datasrc">
              <option value="@datasrc.id@">
                    @datasrc.label@
              </option>
        </multiple>
</select>

Collapse
Posted by Brian Fenton on
Hello Hsin

I presume that the example you gave of a select widget is a simplified version to give us an idea of what you want? Otherwise I would just recommend that you use the existing template::widget (or write your own if needed).

Having said that, it seems to me that what you are trying to achieve should work in theory? It's just a matter of getting the correct references to the data structures (and the correct quoting etc). It could be as simple as a missing backslash in the reference to the variable. Or possibly you could embed some TCL code in the ADP using the <%= syntax.

Did you see some of the usage notes here about using the include tag? https://openacs.org/doc/acs-templating/tagref/include.html

If you could post the actual full code you're working with, I'd be happy to take a look.

Brian

Collapse
Posted by hsin wang on
@Gustav

That was the closest I could get this, but as my post mentions, I would prefer avoiding sql aliases and let the developer decide what to pass to the template. The reasoning for this is that I may have a query saved in an xql that I use in multiple locations. Requiring my query to provide aliases takes away from flexibility. Maybe my logic behind is flawed, and if that is the case please enlighten me.

@Brian

I read the templating documentation on the Include tag pretty thoroughly, but I'm still having trouble double dereferencing.

The example code I posted is actually exactly all I needed. If there backslashes I'm supposed to put in the right place, I haven't figured out where to put them.

I haven't looked at the template::widget procs, but I'm essentially writing my own widgets, base widgets upon which I can build page-specific elements with other libraries. For example, my SELECT component can later go into another template that eventually transforms the SELECT to a jQueryUI element.

I was hoping to avoid building everything in TCL, as it seems to get messy pretty quickly, although I understand that to be merely an opinion. Maybe my approach is wrong altogether?

Collapse
Posted by Brian Fenton on
Hi Hsin

ok, I just tested your code, and it worked perfectly for me. I'm not using latest OpenACS, but that shouldn't be an issue. The only other difference is that I put the include in the same directory.

Here's what I did:
1. create aaa.tcl with 2 multirows:
# get users
db_multirow users nx {
  select user_id as id, username as label from users
  where rownum < 6
}

db_multirow users2 nx {
  select user_id as id, username as label from users
  where rownum < 3
}

2. create aaa.adp with this:
<include src="bbb" &datasrc="users" />
<hr>
<include src="bbb" &datasrc="users2" />

3. create bbb.adp with this:
<select>
        <multiple name="datasrc">
              <option value="@datasrc.id@">
                    @datasrc.label@
              </option>
        </multiple>
</select>

Output is a HTML page with 2 select boxes. Works perfectly!

Collapse
Posted by Gustaf Neumann on
As hsin wrote, he asked for the double substitution (first replace in the template the attribute names, then evaluate the multirow with the passed in datasource). As written above, this is more tricky, and used as well in OpenACS for e.g. the list templates. I would not recommend this for beginners, but here it goes:

First, one has to protect in the template content with "noparse", which should not be substituted in the first substitution step. The attribute names and labels are passed via @value_var@ and @label_var@.

<select>
    <noparse><multiple name="datasrc"></noparse>
       <option value="@datasrc.@value_var@@">
             @datasrc.@label_var@@
       </option>
    <noparse></multiple></noparse>
</select>
We assume that the template above is e.g. saved in the global www directory. When we define a function "adp_double_subst", we can provide a .tcl page with a multirow as follows.
db_multirow table table_query {
  select object_id, title from acs_objects order by object_id desc limit 10
}

set html [adp_double_subst /www/select-widget {&datasrc "table" value_var object_id label_var title}]

ns_return 200 text/plain $html
Finally, the proc adp_double_subst is defined below. As mentioned, not for beginners, but maybe it helps to understand the problem better, and maybe someone has usage for this code.

all the best
-g

ad_proc adp_double_subst {__src __args} {
    
    This proc is in its interface similar to template::adp_include,
    but it handles double de-references of ADP variables and returns
    the resulting HTML.
    
} {
    set __file [template::util::url_to_file $__src]
    set __code [template::adp_compile -file $__file.adp]

    #
    # Handle passed-in argument lists, by turning argument list into
    # local variables. So be careful with local variable names to
    # avoid unwanted interactions with the imported ones.
    #
    foreach {__key __value} $__args {
        if {[string match "&*" $__key]} {    
            # names starting with "&" flag call by reference
            if {"&" ne $__key } {
                set __name [string range $__key 1 end]
            } else {
                set __name $__value
            }
            upvar \#[adp_level] $__value $__name \
                $__value:rowcount $__name:rowcount \
                $__value:columns  $__name:columns

            # upvar :rowcount and :columns just in case it is a multirow
            if { [info exists $__name:rowcount] } {
                for { set __i 0 } { $__i <= [set $__name:rowcount] } { incr __i } {
                    upvar \#[adp_level] $__value:$__i $__name:$__i
                }
            }
        } else {
	    # var names not starting with a "&" => normal args (no reference)
            set $__key $__value
        }
    }
    #
    # Evaluate the template code
    #
    set __template [template::adp_eval __code]

    #
    # Now we have plain template, with the first evaluation level
    # removed. Perform the standard template compilation and
    # evaluation and return the resulting HTML.
    #
    set __code [template::adp_compile -string $__template]
    lappend ::template::parse_level [expr {[info level] -1}]
    set __html [template::adp_eval __code]
    template::util::lpop ::template::parse_level
    return $__html
}