The docs for renderable arrays - https://drupal.org/node/930760 define #markup as:
The simplest property, this simply provides a markup string for #type => 'markup'
But, as per #2012818: Remove #type 'markup' this is both inaccurate and vague about what's really going on.
A better description of what #markup does, and this is *by design* according to @tim.plunkett, so we should update the docs accordingly:
"#markup defines a string of raw HTML that will be prepended to the rendered children of this renderable array, before #theme_wrappers are called, but only if #theme is not set. If #markup is set and #type is not set then #type will be set to 'markup' to ensure any relevant defaults are loaded from element_info()."
The reason for this is that #markup is intended as a fallback/default when there is no theme implementation available to process the renderable array.
The guiding principle here is that if #theme is set then it is theme()'s responsibility to render the array 100% so we should be clearer in the docs what falls in this "100%" and what doesn't - eg., pre_prender, post_render, prefix, suffix
As well as the d.o docs needing updates, there is no mention of #markup in the docblock of the drupal_render() function itself so we should add a paragraph there explaining its intended usage.
| Comment | File | Size | Author |
|---|---|---|---|
| #33 | drupal_render_docs_do-2015307-1.patch | 6.56 KB | valentine94 |
Comments
Comment #1
jhodgdonIs this a core API documentation issue? It seems to be pointing to a documentation page on api.drupal.org. In which case you can:
a) just edit the page and fix it (preferable)
b) move this issue to the Documentation project issue queue.
Comment #2
thedavidmeister commentedComment #3
jhodgdonOK, good point. So the drupal_render() documentation currently says:
We need it to also say here that if there is a #markup property, that is concatenated as well (before the children). I'm sure there's a concise and precise/accurate way to say that. Seems like a good Novice project.
As for the on-line documentation you mentioned in the original issue here, please either file an issue in the Documentation project about that, or (preferably) edit the page and fix it. Thanks!
And note that this is Drupal 8.x only. The 7.x drupal_render() does *not* do this at all.
Comment #4
thedavidmeister commentedThat's actually not quite true. It's currently "If #theme returns an empty value", which can happen when #theme is present but not a hook that has been implemented, like suggestions provided by the Form API.
See #2012812: drupal_render() can't distinguish between empty strings from theme() and no hook being matched.
Comment #5
jhodgdonActually, it's even more complicated than that, sigh. In 8.x, the sequence is:
- #children is set to '' if it was unset prior to the call to drupal_render().
- If #theme is set and #render_children is not, set #children to the output of theme(), using #theme as the theme hook. [Note: theme() returns an empty string if the theme hook is not matched.]
- if #children is still identical to '', render the children and concatenate them; store the result in #children.
- if #theme is not set and #markup is, prepend #markup to #children.
Sigh. I'm not sure how to write that out in clear concise prose... but we may not need every technicality to get into the documentation. I think the basic idea is that if #theme hasn't been defined or the theme hook hasn't been implemented, #markup and the rendered children are concatenated as the default output.
Comment #6
thedavidmeister commentedThe whole #markup thing is cray, check out failing tests in https://drupal.org/node/2012818#comment-7515983
Yeah, that's about it - where that description falls down has already been filed as a bug with a patch waiting for review. This is fine, the main potential for weirdness in the behaviour of #markup comes from the way #type 'markup' is merged in so I think that's really important to document. The #markup attribute sets a #type of 'markup' too if it isn't already set, regardless of whether #theme being set precludes #markup from being used to render markup.
This leads to:
array A:
array('#theme' => 'foo', '#markup' => 'bar');array B:
array('#type' => 'foo', '#markup' => 'bar);Assuming that element_info() does not define #theme = #type by default for 'foo', which is a common enough situation.
Renderable array A in HEAD will have its #type set to 'markup' (as #type is not set) but #markup will not be rendered.
Renderable array B in HEAD will have its #type left as 'foo' but #markup will be rendered.
For somebody trying to implement hook_element_info_alter() for 'markup', it would be good to know a little bit about how the #type for markup is merged in.
Comment #7
jhodgdonI'm not sure exactly what you're saying in #6...
It's true that if #type is not set, and #markup is, then #type is set to "markup" in drupal_render(). And this happens before the defaults are added from hook_element_info() for the #type. That should be mentioned if it isn't already in the documentation... and it isn't... actually the documentation doesn't even say that defaults are added from hook_element_info(), and it should.
It's also true that defaults for many elements set #theme; however, in Drupal 8, there is no default #theme for markup elements. So I don't think we need to worry about this in our discussion of #markup.
This is becoming "not a novice"... Oh I see you already removed that tag. Good!
Care to make a patch?
Comment #8
thedavidmeister commentedno, I was saying that #markup sets #type to 'markup' for elements that have an unrelated #theme set, despite the fact that #theme being set means that #markup probably won't be rendered. That's the bit that's weird - I don't know if we have to explicitly spell that out but we need to be clear that #type 'markup' is merged in as a default unconditionally when #markup is set so somebody has a chance to realise that the defaults from #type 'markup' will be there whether or not #markup is used in the final render without poking through the code of drupal_render().
Anyway, I'm happy to make a patch. Not today though, but I'm watching this issue so I'll get to it sooner or later if nobody else does.
Comment #9
jhodgdonIn Drupal 8, setting the type to 'markup' really doesn't do anything, since system_element_info() doesn't add a pre-render or anything else there. So I don't think you really need to worry about it.
And just as a note, #type is never overridden -- it is only set to 'markup' if it wasn't set previously.
So... Let's just document that:
- Elements without #type are set to type 'markup' if #markup is set.
- All elements have defaults from hook_element_info() added to them before any other processing.
- The main rendering step is normally done by calling theme() with the theme hook given in #theme.
- If #theme hasn't been defined or the theme hook hasn't been implemented, #markup and the rendered children are concatenated as the main rendering step output.
Maybe some clarification between the terminology of "element" vs. "renderable array" is also needed?
Comment #10
thedavidmeister commentedThat all sounds good to me.
Comment #11
thedavidmeister commented#2012812: drupal_render() can't distinguish between empty strings from theme() and no hook being matched and #2012818: Remove #type 'markup' both landed so hopefully these docs will be a little easier to write.
Comment #12
jhodgdonRE #11...
- In light of the first issue, we should now say that ... well I'm not sure because I don't quite understand what that issue does (it needs a change notice).
- In light of the second issue, we should not now say that #type is set to '#markup' if it is omitted. I guess that items without a #type are OK.
Comment #13
thedavidmeister commentedItems without a #type are fine currently, there's lots of render arrays in core that just have #theme set and no #type.
The first issue actually implements/enforces what you described earlier:
Previously there was no check to see if the #theme hook is actually implemented before trying to use the fallback/inline method of rendering children, it simply assumed that empty string = no theme hook implemented (but that could have also just meant that the theme hook *was* implemented but intended to render that element as an empty string).
Comment #14
thedavidmeister commentedWhat about this, we just explain what actually happens in the code in plain English:
Comment #15
jhodgdonWhat a concept! I like the idea of #14.
Let's see if I like the details... I think it's mostly good, but I have a few suggestions:
a) I am not sure I would refer to "this element", since the input is $elements. Maybe it would be OK if there was an explanation that it refers to the outermost array of $elements at the beginning?
b) Third bullet point:
- "defaults attributes" should be "default attributes"
- missing comma after hook_element_info()
- you might want to note that #defaults_loaded is then set to TRUE so that the defaults are not merged again? ... or ... when/where does this actually happen? hmmm.
c) After #pre_render, #printed is checked again, which isn't in your list.
d) Detail: #theme is only used if #render_children is not set:
Oh I see you mention this later on... I would mention it in each bullet point where it's relevant rather than getting to it later on.
e) Detail: drupal_render() is called on children either if #render_children is set or there wasn't a #theme or theme() returned FALSE indicating there was no theme function/template:
I also think I would mention that #children could have been set by pre-render or theme() at this point.
f) Typo in the #markup bullet point "if #theme is not an implemented" (remove "an").
g) In post_render, you might mention that #children is passed in, as opposed to many of the other steps, when $element is passed in as a whole.
h) At the end you might mention that #printed is set and that the result is cached if #cache is set.
Comment #16
thedavidmeister commentedHere's an update.
Not sure about point A though, I thought drupal_render() always acts on a single array element (the outermost one) and $elements (plural) is referring to the fact that this element may have children - which are processed recursively, but each one is rendered individually as "an element" by drupal_render(). theme() can process multiple elements at once, but that's a different function.
Comment #17
star-szrI'm liking the way this is heading :)
- Any additional JavaScript, CSS or custom data is added to this elemnet.Typo on element.
@thedavidmeister, your last paragraph in #16 might be a good candidate for a docs addition as well!
Comment #18
jhodgdonI think the documentation should not start out by referring to "each element" though. drupal_render renders one single $elements array (recursively, but still each time it is called, it is only dealing with one element really). When you say "each element", it sounds to me like a for loop.
I guess this is a problem with the existing drupal_render documentation...
Let's see.
What if the documentation read:
First line:
Renders a structured renderable array into HTML.
Then skip the next line in the current doc... and continue with this paragraph, which I think we still need:
And then skip all the rest of what's there (up to the param/return section) and replace with:
The process of rendering an element is recursive. During each call to drupal_render(), the outermost renderable array (also known as an "element") is processed using the following steps:
[your list from #16]
I think the list in #16 of steps is looking pretty good. A few minor grammar/style/etc. cleanups to do:
- Typo mentioned in #17
- Don't say "FAPI" without defining what it is. Say "Form API" (3rd bullet point)
- Every list (or sub-list) needs to be preceded by a : -- so the 5th bullet point should end in "...element takes place:".
- i.e. and e.g. are almost always confused, almost always used wrong, and almost always punctuated wrong (as they are here). So I advise everyone to avoid them in drupal API docs... In the "special case" sub-list bullet point, can we say instead of "...even if #theme is an implemented theme hook, i.e. theme() will be bypassed." ==> "...implemented theme hook; that is, theme() will be bypassed."
- Some of the bullet points use grammatical structure like "If this element has an array of #post_render functions defined" or "If this element has #type defined ", whereas others have structure like "If #theme is defined". Can we make them all have the same grammatical structure? Pick one... I don't have a strong preference; both seem clear enough to me.
Comment #19
thedavidmeister commentedHow's this? I tried to clean up the points from #18 and #17 and incorporate the existing docs so we can see it all together.
Comment #20
jhodgdonWow, this is looking really good!
Just a couple of minor things I noticed:
a)
This is still kind of hard to follow. The third bullet point here seems to be kind of redundant... Can we just say in the second bullet point perhaps:
And leave out the third bullet?
b)
At the very end I think we should say "element" instead of "render array"? The rest of the text refers to $elements as "the element".
Let's have a patch and get this in! Vast improvement over what was in drupal_render() previously.
Comment #21
thedavidmeister commentedHow about this?
I tried to address #20 and I added a bit more information about what #render_children actually even is.
Comment #22
meeli commentedSome minor readability points:
If #render_children is set theme() will not be called.should have a comma after "set": "If #render_children is set, theme() will not be called."
There's a double space after "set," and before "then drupal_render()...". Needs to be a single space.
If this element has an array of #theme_wrappers defined and #render_children is not set then #children is re-rendered by passing the element in its current state to theme() successively for each item in #theme_wrappers.I'd rewrite this run-on sentence by replacing "then" with a comma: "If this element has an array of #theme_wrappers defined and #render_children is not set, #children is then re-rendered by passing the element in its current state to theme() successively for each item in #theme_wrappers."
If this element has #cache defined the rendered output of this element is saved to drupal_render()'s internal cache.Same deal here, needs a comma after "defined".
Comment #23
thedavidmeister commentedChanges attached.
Comment #24
jhodgdonI read through the entire patch, and I think this is in very good shape...
The only question I had was in this line near the bottom:
Do we need to mention here what #properties are used to do this, in order to satisfy the current issue title's part "do not mention most of the base # attributes"? Or at least what function or functions are called to do this? Also, there should be a comma before "or" in this line.
Comment #25
thedavidmeister commentedsure
Comment #26
thedavidmeister commentedUpdated as per #24 to document both the attributes and called functions of drupal_render() better.
Comment #27
jhodgdonAny time there is a list with "and" or "or", our style standards require a serial comma before and/or.
In fact, I think you could also use commas in several other places:
(before the last "and"
(before "then")
etc.
But if we're just quibbling over commas, this is pretty good... I think the whole thing overall reads very well and I'd be willing to commit it as-is, although I'd prefer a few more commas.
Comment #28
thedavidmeister commentedUpdates as per #27.
Comment #29
jhodgdonGreat! Let's get this one in once the bot says "go". :)
Thanks for your hurculean effort on this, David!!!
Comment #30
jhodgdonThank you again!!!!!!!!!!
I have committed this to 8.x. I think we should backport this change to 7.x. This is probably not just a straight reroll of the patch -- we need to read through the code to drupal_render() in 7.x and take out/alter any sections that do not apply to 7.x.
Comment #31
thedavidmeister commentedYeah, I'm much less familiar with the inner workings of drupal_render() in D7 - I'm not sure that I'm the best person to follow up on the backport..
Comment #32
jhodgdonActually I do not think the code is all that different. Just needs someone to read through this patched documentation vs. the D7 code and take out or modify the things that aren't correct.
Comment #32.0
jhodgdonUpdated issue summary.
Comment #33
valentine94Back-port for D7.
Comment #34
jhodgdonUm... This patch doesn't look right to me. Why did you indent the cache section list? It was indented right before. And now the part about "If this element has an array of #pre_render functions defined..." is part of the caching list?
So this needs some work. The thing to do is probably to start over and go to Drupal 8 and copy the entire doc block for drupal_render(), and paste it into Drupal 7's doc block. Then remove or rewrite sections that do not apply to Drupal 7.
However, before we do that, I just noticed that the entire outer list in the Drupal 8 drupal_render() documentation is indented two spaces more than it should be:
So can we have a patch that fixes that before we proceed to Drupal 7? Let's not change the wrapping, just indent that entire list (and sublists) back to the left by two spaces.
Comment #47
andypostThe
drupal_render()is removed from 8.x coreComment #48
andypostComment #49
star-szrIt's been 7 years, I think we should spin off that formatting fixup into a separate issue. And either close this or backport to 7.x.
Comment #51
quietone commentedYes, a followup is a good option. It is now created #3270081: Fix indentation in doc block \Drupal\Core\Render\RendererInterface::render.
This was committed to 8.x so changing the status to Fixed and restoring the version.
Thanks!