When and How to Use XHP Categories
I remember when we first added the children keyword to XHP. We had the problem that validating them was really cumbersome. Nearly every element was valid inside a <div> but not all (for instance ). Listing out every valid child wasn’t very elegant and certainly would cause problems for custom XHP components. Fortunately, the HTML spec categorized elements just for this purpose. Grouping elements into “block” or “inline” categories made validation far simpler. But if you’ve used XHP you might be wondering, “Why have I never needed to define my elements as inline or block before?” Well, the answer lies in how XHP renders an element tree. First it will render down all your custom elements into their eventual HTML primitives, then it will render the entire HTML primitive tree into a text string. The key here is that there are actually two passes, meaning XHP validates children in two sets: your elements first and then core HTML elements second.
When you put a custom component inside of a element, you don’t need to give it a category of %phrase (the HTML5 equivalent of %inline). When XHP renders the tree it will wait on validating the children of the root until its children are HTML elements.
class :ui:hello-world extends :x:element { protected function render() { return Hello World!; }}$root =
So when you render $root, XHP will first render the
class :ui:button extends :x:element { attribute :button; category %ui:button; protected function render() { return ; }}class :ui:dialog:footer extends :x:element { attribute :div; children (%ui:button+); protected function render() { return <div class="footer"> {$this->getChildren()} </div>; }}class :ui:submit-button extends :x:element { attribute :ui:button; category %ui:button; protected function render() { return
What’s even better is if you have a UI library set up as I described in Building a Good UI Framework with XHP, your categories double as an indicator about which attributes are supported on any given element. But regardless of that, this system ensures that you can compose custom elements with good abstractions and keep custom children validation. There are drawbacks to this however. First of all, this is all pretty loosely regulated, and by that I meant it isn’t regulated at all. Nothing is preventing another developer working in your system from just adding a category to anything simply to have it pass validation. There are instances of this at Facebook by engineers who didn’t understand the system or were forced to because of poor API design. Depending on the case, sometimes it works seamlessly, sometimes it doesn’t. But the fact that it can happen is a pretty big downside in my eyes. However, the principle and how to use them correctly is pretty easy to understand so this is a rare occurrence. It’s worth mentioning that yes, you could just extend :ui:button for all your abstractions and it would make this a moot point, but I would argue that the advantages of building an XHP library focused around composition far outweighs the benefits of using object inheritance for children validation. This really is just my preference and opinion when working with categories. I’m sure there are plenty of good cases where using categories to group elements together similar to HTML’s method is a very valuable tool, or even some completely other system that I haven’t thought of, but so far this organizational pattern has served me (and Facebook) very well.