Add observer dynamic on dom-if element - javascript

1) If I want to select element on dom-if condition and run observer how can I achieve that scenario i.e I have dropdown which is wrapped inside dom-if and on page load some observer is changing flag to true,which trigger dom-if condition to render that dropdown,but the problem is when page loads I bind the options for dropdown in observer which get the element this.$.elementID || this.$.querySelector('#elementID') and binds it so I am not getting that element but in ui it shows blank dropdown without options so I guess element is not getting selected.
Please help?
<template is="dom-if" if="[[flag]]" restamp="true">
<dropdown id = "elementID"></dropdown>
</template>
JS:
properties:{
list: {
type: Array,
notify: true,
value: [{label:"f1",value:"f1"},{label:"f2",value:"f2"}]
}
}
static get observers() {
return [
'_bindDrop(list)',
];
}
_bindDrop(list) {
const select = this.$.querySelector('#elementID');
if (select) {
select.options.length = 0;
list.forEach(function (item) {
const option = document.createElement('option');
option.textContent = item.label;
option.value = item.value;
select.appendChild(option);
});
}
}
or
2) How to add dynamic observer method on an element in dom-if condition,if element gets flag to true then it adds observer method ?

He an example add, remove options with Polymer-2.x :
Demo
window.addEventListener('WebComponentsReady', () => {
class XFoo extends Polymer.Element {
static get is() { return 'x-foo'; }
static get properties() {
return {
selected:{type:String}, style:{type:String},
list: {
type: Array,
value() {return [{label:"f1",value:"f1", flag:false},{label:"f2",value:"f2", flag:true},{label:"f3",value:"f3", flag:true}] }
}
};
}
static get observers(){ return ['_toogleSelected(selected)', '_listChanged(list.*)']}
_toogleSelected(s){
let objinx = this.list.findIndex(o => o.label === s);
let obj = this.list[objinx]
obj.flag = !obj.flag;
this.set('list.'+objinx, obj)
this.notifyPath('list.'+ objinx)
}
_styleIt(s) {
return s? "green;":"red;";
}
_listChanged(l){
console.log('List changed', l)
}
}
customElements.define(XFoo.is, XFoo);
});
<head>
<base href="https://cdn.rawgit.com/download/polymer-cdn/2.6.0.2/lib/">
<script src="webcomponentsjs/webcomponents-loader.js"></script>
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="paper-dropdown-menu/paper-dropdown-menu.html">
<link rel="import" href="paper-item/paper-item.html">
<link rel="import" href="paper-listbox/paper-listbox.html">
<link rel="import" href="paper-input/paper-input.html">
<link rel="import" href="iron-selector/iron-selector.html">
</head>
<body>
<x-foo></x-foo>
<dom-module id="x-foo">
<template>
<paper-dropdown-menu label="Listed Items">
<paper-listbox slot="dropdown-content" >
<template is="dom-repeat" items="[[list]]" id="lists" mutable-data>
<template is="dom-if" if="[[item.flag]]">
<paper-item >[[item.value]] - [[item.flag]]<paper-item>
</template>
</template>
</paper-listbox>
</paper-dropdown-menu>
<div>Options display Toogle</div>
<iron-selector attr-for-selected="name" selected="{{selected}}" >
<template is="dom-repeat" items="[[list]]" mutable-data>
<button name="[[item.label]]" id="[[item.label]]" style="background-color:{{_styleIt(item.flag)}}"> [[item.value]]</button>
</template>
</iron-selector>
</template>
</dom-module>
</body>

Related

How to remove list item from Typescript todo list app

I am attempting to build a basic todo list app using ONLY Typescript, HTML, and CSS. app.ts is set up to listen for a submit event. The ListTemplate class defines a render method used to create and append the list item elements to the DOM. The ListItem class defines listItem as a string type and executes the format() function defined in the HasFormatter interface. List items are dynamically appended to <ul></ul> container in the HTML. The functionality to input text and append list items to the DOM currently works fine. I am now trying to append functioning remove item buttons to each list item, which I set up in the render method: const btn = document.createElement('button'). I am not sure how to set up the functionality to remove individual list items when clicking the remove buttons. I tried adding another event listener to app.ts that listens for the remove button with the id listItemBtn, but am not sure how to set up the listener (or potentially a remove method in ListTemplate) to target specific list items when clicking remove button. Any thoughts on how to do this? Thanks!
Here is the CodeSandBox link for the project: https://codesandbox.io/s/vanilla-typescript-forked-xnnf4q
app.ts
import { ListItem } from './classes/ListItem.js';
import { ListTemplate } from './classes/ListTemplate.js';
import { HasFormatter } from './interfaces/HasFormatter.js'
const form = document.querySelector('#newForm') as HTMLFormElement; // typecasting, casting the element to be a certain type
const listItemBtn = document.querySelector('#listItemBtn') as HTMLButtonElement;
const listItem = document.querySelector('#listItem') as HTMLInputElement;
//list template instance
const ul = document.querySelector('ul')!;
const list = new ListTemplate(ul)
form.addEventListener('submit', (e: Event) => {
e.preventDefault();
let values: [string] // tuple
values = [listItem.value]
let doc: HasFormatter;
doc = new ListItem(...values)
list.render(doc, listItem.value, 'start')
})
listItemBtn.addEventListener('onclick', (e: Event) => {
e.preventDefault();
???
}
ListTemplate.ts
import { HasFormatter } from "../interfaces/HasFormatter.js";
export class ListTemplate {
constructor(private container: HTMLUListElement){}
render(item: HasFormatter, heading: string, position: 'start' | 'end'){
const li = document.createElement('li');
const p = document.createElement('p');
const btn = document.createElement('button');
btn.appendChild(document.createTextNode('x'));
btn.setAttribute('id', 'listItemBtn')
p.innerText = item.format();
li.append(p);
li.append(btn)
if (position === 'start'){
this.container.prepend(li);
} else {
this.container.append(li);
}
}
remove() {
???
}
}
ListItem.ts
import { HasFormatter } from '../interfaces/HasFormatter.js'
export class ListItem implements HasFormatter { //ensuring that all structures follow HasFormatter structure
constructor(
public listItem: string
) {}
format() {
return `${this.listItem}`
}
}
HasFormatter.ts
export interface HasFormatter {
format(): string
}
index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TypeScript Tutorial</title>
<!-- <link rel="stylesheet" href="styles.css"> -->
</head>
<body>
<header>
<form id="newForm">
<div class="field">
<label>List item</label>
<input type="text" id="listItem">
</div>
<button>Add</button>
</form>
<div class="wrapper">
<!-- output list -->
<ul class="item-list">
</ul>
</div>
</header>
<script type="module" src='app.js'></script>
</body>
</html>
I'm not prety sure but I think that shoul be some like
listItemBtn.addEventListener('onclick', (e: Event) => {
//get the "<li>" where the button is
const element=e.target.parent;
//remove it
element.parent.removeChild(element);
}

how to filter/search list items and markers

The objective is to filter the display of list elements and corresponding markers.I'm unable to understand what is wrong with the logic. The search input should filter and when you undo/cancel the search input then the list should reappear with the markers.
HTML:
enter <html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title> Neighborhood Map</title>
<link href="style.css" rel="stylesheet">
<script src="js/jquery-3.2.1.js"></script>
<script src="js/knockout-3.4.0.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div id="sidebar" class="col-xs-12 col-sm-5 col-md-3">
<h1 id="header">Chennai City Cultural Hubs</h1>
<div class="search-box">
<input class="text-search" type="text" placeholder="Enter here" data-
bind="textInput: query">
</div>
<div class= "list-box">
<div class="menu" data-bind="foreach: filteredItems">
<a class="menu-item"data-bind="text: title, click: $parent.setLoc" >
</a>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-6 col-md-8">
<div id="map"></div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>
JS:
function appViewModel() {
var self = this;
this.query = ko.observable('');
var filter = self.query().toLowerCase();
this.locationArray = ko.observableArray([]);
locations.forEach(function (item) {
self.locationArray().push(item);
});
self.setLoc = function (clickedLoc) {
var clickedData = clickedLoc.marker;
google.maps.event.trigger(clickedData, 'click')
};
this.filteredItems = ko.computed(function () {
if (!this.filteredItems) {
return self.locationArray();
} else {
return ko.utils.arrayFilter(self.locationArray(), function (item) {
var result = (item.title.toLowerCase().indexOf(filter) > -1)
item.marker.setVisible(result);
return result;
});
}
}, self);
};
ko.applyBindings(new appViewModel());
An explanation and solution would be very helpful.
Issues:
Your filter variable is not observable, so it won't update after instantiation. It's always an empty string, since by the time you assign it, query() returns "".
Checking for !this.filteredItems in the computed does not do anything, because it will never be false. (filteredItems is a ko.computed instance, which will evaluate to true)
Solution
You can rewrite your filteredItems to:
this.filteredItems = ko.computed(function () {
var q = this.query().toLowerCase();
if (!q) {
// TODO (?): reset all `setVisible` props.
return this.locationArray();
}
return this.locationArray()
.filter(function(item) {
var passedFilter = item.title.toLowerCase().indexOf(q) > -1;
item.marker.setVisible(passedFilter);
return passedFilter;
});
}, self);
By calling query you create a dependency to any changes in the search input. By calling locationArray you ensure updates when the data source changes. Note that you'll need to make sure your setVisible logic executes, even when you clear the query...
Remarks & tips
If you want, you can swap out Array.prototype.filter with ko.utils.arrayFilter, but the .filter method is widely supported by now.
You can create more (pure) computeds to separate logic. (E.g.: const lowerQuery = ko.pureComputed(() => this.query().toLowerCase()))
I wouldn't call setVisible in the filter since it's an unexpected side effect. Why not make it computed as well?

ReferenceError: obj is not defined using webcomponents & polymer

This is my first project using web components and I'm a rookie at best with js/jquery, so I am not sure why this happening.
I have a custom element built in "search-form.html", all of my components and scripts are then brought in via a master "components.html" in the head of my index as such...
index.html:
<head>
<meta charset='utf-8'>
<script src="/static/template/js/webcomponents.min.js"></script>
<link rel="import" href="/static/template/components/components.html">
<link rel="icon" type="image/png" href="/static/template/img/favicon.png">
</head>
components.html:
<!-- POLYMER MUST BE LOADED -->
<link rel="import" href="/static/template/polymer/polymer.html">
<!-- TEMPLATE SCRIPTS -->
<link rel="import" href="/static/template/components/scripts.html">
<!-- Loads jquery and other scripts -->
<!-- TEMPLATE COMPONENTS -->
<link rel="import" href="/static/template/components/search-form.html">
then the search-form.html looks like this:
<!-- Defines element markup -->
<dom-module id="search-form">
<template>
<div class="input-prepend input-append">
<form id="search" method="get" action="/property/search">
<div class="btn-group">
<button id="search-type" class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
Search by
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a data-name="city" data-label="city">City</a></li>
<li><a data-name="zip" data-label="zip">Zip Code</a></li>
<li><a data-name="mls" data-label="mls">MLS Number</a></li>
</ul>
</div>
<input id="input-tag" class="input-xlarge" type="text" placeholder="Select a search type..." data-provide="typeahead" value="" />
<button type="submit" class="btn"><i class="fa fa-search"></i></button>
</form>
</div>
</template>
<script>
/********************************
/ TYPEAHEAD ARRAY
/*******************************/
var obj = {
"city" : [],
"zip" : [],
};
$(document).ready(function() {
/*dynamic zipcode*/
for(i=43000; i<=45999;i++){
obj.zip.push({val: i.toString(), string: i.toString()});
}
for(i=48000; i<=49999;i++){
obj.zip.push({val: i.toString(), string: i.toString()});
}
});
/********************************
/ SEARCH TYPEAHEAD FUNCTION
/*******************************/
$(function searchTag($) {
var data = [];
$('.dropdown-menu > li > a').on("click", function() {
data = $(this).data('name');
});
var that = this;
$('#input-tag').typeahead({
source: function(query, process) {
var results = _.map(obj[data], function(value) {
return value.val;
});
process(results);
},
highlighter: function(val) {
var value = _.find(obj[data], function(p) {
return p.val == val;
});
return value.string;
},
updater: function(val) {
var value = _.find(obj[data], function(p) {
return p.val == val;
});
that.setSelected(value);
return value.string;
}
});
this.setSelected = function(value) {
$('#input-tag').val(value.string);
//$('#input-tag').attr('data-value', value.val);
$('#input-tag').data('value', value.val);
};
});
/********************************
/ QUICK SEARCH TAG FUNCTION
/*******************************/
$(function () {
var caret = ' <span class="caret"></span>';
function searchSelect() {
$('.dropdown-menu > li > a').on("click", function() {
$('#search-type').html($(this).text() + caret);
$('#input-tag').attr('placeholder', $(this).data('label'));
$('#input-tag').attr('name', $(this).data('label'));
});
}searchSelect();
});
Polymer({
is: 'search-form',
created: function() {},
ready: function() {},
attached: function() {},
detached: function() {},
attributeChanged: function(name, type) {}
});
</script>
</dom-module>
so the var obj is declared in search-form.html
finally, because of the way our back end is written, I have to run a loop on index.html to get the array to be used in var obj for "city" : [],
that looks like this:
/*TYPEAHEAD ARRAY*/
//MUST BE RUN FROM SHELL BECAUSE OF THE TMPL LOOP
<!-- TMPL_LOOP Cities -->
obj.city.push({val: "<!-- TMPL_VAR city_name -->", string: "<!-- TMPL_VAR city_name -->"})
<!-- /TMPL_LOOP -->
So now the problem. This all works without error in chrome, but I get the same error in FF, IE11, and Edge. That error is:
ReferenceError: obj is not defined
obj.city.push({val: "ALLEN PARK", string: "ALLEN PARK"})
Man I hope I documented this well enough for someone to help, because I have been pulling my hair out for hours before turning to stack :)
I think the imports are not loaded yet when you access the obj var in index.html, thus obj is undefined at this time. Since Chrome supports HTML imports natively, the imports are loaded earlier and it works there. Wrap the access to obj in HTMLImports.whenReady(callback). The callback will be called when all HTML imports have finished loading.
HTMLImports.whenReady(function(){
<!-- TMPL_LOOP Cities -->
obj.city.push({val: "<!-- TMPL_VAR city_name -->", string: "<!-- TMPL_VAR city_name -->"})
<!-- /TMPL_LOOP -->
});

Exposing API in polymer

I'm trying to learn polymer, and I'm trying make a basic messaging framework. So I created a little polymer-element called messages-framework that will display messages, and after 3 seconds remove the message. How can expose a method that will add a message to this element?
Here's my polymer element
<link rel="import" href="../bower_components/paper-input/paper-input-decorator.html">
<link rel="import" href="../bower_components/paper-button/paper-button.html">
<polymer-element name="messages-framework">
<template>
<ul>
<template repeat="{{ m in messages }}">
<li>{{ m }}</li>
</template>
</ul>
<paper-input-decorator label="message" floatinglabel>
<input is="core-input" value="{{ message }}" type="text">
</paper-input-decorator>
<paper-button raised on-tap="{{ addMessage }}">Add Message</paper-button>
</template>
<script>
Polymer({
messages: [],
addMessage: function () {
this.messages = this.messages.concat(this.message);
setTimeout(this.removeMsg.bind(this, this.message), 3000);
this.message = "";
},
removeMsg: function (msg) {
this.messages = this.messages.filter(function (m) {
return msg !== m;
});
}
});
</script>
</polymer-element>
Any help would be appreciated!
I don't think I phrased my question well. If have had two polymer elements, for instance messages-framework and a newuser-form, if newuser-form needed to add to the messages-framework, how would that polymer element use messages-framework API?
Exposing API in Polymer is just like one would do in every day elements.
Get a reference to the Element. eg var p = document.getElemnetTagName('p');
use it's methods. eg p.innerHTML = "Hello World" or p.setAttribute(attribute,value)
Example:
Your Custom Element
<polymer-element name="messages-framework">
<template>
//all your core markup
</template>
<script>
Polymer({
messages: [],
addMessage: function () {
this.messages = this.messages.concat(this.message);
setTimeout(this.removeMsg.bind(this, this.message), 3000);
this.message = "";
},
removeMsg: function (msg) {
this.messages = this.messages.filter(function (m) {
return msg !== m;
});
}
});
</script>
</polymer-element>
Using it's API
<body>
//Initialing the Element by loading it on to the page
<messages-framework></messages-framework>
<script>
var messagesFramework = document.getElementsByTagName('messages-framework');
//this will call the addMessage() method of the element
messagesFramework.addMessage();
</script>
</body>

Polymer: How to access elements within the 'content' tag

I have made a custom element, a list with a built-in filter:
Template for 'filter-list':
<template>
<content select="item"></content>
</template>
...
<filter-list filter='AAA'>
<item class="AAA">1</item>
<item class="AAA">2</item>
<item class="BBB">3</item>
<item class="BBB">4</item>
<item class="CCC">5</item>
<item class="CCC">6</item>
</filter-list>
The idea is that the list will show/hide items that match the class in filter. To this end, I wrote the following callback:
Polymer('filter-list', {
filter: '',
ready: function() {
},
filterChanged: function() {
if(this.filter) {
items = this.$.items.querySelectorAll("."+this.filter);
console.log("Showing "+items.length+" items.");
}
},
});
But I can't get through to the item nodes. items.length is always 0. How can I get to the item-elements of the light DOM within the custom element API?
You can access the nodes that are inserted with the <content> tag through the getDistributedNodes() function.
Here is a small complete example for your scenario:
<polymer-element name="filter-list" attributes="filter">
<template>
<content id="items" select="item"></content>
</template>
<script>
Polymer('filter-list', {
ready: function() {
this.filter = '';
},
filterChanged: function() {
var items = this.$.items.getDistributedNodes();
for (var i = 0; i < items.length; ++i) {
items[i].style.display = items[i].className == this.filter ? 'block' : 'none';
}
}
});
</script>
</polymer-element>
<polymer-element name="my-app" noscript>
<template>
<input value="{{filter}}">
<filter-list filter="{{filter}}">
<item class="AAA">1</item>
<item class="AAA">2</item>
<item class="BBB">3</item>
<item class="BBB">4</item>
<item class="CCC">5</item>
<item class="CCC">6</item>
</filter-list>
</template>
</polymer-element>
<my-app></my-app>
One can enter the filter string in the input field. The filter is initially empty, so no items are displayed after a page load.

Categories