问题
I am currently writing my own Component base class with a whole bunch of helper methods for using the Twitter Bootstrap CSS framework (and avoiding all the boilerplate code around it). They can be used very much like the existing form helpers (like html div ...
).
E.g., the one for horizontal text fields (in a horizontal form) looks like this:
horizontalTextField: aLabel
| field |
field := WATextInputTag new
class: 'input-xlarge';
yourself.
self html div
class: 'control-group';
with: [
self html label
class: 'control-label';
with: aLabel.
self html div
class: 'controls';
with: [self html brush: field].
].
^ field.
Which I aim to be using like this when rendering a component:
(self horizontalTextField: 'Titel')
on: #title of: self article;
id: 'title'.
So, the aim is to wrap the actual text field in a few divs, but still be able to make changes to this wrapped element outside of the helper function (using the normal tag helper accessors), as shown above.
However, this does not work as the with:
method causes the wrapping divs to be serialized (aka rendered) before I return the element, which I then cannot edit anymore.
POSSIBLE SOLUTIONS:
- Actually extend the WACanvasTag subclasses used for rendering the fields and create instances of those new subclasses in my custom helpers in my component class. I would simply overwrite their rendering code for my pleasure. This would probably be the most object-oriented way to do things, but quite arduous, especially as I'd have to repeat quite a lot of code for inserting my own HTML before and after the element for every single one of these subclasses (as they already have to inherit from the Tag class which I'm trying to wrap).
- When calling the helpers, do something like
self horizontalTextField: 'Titel' with: [:field | id: 'title']
. The block would then be applied before rendering the actual text field in the helper method. This would be quite flexible, but not very pretty (syntax-wise). - Hardcode the wrapping HTML (related to this question) in my helper functions. Like this:
self html html: '<label class="control-label">
etc. Quite the hack, in a way, and not very object-oriented.
Comments? Ideas? Better suggestions?
回答1:
As you noticed, the above code does not work because the HTML Canvas emits the HTML right away. Brushes should never be stored anywhere, they are only valid a very short time. The same goes for the HTML canvas, it is very uncommon and a possible cause of bugs to store it somewhere.
The typical way to do this in Seaside is to create a helper method:
renderHorizontalLabel: aLabelRenderer andField: aFieldRenderer on: html
html div
class: 'control-group';
with: [
html label
class: 'control-label';
with: aLabelRenderable.
html div
class: 'controls';
with: aFieldRenderer ]
The nice thing about the above code is that both aLabelRenderer
and aFieldRenderer
can be any object implementing #renderOn:
(String, Number, Block, WAPresenter, WAComponent, ...).
renderContentOn: html
self
renderHorizontalLabel: 'Comment'
andField: [
html textField
value: comment;
callback: [ :value | comment := value ] ]
on: html.
self
renderHorizontalLabel: 'Acknowledge'
andField: [ self renderCheckbox: false on: html ]
on: html.
...
To generate the actual field you could create other methods that you then call from the block you pass in as aFieldRenderer
. This gives you the flexibility to arbitrarily compose the different parts. Have a look at the examples in Seaside, there are many users of this pattern.
回答2:
It looks like you want to use it like a Tag Brush, so I'd say you definitely want to go with the extending WACanvasTag approach - but only when you actually want to create a compound "tag" like in the horizontalTextField situation.
For other Bootstrap concepts - like row, container, etc, your best bet is to simply add extension methods to WAHtmlCanvas.
I've done this myself - here's my implementation of a Bootstrap NavBar tag:
WABrush subclass: #BSNavBar
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Bootstrap'
BSNavBar>>with: aBlock
super with: [
canvas div class: 'navbar'; with: [
canvas div class: 'navbar-inner'; with: [
canvas container: aBlock]]].
closed := true
And here's how I've implemented "row" -
WAHtmlCanvas>>row
^self
div class: 'row'
WAHtmlCanvas>>row: aBlock
self row with: aBlock
In addition I added extension methods to WATagBrush to support span, offset and pull-right, as well as fluid containers:
WATagBrush>>offset: anInteger
self class: 'offset', anInteger asString
WATagBrush>>beFluid
self attributeAt: #class
put: (((self attributeAt: #class ifAbsent: [^self])
copyReplaceTokens: 'container' with: 'container-fluid')
copyReplaceTokens: 'row' with: 'row-fluid')
And finally, here's an example render method that uses some of the above:
renderContentOn: html
html navBar: [
html div pullRight; with: [
self session isLoggedIn
ifTrue: [self renderUserMenuOn: html]
ifFalse: [self renderLoginBoxOn: html]]]
来源:https://stackoverflow.com/questions/10746673/write-helpers-for-wrapping-html-elements