Closed lega911 closed 8 years ago
Hm, this sounds interesting and seems to be something i thought about a few days before. Defining components only using javascript might result in something jQuery already provides: long spaghetti code. :-)
I would like to see defining components more like a template definition to simplify html structures. This already works well using al-repeat, but it is not as flexible as defining custom template blocks. This is, what i meant with an alias-directive in #76.
Here an example what i think about:
<div al-component="'teaser':firstTeaser"></div>
<hr />
<div al-component="'teaser':secondTeaser"></div>
<script type="component:teaser">
<h3>{{headline}}</h3>
<img src="{{img.src}}" />
<p>{{desc}}</p>
</script>
which would result in this:
<h3>{{firstTeaser.headline}}</h3>
<img src="{{firstTeaser.img.src}}" />
<p>{{firstTeaser.desc}}</p>
<hr />
<h3>{{secondTeaser.headline}}</h3>
<img src="{{secondTeaser.img.src}}" />
<p>{{secondTeaser.desc}}</p>
The 'alias' effect in this is, that you define a data structure as template, which will automatically be prefixed using an 'alias' of a scope variable. The only issue might be that the developer has to ensure that the template will be compatible. Also it might introduce some unwanted complexity (e.g. if the user tries to build recursive/hierarchical structures e.g. when rendering children of a component:
<div al-component="'teaser':firstTeaser"></div>
<script type="component:teaser">
<h3>{{headline}}</h3>
<img src="{{img.src}}" />
<p>{{desc}}</p>
<div class="recommended item" al-repeat="it in children">
<div al-component="'teaser':it"></div>
</div>
</script>
Just my thoughts - now make your own with my ideas. ;-)
Component implies that beside of a template, it has some logic / js code. Where do you propose do (place) this?
It is possible to make your "al-component" with current version.
example: http://jsfiddle.net/lega911/rovyaevp/ but it link via {{teaser.headline}}
is it necessary to have "direct" links, like {{headline}}
? It's possible, but it will have the same problems.
Awesome!
I "tried" to define some structure how to place the logic, but it does not work, maybe because i converted it to CS :D https://jsfiddle.net/yr57hnxe/2/
My basic idea is to define a template controller, which translates into a normal alight controller and also registers the DOM structure as an alight template. The main problem would be to force the processing of al-component AFTER al-template-ctrl - this is why i tried using priority property, but i think i have to trigger the al-component initialization after the dom has been parsed completely. The next problem is to map the abstract template controller definition when assigning the scope of the al-component directive. The scope must copy all properties of the controller definition to fetch all dependencies of the abstract template controller.
I think the whole process should be a bit improved, but i think this helps to get the idea.
jsfiddle has problems with CoffeeScript, a script starts too late, so you have to start alight manually: alight.bootstrap('#app')
priority works only within a single element (e.g. it solves al-repeat and al-click problem in one element), so you need place your template before using it. you can call a controller* as simple function, I've made alight.components.teaser in exmaple http://jsfiddle.net/lega911/hjq4h0xg/
also you can make a directive instead of controller and declare it in template (al-ctrl makes isolated scope)
jsfiddle has problems with CoffeeScript, a script starts too late, so you have to start alight manually
Thank you, i always forget this :D
priority works only within a single element (e.g. it solves al-repeat and al-click problem in one element), so you need place your template before using it.
Ah ok, maybe this should be documented.
Is it possible to "delay" initialization of al-component directive to solve the issue that al-component would be called before al-template-ctrl? Its hard to force the user to define templates before using them. A better way might be invoking a "ready" handler after component initialization finished. I give it a try.
you can make al-app for templates and al-app for your application, and bootstrap al-app for templates first.
yay, this is an very interesting approach!
No, i think this would not work. If i bootstrap templates first they will try to fill scope variables, dont they?
no, only alight.templates fixed - http://jsfiddle.net/lega911/hjq4h0xg/
Hm, i dont like this approach: if you want to standardize this as standard alight directive, it is unclear for a developer why he must instantiate two times to use the templates directive.
When you like to standardize this for components, the templates should be implicitly loaded before processing of other directives start. So i assume we need to hook into al-app / bootstrapping?
it's why looking for id="teaser" is better.
Ok, i thought a lot. The fastest implementation would be using id references. The best developer experience would be having only standard directives. This could be achieved by hooking into the bootstrap process, iterating over directives if they contain a "pre-bootstrap" method which will be executed before binding. Currently there is a init method per directive, but the bootstrap process runs it only per element and not once per al-app / bootstrapped instance.
I think it the bootstrap process would be more flexible when it would run all used directive init methods before binding to each element, after this the binding and linking process can be executed. But this would be a deep modification of the core... :-/
OMG. Got an idea. al-template="teaser" should access a scope variable in parent scope and set the innerHTML of the template. al-component just watches this template variable for changes. :-) Example follows in a few minutes.
Ok, after hours i dont get it to work. Its hard to understand when i have to use which scope and which changeDetector. Heres my code: https://jsfiddle.net/jqpdLdb6/2/
The basic idea is to have a internal scope variable "$template" in rootscope. This variable is accessed by al-template to pre-fill it with a named property containing the innerHTML of the template element. al-component watches the variable for changes, when it changes it reads from "$template.teaser", sets the innerHTML of the component and creates a new child scope. The child scope get the reference of the parent scope of "firstTeaser" for the first component and "secondTeaser" for the second component to override the scope property "teaser". Then the child scope should be bound to the element. And the last step does not work as expected. I think my approach is not bad, but i dont get the main issue.
Also i do not understand why watching on scope.$parent.$watch does not work (example code below):
alight.d.al.component =
scope: false
stopBinding: true
link: (scope, element, expression, env) ->
args = expression.split ':'
bindComponent = (value) ->
# ....
# why does a watcher on scope.$parent not work?
scope.$parent.$watch "$templates." + args[0], bindComponent, {deep: true, OneTime: true}
why does a watcher on scope.$parent not work?
scope.$parent.$watch "$templates.args")
You try to $watch not a current scope (I will explain later), you can do:
scope.$watch('$parent.$template.args')
or
scope.$template = scope.$parent.template
scope.$watch('$template.args')
i dont get it to work.
There were a few problems, http://jsfiddle.net/lega911/s5g2rtap/ When you alight.bind an element with 'al-component', you should pass an option - skip_attr: ['al-component'], then alight skips 'al-component' in child binding, otherwise 'al-component' will be executed, then child 'al-component' call alight.bind with child 'al-component' which call alight.bind with 'al-component', again and again - recursion.
You've made a problem and try to fix it, I prefer not to make a problem at all. I would use searching by id, this approach is used in other frameworks.
About scope.$parent.$watch
So when you call scope.$parent.$watch, your parent can have a few CD, and your parent doesn't know which CD should serves your watch-expression. It's why you should take needed CD and call CD.watch
On the other side you can call scope.$watch (inside link function) because in this moment one of the CD is active, so scope.$watch calls activeCD.watch
any questions?
Angular2 implements something similar, but it doesn't give you chance to do this "parent.$watch", you should use events and props to communicate between components.
PLEASE put this great change detector explanation to the docs! :-) When one implements own directives or controllers it is necessary to understand how the process works to avoid wrong approaches, so this improves the change detector page with relevant information. I always thought there is only a single CD per scope.
Back to the code: Question for skip_attr: dont i need to add "data-al-component" when i want to skip it also as data attribute? I ask because it would be great to handle everything correct as data attributes - some templating engines do not allow non-standard html attributes so they will be dropped.
And yes, you are right, resolving tag ids might be more simple and even faster, but also more inconsistent, because currently no directive uses tag ids as parameter (as far as i know). A benefit of the directive vs. tag id is, that you can place multiple directives bound to the same name in different scopes. If you use tag ids, you can define them only once per DOM. So a directive would allow more dynamic usage. Also using a directive which has "template-ctrl" in it implies that this is a directive which also allows to define a controller with the same name - tag ids dont imply this. The binding between a template and a template controller a developer keeps in mind is more obvious.
So i would like to see using this directives as default directives, if you agree. I slightly modified it, @see: https://jsfiddle.net/s5g2rtap/2/
Instead of using a simple name like 'teaserTest' i tried also using a name like 'my.templates', but this would result in complex to resolve which the right controller is (camel case transformations etc).
Ok, next version: https://jsfiddle.net/s5g2rtap/3/ Now this is great to render e.g. a news stream of different object types and fetch the objects by its name.
Question for skip_attr: dont i need to add "data-al-component" when i want to skip it also as data attribute?
you can use: skip_attr: env.skippedAttr()
, env.skippedAttr() gives you a list of used directives. I've added it to the docs.
if you use tag ids, you can define them only once per DOM.
You can make a directive "al-template" which collects all child templates for this application into your scope, something like:
<div al-app="main" al-template>
<.. .app body...>
<script name="newsitem">
... a template
</script>
<script name="website">
... a template
</script>
</div>
Then you will have all app's template on start.
Yes i could do this, but i think it's still not flexible as https://jsfiddle.net/s5g2rtap/4/
If you put the al-template directive to the body tag and use several tracking javascript libraries (e.g. google analytics) then they will place a script tag at the end of the body. So this might result in templates the user is unsure how to address it. Using a separate directive is the most clean way in my opinion. So just give me please a clear answer: will you include it or not? Its ok for me if you dont like my approach. :-) But then i would like to talk about a external directive repository, which we can include in a custom build. ;-)
So just give me please a clear answer: will you include it or not?
No, I (not only I) think about isolated components in different way, and templates is not a problem there. The main problem is a communication between isolated components, combine one-way with two-way binding etc.
Its ok for me if you dont like my approach. :-)
It is why AL is flexible, anyone can make anything.
Have you seen new way to use events? https://github.com/lega911/angular-light/issues/123
But then i would like to talk about a external directive repository, which we can include in a custom build. ;-)
It's good idea, I've started one, but it's abandoned now :)
It is why AL is flexible, anyone can make anything.
for example for your approach to template, you can place $template to every scope automatically:
allTemplates = {}
alight.hooks.scope.push
code: '$templates'
fn: ->
@scope.$templates = allTemplates
http://jsfiddle.net/lega911/1em8jskc/ this function is called on scope creation, now it places global templates, you can place allTemplate to root if you want. It gives you an able to use scope.$templates without thinking about "$parent"
Also I changed:
template = scope.$compile(templateName)(scope.$parent)
to
template = scope.$parent.$eval templateName
they work the same way
scope.$parent.$eval templateName
You can call any functions from $parent, except $watch, $new
Ok, it seems you think about re-implementing web components, right? http://webcomponents.org/ I think there is no need for. When web components are realized, they will be natively supported by the browsers, until then you can use several polyfills. But i think it is necessary to have a flexible template structure in alight. If you realize this using web components or anything else does not matter in my opinion. But when you use web components you do not have to implement it yourself.
scope.$parent.$eval templateName
You can call any functions from $parent, except $watch, $new
Docs! ;-)
@dev-rke no, no web components)) something like react or riot components, but in simple way:)
skip_attr: env.skippedAttr()
also, in your case, you can just skip whole "top" element (of component), so you can use skip_top: true
instead of skip_attr, these options in docs since start.
Ok, it seems you think about re-implementing web components, right?
Not exactly, webcomponents is just a bunch of technologies which isn't supported by browsers. I want something that can help reuse js+template, and it should be isolated, and have nice api.
@fend25: I see the alight data binding like a simple way of templating. Web developers are familiar with several templating engines. Most templating engines allow defining "blocks" which can be used to extract template parts to reuse them easily. When you need to define a component, you definitively need to implement a javascript snippet. When you use a standard component directive there would not be a need for javascript implementation. E.g. when you need to render a directory tree structure, containing files and subdirectories, you would currently need to implement multiple loops. Using a template block would reduce this, because you are able to use recursion. Implementing this as components as you and lega911 propose would also require to define a javascript snippet which defines the component name (as i interpret from the links in the first post in this issue). I do not vote against your component approach, but i think it is overhead for the developer and he has no additional benefit to simple template blocks.
When you see some additional benefits i did not recognize, please let me know. :-)
I like your al-component, but I don't like "al-template-ctrl", and storing templates in a scope.
Using a template block would reduce this, because you are able to use recursion.
Maybe we could extend al-include for this, or make al-template, something like this:
<div al-include="'testComponent'" controller="'myController'" value="user1"></div>
<div al-include="'testComponent'" controller="'myController'" value="user2"></div>
<script id="testComponent" type="text/html">
{{testComponent.name}}
<div al-include="'testComponent'" controller="'myController'" value="testComponent.children"></div>
</script>
Dynamic:
<div al-repeat="it in data">
<div al-include="it.type" controller="it.type" value="it"></div>
</div>
if template isn't exist, then al-include="it.type"
loads it from server
Maybe we could extend al-include for this, or make al-template, something like this:
I would prefer al-template or al-block to define a template. If you do not like storing the html inside of a variable, we could also reference it by an element. But storing it in a variable has the advantage, that you can change the template on the fly and the component re-renders as it changes. :-) This would allow the developer e.g. to render additional fields on all items, e.g. a date field.
if template isn't exist, then al-include="it.type" loads it from server
No, please not. APIs should be "standalone", they should only be there for one thing. It might also be critical for security: if an attacker changes a scope variable (e.g. by an input field) suddenly an attacker could fire ajax requests against the server.
you can change the template on the fly and the component re-renders as it changes. :-)
you can use al-html for this
No, please not.
ok, angular.js works this way
@dev-rke, you are mixing up two diffirent things: components and templates
Component contains three features:
So, if you need simple and easy-reusable html snippet, template fits well. In the other case, if you need throw data down and bubble events up, manage the state and control underlying components, you have to write controlling code, i. e. javascript
I think, that define and keep bunches of components in an html file is a bit inconvenient. It should be code, not markup. And about coding convenience, IDEs highlight html in strings in javascript.
So, I am not opposite to you, I just offer to divide this two things: components and templates.
So, if you need simple and easy-reusable html snippet, template fits well.
I agree. What about this one? http://jsfiddle.net/lega911/oxjzocpa/
I've fixed two bugs, scope + stopBinding worked wrongly 8-\ and alight processed a content of script element, I think it shouldn't do this.
@lega911:
What about this one?
It would be ok for me, but i recommend to put all parameters required for the template into the template directive. The approach splitting the colon (":") was better in my opinion. The attribute "value" is not valid for a div tag, so i do not recommend this.
@fend25: I don't mix them up. I thought first we don't need template blocks or components in alight. Then i thought about directory trees and news streams which may need different renderings for objects. When building components there is more effort needed to achieve the same like when implementing a template block. A component aims to be re-usable in different projects, a template block not.
There are so much "component" libraries and parts of frameworks. I'd like to see NOT to reinvent the wheel again. It might be better to provide a good alight interface for existing component libraries. They are well tested and have their specific approaches, they are also well documented. When more component libraries exist, each component is less usable, because they need to be translated for other framework interfaces.
There are also drafts for news standards: https://www.w3.org/standards/techs/components#w3c_all The polymer lib seems to use the standard drafts. https://www.polymer-project.org/ I think people would like to implement components framework independent. So lega911 would invest time into a component framework which maybe would not be used.
I forgot to say: it's just my personal opinion, lega is the owner of the project, he decides what he does. :-) I would agree with a component implementation for alight, just want you to think about if there could be a better and more modern solution to integrate better with other frameworks and new web technologies.
lega is the owner of the project, he decides what he does
I want to have a democracy or something like this, because I'm not always right. I think it should be not real web components, just a tool for some cases, and 80% of developers will not need it. alight has a lot of things which are not used by majority of developers, e.g. hooks and text dircetives.
@dev-rke ok, now I see what about you are talknig
I have tried to make a component in several ways, and if case of recursion I can't think up a beautiful and short solution. of course it will be js, react-way
Now I understand the case and the solution you have described, and yes, al-template
seems to be a pretty feature.
But components are not only a recursive code. Let's won't mix it up. Components are really useful in lots of cases, it can extremely reduce amount of markup, especially with complex conditions and other logic garbage in markup.
About libraries for components: I don't know acceptable component library. polymer and web-components are off-standard libraries with poor browser support. All client-side libraries and frameworks (A1, A2, React, Vue, Riot, and, finally ALight) makes own component system. I think that's because components are really convenient and reusable. Let's make a working draft, make it as we want and then decide do we need it and do we want it.
By the way. Thank you very much for the builder. I thought about it for a month or more. It's really great.
@lega911:
lega is the owner of the project, he decides what he does
I want to have a democracy or something like this, because I'm not always right.
Please don't do this. Democracy won't work with software developers. You should respect everyones opinion but please make a decision on your own. When we discuss everything in all details, project development gets slower and slower. :-) Its better to have a monarchy with a parliament, because someone has to push things forward, even when all others do endless discussing. This is why Linus Torvalds is still the leader of the linux kernel and why he sometimes has trouble with community members, but in the end there is a solution for the problem which works. And this is the way to go.
I think it should be not real web components, just a tool for some cases, and 80% of developers will not need it.
I imagine alight components as something like jQuery plugins. You add the sources, write a few lines of code and they run. Alight components would be great in a library marketplace, where everybody can upload own components, including a simple user rating and everybody can also download the components. It would be great providing all components in bower. Some components ideas:
@fend25:
But components are not pieces of recursive code.
I do not understand this sentence.
Components are really useful in lots of cases
Sure, components aim to be re-usable across different projects. Web components aim to be re-usable across different projects and across different frameworks.
Web components are an upcoming standard. webcomponents.js is just a polyfill to provide these features in current browsers: https://github.com/webcomponents/webcomponentsjs/#browser-support Polymer is a framework on top of webcomponents which implements additional features. When every framework implements its own components, they are not framework independent. So developers implement components for angular.js or react or whatever, but these components are not re-usable in alight. But when alight would be compatible to components from angular.js or react there would already be a big component database. But to be compatible there would be much compatibility code written and even then there only are a few components which would work. Using web components would ensure, that in two or three years, when the standard is implemented natively in every modern browser, these could be used in alight and they also could be used without alight.
polymer and web-components are off-standard libraries with poor browser support.
This might be. I think about long term support. What currently has poor browser support will have better browser support in two or three years.
All client-side libraries and frameworks makes own component system. I think that's because components are really convenient and reusable. Let's make a working draft, ...
Working draft:
By the way. Thank you very much for the builder. I thought about it for a month or more. It's really great.
Thank you.
Some thoughts about al-template: A template is where additional html is defined, not when it is used / inserted. So it would be better to use al-render as name for the rendering process.
Another point is, that we do not define a real 'template', we only define a html snippet block. I'd call this 'partial', not 'template', because the partial is already a part of the current DOM. A template would be loaded from an external resource, e.g. using ajax - this is what al-include already does.
We also do not need to implicitly try to run a controller derived from the partial name / identifier. The user can implement a controller inside of a partial, this would reduce overhead of the directive. Now i am also fine using a selector. If one needs more flexibility e.g. to modify the html source dynamically, there is already al-html for this. And reading html from an existing partial into a scope variable is very simple, so this can be done in a controller.
var content = document.getElementById('template-comments').innerHTML
So this i my proposal for rendering a template block.
<div al-render="'template-comments'"></div>
<script id="template-comments">
<ul al-ctrl="myController">
<li al-repeat="comment in comments">
<h5>{{comment.title}}</h5>
<p>{{comment.description}}</p>
</li>
</ul>
</script>
I modified lega911's last example: https://jsfiddle.net/oxjzocpa/10/ I would like to finish the template / partial / block discussion first and standardize this, afterwards we can implement real components. :-) So, please discuss.
Ok, let's continue the discussion)
Why the template is in the <script>
tag (even with type text/html
)?
is this required or just an example?
Another point is, that we do not define a real 'template', we only define a html snippet block.
Template is just a markup. Look, jade, ejs and so on. So, since the subject of discussion is embeddable html block, the template is a really good name for it.
I am against of directive name al-render
. I think that al-template
or al-template-id
is more semantic and less confusing.
And reading html from an existing partial into a scope variable is very simple, so this can be done in a controller.
Why not to use al-html in this case? Why is separate directive is needed? I think that it is not necessary to place it in core, but it can be a good start for the out-of-core directives/features bunch (and maybe marketplace as you mentioned).
About your example: could you make an example with recursive tree, with two-three layers. By the way, https links to the jsfiddle don't work due to http link to the alight
Why the template is in the
<script>
tag (even with type text/html)? is this required or just an example?
You can also use a hidden div tag. To render a partial it is be required to have a part in the DOM which will be used as source for rendering.
Template is just a markup. Look, jade, ejs and so on. So, since the subject of discussion is embeddable html block, the template is a really good name for it.
No, e.g. Jade calls the feature "extends": http://jade-lang.com/reference/extends/ EJS has includes, but still no partials / blocks: see open feature request https://github.com/tj/ejs/pull/142 Includes differ from partials: An include means to require and render a template from a given path, we already have al-include for this. A partial means the needed content/html is already there, but outsourced in an own block.
You need to consider that a template can be defined and being used. Initially the term "template" assumes i can define something, what can be reused later. The term "render" means to apply something, in this case a given partial.
And reading html from an existing partial into a scope variable is very simple, so this can be done in a controller.
Why not to use al-html in this case?
Because al-render renders a block from a partial. al-html does not read data from a DOM structure, it only outputs html into the DOM.
I wanted to create a combination of both, but lega911 does not like this approach. I understand that, because it is better to separate features. That's the reason why i am now ok with using an id instead of reading the html into a scope-variable.
About your example: could you make an example with recursive tree, with two-three layers.
done, see https://jsfiddle.net/oxjzocpa/11/
By the way, https links to the jsfiddle don't work due to http link to the alight
I use automatic https conversion of links in my browser so all my links get transformed to https. This is why i'd like to see https support for angular-light.org, so i would not need to disable security for the fiddle anymore.
user can implement a controller inside of a partial
Yes, but I don't like extra isolated scope here {{$parent.group.name}}
, {{group.name}}
is better.
New thought, you don't need al-ctrl at all, if you want to make a specific js for every template - make a component, in other cases you can place your js in the main (parent) controller.
That is you should make click function in controller "data", then you can use it as al-click="$parent.click()"
and al-click="$parent.$parent.click()"
for children, but we can use "transparent" scope (scope where prototype = $parent), then you will able to use it as al-click="click()"
for any depth of template.
example: http://jsfiddle.net/lega911/c7bgy6w0/ works wrongly
fixed: http://jsfiddle.net/lega911/c7bgy6w0/
Look at <a href (click)="active=newsitem">set active</a>
, any depth of template, but (click) assigns variable "active" for "real" scope, not for transparent one, for scope which is in controller "data".
Also it should convert a property, id="news-line" -> scope.newsLine
PS: Angular2 have no this feature - al-include and analogs. Dynamic templates seem to be problem there (it's not so easy).
Accepted. Like it. Ship it. :-)
I don't really like name "render" for it, what versions are there?
Also, what if I want to pass a couple of arguments into, or want another name for an inner variable? On the other hand they are rare cases.
Also, "render" probably should $watch the prop if it is changed.
Why should you want to inject multiple variables? When we define a subtemplate, we need a reference which we can process. Multiple references could be realized using an object as parameter. And no, i think $watch is not required, because the template does not change. Scope changes are implicitly watched, arent they?
http://plnkr.co/edit/lM4wwJq28egHIJzS7pTX?p=preview http://plnkr.co/edit/8jeW3XkkPPE0OTrwcadD?p=preview http://plnkr.co/edit/BhUwynTHrat6Tm76unTo?p=preview