/foo?sortby=name&order=asc&page=3
Dataseq is my major motivator, but I have some other things that need
to be exposed this way. I haven't followed development for a while so
please tell me if the following makes sense.
In src/widgets/widget/widget-mop.lisp, add a new
expose-via-query-string-p slot to widget-slot-definition-mixin. This
lets the programmer declaratively state which widget slots should
expose their state to the URI. Because a single page may contain
multiple widgets with the same slot names, when exposed to the URI the
slots may be prepended by the widget's id. Also, because the
programmer may want to turn the behavior off for some widgets, it's
probably a good idea to add expose-via-query-string-p slot to widget
class itself, and to look at it before serializing widget slots to the
query string.
The next step is rather simple. Every link and every form will have to
be appended with the state of the rendered widgets. One simple way of
doing it is collecting a list of widget instances/slot name pairs
during rendering, and then having hunchentoot rewrite the URLs to add
the slots and their serialized values to the query string. I can then
do:
(defwidget color-chooser ()
((color :initform 'red :expose-via-query-string-p t)))
When I render a page that has color-chooser in it, I get
/foo/bar?color=red. Obviously, if the user types in
/foo/bar?color=blue, weblocks will automatically update the slot in
the color-chooser.
Thoughts? Will this work with the new additions/modifications to weblocks?
On a different note, could someone take a little time to write a
summary of the new additions/modifications to weblocks? I don't want
my branch to hopelessly diverge from offical weblocks, and maintaining
everything in sync is hard (what will I have to change in my app and
how much work will it involve?).
You want to connect with Ian to avoid duplicating work.
> In src/widgets/widget/widget-mop.lisp, add a new
> expose-via-query-string-p slot to widget-slot-definition-mixin. This
> lets the programmer declaratively state which widget slots should
> expose their state to the URI.
That's a good start.
> Because a single page may contain
> multiple widgets with the same slot names, when exposed to the URI the
> slots may be prepended by the widget's id.
If the ID is an ephemeral symbol then it will not be an URL
that can be shared.
> Also, because the programmer may want to turn the behavior off for some
> widgets, it's > probably a good idea to add expose-via-query-string-p
> slot to widget class itself, and to look at it before serializing widget
> slots to the query string.
Yes, a useful extension. Perhaps add a global switch, too.
> When I render a page that has color-chooser in it, I get
> /foo/bar?color=red. Obviously, if the user types in
> /foo/bar?color=blue, weblocks will automatically update the slot in
> the color-chooser.
How is this going to mix with AJAX updates?
JavaScript still isn't allowed to change the query string in place.
> Thoughts? Will this work with the new additions/modifications to weblocks?
I don't think there will be any incompatibilites. I say this lightly,
though.
> On a different note, could someone take a little time to write a
> summary of the new additions/modifications to weblocks? I don't want
> my branch to hopelessly diverge from offical weblocks, and maintaining
> everything in sync is hard (what will I have to change in my app and
> how much work will it involve?).
Gladly, but you need to be a bit more specific. Are you talking about
upcoming merges, or a certain window that has already gone by...?
Leslie
> If the ID is an ephemeral symbol then it will not be an URL
> that can be shared.
Yes. Resolving by widget ID would probably happen only if there are
two slots with the same name which should be rare enough. It would
then be up to the programmer to provide the IDs for appropriate
widgets.
> How is this going to mix with AJAX updates?
For the updates to show up on the query string, the relevant links and
forms would have to be AJAX-free (at least until someone works on
integrating the URL hash thing into weblocks). This might be done
automatically (changing a state of a slot that is marked to be
serializable into the URL would force a refresh), or the burden might
be left on the user (which isn't that big of a deal, it seems).
> Gladly, but you need to be a bit more specific. Are you talking about
> upcoming merges, or a certain window that has already gone by...?
I was thinking about everything from two months ago until now (plus
upcoming changes). It's obviously difficult to come up with a detailed
changeset (I can just look at the hg log), so instead if any
reasonably major features come to mind, it'd be great if you could
mention them with a very brief overview. I can then dig in deeper
myself.
I suspect what Leslie was referring to is a recent e-mail where I
expressed interest in implementing the URL hash trick to clean up some
back button issues for my application while reducing page load and
redirects.
What you are describing sounds like what we talked about last summer
and I think it's a nice way to generalize the friendly URL idea. I
think the rendering pipeline can now handle the case where there is an
action and a different destination URL in the link, correct?
Then render-link can simply render the current widget-url + current
parameters + action. When clicked, the onclick javascript chooses to
do AJAX or a standard load based on whether the link URL matches the
current URL. We should add a render-simple-link which renders a
standard link without an action but is state sensitive.
It probably behooves us to have a discussion generally about the
different kinds of links we can generate, how they interact with state
and ajax updates, and what the right general interface is. I'm
upgrading to the latest weblocks over the next week or two so I'll get
up to speed on what's going on and can provide some input into this
discussion if it's helpful.
Ian
> When clicked, the onclick javascript chooses to do
> AJAX or a standard load based on whether the link URL matches the current
> URL.
Yes, that's a good way of doing it (the action will have to be
stripped off before doing the comparison).
> We should add a render-simple-link which renders a standard link
> without an action but is state sensitive.
I'm not sure what this is for. Why not just use an href? Presumably in
this case you'd go to a different page so that it's ok for the query
string to be lost.
> I'm upgrading to the latest weblocks over the next week or two so
> I'll get up to speed on what's going on and can provide some input
> into this discussion if it's helpful.
When you're done, could you briefly post about the experience? I'm
curious how much effort moving from a private branch back into
official weblocks involves.
> On Mon, Feb 16, 2009 at 8:24 AM, Ian Eslick <esl...@media.mit.edu>
> wrote:
>> Then render-link can simply render the current widget-url + current
>> parameters + action.
> It's not quite that simple because at any given point render-link may
> not know all of the current parameters. If I have two widgets on a
> page that contribute to the query string, at the time widget 1 is
> rendered it knows nothing about the parameters of widget 2, so if it
> renders a link without that knowledge and the user bookmarks it later,
> the information about the state of widget 2 will be lost. The easiest
> fix I could think of for this is collecting all the query parameters
> during rendering, and then rewriting the html with Edi's url-rewrite.
I don't know if the new navigation has implemented this, but I have
been advocating for some time that we have a full pass through the
tree happening between action processing and rendering. This can
update the dispatchers/selectors as well as extract and collect
parameter information, etc. There are lots of reasons to have hooks
into a tree-global operation so we can do things that don't depend on
rendering order. Rendering should do nothing but serialize widgets.
>> When clicked, the onclick javascript chooses to do
>> AJAX or a standard load based on whether the link URL matches the
>> current
>> URL.
> Yes, that's a good way of doing it (the action will have to be
> stripped off before doing the comparison).
>
>> We should add a render-simple-link which renders a standard link
>> without an action but is state sensitive.
> I'm not sure what this is for. Why not just use an href? Presumably in
> this case you'd go to a different page so that it's ok for the query
> string to be lost.
Sorry, that was an incomplete thought. I'd like to link to a
particular tree state via a function like (render-widget-link named-
nav text). The idea is to allow certain widgets to export a name so I
can find and/or link to them easily. This way I can refer to related
widget state programatically rather than having to reconstruct the URL
at each rendering point.
This idea can be extended to link to a tree state + widget state by
adding a configuration arguments, such as
(render-widget-link mail-reader
"Link to mail-reader message"
:config `((mail-reader :message ,msg-id :state :reply)
(mail-chat-sidebar :update)))
The webapp can build/maintain a lookup table for widgets with names.
Two things I kept doing was finding ways for one widget to find
another. The other was writing functions to compute the URL for
particular tree state when I wanted to do a link from one tree state
to another. This was very ad-hoc and for friendly URL schemes a
common operation.
>> I'm upgrading to the latest weblocks over the next week or two so
>> I'll get up to speed on what's going on and can provide some input
>> into this discussion if it's helpful.
> When you're done, could you briefly post about the experience? I'm
> curious how much effort moving from a private branch back into
> official weblocks involves.
I haven't updated my branch since at least September! I'll let you
know.
Ian
> I haven't done anything along these lines yet. I did do some friendly
> URL work, but that was mostly custom render-link + dispatcher to strip
> tokens instead of AJAX links when I needed to change state.
>
> I suspect what Leslie was referring to is a recent e-mail where I
> expressed interest in implementing the URL hash trick to clean up some
> back button issues for my application while reducing page load and
> redirects.
Are you considering existing solutions for that? I have an item on my
"future work" list: integrate YUI Browser History Manager. Not that I'm
about to take on it, but the docs I read (at
http://developer.yahoo.com/yui/history/) were very promising.
--J.
> On Feb 16, 2009, at 8:37 AM, Vyacheslav Akhmechet wrote:
>
>> On Mon, Feb 16, 2009 at 8:24 AM, Ian Eslick <esl...@media.mit.edu>
>> wrote:
>>> Then render-link can simply render the current widget-url + current
>>> parameters + action.
>> It's not quite that simple because at any given point render-link may
>> not know all of the current parameters. If I have two widgets on a
>> page that contribute to the query string, at the time widget 1 is
>> rendered it knows nothing about the parameters of widget 2, so if it
>> renders a link without that knowledge and the user bookmarks it later,
>> the information about the state of widget 2 will be lost. The easiest
>> fix I could think of for this is collecting all the query parameters
>> during rendering, and then rewriting the html with Edi's url-rewrite.
>
> I don't know if the new navigation has implemented this, but I have
> been advocating for some time that we have a full pass through the
> tree happening between action processing and rendering. This can
> update the dispatchers/selectors as well as extract and collect
> parameter information, etc. There are lots of reasons to have hooks
> into a tree-global operation so we can do things that don't depend on
> rendering order. Rendering should do nothing but serialize widgets.
I was about to mention that -- in my navigation work the tree is walked
twice: once to shake it down, e.g. modify based on the current URL, for
example, and another time while rendering (however, note that there are
no "dispatchers" in my rewrite, only selectors, the actual
get-widget-for-tokens functionality being delegated to a method).
There are lots of things this can be used for -- as an example, I
figured that it should be up to the most specific widget to define the
HTML page <title>. "Most specific" as defined by "the deepest widget in
the tree that wishes to set the title". The title gets extracted during
the tree shakedown pass, so we have it nice and ready for rendering. It
worked out for me quite well.
You could use a similar approach to gather query string information, and
then render all links appropriately in the second phase.
--J.
Sure. Please forgive my copy/pasting from that thread, some passages are
relevant, so I recap them here:
The navigation-rewrite topic branch contains a significant rewrite of
the whole navigation and dispatching system in weblocks, as well as a
number of related improvements, that are only possible because of the
rewrite. The tree may be viewed at
http://github.com/jwr/weblocks-jwr/tree/navigation-rewrite
Overall, the rewrite resulted in less code and a smaller number of
concepts, which I'm very happy about.
Major features:
-- navigation items can have URIs different from their names
-- you can now have hidden navigation items
-- you can render navigation content separately from the menu (teleport hack)
-- generalized tree walking (see walk-widget-tree)
-- tree walking now tracks depth (necessary for the next feature)
-- ability to set the page title and breadcumbs name from anywhere in
the tree ("most specific widget" gets to do that)
-- breadcrumbs widget (You are here: Home > Users > All Users)
-- container and composite are gone, use (make-instance 'widget :children (list...))
-- root-composite is now root-widget
-- dispatcher is gone, subclass selector and define get-widget-for-tokens
-- *uri-tokens* is now an object
-- you can't (setf widget-children) anymore, see set-children-of-type
-- anyone who uses set-children-of-type can participate in flows as a parent
-- new render-widget-children method for additional rendering flexibility
-- lazy-navigation: acts just like navigation, except that instead of
passing widgets to it you may pass functions (closures). These will be
called when the navigation panes are actually selected and should return
widgets. That way you avoid creating the entire widget tree when the
session is initialized, so you get a quicker response time initially and
don't waste resources on things the user may never get to. For larger
sites this could be quite important.
-- there is one make-widget-place-writer function which will work for
everyone that maintains their children using set-children-of-type.
From the design point of view:
-- the widget tree is walked twice: once to update, once to render
-- the second rendering pass should be side-effect free
-- dispatcher, composite, container are gone
-- look at get-widget-for-tokens to see how dispatching is done
-- selector is the key to selecting parts of the tree based on something
(e.g. URIs)
-- static-selector specifically implements selecting based on URIs
-- navigation implements, well, navigation, which is basically
static-selector functionality with human-readable names for items
-- dynamic dispatching with caching is done by on-demand selector
A quick guide to updating your code:
-- replace (root-composite) with (root-widget)
-- replace (make-instance 'composite :children XXX) with (make-instance
'widget :children XXX)
-- replace (setf (widget-children XXX) YYY) with (set-children-of-type XXX
YYY :your-type)
-- replace (composite-widgets XXX) with (get-children-of-type XXX :your-type)
-- adapt your make-navigation calls to the new interface
-- if you have used on-dispatch, you want
(make-instance 'on-demand-selector :lookup-function #'your-lookup-function)
where your-lookup-function takes a selector object and tokens (the
selector is usually ignored) and returns three values: widget or
nil, consumed tokens, remaining tokens.
I think that can serve as a quick guide. I've included extensive
docstrings (look in selector.lisp for example), so that should help.
There are no tests. I do feel bad about it, but it isn't likely I'll be
implementing them anytime soon.
This work has not been merged yet, and I believe there is still
disagreement as to whether set-children-of-type is a good idea. See the
thread for detailed discussion. Having considered it somewhat more, I
still think it is the right approach for a mutable widget tree framework
that weblocks is. I would not choose this approach for a purely
functional web framework.
--J.
Now, some questions.
1. I don't understand set-children-of-type. Your rationale is a widget
sharing multiple parents, so you accept the type keyword. First,
multiple widget inheritance has moot semantics, I'm not sure when one
would reuse a widget and set it with different children in each case.
Second, what if both parents of a widget are :selectors? I'm not
completely sure what is the alternative, but the scheme in place right
now seems pretty difficult to understand for newcomers (which is me
:]). Why not (setf widget-children) again? If the parents set children
of one of their child widgets, shouldn't it be their responsibility to
reset this list every time the tree is walked? Why store this
information in the child widget?
2. Currently render-widget-children is called from render-widget. This
means that in order for me to have control over how the children are
rendered, I have to override render-widget, which means I'll have to
do more work than I want to. For example, how would you implement
table-composite with this scheme
(http://bitbucket.org/coffeemug/weblocks-slava/src/tip/src/widgets/table-composite.lisp)?
I find multiple inheritance to be tremendously useful. As an example for
our discussion, let's pick the "commentable" mixin class, which itself
is a subclass of pageable-list, a widget that combines a pagination
widget with a list of widget items. I can make any other widget
"commentable" just by adding commentable to the list of
superclasses (well, subject to database model restrictions). This is
tremendously useful.
> Second, what if both parents of a widget are :selectors?
I guess if it hurts, don't do it then :-) I mean, not all combinations
will make sense, but I don't think this is a problem.
> I'm not completely sure what is the alternative, but the scheme in
> place right now seems pretty difficult to understand for newcomers
> (which is me :]). Why not (setf widget-children) again? If the parents
> set children of one of their child widgets, shouldn't it be their
> responsibility to reset this list every time the tree is walked? Why
> store this information in the child widget?
There is one widget-children slot -- how do you combine the various
children that each of your parent widgets has? How do you maintain that
list -- remember that on a tree update we need to update the list of
children. In the example above, a request to go to the next page of
comments might have come in. Now someone has to update a _part_ of
widget-children with the new comments. It's impossible to do using (setf
widget-children), all you can do is have each widget maintain its own
list and then have them compose all the lists together. All I did was
provide a uniform interface for these kinds of hacks.
The other rationale is that when you subclass other widgets, you might
want to control the rendering order. This was important in my
application. Since your specialized widget has to "know" about all
superclasses anyway, one might argue it can also know about their
children types -- so it can override all rendering and control it
itself.
I realize this is controversial and I myself am not that happy with this
solution. However, it's the best I could work out, it is reasonably
simple to follow and easy to debug (way easier than method combinations)
and works.
> 2. Currently render-widget-children is called from render-widget. This
> means that in order for me to have control over how the children are
> rendered, I have to override render-widget, which means I'll have to
> do more work than I want to. For example, how would you implement
> table-composite with this scheme
> (http://bitbucket.org/coffeemug/weblocks-slava/src/tip/src/widgets/table-composite.lisp)?
No, the goal was for you to override render-widget-children. I have
places where I override it to do nothing at all, because
render-widget-body handles all the work explicitly.
--J.
My vote then goes towards the append method combination for some
get-widget-children method. It seems to fit into this near perfectly,
is easy to manipulate, and if I saw it I would immediately understand
what it's for, while your solution was actually more confusing :) I
don't think newbie-friendliness is a good argument not to use advanced
CL features - if that were the goal you might as well write weblocks
in Java. I also don't think method combinations are very difficult to
debug (you can just print the return value of the function). These are
my two cents.
> No, the goal was for you to override render-widget-children.
Ok. I think that in most cases people will want to interleave child
widgets with their own markup so it makes more sense to render child
widgets in the specialization of render-widget-body for widget, and
let people render them on their own when they specialize
render-widget-body. It seems like this would be optimizing for a more
common case.
(setf (child-widget parent-widget 'child-name) child-widget)
(child-widget parent-widget 'child-name)
And of course one could implement (child-widgets parent-widget).
This association can be stored in a per-session hashmap, not widget
slots, so it would avoid the multiple inheritance issue. The benefit
is that one could easily store and access child widgets as one does
with slots, which is immensely useful, without having to use an
additional interface to specify the child widgets for weblocks
machinery.
I am not particularly attached to my solution, in fact I would gladly
trade it for something which is nicer and works better. It just needs to
do all that is required.
As for debugging, I disagree: to print the return value you need to have
a full environment (session, etc) set up and you have to instrument your
code. Then you have to track method ordering, and that isn't
obvious. I'd much rather just inspect a slot and see what's there. But
that point isn't that important.
Please remember that we also want these children to be ready to
participate in flows, so storing them in different ways isn't a good
idea unless one wants to write multiple make-widget-place-writer
functions. In particular, I found that storing widgets in slots and
ad-hoc lists really isn't such a great idea.
Also, an append method combination fixes the ordering, which in my case
is a no-no.
>> No, the goal was for you to override render-widget-children.
> Ok. I think that in most cases people will want to interleave child
> widgets with their own markup so it makes more sense to render child
> widgets in the specialization of render-widget-body for widget, and
> let people render them on their own when they specialize
> render-widget-body. It seems like this would be optimizing for a more
> common case.
I try to be careful in using words like "most cases" -- I found out that
people have wildly different use cases. Let's take the "commentable"
mixin example. Here's its render-widget-children method (it does not
provide a render-widget body method):
(defmethod render-widget-children ((obj commentable) &rest args)
(declare (ignore args))
(when (show-comment-bar-p obj)
(apply #'render-widget (get-children-of-type obj :comment-bar)))
(when (show-comments-p obj)
(call-next-method)))
Note that comment-bar participates in flows (it gets replaced by a rich
text editor).
Now, call-next-method will call pageable-list's render-widget-children,
which will render the pagination widget and the current list of comments
(which will vary depending on pagination).
The nice thing about this is that all I have to do to use this is to
make, say a news-item widget "commentable". That's pretty much it --
render-widget-children kicks in and things Just Work. However, should I
want to override the rendering order, I can easily do that by overriding
render-widget-children and doing all the work myself,
get-children-of-type being my only interface to actually get at
appropriate widgets.
I don't really want to defend my approach, as I myself am not that happy
with it, but I'd like everyone to understand the motivation behind, so
that we (I?) don't lose functionality.
--J.
On Mon, Feb 16, 2009 at 5:09 PM, Jan Rychter <j...@rychter.com> wrote:
> Please remember that we also want these children to be ready to
> participate in flows, so storing them in different ways isn't a good
> idea unless one wants to write multiple make-widget-place-writer
> functions. In particular, I found that storing widgets in slots and
> ad-hoc lists really isn't such a great idea.
I agree, I think there should be one uniform way of doing this. I
think what I proposed fills this requirement (of course proposing
something and coding it are two different beasts :]).
> Also, an append method combination fixes the ordering, which in my case
> is a no-no.
Ok. I think this is easy to fix, though.
Hmm. But how is that different from get-children-by-type? (except for
the fact that you can have only one child widget per name, which is a
limitation I would not be happy with)
The above interface looks almost exactly like what I implemented, just
change the names.
As I wrote before, I do not think storing widgets in slots is a good
idea at all, because by default they can't participate in flows. This
could change if someone hacks a general make-widget-place-writer for
widget slots.
> This association can be stored in a per-session hashmap, not widget
> slots, so it would avoid the multiple inheritance issue. The benefit
> is that one could easily store and access child widgets as one does
> with slots, which is immensely useful, without having to use an
> additional interface to specify the child widgets for weblocks
> machinery.
I really don't like mixing additional session hashmaps into that. We
already have a data structure intended to hold widgets: it's the widget
tree. I don't think multiplying data structures is a good idea. If the
widget tree doesn't serve its purpose, let's get rid of the widget tree
and replace it with something else, but let's not have two structures in
parallel.
--J.
Here's something I really would like to understand: could you please
describe a use case where you have to find a widget in the tree?
The reason I ask is because in all of my code whenever I need to link
between widgets, there is always a place in the code where the linked
widgets are processed (or created) together, so one can simply store a
reference to another and there is no need for walking the tree to find
anything.
In general, weblocks has a mutable approach to data, so we can safely
keep references without them becoming stale. In a purely functional
language you would need to find widgets by name or path, but not
necessarily with Weblocks. Since this is a design decision, let's use it
to our advantage.
--J.
Hmm. But how is that different from get-children-by-type? (except for
the fact that you can have only one child widget per name, which is a
limitation I would not be happy with)
The above interface looks almost exactly like what I implemented, just
change the names.
As I wrote before, I do not think storing widgets in slots is a good
idea at all, because by default they can't participate in flows. This
could change if someone hacks a general make-widget-place-writer for
widget slots.
> This association can be stored in a per-session hashmap, not widget
> slots, so it would avoid the multiple inheritance issue. The benefit
> is that one could easily store and access child widgets as one does
> with slots, which is immensely useful, without having to use an
> additional interface to specify the child widgets for weblocks
> machinery.
I really don't like mixing additional session hashmaps into that. We
To make it look similar to auxiliary method qualifiers, you mean?
I have just searched the spec but just found "symbol", no information
on whether this is evaluated. I think it isn't because the current syntax
works and an argument being evaluated by a macro is always mentioned
explicitly.
SBCL seems to agree:
EQM(52): (defgeneric foo () (:method-combination append))
#<STANDARD-GENERIC-FUNCTION FOO (0)>
EQM(53): (defgeneric foo () (:method-combination :append))
STYLE-WARNING: redefining FOO in DEFGENERIC
debugger invoked on a SIMPLE-ERROR in thread #<THREAD "initial thread" RUNNING {A8EA7D9}>:
There is no applicable method for the generic function
#<STANDARD-GENERIC-FUNCTION SB-MOP:FIND-METHOD-COMBINATION (11)>
when called with arguments
(#<STANDARD-GENERIC-FUNCTION #<unbound slot> "?"> :APPEND NIL).
> On Feb 17, 11:12 am, Jan Rychter <j...@rychter.com> wrote:
>> Vyacheslav Akhmechet <coffee...@gmail.com> writes:
>
>> > And of course one could implement (child-widgets parent-widget).
>>
>> Hmm. But how is that different from get-children-by-type? (except for
>> the fact that you can have only one child widget per name, which is a
>> limitation I would not be happy with)
>>
>> The above interface looks almost exactly like what I implemented, just
>> change the names.
>
> That's what I thought when I read it.
>
>> As I wrote before, I do not think storing widgets in slots is a good
>> idea at all, because by default they can't participate in flows. This
>> could change if someone hacks a general make-widget-place-writer for
>> widget slots.
>
> It wouldn't be hard to do this.
>
> I'm fond of the idea of storing widgets in slots, the children-of-type
> seems a bit clumsy in comparison.
>
> Are there any other disadvantages of using slots for children?
Three I can think of -- 1) the make-widget-place-writer problem
described above, 2) the need to supply an *additional* enumeration
function (which means lots of fun with method combinations to make it
work), and 3) if you want to control rendering in your specialized
subclasses, they still need to "know" about these slots and reference
them directly, which I'd say is just as clumsy as the solution I
implemented.
Of course there is also a small (4) -- get/set-children-of-type always
operates on lists, promoting widgets to lists if necessary, which means
you always use map on what get-children-by-type returns.
But, I think the only way to really know is to try -- I did, rewrote the
code a couple of times and ended up with a solution that is a
compromise. Like I said, I'm not that enthusiastic about it, but I think
it isn't such a bad compromise. If someone comes up with a better
working solution, I'd be more than happy to migrate.
--J.
I don't really consider this to be a problem. The protocol is
in place and we'd just need to supply a small adapter.
> 2) the need to supply an *additional* enumeration function
> (which means lots of fun with method combinations to make it
> work),
What needs to be enumerated, and how?
> and 3) if you want to control rendering in your specialized
> subclasses, they still need to "know" about these slots and reference
> them directly, which I'd say is just as clumsy as the solution I
> implemented.
Yes, there's not really a difference. Children classes often
need information about their superclasses and that can't be avoided.
Either way we can supply a function that returns a widget's
children without this additional information.
> Of course there is also a small (4) -- get/set-children-of-type always
> operates on lists, promoting widgets to lists if necessary, which means
> you always use map on what get-children-by-type returns.
>
> But, I think the only way to really know is to try -- I did, rewrote the
> code a couple of times and ended up with a solution that is a
> compromise. Like I said, I'm not that enthusiastic about it, but I think
> it isn't such a bad compromise. If someone comes up with a better
> working solution, I'd be more than happy to migrate.
What I'm trying to figure out right now is what should be at
the core. Custom accessors can be provided above children-of-type
and children-of-type can be implemented atop of slots.
The latter solution seems more natural and effective to me,
but I'm still very open to be convinced of the converse.
It would be good if Stephen and Slava participated again in this
discussion.
By the way it's nice to see that we apparently have an agreement
about the other 80% of navigation-rewrite. :)
Someone has to enumerate *all* children of a given widget, in order to
walk the tree during the update phase.
>> and 3) if you want to control rendering in your specialized
>> subclasses, they still need to "know" about these slots and reference
>> them directly, which I'd say is just as clumsy as the solution I
>> implemented.
>
> Yes, there's not really a difference. Children classes often
> need information about their superclasses and that can't be avoided.
Yes, that was exactly my point -- you need a class-specific interface to
access them anyway, so it doesn't really make any difference whether you
do
(map #'render-widget (get-my-superclass-fancy-widgets obj))
or
(map #'render-widget (get-children-of-type obj :my-superclass))
>> Of course there is also a small (4) -- get/set-children-of-type always
>> operates on lists, promoting widgets to lists if necessary, which means
>> you always use map on what get-children-by-type returns.
>>
>> But, I think the only way to really know is to try -- I did, rewrote the
>> code a couple of times and ended up with a solution that is a
>> compromise. Like I said, I'm not that enthusiastic about it, but I think
>> it isn't such a bad compromise. If someone comes up with a better
>> working solution, I'd be more than happy to migrate.
>
> What I'm trying to figure out right now is what should be at
> the core. Custom accessors can be provided above children-of-type
> and children-of-type can be implemented atop of slots.
>
> The latter solution seems more natural and effective to me,
> but I'm still very open to be convinced of the converse.
Same here :-)
> It would be good if Stephen and Slava participated again in this
> discussion.
>
> By the way it's nice to see that we apparently have an agreement
> about the other 80% of navigation-rewrite. :)
I think we agree that at the very least it is slightly better than what
we had before, but I wouldn't consider it "done" -- there is certainly
lots of room for improvement.
As an example, I'll toss a thought -- I've been toying with the idea of
a Clojure web framework. Trying to avoid mutable state, I implemented a
widget tree where each widget *may* have a :path attribute. If that
attribute is set, it is matched to uri-tokens during rendering.
What you get is pretty nice -- a widget tree that renders differently
depending on uri-tokens without *any* selectors. Plus there is just one
widget tree, you don't have to choose what will be rendered -- the
filtering does that on its own. It is a kind of path-to-tree matching
thing. The additional bonus is that you can have different parts of the
tree react to the same tokens.
What makes me happy is that the entire code for walking the tree,
matching uri-tokens, selecting and rendering fits on a single screen.
I don't think this is necessarily a good match for weblocks, but it's an
example of an idea going further than the current navigation-rewrite.
--J.
After sleeping on it for a few days, my only problem is the whole type
thing. It just doesn't seem to fit and is pretty confusing at the
beginning. If we passed an object instance instead of an arbitrary
type argument, I think it would be a much more intuitive solution
(small matter, I know).
I think the rest of the navigation rewrite is a great design - it
really smoothes out some rough corners.
I agree the naming sucks. I'm not happy about it myself. I can't even
remember the order of arguments to set-children-of-type sometimes.
But object instances are problematic -- because then you'd need to key
by class name or a hash of class instance, and I have widgets that store
children of two different kinds.
Perhaps set-children-of-kind sounds better?
> I think the rest of the navigation rewrite is a great design - it
> really smoothes out some rough corners.
Thanks, I'm glad you like it.
--J.
I am afraid it's me who doesn't understand :-)
Could you provide a short example of what you consider to be "parent
object instances"? And could you please explain how I could store two
different lists of widgets keyed to that same object instance?
--J.
I still don't like set-children-of-type (it's not really about the
name, just the way the whole thing is rubs me the wrong way), but I
can't come up with a reasonable improvment. It's not *too* bad though,
there are definitely less pleasant parts of weblocks than this :)
> I still don't like set-children-of-type (it's not really about the
> name, just the way the whole thing is rubs me the wrong way), but I
> can't come up with a reasonable improvment.
I agree. It doesn't smell right to me either. And when something feels
wrong, it usually points to a conceptual mismatch. I think in this case
it results from trying to mix widget composition with CLOS inheritance.
Again: I have been defending the functionality, not the
implementation. I would love to see something better.
--J.
Well, I read Leslie's long E-mail and... agreed with it. :-)
Leslie, you restated the problem well. But, as I wrote before, even
though I am not entirely happy with get/set-children-of-type, I am not
in favor of playing with method combinations in this case and I know of
no other solutions. So, being the practical type, I just use what I
wrote and it works for me.
And since I don't have any better ideas, I don't plan on implementing
anything new.
--J.
Hmm. I would like to get some pointers from weblocks maintainers as to
whether navigation-rewrite is going to get merged or not.
I have to sync with recent weblocks-dev -- I've been postponing that (It
has been over a month since I finished working on navigation-rewrite)
thinking that my navigation-rewrite branch would get merged.
Is there agreement on navigation-rewrite EXCEPT
set/get-children-of-type? If so, then perhaps you can start your work
basing it on navigation-rewrite? At least you would have the places that
need changing clearly tagged. I believe a working solution is better
than no solution, so I would suggest we merge and then improve on
it. It's "-dev" after all, remember?
If the navigation-rewrite branch is to hang there indefinitely, I'll
have to go on working and my fork will diverge significantly.
--J.
It will be, and get-/set-children-of-type essentially too
except that it will become widget-children as discussed earlier.
The first merge might not contain any traces of the append/mc
layer, in fact. It can be added later beneath widget-children.
> Is there agreement on navigation-rewrite EXCEPT
> set/get-children-of-type? If so, then perhaps you can start your work
> basing it on navigation-rewrite?
I will work directly on weblocks-jwr/navigation-rewrite.
I hope to get it merged by this or next week.
Lots of fun with the test suite waiting for me...
My dirty conscience got the better of me, so I'll at least provide you
with a navigation-rewrite merged into the latest -dev, later today.
--J.
The merge is in a new branch:
http://github.com/jwr/weblocks-jwr/tree/navigation-rewrite-merge-with-dev-cd83809dfadc
I verified that it compiles, but that's pretty much it. It probably
doesn't work. Here are notes that I made while merging:
render-menu in menu.lisp:
Instead of disabled-pane-names we now have disabled-panes. It is now a
list of uri-tokens corresponding to the disabled menu items, e.g. we key
on the token instead of on the attributized name, in sync with other
parameters to render-menu. Keying on the token is in general more
reliable and predictable.
Passing functions as cdrs of pane cons cells will probably not work, as
the 'target' is compared using equalp to strings. This needs to be
reworked -- I'm leaving it as-is for now.
navigation.lisp:
I renamed disabled-pane-names to disabled-panes, to bring it in line
with the rest of the navigation stuff, which means it now stores a list
of uri-tokens, not a list of attributized names.
table-composite.lisp: table-composite got inadvertently killed, because
there is no composite anymore. This could be reimplemented as a simple
widget, as most of what composite did is now in widget.
template-block.lisp: couldn't get asdf-system-connections to work -- I
don't even get the required symbol on *features*, and then it complains
about how it can't get the truename of the template-block.lisp file. I
don't have time to debug this right now, so I just loaded html-template
and template-block.lisp manually. Perhaps those who brought in
asdf-system-connections can take a look at this, or perhaps it works for
everyone but me.
There are two changesets on top of the merge, which are really "oops"
fixes.
I hope this helps.
--J.
Btw, here's my idea for caching:
(defun get-foo (...) ...)
(defcache get-foo :request)
(defun get-bar (...) ...)
(defcache get-bar :application :timeout 5)
This way get-foo will only get called once per request (the other
times the cached value will be returned immediately) and the result of
get-bar will be cached accross the application and will not be called
more often than every 5 seconds.
You might want to know why cache in the request. This can become very
comfortable - for example, dataseq calls dataseq-data many times for a
single render. Instead of refactoring dataseq to call dataseq-data
only once and cache the value manually, we can simply use the defcache
macro.
Defcache would be implemented in a somewhat standard fashion - the way
CL people normally implement defmemoize.
It's probably too many ideas for a single thread, but that's what I
have on my mind and todo list.
That's alright, but it's important for me to know that.
About the support for caching (or rather scoped memoizing)
-- it's neat but I don't think the returns are useful enough
given our limited resources.
Things like fixing the serializer (esp. with the dropdown)
and integrating CSS-Lite are much more important IMO.
> On Tue, Feb 24, 2009 at 8:05 AM, Leslie P. Polzer <leslie...@gmx.net> wrote:
>> What are your plans for implementing this?
> I can't commit to a timeline, unfortunately. There are a couple of
> things I'd like to see happen in weblocks - this, a jQuery rewrite
> (it's just more practical than Prototype at this point),
If you do, could you please keep in mind that some people (like me) will
want to rip out both Prototype and jQuery and use YUI (or something
else)?
Thing is, I need YUI widgets, so since I'm pulling all of YUI anyway, I
might as well use the base functionality instead of additionally loading
Prototype of jQuery.
Perhaps a Weblocks API will emerge?
> and support for caching. I'm not sure when and in what order I'll need
> these things yet, so the only thing I can say is that I'll probably do
> them eventually. Sorry, that's the best I can do now.
>
> Btw, here's my idea for caching:
>
> (defun get-foo (...) ...)
> (defcache get-foo :request)
>
> (defun get-bar (...) ...)
> (defcache get-bar :application :timeout 5)
This is something I implemented in my usual "ain't pretty, but works as
a first shot" manner. I have a 'cached-content-mixin':
(defclass cached-content-mixin ()
((last-update :accessor last-update-of :initarg :last-update :initform nil)
(update-interval :accessor update-interval-of :initarg :update-interval :initform (* 5 60))))
(defmethod update-if-needed ((obj cached-content-mixin) update-function)
(unless (and (last-update-of obj)
(< (- (current-time) (last-update-of obj)) (update-interval-of obj)))
(setf (last-update-of obj) (current-time))
(funcall update-function)))
Some widgets inherit from it, and have a :before method on
render-widget-body which updates the content if necessary:
(defmethod render-widget-body :before ((w newsboard) &rest args)
(declare (ignore args))
(update-if-needed w (lambda () (update-items w))))
The update should really be done during tree shakedown, not during
render, but I haven't gotten around to that yet. And it isn't that
important.
Solves my immediate problem.
--J.
I did the merge with latest weblocks-dev (at the time, which means
cd83809dfadc) -- what I'd do if I were you is first, check out my
'master' branch (which tracks weblocks-dev), and the
navigation-rewrite-merge-with-dev-cd83809dfadc branch. Then do a
git checkout navigation-rewrite-merge-with-dev-cd83809dfadc
git format-patch master
The result is 62 patches that are the difference between
weblocks-dev-cd83809dfadc and navigation-rewrite-merge. These include
modern-navigation work and my work. They can probably be imported into
hg with a script, I guess, as they preserve author information.
If you want me to send you the patch bundle, please let me know.
The fact that you recently pulled in changes to navigation
(7408fb1d7fe9) after my merge might make it more difficult for
you. Things like these will obviously conflict.
--J.
All the merging work you did in master will be lost this way,
won't it?
> The result is 62 patches that are the difference between
> weblocks-dev-cd83809dfadc and navigation-rewrite-merge. These include
> modern-navigation work and my work. They can probably be imported into
> hg with a script, I guess, as they preserve author information.
I must ask you to provide an appropriate changeset; these 62 patches
include stuff already applied to the main repo...
Leslie
I don't think so. Here's the topology:
master corresponds to weblocks-dev as of cd83809dfadc
navigation-rewrite is my navigation-rewrite work, also including a large
part of modern-dispatching
navigation-rewrite-merge-with-dev-cd83809dfadc was created by forking it
off master and merging in navigation-rewrite (resolving all problems)
So, navigation-rewrite-merge-with-dev-cd83809dfadc contains what you
want: weblocks-dev with the complete merge.
Now, issuing "git format-patch master" in that branch causes git to
export all patches relative to the master branch, which is also what you
want.
>> The result is 62 patches that are the difference between
>> weblocks-dev-cd83809dfadc and navigation-rewrite-merge. These include
>> modern-navigation work and my work. They can probably be imported into
>> hg with a script, I guess, as they preserve author information.
>
> I must ask you to provide an appropriate changeset; these 62 patches
> include stuff already applied to the main repo...
I think this is how git works -- for git it is perfectly acceptable to
have two patches in different branches modifying the same thing. A merge
then happens, during which git figures out what the final result
is. This is what happened here -- for example, my presentation-dom-id
work went into weblocks-dev. Git doesn't mind, as long as the final
result is the same. However, if you ask it for patch history, it will
faithfully answer with all the changesets it has.
I don't know how hg treats mergers, perhaps this isn't a problem.
I also don't know of other ways to help -- I mean, I can provide one
huge diff, but that's probably not what you want.
--J.