Giter Site home page Giter Site logo

phpgt / domtemplate Goto Github PK

View Code? Open in Web Editor NEW
17.0 5.0 4.0 604 KB

Bind dynamic data to reusable HTML components.

Home Page: https://www.php.gt/domtemplate

License: MIT License

PHP 100.00%
template-engine webcomponents custom-elements declarative declarative-ui webcomponent markup-template html-template phpgt dynamic-data

domtemplate's People

Contributors

dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar g105b avatar peter279k avatar zach-clare avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

domtemplate's Issues

data-bind:X with no value allowing {placeholders}

Using {placeholders} as a template language is generally a bad idea, because it breaks the functionality of common standards and mimics a lot of the DOM's capabilities.

However, there are certain times when it would just be a lot easier to state the placeholders inline, without requiring the inclusion of extra elements.

Idea: elements can be opted in to having data injected into curly-braced placeholders by using a value-less data-bind attribute.

For example:

<p data-bind:text>Hello, {name}!</p>

$p->bindKeyValue("name", "World");

The downside of this approach is that there can never be a default supplied. Maybe even more syntax can solve that, such as <p data-bind:text>Hello, {name ?? World}!</p>.

This is an idea. I'm not sure if it's necessarily a good one.

Custom elements are not templated before render

When a custom element is replaced, if it contains any template content itself, the template content isn't replaced in turn.

For example:

index.html:

<ul class="shop-items">
    <li data-template="shop-item">
        <shop-item></shop-item>
    </li>
</ul>

shop-item.html

<div>
    <h1>Product name</h1>
    <h2>£0.00</h2>
    <ul class="categories">
        <li data-template="category"></li>
    </ul>
</div>

The rendered page shows the data-template attribute on a single <li>, rather than extracting the template.

Probable improvement to binding single row of data

When providing a single row of data to bind, like this:

$element->bind([
    "name" => "John Smith",
]);

... the following line assumes an indexed array representing rows of data:

iterable $data,

This is confirmed by the use of rowNumber here:

foreach($data as $rowNumber => $row) {

When only passing in a single row of data, rowNumber is in fact the key of the data... not what is expected.

Improvement suggestion: should the start of the function ensure the array is indexed, and if not, wrap itself in another array?

if(!is_array_indexed($data)) { 
    $data = [$data];
}

?

Unbindable Object bug

Since the introduction of BindMapper and BindGetter, ordinary POPO objects are not possible to use as bind data.

Binding on parent containing unnamed template child adds template to the wrong node

Example of bug:

<div id="container">
	<ul>
		<li data-template data-bind:text="content">Template element</li>
	</ul>
</div>
$document->getElementById("container")->bind([
	["content" => "One"],
	["content" => "Two"],
	["content" => "Three"],
]);

Actual result:

<div id="container">
	<ul>
	</ul>
	<li>One</li>
	<li>Two</li>
	<li>Three</li>
</div>

Expected result:

<div id="container">
	<ul>
		<li>One</li>
		<li>Two</li>
		<li>Three</li>
	</ul>
</div>

The problem is that when a parent is used to "bind", the parent node is where the new template children are appended. Instead, the original parent of the template should be used.

Allow getting unnamed template by omitting name

The getTemplate() function takes a template name, but it is possible for a template to exist attached to an Element without a name. If a name of null is passed, it should return the first template element that matches the context node.

Efficiency of bindList with many items

Rather than cloning the template element for each iteration, create a fragment to output to, then append the fragment to the page.

Appending to the DOM is a relatively costly function, but appending to a fragment is not.

Bind functionality

Elements within the DomTemplate extension of Dom should have a bind method, taking two parameters.

  1. The name of the template to bind data to.
  2. The data to bind with.

For example:

<ul id="shopping-list">
	<li data-template="shopping-item" data-template-text="title">Example text</li>
</ul>
$shoppingList = [
	["title" => "Apples", "quantity" => 5],
	["title" => "Bananas", "quantity" => 12],
	["title" => "Oranges", "quantity" => 7],
];
$outputTo = $document->getElementById("shopping-list");
$outputTo->bind($shoppingList);

Outputs:

<ul id="shopping-list">
	<li>Apples</li>
	<li>Bananas</li>
	<li>Oranges</li>
</ul>

Binding boolean/value-less attributes

Since the "optional value" ? syntax was dropped in Bindable, we could reintroduce the ? syntax to indicate boolean/value-less attribute usage.

The main example for this is the disabled attribute. If we are binding many option elements to a select, using $select->bindList($data), we could indicate within the data whether or not to include the disabled attribute.

To disable every other option (using modulus):

// Assume $data contains many data rows:
$dataRow = [
	"value" => $i,
	"text" => $formatter->format($i),
	"isDisabled" => ($i % 0)
];
<select>
	<option data-bind:text="text" data-bind:value="value" data-bind:disabled="?isDisabled" data-template></option>
</select>

The question mark indicates to not bind anything if the value is non-truthy.

Binding optional data keys using ? syntax

When the HTML specifies a key in its data-bind attribute that doesn't exist in the data source, it currently throws a BoundDataNotSetException.

Sometimes it makes sense to ignore the missing keys. The first idea that comes to mind is the PHP-style syntax of optional types by using a question mark.

For example: <input name="test" data-bind:value="?somekey" /> the question mark indicates to skip this value if there is no matching key.

Could also work on referenced attributes: <input name="test" data-bind:value="?@name" />

bindNames

The bindData function uses the data-bind attributes to set data.

There should also be a function that binds name-value pairs, so a whole form can be bound using the name attribute.

Example:

$document->bindNames([
    optional => extra
]);

bindDataGetter use bind* prefix rather than get*

Function names that are prefixed with "get" are so common that it will be very possible to clash and provide unintended functionality.

Using "bind" as the prefix makes more sense here because it is contextual to the action being performed.

WebEngine documentation already specified bind* as the function name.

Separate bindData and bindList calls

Currently, binding data to placeholder elements and binding iterable data into lists is done through the same bind function, and separate calls might be easier to understand in some situations.

Empty data-bind:X

It is documented that data-template can be used without a value, so that the whole element is cloned for each row of the result set.

This assumes that there are subchildren that have data-bind:X attributes, but consider the following:

$dataWithoutKeys = ["one", "two", "three"];

<ul>
	<li data-template data-bind:text>Put the number here</li>
</ul>

The LI will be cloned for each element of the array, but due to there being no keys on the array, the data-bind:text attribute has no value, and will simply set the value of the array element to each cloned element's text content.

Nested templates

A nice improvement made in v2 is to be able to insert a templated element back where it originally came from, without having to select its parent in any way. It does this by storing a reference to its parent as it gets extracted from the DOM into the Template object.

Then when you come to add it back to the page, you can do this:

$messageItem = $this->document->getTemplate("message-item");
$messageItem->insertTemplate();

The insert part was crucial - rather than append, it remembers the sibling element too in order to keep its original place.

But the question is, where does (or should) the template named "message-item" get inserted in the following example:

<div data-template="conversation">
    <h1>Title</h1>
    <ul class="messageList">
        <li data-template="message-item">
            Message content
        </li>
    </ul>
</div>

Once the first conversation element has been inserted, should inserting message-item elements go into the original, or the latest added? If the latter, how do we define this?

It might make most sense to do something like the following:

  • Templated elements need to remember their ancestors with data-template attributes.
  • When being inserted, templated elements need to search for the latest-added data-template ancestor.
  • Some clever XPath here?

bindDataMap return type

Return type could be an iterator, not just an array. This is according to the WebEngine documentation.

bindList with multiple iterables

A data structure that contains multiple iterable objects should try to bind to subsequent HTML lists.

For example:

$data = [
  "list-1" => [1, 2, 3, 4, 5],
  "list-2" => [6, 7, 8, 9, 0],
];

$document->bindList($data);
<h1> My lists:</h1>

<div data-template>
  <h2 data-bind:text>List name</h2>
  <ul> <li data-template data-bind:text>Value</li> </ul>
</div>

Error messages do not explain exactly what has gone wrong

When an element has bindList called on it, but there are no child elements with a data-template attribute, the error message should explain that. Instead, we get a Trying to get property 'templateParentNode' of non-object warning along with a fatal error of Call to a member function getNodePath() on null. It should say something like No template element found as a child of $nodePath.

Add any more confusing error messages to this issue to tackle them all at the same time.

bindDataGetter deprecate get* prefix

This is to avoid unwanted clashes with getter functions starting with "get".

bind* functions are already implemented, so a deprecation notice should be emitted before v2's removal of the function prefix.

Documentation

  • Class Binding

From #8 :

Typical todo list application:

  • The data's title key will be bound to the textContent of the element. If only a class name is provided in the data-bind:class attribute, then this class will be active if the matching data key is truthy.

    data-bind:class="dateTimeCompleted:isComplete"

    Use colon separator to bind a data key to a class name. If dateTimeCompleted is truthy, element will get the isComplete class.

    data-bind:class="dateTimeCompleted:isComplete dateTimeDeleted:isDeleted"

    Split multiple class matches with a space.

  • More to follow...

    Directory nesting custom components

    It would be useful to be able to organise components into subdirectories rather than putting them all in the same root directory. When there are lots of components it gets a bit messy.

    The idea is limited by the standard characters allowed within an HTML tag. Something like a double hyphen to indicate directory or the use of namespaces could be used.

    Something like:

    <nav--admin--menu />
    or
    <nav/admin:menu />

    but I'm not sure about the validity of the second example... worth looking into.

    Binding data causes BoundDataNotSetException if parent contains any bindable elements too

    <div class="parent>
    	<label>
    		<span data-bind:text="outside-scope">This node is outside the scope</span>
    
    		<ul>
    			<li data-template="target-template">
    				<span data-bind:text="target-key">This node is the target</a>
    			</li>
    		</ul>
    	</label>
    </div>
    foreach($data as $row) {
    	$t = $this->document->getTemplate("target-key");
    	$t->bind($row);
    	$t->insertTemplate();
    }

    The above code throws an error that BoundData is not set: "outside-scope"

    Simpler definition for types of binding operations

    For the purposes of the description here, the term BindableValue will act as a union type of either:

    • a primitive, like a string, int or float.
    • an object with a __toString function.

    The term BindableData will act as a union type of either:

    • an associative array of key-values, where the values are of type BindableValue.
    • an object with public properties, acting as key-values.
    • an object that implements the BindDataGetter or BindDataMapper interfaces.

    The bind function will make a call to the following bind functions, depending on the parameters passed into it:

    • bindValue(BindableValue $value) - binds any element(s) in the tree that has a data-bind:X attribute without a key name.
    • bindKeyValue(string $key, BindableValue $value) - binds any element(s) in the tree that has a data-bind:X=Y attribute, where Y matches $key.
    • bindData(BindableData $data) - calls bindKeyValue for every key-value-pair of $data.
    • bindList(iterable $list) - creates a template element for each iteration, and calls bindData on the template element, passing the value of the iterator. This functionality is broken down below.
    Breaking down bindList

    The iterable $list is bindList's only parameter, but its contents can determine the function's behaviour (sometimes recursive).

    For every iteration of the iterable, a new template element is cloned. The first extracted template element that matches the current context element's nodePath is used as the template element.

    Once a template element has been cloned, bind functions will be applied to it. If the $list is an associative array (or anything other than an indexed array/iterator), the key is passed to bindValue on the current template element.

    If the value is a BindableValue, the value is passed to bindValue. Therefore, an associative array of strings would not be a valid $list, and an exception must be thrown.

    If the value is a BindableData, the value is passed to bindData.

    If the value is iterable, the value is passed to bindList again (recursively), but the context node shifts to the newly cloned template.

    data-bind:class

    Typical todo list application:

    <li data-template data-bind:text="title" data-bind:class="complete">

    The data's title key will be bound to the textContent of the element. If only a class name is provided in the data-bind:class attribute, then this class will be active if the matching data key is truthy.

    data-bind:class="dateTimeCompleted:isComplete"

    Use colon separator to bind a data key to a class name. If dateTimeCompleted is truthy, element will get the isComplete class.

    data-bind:class="dateTimeCompleted:isComplete dateTimeDeleted:isDeleted"

    Split multiple class matches with a space.

    DocBlocks on all overridden classes

    Due to the use of registerNodeClass, IDEs don't know that simple functions such as getElementById actually return an overridden Element rather than the base DOMElement.

    @name for binding

    When binding data to templated elements it is a nice idea to be able to use the value of other attributes on the element to indicate the key of the data to use:

    <!doctype html>
    <meta charset="utf-8" />
    <title>Shopping List app</title>
    <ol id="shopping-list">
    	<li data-template="shopping-item">
    		<form method="post">
    			<input type="hidden" name="id" data-template-value="@name" />
    			<input name="title" data-template-value="@name" />
    			<button name="do" value="save">Save</button>
    			<button name="do" value="delete">Delete</button>
    		</form>
    	</li>
    </ol>
    <?php
    namespace ShoppingListApp\Page;
    
    class IndexPage extends \Gt\Logic\Page {	
    	public function go() {
    		$outputTo = $this->document->getElementById("shopping-list");
    		
    		$outputTo->bind(
    			"shopping-item",
    			$this->database->fetchAll("shoppingList/getAll")
    		);
    	}
    	
    	public function do_save(InputData $data) {
    		if($data->id) {
    			$this->database->insert("ShoppingList/insert", $data);
    		}
    		else {
    			$this->database->update("ShoppingList/update", $data);
    		}
    	}
    	
    	public function do_delete(InputData $data) {
    		$this->database->delete("ShoppingList/deleteById", $data->id);
    	}
    }

    BindList with callback

    This might be a way to simplify the sometimes confusing distinction between bindList and bindNestedList.

    If bindList was made to be simple-data-only, bindNestedList could be deprecated completely to keep things simple, and a new bindListCallback could be introduced.

    This will allow a callback function to be used to bind a nested list, with preconfigured context of the current node - increasing flexibility and making everything a lot more understandable.

    Wildcard match getNamedTemplateChildren

    When nested templates are used, their template name doesn't contain the whole XPath query (because they haven't been added to the page yet), and will start with a ?.

    Use the "?" character as a wildcard match, rather than a strict strpos === 0 check as in this line:

    if(strpos($templateName, $name) === 0) {

    Improve DocBlock types

    DocBlocks need improving to allow IDEs to see the correct types that are handled.

    • HTMLDocument::getElementById states Node, Element actually returned

    c- for HTML components, t- for added templates

    Currently, all elements that are added by this repository are given the t- prefix, but that might not make sense.

    Only add t- prefix if the element is added as a template, using $document->getTemplate() or output using $document->bind(), otherwise use the c- prefix to indicate that it has been loaded as a component.

    Also makes sense to use the _component directory rather than _template, for consistency with the way the documentation references element types.

    Automatically fill table with database result set

    This is something so simple, but will make prototype crud applications a breeze to build.

    Take a result set from a database, pass directly into a function of this repository, have it build up a table representing the result set.

    This mighty be a new bind parameter, for example data-bind:table

    data-template-count helper

    Adding data-template-count="5" to an element will make PHP.Gt clone the element and add it to the screen 5 times... useful for tweaking dummy content in the design phase.

    Some nice template helpers could be:

    <ul>
        <li data-template-count="2">
            <span class="fullName" data-template-dummy="fullName">Example Name</span>
        </li>
    </ul>
    
    Will become:
    <ul>
        <li>
            <span class="fullName">Brian Jones</span>
        </li>
        <li>
            <span class="fullName">Thomas Spencer</span>
        </li>
    </ul>
    
    (names taken from random internal list)

    Missing doc block

    Within a Logic class:

    $t = $this->document->getTemplate("template-name");
    $newItem = $t->templateParentNode->appendChild($t);
    $newItem->querySelector(".whatever")->selected = true;

    $newItem is a DOMNode, not a \Gt\DomTemplate\Element.

    Recommend Projects

    • React photo React

      A declarative, efficient, and flexible JavaScript library for building user interfaces.

    • Vue.js photo Vue.js

      🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

    • Typescript photo Typescript

      TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

    • TensorFlow photo TensorFlow

      An Open Source Machine Learning Framework for Everyone

    • Django photo Django

      The Web framework for perfectionists with deadlines.

    • D3 photo D3

      Bring data to life with SVG, Canvas and HTML. 📊📈🎉

    Recommend Topics

    • javascript

      JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

    • web

      Some thing interesting about web. New door for the world.

    • server

      A server is a program made to process requests and deliver data to clients.

    • Machine learning

      Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

    • Game

      Some thing interesting about game, make everyone happy.

    Recommend Org

    • Facebook photo Facebook

      We are working to build community through open source technology. NB: members must have two-factor auth.

    • Microsoft photo Microsoft

      Open source projects and samples from Microsoft.

    • Google photo Google

      Google ❤️ Open Source for everyone.

    • D3 photo D3

      Data-Driven Documents codes.