AngularDart: using template input variables in structural directives - angular-directive

I have learning AngularDart. Everything went well so for. But I am stuck with structural directives : I cannot figure out how to use the template input variables to implement my own structural directive.
I read many times this document: Structural Directives.
And, although the material below refers to AngularJS, I read this questions/documents:
Angular 2: How to access Template Input Variables in Structural Directives
Angular 2 Custom Structural Directive binding using template input variable is not working
How to use Angular structural directive with multiple inputs
Or, how I wrote a customized version of ngFor
It is said that from the micosyntax declaration "let v=value", Angular creates the template variable "let-v". However, I cannot use the name "let-v" in a template since "let-v" is not a valid name for a variable.
By the way, if you look at the explanation that is given here for the directive ngFor :
<div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackByHeroId"
[class.odd]="odd">
({{i}}) {{hero.name}}
</div>
<template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"
[ngForTrackBy]="trackByHeroId">
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
</template>
You see that, inside the template, the template input variable i is called i (not let-i):
<div [class.odd]="odd">({{i}}) {{hero.name}}</div>
I tried a LOT of things within the Dart code of my structural directive. But nothing works.
I read the source code for the directive NgFor. Something potentially interesting here :
viewRef.setLocal('first', identical(i, 0));
viewRef.setLocal('last', identical(i, len - 1));
viewRef.setLocal('index', i);
viewRef.setLocal('count', len);
However, I tried that with no success.
Here is the simple code I wrote:
File: lib/src/directive_my_dummy.dart
import 'package:angular/angular.dart';
#Directive(
selector: '[myDummy]'
)
class MyDummyDirective implements OnInit {
TemplateRef _templateRef;
ViewContainerRef _viewContainer;
MyDummyDirective(TemplateRef templateRef, ViewContainerRef viewContainer) {
_templateRef = templateRef;
_viewContainer = viewContainer;
}
#Input('let-d')
List<int> d;
void ngOnInit() {
print("One instance of MyDummyDirective is instantiated.");
EmbeddedViewRef vr = _viewContainer.createEmbeddedView(_templateRef);
vr.setLocal('d', [1,2,3]);
print(d.toString());
}
}
File: lib/app_component.html
<div *myDummy="let d=data">
This is a dummy test. {{d.toString()}}
</div>
<div *myDummy="let d=[1,2,3]">
This is a dummy test. {{d.toString()}}
</div>
<div *myDummy="let d=getData()">
</div>
<div *myDummy="let d=[1,2,3]; let name='Toto'"></div>
The full code can be found here.
Can you show me a basic example that illustrates the use of the template input variables ?

First, there are two entities that we call "template":
The component template.
The (structural) directive template.
The term "input template variable" makes reference to the (structural) directive template.
I think that it would be better to use the appellation "input directive template variable".
I'll use the appellation "input directive template variable" instead of "input template variable".
The role of an input directive template variable is to configure a (structural) directive template.
Where does the value of an input directive template variable come from ?
The answer is: the value of an input directive template variable gets assigned within the directive instance. You cannot define the value of an input directive template variable directly within the component template. For example, the code <div *myDummy="let d=10"> below will NOT assign the value 10 to the variable d.
The value of the input directive template variable is assigned from within the directive instance. For example:
TemplateRef _templateRef;
ViewContainerRef _viewContainer;
// ...
_viewContainer.createEmbeddedView(_templateRef);
_viewContainer.get(0).setLocal('data', 'MyDummyDirective.data');
And you write, within the component template:
<div *myDummy="let d=data">
I give a simple example:
lib/src/directive_my_dummy.dart
#Directive(
selector: '[myDummy]'
)
class MyDummyDirective implements OnInit {
TemplateRef _templateRef;
ViewContainerRef _viewContainer;
#Input('myDummyVariable')
String variable;
MyDummyDirective(this._templateRef, this._viewContainer);
void ngOnInit() {
// WARNING: the property "variable" has no value assigned within the constructor.
_viewContainer.createEmbeddedView(_templateRef);
_viewContainer.get(0).setLocal('data', 'MyDummyDirective.data');
print('MyDummyDirective.variable = ${variable}');
_viewContainer.get(0).setLocal('var', 'This is ' + variable);
}
}
lib/app_component.html
<div *myDummy="let d=data; variable:'value from the lib/app_component.html'; let v=var">
<p>This is a dummy directive.</p>
<ul>
<li><b>d</b>=<code>{{d.toString()}}</code></li>
<li><b>data</b>=<code>{{data}}</code> (makes reference to the instance of AppComponent)</li>
<li><b>v</b>=<code>{{v}}</code></li>
</ul>
</div>
lib/app_component.dart
import 'package:angular/angular.dart';
import 'package:myapp/src/directive_my_dummy.dart';
#Component(
selector: 'app-component',
templateUrl: 'app_component.html',
directives: [MyDummyDirective],
)
class AppComponent {
List<int> getData() => [100, 200, 300];
String data = 'AppComponent.data';
}
The result:
This is a dummy directive.
<ul>
<li>d=MyDummyDirective.data</li>
<li>data=AppComponent.data (makes reference to the instance of AppComponent)</li>
<li>v=This is value from the lib/app_component.html</li>
</ul>
EDIT:
As a picture often speaks better than words...

Related

How to include Handlebars partial in a string? (add it to the innerHTML of a DOM Element)

Is there a way to get the "string version" of a handlebars partial to include it in the innerHTML of an HTML element?
For instance, imagine I have a ToDo list, and I want to add a task everytime I click the button "Add Task", like this:
todo_list.hbs
<div id="todo-list">
</div>
<button onclick="addTask">Add Task</button>
And that I have a handlebars partial in the file "task.hbs":
task.hbs
<h1 class="task-title">The task is: {{title}}</h1>
<button id="delete-task">Delete task</button>
<script>
const button_delete_task = document.getElementById('delete-task');
button_delete_task.addEventListener('click', deleteTask);
function deleteTask () {
// delete task code here
}
</script>
My question is: How could I create a Task partial everytime the button "Add Task" is clicked? Something like this:
<div id="todo-list">
</div>
<button onclick="addTask">Add Task</button>
<script>
function addTask() {
const todo_list = document.getElementById('todo_list');
todo_list.innerHTML += {{> Task title="A new task"}};
// More code here...
}
</script>
I have also tried enclosing the partial with backticks (`{{> Task title="A new task"}}`), and quotes ("{{> Task title='A new task'}}") as well as read many posts on this subject, but all of them use handlebars.js, not express-handlebars.
I am using express.js for the backend, and therefore, express-handlebars as the view engine. In advance, thanks a lot for your help!
I managed to solve the issue!
It turns out that enclosing the partial with backticks works! The problem was that my partial had <script></script> tags.
Imagine my task.hbs looked like this:
<div>
<script></script>
</div>
then, the processed version of todo_list.hbs would look like this:
<div id="todo-list">
</div>
<button onclick="addTask">Add Task</button>
<script>
function addTask() {
const todo_list = document.getElementById('todo_list');
todo_list.innerHTML += `<div>
<script></script>
</div>`;
// More code here...
}
</script>
This would be valid in a normal HTML file, but it looks like handlebars process the closing script tag that is inside the string (</script>) as a normal tag, and with it, closes the <script> tag of todo_list.hbs.
The solution I found was to not use <script> tags into my partial (not a beautiful solution, but works for me!) and instead, declare the javascript code in another file, and import it into todo_list.hbs using <script> tags with the src parameter like this:
todo_list.hbs
<div id="todo-list">
</div>
<button onclick="addTask">Add Task</button>
<script>
function addTask() {
const todo_list = document.getElementById('todo_list');
todo_list.innerHTML += `{{> Task title="New task!"}}`;
// More code here...
}
</script>
<!-- JAVASCRIPT CODE REQUIRED BY TASK PARTIAL -->
<script src="/foo/bar/partials/Task.js"></script>
Where Task.js is the file containing the javascript of the Task.hbs partial:
Task.js
const button_delete_task = document.getElementById('delete-task');
button_delete_task.addEventListener('click', deleteTask);
function deleteTask () {
// delete task code here
}
And with this changes, Task.hbs would look like this:
Task.hbs
<h1 class="task-title">The task is: {{title}}</h1>
<button id="delete-task">Delete task</button>
You are very close to getting this to work.
As you have noted, your Handlebars is executing on the server-side. In the case of your partial, you are trying to have it render within a script block. In order for the result to be valid JavaScript, you would need have quotes around the output of the partial so that it will be a valid JavaScript string. Therefore:
todo_list.innerHTML += "{{>Task title='A new task'}}";
Which, when rendered, would result in:
todo_list.innerHTML += "<h1>The task is: A new task</h1>";
It should be noted that quotes in your partial could be problematic. For example, if the <h1> in your partial had a class <h1 class="task">, the resultant JavaScript would now be invalid because the quote after the = would be interpreted as the closing quote of the JavaScript string. Therefore, you would need to be sure to either escape the quotes in your partial or ensure they are different from those used to wrap your partial call (a single-quote ('), in this case.
todo_list.innerHTML += "<h1 class=\"task\">The task is: A new task</h1>";
Additionally, you have an inconsistency with the id of your <div>. The tag has id="todo-list" (with a dash); but your JavaScript has document.getElementById('todo_list') (with an underscore). Those will need to be consistent.
Update
As #Sharif Velásquez Alzate noted in comments, the quotes will not work when the partial contains line-breaks because JavaScript strings cannot span multiple lines (unless each line ends with a \ to signify that the text continues to the next line. However, a template literal, using back-ticks, will support text with line-breaks.
Therefore, a better solution is:
todo_list.innerHTML += `{{>Task title='A new task'}}`;

What is the most reliable way to fetching the scroped CSS attribute in vuejs?

In vuejs the elements are assigned an attribute starting 'data-v-***'
I could not find any docs about fetching this value so ended up using refs and grabbing the attributes of the main node:
<template>
<div class="m-colour-picker" ref="thisContainer">
...
</div>
</template>
const attributes = this.$refs.thisContainer.getAttributeNames();
let dataAttribute = '';
attributes.forEach((attribute: string) => {
if (attribute.substring(0, 5) === 'data-') {
dataAttribute = attribute;
}
});
But it feels a little forced.. is there a method in vue to fetch this already built in?
That has little to do with Vue.js. Data attributes for any element are automatically synced with it's internal dataset object.
Example:
console.log(foobar.dataset);
console.log(foobar.dataset.vFoo);
console.log(foobar.dataset.vBar);
// notice how data attributes containing more than the initial data- dash
// are automatically transformed to camel case:
// data-v-foo-bar ===> dataset.vFooBar
console.log(foobar.dataset.vFooBar);
// if all you care about is the names of the attributes:
console.log(Object.keys(foobar.dataset));
<div id="foobar" data-v-foo="bar" data-v-bar="baz" data-v-foo-bar="foobaz"></div>

VueJS: :class, condition with index

I'm creating a list of (thumbnail) 2 images, and #click each image should be expanded to max-width, by adding said class ('max-w-full') to the classes. The class is added by setting the array entry with the same index nr. as the image (imgclicked[0], imgclicked[1]) in the list to 1.
data:() {
imgclicked=[0,0], //by default both are set to 'small'/not 'max-w-full'
},
the template part looks like this:
<template v-for="(image,index) in images>
<a #click="zoomImgClicked(index)">
<img :src="image.filename" :class={'max-w-full' : imgclicked[index]==1,'w-12' : imgclicked[index]==0}">
</a> // using this.imgclicked[index] gives me the error 'this.imgclicked[0] is undefined'
</template>
on click the method zoomImgClicked() ist launched:
zoomImgClicked: function(i){
if(this.imgclicked[i]==0){
this.imgclicked[i]=1
}else{
this.imgclicked[i]=0
}
},
But this is not working. If I open the vue console and change the values of imgclicked[0] manually from 0 to 1 it works (images ae being resized). Also I see the method doing it's work, when logging in the console, the values are changed from 0 to 1 and vice versa. But it's not reflected on the page #click.
Why is that?
Please read Change Detection Caveats in the Vue docs.
Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
You should use the $set method for this:
this.$set(this.imgclicked, i, 1)
try with array syntax:
<img
:src="image.filename"
:class="[
{
'max-w-full': imgclicked[index]==1,
'w-12': imgclicked[index]==0
},
'static class if need'
]"
>

How to specify multiple dynamic attributes by single computed prop in VueJS

I have this html element:
Link text
I want to add data-tooltip and title attributes dynamically by condition:
Link text
Is there any way in VueJS to add multiple dynamic attributes at same time:
<!-- instead of this: -->
Link text
<!-- something like this: -->
<a href="javascript:" ...tooltipAttributes >Link text</a>
You could take advantage of v-bind on the DOM element you wish to apply multiple attributes to based on some dynamically changing condition.
Here's a Plunker example demonstrating how you might go about it.
Take note of the object returned:
computed: {
multiAttrs() {
return this.showAttrs ? {
'data-toggle': 'tooltip',
title: 'Some tooltip text',
} : null;
}
}
You should be able to use v-bind="tooltipAttributes"
the docs here https://v2.vuejs.org/v2/api/#v-bind have more info, but the key part is under usage
Dynamically bind one or more attributes, or a component prop to an expression.
From the Docs:
1. You can dynamically bind multiple attributes/props to a single element by using v-bind:
(no colon, no extra attribute, just v-bind)
<a href="#" v-bind="tooltipAttributes" >Link text</a>
2. And then declare the variable in the computed section:
(you can also declare it in the data section, but that would require manual direct value changes)
computed() {
return {
tooltipAttributes: {
title: 'Title',
'data-toggle': this.toggle === true && !disabled
}
}
}
Note: Attributes with dashes/hyphens - in them (e.g. data-toggle) need to be a string because Javascript doesn't recognize - as a valid symbol in variable naming.
This is THE SAME AS:
<a href="#" title="Title" :data-toggle="this.toggle === true && !disabled" >Link text</a>

How to dynamically generate css class inside an each statement for an Ember View

<div>
{{#each value in controller}}
<div {{classNameBindings "col-lg-{{value}}"}}>{{value}}</div>
{{/each}}
</div>
Above is my partial view.
I want to generate classes like: col-lg-1, col-lg-2 etc
My controller is:
App.circleController = Ember.ArrayController.extend({
setupController: function(controller) {
controller.set('content', [1,2,3,4,5,6,7]);
}
});
why I get error: assertion failed: an Ember.CollectionView's content must implement Ember.Array. ?
I use a custom view to apply dynamically-named classes to items inside of an each helper. The class name is generated inside the view by a property than depends on a supplied index.
App.ItemView = Ember.View.extend({
classNameBindings: ['itemClass'],
index: null,
itemClass: function() {
return 'class-'+this.get('index');
}.property('index')
});
In the template, I supply the index through a {{view}} helper inside each iteration.
{{#each value in controller}}
{{#view App.ItemView indexBinding="value"}}
Item #{{value}}
{{/view}}
{{/each}}
For a closer look, check out this jsfiddle.