JS variable binding in Polymer - javascript

I want to do some data binding with Polymer. I defined a custom element:
<dom-module id="my-voltage">
<template is="auto-binding">
<div class="circle">{{volts}}</div>
</template>
<script>
class MyVoltage extends Polymer.Element {
static get is() {
return "my-voltage";
}
static get properties() {
return {
volts: {
type: String
},
}
}
constructor() {
super();
}
}
customElements.define(MyVoltage.is, MyVoltage);
</script>
</dom-module>
Now, I want to dynamically get the volts to be bound to a JS variable. So the JS is like:
volt = JSON.parse(httpGet('some-api-call'));
voltage = volt.value.toString();
and I want volts to display the value of voltage. I'm trying document.getElementById("volt").setAttribute("volts", voltage);, but I keep getting "Uncaught TypeError: Cannot read property 'setAttribute' of null" at the attribute assignment. What's the proper way to do this in polymer?
I do have the element in html:
<my-voltage id="volt" class="circle" volts="-1"></my-voltage>

I see that you're using Polymer 2.0+. I've only been using previous versions.
Anyway, so you want to assign data to a child element? The problem here is that during the "ready" method, there are no guarantees that the child elements have loaded. If you would like to do something more advanced, like doing a form validation, you have to create an interval that loops until the child element actually exists.
var voltElement = {};
var intervalId = setInterval((function() {
voltElement = this.$.volt;
if (voltElement) {
//enter code here
clearInterval(intervalId);
}
}).bind(this));
The .bind(this) is only there so you can use the correct scope inside the anonymous function in setInterval.
However, the problem isn't that complex for you. What you can instead do is to create a property variable and assign that to the voltage property in the my-voltage element when it's created.
<my-voltage id="volt" class="circle" volts="[[voltage]]"></my-voltage>
volt = JSON.parse(httpGet('some-api-call'));
this.set('voltage', volt.value.toString());
Using this.set() makes Polymer send an event to elements using that variable if the variable has been already set on beforehand, like been given a default value.

Inside Polymer's components you should use native polymer operators.
So, next js code should be rewritten via polymer's operators.
document.getElementById("volt").setAttribute("volts", voltage);
In Polymer 1.9.1, this line will have next view:
this.$.volt.set("volts", voltage);
or
this.$$('#volt').set("volts", voltage);
In Polymer 2.0 or 3.0, you could use next syntax:
this.shadowRoot.querySelector('#volt').set("volts", voltage);
More info about the node finding in Polymer 1.9.1, you can find there:
https://polymer-library.polymer-project.org/1.0/docs/devguide/local-dom#node-finding
More examples with this.shadowRoot.querySelector, you can find there: https://polymer-library.polymer-project.org/3.0/docs/devguide/dom-template

Related

How to display a HTMLElement from a variable in an Angular component?

I store a dynamically generated HTMLElement in a variable in my component class. I would like to display it, but I have no idea how is that possible. I tried it like this:
Inside the component class:
elem: HTMLElement;
In the template:
<ng-container *ngTemplateOutlet="elem"></ng-container>
I get this error:
TypeError: templateRef.createEmbeddedView is not a function
If you have a naked HTMLElement, there's nothing Angular-y you can do. NgTemplateOutlet expected a TemplateRef, not a vanila HTMLElement object.
To insert an HTML Element into the DOM, simply use the appropriate DOM method for that. Which one you'll use entirely depends on the place where you want to use it.
For example, you could do
const wrapper = document.querySelector('div.wrapper')
wrapper.append(el) // pass in your element
You can also grab the reference to the wrapper in a more Angular way, by adding an Angular reference in the template on the element you want to select,
<div class="wrapper" #wrapper></div>
and using ViewChild decorator on a property where you want Angular to assign the ElementRef to.
#ViewChild('wrapper', { static: true, read: ElementRef })
public wrapperElRef: ElementRef<HTMLDivElement>
Once the view is initialized (you'll be notified when this happens via the ngAfterViewInit hook), you can grab the HTML element within by accessing `.nativeElement:
this.wrapperElRef.nativeElement //: HTMLDivElement
You can now do
this.wrapperElRef.nativeElement.append(elem)
That said, unless you know what you're doing (usually wrapping an existing library or integrating with an existing large system that you cannot yet refactor completely)
Most of the times it depends how you have created the html.
I suggest specifying in comments where are you receiving the variable which holds the HTMLElement, so I could give you the most effective way to do it.
But generally -
try using innerHTML binding in the parent element.
Typescript:
public elem: HTMLElement;
public getElement(): string {
return this.elem.innerHTML;
}
HTML:
<div innerHTML="getElement()"></div>

How to focus on a field?

I updated from Angular 2 to Angular 4 and in the docs it's written to use the Renderer2 instead of Renderer which is deprecated.
Now am looking into the Renderer source, but cannot find a way to invoke the focus() method as I used to.
Old method:
this.renderer.invokeElementMethod(element, 'focus', []);
What is the new aproach?
EDIT
What if the element i am focusing onto is obtained via QuerySelector?
For instance:
let inputField = document.querySelectorAll('.dialog input');
if ( inputField[0] ) {
inputField[0].focus();
}
Since its obtained via QuerySelector, the focus() method doesn't exist on it.
The invokeElementMethod is deprecated, and will not find its way back into the new renderer. To set focus to an element, you can simply use this now:
element.nativeElement.focus();
If you are using document.querySelectorAll, you are doing something not the angular way, and you should find another way to do it. If there is no other way to do it, then the same principle applies. The focus() method is plain javascript, so you can use it within angular2/typescript. Be sure to do the querySelectorAll inside the ngAfterViewInit hook of the component though:
ngAfterViewInit() {
let inputField: HTMLElement = <HTMLElement>document.querySelectorAll('.dialog input')[0];
inputField && inputField.focus();
}
Also you can use selectRootElement method of Renderer2.
For example:
constructor(private renderer: Renderer2) {}
this.renderer.selectRootElement('#domElementId').focus()
, where domElementId is id='domElementId' in your html
template reference variable :#inlineEditControl
<input #inlineEditControl class="form-control form-control-lg" [placeholder]="label">
#ViewChild('inlineEditControl') inlineEditControl: ElementRef; // input DOM element
this.inlineEditControl.nativeElement.focus();
If you are using Angular CDK, you can set focus using FocusMonitor.focusVia method that is part of #angular/cdk/a11y Accessibility module (A11yModule).
This way you can avoid any DOM manipulation and avoid referencing nativeElement altogether.
import { FocusMonitor } from '#angular/cdk/a11y';
export class ExampleComponent implements AfterViewInit {
#ViewChild('elem', {static: false}) elemRef;
constructor(private focusMonitor: FocusMonitor) {
}
ngAfterViewInit() {
// Programmatically set focus. Focus source is 'program'.
this.focusMonitor.focusVia(this.elemRef, 'program');
}
}
At this time, it appears that this task is not achievable without directly modifying the DOM. As others have said, the invokeElementMethod() is deprecated. However, you will find issues with using selectRootElement as well, as this is not its intended purpose and you will end up losing the children inside of your DIV. Here is a link to a SO question that explains selectRootElement in more detail (since the angular docs are atrocious):
Renderer multiple selectRootElement Issue (with plnkr provided)
As for now, it appears the only way to easily achieve your goal is to use a template reference variable, a view child, and the yourViewChild.nativeElement.focus() (though this practice is also not recommended)
There is also a suggested workaround using a FocusService found on this GitHub issue: https://github.com/angular/angular/issues/15674
In this issue, Tytskyi mentions that you can implement a FocusService and provide different implementations of focus() based on AppModuleBrowser and AppModuleServer. I'm not sure of the fine details of how that would work but it may be the only way to achieve this without using nativeElement at this time.
If you declare the correct type of your inputField var, you can use any JS query selector.
For instances declaring:
let inputField: HTMLInputElement = document.querySelector('.dialog input');
enables you to call
inputField.focus();
that it will work and no TS error will be thrown.

vue JS not propagating changes from parent to component

I am pretty new to Vue Framework. I am trying to propagate the changes from parent to child whenever the attributes are added or removed or, at a later stage, updated outside the component. In the below snippet I am trying to write a component which shows a greeting message based on the name attribute of the node which is passed as property from the parent node.
Everything works fine as expected if the node contains the attribute "name" (in below snippet commented) when initialized. But if the name attribute is added a later stage of execution (here for demonstration purpose i have added a set timeout and applied). The component throws error and the changes are not reflected . I am not sure how I can propagate changes for dynamic attributes in the component which are generated based on other events outside the component.
Basically I wanted to update the component which displays different type of widgets based on server response in dynamic way based on the property passed to it .Whenever the property gets updated I would like the component update itself. Why the two way binding is not working properly in Vuejs?
Vue.component('greeting', {
template: '#treeContainer',
props: {'message':Object},
watch:{
'message': {
handler: function(val) {
console.log('###### changed');
},
deep: true
}
}
});
var data = {
note: 'My Tree',
// name:"Hello World",
children: [
{ name: 'hello' },
{ name: 'wat' }
]
}
function delayedUpdate() {
data.name='Changed World';
console.log(JSON.stringify(data));
}
var vm = new Vue({
el: '#app',
data:{
msg:data
},
method:{ }
});
setTimeout(function(){ delayedUpdate() ;}, 1000)
<script src="https://vuejs.org/js/vue.js"></script>
<div id="app">
<greeting :message="msg"></greeting>
</div>
<script type="text/x-template" id="treeContainer">
<h1>{{message.name}}</h1>
</script>
Edit 1: #Craig's answer helps me to propagate changes based on the attribute name and by calling set on each of the attribute. But what if the data was complex and the greeting was based on many attributes of the node. Here in the example I have gone through a simple use case, but in real world the widget is based on many attributes dynamically sent from the server and each widget attributes differs based on the type of widget. like "Welcome, {{message.name}} . Temperature at {{ message.location }} is {{ message.temp}} . " and so on. Since the attributes of the node differs , is there any way we can update complete tree without traversing through the entire tree in our javascript code and call set on each attribute .Is there anything in VUE framework which can take care of this ?
Vue cannot detect property addition or deletion unless you use the set method (see: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats), so you need to do:
Vue.set(data, 'name', 'changed world')
Here's the JSFiddle: https://jsfiddle.net/f7ae2364/
EDIT
In your case, I think you are going to have to abandon watching the prop and instead go for an event bus if you want to avoid traversing your data. So, first you set up a global bus for your component to listen on:
var bus = new Vue({});
Then when you receive new data you $emit the event onto the bus with the updated data:
bus.$emit('data-updated', data);
And listen for that event inside your component (which can be placed inside the created hook), update the message and force vue to re-render the component (I'm using ES6 here):
created(){
bus.$on('data-updated', (message) => {
this.message = message;
this.$forceUpdate();
})
}
Here's the JSFiddle: https://jsfiddle.net/9trhcjp4/

In Aurelia, can I bind a function from my containing view-model to be called by my custom element?

I have a custom element which will take user input, and on [save] button click, I want to pass information to the parent view-model so I can send it to the server and move to the next section. I'm going to simplify this for example's sake:
my-element.js:
import { customElement, bindable } from 'aurelia-framework';
#customElement('my-element')
#bindable('save')
export class MyElement { }
my-element.html:
<template>
<button click.delegate="save()">Click this</button>
</template>
parent-view-model.js:
export class ParentViewModel {
parentProperty = 7;
parentMethod() {
console.log(`parent property: ${this.parentProperty}`);
}
}
parent-view-model.html:
<template>
<require from="./my-element"></require>
<div class="content-panel">
<my-element save.bind="parentMethod"></my-element>
</div>
</template>
For a demo, see (app.js and app.html represent parent-view-model.js and parent-view-model.html):
https://gist.run/?id=96b203e9ca03b62dfb202626c2202989
It works! Kind of. Unfortunately, this seems to be bound to my-element instead of parent-view-model, so in this example, what is printed to console is: parent property: undefined. That will not work.
I know I can utilize the EventAggregator to facilitate some communication between the custom element and the view-model, but if I can help it I'd like to avoid the added complexity.
You have two options for this. You could handle this using Custom Events, or you can do it using the call binding that Anj mentioned in his answer. Which one you use depends on your actual use case.
If you want the custom element to be able to call a method on your parent VM and pass data out of the custom element, then you should use a Custom Event as shown in this gist: https://gist.run/?id=ec8b3b11f4aa4232455605e2ce62872c:
app.html:
<template>
<require from="./my-element"></require>
<div class="content-panel">
<my-element save.delegate="parentMethod($event)"></my-element>
</div>
parentProperty = '${parentProperty}'
</template>
app.js:
export class App {
parentProperty = 7;
parentMethod($event) {
this.parentProperty = $event.detail;
}
}
my-element.html:
<template>
<input type="text" value.bind="eventDetailValue" />
<button click.delegate="save()">Click this</button>
</template>
my-element.js:
import { inject, customElement, bindable } from 'aurelia-framework';
#customElement('my-element')
#inject(Element)
export class MyElement {
eventDetailValue = 'Hello';
constructor(element) {
this.element = element;
}
save() {
var event = new CustomEvent('save', {
detail: this.eventDetailValue,
bubbles: true
});
this.element.dispatchEvent(event);
}
}
You would basically attach any data you need to pass on the detail property of the Custom Event. In the event binding declaration, you would add $event as a parameter to the function and then check the detail property of $event in your event handler (you could also just pass $event.detail if you wanted).
If you want the custom element to be able to call a method on your parent VM and have data passed in from the parent VM (or from another custom element or something), then you should use the call binding. You can specify arguments that will be passed to the method by specifying them in the binding declaration (foo.call="myMethod(myProperty)". These arguments come from the VM context where the binding is being declared, not from the Custom Element's VM).
I was able to solve this after trolling through the aurelia docs hub for a bit. I don't know all the nuances that may be involved, but for this simple example, I was able to fix it by doing the following simple change:
In parent-view-model.html (or app.html in the gist-run example), change save.bind="parentMethod" to save.call="parentMethod()"
I am still unsure how to pass data from the custom element into the parent view-model's method, however.
Here is the documentation from aurelia website.
To pass parametrized functions to custom child components write the following (according to documentation http://aurelia.io/docs/binding/basics#function-references).
Parent ViewModel
private _generate(myParam:string) {
return myParam + " world";
}
Parent View
<custom-comp generator.call="_generate(myParam)"></custom-comp>
Child View
<template bindable="generator">
${generator({myParam:"hello"})}
</template>
This has been updated for aurelia v2:
The call binding no longer assigns properties of the first argument pass to the call to the calling override context. This is unreasonably dynamic and could result in hard-to-understand templates.
In Aurelia 1, you would have used call bindings like this:
export class MyElement {
onChange;
onInternalButtonClick() {
this.onChange({ value: this.value });
}
}
<my-element on-change.call="propertyChanged(value)">
In Aurelia 2, the property name is now on the $event property passed to the callback. It's a minor change, but you now do this instead:
<my-element on-change.call="propertyChanged($event.value)">

Make a child component call his parent API in Polymer 1.0.0

I have two web components defined with Polymer 1.0.0 and my question is about accessing the parent public API
<dom-module id="x-gallery">
<template is="dom-repeat" items="{{photos}}">
<x-photo photo="{{item}}"></x-photo>
</template>
</dom-module>
<script>
Polymer({
is: 'x-gallery',
...
getAll: function() {
return this.photos;
}
});
</script>
<dom-module id="x-photo">
<template>
<img src="{{photo.src}}">
</template>
</dom-module>
<script>
Polymer({
is: 'x-photo',
properties: {
photo: Object
},
ready: function() {
// HERE ---
// How could I access the x-gallery.getAll public method?
// ...
}
});
</script>
As you can see I wonder how you could easily access the getAll public method from the children?
I've seen some documentation referring to event based solutions (listening to the child events) but that doesn't really fit with my need. Unless you told me that the only solution available..
Any ideas?
ready: function() {
this.domHost.getAll()
}
from documentation:
http://polymer.github.io/polymer/
"domHost" is the
"element whose local dom within which this element is contained"
In this way you can access the "parent" and its functions.
In my opinion it is not the right approach in Polymer framework.
I have used it, in my project, only to define callback functions to the parent.
(sorry for my bad English)
Confirmed..I have used both methods - 1. parent explicitly setting a child's property to point back to parent, and 2. the domHost. domHost is easier and better as it is built-in.
In method 1, you have to make sure that the child is ready before setting the property.

Categories

Resources