Closed smolinari closed 8 years ago
A quick question, what does the template system have to do with event firing/listening? Can you give an example of where this would be useful and a quick and dirty example of the API? (E.g. what kind of thing you'd expect to see in the .cds)
Like I said, I am no designer, but I'll give this a try.
Say there is a standard JS function that determines if all required fields in a form are filled out properly. The designer only knows the function is called validate
. He also knows, the form elements, the fields, would need to trigger the function and can do this through the CDS. Maybe it could look like this?
.first-name {input: text; placeholder: i18n(first_name); trigger: validate; required}
.last-name {input: text; placeholder: i18n(last_name); trigger: validate; required}
.date-of-birth {input: date; placeholder: i18n(date_of_birth); trigger: validate; required}
.telephone {input: phone; placeholder: i18n(telephone); trigger: validate;}
.remarks {input: textarea; cols: 40; rows: 10;}`
This does quite a bit.
And the designer also wants the submit button to be disabled, until the fields are properly validated.
.submit-button {form: button; validate: enabled};
Does that make any sense or is feasible at all?
Scott
.
I'll have to admit I still don't fully understand the purpose of this. Since the template engine takes XML, Data and CDS and spits out some rendered HTML, it might be useful to show the 3 components (template, CDS as you have above, some sample data) and then the expected rendered HTML.
edit: I totally get localisation, but isn't that essentially just a data()
lookup?
However you did just make me think of something cool. Consider the simple task of re-populating form components based on $_POST...
$template = '<template>
<form action="" method="post">
<input type="hidden" name="user[id]" />
<input type="text" name="user[name]" />
<input type="text" name="user[email]" />
<textarea name="user[address]"></textarea>
</form>
</template>';
$cds = 'form [name]:attr[value] {content: data(attr(name)); }
$data = $_POST;
Syntax could be improved but it might be a nice way of doing it :)
form [name]
like CSS will select any element inside form
that has a name
attribute.
:attr[value]
would select the value attribute (not element) on each matched element and content
would set the attribute's value.
attr(name)
would read the name attribute and data(user[name])
would function identically to how data(user.name)
does at the moment. :)
The system would also need to somehow have registered JS functionality in it too, so the designer can add any view behavior into the CDS too. I am even thinking the CDS system should also spit out React JSX or something along those lines. The rendered HTML would automatically entail the needed binding for the JSX. Without a JS integration like this, a CDS would only be a data binding system and nothing else. If that is all it should be, then forget my contemplation. :smile:
But then, the designer is back to the problem of having to hack in JS throughout the HTML/XML herself. Ok. A lot of designers understand and can program in JS too. Maybe the idea of having view behavior be controlled in the CDS isn't necessary or too much. I don't know. It just seems like a cool way to get more people into working with the web and splitting the responsibilities of design, UI behavior and data integration. If the JS functions are pre-built and in the back-ground (where they should be), then all a designer has to know is HTML, CSS and CDSs (and the available JS functions and return values (the API)). It is all declarative and relatively easier to learn. Much easier than learning JS.
Scott
Oh, and you also need some form of JS anyway, for the client side CDS data binding and querying behavior to get data from the server. Might as well build CDSs out to be extensible to any type of JS behavior.
Scott
I'm happy to have the system integrate with JS, I'm just failing to see the use-case at the moment.
In your example you have this:
.first-name {input: text; placeholder: i18n(first_name); trigger: validate; required}
What exactly would specifying input: text
do? Presumably it would just add some validation to the input? Would it add an automatic blur event, for example?
Wouldn't this be better off being described in a single, reusable cds file?
[data-validate="text"]:attr[onblur] {content: "validateText(this);" }
Then in our template we put:
<input data-validate="text" />
And we can use the same cds file with each template? Possibly via @import
One of the problems this is instantly going to cause is a lack of portability between clientside and serverside. In my mind, you should be able to take a data structure, some XML and a .cds file and have it rendered identically via JS or PHP (or any other language that cares to implement it)
edit: and if you wanted to get clever, add the data-validate attribute inside the cds itself:
.first-name:attr[data-validate] {content: "text";}
edit2: If you don't like event attributes such as 'onblur=""' then we could even add:
input:event[blur] { content: "validateText(this);"; }
When the template is rendered on the server, it creates the "onblur=" attribute, when done using javascript it assigns it using addEventListener.
How about just making the entire system extensible?
$cds = 'input[name="firstname"] {input: text}';
$template = new \CDS\Builder($template, $cds, $data);
$template->registerProperty('input', function($element, $value) {
//$element is the matched element, this will be called once for each element
//$value is the value supplied in the cds so in this example "text"
});
Then when it comes to javascript
A) It doesn't lock people into a specific validation library B) Anyone can add any property they want and do the same thing:
cds = 'input[name="email"] {input: email;}';
var template = new CDS_Builder(template, cds, data);
template.registerProperty('input', function(element, value) {
$(element).blur(function() {
if (value == 'email') {
if (!element.value.match('///regex-for-email//')) {
alert('Invalid email address');
}
}
});
});
The input: text
is only to determine the type of input field it should be, along with the necessary JS required. For instance, you don't have telephone or date (well, not in IE) input field types.
cds (let's go with ids)
#first-name {input: text; placeholder: i18n(first_name); trigger: validate; required}
XML
<dl>
<dt>
<dd>
<label></label>
</dd>
<dd>
<input id="first-name">
</dd>
</dt>
</dl>
And let's say the designer wants to have a hint popup in the field and asks the JS programmer to make him the hint. (or she can plug in a hint module),
cds
#first-name-hint {hint: i18n(add_valid_first_name);}
XML
<dl>
<dt>
<dd>
<label></label>
</dd>
<dd>
<input id="first-name">
<span id="first-name-hint"></span>
</dd>
</dt>
</dl>
So that is all the designer would need to do.
The output would be.
<dl>
<dt>
<dd>
<label>First Name</label>
</dd>
<dd>
<input type="text" id="first-name" value="Scott Molinari" name="first_name">
<span id="first-name-hint" class="hint">Please add a valid first name!</span>
</dd>
</dt>
</dl>
Scott
How about just making the entire system extensible?
Absolutely! :+1:
Scott
I've added a registerProperty method to enable this. Take a look at Builder.php
I think that's the way forward. Provide some basic data manipulation tools (repeat, content, etc) and allow anything more than that to be either added on a per-application basis or as a separate project that extends the basic functionality of this one.
Great. Anything to support extensibility is the way to go. CDSs must be completely self-definable. There should be only a basic set of rules and standard behaviors. Everything else is an extension.
One thing still open in my mind though....how can the sheets, or rather the declarations in them, cascade?
Scott
The same way as CSS :) iteration() is available in all rules for child elements. On 3 Sep 2015 19:36, "Scott" notifications@github.com wrote:
Great. Anything to support extensibility is the way to go. CDSs must be completely self-definable. There should be only a basic set of rules and standard behaviors. Everything else is an extension.
One thing still open in my mind though....how can the sheets, or rather the declarations in them, cascade?
Scott
— Reply to this email directly or view it on GitHub.
Can you give me a quick example please? Cause, I think this might be a point on where our perspectives are diverging or rather I am misunderstanding the purpose. Because, I am thinking of the ability to define the actual sources of data in CDSs, to be able to bind that data to the structure. So, if I would define a cds declaration to use a user object's fields, how could I use that same declaration for anything else?
Scott
Take a look at testRepeatObjectChildNodes
The CDS is this:
'ul li {repeat: data(list);}
ul li h2 {content: iteration(id)}
ul li span {content: iteration(name); }';
The applied iteration cascades down child elements, although not quite as cascading as css, I think it still fits.... I guess the name could be changed if it doesn't really fit :)
How about Behavioral Data Sheets? :sweat_smile:
Scott
For the project, I was thinking keeping in the theme of Vision
and my other projects: Maphper
, Aphplication
a nice crossover might per Percephption
although it's a bit of a mouthful :p I think you're right that we need a specific name for the file format, though.
edit: or how about Transphporm
, might be a better description of what is actually happening.
Yeah, that sounds good. I like the play on PHP.
Though, the template system needs a name. Transphporm is just the interpreter/ compiler of BDSs for the template system. :wink:
Scott
The big question, really, is which properties should be inbuilt in the .bds
format and which should be added via extensions? I think at minimum the ones I've already done (content
, repeat
and display
) but possibly some others might be needed as well.
Javascript version: Tranjsform
;)
Enough to get people to understand the possibilities and enough to allow people to use .bds files successfully. I think the data binding is the first challenge, since behavior can always still be done over JS (as usual). More behavioral aspects could be the "add-ons". Kind of like building a view framework, but with the template system and .bds files as part of it all.
I wanted to go into this a few posts ago, but I deleted it all for a shorter more pertinent answer. I'll say it now. CSS was made to separate content from design. A great concept. The thing is though, content isn't just content. CSS covers the design.
So, what is the content?
I'd say, content is structure, data(state) and behavior. Structure comes from the meta languages. Data/state is what PHP or any other back-end delivers. And behavior is what JS delivers. With .bds, we are separating data from the structure (and why it is so cool), also with (hopefully) the secondary ability to also split behavior from structure, when it deals with data/state. That is my birds-eye view of Transphporm.
Scott
Data binding is pretty much done. I need to add a way of writing content to attributes and I've pretty much settled on
input.name:attr[value] {content: "whatever";}
Rather than
input.name: {attribute: value "whatever" }
As it's difficult to do concatenation with the latter, e.g.
input.name:attr[value] {content: "string1", data(something);}
although I'm open to suggestions.
I am also uncertain. Can't it stay with key: value pairs through and through? That way the format is standard. So, like this?
input.name {attribute: value; content: "what ever";}
or
input.name {attribute: placeholder; content: "what ever";}
And how would Transphporm know where input.name
is? Does the designer have to add the name attribute, for it to find it? And what if "name" is something coming from the data source? Or am I misunderstanding something again? I mean to me, it can't be properly dynamic, if any attribute in any tag, except for class or id attributes, are entered into the meta language.
That is why I had ended up with this.
#first-name {input: text; placeholder: i18n(first_name); trigger: validate; required}
ok. to stick with key value pairs, it would need to be this.
#first-name {input: text; placeholder: i18n(first_name); trigger: validate; required: true;}
LOL!
Scott
Noooo input.name is just a CSS selector that matches an input element with the class "name" in order to set its attribute "value" to "whatever"
So from
After applying the BDS becomes
Sorry for formatting, I'm on my phone.
Ah, Ok.
input.name {attribute: value; content: "Scott";}
Edit: Wait. Couldn't this work?
input.name {value; "Scott"}
Scott
And this?
input.name {
value: "Scott";
readonly: true;
disabled: false;
maxlength: 25;
}
Scott
Only if we're not allowing custom attributes such as data-whatever
. Otherwise, we'll have name clashes with other properties.
Hmm.... I'm going to start from scratch and go with a full use case of what I am imagining. Let's say we want to have this table and the behavior.
https://jsfiddle.net/0u4yes58/15/
I know it is possible to do the alternating colors with CSS. It's just an example of behavior.
The BDS for this kind of table template might look like this.
#table.[name_list] {id: [name_list]; body.onload: alternate([name_list]); style: 100%;}
#table.[name_list] tr {repeat: [name_list.row_count];}
#table.[name_list] th {repeat: i18n([name_list.th])}
#table.[name_list] td {repeat: [name_list.td]}
Note: maybe we could also come up with a way to reduce the repeated "#table.[name_list]" selectors? It has to know the "th" and "td" selectors are also nested in the "tr"s.
and the template would look list this.
<!-- name_list table @client -->
<table>
<tr>
<th>Some Example Text</th>
<th>Some Example Text</th>
</tr>
<tr>
<td>Some Example Text</td>
<td>Some Example Text</td>
</tr>
</table>
<!-- /name_list table -->
So, what is happening in my mind with this?
id
"name_list. It is also saying the id should be inserted into the table tag with id: [name_list];
. id
of "name_list" from whatever object is being requested from the template at the time. In other words, the call might be for blogs or authors, but the list is reusable for both. (Note: The brackets "[]" are just delimiters for the array variables, as I didn't know what else to use.)id
and it is triggered by "onLoad" through the body tag. Is this too much or too wild? Does it make sense? I think it does. Imagine the ugly conditional code the template would need to have to make this happen, not to mention the complexity for resuability of the JS behavior. Such conditional template syntax makes the template mofugly and hard to read..... for everyone!
This concept makes the designers job so much more simpler and the possibilities are so cool. It also takes and puts the data and behavior outside of the structure and simplifies both. I think. And the idea is, all that extra work that is normally needed for designers is reduced down to DEFT and BDSs. It properly splits responsibilities. It's awesome! :+1:
Well, it can be.
Scott
Man, my head is spinning. What about the issue of colspan and rowspan?
#table.[name_list] td { content: [name_list.sum]; colspan: [name_list.th.count];}
This would be added to the end of the above BDS and would tell DEFT to add the td to the end and span the whole number of available columns. Hmmm...
Scott
Personally this is going well beyond the original goal, and in the wrong direction.
#table.[name_list]
.
and [
are already CSS selectors. Although other selectors may be useful (although I don't really see why), I'd prefer to stick with pure CSS for element selection. Why reinvent the wheel? People already know CSS, let's use that<!-- name_list table @client -->
They need to know the special syntax here and it's moving back towards having special markup in the template. Not only that, this can't be achieved with DOM manipulation and will rely on ugly/slow regex.
And for comparison, here's how to do essentially what you want with the current implementation in this repo:
<table>
<tr>
<th>Some Example Text</th>
<th>Some Example Text</th>
</tr>
<tr>
<td>Some Example Text</td>
<td>Some Example Text</td>
</tr>
</table>
bds. Let's consider looping through users. We could add classes rather than targeting the element names here
table tr {repeat: data(users); }
table tr th:nth-child(1) {content: "Name"; }
table tr th:nth-child(2) {content: "Email"; }
table tr td:nth-child(1) { data: iteratation(name); }
table tr td:nth-child(2) { data: iteratation(email); }
/* The nth-child selector is implemented but the attribute property is not yet */
table tr td:nth-child(odd) {attribute: class odd}
table tr td:nth-child(even) {attribute: class even}
About the template author worrying about display logic, isn't that is often the case? However, I was also contemplating whether or not the @server or @client declarations might be better off in the bds itself.
Scott
I'll go back to saying: The same .bds, xml and data structure should be able to run and produce and identical result whether it's in PHP or javascript. @server
and @client
seem entirely redundant here.
Going back to:
About the template author worrying about display logic, isn't that is often the case?
What I'm trying to avoid is mixing markup and processing instructions. Your comment there is entirely processing instructions.
Yes, I am envisioning the use of processing instructions (behavior) being "coordinated" in the bds. The logic or the instructions themselves are in JS or Node or PHP or Java or .net. etc. Basically, what I am wanting is not only data binding, but also behavior binding. Without the behavior, we are back to CDSs. or DBSs. Data Binding Sheets. :smile:
On @server or @client being redundant, I thought about that some more and I understand what you mean. The decision for where BDSs need to be compiled or rather how, is a global one.
table tr {repeat: data(users); }
table tr th:nth-child(1) {content: "Name"; }
table tr th:nth-child(2) {content: "Email"; }
table tr td:nth-child(1) { data: iteratation(name); }
table tr td:nth-child(2) { data: iteratation(email); }
/* The nth-child selector is implemented but the attribute property is not yet */
table tr td:nth-child(odd) {attribute: class odd}
table tr td:nth-child(even) {attribute: class even}
This bds example is very specific and can only be used for a specific user list template with email and name fields. Couldn't it be possible to abstract the bds to be more common in someway? For instance, a list of data that should fit into a table with a varying number of fields and varying column titles, and varying values, etc. That was my intention with the [name_list] array or object.
I am coming from the idea that in the end, these values can be determined completely in userland and inserted by the application during runtime (compilation), meaning the BDS system would require and insert the variables needed to gather the data required to compile the template. It is sort of like the concept of Less or Sass for CSS, but for DBSs.
Scott
The problem is, you'll need some binding logic somewhere that says "field name
goes in this html element`
For something like what you suggest, it's very easy:
.users tr {repeat: data(users); }
.users td {content: iteration(attr(data-field));}
Then in my template I have:
<table class="users">
<tr>
<td data-field="name">User name</td>
<td data-field="email">Email</td>
</tr>
</table>
I can then use the same .bds file with any table of the class .users
.
My BDS could even look more like this.
@name_list = $nameList;
#table.@name_list.id {
body.onload: alternate(@name_list.id);
style: 100%;
.tr {repeat: @name_list.row_count;}
.th {repeat: i18n(@name_list.th)}
.td {repeat: @name_list.td}
}
Prerequisite: The alternate
and i18n
functions must be registered with the BDS system, otherwise it errors during compilation of this BDS. Also the $nameList variable must be available at compilation time. I know this is a lot more than what you intended BDSs to be, but can't you see the power?
Scott
I'd prefer to stick as closely to CSS syntax as possible as people are already familiar with it.
Ok. It is a start. :+1:
Scott
The problem is, you'll need some binding logic somewhere that says "field name goes in this html element`
I don't understand this though. Why can't the BDS system just compile a proper td for the field data it is given, in the order it is given? With the field name definition in the template, that puts the data order in the hands of the designer, which is not always wanted.
Scott
The thinking behind data()
and iteration()
is that it works like url()
and references an external resource. Adding real variables and annotations adds a lot of complexity to the format.
I don't understand this though. Why can't the BDS system just compile a proper td for the field data it is given, in the order it is given? That puts the data order in the hands of the designer, which is not always wanted.
What exactly is the designer doing here then? Defining a template that looks like this?
<table class="users">
<tr>
<td></td>
</tr>
</table>
What I'm struggling to see with your suggestion is that most of the time, we're not dealing with just flat tabular data. How would I do this user list:
<table class="users">
<tr>
<td class="name">Name</td>
<td class="email"><a href="mailto:email">email</a></td>
<td class="edit"><a href="user/edit/id">Edit</a></td>
<td class="delete"><a href="user/delete/id" onclick="return confirm('Delete this user?');">Delete</a></td>
</tr>
</table>
Using the format I'm proposing it's easy to do this:
.users tr {repeat: data(users);}
.users .name {content: iteration(name);}
.users .email a {content: iteration(email);}
/* Still not 100% sure on this way of writing to attributes but lets run with it for this example */
.users .email a:attr[href] {content: "mailto:", iteraton(email);}
.users .delete a:attr[href] { content: "user/delete/", iteration(id)};
.users .edit a:attr[href] { content: "user/edit/", iteration(id)};
Adding real variables and annotations adds a lot of complexity to the format.
I understand. But, why did Less and Sass get invented? To solve a problem, right? And I'll bet money BDSs will have that problem at some point too. The data will need to be "abstracted" to include userland variables and formatting.
What exactly is the designer doing here then? Defining a template that looks like this?
His job? :) His fun is no longer putting behavior and data in the structure, and design in CSS, but rather putting data and behavior in BDSs.
The table template would actually be more like what it should look like for the design, but with no functionality or real data.
<table class="list">
<tr>
<th>Name</th>
<th>Email</th>
<th>Functions</th>
</tr>
<tr>
<td><a href="">Test Name</a></td>
<td><a href="">some@test-email.com</a></td>
<td><a href="">Edit</a></td>
<td><a href="">Delete</a></td>
</tr>
</table>
The BDS could be:
@list = $list;
.@list.name {
.tr {repeat: @list.row_count;}
.th {repeat: @list.th; colspan: @list.functions.count;}
.td {repeat: @list.td.data; repeat: @list.functions;}
}
Scott
Actually, no. That won't work.
@list = $list;
.@list.name {
tr {repeat: @list.row_count;}
th {repeat: @list.th;}
th .functions {content: @list.function.name; colspan: @list.functions.count;}
td {repeat: @list.td.data; repeat: @list.functions;}
}
That's better. :smile:
Scott
how would you write the email address to the content of the <a>
tag (and set its href)?
As for repeating columns using a data set, how about this:
$data = ['users' => []];
$data['users'][] = ['name' => 'User 1', 'email' => 'user1@example.com'];
$data['users'][] = ['name' => 'User 2', 'email' => 'user2@example.com'];
.users tr {repeat: data(users); }
.users td.info {repeat: iteration(); content: iteration();}
.users th.info {repeat: data(users) 1; content: key();}
<table class="users">
<thead>
<tr>
<th class="info">heading</th>
<th> </th>
<th> </th>
</tr>
</thead>
<tr>
<td class="info"> user info</td>
<td><a href="#">Edit</a></td>
<td><a href="#">Delete</a></td>
</tr>
</table>
Where key()
is the array key.
how would you write the email address to the content of the tag (and set its href)?
Because an <a>
tag is in the structure, the data given must match and hold the link and proper attributes for the <a>
tag. Also, for the email, the data source should have some sort of meta data denoting it as an email, so Transphporm forms the HTML accordingly.
I also forgot something and changing things up a bit with more nesting.
@list = $list;
.user_list {
tr {repeat: @list.row_count;}
th {
repeat: @list.th;
.functions {content: @list.function.name; colspan: @list.functions.count;
}
td {
repeat: @list.td;
.functions {repeat: @list.td.functions;}
.delete {onclick: confirm("Are you sure?");}
}
}
So this is saying.
object.name_variable
. Like "user_list". The designer should know not to give a div this class name, for instance.<tr>
s according to the number of rows in the list object. <tr>
, insert all `<th>
s add the "functions" part of the array, add colspan so this only spans the number of functions available. <tr>
s, add <td>
s with the data in list.td and at the end list off <td>
s for the list.td.functions. The delete function must also get the confirm JS function injected.Piece of cake. :smile: LOL!
Edit: Ok. I see the class or identifier must be matched. It can't be as generic as I'd like it to be.
Scott
Because an tag is in the structure, the data given must match and hold the link and proper attributes for the tag. Also, for the email, the data source should have some sort of meta data denoting it as an email, so Transphporm forms the HTML accordingly.
This is where I find it falls down. Essentially we are using the BDS to transform a data structure into HTML. If we have to transform a data structure into a HTML-like structure (With knowledge of the tag and attribute names) before passing it to the BDS then we have at best a duplication of effort. You'll have to write a function to format the data in the BDS "sorta html" ready format. Then have the BDS change the "sorta html" into proper html.
If I want to add a single new element with a new link (Another table column) I need to edit the function that is extracting data from the backend to structure the data in a new way that represents the updated HTML. This is backwards, the person writing the function that assigns data to the BDS shouldn't care about the structure of the HTML, only the data it could potentially reference it should be up to the person who is writing the BDS to extract the required data and decided how it is applied to the template, not the person passing the data to the template.
Hmmm.....the data must have some form of metadata determining the type. This metadata is what Transphporm needs to know to do the proper formatting with the data. It "transphporms" it. :smile: I am liking that name more and more. If it is an email data type, then the appropriate tag is formed. In other words, tag attributes are data, not structure.
Oh, and the designer could also use the BDS to disable any linking.
.email {a: nolink}
Or add a function to the email field, for hiding the email address appropriately.
.email {a: hidelink()}
Which brings me back to what I said about the structure. Instead of the if/then normally needed in the structure with Twig to hide an email link for the non-admin, the designer just puts in the <a>
tag in the structure, indicating that links should be added for all instances where the data allows for it, like a name for a user or title for a blog. Then through the BDS, the designer just denotes special behaviors. Like
.email {a: showadmins()}
The functions make the system so flexible, as this is client code or pre-built plug-ins. There is no more if/then/else in a template..
As for repeating columns using a data set, how about this:
At least I got you thinking a bit in the same direction! :+1:
I can't say what I am wishing for is a better solution than the template syntax like Twig has. It seems it could get just as complicated. But still, the idea of splitting out data and behavior from the structure is intriguing. I mean. It solves the same problem that CSS solves with design and content. And that is cool!
Scott
Ok. To conform more to CSS. How about this?
.email a {link: false;}
or
.email a {link: showadmins();}
Scott
This is a list of all the web application behaviors. which should/ could be or are covered by CDSs.
[x] model data binding
[x] HTML element iteration
[x] data binding
[ ] even/ odd element identification
[ ] event firing
[ ] event listening
This list is not even close to finished. As we get documentation for each type of behavior, I'll link to them. I am not a designer, so any suggestions or additions to improve this list are very much welcome! As they come in, I'll add them.
Scott