phpgt / domtemplate Goto Github PK
View Code? Open in Web Editor NEWBind dynamic data to reusable HTML components.
Home Page: https://www.php.gt/domtemplate
License: MIT License
Bind dynamic data to reusable HTML components.
Home Page: https://www.php.gt/domtemplate
License: MIT License
Elements within the DomTemplate extension of Dom should have a bind
method, taking two parameters.
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>
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:
data-template
attributes.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.
$this->document->getElementById
returns a Gt\DomTemplate\Node
rather than the actual Gt\DomTemplate\Element
.
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
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:
DomTemplate/src/HTMLDocument.php
Line 71 in 623527f
The html template files should be stored in src/page/_template by default, to match the convention with database queries (PhpGt/Database#25)
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.
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.
This would be useful so that elements that represent a list of data that are bound with an empty dataset are considered to CSS as :empty
.
Since the introduction of BindMapper and BindGetter, ordinary POPO objects are not possible to use as bind data.
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.
Currently bind() is an alias of bindKeyValue, but if associative data is passed as the only parameter, it should become an alias of bindData for backwards compatibility withe pre-1.0.
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...
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.
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.
As with #124, this needs to be implemented when using the bindNestedList
functionality.
DocBlocks need improving to allow IDEs to see the correct types that are handled.
For the purposes of the description here, the term BindableValue
will act as a union type of either:
string
, int
or float
.__toString
function.The term BindableData
will act as a union type of either:
BindableValue
.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.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.
<button>Go on</button> this text doesn't get included
When I set a class to the outer-most parent element of a custom component, the class name is removed when the .c-
class is added.
$todoElement = $this->document->getTemplate("todo");
$todoElement->textContent = "blah";
$todoElement->insertTemplate();
Actually replaces the node itself with text string of "blah"
Example: Music app that shows tracks within albums within artists.
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.
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.
This only needs to be done on named templates, otherwise large lists using proper DOM binding will have a lot of unnecessary bloat in them.
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.
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.
Todo list example: data-bind:class="?isComplete"
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.
Return type could be an iterator, not just an array. This is according to the WebEngine documentation.
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.
<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"
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.
See the latest Scrutinizer: https://scrutinizer-ci.com/g/PhpGt/DomTemplate/inspections/50b3f08b-0d89-46ed-bd3f-039f3e3ab945/code-structure/operation/Gt%5CDomTemplate%5CTemplateParent%3A%3AextractTemplates
Went from grade C to F :(
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.
They do the same thing!
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:
Line 39 in f2ea682
This is confirmed by the use of rowNumber
here:
Line 59 in f2ea682
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];
}
?
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);
}
}
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>
DomTemplate/test/unit/Helper/Helper.php
Line 184 in 63106d8
Is this code obsolete? I'm not familiar with this style of coding... needs investigation.
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" />
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.
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.
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)
It is a synonym. Deprecate and remove next major version.
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
]);
If a component HTML contains a data-template, using insertTemplate
does not work.
I have just noticed this when using bindList
on an element within a component html file.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.