Create a polymer element using an ES6 class? - class

I'd like to be able to create a class to pass to the Polymer function to create elements. The obvious use case is to have a base class that our developers can use to build Polymer elements.
However, Polymer seems to ignore lifecycle methods on the class. The following code does not run #created. Is there a workaround?
//attempt 1
class CustomElement {
is = 'sample-multi-view-polymer-buic'
created() { console.log('created') } // never called
}
export default Polymer(new CustomElement()) // doesn't work (see error below)
Since I'm using Babel to transpile ES6 to ES5, the above is equivalent to the code below. It also does not work.
// attempt2
function CustomElement() {
this.is = 'sample-multi-view-polymer-buic'
}
CustomElement.prototype.created= function() { console.log('hi') } //never called

There's an article for using ES6 with Polymer in the docs
So there's 2 things missing on your end:
You are missing a beforeRegistered() method.
You need to use Polymer() to instantiate your element's Class.
Here's how I do it:
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link href="paper-input/paper-input.html" rel="import">
<dom-module id="x-example">
<template>
<paper-input value="{{name::input}}"></paper-input>
<h4>{{name}}</h4>
</template>
<script>
"use strict";
class xExample {
beforeRegister() {
this.is = "x-example";
this.properties = {
name: {
type: String,
value: "I'm a data-binded prop"
}
}
}
ready() {
console.log("ready");
}
}
Polymer(xExample);
</script>
</dom-module>
<x-example></x-example>
Note:
The above snippet only works on Chrome/FF
For everything else you can of course "babelize" it.

Related

Is it possible in vue3 to access the root DOM element in a child component slot? I am trying to use a 3rd party library (sortablejs) in vue3

In vue2 I could use this.$el
export default {
render() {
return this.$slots.default[0]
},
mounted() {
Sortable.create(this.$el, {});
})
}
If, in vue3 I try to use this.$slots.default()[0] I can't see how to target the element.
If I use a template ref, I can get the div, but not the contained slot.
The closest question / answer I have found is here Vue 3 Composition API - How to get the component element ($el) on which component is mounted
but this also seems to give the div, but not the slot $el.
This was extremely powerful in vue2 because sortable could be passed a ul, or a div, or another constructed sortable vue component in a slot, and work without the element having to be defined in the child component and I can't work out how to replicate this in vue3.
I originally came across this in a screen cast by Adam Wathan: "Building a Sortable Component with Vue.js", but this was vue2.
I've come up with the following (perhaps there are better out there)
Use template ref:
<template>
<div ref="root">
<slot></slot>
</div>
</template>
Then in the script:
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
// console.log(root.value.children[0]) // this is your $el
let el = root.value.children[0]
Sortable.create(el, {})
})
return {
root
}
}
}

How can I make StencilJS component to render without component tag itself with TSX?

While I understand this is probably a terrible practice, I need to build StencilJS component such that inside render(), I don't want to render component tag itself due to already existing style guide and it expect DOM to be constructed in certain way. Here is what I'm trying to achieve - component code (from HTML or within another component):
<tab-header-list>
<tab-header label="tab 1"></tab-header>
<tab-header label="tab 2"></tab-header>
</tab-header-list>
when rendered, I want generated DOM to be something like:
<tab-header-list>
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
</tab-header-list>
so inside tab-header-list render() function, I'm doing
return (
<ul>
<slot/>
</ul>
);
and I can do this inside tab-header render() function
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
to get what I want but how can I do this with TSX? (for simplicity sake, above code is really simple but what I really need to build is lot more complicated li tag with events etc so I would like to use TSX)
Tried to store DOM to variable but I'm not sure how I can assign it as this.el (outerHTML seem to be only way I can come up with, but I feel there must be better way)
#Element() el: HTMLElement;
#Prop() label: string;
render() {
var tabheaderDOM = (<li>{this.label}</li>);
// how can I assign above DOM to this.el somehow?
//this.el.outerHTML = ?
}
I appreciate any help I can get - thanks in advance for your time!
Unfortunately, you can't use custom elements without tags, but there is a workaround for it:
You can use Host element as reference to the result tag.
render () {
return (
<Host>....</Host>
)
}
Then in your stylesheet you can set the display property for it:
:host {
display: contents;
}
display: contents causes an element's children to appear as if they were direct children of the element's parent, ignoring the element itself
Beware: it doesn't work in IE, opera mini... https://caniuse.com/#feat=css-display-contents
UPD:
If you are not using the shadowDOM then you need to replace :host by the tag name like:
tab-header {
display: contents;
}
Functional components might be able to help you achieve this. They are merely syntactic sugar for a function that returns a TSX element, so they are completely different to normal Stencil components. The main difference is that they don't compile to web components, and therefore only work within TSX. But they also don't result in an extra DOM node because they simply return the template that the function returns.
Let's take your example:
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
you could write it as a functional component:
import { FunctionalComponent } from '#stencil/core';
interface ListItemProps {
label: string;
}
export const ListItem: FunctionalComponent<ListItemProps> = ({ label }) => (
<li>{label}</li>
);
and then you can use it like
import { ListItem } from './ListItem';
#Component({ tag: 'my-comp' })
export class MyComp {
render() {
return (
<ul>
<ListItem label="tab 1" />
<ListItem label="tab 2" />
</ul>
);
}
}
Which will render as
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
Instead of a label prop you could also write your functional component to accept the label as a child instead:
export const ListItem: FunctionalComponent = (_, children) => (
<li>{children}</li>
);
and use it like
<ListItem>tab 1</ListItem>
BTW Host is actually a functional component. To find out more about functional components (and there limitations), see https://stenciljs.com/docs/functional-components.

Vuejs v-model escape automatically html entities

I'm trying to display some html entities in a form text input, but v-model seems escaping them.
Is there something I need to write to make v-model displaying correctly html entities?
my sample code is
<el-input v-model="data" readonly="readonly"></el-input>
I know about v-html but I prefer keep using v-model due the automatic two-way binding.
UPDATE
Maybe I expressed myself wrong, I want to display the character, not the html entity, so instead 49.42₹ i need to display 49.42₹.
If you v-model a computed that interprets HTML entities, I think you get the effect you want. You can type in entity values and it will interpret them correctly. However, it might prematurely turn &#8 into a different character; you have to type #8377; and then go back in and insert the &.
new Vue({
el: '#app',
data: {
a: '49.42₹'
},
computed: {
asText: {
get() {
return this.toText(this.a);
},
set(newValue) {
this.a = newValue;
}
}
},
methods: {
toText(html) {
const div = document.createElement('div');
div.innerHTML = html;
return div.textContent;
}
}
})
<link href="//unpkg.com/element-ui#1.0.0-rc.3/lib/theme-default/index.css" rel="stylesheet"/>
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui/lib/index.js"></script>
<div id="app">
<el-input v-model="asText"></el-input>
{{a}}
<div v-html="a"></div>
</div>

Using jQuery autocomplete or Twitter Typeahead with Aurelia

I’m trying to add an input filed with jQuery ui autocomplete or Twitter Typeahead. I can’t make either work. I get “$(...).typeahead is not a function” or “$(...).autocomplete is not a function” error.
I also tried aurelia-widget from https://github.com/drivesoftware/aurelia-widgets, but I also get “$(...).autocomplete is not a function” error.
I would appreciate if someone could tell me what I am doing wrong.
locate.js
import {customElement, bindable} from 'aurelia-framework';
import $ from 'jquery';
import { autocomplete } from 'jquery-ui';
#customElement('locate')
export class Locate {
#bindable data;
constructor(element) {
this.element = element;
}
activate() {}
detached(){}
attached(){
$(this.element).autocomplete({
source:['Japan', 'USA', 'Canada', 'Mexico']
});
}
}
locate.html
<template>
<label for="locator-input"></label>
<div id="locator-input-wrapper">
<input id="locator-input" type="text" placeholder="Search">
</div>
</template>
First, you have to be sure about what 'jquery-ui' exports. Does it export something? I believe it exports nothing, instead, it just add some functions to jquery objects. So, you could try this:
import {customElement, bindable} from 'aurelia-framework';
import $ from 'jquery';
import 'jquery-ui';
#customElement('locate')
export class Locate {
#bindable data;
constructor(element) {
this.element = element;
}
activate() {}
detached(){}
attached(){
$(this.element).autocomplete({
source:['Japan', 'USA', 'Canada', 'Mexico']
});
}
}
I had the same error but when I retrieved jquery-ui using npm it worked. So instead of "jspm install jquery-ui" (which gave me the error) try:
jspm install npm:jquery-ui
package.json
"jquery-ui": "npm:jquery-ui#^1.10.5",
I had the same problem with jQuery UI datepicker. So i used jquery-ui-dist instead of jquery-ui when doing NPM install.
import "jquery-ui-dist/jquery-ui";
import "jquery-ui-dist/jquery-ui.min.css";
import "jquery-ui-dist/jquery-ui.theme.min.css";
And then:
$(this.element).datepicker()
There are several step involved on this. Please let me note the key points
First you must install the following packages (I am using nodeJS).
npm install -save jquery jquery-ui
(then and if you are coding on typescript the requested types…)
npm install -save #types/jquery #types/jqueryui
I am installing those packages only for coding with typescript and have intellisense working, but really I will not be using them on runtime.
Where the jquery-ui package resides, on node_modules directory, go and create a ../node_modules/jquery-ui/dist directory.
Then download the built zip minimized version from https://jqueryui.com/ and decompress into that dist directory. Those are the files the we will be really using at runtime.
Configure your AMD loader to point to that dist min file creating paths and shims for jquery and jquery-ui. In my case, the AMD loader is requireJS.
require.config(
{
"paths": {
"jquery": '../node_modules/jquery/dist/jquery.min',
"jquery-ui": '../node_modules/jquery-ui/dist/jquery-ui.min'
(code removed for brevity…)
"shim": {
"jquery": {
"exports": '$'
},
"jquery-ui": {
"exports": '$.autocomplete',
"deps": ['jquery' ]
},
(notice that the line "exports": '$.autocomplete' is not required. Since autocomplete, datepicker, etc. widgets, will be loading onto the $ jQuery global variable, I only used this line only as signaler to my AMD loader the it has really loaded something)
Since my AMD loader don’t “parse” css files, I had to add jquery-ui.min.css style sheet manually to my index.html the
<!DOCTYPE html>
<html>
<head lang="en">
(code removed for brevity…)
<link href="./node_modules/jquery-ui/dist/jquery-ui.min.css" rel="stylesheet" />
Create a custom attribute or a custom element (in my opinion for this case the best choice is a custom attribute
i.e. create a class file called: auto-complete.ts (I am coding on typescript, remove types for vainilla javascript).
import { DOM, inject, bindable, bindingMode } from 'aurelia-framework';
import { fireEvent } 'library';
import * as $ from 'jquery';
import 'jquery-ui';
#inject(DOM.Element)
export class AutoCompleteCustomAttribute {
#bindable source: any;
#bindable options = {};
#bindable({ defaultBindingMode: bindingMode.twoWay }) value: JQueryUI.AutocompleteUIParams;
private readonly element: Element;
constructor(element: Element) {
this.element = element;
}
attached() {
$(this.element).autocomplete({
change: (event, ui) => {
if (ui.item == null) {
$(this.element).val('');
$(this.element).focus();
}
},
select: (label, value) => this.value = value,
source: this.source
}).on('change', e => fireEvent(<any>e.target, 'input'));
}
detached() {
$(this.element).autocomplete('destroy');
}
}
Create a shared module where to code shared functionality (or code directly on custom attribute itself, I am going to stick with the shared module option)
i.e. create a class file called: library.ts
export function fireEvent(element: Element, name: string) {
var event = createEvent(name);
element.dispatchEvent(event);
}
export function createEvent(name: string) {
var event = document.createEvent('Event');
event.initEvent(name, true, true);
return event;
}
The usage of this custom attribute on your code is just to attach it to a input text tag as follows:
<input auto-complete="source.bind:countries; value.two-way: country">
where countries (string array) and country (string) are properties on your view model.
Don’t forget to register your custom attribute as a global resource at your Aurelia project's ./src/resources/index.ts or manually adding it on you main.js configure() function as follows:
aurelia.globalResources(["auto-complete"]);
I hope this answer be usefull
Hi again, I am adding an updated code for the custom attribute here below
import { DOM, inject, bindable, bindingMode } from 'aurelia-framework';
import * as $ from 'jquery';
import 'jquery-ui';
import { fireEvent, AutoCompleteSource } from 'libs/library';
#inject(DOM.Element)
export class AutoCompleteCustomAttribute {
#bindable options = {
applyLabel: true,
forceMatch: true
};
#bindable source: AutoCompleteSource[];
#bindable({ defaultBindingMode: bindingMode.twoWay }) value: number;
#bindable({ defaultBindingMode: bindingMode.twoWay }) label: string;
private readonly element: JQuery<HTMLElement>;
constructor(element: Element) {
this.element = $(element);
}
attached() {
this.element
.autocomplete({
source: this.source,
change: (event, ui) => {
if (ui.item == null && this.options.forceMatch) {
this.element.val('');
}
},
select: (event, ui) => {
if (this.options.applyLabel) {
event.preventDefault();
this.element.val(ui.item.label);
}
this.label = ui.item.label;
this.value = ui.item.value;
},
focus: (event, ui) => {
if (this.options.applyLabel) {
event.preventDefault();
this.element.val(ui.item.label);
}
this.label = ui.item.label;
this.value = ui.item.value;
}
}).on('change', e => fireEvent(<any>e.target, 'input'));
}
detached() {
this.element
.autocomplete('destroy');
}
}
This version funcionality allows us to get the label and the value of the source array when dealing with scenarios where label is the text to search and value is a foreing key.
Added functionality to force the typed text to match one of the existing values.
Added funcionality to apply the label instead of value on the input text display.
Custom attribute should be used as follows:
<input type="text" value="${color}" auto-complete="source.bind:colors;value.bind:colorId;label.bind:color">
where colors (array of { "label": string, "value": number }), colorId (number) and color (string) are properties on your view model.
notice also this new type definition added to the library (just simple typescript stuff)
export type AutoCompleteSource = { "label": string, "value": number };

Meteor: apply function after rendering things from mongodb

I am using sage cell to convert html to math stuff
Template.home.rendered = function(){
\\ apply sagecell and mathjax
}
However, the content that are rendered comes from mongo, so it's sometimes loaded after sage cell is applied to it. I can do something like this
Template.home.rendered = function(){
Deps.autorun(function(){
if (Content.findOne({_id: ...})){
\\ apply sagecell and mathjax
}
});
}
It's better but still doesn't work all the time. Is there other things I can use to detect the content is completely rendered?
Edited with new response:
<template name='pendingAnswer'>
The answer to your question, coming back whenever, is:
{{>answer}}
</template>
<template name='answer'>
{{fromSage}}
</template>
Template.answer.helpers({
fromSage: function () {
Session.get('fromSage');
}
});
Invoked whenever - from a button, from navigating to the page, on blur...
function GetAnswerFromSage(data) {
callHTTP(website,data, callbackFromSage)
}
function callbackFromSage(err, data) {
if (err) then log(err);
Session.set('fromSage', data);
}
Earlier: try transform upon retrieval of mongo:
From Meteor Doc
// An Animal class that takes a document in its constructor
Animal = function (doc) {
_.extend(this, doc);
};
_.extend(Animal.prototype, {
makeNoise: function () {
console.log(this.sound);
}
});
// Define a Collection that uses Animal as its document
Animals = new Meteor.Collection("Animals", {
transform: function (doc) { return new Animal(doc); }
});
// Create an Animal and call its makeNoise method
Animals.insert({name: "raptor", sound: "roar"});
Animals.findOne({name: "raptor"}).makeNoise(); // prints "roar"
The script
<script type='text/javascript' src="http://sagecell.sagemath.org/static/embedded_sagecell.js"></script>
that is supposed to be in the head needs to be removed and instead be loaded after the content is completely loaded like so:
Template.content.rendered = function(){
// sage
Deps.autorun(function(){
if (Session.get('contentChanged')){
// loading this script causes mathjax to run
$.getScript("http://sagecell.sagemath.org/static/embedded_sagecell.js", function(d, textStatus){
if (textStatus=='success'){
// this converts <div class='compute'> to a sage cell
sagecell.makeSagecell({
inputLocation: 'div.compute',
evalButtonText: 'Evaluate',
hide: ['editorToggle']
});
}
})
}
})
and if I go from 1 content template to another content template, it seems that nothing is rerendered and so the mathjax was not applied. The only fix I can think is to force a page reload:
Template.content.events({
'click a': function(evt){
evt.preventDefault();
location.href = evt.currentTarget.href;
}
})
which makes the site much slower, unfortunately.