Javascript Mediator In Browser Can't Find Instance Variables - javascript

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)

Related

How can I set up a function that can be referenced from several event listeners and use different properties for each element? (JS)

Edit: I found the solution (found at the bottom).
The problem that I am having is that I want to consolidate my code into one small function that can be referenced multiple times. I have a currently working code, which has several functions, one for each event-listener that my webpage has. I am trying to render objects after a user clicks a "button," which is really a div element. Here's some code:
//Constructor Function
function Object(objectType) {
this.objectType = objectType;
}
//Render function for the objects
Object.prototype.render = function() {
//Getting the elements, creating them, setting to variables to be used later.
let objectDiv = document.createElement('div');
objectDiv.setAttribute('class', `${this.objectType}Object`);
objectDiv.textContent = this.objectType;
let makeButton = document.querySelector(`#${this.objectType}Section`);
//Appending element to webpage
makeButton.appendChild(objectDiv);
};
//All the current code above works like it is supposed to. I have a few buttons already that work, but they each have their own function. Like so:
//Setting up Button1 code.
let button1 = document.getElementById("makeObj1");
//Event listener for Button1, which calls the Render function for the Button1 object.
button1.addEventListener("click", goMakeButton1);
function goMakeButton() {
let button1 = new Object('Obj1');
button1.render();
}
//And so forth three times, for each button.
//I want to make it so that there is one section, and I currently have it set up like so:
let button1 = {
element: document.getElementById('makeObj1'),
name: 'Obj1',
}
let button2 = {
element: document.getElementById('makeObj2'),
name: 'Obj2',
}
let button3 = {
element: document.getElementById('makeObj3'),
name: 'Obj3',
}
let button4 = {
element: document.getElementById('makeObj4'),
name: 'obj4',
}
catButton.element.addEventListener('click', makeObj);
dogButton.element.addEventListener('click', makeObj);
sheepButton.element.addEventListener('click', makeObj);
horseButton.element.addEventListener('click', makeObj);
function makeObj(object) {
console.log('I am called');
//To check my function gets called - it does.
let myObject = new Animal(this.name);
myObject.render();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Animal Farm</title>
<link rel="stylesheet" href="./style/style.css" />
</head>
<body>
<h1>DOM Manipulation and Events</h1>
<!-- create one big container -->
<main id="mainContainer">
<section id="Obj1Section">
<div class="make" id="makeButton1">Make Object 1</div>
</section>
<section id="Obj2Section">
<div class="make" id="makeButton2">Make Object 2</div>
</section>
<section id="Obj3Section">
<div class="make" id="makeButton3">Make Object 3</div>
</section>
<section id="HorseSection">
<div class="make" id="makeButton4">Make Objecvt 4</div>
</section>
</main>
</body>
<script src="./script/script.js"></script>
</html>
This just gives me the error of not being able to append child of null. When I try to take in a parameter and give each event listener its own parameter to pass, that fails as well and doesn't even allow me to click them. What do I do here?
The fix:
What I did was run an arrow function that called another function, passing a parameter defined in the arrow function, rather than trying to pass a parameter directly to the function from the event listener (it would just run, and then become useless.)
"use strict";
//Creating the constructor function for the buttons
function Button(buttonName) {
this.buttonName = buttonName;
}
//Render function for Buttons. Able to be used for any button name.
Button.prototype.render = function () {
//Getting the elements, creating them, setting to variables to be used later.
let buttonDiv = document.createElement('div');
buttonDiv.setAttribute('class',`${this.buttonName}Object`);
buttonDiv.textContent = this.buttonName;
let makeButton = document.querySelector(`#${this.buttonName}Section`);
//Appending element to webpage
makeButton.appendChild(buttonDiv);
};
//Setting up the condensed function:
let buttonOne = document.getElementById('Obj1');
let buttonTwo = document.getElementById('Obj2');
let buttonThree = document.getElementById('Obj3');
let buttonFour = document.getElementById('Obj4');
function buttonMake(button){
let myButton = new Button (button);
myButton.render();
}
buttonOne.addEventListener('click', ()=>{
let theButton = 'Obj1';
makeButton(theButton);
})
buttonTwo.addEventListener('click', ()=>{
let theButton = 'Obj2';
makeButton(theButton);
})
buttonThree.addEventListener('click', ()=>{
let theButton = 'Obj3';
makeButton(theButton);
})
buttonFour.addEventListener('click', ()=>{
let theButton = 'Obj4';
makeButton(theButton);
})

Passing onclick event into template literals without global function

I've been working on adding onclick event in template literals with plain javascript (without jquery only javascript). As you can see the result html knows that onclick event on each div has function which will alert as I click but when I click, it didn't respond. It seems like suppose to work but somehow it is not working.
I got lots of help from Stackoverflow but most of the anwser was using global function. but I don't personally want to use global function since sometimes it cause some trouble.
so how can I actually pass the function into onclick event by using template literals?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="ul"></div>
</body>
<script src="/index.js"></script>
</html>
index.js
function test() {
const list = [
{ number: 1, check: () => alert("1") },
{ number: 2, check: () => alert("2") },
{ number: 3, check: () => alert("3") },
];
const $list = list.reduce((acc, item) =>
acc + `<div onclick='${item.check}'>${item.number}</div>`,""
);
const $ul = document.querySelector("#ul");
$ul.innerHTML = $list;
}
test();
result
Instead of building the DOM via HTML strings, create actual DOM elements.
const $ul = document.querySelector("#ul");
for (const item of list) {
const element = document.createElement('div');
element.textContent = item.number;
element.onclick = item.check;
$ul.appendChild(element);
}

web component (vanilla, no polymer): how to load <template> content?

i'm new on web component. I checked some example, but i really can't figure out how to load (insert in the DOM) the content of a of a separate web component. Starting from this example , I put this code in a file named my-element.html:
<template id="my-element">
<p>Yes, it works!</p>
</template>
<script>
document.registerElement('my-element', class extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
const t = document.querySelector('#my-element');
const instance = t.content.cloneNode(true);
shadowRoot.appendChild(instance);
}
});
</script>
This is my index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>my titile</title>
<link rel="import" href="my-element.html">
</head>
<body>
Does it work?
<my-element></my-element>
</body>
</html>
I'm on latest Chrome 56, so i don't need polyfill. I run polyserve, and only "Does it works?" appears. I tried (like the original example) the "customElements.define" syntax instead of "document.registerElement", but won't work. Have you some ideas? And what have I to change if i don't want to use shadow dom?
thanks
It's because when you do:
document.querySelector( '#my-element' );
...document refers to the main document index.html
If you want to get the template, you should use instead document.currentScript.ownerDocument
var importedDoc = document.currentScript.ownerDocument;
customElements.define('my-element', class extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({mode: 'open'});
const t = importedDoc.querySelector('#my-element');
const instance = t.content.cloneNode(true);
shadowRoot.appendChild(instance);
}
});
Note that document.currentScript is a global variable, so it refers to your imported document only when it is currently parsed. That's why it's value is saved in a variable (here: importedDoc) to be reusable later (in the constrcutor call)
If you have multiple imported document you may want to isolate it in a closure (as explained in this post):
( function ( importedDoc )
{
//register element
} )(document.currentScript.ownerDocument);

Defining Polymer element after importing ES6 code via System.js

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.

A web component with ECMA6

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.

Categories

Resources