How to share data between two elements in Polymer 2.0 - data-binding

I have the following elements, which simply has an attribute, which the element itself sets:
<link rel="import" href="../polymer/polymer-element.html">
<dom-module id="test-element-1">
<template>
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
</template>
<script>
/**
* `test-element-1`
* Test Element 1
*
* #customElement
* #polymer
* #demo demo/index.html
*/
class TestElement1 extends Polymer.Element {
static get is() { return 'test-element-1'; }
static get properties() {
return {
prop1: {
type: String,
value: 'test-element-1',
notify: true,
readOnly: true
}
};
}
}
window.customElements.define(TestElement1.is, TestElement1);
</script>
</dom-module>
and I'd like a second element to be able to use the same data:
<link rel="import" href="../polymer/polymer-element.html">
<dom-module id="test-element-2">
<template>
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
</template>
<script>
/**
* `test-element-2`
* Test Element 2
*
* #customElement
* #polymer
* #demo demo/index.html
*/
class TestElement2 extends Polymer.Element {
static get is() { return 'test-element-2'; }
static get properties() {
return {
prop1: {
type: String,
notify: false,
readOnly: false
}
};
}
}
window.customElements.define(TestElement2.is, TestElement2);
</script>
</dom-module>
I'd like Test Element 2 to be able to get the value of prop1 from Test Element 1:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>test-element-2 demo</title>
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../iron-demo-helpers/demo-pages-shared-styles.html">
<link rel="import" href="../../iron-demo-helpers/demo-snippet.html">
<link rel="import" href="../test-element-2.html">
<link rel="import" href="../../test-element-1/test-element-1.html">
<custom-style>
<style is="custom-style" include="demo-pages-shared-styles">
</style>
</custom-style>
</head>
<body>
<div class="vertical-section-container centered">
<h3>Basic test-element-2 demo</h3>
<demo-snippet>
<template>
<test-element-1 prop1="{{prop1Value}}"></test-element-1>
<test-element-2 prop1="{{prop1Value}}"></test-element-2>
</template>
</demo-snippet>
</div>
</body>
</html>
Here's the output of my demo though:
What is it that I'm doing wrong?

Since you are using index.html or any other HTML page to display the value of polymer elements, it cannot bind the value of prop1Value.
If you do same thing using a polymer-element then it will definitely work.
A property is declared implicitly if you add it to a data binding or
add it as a dependency of an observer, computed property, or computed
binding.
Polymer automatically creates setters for these implicitly declared
properties. However, implicitly declared properties can't be
configured from markup.

The issue is explained by #Osifara, if you want share property with children components you need to make the top level parent a webcomponent,
you need to do something like this:
<body>
......the other code
<dom-module id="my-index">
<template>
<demo-snippet>
<template>
<test-element-1 prop1="{{propIndexTest}}"></test-element-1>
<test-element-2 prop1="{{propIndexTest}}"></test-element-2>
</template>
</demo-snippet>
</template>
</dom-module>
<my-index></my-index>
......the other code
</body>
<script>
class MyIndex extends Polymer.Element {
static get is() {
return 'my-index';
}
static get properties() {
return {
propIndexTest: {
type: String,
value: 'We are sharing the same value'
notify: false
}
};
}
}
window.customElements.define(MyIndex.is, MyIndex);
</script>
So now becames your top level parent container and then you can use the Polymer to work with that.

Related

Transform for child element by parent element tailwind

I want transform child element rotate if parent element have class open. Example in css
ul li.open > a > .sidebar-collapse-icon {
-webkit-transform: rotate(-180deg);
-ms-transform: rotate(-180deg);
transform: rotate(-180deg);
}
How can I do it in tailwind?
Couldn't find the exact solution on Tailwind documentation. So tried this hack (instead of open, use group class):
<ul>
<li class="group">
<a>
<span class="sidebar-collapse-icon group-only:-rotate-180"></span>
</a>
</li>
</ul>
You should control css through JavaScript. Tailwind CSS is not relevant by question. I work with her and love this lib.
For example if you use:
Vanilla (pure) JavaScript:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.container {
height: 200px;
width: 200px;
}
</style>
</head>
<body>
<div class="container"></div>
<script>
const el = document.querySelector('.container')
if (el) {
el.style.backgroundColor = 'red'
}
</script>
</body>
</html>
React (Hooks):
import React, { useState } from 'react'
export default function MyComponent() {
const [isOpen, setIsOpen] = useState(false)
if (!isOpen) {
return null
}
return (<div className="some-tailwind-class"></div>)
}
Vue if-show directive:
<template>
<div if-show="!isOpen" class="some-tailwind-class"></div>
</template>
<script>
export default {
data() {
return {
isOpen: false;
}
}
}
</script>
Vue v-if directive:
<template>
<div if-show="!isOpen" class="some-tailwind-class"></div>
</template>
<script>
export default {
data() {
return {
isOpen: false;
}
}
}
</script>

Using Next.js how to add a class to body or _next div during SSR?

I have a use case where I have to add a class to a root element may it be body or _next during SSR. The class needs to be added conditionally/dynamically based on getInitialProps responses.
I want to know if such is possible in the current state of Next.js.
import React from "react";
import NextDocument, { Html, Head, Main, NextScript } from "next/document";
class Document extends NextDocument {
static async getInitialProps(ctx) {
const initialProps = await NextDocument.getInitialProps(ctx);
// Determine if class name should be added
return {
...initialProps,
shouldShow: true,
};
}
render() {
return (
<Html>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
{`#__next {
height: ${this.props.shouldShow ? "100%" : "0"}
}`}
</style>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default Document;

how to get a slot content in the css content property of a pseudo ;:before element (or :;after) in a web component?

Well, I think that the question is in the title :
in a web component, with shadowRoot, I want to use a slot text-content inside the content property of a pseudo ::before or ::after element.
This could make me gain much lines.
Would you have an idea, a proposal, a solution ?
You can achieve this with the use of CSS custom properties as the source for the content of ::before and ::after and the slotchange event:
<!DOCTYPE html>
<html lang="en">
<head>
<title>WC</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
class XTest extends HTMLElement {
constructor() {
super();
// add shadow dom and insert template content
const shadowRoot = this.attachShadow({ mode: "open" });
const template = document.getElementById("x-test-template");
const templateContent = template.content;
shadowRoot.appendChild(templateContent.cloneNode(true));
}
connectedCallback() {
// get all the named slots
const slots = this.shadowRoot.querySelectorAll("slot[name]");
[...slots].forEach(slot => {
// add a listener to run when the slot changes
slot.addEventListener("slotchange", event => {
// get the slot name
const name = event.target.name;
// get the slot content
const text = event.target.assignedNodes()[0].textContent;
// update the custom property
this.style.setProperty(`--content-${name}`, `'${text}'`);
});
});
}
}
customElements.define("x-test", XTest);
</script>
</head>
<body>
<template id="x-test-template">
<style>
:host {
display: inline-block;
position: relative;
font-size: 2rem;
padding: 2rem;
}
:host::before,
:host::after {
content: "";
position: absolute;
top: 0;
font-size: 1rem;
}
:host::before {
/* set the pseudo selector content to the custom property */
content: var(--content-before, "");
left: 0;
}
:host::after {
/* set the pseudo selector content to the custom property */
content: var(--content-after, "");
right: 0;
}
slot[name] {
display: none;
}
</style>
<!-- slot for ::before -->
<slot name="before"></slot>
<!-- default slot -->
<slot></slot>
<!-- slot for ::after -->
<slot name="after"></slot>
</template>
<x-test>
<span slot="before">B</span>
<span>Hello</span>
<span slot="after">A</span>
</x-test>
</body>
</html>
#lamplightdev
Thank you for your answer.
I was looking for something wich doesn't exist yet.
So I have chose a solution with css var and a setter to set it :
class extends HTMLElement {
set content(val) {
this.setAttribute('data-content',val);
}
constructor() {
...
and, of course :
:host::before {
content: attr(data-content);
...
This seems to be the lighter solution I may imagine.
I'd like to suggest to web's standards developpers to create a new css function : slot(name) witch, with attr(...), var(...) and calc(...), could help the use of pseudo elements inside a web component.
Could someone show me the way to present this proposal ???
I do apologize for my poor english language (I'm french, nobdy's perfect).

Data binding between two Polymer elements using polymer 1.0

Question
In the below example, how do I bind the obj.name variable of the <input> field in <test-element2> to <test-element> ?
Background:
Below is my code. I have two polymer elements. test-element has the data binded to obj.name. test-element2 has an input field which is observed by the function objChanged. Whatever value I change in the input field it changes and prints in test-element2 but the change isn't reflected in test-element. Can any body help get the value reflected to test-element1? I have a solution using this.fire("object-change") for when the text changes but I am looking for a solution without using event listeners.
One more thing is that I need to create an element from the script and it cannot be hardcoded in the HTML DOM.
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo</title>
<script src="../../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="../../bower_components/polymer/polymer.html"/>
</head>
<body>
<dom-module id="test-element">
<template>
<div>Hello <span>{{obj.name}}</span></div>
</template>
<script>
TestElement = Polymer({
is: "test-element",
properties: {
"obj": {
type: Object,
notify: true
}
},
observers: [
"objChanged(obj.name)"
],
"objChanged": function() {
var that = this;
console.log("First element in 1",that.obj);
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{obj.name::input}}"/>
</template>
<script>
Polymer({
is: "test-element2",
properties: {
"obj": {
type: Object,
notify: true,
value: {
"name": "Charlie"
}
}
},
observers: [
"objChanged(obj.name)"
],
ready: function() {
var element = new TestElement();
element.set("obj", this.obj);
this.appendChild(element);
},
"objChanged": function() {
console.log("changed in test-element2:", this.obj);
}
});
</script>
</dom-module>
<test-element2></test-element2>
</body>
</html>
If you include <test-element> in the <template> of test-element2 you can avoid using event listeners or observers. In this way test-element2 handles the data binding between the input and <test-element> for you.
Below is a live working example that maintains the obj property as you have set it up in your elements.
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="test-element">
<template>
<div>Hello <span>[[obj.name]]</span>
</div>
</template>
<script>
TestElement = Polymer({
is: "test-element",
properties: {
"obj": {
type: Object,
notify: true
}
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{obj.name::input}}" />
<test-element obj="[[obj]]"></test-element>
</template>
<script>
Polymer({
is: "test-element2",
properties: {
"obj": {
type: Object,
notify: true,
value: {
"name": "Charlie"
}
}
}
});
</script>
</dom-module>
<test-element2></test-element2>
Currently, imperative data-binding is not supported in Polymer 1.0 outside of <template is="dom-bind">.
Polymer 1.0 Node.bind() - Can I create a binding via javascript instead of double braces?
Binding imperatively
[1.0] Data-binding: Is there any way to do this imperatively?
I would recommend setting up observers like the example below or adjusting your requirements to include <test-element> in test-element2.
button {
display: block;
}
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="test-element">
<template>
<div>Hello <span>[[obj.name]]</span>
</div>
</template>
<script>
TestElement = Polymer({
is: "test-element",
properties: {
obj: {
type: Object,
notify: true
}
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{obj.name::input}}" />
</template>
<script>
Polymer({
is: "test-element2",
properties: {
obj: {
type: Object,
notify: true,
value: {
"name": "Charlie"
}
}
},
observers: ["objNameChanged(obj.name)"],
objNameChanged: function(newValue) {
Polymer.dom(document).querySelectorAll("test-element").forEach(function(element) {
element.notifyPath("obj.name", newValue);
});
Polymer.dom(this.root).querySelectorAll("test-element").forEach(function(element) {
element.notifyPath("obj.name", newValue);
});
}
});
</script>
</dom-module>
<test-element2></test-element2>
<button>Add test-element to <em>test-element2</em>
</button>
<button>Add test-element to <em>body</em>
</button>
<script>
var testElement2 = document.querySelector("test-element2");
var createTestElement = function(insertPoint) {
var testElement = new TestElement();
testElement.notifyPath("obj.name", testElement2.obj.name);
insertPoint.appendChild(testElement);
};
document.querySelector("button:nth-of-type(2)").addEventListener("click", function() {
createTestElement(Polymer.dom(document).querySelector("body"));
});
document.querySelector("button").addEventListener("click", function() {
createTestElement(Polymer.dom(testElement2.root));
});
</script>
If you choose to break out your elements into their own files, you could follow this Plunker example (by nazerke) demonstrating two-way data binding by having one component observing another's property.
Code
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://www.polymer-project.org/1.0/samples/components/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="parent-element.html">
<link rel="import" href="first-child.html">
<link rel="import" href="second-child.html"> </head>
<body>
<parent-element></parent-element>
</body>
</html>
parent-element.html
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="parent-element">
<template>
<first-child prop={{value}}></first-child>
<second-child feat1={{prop}}></second-child> In parent-element
<h1>{{value}}</h1> </template>
<script>
Polymer({
is: "parent-element",
properties: {
value: {
type: String
}
},
valueChanged: function() {
console.log("value changed");
}
});
</script>
</dom-module>
first-child.html
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="first-child">
<template>
<p>first element.</p>
<h2>{{prop}}</h2> </template>
<script>
Polymer({
is: "first-child",
properties: {
prop: {
type: String,
notify: true
}
},
ready: function() {
this.prop = "property";
}
});
</script>
</dom-module>
second-child.html
<link rel="import" href="http://www.polymer-project.org/1.0/samples/components/polymer/polymer.html">
<dom-module id="second-child">
<template>
<p>Second element.</p>
<h2>{{feat1}}</h2> </template>
<script>
Polymer({
is: "second-child",
properties: {
feat1: {
type: String,
notify: true,
value: "initial value"
}
},
ready: function() {
this.addEventListener("feat1-changed", this.myAct);
},
myAct: function() {
console.log("feat1-changed ", this.feat1);
}
});
</script>
</dom-module>
If you choose to break out your elements into their own files, you could use <iron-meta> to two-way data bind as described here:
Example Code:
<iron-meta key="info" value="foo/bar"></iron-meta>
...
meta.byKey('info').getAttribute('value').
or
document.createElement('iron-meta').byKey('info').getAttribute('value');
or
<template>
...
<iron-meta id="meta"></iron-meta>
...
this.$.meta.byKey('info').getAttribute('value');
....
</template>
If you choose to break out your elements into their own files, you could use <iron-localstorage> to two-way data bind as described here.
Example Code:
<dom-module id="ls-sample">
<iron-localstorage name="my-app-storage"
value="{{cartoon}}"
on-iron-localstorage-load-empty="initializeDefaultCartoon"
></iron-localstorage>
</dom-module>
<script>
Polymer({
is: 'ls-sample',
properties: {
cartoon: {
type: Object
}
},
// initializes default if nothing has been stored
initializeDefaultCartoon: function() {
this.cartoon = {
name: "Mickey",
hasEars: true
}
},
// use path set api to propagate changes to localstorage
makeModifications: function() {
this.set('cartoon.name', "Minions");
this.set('cartoon.hasEars', false);
}
});
</script>
<dom-module id="test-element">
<template>
<div>Hello <span>{{name}}</span></div>
</template>
<script>
Polymer({
is: "test-element",
properties: {
name: String
}
});
</script>
</dom-module>
<dom-module id="test-element2">
<template>
<input value="{{name::input}}"/>
<test-element name="[[name]]"></test-element>
</template>
<script>
Polymer({
is: "test-element2",
properties: {
name: String
}
});
</script>
</dom-module>

Preventing fancybox returning to top of page

In my website, I am using fancybox 2.1.5. when I open an image and close it I return to the top of the page unintentionally. The problem can be seen in the following minimal example
<!DOCTYPE html>
<head>
<style media="screen" type="text/css">
body {
height: 100%;
}
html {
height: 100%;
}
</style>
<link rel="stylesheet" type="text/css" href="css/jquery.fancybox.css?v=2.1.5" media="screen" />
</head>
<body>
<a href=#>
<img src="http://placehold.it/1000x600">
</a>
<a class="fancybox" href="img/Gallery/500x600.gif">
<img src="http://placehold.it/500x600">
</a>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="js/jquery.fancybox.js?v=2.1.5"></script>
<script>
$(document).ready(function() {
$('.fancybox').fancybox();
});
</script>
</body>
</html>
You will see that if you open and close the second image you will find yourself at the top of the page.
It appears that if I delete the initial style in the head
<style media="screen" type="text/css">
body {
height: 100%;
}
html {
height: 100%;
}
</style>
The problem disappears. if I erase only the body style or the html style the problem also disappears. In order for the problem to appear both body and html heights must be to 100%
Unfortunately I don't understand why this is happening. Can someone please explain?
Note: I have found solutions and hacks to this problem but I would like to understand why this is happening
Update: seems not to work if you trigger the Fancybox while on a URL that points to a tag with an ID (e.g., "https://example.com/#currentsection"). When you exit the Fancybox, it doesn't go to the top of the page, but does scroll to the top of the tag with the ID, even if you've set the autoFocus and placeFocusBack options to false. Strangely, it still works if your URL is pointed at #top.
Original answer
I found that when using Fancybox in Next.js, binding or configuring Fancybox with autoFocus set to false fixed this. It then seems that placeFocusBack property (default: true) will apply. Set it up like so:
npm install --save #fancyapps/ui
components/fancybox-wrapper.js:
// Fancybox UI wrapper for lightbox
// Thanks to https://fancyapps.com/docs/ui/fancybox/react
import React, { useEffect } from "react";
import { Fancybox as NativeFancybox } from "#fancyapps/ui/dist/fancybox.esm.js";
function Fancybox(props) {
const delegate = props.delegate || "[data-fancybox]";
useEffect(() => {
const opts = props.options || {};
NativeFancybox.bind(delegate, opts);
return () => {
NativeFancybox.destroy();
};
}, []);
return <>{props.children}</>;
}
export default Fancybox;
pages/_app.js:
import Fancybox from "../components/fancybox-wrapper";
import "#fancyapps/ui/dist/fancybox.css";
import { SSRProvider } from "#react-aria/ssr";
function MyApp({ Component, pageProps }) {
return (
<SSRProvider>
<Fancybox options={{ infinite: false, autoFocus: false }}>
<Component {...pageProps} />
</Fancybox>
</SSRProvider>
);
}
export default MyApp;
You can use native helper of fancy box to fix returning to top of page problem.
$('.fancybox').fancybox({
padding: 0,
helpers: {
overlay: {
locked: false
}
}
});
reference : http://davekiss.com/prevent-fancybox-from-jumping-to-the-top-of-the-page/

Resources