Here are my thoughts on how I think navigation should work. Please
also take a look at the figure at:
http://screech.rychter.com/files/weblocks-new-nav-design-20081210.pdf
Comments are very, very welcome. In particular, if you find anything is
hard to understand, does not make sense, or (most importantly) if your
application won't fit in it, I'd like to know.
I read Ian's post in detail and I agree with what he said to an amazing
extent, with a few small exceptions (mostly noted below in the design
notes).
I intend to implement whatever results in the next few days.
--J.
General notes:
I think the initial dispatcher/selector naming was right-on, we just got
confused later. What I'm describing is mostly about separation of
concerns, I've kept the naming, because it fits perfectly.
I haven't addressed actions and flows at all -- but I believe that is an
orthogonal problem and we need to get navigation sorted out first.
I do not believe we should strive for URLs to reflect the current widget
hierarchy or state. I think the mapping should be restricted to URIs ->
application entry points. You can't reflect the entire application state
in an URI anyway, and nobody really expects that. You just want people
to be able to pass on URLs to your sites so that they point to existing
and known entry points (locations) in your application.
-----------------------------------------------------------------------
Dispatcher is responsible for mapping from URIs to selector/pane pairs.
Aside from its dispatching table, the dispatcher is stateless. It does
not know what is currently selected. It does not maintain caches, it
does not generate pages. It only dispatches.
Dispatcher knows about URIs, selector objects and pane names
(symbols). When an URI is matched, it calls the select-content method
with the selector object and pane-name parameters.
There can be multiple dispatchers in a widget tree and not all
arrangements of dispatchers and selectors will make sense. Also, usually
dispatchers will want to dispatch on a fixed number of URI tokens, but
that is not enforced.
Dispatchers should have multiple strategies -- simple string matching,
regexp matching and wildcard matches come to mind. When thinking of
dispatch strategies, keep in mind that we do not want to extract
parameters here, we only dispatch to a particular selector with a
particular pane-name that it registered with us. For wildcard matches,
we pass on the uri-token that was matched as the pane-name. It is the
selector's job to understand this.
-----------------------------------------------------------------------
Selector is a container that can select particular content based on a
name (pane name).
The selector has state: it knows what is currently selected. That state
can be updated either by the dispatcher (when someone lands via an URL)
or by any other widget (e.g. from actions). That makes the selector
useful also for some AJAX work, even without involving the dispatcher.
Selectors need to be registered with Dispatchers. When registered, the
Dispatcher remembers the URI->selector/pane-name mappings. Dispatcher
references Selector objects directly. I do not think there is a need to
introduce another naming scheme, when we have a perfectly valid widget
tree.
-----------------------------------------------------------------------
Dynamic Selector is a Selector that implements a dynamic content
creation strategy. It creates widgets on demand, and usually gets
registered with a dispatcher using a wildcard URI match. The "widget
cache" belongs in the dynamic selector, not in the dispatcher.
-----------------------------------------------------------------------
Navigation Content is a Selector specifically designed for being
navigated from a Menu.
-----------------------------------------------------------------------
Navigation Menu is both a Dispatcher and a Selector. Dispatcher, because
it reacts to URIs and changes other widgets' state based on
URIs. Selector, because it has to update its own state to highlight the
currently selected menu item.
-----------------------------------------------------------------------
Navigation is a Navigation Menu that also contains Navigation
Content. It *is* a Navigation Menu because it needs to be placed in the
widget hierarchy (because of dispatching), but it contains its content,
because of the loose relation between dispatchers and selectors. If you
just use Navigation objects, you can forget about the whole
Dispatcher/Selector separation of concerns business.
-----------------------------------------------------------------------
The scheme, as described, lets you address the following scenarios:
dynamic URLs (wiki): dispatcher does not create any content, it only
dispatches a wildcard token (or multiple tokens) to a
dynamic-selector. It is the dynamic-selector's job to actually create
content, maintain a cache, and select it.
user-interface/admin-interface: the main dispatcher object knows about
the user navigation (registered by the user navigation selector) and the
"/admin" URI registered by the admin interface selector. The admin
interface selector is placed (normally invisible) above the
user-interface navigation content in the widget tree. This means that
the admin interface selector can "choose" whether to render the user
interface or the admin interface, based on updates from the dispatcher,
or (in the future) based on updates from an action.
multi-level navigation: you can have multiple dispatchers in a widget
tree. For convenience, there will be a register-with-nearest-dispatcher
method that will walk up the tree, find the closest dispatcher object
and register there.
> Dispatcher knows about URIs, selector objects and pane names
> (symbols). When an URI is matched, it calls the select-content method
> with the selector object and pane-name parameters.
>
> There can be multiple dispatchers in a widget tree and not all
> arrangements of dispatchers and selectors will make sense.
How are nested dispatchers handled in this scheme?
> Also, usually dispatchers will want to dispatch on a fixed number of
> URI tokens, but that is not enforced.
Consider /documents/65 and /documents/65/printable.
For the first URL I'd currently return
(make-instance 'document-widget :id 65)
and for the second either
(make-instance 'document-widget :id 65 :printable t)
or
(make-instance 'printable-document-widget :id 65)
How would this work with your scheme?
I don't mean to say that it doesn't work, but I'm having trouble
imagining it...
> Dispatchers should have multiple strategies -- simple string matching,
> regexp matching and wildcard matches come to mind.
How about a dispatcher that takes the current time or system load
into account? Is that possible?
It's not that I'm trying to make much sense with this, but I'm
interested in the limitations of your model; this will help me
understand it.
> When thinking of
> dispatch strategies, keep in mind that we do not want to extract
> parameters here, we only dispatch to a particular selector with a
> particular pane-name that it registered with us. For wildcard matches,
> we pass on the uri-token that was matched as the pane-name. It is the
> selector's job to understand this.
So we can also pass on a list of tokens? Does a wildcard match
slashes, too? If yes then it might make sense to have another
wildcard that doesn't match slashes...
> Dynamic Selector is a Selector that implements a dynamic content
> creation strategy. It creates widgets on demand, and usually gets
> registered with a dispatcher using a wildcard URI match. The "widget
> cache" belongs in the dynamic selector, not in the dispatcher.
I suppose this one would make the /document/65/printable thing
possible?
> Navigation Menu is both a Dispatcher and a Selector. Dispatcher, because
> it reacts to URIs and changes other widgets' state based on
> URIs. Selector, because it has to update its own state to highlight the
> currently selected menu item.
Makes perfect sense, very good.
> -----------------------------------------------------------------------
> Navigation is a Navigation Menu that also contains Navigation
> Content. It *is* a Navigation Menu because it needs to be placed in the
> widget hierarchy (because of dispatching), but it contains its content,
> because of the loose relation between dispatchers and selectors. If you
> just use Navigation objects, you can forget about the whole
> Dispatcher/Selector separation of concerns business.
Alright, so how do MAKE-NAVIGATION and MAKE-SPLIT-NAVIGATION fit
into this?
How will I be able to render the menu separate from the content
or not at all?
> user-interface/admin-interface: the main dispatcher object knows about
> the user navigation (registered by the user navigation selector) and the
> "/admin" URI registered by the admin interface selector. The admin
> interface selector is placed (normally invisible) above the
> user-interface navigation content in the widget tree. This means that
> the admin interface selector can "choose" whether to render the user
> interface or the admin interface, based on updates from the dispatcher,
> or (in the future) based on updates from an action.
I'm not sure I get this. Why does the main dispatcher need to know
about the user navigation when the latter is fully managed via the
admin nav?
Thanks a lot so far!
Leslie
Furthermore, attempts to turn all drilldowns into URL navigation should
be discouraged. I have a lot to say about that, but will save it for
another time.
> Dispatcher is responsible for mapping from URIs to selector/pane pairs.
>
> Aside from its dispatching table, the dispatcher is stateless. It does
> not know what is currently selected. It does not maintain caches, it
> does not generate pages. It only dispatches.
If you are not responding to a URL request, how do you walk down to the
currently displayed content?
> Dispatcher knows about URIs, selector objects and pane names
> (symbols). When an URI is matched, it calls the select-content method
> with the selector object and pane-name parameters.
>
> There can be multiple dispatchers in a widget tree and not all
> arrangements of dispatchers and selectors will make sense. Also, usually
> dispatchers will want to dispatch on a fixed number of URI tokens, but
> that is not enforced.
>
> Dispatchers should have multiple strategies -- simple string matching,
> regexp matching and wildcard matches come to mind. When thinking of
> dispatch strategies, keep in mind that we do not want to extract
> parameters here, we only dispatch to a particular selector with a
> particular pane-name that it registered with us. For wildcard matches,
> we pass on the uri-token that was matched as the pane-name. It is the
> selector's job to understand this.
I believe the current dispatcher design, except for the timing of URI
stripping (fixed in modern-dispatching), is right. Different kinds of
URI stripping can be implemented with function-returning functions,
without complicating the class hierarchy, or putting even more logic on
the already-overloaded selectors.
It is not like the cache is optional for dispatchers, given that not all
requests have URIs attached, and we would surely like to be able to
dirty them (which we cannot do in dev, IIUC).
> Dynamic Selector is a Selector that implements a dynamic content
> creation strategy. It creates widgets on demand, and usually gets
> registered with a dispatcher using a wildcard URI match. The "widget
> cache" belongs in the dynamic selector, not in the dispatcher.
I disagree with this point. Putting widget creation on the dispatcher
means you only need one object to implement navigation among ephemeral
subwidgets ("on demand"). Dispatcher plus chooser function equals
magic. "Dynamic selector" feels far too much like the "Factory"
pattern, and factories are not needed in Common Lisp as we have
first-class functions.
(I have less to say about your other selector proposals. I don't really
like how they are in dev or modern-dispatching.)
> dynamic URLs (wiki): dispatcher does not create any content, it only
> dispatches a wildcard token (or multiple tokens) to a
> dynamic-selector. It is the dynamic-selector's job to actually create
> content, maintain a cache, and select it.
As I have mentioned above, I believe this only complicates things beyond
what you can already do with dispatcher and the default
widgets-ephemeral mode.
> user-interface/admin-interface: the main dispatcher object knows about
> the user navigation (registered by the user navigation selector) and the
> "/admin" URI registered by the admin interface selector. The admin
> interface selector is placed (normally invisible) above the
> user-interface navigation content in the widget tree. This means that
> the admin interface selector can "choose" whether to render the user
> interface or the admin interface, based on updates from the dispatcher,
> or (in the future) based on updates from an action.
While this has less to do with your proposal, I believe this sort of
thing should be discouraged, considering the user's standpoint.
Consider http://www.hfsbo.com/services-and-pricing . As the
non-technical administrator of this site, you would typically decide to
make changes to the information shown on this page while looking at it.
The workflow encouraged by separate admin interfaces such as those
insisted on by Django and the like is:
1. Go over to the admin side, logging in if necessary.
2. Find what it is you want to change *again*, using a completely
different interface for the same data.
3. Change it.
4. Reload the original and make sure it's what you want.
If your admin isn't technical enough to use multiple tabs/windows
liberally, themselves a kludge to deal with poor web interfaces like
this one, add even more interpage navigation to the above. I would not
be surprised if many admins log out, navigate back to the front page by URL,
and find the right page from the root again.
The main element of the page I linked is actually a listedit with
post-view-rendered gridedits, linked into a tree by a primitive version
of the `generate-subwidgets' system I recently committed to my
modern-dispatching branch, where the login flow calls a version of
`walk-widgets' (similarly recently committed) with the `auth-changed!'
GF. The listedit and gridedits respond with mark-dirty, and the various
allow-{add,delete,drilldown} methods on them eventually test whether the
authenticated user is an admin.
So here is what the change flow looks like:
1. Log in using the link at top if needed.
2. Make changes, seeing them incrementally as you save dataforms, in
exactly the same interface as the public side.
I really like this pattern, and it is in use in a few other places on
that site.
Obviously, not all information can be managed in this way. However, it
does cover a great deal of it. The danger of talking about "admin
interface" is that to the Weblocks user it says "you're supposed to do
it like in Django", which is a very poor substitute for the power to
create intuitive interfaces that Weblocks offers.
--
I write stuff at http://failex.blogspot.com/ now. But the post
formatter and themes are terrible for sharing code, the primary
content, so it might go away sooner or later.
I am not sure I understand the question -- we have a full widget tree,
where everything that could possibly be displayed is in the
tree. Selectors maintain the current state, e.g. what is selected. This
means that you can walk the tree and render it, or find currently
displayed content. You do not need to dispatch to do that.
>> Dispatcher knows about URIs, selector objects and pane names
>> (symbols). When an URI is matched, it calls the select-content method
>> with the selector object and pane-name parameters.
>>
>> There can be multiple dispatchers in a widget tree and not all
>> arrangements of dispatchers and selectors will make sense. Also, usually
>> dispatchers will want to dispatch on a fixed number of URI tokens, but
>> that is not enforced.
>>
>> Dispatchers should have multiple strategies -- simple string matching,
>> regexp matching and wildcard matches come to mind. When thinking of
>> dispatch strategies, keep in mind that we do not want to extract
>> parameters here, we only dispatch to a particular selector with a
>> particular pane-name that it registered with us. For wildcard matches,
>> we pass on the uri-token that was matched as the pane-name. It is the
>> selector's job to understand this.
>
> I believe the current dispatcher design, except for the timing of URI
> stripping (fixed in modern-dispatching), is right. Different kinds of
> URI stripping can be implemented with function-returning functions,
> without complicating the class hierarchy, or putting even more logic on
> the already-overloaded selectors.
>
> It is not like the cache is optional for dispatchers, given that not all
> requests have URIs attached, and we would surely like to be able to
> dirty them (which we cannot do in dev, IIUC).
Perhaps I misunderstand the cache -- but isn't the cache supposed to
cache *widgets*? When I designed, I believed that role belongs to the
selector.
>> Dynamic Selector is a Selector that implements a dynamic content
>> creation strategy. It creates widgets on demand, and usually gets
>> registered with a dispatcher using a wildcard URI match. The "widget
>> cache" belongs in the dynamic selector, not in the dispatcher.
>
> I disagree with this point. Putting widget creation on the dispatcher
> means you only need one object to implement navigation among ephemeral
> subwidgets ("on demand"). Dispatcher plus chooser function equals
> magic. "Dynamic selector" feels far too much like the "Factory"
> pattern, and factories are not needed in Common Lisp as we have
> first-class functions.
Interesting. I think you have a valid point. But let's discuss. I
believe that the major flaw of the current dispatcher design is that it
mixes up dispatching with selecting and rendering. I think I would be ok
with the dispatcher extracting arguments from URLs and creating widgets,
but it would need to pass them on to selectors. Otherwise we will still
have two entities responsible for selecting and rendering their content,
which I think is a design flaw.
I'm not a fan of factories myself. But please note that
"dynamic-selector" is more than just a factory. Its role is mainly to
*select* among cached widgets and possibly create one on demand, if
needed. In fact, as this is CL and not Java and we don't do factories,
the selector itself maintains state (what is selected and what is
cached), it is always a function that creates new widgets.
My point was mostly to keep the dispatcher as an URI processor, but
remove all rendering from it.
> (I have less to say about your other selector proposals. I don't really
> like how they are in dev or modern-dispatching.)
>
>> dynamic URLs (wiki): dispatcher does not create any content, it only
>> dispatches a wildcard token (or multiple tokens) to a
>> dynamic-selector. It is the dynamic-selector's job to actually create
>> content, maintain a cache, and select it.
>
> As I have mentioned above, I believe this only complicates things beyond
> what you can already do with dispatcher and the default
> widgets-ephemeral mode.
In a way, yes, it does, but only for the dynamic URIs case. Looking at
it another way, it is the dynamic-uri case that seemingly complicates
things, as we are trying to force the dispatcher to also render
widgets and maintain a cache of widgets. And our dispatchers are not
containers, remember?
Hmm. Now that I think of it -- perhaps a better solution is to implement
the generic dispatcher without any wildcards or dynamic URIs, and then
implement a wiki-dispatcher that would be both a dispatcher and a
selector, just as navigation-menu is? That would keep the functionality
you want in one place.
Please let me know if you still disagree after my clarifications -- I
have the impression that we might meet in the middle.
>> user-interface/admin-interface: the main dispatcher object knows about
>> the user navigation (registered by the user navigation selector) and the
>> "/admin" URI registered by the admin interface selector. The admin
>> interface selector is placed (normally invisible) above the
>> user-interface navigation content in the widget tree. This means that
>> the admin interface selector can "choose" whether to render the user
>> interface or the admin interface, based on updates from the dispatcher,
>> or (in the future) based on updates from an action.
>
> While this has less to do with your proposal, I believe this sort of
> thing should be discouraged, considering the user's standpoint.
[...]
> Obviously, not all information can be managed in this way. However, it
> does cover a great deal of it. The danger of talking about "admin
> interface" is that to the Weblocks user it says "you're supposed to do
> it like in Django", which is a very poor substitute for the power to
> create intuitive interfaces that Weblocks offers.
Good stuff. I agree with this, but I still think there are cases where
you need to replace the user interface altogether. And I have one of
them right in front of me right now :-)
I think both approaches should be implementable using Weblocks.
--J.
Same as now, no difference.
>> Also, usually dispatchers will want to dispatch on a fixed number of
>> URI tokens, but that is not enforced.
>
> Consider /documents/65 and /documents/65/printable.
>
> For the first URL I'd currently return
>
> (make-instance 'document-widget :id 65)
>
> and for the second either
>
> (make-instance 'document-widget :id 65 :printable t)
>
> or
>
> (make-instance 'printable-document-widget :id 65)
>
> How would this work with your scheme?
[...]
I did not intend to limit what you can do with uri-tokens processing. My
draft did not describe it in detail, as I wanted to flesh out the major
design points first. I do not see a problem with doing what you
described above -- but I think Stephen has a point that it really is
better to let dispatchers extract information from URLs and call
functions that create widgets.
>> Dispatchers should have multiple strategies -- simple string matching,
>> regexp matching and wildcard matches come to mind.
>
> How about a dispatcher that takes the current time or system load
> into account? Is that possible?
Why not?
The main point of my redesign is to separate dispatching from
selecting/rendering. This is what we got mixed up in both our current
implementations. Dispatcher also renders and selectors know about
uri-tokens, this is all wrong.
> It's not that I'm trying to make much sense with this, but I'm
> interested in the limitations of your model; this will help me
> understand it.
>> When thinking of
>> dispatch strategies, keep in mind that we do not want to extract
>> parameters here, we only dispatch to a particular selector with a
>> particular pane-name that it registered with us. For wildcard matches,
>> we pass on the uri-token that was matched as the pane-name. It is the
>> selector's job to understand this.
>
> So we can also pass on a list of tokens? Does a wildcard match
> slashes, too? If yes then it might make sense to have another
> wildcard that doesn't match slashes...
I think this needs to be redesigned according to Stephens suggestion --
keep all uri parsing and parameter extraction in the dispatcher.
>> Dynamic Selector is a Selector that implements a dynamic content
>> creation strategy. It creates widgets on demand, and usually gets
>> registered with a dispatcher using a wildcard URI match. The "widget
>> cache" belongs in the dynamic selector, not in the dispatcher.
>
> I suppose this one would make the /document/65/printable thing
> possible?
Yes, if that is to be created on demand. If you intend to keep both
/document/65 and /document/65/printable in your widget tree (say to
navigate with a static javascript tab widget), then you might not need
the dynamic part.
But perhaps dynamic-selector is a bad idea. I have to think about it.
[...]
>> -----------------------------------------------------------------------
>> Navigation is a Navigation Menu that also contains Navigation
>> Content. It *is* a Navigation Menu because it needs to be placed in the
>> widget hierarchy (because of dispatching), but it contains its content,
>> because of the loose relation between dispatchers and selectors. If you
>> just use Navigation objects, you can forget about the whole
>> Dispatcher/Selector separation of concerns business.
>
> Alright, so how do MAKE-NAVIGATION and MAKE-SPLIT-NAVIGATION fit
> into this?
>
> How will I be able to render the menu separate from the content
> or not at all?
make-navigation would build a single navigation object, so you would get
everything in one block. The single navigation object is actually a
specialized navigation-menu that also contains its navigated content as
a convenience.
make-split-navigation would return a navigation-menu widget and
navigation-content widget. See the diagram for what they actually are.
>> user-interface/admin-interface: the main dispatcher object knows about
>> the user navigation (registered by the user navigation selector) and the
>> "/admin" URI registered by the admin interface selector. The admin
>> interface selector is placed (normally invisible) above the
>> user-interface navigation content in the widget tree. This means that
>> the admin interface selector can "choose" whether to render the user
>> interface or the admin interface, based on updates from the dispatcher,
>> or (in the future) based on updates from an action.
>
> I'm not sure I get this. Why does the main dispatcher need to know
> about the user navigation when the latter is fully managed via the
> admin nav?
Remember that I split dispatching from selecting/rendering. The main
dispatcher object will dispatch based on the first uri-token and will
either change the state of the first selector (switching between
admin/user interfaces) or the user interface will kick in (it will
typically have another dispatcher inside). So, the main dispatcher does
not "know" about the user navigation, it only needs to know about the
"/admin" entry point.
I am not sure if that is such a good example anymore :-)
--J.
Ah, yes. I guess my question pointed in that direction, too:
I think the current scheme or extracting tokens and returning
a widget is fine. But I wasn't sure whether the new thing you
proposed would be even better.
>>> Dispatchers should have multiple strategies -- simple string matching,
>>> regexp matching and wildcard matches come to mind.
>>
>> How about a dispatcher that takes the current time or system load
>> into account? Is that possible?
>
> Why not?
With the above this question has been answered.
> The main point of my redesign is to separate dispatching from
> selecting/rendering. This is what we got mixed up in both our current
> implementations. Dispatcher also renders and selectors know about
> uri-tokens, this is all wrong.
The dispatcher rendering anything has bugged me all this time,
and it was one of the things that motivated me to build the two-phase
request handler.
> make-split-navigation would return a navigation-menu widget and
> navigation-content widget.
I like this idea very much.
> See the diagram for what they actually are.
Yes, it's been quite helpful.
Leslie
--
LinkedIn Profile: http://www.linkedin.com/in/polzer
Xing Profile: https://www.xing.com/profile/LeslieP_Polzer
Blog: http://blog.viridian-project.de/
What do you mean by "dev"? Marking widgets as dirty works in
weblocks-dev and weblocks-dev-modern-dispatching.
> (I have less to say about your other selector proposals. I don't really
> like how they are in dev or modern-dispatching.)
I think we all agree on the restructuring of the selector/navigation
part then.
> As I have mentioned above, I believe this only complicates things beyond
> what you can already do with dispatcher and the default
> widgets-ephemeral mode.
Speaking of which, I don't like the current dichotomy:
I can either choose fresh generation of a widget (ephemeral) every
time and lose AJAX updates, flows and so on or have it cached all
the time for the whole session.
I'm sure we can find something better.
> Obviously, not all information can be managed in this way. However, it
> does cover a great deal of it. The danger of talking about "admin
> interface" is that to the Weblocks user it says "you're supposed to do
> it like in Django", which is a very poor substitute for the power to
> create intuitive interfaces that Weblocks offers.
I agree with Jan here that it should be possible to choose
to separate the administration from the rest. It's sometimes desirable
to do this.
We can show both approaches in the upcoming user guide and give
HCI advice.
Leslie
It doesn't sound like a dispatcher is a widget at all. If a dispatcher
does not render, how can it be a widget, and how can it be in the widget
tree?
> Jan Rychter <jan-JAsPCFd0e...@public.gmane.org> writes:
>> There can be multiple dispatchers in a widget tree and not all
>> arrangements of dispatchers and selectors will make sense. Also, usually
>> dispatchers will want to dispatch on a fixed number of URI tokens, but
>> that is not enforced.
>
> It doesn't sound like a dispatcher is a widget at all. If a dispatcher
> does not render, how can it be a widget, and how can it be in the widget
> tree?
This is a good question. I wondered about that, and I believe the only
reason for the dispatcher to be in the tree is to have hierarchical
navigation systems, where the order of consuming uri-tokens is
determined by position within the hierarchy. Instead of having a global
dispatch table you basically delegate dispatching knowledge down the
tree.
I am not entirely convinced this is the best approach -- but it is the
approach weblocks traditionally took. Django, for example, has an
entirely different take on it. I figured people would want to keep the
current approach, and as I don't have strong arguments either way, left
it that way.
I don't think rendering has much to do with it, though -- we have other
widgets that do not render (containers?), but serve useful purposes
within the tree.
--J.
Moving the URL parser before the render doesn't preclude dispatcher
needing to render something; it solves the core issue just as well to
compute a new selected widget and save it during the URL-parse phase,
then render said widget during the render phase.
Containers do render; you are required to come up with a render for
them.
If, and I believe it is sensible to keep this setup, a dispatcher must
be hierarchically positioned above sub-dispatchers, then they cannot be
pure leafs. Since this is the case, each dispatcher must accept at
least one child, and this might as well be the function-selected cache.
The idea of a dispatcher choosing widgets elsewhere in the tree is
certainly valuable. But this can happen anyway; it is merely a matter
of communication. For example, a dispatcher that you do not wish to
select for different widgets at that point in the tree, but at some
other arbitrary points, can adjust those locations manually, and always
return the same widget to be "selected".
This permits the sophisticated tree-disconnected-selection that we would
like, without undue complication of the simple case of a standalone
ephemeral-selecting dispatcher that is suitable for many cases. It
could not be done with the dev dispatcher (because the widget you might
like to select differently might already be rendered), but can be done
with the two-stage style.
I was referring only to dispatchers. The render function in dev always
looks for *uri-tokens*; I thought this might be nonsense in some Ajax
rendering contexts. Anyway, I have never tried it.
> I can either choose fresh generation of a widget (ephemeral) every
> time and lose AJAX updates, flows and so on or have it cached all
> the time for the whole session.
>
> I'm sure we can find something better.
Even in ephemeral mode, the cached/selected widget should only be
changed when URI stripping. This is handled by the modern-dispatching
dispatcher.
> Jan Rychter <j...@rychter.com> writes:
>> I don't think rendering has much to do with it, though -- we have other
>> widgets that do not render (containers?), but serve useful purposes
>> within the tree.
>
> Containers do render; you are required to come up with a render for
> them.
>
> If, and I believe it is sensible to keep this setup, a dispatcher must
> be hierarchically positioned above sub-dispatchers, then they cannot be
> pure leafs. Since this is the case, each dispatcher must accept at
> least one child, and this might as well be the function-selected cache.
>
> The idea of a dispatcher choosing widgets elsewhere in the tree is
> certainly valuable. But this can happen anyway; it is merely a matter
> of communication. For example, a dispatcher that you do not wish to
> select for different widgets at that point in the tree, but at some
> other arbitrary points, can adjust those locations manually, and always
> return the same widget to be "selected".
Stephen,
I believe our viewpoints are not very far apart. My main point is that I
don't think *every* dispatcher should carry the full on-demand
content-generation functionality along with caching.
I'm still working on the design, and for the moment it seems as if one
could nicely separate a dispatcher-mixin (doing uri-token processing and
not being a widget), and have an on-demand-content widget or similar
that would do what you want. A draft illustration is here:
http://screech.rychter.com/files/weblocks-new-nav-design-20081213.pdf
That way you would still have a simple way to autogenerate content, the
dispatching functionality would be abstracted away from rendering,
dispatchers would dispatch and selectors would select. And we wouldn't
have to deal with things like selector-on-dispatch,
render-dispatcher-content and render-dispatcher-decoration.
That approach would also avoid the traps you pointed out: building
pseudo-widgets that don't really render and overly complicating the
dynamic content generation case.
--J.
It turns out Stephen was right being suspicious about the dispatcher
(Stephen, thanks for your comments!). In my design the dispatcher is
gone, so is selector-mixin. A rough diagram of what things look like is
here:
http://screech.rychter.com/files/weblocks-new-nav-design-20081214-2.pdf
Major points:
* The dispatcher is gone, so is selector-mixin, and so is the whole
pane-info structure business along with its canonicalization.
* wiki-style navigation is done by on-demand-selector
* We get a fairly simple static-selector which will likely work for 99%
of all dispatching needs and won't force people to write their own
token-processing functions. Utility function for creating this
selector will be coming soon.
* As a bonus, I wrote a simple breadcrumbs widget that shows the current
position within the navigation tree.
* A navigation where you can separate the menu from the content should
be easy to implement and I'll likely do it over the next day or two.
* The make-navigation interface will certainly change to something more
useful. In particular, hidden menu items will be supported and the
syntax will be more sane. As for now, if you'd like to test, please
use
(make-navigation "Name"
(list "Item Name" (make-widget blah blah) "item-uri")
(list "Item Name2" (make-widget blah blah) "item-uri2"))
Since I found working with multiple hg branches on bitbucket
troublesome, I forked the repository (sorry about that, but I have
deadlines to meet). You can find it on github at
http://github.com/jwr/weblocks-jwr/tree/weblocks-jwr
Note there are multiple branches there -- weblocks-jwr is really a
merge between some things from modern-navigation and weblocks-dev. The
yui branch contains my (limited) YUI stuff, and the navigation-rewrite
branch contains the navigation work. I intend to work with small,
contained topic branches, as I find this works much better than huge
long-lived mega-branches which later cause mega-merges.
The code I wrote about above is in the navigation-rewrite branch on
github. I will be pushing to it regularly as I develop from now on.
Comments very welcome.
--J.
PS: I know that a description of my git workflow might be useful for
some of you:
What I do locally isn't complicated and works very well:
1. I develop on a branch called 'jwr' that gets rebased all the time on
top of pretty much everything else (weblocks-jwr, yui, and a bunch of
other local topic branches). I commit everything here.
2. From time to time, I check out 'weblocks-jwr', 'yui', or other
branches that I push to github and "git cherry-pick" the changes from
'jwr' that I think make sense for weblocks or yui.
3. From time to time, I 'git push github' which syncs weblocks-jwr and
yui branches with those on github. It also syncs any other branches that
I ever pushed to github automatically. So, github will end up with my
public branches, while my private branches stay private.
4. Go back to step 1. The nice thing about this is that git notices the
changesets that were cherry-picked into another branch and just silently
reorders things. I don't get conflicts or even warnings about duplicate
changesets. This is beautiful.
# git clone git://github.com/jwr/weblocks-jwr.git
Initialized empty Git repository in /private/tmp/weblocks-jwr/.git/
remote: Counting objects: 8505, done.
remote: Compressing objects: 100% (2497/2497), done.
remote: Total 8505 (delta 5969), reused 8377 (delta 5883)
Receiving objects: 100% (8505/8505), 2.03 MiB | 263 KiB/s, done.
Resolving deltas: 100% (5969/5969), done.
warning: remote HEAD refers to nonexistent ref, unable to checkout.
(this is probably because I don't have a default "master" branch, I think)
# git checkout -b my-branch origin/navigation-rewrite
warning: You appear to be on a branch yet to be born.
warning: Forcing checkout of origin/navigation-rewrite.
Branch my-branch set up to track remote branch refs/remotes/origin/navigation-rewrite.
Switched to a new branch "my-branch"
At this point you'll have all the stuff ready to play with. You can also
do a git rebase origin/weblocks-jwr to rebase your branch on top of
anything that is in weblocks-jwr as well.
You can see there are many remote branches:
# git branch -r
origin/navigation-rewrite
origin/weblocks-jwr
origin/yui
--J.
I really like this. Some points:
* I don't think it should subclass `container'. See how I've stripped
down `container.lisp' in [1]. Container seems to be becoming a
type-tag (as it is for selector), leading webapp authors down the
path of indiscriminately spraying it on their widget trees; this is
part of my motivation for removing CUC/CUDC in favor of widget tree
walking.
* I'm not great at naming, but shouldn't the name of `selector' reflect
that it is related to URI parsing? If `selector' communicates this
effectively, ignore this point.
* Assuming CUC and CUDC are abandoned as I suggest in [1], and replaced
with the walk-widgets/update-uri-tokens semantics implemented there,
it ought to be specified that all you need to have a selector widget
is to implement get-widget-for-tokens and update-uri-tokens for your
widget class, and subclassing selector is unnecessary (although
convenient, as selector has a reasonable update-uri-tokens
implementation).
* Question: Is a selector required to render its selected widget?
> 4. Go back to step 1. The nice thing about this is that git notices the
> changesets that were cherry-picked into another branch and just silently
> reorders things. I don't get conflicts or even warnings about duplicate
> changesets. This is beautiful.
I am very much against changing VCS again.
¹ http://bitbucket.org/S11001001/weblocks-modern-dispatching/
+1. While I do believe that Mercurial has to catch up with
git in some areas the overhead of switching would far outweigh
the benefits.
And hg has still the edge in newbie-friendliness and (IME)
speed.
Leslie
I try to have a very pragmatic approach to tools. If something works,
I'll use it, until I find something that suits my needs better.
In the weblocks/hg case, we know that darcs did not work. Hg was
chosen. I gave hg a good try and I believe it doesn't work well. Here's
why:
-- each branch is effectively a separate tree, which makes branching
artificially expensive. Also, once I clone a branch, I know of no
way to visualize where it split from weblocks-dev other than
tracking the last common changeset manually.
-- you can't maintain small topic branches effectively,
-- as a result, weblocks development has split into multiple huge
forks, instead of tiny topic branches. These mix a lot of
functionality in them and are very difficult to pick apart. It took
me a long time to pick what I needed from Leslie's
modern-dispatching fork and it wasn't a happy experience,
-- trying to co-maintain large branches alongside -dev results either
in them diverging or in a gazillion of merge commits which makes
reading history difficult,
-- last, but not least, I do not have a good visualization tool for hg
on a Mac and I haven't found an easy way to show just one changeset
diff in hg (git show does this). But that's probably just me being
stupid.
> And hg has still the edge in newbie-friendliness and (IME)
> speed.
I disagree with both, I believe the newbie-unfriendliness of git is a
myth. See http://whygitisbetterthanx.com/#easy-to-learn for a comparison
of git and hg commands. I really don't see what is easier or more
difficult with either of them. Possible differences speed are negligible
and usually in favor of git.
As to weblocks, the fact is, instead of small topic branches with
well-defined scope we ended up with long-lived large forks. I do not
think this is good, and more importantly it makes my life difficult on a
very practical level (meeting development deadlines). That is why I
decided to maintain a separate repository with a bunch of topic
branches, which git makes much easier.
Also, I do not buy the story about "the overhead of switching". I do not
see where the overhead is. In fact, apart from learning how a new VCS
works (which takes all of 10 minutes for VCS out there) there is no
overhead to speak of. We could be switching VCS systems every week and
we wouldn't really notice.
However. I do not intend to convince anyone to switch to anything. To
put it in other words: may ketchup users live long and prosper, while us
mustard users are happy with our mustard. Whatever works for you. After
all, there is no harm in exchanging patches as patches, instead of that
all-fancy pull/fetch/push thing in every VCS out there. As for me, for
the time being I will *have* to keep working with git not because I like
it more, but simply because I am unable to do the same with Mercurial.
--J.
Yes, that's my approach, too.
I also use git for other projects.
> In the weblocks/hg case, we know that darcs did not work. Hg was
> chosen. I gave hg a good try and I believe it doesn't work well. Here's
> why:
>
> -- each branch is effectively a separate tree, which makes branching
> artificially expensive. Also, once I clone a branch, I know of no
> way to visualize where it split from weblocks-dev other than
> tracking the last common changeset manually.
>
> -- you can't maintain small topic branches effectively,
Recent work for hg (pbranch, localbranches, bookmarks) has
offered new possibilities that are very similar to git
branches.
They have to evolve another little bit, but I think in the
mid-term it won't be a problem to use these facilities.
> -- as a result, weblocks development has split into multiple huge
> forks, instead of tiny topic branches. These mix a lot of
> functionality in them and are very difficult to pick apart. It took
> me a long time to pick what I needed from Leslie's
> modern-dispatching fork and it wasn't a happy experience,
Yes, we could do better here.
> -- trying to co-maintain large branches alongside -dev results either
> in them diverging or in a gazillion of merge commits which makes
> reading history difficult,
The "rebase" extension that is part of hg 1.1 should help here, too.
> -- last, but not least, I do not have a good visualization tool for hg
> on a Mac and I haven't found an easy way to show just one changeset
> diff in hg (git show does this). But that's probably just me being
> stupid.
hg diff -r tip:-2
> I disagree with both, I believe the newbie-unfriendliness of git is a
> myth. See http://whygitisbetterthanx.com/#easy-to-learn for a comparison
> of git and hg commands. I really don't see what is easier or more
> difficult with either of them.
Probably a matter of taste...
> Possible differences speed are negligible and usually in favor of git.
Not for me. I can definitely confirm git thrashing my disk more
and taking longer for simple operations like diff and log.
After "git gc", that is.
> Also, I do not buy the story about "the overhead of switching". I do not
> see where the overhead is. In fact, apart from learning how a new VCS
> works (which takes all of 10 minutes for VCS out there) there is no
> overhead to speak of. We could be switching VCS systems every week and
> we wouldn't really notice.
Well. Updating all the URLs, moving the whole shebang to Github
and everyone else would have to convert their repos.
It's a certain overhead that would surely take more than a few
minutes.
> However. I do not intend to convince anyone to switch to anything. To
> put it in other words: may ketchup users live long and prosper, while us
> mustard users are happy with our mustard. Whatever works for you. After
> all, there is no harm in exchanging patches as patches, instead of that
> all-fancy pull/fetch/push thing in every VCS out there. As for me, for
> the time being I will *have* to keep working with git not because I like
> it more, but simply because I am unable to do the same with Mercurial.
We need to devise a better development model, that's definitely
true.
I will try to come up with something.
> Jan Rychter <jan-JAsPCFd0e...@public.gmane.org> writes:
>> Ok, I have the first cut of the new nav design, it works for my
>> application so far. Dynamic wiki-style on-demand navigation is untested.
>>
>> It turns out Stephen was right being suspicious about the dispatcher
>> (Stephen, thanks for your comments!). In my design the dispatcher is
>> gone, so is selector-mixin. A rough diagram of what things look like is
>> here:
>> http://screech.rychter.com/files/weblocks-new-nav-design-20081214-2.pdf
>
> I really like this. Some points:
>
> * I don't think it should subclass `container'. See how I've stripped
> down `container.lisp' in [1]. Container seems to be becoming a
> type-tag (as it is for selector), leading webapp authors down the
> path of indiscriminately spraying it on their widget trees; this is
> part of my motivation for removing CUC/CUDC in favor of widget tree
> walking.
Thanks for your comments. As I understand it, the only purpose of the
container is to keep a list of children, e.g. maintain tree structure
for non-leaf nodes. If you remove it, where do you keep the list of
children? Do we make all widgets keep that list?
If we make all widgets keep a list of children, I'm all for removing the
container. I just wasn't sure whether we want to carry that baggage
around.
> * I'm not great at naming, but shouldn't the name of `selector' reflect
> that it is related to URI parsing? If `selector' communicates this
> effectively, ignore this point.
I'm a naming nut, I believe naming is very important, especially now
that we read code more than we write code. See how I'm going around
renaming things like widget-current-navigation-url to widget-uri-path.
But in this case, I think the selector name is right. Its role is to
provide a selector (switch) in the tree, that selects something among
several widgets. It does so based on uri-tokens (for now), but it
doesn't have to be the only way.
But -- if you get rid of it, you might as well put everything into
'widget'. Keep a list of children there, along with a marker for the
"active" child.
> * Assuming CUC and CUDC are abandoned as I suggest in [1], and replaced
> with the walk-widgets/update-uri-tokens semantics implemented there,
> it ought to be specified that all you need to have a selector widget
> is to implement get-widget-for-tokens and update-uri-tokens for your
> widget class, and subclassing selector is unnecessary (although
> convenient, as selector has a reasonable update-uri-tokens
> implementation).
I can't comment on the walk-widgets thing as I haven't seen it yet...
I really wanted to get rid of the selector class, but could not. In fact
it was gone at one point, but came back, because of
container-update-direct-children. It represents an interface between
widget tree updating and the get-widget-for-tokens method, and that
interface is common to all selectors. Some implement a static mapping
(static-selector), some export the get-widget-for-tokens externally
(on-demand-selector).
I believe there should be a single place where we call the mapping
function, deal with widgets that are created (plug them into the tree)
and maintain the current url. I do not want to have url-building and
tree-maintaining code all over the place, that way lies madness. Hence
the container-update-direct-children and update-dependents functions in
selector.lisp on my navigation-rewrite branch.
I think we are talking about very similar things. I am not at all
attached to selector or container being there if there is a more
sensible place for their functionality.
(I am, however, against using generators, as I described elsewhere)
> * Question: Is a selector required to render its selected widget?
A good question (as usually) :-)
I am not sure about the answer. I will be once I finish implementing the
split navigation, where a descendant of static-selector will render the
menu, but will also select a widget elsewhere in the tree. For the
moment, I *think* the answer is "yes", as both selectors in that case
will render their widgets (one is a menu and the other is the content).
--J.
It is not quite the same. Non-containers can have and render children;
this is what MWPW is for. From a refactoring I did yesterday (uses
SERIES, note this is in a project with a completely different
`walk-widgets' interface):
(defwidget pseudo-composite ()
()
(:documentation "Instead of leaving things in a list, put them in
slots and define a method on pseudo-composite-slot-names."))
(defgeneric pseudo-composite-slot-names (pc)
(:method-combination append)
(:documentation "Calculate the slots that hold widgets. When
unbound or nil, they are assumed to be nonexistent."))
(defun scan-pc-slot-names (pc)
(declare (optimizable-series-function))
(scan 'list (pseudo-composite-slot-names pc)))
(defun scan-pc-children (pc)
"Answer a series of child widgets to be rendered."
(declare (optimizable-series-function))
(choose (mapping ((slot (scan-pc-slot-names pc)))
(slot-get pc slot))))
(defmethod walk-widgets progn (proc (self pseudo-composite))
(iterate ((subwij (scan-pc-children self)))
(walk-widgets proc subwij)))
(defmethod weblocks:render-widget-body ((self pseudo-composite) &key &allow-other-keys)
"Render each pc-child in order."
(iterate ((subwij (scan-pc-children self)))
(weblocks:render-widget subwij)))
(defmethod weblocks:make-widget-place-writer ((container pseudo-composite) subwidget)
"Linear search among subwidgets."
(find-for-widget-place-writer
container subwidget
(mapping ((slot (scan-pc-slot-names container)))
(make-slot-widget-place container slot))))
Pseudo-composite is probably a terrible name for this :)
> If we make all widgets keep a list of children, I'm all for removing the
> container. I just wasn't sure whether we want to carry that baggage
> around.
One of the things I do is rename `container-children' to
`widget-children'. However, the intention is not that all children must
be answered from that method; that is what `generate-subwidgets' is
for. Instead, `widget-children' is *only* for a settable list of such
children, and has no backing slot on widget; all container still
provides is a slot with :accessor and an MWPW method.
> I really wanted to get rid of the selector class, but could not. In fact
> it was gone at one point, but came back, because of
> container-update-direct-children. It represents an interface between
> widget tree updating and the get-widget-for-tokens method, and that
> interface is common to all selectors.
Which I do differently, without CUC and CUDC, with the help of widget
tree walking.
>> * Question: Is a selector required to render its selected widget?
>
> I *think* the answer is "yes", as both selectors in that case
> will render their widgets (one is a menu and the other is the
> content).
Actually, I think this makes the answer "no", because the menu one
*doesn't* render the selected widget.
Anyway, if the answer is yes, I think the cache ought to mve back into
selector, since for all selectors there is the notion of a selected and
to-be-rendered widget.
Please say you are not planning to make weblocks use SERIES...
(Yes, I do know what SERIES is, in fact I have used it in the past, and
more importantly, debugged software that used it, which influences my
perception of it significantly)
>> If we make all widgets keep a list of children, I'm all for removing the
>> container. I just wasn't sure whether we want to carry that baggage
>> around.
>
> One of the things I do is rename `container-children' to
> `widget-children'. However, the intention is not that all children must
> be answered from that method; that is what `generate-subwidgets' is
> for. Instead, `widget-children' is *only* for a settable list of such
> children, and has no backing slot on widget; all container still
> provides is a slot with :accessor and an MWPW method.
>
>> I really wanted to get rid of the selector class, but could not. In fact
>> it was gone at one point, but came back, because of
>> container-update-direct-children. It represents an interface between
>> widget tree updating and the get-widget-for-tokens method, and that
>> interface is common to all selectors.
>
> Which I do differently, without CUC and CUDC, with the help of widget
> tree walking.
Having just looked at your changes:
I like the walk-widgets function. But even though it looks simpler than
my two tree-walking methods, you still need to provide it with a
function (or a method) that will be able to deal with all our widget
types (widgets, strings, functions, and symbols I think).
What I don't understand about your design is why you keep juggling the
children list as if pretending it isn't stored anywhere. It does need to
be stored somewhere. We can avoid having a slot for it, instead
specifying a method, but the implementor will need to get the list of
subwidgets from *somewhere*. We can juggle all we want, but the
information has to be stored by someone at some point. And if it is, we
might as well provide a place for it, along with a suitable
abstraction. Am I missing something here?
Plus, I am still very much against using generators. It's not like we'll
have infinite lists of widgets and need lazy evaluation, so generators
buy us no benefit that I can see, while introducing additional
complexity.
On a related note, I think the update-uri-tokens function is
misnamed. It doesn't update any tokens. What it does is
(in your tree, according to the docstring):
"Looks up and returns the widget in the cache based on the tokens."
in my tree it is called 'get-widget-for-tokens', which I think is a much
more appropriate name. It gets you a widget for given tokens. And I
don't think caching has anything to do with the specification, it's an
implementation issue.
>>> * Question: Is a selector required to render its selected widget?
>>
>> I *think* the answer is "yes", as both selectors in that case
>> will render their widgets (one is a menu and the other is the
>> content).
>
> Actually, I think this makes the answer "no", because the menu one
> *doesn't* render the selected widget.
I will be implementing a menu whose menu items will be widgets, so in
this case it will select a widget. But not all menus will, so I don't
think this is a requirement.
> Anyway, if the answer is yes, I think the cache ought to mve back into
> selector, since for all selectors there is the notion of a selected and
> to-be-rendered widget.
Even if the answer were yes, I would not agree -- because then all
selectors would have a notion of a selected widget, not of a widget
cache with all its management. That's a significant difference. In 99%
of my selecting needs I don't use a cache.
--J.
Not even a little. But it might end up required by my contribs, because
I really like the little MWPW helper defined for that code.
> I like the walk-widgets function. But even though it looks simpler than
> my two tree-walking methods, you still need to provide it with a
> function (or a method) that will be able to deal with all our widget
> types (widgets, strings, functions, and symbols I think).
For the simple widgetp case, this is straightforward.
For generic functions, I defined something to help users write new
universal-widget operating GFs in dev, but it only works in Clozure
thanks to my ignorance of the semantics of method-functions.
> What I don't understand about your design is why you keep juggling the
> children list as if pretending it isn't stored anywhere. It does need to
> be stored somewhere. We can avoid having a slot for it, instead
> specifying a method, but the implementor will need to get the list of
> subwidgets from *somewhere*. We can juggle all we want, but the
> information has to be stored by someone at some point. And if it is, we
> might as well provide a place for it, along with a suitable
> abstraction. Am I missing something here?
Yes, I have discussed this previously in
http://groups.google.com/group/weblocks/msg/0e5993f23843b7b8 , as well
as elsewhere in that thread.
There are two notions at work here:
* The *settable* list of subwidgets, defined by widget-children. This
is important because these are *not composable*.
* A superset of this list is the widget-intrinsic list of subwidgets.
This doesn't affect rendering, and is only there to give the tree a
full structure. In my modern-dispatching, this is implemented with
generate-subwidgets. In HFSBO and that post above, it is implemented
using `walk-widgets' (not to be confused with the pure-function
walk-widgets in my modern-dispatching).
Also, MWPW operates on this list, not the first one.
Let us say that you define two widgets that hold onto 3 subwidgets each
to do their work. Then you wish to compose them with multiple
inheritence. If you do (setf widget-children) in initialize-instance,
you lose the 3 from one of them depending on CPL order.
If you append to (setf widget-children), the order depends on CPL, so
when trying to access a subwidget, each widget's methods must be
type-sensitive (and hope their types don't clash with the other mixins)
or understand that mixing-in may alter the list positions of "it's"
subwidgets.
If you track by both appending to (setf widget-children) and storing in
a slot, you must take care to follow every change to a child-holding
slot with a SUBSTITUTE on and (setf widget-children).
There are many circumstances where altering a list is even more
error-prone than this. The big widget on
http://www.hfsbo.com/services-and-pricing is a listedit with:
(defmethod walk-widgets progn (proc (self available-contract-browser))
"Include all current interval browsers."
(maphash #L(walk-widgets proc !2) (slot-value self 'interval-browsers)))
Without the above method, the interval-browsers (the little gridedits
below each item) could not be altered when authentication information
changes. Interval-browsers is, incidentally, lazily updated by a
render-object-view-impl method that is also responsible for rendering.
If I had to store these in a separate list, I would have to be sensitive
to pagination myself, instead of letting whoever calls ROVI figure it
out for me. The available-contract-browser, incidentally, *does* use
subwidget composition; the ACB method walks into the interval browsers,
and the dataedit-mixin walks into the dataedit-item-widget if it is
present.
So the list of subwidgets (the second notion) *isn't* stored anywhere,
though of course the subwidgets themselves are, but it *is* accessible
to all code via `generate-subwidgets', or rather, whatever replaces
that.
On a side note, this is another naming problem. I have been calling the
first notion "children" and the second "subwidgets".
> Plus, I am still very much against using generators. It's not like we'll
> have infinite lists of widgets and need lazy evaluation, so generators
> buy us no benefit that I can see, while introducing additional
> complexity.
I was more thinking of the cost of repeated `append' operations as
multiple users walk the widget tree in code. If you think this cost
will be insignificant, then it probably will be.
> Even if the answer were yes, I would not agree -- because then all
> selectors would have a notion of a selected widget, not of a widget
> cache with all its management. That's a significant difference. In 99%
> of my selecting needs I don't use a cache.
You are probably right, but I would like to see one more thing: outside
a request, when the URI is no longer available, how would code with a
reference to such a selector determine which is the selected widget,
without having specific knowledge of what subclass of selector it is?
I forked dev where you seemed to fork it, cherried the bits of
modern-dispatching you have (in particular, and this is very important,
reproducing "Fix compile on Clozure by moving container-update-children
defgeneric earlier"'s move to widget.lisp instead of earlier in the same
file), skipped past the merges of presentation-related features, and
then everything up to "add missing parentheses in weblocks.asd".
Even though the conflicts were mostly related to CUC and CUDC (with
respect to said Clozure patch), and I want to remove those GFs entirely
anyway, I tried to faithfully reproduce all changes. Some things like
"reintroduce composite's make-widget-place-writer method lost during
merges" were left out, as I didn't experience this lossage.
It's on http://www.bitbucket.org/S11001001/weblocks-nav4/ .
Oh -- I messed up that patch. I will fix that in my tree as well.
As to navigation-rewrite, the more I think about it (and about what you
wrote in the thread recently, your comments are very valuable), the more
I see it needs to be redone again. I didn't get it right this time and I
need to think about it some more.
In particular, if you think about how the current navigation works, you
realize that it has serious limitations -- for example, it uses dynamic
variables, so if you want to do something based on subsequent url
tokens, you need your selector widgets to be on a depth-first traversal
tree path. You can't have one part of the tree affect another, and then
place a selector in that second branch that reacts to subsequent url
tokens. That is a problem for example if you want flexible navigation
with detachable menus, which is what I need.
--J.
This is a manifestation of using CUC and CUDC. Eliminating these solves
the problem, by letting you pass a closure to WALK-WIDGETS or something
instead of just plonking a hodgepodge of methods onto a GF and hoping
the others cooperate.
> You can't have one part of the tree affect another, and then
> place a selector in that second branch that reacts to subsequent url
> tokens. That is a problem for example if you want flexible navigation
> with detachable menus, which is what I need.
Any solution that would remove the depth-first limitation would also
make a new tree and either ignore or overcomplicate flows, so given the
philosophy of "URL as entry-point", I think to solve this you should
just add another phase that is just a function someone can plug in to
tweak the widget tree right. Maybe this phase already exists.
Even now we can slightly alter this; BFS is almost as easy to implement
on top of whatever generate-subwidgets becomes as DFS.
> Jan Rychter <j...@rychter.com> writes:
>> In particular, if you think about how the current navigation works, you
>> realize that it has serious limitations -- for example, it uses dynamic
>> variables
>
> This is a manifestation of using CUC and CUDC. Eliminating these solves
> the problem, by letting you pass a closure to WALK-WIDGETS or something
> instead of just plonking a hodgepodge of methods onto a GF and hoping
> the others cooperate.
Yes and no. While I do agree that something like walk-widgets is needed,
I feel that the right approach is a combination of a functional approach
with reasonable use of CLOS.
--J.
> Jan Rychter <j...@rychter.com> writes:
>> In particular, if you think about how the current navigation works, you
>> realize that it has serious limitations -- for example, it uses dynamic
>> variables
>
> This is a manifestation of using CUC and CUDC. Eliminating these solves
> the problem, by letting you pass a closure to WALK-WIDGETS or something
> instead of just plonking a hodgepodge of methods onto a GF and hoping
> the others cooperate.
Here's a progress report (detailed changesets are at
http://github.com/jwr/weblocks-jwr/tree/navigation-rewrite):
-- killed container-update-direct-children and container-update-children
-- introduced a walk-widget-tree method
-- killed *current-navigation-url*, widget-uri-path (formerly
widget-current-navigation-url), *uri-tokens-fully-consumed*
-- *uri-tokens* is now an object that holds all tokens, remaining
tokens, consumed tokens, and has a bunch of methods implemented.
-- only selectors now know their base-uri, because only they can
meaningfully relate to it in any way
The hodgepodge of methods you refer to is something I'd call a protocol
and I believe is still required to a certain extent. For example, here's
what gets called when handling requests:
(defun update-widget-tree ()
(walk-widget-tree (root-composite) #'update-children))
Those widgets that wish to dynamically update their children will
implement the method. For the moment, all selectors do so. I do not
think a method is a bad solution here. Even if you want to juggle your
children without holding them in a slot somewhere, you should let others
store them and update as needed.
And really, update-children could be called
modify-state-based-on-uri-tokens, because that's really what it is
supposed to do.
The jury is still out on the container.
This is all a work in progress, e.g. it will most certainly change.
--J.
The problem is with methods that are required to handle both the
updating and the descent part. The only way to keep one from randomly
screwing up descent, especially in the case of combination, is to take
away that responsibility.
> (defun update-widget-tree ()
> (walk-widget-tree (root-composite) #'update-children))
This is what I was suggesting.
I will be merging this branch into my github weblocks-jwr branch soon --
I have been using it for a while and it works very well for me. All of
my current work is based on it.
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
-- breadcrumbs widget
-- 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
Altogether, this let me implement things like pageable-list (a pageable
list of widgets) or a 'commentable' mixin that makes some of my widgets
commentable just by adding it to superclasses and defining three methods
(get-comment-data, get-comment-count, make-comment). A commentable
widget is one that displays its comments, and allows for adding and
editing them. Also, I can finally have working multi-level navigation
with hidden items.
Apart from bug fixes, I consider this topic branch closed, e.g. no new
features will go onto it. My current YUI work requires this branch
anyway.
Stephen, I went with a mutable-data approach to widget-children, instead
of the functional approach you were suggesting. I do think your design
is nicer, but I think it is much more difficult to debug and tough for
newcomers.
--J.
And another feature I forgot (now pushed) -- lazy-navigation. It is a
widget that is identical to navigation, but 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.
--J.
Well, what I did with my code was almost a search-replace, you basically
need to:
-- 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)
it isn't that hard. I don't think a compatibility interface is possible,
because you simply can't (setf widget-children) anymore without breaking
things.
What I initially wanted was to have a (setf (my-children x) children)
and (my-children x) thing, which would automatically determine the type
of x and use it as a key. It might be possible to do and it would make
the interface somewhat nicer -- but I'd still want to keep the
{get|set}-children-of-type methods, because I have widgets that maintain
two or three lists of children. As an example, a "commentable" mixin
widget maintains a comment bar with a comment count and an action link
and a pageable list of comments. The comment bar is on a
:commentable-comment-bar list -- so that it can be replaced by an editor
when a "comment this" action is initiated. That's just immensely
useful. Same for rendering, it is much easier to lay things out without
having a big bag of all children all mixed up.
>> -- you can render navigation content separately from the menu (teleport hack)
>
> It's a hack?
Yes, I consider it a hack, but I don't see a way to do it any
better. The problem is that the source containing widget has to know
that it isn't supposed to render its contents and that you're basically
cheating the tree structure -- if you have multiple navigation widgets
this might be a problem.
This solves my immediate problem, but one could certainly implement a
real split navigation where one part would influence another. I just
couldn't justify the effort involved.
>> -- you can't (setf widget-children) anymore, see set-children-of-type
>
> Looks more complicated that it needs to be. Why not a mandatory
> type argument to (setf widget-children) ?
You can supply arguments to (setf) functions?
>> -- anyone who uses set-children-of-type can participate in flows as a parent
> How does this relate to make-widget-place-writer?
There is one make-widget-place-writer which will work for everyone that
maintains their children using set-children-of-type. If you don't stick
you children there, you're on your own and you need your own
make-widget-place-writer. But I don't see why you'd want to do that
anymore.
--J.
Well, you can't really compare the two, because as I understand it,
map-subwidgets serves a single purpose: enumerating subwidgets and
mapping a function onto them. The whole {get|set}-children-of-type
implementation does a bit more than that.
There are three reasons why I chose to go this way:
1. Debuggability: It is much easier to debug and understand variables
than method combinations. This means that you can easily insert a
(break) somewhere in your code and just inspect what this particular
widget's children are. You can't easily do that using the map-subwidgets
approach.
2. Rendering order control: I have cases where I want to control
rendering order of all children precisely, in one place. I do not want
to juggle :before, :after and :around methods on render-widget-body and
render-widget-children, I just want to override one method and render
all children. As an example, I have an "event-story" widget that
inherits from a number of other widgets, each providing children
(commentable is one of them). It is natural to render everything in the
proper order in one place.
3. Children storage and flows: This approach solves the children storage
problem for everyone, and provides a default make-widget-place-writer
method. This means that you will very rarely need to write your own,
unless you do something strange.
I think map-subwidgets is more elegant, but less practical.
Also, I would be interested to know what it is you find hard to use
about {get|set}-children-of-type?
--J.
Okay, but see response to #3 below.
> 2. Rendering order control: I have cases where I want to control
> rendering order of all children precisely, in one place. I do not want
> to juggle :before, :after and :around methods on render-widget-body and
> render-widget-children, I just want to override one method and render
> all children. As an example, I have an "event-story" widget that
> inherits from a number of other widgets, each providing children
> (commentable is one of them). It is natural to render everything in the
> proper order in one place.
As seen with HFSBO services-and-pricing, where rendering subwidgets is
intermixed with rendering object views, this only hinders you when doing
anything but the most basic one-widget-after-another rendering. It
would be less-than-ideal to require inheritors of this widget to
reimplement its particular rendering logic.
> 3. Children storage and flows: This approach solves the children storage
> problem for everyone, and provides a default make-widget-place-writer
> method. This means that you will very rarely need to write your own,
> unless you do something strange.
It doesn't when storing children in lists is inconvenient. In my case,
I frequently store children in separate slots, or in the case of
services-and-pricing, in a fast-changing hash table. There are *no*
cases where I've defined a widget that stores children in lists; it's
never been the proper representation for me.
This also shows why #1 isn't an issue. You can see these slots, and
drill into the hash table.
--
Sorry but you say Nibiru is a Hoax? Doesnt Exist? So maybe The
Sumerian people doesnt exist also! --Anonymous by way of SkI
I disagree -- but I think we have different use cases, so it is
difficult for us to understand each other's motivation.
In the "commentable" widget case for example, inheritors do not have to
do anything. They can rely on the built-in render-widget-body and
render-widget-children methods and their combinations, which will
usually get things right. However, if they need to, they may override
the methods and compose things differently, having a uniform interface
to the children of each type (and there may be multiple types per
superclass).
I don't see my solution hindering anything, but I do see it being
helpful in a number of cases in my current application.
>> 3. Children storage and flows: This approach solves the children storage
>> problem for everyone, and provides a default make-widget-place-writer
>> method. This means that you will very rarely need to write your own,
>> unless you do something strange.
>
> It doesn't when storing children in lists is inconvenient. In my case,
> I frequently store children in separate slots, or in the case of
> services-and-pricing, in a fast-changing hash table. There are *no*
> cases where I've defined a widget that stores children in lists; it's
> never been the proper representation for me.
But then you have to provide a make-widget-place-writer method, right?
If you do not, your widgets aren't future-proof -- they aren't ready to
deal with flows.
I like the fact that I can store my children anywhere I want, then call
set-children-of-type and know that any of them can now be replaced in
flows, if only my render methods stick to using get-children-of-type.
I've found this to be very useful.
> This also shows why #1 isn't an issue. You can see these slots, and
> drill into the hash table.
Yes, I also store widgets in slots sometimes. However, I found that
sometimes I use slots (comment-bar), sometimes slots with lists
(comments), and providing make-widget-place-writer methods in
appropriate places was rapidly becoming cumbersome. And really, you
don't even have to notice that set-children-of-type deals with lists,
because it also accepts a single widget. So think of it as a storage
location, not necessarily a list.
As for debugging, I realize it's subjective. I find it very helpful to
be able to see at a glance what ALL the children of a particular widget
are.
--J.
Could be done -- but I'd rather first finish the discussion with
Stephen, as his approach might turn out to be better and the whole issue
might become moot :-)
[...]
> Yeah, let's worry about it later.
>
>> You can supply arguments to (setf) functions?
>
> Works fine around here. ;)
Oh -- the 'place' form can take optional arguments -- I didn't know
that. That could indeed be used.
> How about the tests? Have you written tests for your new nav system
> and/or adapted the current tests?
No, of course not -- there is no point unless we agree on the general
design. If I spend time on fixing/writing tests now, and my design never
gains any acceptance, the effort would be wasted.
--J.
The correct answer is "maybe", I'm afraid. I do not know if I'll be able
to dedicate the time necessary to do so.
--J.
Well, don't worry too much about it. :)
I do not usually endorse this (i.e. sending in work without tests)
but the quality of your work is a good excuse for that, plus
the prospect of you investing that time in a new view design.
Cheers,
Leslie
--
LinkedIn Profile: http://www.linkedin.com/in/polzer
Xing Profile: https://www.xing.com/profile/LeslieP_Polzer
Blog: http://blog.viridian-project.de/
> On Jan 20, 10:36 am, Jan Rychter <j...@rychter.com> wrote:
>> [...] I'd rather first finish the discussion with
>> Stephen, as his approach might turn out to be better and the whole issue
>> might become moot :-)
>
> So anyone of you still got to say something you haven't so far?
I don't -- I'm using my code, I'm not that happy with it (could
definitely be improved) but it does the job for now. I didn't discover
anything better. Plus, I have very little time for pure weblocks work
now, so it is unlikely I will do much more in the near future.
> If not then I'd like to merge this in and we can work off that.
>
> No use fussing too much over API issues.
Oh, I think we should fuss a lot over API issues, but it should not stop
us from developing and trying stuff out. I never managed to design
anything right the first time, always had to write things, start using
them, and then discover a better way.
--J.
It's rather the other way around -- map-subwidgets (as I understand it)
is an interface that lets you do things with your subwidgets.
{get|set}-children-of-type is a different solution to the same problem,
which (while admittedly being less elegant) also provides
make-widget-place-writer functionality.
--J.