Polymer- add behavior without creating a custom element? - javascript

In Polymer 1.1, this is my index.html
<template is="dom-bind" id="app">
<paper-drawer-panel force-narrow id="the-drawer">
<paper-header-panel drawer>
<paper-toolbar></paper-toolbar>
<div> Drawer content... </div>
</paper-header-panel>
<paper-header-panel shadow="true"
mode="waterfall-tall"
main
class="fit">
<paper-toolbar>
I would like to add iron-resizable-behavior to catch resize events. The thing is, I don't want to make a custom element to do it. Is it possible to add iron-resizable-behavior to index.html without making a custom element?

If you check the Behaviors guide, it mentions that the whole purpose for behaviors is for them to be extended by custom elements, so you can't use them without extending them in an element.
You could either create a simple element which extends iron-resizeable-behavior and fires events you can listen to or just make that entire dom-bind into an element.
Then again, depending on what you need to do, using iron-media-query might be enough.

Related

Tracking user interaction based on clicked elements by class

For a research project, I am building a web tool to track users' interactions with specific elements that I need for later analysis.
Often, I have interactable containers that contain graphics or text elements, sometimes both. For instance:
<div class="interaction-field trackable" id="specific-interaction-id"> <!-- This id is what I want to track -->
<img id="img-id" src="path/to/img.png"/> <!-- But i am triggering either this... -->
<p> Some Text </p> <!--...or this -->
</div>
I style the container by its interaction-field class and have a javascript function that logs all interactions based on the trackable class. Now, I got two issues.
For the tracking, I want to store the container's id for simplicity, as in such cases the <img> and <p> belong together. However, most click events, for example, are only recognised on child elements.
Because of this, the parents' targetable class is not triggering, denying any logging. Since I want the parents' id as a compound trackable element, I would like to avoid giving the children the targetable class to avoid ambiguity and redundancy.
I do get the general layering problem and it is logical that I rather hit the children than their parents. But is there an elegant way to always pass the parents' classes and id's to make the logging easier? Or is there an even simpler solution that I am not seeing?
Thanks in advance!
You can delegate the click handler to the document. Then grab the clicked element and use closest to find the parent element's ID if it has the trackable class.
document.addEventListener("click",(e)=>{
let trackedEl = e.target.closest(".trackable");
if(trackedEl){
console.log(trackedEl.id)
}
});
<div class="interaction-field trackable" id="specific-interaction-id"> <!-- This id is what I want to track -->
<img id="img-id" src="path/to/img.png"/> <!-- But i am triggering either this... -->
<p> Some Text </p> <!--...or this -->
</div>
First of all, if your question is on javascript you should have showed us the corresponding code.
Now, if you have event listeners on parent and child elements you can handle this through propagation.
When an event is triggered there are three phases.
Capturing - It first goes up the tree of elements starting from the root up to the targeted element. This by default doesn't trigger anything
Target phase - It triggers the targeted element's event.
Bubbling - It goes back down the tree again all the way to the root. This phase does trigger the events of the parents.
Your parent's event should be triggering in the bubbling phase. First make sure
that you set it correctly. Make it trigger an alert or something that makes you know if its happening.
You can also decide to trigger it in the capturing phase but I don't you need that.
Once you know for sure the event works it should be triggered when you click on its children. From there it is only a matter of adding the id to your list.
You can check this page to learn more about event propagation:
https://javascript.info/bubbling-and-capturing
I think you might find exactly what you need in there.

Nextjs react: what is the div with the class selection_bubble_root for?

I'm new to Nextjs, doing setup for a nextjs react project. I used create-next-app to initialize code base, then realized that there's a weird div tag with the class 'selection_bubble_root' right inside body. Its visibility is set to hidden but it still spares an annoying empty block.
Does anyone know about it? What is it for or how to remove it? Many thanks!!
Not sure what that is for, but there is a thing called bubbling and it is triggered for example when you have a clickable element inside another clickable element.
something like
<div class="item1" onclick="do1()">
<div class="item2" onclick="do2()">
<div class="item3" onclick="do3()">
</div>
</div>
So when you click on item3 it will execute do3() first, but then it will also execute do2() and do1() as long as you dont disable it.
So this is called bubbling, it goes up from child to parent and the bubble root is item1 (in this case).
Opposite effect is called capturing

While using Bootstrap-Vue, how do you prevent a b-dropdown from closing when clicking on a nested b-input component?

I'm pretty sure I just don't understand how to implement Vue's Event Modifiers. According to that documentation, all I have to do is add this:
<!-- the click event's propagation will be stopped -->
<a v-on:click.stop="doThis"></a>
Here's how I interpreted the example into my pug code:
b-dropdown(text="Actions")
b-dropdown-item
b-form(inline)
.row
.col
b-input(#click.stop='' placeholder="#123")
b-button(:href='printCheck' variant="primary") Print Check
It looks pretty simple, however it's not working as expected. If you need more supporting info, just ask. And feel free to tweak the title; I wasn't sure if my question is a vue, bootstrap-vue, or javascript question.
Thanks for your time in advance,
Kevin
Since you're clicking on a component you should combine .native with .stop modifiers like so :
b-input(#click.native.stop='' placeholder="#123")
if you're using a simple HTML element like input you could use only .stop modifier:
input(#click.stop='' placeholder="#123")
You can now use the new <b-dropdown-form> sub-component for placing input fields into dropdowns.
You should avoid placing input controls inside <b-dropdown-item> (which renders an <a> as it's root element), or <b-dropdown-item-button> (which renders a <button> as its root element). HTML5 doesn't like interactive elements inside <a> or <button> elements.
<b-dropdown-form> does not auto-close the dropdown when it is clicked.

querying for elements that are inside binding helpers

In order to to get Polymer's data-binding without creating a custom element, I am using the "dom-bind" template helper. Later on, I am going to need to access the nodes inside the template so I can use masonry.js
to create a grid out of the data.
Here is the my template that is inside the main document:
<!-- Skills -->
<template is="dom-bind" class="careerSkills_consumer projects_consumer" id="resume-container">
<page-section id="resume">
<section-title>Skills and Projects</section-title>
<section-content>
<template is="dom-repeat" items="{{careerSkills}}">
<skill-category class="grid-item" title="{{item.header}}" skills="{{item.skills}}"></skill-category>
</template>
<project-showcase class="grid-item" projects="{{projects}}"></project-showcase>
</section-content>
</page-section>
</template>
The data itself is provided elsewhere and is irrelevant. The issue I am running into is that both dom-bind and dom-repeat seem to create local dom and put the result inside of it.
To create my grid, I need to access both the container for the grid, which will be the section-content element and the grid items, which are the skill-category elements inside the dom-repeat template.
If they all resided in the same document, I think could do (I am new to masonry, so this might not actually work):
document.addEventListener('WebComponentsReady', function () {
$('#resume section-content').masonry({
columnWidth: $('#resume skill-category')[0],
itemSelector: 'skill-category',
isFitWidth: true
});
});
But the queries don't seem to work because presumably the elements I need are hidden away from the main document in the shadow dom.
I was able to get access to the content inside #resume-container via:
Polymer.dom(document.querySelector('#resume-container')).node.content
However, I still can't get to the skill-category elements in the dom-repeat. This is getting kind of pedantic and I'm not even sure if it will work when masonry tries to do the positioning.
Is there a better way to go about this?
To be clear, this question is about how to properly gain reference to the content distributed inside of template helpers, but I would also appreciate any general advice to using polymer to do this sort of thing, where a custom element isn't exactly what I'm looking for since I'm only going to use the template in one spot and shadow dom is more hassle than help, but I need the data-binding.

What is a good way for a Angular directive to act as an facade to other elements?

This is more a generic question about Web Components, however I'll write the examples in Angular as it offers some more ways to handle this problems (like replace even if it is deprecated) and it is also more familiar to me and probably others.
Update
Because of this comment I think many problems I face are Angular specific, because of the way Angular "compiles" directives. (I can't easily add or remove a directive at runtime.) Therefor I don't search for a generic solution anymore, but for a Angular specific solution. Sorry for this confusion!
Problem
Say I want to create a menu bar which could look like this:
<x-menu>
<x-menu-item>Open</x-menu-item>
<x-menu-item>Edit</x-menu-item>
<x-menu-item>Create</x-menu-item>
</x-menu>
This could translate to this:
<section class="menu">
<ul class="menu-list">
<li class="menu-list-item">
<button type="button" class="menu-button">Open</button>
</li>
<li class="menu-list-item">
<button type="button" class="menu-button">Edit</button>
</li>
<li class="menu-list-item">
<button type="button" class="menu-button">Create</button>
</li>
</ul>
</section>
This is fairly trivial. The problems arise, if I want to configure my <x-menu-item> with (existing) directives/attributes. Sometimes an attribute should refer to the button. E.g. a click on <x-menu-item> should probably be proxied to the <button>, because it is the "real" interactive element inside <x-menu-item>.
<x-menu-item ng-click="foo()">Open</x-menu-item>
<!-- → possible translation -->
<li class="menu-list-item">
<button type="button" class="menu-button" ng-click="foo()">Open</button>
</li>
However other attributes should refer to the <li>. Say I want to hide <x-menu-item> I probably want to hide everything, not just the <button>.
<x-menu-item ng-hide="bar">Open</x-menu-item>
<!-- → possible translation -->
<li class="menu-list-item" ng-hide="bar">
<button type="button" class="menu-button">Open</button>
</li>
And than there are of course attributes which affect the <li> as well as the <button>. Say I want to disable the <x-menu-item> I probably want to style the <li> and I want to disable the <button>.
<x-menu-item ng-disabled="baz">Open</x-menu-item>
<!-- → possible translation -->
<li class="menu-list-item" ng-class="{ 'is-disabled': baz }">
<button type="button" class="menu-button" ng-disabled="baz">Open</button>
</li>
That is basically what I want to achieve. I know some solutions, but all have their downsides.
Solution #1: Generate template dynamically and handle attributes manually
I could replace the <x-menu-item> with a complete dynamic template and handle the attributes manually. It could look like this (not fully functional):
// directive definition
return {
restrict: 'E',
transclude: true,
template: function(tElement, tAttrs) {
var buttonAttrs = [];
var liAttrs = [];
// loop through tAttrs.$attr
// save some attributes like ng-click to buttonAttrs
// save some attributes like ng-hiden to liAttrs
// save some attributes like ng-disabled to buttonAttrs and liAttrs
// optionally alter the attr-name and -value before saving (so ng-disabled is converted to a ng-class for liAttrs)
// unknown attribute? save it to either buttonAttrs or liAttrs as a default
// generate template
var template =
'<li class="menu-list-item" ' + liAttrs.join(' ') + '>' +
'<button class="menu-button" ' + buttonAttrs.join(' ') + ' ng-transclude>' +
'</button>' +
'</li>';
return tElement.replaceWith(text);
}
}
This actually works quite well in some cases. I have a custom <x-checkbox> which uses a <input type="checkbox"> internally. In 95% cases I want all attributes placed on <x-checkbox> to be moved to <input type="checkbox"> and just some on a wrapper around <input type="checkbox">.
I actually handle ng-disabled here, too. In case you wonder how this could look like, here is an example:
angular.forEach(tAttrs.$attr, function(attrHtml, attrJs) {
buttonAttrs.push(attrHtml + '="' + tAttrs[attrJs] + '"');
if (attrHtml === 'ng-disabled') {
liAttrs.push('ng-class="{ \'is-disabled\': ' + tAttrs[attrJs] + ' }"');
}
});
Downsides: I need to decide where to place attributes I don't know beforehand. Should they be placed on the <button> or <li>? I think I want more attributes on the <button> than on the <li>, because my <x-menu-item> is basically a wrapped button and using it feels like you would use button. A developer would expect <x-menu-item> to work like a <button>. However it seems strange to not place unknown attributes on the root element (in this case <li>). One would also expect that attributes on <li> would affect <button>, if necessary (like a CSS class does). I also create my markup in JavaScript, instead of plain HTML.
Replace or don't replace
I know its deprecated, but sometimes I like to use replace my directive with my template. Say someone places an id on my directive, I like to move the id to the canonical element in the template representing the directive (.e.g. on a <x-checkbox> the id would be transferred to the <input checkbox="type">). So if somebody tries to getElementById he will get the canonical element behind it. If I don't replace the whole directive, I would need to decide which attributes (or all) should be removed on the directive, because they were moved to a different element. This can be buggy, if you miss something (and suddenly have the same id twice).
Solution #2: Use prefixed attributes
Similar to #1, but the user decides if an attribute should be used on the directive or on certain elements. It could look like this:
<x-menu-item li-ng-hide="bar" button-ng-click="foo()">Open</x-menu-item>
<!-- → possible translation -->
<li class="menu-list-item" ng-hide="bar">
<button type="button" class="menu-button" ng-click="foo()">Open</button>
</li>
Downsides: This one gets more verbose, but offers more flexibility. E.g. a developer could create a custom id for the directive, the li and the button. But what is with ng-disabled? Should the developer place a button-ng-disabled as well as a li-ng-class? That is cumbersome and error prone. So we probably need to handle those cases manually again...
Solution #3: Use two directives
If we can't decide how to handle our attributes, we could introduce two directives. That way we don't introduce artificially prefixed attributes.
<x-menu-item ng-hide="bar">
<x-menu-button ng-click="foo()">Open</x-menu-button>
</x-menu-item>
<!-- → possible translation -->
<li class="menu-list-item" ng-hide="bar">
<button type="button" class="menu-button" ng-click="foo()">Open</button>
</li>
Downsides: This isn't very dry and therefor error prone. I always have a <x-menu-button> in my <x-menu-item>. There will never be an empty <x-menu-item> nor a <x-menu-item> with a different child element. I also have the same problem with ng-disabled as in solution #2. A developer should be able to easily deactivate my whole <x-menu-item>. He shouldn't care to add a certain ng-class for styling purposes and disable the button on his own.
Solution #4: Use a generic interface
Limit your interface. Instead of trying to stay generic (which is nice, but cumbersome) one should limit its interface. Instead of special handling for ng-disabled, ng-hide and ng-click try to identify your common use cases and offer a more custom interface to use them. That way we only handle explicitly defined attributes in a special way.
<x-menu-item hidden="bar" action="foo()" disabled="baz">Open</x-menu-item>
<!-- → possible translation -->
<li class="menu-list-item" ng-show="bar" ng-class="{ 'is-disabled': baz }">
<button type="button" class="menu-button" ng-click="foo()" ng-disabled="baz">Open</button>
</li>
Downsides: This approach isn't very intuitive. Every Angular developer knows ng-click. No one knows my action attribute.
(Partly) Solution #5: Proxy DOM events
Instead of moving a ng-click from the directive to buttons it can't sometimes be useful, if the directives listens for clicks on itself and automatically trigger a click on the button (or the other way around - it depends on the use case).
Solution #6: Dirty queries.
See the answer from #gulin-serge for details. Short explanation: "Decorating" existing directives like ng-click with custom logic, if it is used on a certain element and prevent using default behavior.
Downsides: Every ng-click will be checked, if it is used on a certain element even if this is not the case. This checking is a small overhead. You must also remove the default behavior of ng-click which can result in unexpected behavior. E.g. Angulars ngTouch module decorates every ng-click so it also called on a touch event. This is something which should happen for <x-menu-item>, too, but you would now need to check, if ngTouch is used manually and if this is true, listen for touch events as well. This is error prone and doesn't scale. This "decoration step" currently happens on the link phase which can have its own downsides: it would be hard to generate a ng-class for the <li> dependent on ng-disabled here. You would need to use $compile which can have unexpected effects on its own. (E.g. I used it on <select> ones and suddenly all <options> were duplicated. That can be hard to debug.) Other directives have a default behavior which is too useful to loose (e.g. ng-class is "animation aware" and sets utility classes like ng-enter - it wouldn't be enough to rebuild some custom element.addClass(cssClass)).
(Partly) Solution #7: Use multiple templates.
Sometimes it is sufficient to use multiple templates which are chosen dependent on some attributes. This can happen inside the templateUrl function.
<x-menu-item>Open</x-menu-item>
<!-- → possible translation using "template-default.html" -->
<li class="menu-list-item">
<button type="button" class="menu-button">Open</button>
</li>
Or:
<x-menu-item disabled="baz">Open</x-menu-item>
<!-- → possible translation using "template-disabled.html" -->
<li class="menu-list-item" ng-class="{ 'is-disabled': baz }">
<button type="button" class="menu-button" ng-disabled="baz">Open</button>
</li>
Downsides: This isn't very DRY. If you want to change the menu-list-item class you need to do this in two templates. But it's nice to finally write templates in HTML again and not as JavaScript strings. But it doesn't scale well, if you have more variation. However this can be your only solution, if not just some attributes change, but the whole markup behind it.
Solution #8: Try to initialize every hidden directive with some default behavior (even if it is some noop).
Maybe every specially handled attribute can be initialized with some default value, even if this value does nothing. The default behavior can be overridden.
<x-menu-item>Open</x-menu-item>
<!-- → possible translation -->
<!-- $scope.isDisbabled = has('ng-disabled') ? use('ng-disabled') : false -->
<!-- $scope.action = has('ng-click') ? use('ng-click') : angular.noop -->
<!-- $scope.isHidden = has('ng-hide') ? use('ng-hide') : false -->
<li class="menu-list-item" ng-hide="isHidden" ng-class="{ 'is-disabled': isDisabled }">
<button type="button" class="menu-button" ng-disabled="isDisabled" ng-click="action()">Open</button>
</li>
Downsides: You initialize directives which sometimes are never used. This can be a performance problem. But all in all this approach is relatively clean. This is currently my favorite solution.
Solution #?: ???
...
Possible #6. Dirty queries.
What if we will use directive definition as a query expression for the case when we should switch off default implementation?
You write something like:
.directive('ngClick', function() {
return {
restrict: 'A',
priority: 100, // higher than default
link: function(scope, element, attr) {
// don't do that magic in other cases
if (element[0].nodeName !== 'X-MENU-ITEM') return;
element.bind('click', function() {
// passthrough attr value to a controller/scope/etc
})
// switch off default implementation based on attr value
delete attr.ngClick;
}
}})
This will switch off default implementation of ng-click at your tags. Same job for ng-hide/ng-show/etc.
Yep, it looks terrible by sense, but result is closer to your idea. Of course it will slow down linking process of compile a bit.
P.S. According to your list I prefer #2 but with custom directive namespace. Something like:
<app-menu-item app-click="..." app-hide="..."/>
And add a convention to docs to use app prefix for all custom things and behaviour. Where app is an abbr of project name usually.
Update: Rewrite based on Comments
This is largely rewritten based on the clarification within the comments. As a pure component-based framework, the idea behind Polymer Elements is give developers a simple way to accomplish two tasks. The first is the ability to make simple Elements with previously un-programmed functionality. The second is the ability to make complex controls with simple custom markup.
This is a considerably different focus than Angular, which consolidates functionality into directives that translate custom markup into native markup and couples the functionality to the translated markup.
Notes on Polymer:
Polymer is primarily a framework polyfill until Web Components, Shadow DOM, Scoped CSS and HTML Imports becomes standardized and implemented. HTML Templates is already implemented pretty well in modern browsers and is worth reading up on. That said, Polymer won't just go away when those are standard, as it provides convenience functions and attributes, as well as a good set of components to start with. The polyfills are considered to be slower than native support, but that is to be expected.
Differences between Polymer and Angular:
Unlike Angular, when you create an element, you are not 'translating' it from one markup-set to another. You are actually defining its functionality and presentation. Even though it may provide additional markup within its Template(s) and Shadow DOM, that additional markup are all functional elements (whether custom or native).
This also means that your elements can have their own CSS Classes and IDs are are handled by the browser allowing for different presentation quite easily. Selector Engines will get the actual custom element and can get the properties and methods associated with them.
Simple Demonstration:
<polymer-element name="x-menu">
<template>
<style>
/* Scoped Style rules */
</style>
<content></content>
</template>
<script>
/* Registers the Element with the browser. */
Polymer('x-menu', {
// Additional Element properties and methods
});
</script>
</polymer-element>
<polymer-element name="x-menuitem">
<template>
<style>
/* Scoped style rules */
</style>
<button type="button" class="{{parentElement.classList}}">
<content></content>
</button>
</template>
<script>
/* Registers the element with the browser */
Polymer('x-menuitem', {
// Additional Element properties and methods
});
</script>
</polymer-element>
Actual Usage
<x-menu class="cool">
<x-menuitem>Open</x-menuitem>
<x-menuitem>Edit</x-menuitem>
<x-menuitem>Create</x-menuitem>
</x-menu>
When you actually run this, you will see that the button actually copies the classList from the parentElement. In other words: {{parentElement.classList}} is actually shorthand for this.parentElement.classList. Understanding this, you can actually build a number of functions amd properties that are based on parent markup. In contrast, the reverse can be done, as well. You can also use document.querySelector('x-menu') and you will get the <x-menu>.
Additionally, since these are attributes that apply only your own custom element, you need not worry about namespacing the attributes. No other element will understand your attributes, and the browser won't try to do anything funny with them.
Update: Applying Polymer to your Needs
First, and foremost, using your example above, if you have two elements, then you will have two custom elements. How coupled they are depends largely on how they are programmed. It needs be understood that there is only need for native elements within your custom elements if you need multiple types of functionality or presentation. I would recommend, at first, not utilizing native elements except where you need for styling. With Polymer, it is generally best to start small...
For your functional example, there is no real need to have anything other than the content unless you need a) multiple styling blocks or b) to integrate the functionality of another element or component. Since you require click functionality and the ability to focus/style, the most one should add is a button. The rest can easily be handled in CSS.
Getting out of the mindset that a component should handle multiple elements is important, because it is only true when you need multiple elements. A simple element is there to extend the HTML Element set for your needs. Only worry about managing other elements (either custom or native) when you need an advanced component.
Update: Facading Elements
Polymer provides several mechanisms for having an element have multiple presentations or function based on properties. The simplest mechanism is template binding which changes the relevant document fragment based on conditions.
<polymer-element name="x-menuitem">
<template>
<template if="{{condition}}">
<style>Uses this set of styles</style>
</template>
<template if="{{condition2 OR !condition}}">
<style>Use this stylesheet instead</style>
</template>
<content></content>
</template>
<script>Polymer Registration</script>
</polymer-element>
There may be any number of conditions, and since <template> is just an element, you can actually put any elements in there, including more <template> bindings.
Update: Two examples with Disabled
In the first example, we will simply make a <x-menu-item> with the ability to disable it. We're only going to use the core element, without an <li> or <button>. To disable the item, you may either set the attribute directly (when you mark it up, or get the element via a selector query and set the disabled property.
<polymer-element name="x-menuitem">
<template>
<style>
:host { color:blue; }
:host([disabled]) { color:red; }
</style>
<content><content>
</template>
<script>
Polymer('x-menu-item', {
publish: {
disabled: {
value: false,
reflect: true
}
},
method: function() {
if (this.disabled) return;
}
});
</script>
</polymer-element>
In our second example, we will use an <x-menu-item> with child <li> and <button>. This will have some conditional template binding so that it renders with a different classes. It can still be disabled the same as the above.
<polymer-element name="x-menuitem">
<template>
<style>
:host #item {
/* styles */
}
:host([disabled]) #item {
/* styles */
}
</style>
<template if="{{disabled}}">
<li id="item" class="disabled_class1">
<button class="disabled_class2">
<content><content>
</button>
</li>
</template>
<template if="{{!disabled}}">
<li id="item" class="enabled_class1">
<button class="enabled_class2">
<content><content>
</button>
</li>
</template>
</template>
<script>
Polymer('x-menu-item', {
publish: {
disabled: {
value: false,
reflect: true
}
},
method: function() {
if (this.disabled) return;
}
});
</script>
</polymer-element>
Final Note Updated:
Keep in mind that the above examples are just two declarative ways to accomplish what you need. There are other declarative techniques to accomplish the same goal. Further, you may always utilize the ES/JS to imperatively define everything as well, giving even more options.
There is a lot to Polymer that is matches default browser behavior for HTML Elements. That does not mean there isn't a lot to know. This answer was merely to demonstrate one specific desired functionality of your question. Since the development process and terminology is so different, I wasn't sure what else should be addressed. Whatever might be missing can certainly be added to the answer in updates; simply let me know which specifically you would like added.

Categories

Resources