Cascading Data Sheets - Removing logic from templates proof of concept

So I read this article a while back http://www.workingsoftware.com.au/page/Your_templating_engine_sucks_and_everything_you_have_ever_written_is_spaghetti_code_yes_you

And I 100% agree with the theory behind it. What I haven’t managed to find is a decent implementation of this separation of concerns.

It generally results in an awful lot of messy template manipulation code outside the template, which I’ve always found worse than having the logic inside the template. We already do this with CSS, separate the presentation from the markup and that should be the goal here… so then it hit me, why can’t we use a simplistic CSS style syntax to provide our display logic externally from the markup.

So here’s what I came up with. There are 3 components:

  1. The template. This is pure markup and does not contain any processing instructions or display logic (such as loops and if statements)

  2. A set of data that will be used to fill the template

  3. A CDS (Cascading Data-sheet) which describes how the data should be applied to the template. This is as close to CSS grammar and concepts as possible

So here’s an example:


$xml = '<template>
			<h1>Header goes here</h1>
		</template>';

$cds = 'h1 {content: data();}';

$data = 'hello world!';

$template = new \CDS\Builder($xml, $cds, $data);

echo $template->output();

Which then prints:

<h1>hello world</h1>

Of course, it needs to be able to handle more complex data structures:

		$xml = '<template>
			<h1>Header goes here</h1>
		</template>';

		//Read the header attribute from the available data
		$cds = 'h1 {content: data(header);}';

		$data = new \stdclass;
		$data->header = 'hello world!';

		$template = new \CDS\Builder($xml, $cds, $data);
		echo $template->output();

Which generates the same output:

<h1>hello world</h1>

It currently supports most of the basic CSS selectors e.g.

		$xml = '<template>
			<h1 id="foo">Header goes here</h1>
		</template>';

		//Read the header attribute from the available data
		$cds = '#foo {content: data(header);}';

		$data = new \stdclass;
		$data->header = 'hello world!';

		$template = new \CDS\Builder($xml, $cds, $data);
		echo $template->output();

and

		$xml = '<template>
			<h1 class="foo">Header goes here</h1>
		</template>';

		//Read the header attribute from the available data
		$cds = '.foo {content: data(header);}';

		$data = new \stdclass;
		$data->header = 'hello world!';

		$template = new \CDS\Builder($xml, $cds, $data);
		echo $template->output();

as well as nesting:

		$xml = '<template>
			<main>
				<h1 class="foo">Header goes here</h1>
			</main>
		</template>';

		$cds = 'main .foo {content: data(header);}';

		$data = new \stdclass;
		$data->header = 'hello world!';

		$template = new \CDS\Builder($xml, $cds, $data);
		echo $template->output();

Loops

So one of the most important things to be able to do in a template is loop through a data set. This can be achieved by the repeat command and instead of reading content from data() it can be read from iteration() which points at the current iteration’s data:

		$xml = '<template>
			<main>
				<ul>
					<li>List item</li>
				</ul>
			</main>
		</template>';

		$cds = 'ul li {repeat: data(); content: iteration();}';

		$data = ['One', 'Two', 'Three'];

		$template = new \CDS\Builder($xml, $cds, $data);
		echo $template->output();

Which generates:

<main>
				<ul>
					<li>One</li>
					<li>Two</li>
					<li>Three</li>
				</ul>
			</main>

And it’s possible to build all this up to target specific nodes inside the repeated element:


		$data = new stdclass;
		$data->list = [];

		$one = new stdclass;		
		$one->name = 'One';
		$one->id = '1';
		$data->list[] = $one;

		$two = new stdclass;		
		$two->name = 'Two';
		$two->id = '2';
		$data->list[] = $two;

		$three = new stdclass;
		$three->name = 'Three';
		$three->id = '3';
		$data->list[] = $three;


		$template = '<template name="">
				<ul>
					<li>
						<h2>an id</h2>
						<span>and a name</span>
					</li>
				</ul>
		</template>';

		$css = 'ul li {repeat: data(list);}
		ul li h2 {content: iteration(id)}
		ul li span {content: iteration(name); }';


		$template = new \CDS\Builder($template, $css, $data);

notice the use of iteration(value) which reads the value from the iterated object.

Which prints

                             <ul>
                                        <li>
                                                <h2>1</h2>
                                                <span>One</span>
                                        </li><li>
                                                <h2>2</h2>
                                                <span>Two</span>
                                        </li><li>
                                                <h2>3</h2>
                                                <span>Three</span>
                                        </li>
                                </ul>

That’s as far as I’ve got for now. The code is available here

and its very alpha

edit: Why won’t this link work??

Some extension possibilities are:

/* Sets the content to a specific value rather than reading from `data()` or `iteration()` */
#whatever {content: "whatever";}

/*  Append/prepend content to the node rather than overwriting it. */
#whatever:before {content: data(foo); }
#whatever:after {content: data(foo); }


/* Loop through the users but hide any where `$user->type == 'admin'` */
#whatever {repeat: data(user); }
#whatever:iteration[type="admin"] {display: none}

``
/* Set an attribute value/add a class based on data or specific value */

/* set the data-id attribute to `$data->id` */
#whatever {attribute: data-id data(id);}

/* add a class name based on a value from the object */
#element {addclass: iteration(type);}

/* some formatting options: */

/* Formats content to 2 decimal places */
#element {content: data(total) decimal 2}

/* This would probably be better if the class name was lower case */
#element {addclass: iteration(type) lowercase;}

None of this is implemented of course and there are few obvious problems left to solve:

  • How do I call methods on objects in data()?
  • How do I build a string, for example I might want to set a href to be: /products/data(id)

But these should be easy enough to overcome!

As I said, this is entirely proof-of-concept and most of the features I’ve thought of aren’t implemented but I’d be interested in thoughts/opinions of this approach.

As was highlighted in the article that inspired me, the obvious advantage is that the display logic can be reused with multiple templates. By using class rather than element names, it’s easy to write multiple completely different templates that can use the same logic.

Post edited by cpradio to resolve the link issue

4 Likes

I like the idea a lot. Though, the question comes to mind, who would be responsible for creating the CDSs? The backend dev or the frontend dev?

Scott

Interesting idea. I’m wondering about the implementation because while CDSs would look nice for humans to read and write I think it would require quite an amount of non trivial code to parse them, which would mean very poor performance. The parser would need to output compiled php code, probably, and cache it. Or, we would need some help from a php extension written in C.

Actually it’s trivial to parse the CDS. See the code ( https://github.com/TomBZombie/CDS/tree/master/src ) everything between { and } is just explode on ; then explode on : and the selector is easier than you’d think to convert to Xpath: https://github.com/TomBZombie/CDS/blob/master/src/CssToXpath.php :slight_smile:

Hmm, you may be right then! :smiley:

I would imagine this would happen at first request (or with a CLI command) and never again (or until something changes), similar to what Twig does. The CDS system should compile the templates to PHP code and they would be called at runtime for the second, and every following request, after that (or after the CLI command is ran).

Scott

It could be either really. The backend dev knows about the data structure so it makes sense for them to do it. If the CDS used class names and ids rather than element names then it’s easy for the backend dev to program the CDS without knowing anything about the structure of the template. It’s then up to the frontend dev to create elements with the correct ids/classes. The nice thing about this is that the frontend dev can put some dummy data in and do some testing :slight_smile:

That said, using twig or any other template engine with logic in the template, the frontend dev needs to know about the structure of the data passed in to create the loops/if statements so there’s no extra work for them if they did it.

Yeah it would be simple enough to cache the parsed data of the CDS or the HTML output of the template.

I knew you’d say that. I had to pose the question though, as the article author seems to think the front-end devs can be oblivious to the logic requirements needed from them, which sorry, just isn’t the case, ever. Well, not for any decent web application that is.

So, you are absolutely right with the idea to separate any display logic from the actual markup and why not use CSS as a concept to follow, since it does a wonderful job separating the design from the markup too. I wish I were a more confident programmer. I’d love to take this and run with it. It is a refreshing idea and I love that kind of stuff. :slight_smile:

Scott

However you do it, the front end dev needs to know something about the nature of the data to produce relevant markup. Even if we assume that the CDS and entire backend are written after the template, the template author needs to put in relevant tags for the data. Both need to know the business requirements for the template/part of the page which is being worked on.

Here’s another few ideas I had for extension:

/* Want to always hide certain blocks unless someone is logged in? Put that in another .cds and reuse it 
It could contain something like:
.loggedIn:data[user=""] {display: none}
*/ 
@import base.cds;

/* Include another template: */
#block { content: template(name.xml); }

/* Or include a template, with a CDS and some data */
#block { content: template(name.xml, style.cds, data(whatever); }


Hmm…at first glance, I am not too keen on the inclusion of other templates through CDS. Other CDSs yes, but not to define needed templates themselves. CSS files can’t include other markup files, can they? Just other CSS files, I thought.

Although, there is definitely the problem of splitting up an html page into reusable templates and the logic that goes with it. But, I consider that a second problem or functionality of a template system, albeit one of the main reasons for having a template system.

Hmm…I am not sure how complex a CDS would get with that kind of logic in it too. I am simply not sure the template inheritance logic belongs there. I can’t come up with more arguments against it though.

Maybe CDS could also hold a bit of custom markup too? I am not sure what a designer would prefer to be honest. But, there could also be the added inclusion logic like HTPL does it.

which is pretty simple to learn. Everything else a template system needs to offer could work over CDSs?

Scott

I don’t know that i’m in agreement with this.

I understand the idea of separating logic and presentation. But at this point you’re abstracting the template to the point that it may become unrecognizable from it’s final form. Putting the code into so many disparate locations that programmers would struggle to put together the complete picture.

For example.

<h1></h1>
<ul>
     <li></li>
</ul>
<table>
</table>

There’s my template. It… is beyond minimal; I barely have any concept that this could be an entire forum. But it could. That table could have 1000 rows. Or none, and i have no idea that you’re planning to expand it, or with what.

I wouldn’t want to put any markup in the .cds file so I’m guessing you meant the template? “Include another template” is completely display logic so I’d argue it belongs with the rest of the display logic in the template. That way, different CDS files can include different templates. It also adds more flexibility. Consider this:


nav {content: template(navigation.xml) };
nav:data[user.type="admin"] {content: template(admin-navigation.tpl); }

Trying to move this into the template would require logic in the template.Ok we can include both blocks in the template:

<nav class="admin"></nav>
<nav></nav>

and hide/show the required one using the CDS but that means our template must include every possible alternative. For a simple navigation that might be possible. However, what about this:


<html>
	<head>
		<title>Default title</title>
		<link href="style.css" />
		<script src="jquery.hs"></script>

	</head>
<body>
</body>
</html>

Do I want to include everything that could ever possibly be in <body> in this template and use the cds to hide/show the the relevant parts? The scalability of that approach is minimal.

Whereas with being able to include templates in the .cds, I can do this:

body {content: template(contact.xml)};

or even reuse the CDS and specifiy some data that fills the body:

$data = ['page' => $_GET['page']];

$cds = 'body {content: template(data(page))};';

Which admittedly is getting a bit verbose, but it’s considerably more flexible.

Correct.

One of the biggest advantages is that dummy (or default) data can be included to explain what is going on:

<h1>Page title</h1>
<ul>
     <li>List element</li>
</ul>
<table>
<tr>
  <td>Post title</td>
  <td>Post date</td>
 <td>Post Author</td>
</tr>

</table>

Is that any less readable than:

<h1><?= $title; ?></h1>
<ul>
	<?php foreach($list as $item):  ?>
    <li><?= $item; ?></li>
    <?php endforeach; ?>
</ul>
<table>
	<?php foreach($posts as $post):  ?>
<tr>
  <td><?= $post->title; ?></td>
  <td><?= $post->date; </td>
 <td><?= $post->author; </td>
</tr>

	<?php endforeach;?>
</table>

Well I can open the former in a browser or dreamweaver or whatever and have a pretty good idea what it’s for. Using the latter, everything in PHP tags gets hidden and I’m left with bare elements.

That table could have 1000 rows or none. The same is true of CSS, of course, looking at the HTML I have no idea how it’s going to actually be rendered, and with what (colours, shapes, borders), whether some parts will be hidden entirely, etc. The reason is: It’s not relevant. The same is true here, the processing instructions are not relevant. The template author just provides some basic markup and then doesn’t care about how it’s used. With CSS they don’t care how it’s styled. edit: I think the article I linked to in the original post makes the point better than I ever could: http://www.workingsoftware.com.au/page/Your_templating_engine_sucks_and_everything_you_have_ever_written_is_spaghetti_code_yes_you

Yes, sorry. I meant add additional markup in the template to solve the template inheritance problem and only have that “difference” in the templates. The rest should be defined in the CDSs.

How would the necessary CDSs be referenced to the template? Or rather, what is the overall defining mechanism to say a page needs CDS X, Y and/ or Z?

Scott

So what about the <body> example? Surely you’d end up with something like this:


<include file="body-about.xml" />
<include file="body-forum.xml" />
<include file="body-contact.xml" />
<include file="body-openingtimes.xml" />

etc… then you’d have the .cds hide/show the relevant <body> tag… the only other alternative is something like:

<include file="default.xml" />

Which you’d then have the .cds set the attribute?

I think this is a problem which wouldn’t be answerable without trying some actual applications, however I stand by my idea of:

layout.xml

<html>
	<head>
		<title>Default title</title>
		<link href="style.css" />
		<script src="jquery.js"></script>
	</head>
<body>
</body>
</html>

CDS:

/* Set the title content based on the data */
title {content: data(title);}

/* The template() function is basically just a wrapper for new \CDS\Builder and takes the same parameters */
body {content: template(data(body-template), data(body-cds), data(content)); }

Which then lets us build the different pages like this:


$data = ['title' => 'About My Business', 
		 'main-template' => 'about.xml', 
		 'body-cds' => 'about.cds', 
		 'content' => ['user' => $auth->getLoggedInUser()]];


$layoutCDS = 'title {content: data(title);}
body {content: template(data(body-template), data(body-cds), data(content)); }';


$about = new \CDS\Builder('layout.xml', $layoutCDS, $data);


//The forum page, which also sits inside the main layout. We can populate the layout using the same display logic (or different display logic if required)
$forum = new \CDS\Builder('forum.xml', $layoutCds, new Model\Forum);

I guess we’ll have to see. But put it on record, I am sceptical of template inheritance in the CDSs. I think that logic is not a part of presentation logic, but rather logic needed for the template system itself. So, it is another concern. :wink:

Let it also be known, I really like the idea of CDSs for dynamic data and presentation logic binding. That is for sure. :smile:

Scott

This is pretty cool. I’d like to put out an article about this once it’s a bit more developed.

Short answer, weird markdown parsing issue. I had to create a blank line between “Which prints” and your “```php” code block. Don’t ask me why that impacted the link, not 100% sure. I do know that eventually Discourse will get a new markdown parser, it just isn’t ready yet.

Also, apologies for it taking so long for me to investigate. I’ve had a busy couple of weeks and just now had the time to give this topic the attention it deserved.

Thanks :slight_smile:

I’ve now added enough features and documentation that it’s usable in a real project. Check out the code and more examples here:

I’ve also renamed the project to Transphporm