Callback in Polymer(); when element is shown - javascript

Is there a callback available in the Polymer({}) object which fires everytime the element is shown ?
ready is not suitable because it's called when the element is created on initial page load.
I need an event or callback every time the route changes and my element is displayed.
Why do I need this ? I have an element which is behaving differently if a certain request parameter is set. So I need to check on each load whether the parameter is set or not.
Edit:
I worked around my requirement by doing the stuff I need to be done on element display in my routing functions:
page("/app/list", function() {
document.querySelector("my-list").$.loadList.generateRequest();
app.route = "list";
});

In the meantime I came also accross app-route which as well has functionality to call methods on route or view changes.
You can read about it here:
https://www.polymer-project.org/1.0/toolbox/routing#take-action-on-route-changes
Here is a working example:
<!DOCTYPE html>
<html>
<head>
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html" />
<link rel="import" href="bower_components/app-route/app-location.html" />
<link rel="import" href="bower_components/app-route/app-route.html" />
<link rel="import" href="bower_components/iron-pages/iron-pages.html" />
</head>
<body>
<container-element></container-element>
</body>
</html>
<dom-module id="container-element">
<template>
<app-location route="{{route}}" use-hash-as-path></app-location>
<app-route route="{{route}}" pattern=":view" data="{{routeData}}"></app-route>
Page 1 | Page 2
<iron-pages selected="[[routeData.view]]" attr-for-selected="name">
<div name="page1">This is Page 1.</div>
<x-element1 name="element1"></x-element1>
</iron-pages>
</template>
<script type="text/javascript">
Polymer({
is : "container-element",
observers : [ "_viewChanged(routeData.view)" ],
_viewChanged : function(view) {
if (view) {
if (view === "element1") {
document.querySelector("x-element1").test();
}
}
}
});
</script>
</dom-module>
<dom-module id="x-element1">
<template><p>This is Element 1.</p></template>
<script type="text/javascript">
Polymer({
is : "x-element1",
test : function() {
console.log("Callback of Element 1 called.");
}
});
</script>
</dom-module>

Maybe the attached callback is what you're looking for.
This lifecycle callback is called when an element is attached to the DOM and should therefore be the right choice. It is always called after the ready callback.
From the polymer docs:
https://www.polymer-project.org/1.0/docs/devguide/registering-elements.html#initialization-order
attached: function() {
this.async(function() {
// access sibling or parent elements here
});
}

Related

VueJs 2: How to use one component function in another component?

I have 2 Components and need to use a function with prop from component A in component B. The function just hides/shows HTML Element. However, it doesn't work as it should.
My settings Component:
Vue.component('settings', {
props: {
settingsContainer: {
type: String,
required: true
}
},
template: `
<div>
<button #click="toggleOverlay">Settings</button>
</div>
`,
methods: {
toggleOverlay: function() {
if (this.settingsContainer === 'block') {
this.settingsContainer = 'none';
}else if (this.settingsContainer === 'none') {
this.settingsContainer = 'block';
}
console.log(this.settingsContainer);
return this.settingsContainer;
}
}
});
The component where I use it:
Vue.component('sample-comp', {
template: `
<div>
<settings
:settings-container="displaySettings"
></settings>
<div :style="{ display: displaySettings }">
<p>Some Text</p>
</div>
</div>
`,
data: function() {
return {
displaySettings: 'block' //toggleOverlay should change its value
};
}
});
Main App:
new Vue({
el: '#app'
});
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12" defer></script>
<!-- <script src="https://unpkg.com/vue#next" defer></script> -->
<script src="test-settings-bt.js" defer></script>
</head>
<body>
<section id="app">
<sample-comp></sample-comp>
</section>
</body>
</html>
I see in console that toggleOverlay changes the value but it does not affect my style (does not change displaySettings value).
I would recommend using either an event or the Vuex Store to pass the settings value (Use the vuex Store only if this is a part of a bigger more complicated Application and the settings have other influences).
It is better practice to have a well defined API to a component and letting the component itself handle it's appearance instead of altering Component X's appearance directly in Component Y.
Additionally as Phil pointed out in the comments, you should never alter props. They should be handled as read only.
Here is what you could do:
Emit an event in your settings component when the display should be updated. Either have two events, one for showing the overlay, the other for hiding it or emit only one event with a boolean payload.
Listen to the event / events on the sample-comp and alter your css attributes accordingly.
Kind Regards.

Polymer 1.6 and Polymer-cli, access app property from within a custom element

Using the polymer-cli tool, and the shopping cart boilerplate as a starting point, I made a simple mock-up to illustrate the use case.
Assume your index.html file includes "test-app.html" and the matching tag
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>My App</title>
<link rel="shortcut icon" sizes="32x32" href="/images/app-icon-32.png">
<meta name="theme-color" content="#fff">
<link rel="manifest" href="/manifest.json">
<script>
// setup Polymer options
window.Polymer = {lazyRegister: true, dom: 'shadow'};
// load webcomponents polyfills
(function() {
if ('registerElement' in document
&& 'import' in document.createElement('link')
&& 'content' in document.createElement('template')) {
// browser has web components
} else {
// polyfill web components
var e = document.createElement('script');
e.src = '/bower_components/webcomponentsjs/webcomponents-lite.min.js';
document.head.appendChild(e);
}
})();
// load pre-caching service worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
<!-- <link rel="import" href="/src/bewi-app.html"> -->
<link rel="import" href="/src/test-app.html">
<style>
body {
margin: 0;
font-family: 'Roboto', 'Noto', sans-serif;
line-height: 1.5;
min-height: 100vh;
background-color: #eee;
}
</style>
</head>
<body>
<span id="browser-sync-binding"></span>
<test-app id="test"></test-app>
</body>
</html>
Now, assume test-app.html containing the following (again a mere simplified copy of my-app.html):
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/app-route/app-location.html">
<link rel="import" href="../bower_components/app-route/app-route.html">
<link rel="import" href="test-element.html">
<dom-module id="test-app">
<template>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
test-element is loaded bellow
<test-element></test-element>
<div>
</div>
</template>
<script>
Polymer({
is: 'test-app',
properties: {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
},
baseUrl: {
type: String,
value: '/'
},
siteUrl: {
type: String,
value: 'http://fqdn.local'
}
},
observers: [
'_routePageChanged(routeData.page)'
],
_routePageChanged: function(page) {
this.page = page || 'view1';
},
_pageChanged: function(page) {
// load page import on demand.
this.importHref(
this.resolveUrl('my-' + page + '.html'), null, null, true);
}
});
</script>
</dom-module>
Now, the test-element.html
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="test-element">
<template>
<div> I am a test </div>
</template>
<script>
(function() {
'use strict';
Polymer({
is: 'test-element',
ready: function() {
console.log('READY');
console.log('find #test using document.querySelector', document.querySelector('#test')); // OK
console.log('find #test .siteUrl using document.querySelector', document.querySelector('#test').siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom', Polymer.dom(document.querySelector('#test')).siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom().node', Polymer.dom(document.querySelector('#test')).node.siteUrl); // undefined
console.log('find #test .siteUrl using Polymer.dom().properties', Polymer.dom(document.querySelector('#test')).node.properties); // {object} but, I'm guessing not the computed values of the properties
// So, how may I access the #test app's "siteUrl" property from within a custom element?
}
});
})();
</script>
</dom-module>
So, the real question is, how may test-element access the property "siteUrl" in the main app?
I'm planning to make this variable readOnly, and access it from other custom elements.
I'ld prefer this approach VS passing the siteUrl as an attribute to the test-element element..
What do you think?
The right way to pass information through elements is using the Data Binding system, i.e. "passing the siteUrl as an attribute to the test-elemet element"
You'll accomplish the Read Only requirement surrounding the variable with square brackets, like this [[siteUrl]] as described in Property change notification and two-way binding.
You can set a variable in a global environment as you said like
<script>
var myGlobalVar = 'is accessible in any element'
Polymer({
is: 'test-app',
// ...
});
</script>
and you can access it in every element.
BUT, global variables are not recommended as you may know. References about why in the links below.
Global Variables Are Bad
Why are global variables considered bad practice?
I've Heard Global Variables Are Bad, What Alternative Solution Should I Use?

Strange behavior in Polymer data-binding to an attribute

Using Polymer 1.0 I'm trying to bind to an attribute of a custom element, and just display it.
The custom element is in fact an <iron-input> list, that has an add and a delete button. I'd like to reflect any change in that list to the host. It also has a minItemSize attribute meaning it has at least this many elements. So I added a check to the observer, adding extra elements in case it goes under this number.
But when I bind to the attribute that holds the list, things get out of sync, and I can delete all of the inputs from the ui.
I have two <dyn-inputlist> elements. In one of them I don't bind to the data
attribute, in the other I do.
The first one behaves as expected: adds and removes on the button click.
The other doesn't work, because you can remove all input boxes. Even though the data itself is updated, and filled with extra items, for some reason the UI doesn't reflect this. (Checking the data property of the element does show that it has the correct number of items)
I also expect that if I set data={{myData}} on both dyn-inputlist element, they always display the same thing. But pressing add/remove buttons randomly on either component gets them out of sync.
Am I missing something?
Thanks in advance.
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="bower_components/webcomponentsjs/webcomponents.js"></script>
<link rel="import" href="components/dyn-inputlist.html"/>
</head>
<body>
<template is="dom-bind">
<dyn-inputlist min-item-size="4"></dyn-inputlist>
<div>{{mydata}}</div>
<dyn-inputlist min-item-size="4" data="{{mydata}}"></dyn-inputlist>
</template>
</body>
</html>
dyn-inputlist.html:
<link rel="import" href="../../polymer/polymer.html">
<link rel="import" href="../../iron-input/iron-input.html">
<dom-module id="dyn-inputlist">
<template>
<button on-click="removeItem">x</button>
<button on-click="addItem">+</button>
<template is="dom-repeat" items="{{data}}">
<div>
<span>{{index}}</span>
<input is="iron-input" bind-value="{{item.content}}">
</div>
</template>
</template>
<script>
Polymer({
is: 'dyn-inputlist',
properties: {
minItemSize: {
type: Number,
notify: true,
value: 1
},
data: {
type: Array,
reflectToAttribute: true,
notify: true,
value: function () {
return []
}
}
},
observers: ['_dataChanged(data.*)'],
addItem: function (e) {
this.unshift('data', {content: ""});
this.reflectPropertyToAttribute('data')
},
removeItem: function (e) {
this.shift('data');
this.reflectPropertyToAttribute('data')
},
_dataChanged: function (e) {
if (this.data != null) {
while (this.data.length < this.minItemSize) {
this.push('data', {content: ""})
}
} else {
this.data = [{content: ""}];
}
this.reflectPropertyToAttribute('data');
}
});
</script>
</dom-module>
EDIT:
This is the live code: http://jsbin.com/poquke/1/edit?html,output
I have played around a bit with your code and I noticed that it will work if you wrap the code in your changed handler in an async function. This fixed both issues that you described.
_dataChanged: function (e) {
this.async(function(){
if (this.data != null) {
while (this.data.length < this.minItemSize) {
this.push('data', {content: ""})
}
} else {
this.data = [{content: ""}];
}
});
}
I don't have a perfect explanation for this behaviour. I assume it is related somehow to the way Polymer handles the observation for changes. Each time you push to the data array in the changed handler, this in fact changes data and should in turn trigger the handler again.
No async is required if you simplify.
Here is the simplified code, this removes the repeated calls to _dataChanged when you push the minimum values, and allows polymer's built-in eventing system to take care of updating and notifying the other elements. A function: _createNewItem() is for creating an object. This simplifies where item object creation is handled.
http://jsbin.com/vemita/6/edit?html,output
The link and URL references have changed from the sample code in the question above to conform to the polymer element and demo page standards to be used with polyserve.
I've commented on your original code for why each line should or shouldn't be there. this includes the reason for the changes to _dataChanged
http://jsbin.com/ponafoxade/1/edit?html,output

Polymer HTML Import into Custom Element

Hey I'm beginning with Webcomponents and I built a little HTML Import Example, where I import a calander from another Website with VanillaJs and it works perfekt.
As the 2. Step I wanted to HTML import into a Polymer element, so that I can use the element over and over again. Here is my code:
<link rel="import" href="http://www.testsite.com">
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="event-calendar-element">
<template>
<div id="container" style = "width:220px;"></div>
</template>
<script>
var owner = document._currentScript.ownerDocument;
var link = owner.querySelector('link[rel="import"]');
var content = link.import;
var container = document.getElementById('container');
var el = content.querySelector('.slider-teaser-column');
container.appendChild(el.cloneNode(true));
</script>
</polymer-element>
In the other HTML document I use the custom element and I can see, that the import worked(the resources from testsite.com are loaded), but my Polymer element has no shadowDOM - the imported and selected element is not appended to my <event-calendar-element> :/
the container <div> is null and therefore the following error occurs: Cannot read property 'appendChild' of null
Any help appreciated ;)
The <div> is inside the <template> and since you're not using Polymer() you'd need to use template.content.querySelector() get at and modified it's content. Instead, you can do this in Polymer:
<link rel="import" href="http://www.testsite.com" id="fromsite">
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="event-calendar-element">
<template>
<div id="container" style="width:220px;"></div>
</template>
<script>
(function()
var owner = document._currentScript.ownerDocument;
Polymer({
ready: function() {
var content = owner.querySelector('link#fromsite').import;
var el = content.querySelector('.slider-teaser-column');
this.$.container.appendChild(el.cloneNode(true));
}
});
})();
</script>
</polymer-element>

How do I call methods on other elements?

<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="my-thing">
<script>
Polymer('my-thing', {
athing: function () { return 'hello' }
});
</script>
</polymer-element>
I want the use the element defined above in the element below and have access to the athing property.
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="./mything.html">
<polymer-element name="my-hello"">
<template>
<my-thing id="mything"></my-thing>
</template>
<script>
Polymer('my-hello', {
ready: function () {
this.$.mything.athing() // returns undefined
}
});
</script>
</polymer-element>
Nodes with id's are reflected in the $ hash, not in this, so
this.$.mything.athing(); // returns 'hello'
[Poster changed his example to include the missing .$]
After fixing the syntax, I cannot recreate your problem.
http://jsbin.com/tegefura/1/edit

Categories

Resources