For days I try to feed Polymer with some "dynamic" elements :) Unfortunately without success...
My goal is to get an element added during runtime and fill it with content by polymer-data-binding (in a "natural" polymer-way. Without a workaround as suggested in another stackoverflow answer.)
Please take a look at the code in this fiddle (https://jsfiddle.net/mkappeller/ken9Lzc7/) or at the bottom of this question.
I really hope anyone out there is able to help me out.
It would also help to know if there will be a way to do this some day in the future...?
window.addEventListener("WebComponentsReady", function() {
var myAppModule = document.getElementById("my-app");
var myApp = document.getElementsByTagName("my-app")[0];
myApp.set("global", {
text: "Init"
});
});
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link rel="import" href="paper-button/paper-button.html">
<link rel="import" href="paper-input/paper-input.html">
<dom-module id="my-app">
<template>
<paper-input label="global.text" value="{{global.text}}"></paper-input>
<paper-button raised id="addHeadline">Add Headline</paper-button>
<paper-button raised id="changeText">Change Text</paper-button>
<p>global.text: <{{global.text}}></p>
</template>
<script>
Polymer({
is: "my-app",
properties: {
global: {}
},
ready: function() {
this.count = 0;
this.$.addHeadline.addEventListener("click", () => {
var myApp = Polymer.dom(document.getElementById("my-app"));
var t = myApp.node.childNodes[1];
var heading = document.createElement("h1");
heading.textContent = "{{global.text}}";
// Append to my-app
Polymer.dom(this.root).appendChild(heading);
});
this.$.changeText.addEventListener("click", () => {
this.set("global.text", "count-" + this.count++);
});
}
});
</script>
</dom-module>
<my-app></my-app>
I've almost got it by using Polymer.Templatizer behaviour. Unfortunately it seems that there is some bug or my mistake which prevents updates from parent from being applied to the dynamically created template. I'll raise an issue on GitHub.
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<link rel="import" href="paper-button/paper-button.html">
<link rel="import" href="paper-input/paper-input.html">
<dom-module id="my-app">
<template>
<paper-input label="global.text" value="{{global.text}}"></paper-input>
<paper-button raised id="addHeadline" on-tap="addHeadline">Add Headline</paper-button>
<paper-button raised id="changeText" on-tap="click">Change Text</paper-button>
<p>global.text: <{{global.text}}></p>
<template if="true" is="dom-if">
<div>
dom-if: <input type="text" value="{{global.text::input}}" />
</div>
</template>
</template>
<script>
Polymer({
is: "my-app",
behaviors: [ Polymer.Templatizer ],
properties: {
global: {
value: { text:'i' },
notify: true
}
},
ready: function() {
this.count = 0;
// this ensures that global.text is updated when text changes
this._instanceProps = {
text: true
};
},
addHeadline: function() {
this.template = document.createElement("template");
var templateRoot = document.createElement('div');
templateRoot.innerHTML = `<h1>{{text.text}}</h1><input type="text" value="{{text.text::input}}" />`;
// you cannot just set innerHTML in <template>
this.template.content.appendChild(templateRoot);
this.templatize(this.template);
var clone = this.stamp({
text: this.global
});
// Append to my-app
Polymer.dom(this.root).appendChild(clone.root);
},
click: function() {
this.set("global.text", "count-" + this.count++);
},
_forwardInstanceProp: function(inst, p, val) {
debugger;
},
_forwardInstancePath: function(inst, p, val) {
// notify parent's correct path when text.text changes
this.set(p.replace(/^text/, 'global'), val);
},
_forwardParentProp: function(prop, value) {
debugger;
},
_forwardParentPath: function(prop, value) {
debugger;
}
});
</script>
</dom-module>
<my-app></my-app>
Related
I have a component, "cmptest", which have a watched property, "needWatch".
This component is inside a v-if statement, but the watched function is not called when it is rendered.
As shown in the example, the "needWatch" prop is setted with "value" data property from "cmpcheck", what makes me expect that the watch callback should be fired here.
If I remove the v-if statement, the function is called as expected when the checkbox is clicked.
<div v-if="value===true"><!--remove-->
<cmptest :need-watch="value"></cmptest>
</div><!--remove-->
Is this by design? What am I doing wrong here?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="vue.js"></script>
<script type="text/x-template" id="cmptesttemplate">
<div>
<p>needWatch: {{needWatch}}</p>
<p>updateByWatch: {{updateByWatch}}</p>
</div>
</script>
<script type="text/x-template" id="cmpchecktemplate">
<div>
<input id="checkBox" type="checkbox" value="1" v-on:click="setCheckboxValue()">
<div v-if="value===true">
<cmptest :need-watch="value"></cmptest>
</div>
</div>
</script>
</head>
<body>
<div id="container">
<cmpcheck></cmpcheck>
</div>
<script>
var cmptest = Vue.component('cmptest', {
template: '#cmptesttemplate',
data: function() {
return {
updateByWatch: ''
}
},
props: ['needWatch'],
watch: {
needWatch: function(v) {
console.log('Watched!');
this.updateByWatch = Math.random();
}
}
});
var cmpcheck = Vue.component('cmpcheck', {
template: '#cmpchecktemplate',
data: function() {
return {
value: 'Unitialized'
};
},
methods: {
setCheckboxValue: function() {
console.log('SELECTED');
var el = $(this.$el).children('#checkBox').is(':checked');
this.value = el;
}
}
});
new Vue({
el: '#container',
components: {
'cmptest': cmptest,
'cmpcheck': cmpcheck
}
});
</script>
</body>
</html>
Well, as long as value is Unitialized (thus, not true), <cmptest :need-watch="value"></cmptest> will never be rendered, so the watcher does not actually exist. Once setCheckboxValue is called, if value is true, the cmptest will be rendered and then the watcher initialized. But value is already true, so it's not triggered.
However, you can use:
watch: {
needWatch: {
immediate: true,
handler(nv, ov) { ... }
}
}
so that your watcher callback runs when needWatch is initiated.
Here is the problem I am working on.
Attached is an index.html.
Implement the next and previous buttons to navigate to next/previous posts from the API provider (https://jsonplaceholder.typicode.com).
Create a new tag that displays the current post ID.
Bonus points: Create a new array to store all of the previously retrieved posts, and display them in a list.
The API is from jsonplaceholder.typicode.com, so you don't need to implement that. A jsfiddle would be immensely appreciated.
<!DOCTYPE html>
<html>
<head>
<title>Vue Test</title>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css' />
<div id="app">
<h1>{{ message }}</h1>
<div>
<span>{{post.title}}</span>
</div>
<button v-on:click="previousPost">Previous Post</button> <!-- // new vue directive - v-on:click, also -->
<button v-on:click="nextPost">Next Post</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
***emphasized text***crossorigin="anonymous"></script>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Welcome to Vue!',
post: {},
page: 1
},
mounted: function() {
this.getPost()
},
methods: {
nextPost: function() {
this.page = this.page + 1
this.getPost()
// Implement me
},
previousPost: function() {
// Implement me
this.page = this.page - 1
this.getPost()
},
getPost: function() {
var root = 'https://jsonplaceholder.typicode.com';
$.ajax({
url: root + '/posts/' + this.page,
method: 'GET'
})
.then((data) => {
console.log(data);
this.post = data;
});
}
}
})
</script>
<style>
/* Add any additional styles here */
body {
padding: 20px;
}
div {
margin: 12px 0;
}
</style>
I would also like to know what is meant by the post id over there. I have completed one task which shows the next and previous posts, but the other two tasks I am a little confused.
I try set custom-row-style in paper-datatable. demo
This is working code:
<!doctype html>
<html>
<head>
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-datatable/paper-datatable.html">
</head>
<body>
<template is="dom-bind" id="app">
<paper-datatable data="{{data}}" custom-row-style="{{generateRowCss}}" >
<paper-datatable-column header="Calories" property="calories"></paper-datatable-column>
</paper-datatable>
</template>
<script>
var app = document.querySelector('#app');
app.data = [
{id: 0, title: 'Frozen yogurt', calories: 159},
{id: 0, title: 'Frozen yogurt', calories: 159}
];
app.generateRowCss = function(item){
return 'background:red;';
};
</script>
</body>
</html>
generateRowCss is a property, not a function
If I try insert in my module:
var app2 = document.querySelector('#idModule');
or var app2 = document.getElementById('idModule');
app2.generateRowCss = function(item){return 'background:red;';};
app2 return null
Best way for me - to use the properties of the polymer:
Polymer({
is: 'idModule',
properties: {
generateRowCss: {
type: String,
value: "background:red;"
or function(item){return 'background:red;';}
or computed: '_generateRowCss(item)'
},
However, this causes errors in the polymer modules:
Uncaught TypeError: this.customRowStyle is not a function.
Is this possible to have data binding inside an inline script tag? For example:
<script src="{{url}}" class="{{klass}}"></script>
Polymer({
is: "test-app",
ready: function() {
url = "http://google.com/js/some-file.js",
klass = "script-class"
}
});
Based on the Polymer 1.0 Data Binding docs, I could not come up with anything better.
I have edited this post to 100% clarity of what I want to achieve. I want to use Strip Embedded Checkout:
<form action="" method="POST">
<script
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="pk_test_blahblah"
data-amount="2000"
data-name="Demo Site"
data-description="2 widgets ($20.00)"
data-image="/128x128.png">
</script>
</form>
Mason's answer and Anthony's clue led me to this:
<dom-module id="my-app>
<template>
<form action="" method="POST">
<script
src$="{{url}}" class$="{{klass}}"
data-key$="{{key}}"
data-amount$="{{total}}"
data-name$="{{dname}}"
data-description="2 widgets ($20.00)"
data-image="/128x128.png">
</script>
</form>
</template>
<script>
Polymer({
is: "my-app",
properties: {
selection: {
type: String,
observation: "selectionChanged"
}
},
ready: function() {
this.url = 'https://checkout.stripe.com/checkout.js';
this.klass = 'stripe-button';
this.key = 'pk_test_blahblah';
this.dname = 'Demo';
// this.total = "333"; // this value is not static
},
selectionChanged: function () {
if (true) {
this.total = 50; // I need this to assign to "{{total}}" in the template.
}
};
</script>
</dom-module>
How can I get the value of this.total to be assigned to data-amount in the script tag of Stripe's?
See Plunkr
I did a Plunk and it seems it's not possible to do this.
<dom-module id="my-element">
<template>
<script src="{{url}}"></script>
Try to load <span>{{name}}</span>
</template>
<script>
Polymer({
is: 'my-element',
ready: function() {
this.name = 'JQuery';
this.url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js';
this.async(function() {
this.name = 'AngularJs';
this.url = 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js';
}, 5000);
}
});
</script>
</dom-module>
The first loading works but when the bind value has changed, Polymer does not create a new script tag for you. You can create a script tag by using the DOM.
Edited:
Initializing the script tag's attributes without "ready" method.
<template>
<script src="{{_url}}"></script>
</template>
<script>
Polymer({
is: 'my-element',
properties: {
_url: {
type: String,
value: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js'
}
}
})
</script>
Or using hosted attributes
<template>
<script src="{{url}}"></script>
Try to load <span>{{name}}</span>
</template>
<script>
Polymer({
is: 'my-element',
hostAttributes: {
url: {
type: String
}
}
})
</script>
and in the parent element:
<my-element url="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></my-element>
By using hostAttributes you'll see the url in the DOM.
index.html
<html>
<head>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="js/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="js/lib/bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
<link href="js/lib/ratchet/ratchet-theme-ios.css" rel="stylesheet">
<link href="js/lib/ratchet/ratchet.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>totter</title>
</head>
<body>
<div class="content">
</div>
<script src="js/lib/jquery-1.9.1.min.js"></script>
<script src="js/lib/underscore-min.js"></script>
<script src="js/lib/backbone-min.js"></script>
<script src="js/lib/bootstrap/js/bootstrap.js"></script>
<script src="js/lib/handlebars/handlebars-v1.3.0.js"></script>
<script src="js/lib/ratchet/ratchet.js"></script>
<script src="js/common/helper.js"></script>
<script src="js/app.js"></script>
<script src="js/views/home.js"></script>
<script src="js/views/signin.js"></script>
<script src="js/models/home.js"></script>
<script src="js/models/signin.js"></script>
</body>
</html>
app.js
var app = {
views: {},
models: {},
loadTemplates: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (app[view]) {
deferreds.push($.get('template/' + view + '.hbs', function(data) {
app[view].prototype.template = _.template(data);
}, 'html'));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
app.Router = Backbone.Router.extend({
routes: {
"": "home",
"signIn":"SignIn"
},
home: function () {
// Since the home view never changes, we instantiate it and render it only once
if (!app.home) {
app.home = new app.HomeView();
app.home.render();
} else {
// delegate events when the view is recycled
app.home.delegateEvents();
}
},
SignIn:function(){
if (!app.signin) {
app.signin = new app.SignInView();
app.signIn.render();
} else {
// delegate events when the view is recycled
app.signin.delegateEvents();
}
}
});
$(document).on("ready", function () {
app.loadTemplates(["HomeView"],
function () {
app.router = new app.Router();
Backbone.history.start();
});
});
home.js
app.HomeView = Backbone.View.extend({
//Calling the render method to render view from the template
initialize:function(){
this.render();
},
//Pass the handlebars template for complilation and
render: function () {
var path = './template/HomeView.hbs';
Helper.GET_TEMPLATE(path, function(template) {
//pass collection to template to set values
var html = template(app.homeCollection.toJSON());
//pass collection to template to set values
$('.content').html(html);
});
$('.signin').bind('click', function(e) {
app.Router.navigate("signIn", {trigger: true});
});
}
});
app.home = new app.HomeView();
sigin.js
app.SigInView = Backbone.View.extend({
//Calling the render method to render view from the template
initialize:function(){
this.render();
},
//Pass the handlebars template for complilation and
render: function () {
var path = './template/SignInView.hbs';
Helper.GET_TEMPLATE(path, function(template) {
//pass collection to template to set values
var html = template(app.signinCollection.toJSON());
//pass collection to template to set values
$('.content').html(html);
});
}
});
app.signin = new app.SigInView();
helper.js
var Helper = {};
Helper.GET_TEMPLATE = function(path,callback){
var source, template;
$.ajax({
url: path,
success: function(data) {
source = data;
//Compile the raw data we got from the file
template = Handlebars.compile(source);
//execute the callback if passed
if (callback){
callback(template);
}
}
});
}
homeModel.js
var HomeModel = Backbone.Model.extend();
var homeData = new HomeModel({
id: 1,
signUpTitle: 'Sign Up for TOT ',
signInTitle: 'Sign In',
slogan:'slogan slogan slogan slogan slogan slogan slogan slogan slogan slogan '
});
/**
* Defining a Collection to set model
*/
var HomeCollection = Backbone.Collection.extend({
model: HomeModel
});
/**
* Defining a array to hold the collection
*/
app.homeCollection = new HomeCollection([homeData]);
HomeView.hbs
<header class="bar bar-nav">
<h1 class="title">totter</h1>
</header>
<div class="logo">
<img src = "img/choice.png">
</div>
{{#each []}}
<div class="textcontent">
<label>{{this.slogan}}</label>
</div>
<div class="footer">
<button class="btn btn-primary btn-block signup" style="">{{this.signUpTitle}}</button>
</div>
<div class="footer">
<button class="btn btn-primary btn-block signin">{{this.signInTitle}}</button>
</div>
{{/each}}
In the above code I used to develop a simple app with 2 views. I want to show sign-in view on button click of sign-in.
How can I achive this? I am using "handlebars" and "backbone.js".
The events aren't firing because you're utilizing the View el property. Either give it a preexisting element or insert the el itself to the DOM.
see the answer on the following discussion for a more comprehensive explanation.