I have an idea that I would like to put out for discussion. The idea is to create a new function called drupal_element() which would complement the functionality of drupal_attributes(). For example, we currently do the following:
$output .= '<li' . drupal_attributes($attributes_1) . '>' . $contents . '</li>';
$output .= '<input' . drupal_attributes($attributes_2) . ' />';
With drupal_element(), we would do this:
$output .= drupal_element('li', $attributes_1) . $contents . '</li>';
$output .= drupal_element('input', $attributes_2, true);
Code for drupal_element():
function drupal_element($name, $attributes = array(), $close = false) {
$ret = '<' . $name . drupal_attributes($attributes);
if ($close) {
$ret .= ' /';
}
$ret .= '>';
return $ret;
}
This simple function shortens the code in the examples and reduces the likelihood of errors.
Taking this a step further, drupal_element() could push the name of each element it opens onto a stack. Another function, drupal_element_close(), could pop the name and return the close tag. The first example would look as follows (in the second example, it would not push the name):
$output .= drupal_element('li', $attributes_1) . $contents . drupal_element_close();
Another possible extension would see drupal_element() take care of the contents, checking if the third argument is a string and, if so, return the complete element with open tag, content, and close tag:
$output .= drupal_element('li', $attributes_1, $contents);
I am interested to hear any thoughts on this idea.
| Comment | File | Size | Author |
|---|---|---|---|
| #32 | drupal_element.patch | 21.7 KB | liam morland |
| #30 | drupal_element.patch | 20.23 KB | liam morland |
| #29 | drupal_element.patch | 21.61 KB | liam morland |
| #28 | drupal_element.patch | 22.16 KB | liam morland |
| #27 | drupal_element.patch | 22 KB | liam morland |
Comments
Comment #1
mgiffordI do think this could fit into the Butler project from what I know of it - http://groups.drupal.org/wscci
Certainly worth mentioning there. This level of abstraction could possibly allow substitutions that would be useful.
What would be the performance cost of introducing this new function & running thousands and thousands of lines through it? There must be a pretty simple way to test this with enough randomized sets of data that we could determine this with some accuracy.
I don't know if with the use of SimpleTest that there are that many errors produced. I think I'd prefer the 3rd of your options. Just seems shorter & more concise.
I do wonder how this would constrain the development process. Also, we'd have to think about how this fits with the nesting of html.
There is no way to organize spacing or produce more readable HTML at least not easily.
I'm not at all opposed to doing this but also see that it would be hundreds of hours of work to complete. If it fits into the Butler project or gives us enhancements in performance however it may well be worth doing.
Comment #2
liam morlandThanks, Mike. I had a look at Butler/WSCCI. What I am proposing is a much simpler change. It's not a ground-breaking improvement, just a tool to make life easier for developers. I don't think it would make a performance difference since the places where this would be used are calling drupal_attributes() anyway. My suggestion doesn't touch HTML readability or nesting (except in idea 2, which I have not pursued).
The attached patch provides an updated version of drupal_element() and puts it to use in theme.inc, as an example of how it can be used. It wouldn't take more than a few hours to go through the rest of core and put the function to work. Have a look at the patch to see how it shortens and simplifies the code.
Here are examples of the three forms that this function can be used in. The lines in each pair are equivalent.
Open tag:
Empty element:
Non-empty element:
Comment #3
joachim commentedThis is a really good idea.
I find myself missing this in PHP from time to time -- it's been in Perl for years: http://cpansearch.perl.org/src/LDS/CGI.pm-3.29/cgi_docs.html#functionvsoo
No blank line between parameters.
I don't really like the reuse of this parameter. What if you wanted the contents of the element to be the TRUE boolean?
Could the function not actually know which elements are empty? (Are there any HTML elements that can be both empty and non-empty?)
Booleans should be in capitals: TRUE.
Use $output to be consistent with Drupal.
Powered by Dreditor.
Comment #4
liam morlandThanks for the suggestions. Here is an updated version which makes the suggested style changes.
The output has to be a string, so any such boolean would have to be cast to a string. It's much better to make the user of the function do the cast so that they control what exactly it gets cast to, "TRUE", "true", "t", "1", etc. I supposed proper function overloading would be cleaner, if PHP supported it.
Yes, but that would tie the function to XHTML. As it is, it can be used with any XML.
For compatibility reasons, always-empty elements like img need to use empty-element tags (
<img ... />) while sometime-empty elements like script must not (<script ...></script>). The code allows generating both types.It would be possible for the function to enforce restrictions on the generated code, such as only allowing certain tag names, requiring always-empty tags to be empty-element tags, restricting the permitted list of attributes, etc., but these would increase the complexity and decrease the speed of the function. It's designed to be a simple complement of drupal_attributes(), not a server-side HTML validator.
Comment #5
liam morlandSorry, use this one.
Comment #6
joachim commentedAnother niggle... I'm not sure about the best order of parameters.
How often would you want a DIV with contents and no attributes, vs how often would you want an element with attributes and either empty or just the opener?
In other words, should $content go before $attributes?
Comment #7
joachim commentedHmm and the other thing is that while I think this would be hugely useful for themers and module developers, when it comes to its use in core theme functions I think we should get some benchmarking to see how much function calls slows things down by compared to raw strings.
Comment #8
liam morlandWe could use type detection for function overloading: If the second parameter is a string, treat it as the contents and generate no attributes; if is is an array, use it as the attributes and look for the contents in the third parameter.
On the other hand, if there aren't any attributes, this function doesn't help very much, so people would probably not bother to use it.
Note that the order that the parameters are in now is the same order that they appear in if one is typing the complete tag. I think this is more intuitive.
I just ran a quick benchmark. I ran a bunch of testing module tests which use the theme system heavily. With the patch, it took 2 hours 23 min; without: 2 hours 28 min. It's not much of a difference, so I wouldn't say that the patch makes things faster, but it certainly doesn't seem to make it slower.
It might even speed things up if PHP has a bytecode cache installed since it would replace a bunch of string concatenations with a function call which could be cached. I don't know much about this, so perhaps that is not relevant. To get better numbers, someone who know more about Drupal performance would need to look at it.
In any case, this function just does a few string concatenations. A bigger performance issue would be the call to drupal_attributes(), which would happen anyway.
Comment #9
mgiffordCan we get some performance tests with the patch applied to some sample code?
Is there an automated way to replace 90% of the lines of code used here?
How would this be tagged to bring it to a more specific group of developers to review?
Comment #10
mgiffordCan we get some performance tests with the patch applied to some sample code?
Is there an automated way to replace 90% of the lines of code used here?
How would this be tagged to bring it to a more specific group of developers to review?
Comment #11
liam morlandI used regex to do most of the code changes. Attached is a patch that includes updating all of core to use drupal_element() in places where drupal_attributes is already being used.
Comment #13
liam morland#11: drupal_element.patch queued for re-testing.
Comment #15
mgiffordThat patch gives us a good idea of what the code will look like. Seems shorter & easier to read through in many instances. Nice to have cleaned out the html from many places in the code too!
Comment #16
liam morlandFixed thing book module that was causing test failures.
Comment #17
liam morlandUpdated patch rolled against the latest D8.
Comment #18
liam morlandTagging
Comment #19
eric_a commentedThe implementation here seems to behave different from the code inlined in format_xml_elements() which probably means that this one needs work, because both implementations should probably be exactly the same and then it should be possible to reuse the new function in format_xml_elements().
Comment #20
liam morlandformat_xml_elements() recursively turns an array into a series of nested elements. drupal_element() is just a simple generator for a single element. Certainly, a simple change to format_xml_elements() could make it make use of drupal_element().
format_xml_elements() runs check_plain() on the contents. drupal_element() does not on purposes since in many places it is used to wrap an element around full HTML. Besides this difference, they produce the same output.
A new version is attached, rerolled against the latest D8.
Comment #21
liam morlandUpdated patch rolled against the latest D8.
Comment #23
liam morlandSorry, fixed version.
Comment #24
liam morlandUpdated patch rolled against the latest D8.
Comment #25
liam morlandThis new version puts drupal_element() to use in format_xml_elements(), making the latter simpler but having the same effect.
Comment #26
Jeff Burnz commentedSubscribe, switching component to get maintainer feedback.
Comment #27
liam morlandUpdated patch rolled against the latest D8.
Comment #28
liam morlandUpdated patch rolled against the latest D8.
Comment #29
liam morlandUpdated patch rolled against the latest D8.
Comment #30
liam morlandUpdated patch rolled against the latest D8.
Comment #32
liam morlandUpdated patch rolled against the latest D8.
Comment #33
jenlamptonI'm not sure this is a good idea. Do people who write HTML for a living, really want another function to call that will do it for them? This is the kind of thing that will make Drupal harder to learn, and increase the barrier to entry even more. I think we really need to weigh the pros and cons of this one, it should not go in without heavy consideration.
Comment #34
jacineTotally agree with @jenlampton. This is an absolute no-go for me until there is a discussion about this on a much higher level about where we want to go with the theme layer in Drupal 8.
Comment #35
jacineWhoops. Wrong tag.
Comment #36
jenlamptontagging for learnability too
Comment #37
catchIt's also a duplicate of theme('html_tag'); which was only committed to core on the promise that no-one would use it, and would be great to remove if we can.
Comment #38
joachim commented> I'm not sure this is a good idea. Do people who write HTML for a living, really want another function to call that will do it for them?
I understand that point completely.
And yet every time I fiddle with getting my typo-prone fingers to properly close yet another tag, I think fondly of this from the days of perl:
(from http://cpansearch.perl.org/src/LDS/CGI.pm-3.29/cgi_docs.html)
Comment #39
c4rl commentedJust because one has difficulty writing HTML doesn't justify reimplementing an abstraction of HTML in Drupal. I also would like to suggest that referencing a Perl library implementation isn't exactly something I would consider forward-thinking or cutting-edge.
Abstract idealism is the enemy of adoption. HTML5 vs XHTML was a good example of this.
Comment #40
liam morlandThanks for your feedback, everyone. I understand the learnability issue created by having a Drupal-specific way of generating HTML. It seems to me that the same argument would suggest that we shouldn't have drupal_attributes() either. That's also a Drupal-specific function for writing HTML. If we are going to have drupal_attributes(), then it is a very small step for learnability to add the tag name to create drupal_element().
I see the similarity with theme_html_tag(). That has a more verbose interface; I don't think using it would shorten the code the way that drupal_element() does. I agree that they shouldn't both need to exist, unless one calls the other to allow different interfaces to the functionality.
Comment #41
c4rl commentedSo because there exists a counterargument against this feature request, which in your opinion is also a counterargument against an existing feature, the latter's existence justifies the addition of the new feature? Let's not attack a straw man.
IMHO, Drupal needs neither more new APIs nor more API inconsistency. I recommend closing this as Won't fix or status Postponed.
Comment #42
jacineAgreed. This really makes no sense. The cost is just too high.
Comment #43
liam morlandWhat I mean is that either both drupal_attributes() and drupal_element() should exist or neither should exist. The question is do we want to have an API for generating HTML or should developers do that manually? If having such an API is bad for learnability, then why do we have drupal_attributes()?
Comment #44
c4rl commentedHistorically, I believe, drupal_attributes() stems from Form API as a D.R.Y. accessory for imploding $element['#attributes'] arrays.
So, I see drupal_attributes() as an accessory to $element['#attributes'] whereas I don't see how drupal_element() is anything more than syntactic sugar. Outside of Form API, I personally don't use drupal_attributes() much when theming, and I dread the thought of tpl files turning into a bunch of
print drupal_element('div', $attributes, $content)Were I a new developer learning Drupal looking at a theme function for the first time, I would wonder "Where is the HTML?"
Comment #45
webchickNot to pile on a won't fixed issue, but just pointing out that drupal_attributes() is important and necessary for a couple of reasons:
a) It allows modules (such as RDF) to dynamically inject additional attributes into tags. This wouldn't be possible by hand-writing these attributes out one by one like we did in D5 and D6; you'd need to change the templates to manually check for additional variables being present.
b) It also deals with the sanitization of these dynamic values, which are coming from who knows where, which eliminates the risk of XSS vulnerabilities and other nasty things.
Contrast this with writing HTML in a template. In addition to the abstraction argument, there's no chance you introduce XSS vulnerabilities into your own hand-written code, and it wouldn't make sense for modules to dynamically insert more
<h2>s in your template without your prior authorization.Hope that helps.
Comment #46
liam morlandThanks everyone. I think I understand the reasoning and the approach that is being used. It's great to be part of a community which welcomes such discussion.