I'm creating an HTML element using Polymer, and I want it to be able to work with an ES6 class I've written. Therefore, I need to import the class first and then register the element, which is what I do:
(function() {
System.import('/js/FoobarModel.js').then(function(m) {
window.FoobarModel = m.default;
window.FoobarItem = Polymer({
is: 'foobar-item',
properties: {
model: Object // instanceof FoobarModel === true
},
// ... methods using model and FoobarModel
});
});
})();
And it works well. But now I want to write a test HTML page to display my component with some dummy data:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="/bower_components/webcomponentsjs/webcomponents.js"></script>
<script src="/bower_components/system.js/dist/system.js"></script>
<script>
System.config({
map:{
traceur: '/bower_components/traceur/traceur.min.js'
}
});
</script>
<link rel="import" href="/html/foobar-item.html">
</head>
<body>
<script>
(function() {
var data = window.data = [
{
city: {
name: 'Foobar City'
},
date: new Date('2012-02-25')
}
];
var view;
for (var i = 0; i < data.length; i++) {
view = new FoobarItem();
view.model = data[i];
document.body.appendChild(view);
}
})();
</script>
</body>
</html>
Which isn't working for one simple reason: the code in the <script> tag is executed before Polymer registers the element.
Thus I'd like to know if there's a way to load the ES6 module synchronously using System.js or even better, if it's possible to listen to a JavaScript event for the element registration (something like PolymerElementsRegistered)?
I've tried the following without success:
window.addEventListener('HTMLImportsLoaded', ...)
window.addEventListener('WebComponentsReady', ...)
HTMLImports.whenReady(...)
In the app/scripts/app.js script from the polymer starter kit, they use auto-binding template and dom-change event
// Grab a reference to our auto-binding template
var app = document.querySelector('#app');
// Listen for template bound event to know when bindings
// have resolved and content has been stamped to the page
app.addEventListener('dom-change', function() {
console.log('Our app is ready to rock!');
});
Also check this thread gives alternatives to the polymer-ready event.
Related
If we don't want to use ES6 import nor any third party libraries (like require.js etc.) nor any package builder, what is the classical way to pack a library so that the user can use it with what seems to be a library-namespace?
index.html (from the perspective of the user of the library)
<canvas id="mycanvas"></canvas>
<script src="canvas.js"></script>
<script>
canvas.setup({"config1": true, "config2": false});
var mycanvas1 = canvas.get("#mycanvas");
</script>
I sometimes see code similar to
// canvas.js
(function (window, something, else) {
window.canvas.setup = function() { ... } // how to export functions setup and get?
})(window, ..., ...);
How to do this IIFE properly to create this canvas.js library example?
Note: with ES6 import, we can package this way a library called canvas.js:
const canvas = {
setup: config => { console.log("config"); },
get: id => { console.log("get"); }
};
export default canvas;
and use it a index.html this way:
<canvas id="mycanvas"></canvas>
<script type="module">
import canvas from "./canvas.js";
canvas.setup({"config1": true, "config2": false});
var mycanvas1 = canvas.get("#mycanvas");
</script>
but in this question here I am curious about the pre-ES6 solution.
A solution seems to be:
// canvas.js
canvas = (function(window) { // assign the result of the IIFE to a global var to make
// it available for the user of the lib
var internal_variable; // not accessible to the user of the lib
var state; // this variable will be available to the user of the lib
function setup(arg) {
console.log("setup");
}
function get(arg) {
console.log("get"); // + other lines using window object
}
return {setup: setup, get: get, state: state}; // important: here we choose what we
// export as an object
})(window);
Now the user can use the library this way:
<canvas id="mycanvas"></canvas>
<script src="canvas.js"></script> // object canvas is now available
<script>
canvas.setup({"config1": true, "config2": false});
var mycanvas1 = canvas.get("#mycanvas");
console.log(canvas.state);
</script>
Am working on a HTML/JS Mediator that filters a data_model when a user enters text to a field. Have used window.onload = init, and spent four hours trying to find why 'this' in the browser makes it print the calling object, and thus I can't get a class instance to refer to itself using 'this'
console.log(this.text_field)
in setup_list_view() works fine, seemingly because it's within the constructors scope. Running it outside the constructor, as per below, gives:
Cannot set property 'innerHTML' of undefined at HTMLInputElement.handle_text_field_changed
...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function init() {
var text_field = document.getElementById("text-field");
var list_view = document.getElementById("list-view")
form_mediator = new FormMediator(text_field, list_view)
}
class FormMediator {
constructor(text_field, list_view) {
this.setup_text_field(text_field)
this.setup_list_view(list_view)
}
setup_text_field(text_field) {
this.text_field = text_field;
this.text_field.onchange = this.handle_text_field_changed
}
setup_list_view(list_view) {
this.data_model = ['England', 'Estonia', 'France', 'Germany']
this.list_view = list_view
this.list_view.innerHTML = this.data_model
}
does_string_start_with_text_field_text(text) {
return false;
return text.startsWith('E')
}
handle_text_field_changed(){
this.list_view.innerHTML = 'new content' //this.data_model.filter(this.does_string_start_with_text_field_text)
}
}
window.onload = init
</script>
<input id="text-field"><button>Search</button>
<span id="list-view"></span>
</body>
</html>
Any help much appreciated.
The problem in your code occurs in this line:
this.text_field.onchange = this.handle_text_field_changed
A method, by default, won't carry its original binding context if assigned to a variable or another object's property. You need to bind this handle_text_field_changed method first, this way:
this.text_field.onchange = this.handle_text_field_changed.bind(this)
I use iron-list from google Polymer.
<iron-list items="[[data]]" as="item">
<template>
<div tabindex$="[[tabIndex]]">
Name: [[item.name]]
</div>
</template>
</iron-list>
I kwon you can use Polymer.IronA11yKeysBehavior but even with the example I have no idea how I add it in JavaScript to my iron-list.
Using Vaadin Polymer GWT lib. In this lib you have
IronList list;
list.setKeyBindings(???); // don't know how to use this function
list.setKeyEventTarget(????); // don't know how to use this function
When I check the current values of the key bindings I defined a print function to log a variable to the console:
public native void print(JavaScriptObject obj) /-{
console.log(obj);
}-/;
Then I print the current values with:
print(list.getKeyBindings());
The result is:
Object {up: "_didMoveUp", down: "_didMoveDown", enter: "_didEnter"}
It seem that there are some key bindings already defined, but I have no idea where I find the functions _didMoveUp, _didMoveDown and _didEnter.
When I do
print(list.getKeyEventTarget());
I get:
<iron-list class="fit x-scope iron-list-0" tabindex="1" style="overflow: auto;">
</iron-list>
How can I set up a handler for capturing keyboard events using Vaadin Polymer GWT lib? How can I receive an event when keys like enter are pressed?
answering this question
list.setKeyBindings(???); // don't know how to use this function
according to com/vaadin/polymer/vaadin-gwt-polymer-elements/1.2.3.1-SNAPSHOT/vaadin-gwt-polymer-elements-1.2.3.1-20160201.114641-2.jar!/com/vaadin/polymer/public/bower_components/iron-list/iron-list.html:292
the keyBindings should have object of such structure:
{
'up': '_didMoveUp',
'down': '_didMoveDown',
'enter': '_didEnter'
}
to construct such object, you can use following:
new JSONObject() {{
put("up", new JSONString("_didMoveUp"));
put("down", new JSONString("_didMoveDown"));
put("enter", new JSONString("_didEnter"));
}}.getJavaScriptObject();
I have no idea where I find the functions _didMoveUp, _didMoveDown and
_didEnter
they can be found here: com/vaadin/polymer/vaadin-gwt-polymer-elements/1.2.3.1-SNAPSHOT/vaadin-gwt-polymer-elements-1.2.3.1-20160201.114641-2.jar!/com/vaadin/polymer/public/bower_components/iron-list/iron-list.html:1504
here's the extract
_didMoveUp: function() {
this._focusPhysicalItem(Math.max(0, this._focusedIndex - 1));
},
_didMoveDown: function() {
this._focusPhysicalItem(Math.min(this._virtualCount, this._focusedIndex + 1));
},
_didEnter: function(e) {
// focus the currently focused physical item
this._focusPhysicalItem(this._focusedIndex);
// toggle selection
this._selectionHandler(e.detail.keyboardEvent);
}
How can I set up a handler for capturing keyboard events using Vaadin
Polymer GWT lib?
How can I receive an event when keys like enter are
pressed?
I could find this Polymer convention: properties not intended for external use should be prefixed with an underscore.
That's the reason why they are not exposed in JsType IronListElement.
You can change this function using JSNI. I think that smth like this:
private native static void changeDidMoveUp(IronListElement ironList) /*-{
var _old = ironList._didMoveUp;
ironList._didMoveUp = function() {
console.log('tracking');
_old();
}
}-*/;
or add a new one
IronListElement element ...
com.vaadin.polymer.elemental.Function<Void, Event> func = event -> {
logger.debug(event.detail);
...
return null;
};
private native static void addUpdatePressed(IronListElement ironList, Function func) /*-{
ironList._updatePressed = func;
}-*/;
{
addUpdatePressed(element, func);
element.addOwnKeyBinding("a", "_updatePressed");
element.addOwnKeyBinding("shift+a alt+a", "_updatePressed");
element.addOwnKeyBinding("shift+tab shift+space", "_updatePressed");
}
should work. You can get element from IronList#getPolymerElement().
keep in mind, that I haven't tested this code :)
If you want to add key event custom element (I dont know if I understand correct the question)
You have to implement the Polymer.IronA11yKeysBehavior behavior and then to use the keyBindings prototype property to express what combination of keys will trigger the event to fire.
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents.js"></script>
<link rel="import" href="iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
<link rel="import" href="iron-list/iron-list.html">
<body>
<dom-module id="x-elem">
<template>
<iron-list items="[[data]]" as="item">
<template>
<div>
pressed: [[item]]
</div>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'x-elem',
behaviors: [
Polymer.IronA11yKeysBehavior
],
properties: {
preventDefault:{type:Boolean,value:true},
data:{value:[]},
keyEventTarget: {
type: Object,
value: function() {
return document.body;
}
}
},
keyBindings: {
'* pageup pagedown left right down up home end space enter # ~ " $ ? ! \\ + : # backspace': '_handler',
'a': '_handler',
'shift+a alt+a': '_handler',
'shift+tab shift+space': '_handler'
},
_handler: function(event) {
if (this.preventDefault) { // try to set true/false and press shit+a and press up or down
event.preventDefault();
}
console.log(event.detail.combo);
this.push('data',event.detail.combo);
}
});
</script>
<x-elem></x-elem>
</body>
</html>
I hope that answer your question how to listen to keys. the _handler function receive an event so you can look at the detail of the event and get the target (if something was in focus).
event.detail.target
I'm going over the sapui5 walkthrough tutorials and have managed to get to step 9 where it teaches you how to use Component.js file in your app.
Now, prior to using Component.js everything in the app was working fine. However, once I try to use component I get this error:
Uncaught TypeError: this.getView is not a function
Referring to UIComponent.js line 6. Even though my component file is just called Component.js. I also get:
GET http://localhost:58736/InvoicesApp/invoicesapp/Component-preload.js 404 (Not Found)
But I'm not sure they're related
Here is my index.html
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv='Content-Type' content='text/html;charset=UTF-8'/>
<script src="resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-libs="sap.m"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"
>
</script>
<!-- only load the mobile lib "sap.m" and the "sap_bluecrystal" theme -->
<script>
sap.ui.localResources("invoicesapp");
sap.ui.getCore().attachInit(function () {
new sap.ui.core.ComponentContainer({
name : "invoicesapp"
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody" id="content"/>
</html>
My Component.js
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (UIComponent, JSONModel, ResourceModel) {
"use strict";
return UIComponent.extend("invoicesapp.Component", {
metadata: {
rootView:"invoicesapp.view.App"
},
init : function () {
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// set data model on view
var oData = {
recipient : {
name : "World"
}
};
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
// set i18n model on view
var i18nModel = new ResourceModel({
bundleName: "invoicesapp.i18n.i18n"
});
this.getView().setModel(i18nModel, "i18n");
}
});
});
My controller
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel",
"sap/ui/model/resource/ResourceModel"
], function (Controller, MessageToast, JSONModel, ResourceModel) {
"use strict";
return Controller.extend("invoicesapp.controller.App", {
onShowHello : function () {
// read msg from i18n model
var oBundle = this.getView().getModel("i18n").getResourceBundle();
var sRecipient = this.getView().getModel().getProperty("/recipient/name");
var sMsg = oBundle.getText("helloMsg", [sRecipient]);
// show message
MessageToast.show(sMsg);
}
});
});
In the component you can't call this.getView() because there is no getView(), the api docs at https://openui5beta.hana.ondemand.com/#docs/api/symbols/sap.ui.core.Component.html
Instead, you set the model directly on the component itself. In other words, just call
this.setModel(oModel);
and
this.setModel(i18nModel, "i18n");
By the way: in the walktrough it's done the same way.
this.getView() will not be defined in component.js as view is yet to be instantiated.
You can simply set model with just this.setModel(oModel) and you can set names model by this.setModel(oModel,"modelName");
I also had the same problem while going through the walk through.
View is not instantiated in the Component.
You can rewrite as
this.setModel("myModel);
And for i18n
this.setModel(i18nModel,"i18n");
This error was there in walk through but now its fixed it seems. There were few errors with the SAP UI5 Tutorials while I was learning but they have fixed most of them now.
This is also a good source to start with UI5. Almost similar to Demokit.
UI Development Toolkit for HTML5 (SAPUI5)
Happy Coding
Febin Dominic
index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://google.github.io/traceur-compiler/bin/traceur.js"></script>
<script src="https://google.github.io/traceur-compiler/src/bootstrap.js"></script>
<script>
traceur.options.experimental = true;
</script>
<link rel="import" href="x-item.html">
</head>
<body>
<x-item></x-item>
</body>
</html>
and my web component:
x-item.html
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
class Item extends HTMLElement {
constructor() {
let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
</script>
and I get no error nor what I expect to be displayed, any idea if this should actually work?
Is this how one would extend an HTMLElement in ECMA6 syntax?
E: putting it altogether in one page solves the problem at least now I know its the right way to create a custom component, but the problem is having it in a separate file I think it has to do with how traceur handles <link rel="import" href="x-item.html"> I tried adding the type attribute to the import with no luck.
Traceur's inline processor does not appear to have support for finding <script> tags inside <link import>. All of traceur's code seems to access document directly, which results in traceur only looking at index.html and never seeing any <scripts> inside x-item.html. Here's a work around that works on Chrome. Change x-item.html to be:
<template id="itemtemplate">
<span>test</span>
</template>
<script type="module">
(function() {
let owner = document.currentScript.ownerDocument;
class Item extends HTMLElement {
constructor() {
// At the point where the constructor is executed, the code
// is not inside a <script> tag, which results in currentScript
// being undefined. Define owner above at compile time.
//let owner = document.currentScript.ownerDocument;
let template = owner.querySelector("#itemtemplate");
let clone = template.content.cloneNode(true);
let root = this.createShadowRoot();
root.appendChild(clone);
}
}
Item.prototype.createdCallback = Item.prototype.constructor;
Item = document.registerElement('x-item', Item);
})();
</script>
<script>
// Boilerplate to get traceur to compile the ECMA6 scripts in this include.
// May be a better way to do this. Code based on:
// new traceur.WebPageTranscoder().selectAndProcessScripts
// We can't use that method as it accesses 'document' which gives the parent
// document, not this include document.
(function processInclude() {
var doc = document.currentScript.ownerDocument,
transcoder = new traceur.WebPageTranscoder(doc.URL),
selector = 'script[type="module"],script[type="text/traceur"]',
scripts = doc.querySelectorAll(selector);
if (scripts.length) {
transcoder.addFilesFromScriptElements(scripts, function() {
console.log("done processing");
});
}
})();
</script>
Another possible solution would be to pre-compile the ECMA6 into ECMA5 and include the ECMA5 only. This would avoid the problem of traceur not finding the <script> tags in the import and would remove the need for the boilerplate.