Polymer 1.0: Binding css classes does not update - javascript

I have something like this:
<dom-module id="bar-foo">
<template>
<span class$="{{getState()}}">Bar Foo</span>
</template>
<script>
(function() {
class BarFoo {
beforeRegister() {
this.is = 'bar-foo';
this.properties = {
data: {
type: Object,
notify: true,
observer: '_updateData'
};
}
getState() {
if (this.data) {
return this.data.val > 0 ? 'positive' : 'negative';
}
}
}
Polymer(BarFoo);
})();
</script>
</dom-module>
Now, the getState function is only called once, but the data property is updated every second. Is it possible to update the class$ property when the data changes ?

If you want the getState function to be evaluated every time data.val changes, you can pass it as an argument to the function.
<span class$="{{getState(data.val)}}">Bar Foo</span>
Have a look at the docs for more information on computed bindings.

Related

Why is the update function and "this" not providing expected data for component

I'm having trouble getting the data returned properly. When you first load this page, it logs a blank {} for oldData, and "this" returns the element itself. When you click, it then logs the mouseEvent for the oldData, and the window element for "this".
I expect it should consistently log data from the schema. I'm not sure if the object should be blank when the update first fires, but it depends what "oldData" is at that point. I also expect "this" to consistently be a reference to the component function, not the window. What am I not considering here?
<html lang="en">
<head>
<script src="https://aframe.io/aframe/dist/aframe-master.min.js"></script>
<script>
AFRAME.registerComponent('my-component', {
multiple: false,
schema: {
mood: {
type: 'string',
default: 'happy'
}
},
init() {
window.addEventListener('click', this.update)
},
update(oldData) {
console.log("oldData: ", oldData);
console.log("this: ", this);
},
})
</script>
</head>
<body>
<a-scene>
<a-sky color="blue"></a-sky>
<a-box id="bluebox" color="green" position="-1 0 -3" my-component></a-box>
<a-light position="-3 4 2" type="spot" target="#bluebox"></a-light>
</a-scene>
</body>
</html>
You are passing this.update as a callback and the 'this' will be under the context of the listener method. try:
init () {
this.update = this.update.bind(this)
window.addEventListener('click', this.update)
},
Two problems:
Methods are not bound to the component unless you call them as componentObj.methodName(). You can bound them explicitly with bind:
this.methodName = this.methodName.bind(this);
If the update method is called through the event listener the oldData parameter won't be passed. Better to create your own method onClick. You can rely on this.data instead of oldData in your method logic.
window.addEventListener('click', this.onClick.bind(this));
AFRAME.registerComponent('foo', {
schema: {},
init: function () {
window.addEventListener('click', this.onClick.bind(this));
},
update: function () {},
onClick: function () { ... this.data ... }
});
Okay this works correctly. Upon click (using bind method), the color attribute changes on the internal component, which fires the update method, which displays the oldData and the new data. It also changes the attribute of the external color component based on the internal color from this.data.
<script>
AFRAME.registerComponent('my-component', {
multiple: false,
schema: {
color: {default: '#' + Math.random().toString(16).slice(2, 8).toUpperCase()}
},
init: function () {
this.changeAtt = this.changeAtt.bind(this)
window.addEventListener('click', this.changeAtt);
this.el.setAttribute('color', this.data.color)
},
update: function (oldData){
console.log("oldData: ", oldData);
console.log("current Data: ", this.data);
},
changeAtt () {
el = this.el;
el.setAttribute('my-component', {color: '#' + Math.random().toString(16).slice(2, 8).toUpperCase()})
el.setAttribute('color', this.data.color)
}
})
</script>

Capture events of underlying element in component

Trying to use this component.
<select2 v-model="value" :options="options" #change="onChange()"></select2>
The #change callback is not getting called. I know that I can use watch: { value: function () { ... } but, is there a way to capture underlying tag events?
In the current version, select2 component does not handle on-change function. For this, you have to modify the select2 component, you have to add one more prop: onChange and inside component execute the function passed in this prop, changes will be something like following:
Vue.component('select2', {
props: ['options', 'value', 'onChange'], //Added one more prop
template: '#select2-template',
mounted: function () {
var vm = this
$(this.$el)
.val(this.value)
// init select2
.select2({ data: this.options })
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
//New addition to handle onChange function
if (this.onChange !== undefined) {
this.onChange(this.value)
}
})
},
watch: {
value: function (value) {
// update value
$(this.$el).select2('val', value)
},
options: function (options) {
// update options
$(this.$el).select2({ data: options })
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
})
Now, you can pass a function which will be executed onChange like following:
<select2 v-model="value" :options="options" :on-change="onChange()"></select2>

CheckBox disabled attribute bound to Polymer Property's sub-property

I am trying to bind a Polymer property to the disabled attribute of a CheckBox, but the CheckBox only get disabled/enabled at the beginning.
I have to mention that the property I am binding to is a sub-property of a Polymer property.
Component's code:
<link rel="import" href="../polymer/polymer.html"/>
<dom-module id="checkbox-disabled-example">
<template>
<button
on-click="onClick" >
Toggle Disabled Enabled </button>
<template is="dom-repeat" items="{{elementsObject.items}}" as="item">
<input
type="checkbox"
disabled$="{{item.disabled}}" >
{{item.name}} <br>
</template>
</template>
<script>
Polymer({
is: 'checkbox-disabled-example',
properties:{
elementsObject:{
type: Object,
value: {}
},
},
onClick: function(){
this.elementsObject.items.forEach(
function(item, index, array){
item.disabled = !item.disabled;
}
);
}
});
</script>
</dom-module>
index.html code:
<body>
<template id="just-for-demo" is="dom-bind" >
<checkbox-disabled-example elements-object={{ckData}} >
</checkbox-disabled-example>
</template>
<script>
window.addEventListener('WebComponentsReady', function() {
var template = document.querySelector('template[is=dom-bind]');
var data =
{
items:
[
{ disabled: true, name:"Element 01"},
{ disabled: false, name:"Element 02"},
{ disabled: false, name:"Element 03"},
{ disabled: false, name:"Element 04"},
{ disabled: false, name:"Element 05"},
{ disabled: true, name:"Element 06"}
]
};
template.ckData = data;
});
</script>
</body>
As you can see I create several Check Boxes, each one of them bound to the disabled property of its item.
When I click on the button I toggle the disabled value of each item, however the Check boxes states don't change.
What am I doing wrong?
Changes that imperatively mutate an object or array are not observable.
If you have a simple property like:
this.name = 'Jane';
Polymer will automatically create a setter and it will automatically pick up any changes on that property.
However, changes on an object subproperty or an array item will not work:
this.address.street = 'Elm Street';
The setter on address will not be called and the change will not be detected.
Polymer provides specific methods for making observable changes to subproperties and arrays, in the example above you would need to call:
this.set('address.street', 'Elm Street');
The address.street part is called path. It's a string that identifies a property or subproperty relative to a scope. In most cases, the scope is a host element.
If you are creating a path to an array item, you will need to pass the index:
this.set('items.5', 'Changed index 5');
In your specific example, a simple change to the click event handler would be enough:
onClick: function() {
var that = this;
this.elementsObject.items.forEach(function(item, index, array) {
that.set('elementsObject.items.'+index+'.disabled', !item.disabled);
});
}
Or you can use the cool new ES6 arrow function syntax to make it more readable and remove the ugly that = this:
onClick: function() {
this.elementsObject.items.forEach((item, index, array) => {
this.set('elementsObject.items.'+index+'.disabled', !item.disabled);
});
}

call a component from another component in vue.js

I have an alert component like in this video: https://laracasts.com/series/learning-vue-step-by-step/episodes/21 And I have another component (Book). When a book was created how can I call Alert component in the success callback function like this:
<alert>A book was created successfully !!!</alert>
I am a newbie in using vue.js. Thank you for your help.
Updated: This is my code
submit: function () {
this.$http.post('/api/books/add', {
data: this.data,
}).then(function (response) {
// I want to use Alert component right here to notice to users.
}, function (response) {
});
}
Update 2:
Alert Component
<template>
<div class="Alert Alert--{{ type | capitalize }}"
v-show="show"
transition="fade"
>
<slot></slot>
<span class="Alert__close"
v-show="important"
#click="show = false"
>
x
</span>
</div>
</template>
<script>
export default {
props: {
type: { default: 'info' },
timeout: { default: 3000 },
important: {
type: Boolean,
default: false
}
},
data() {
return {show: true};
},
ready() {
if (!this.important)
{
setTimeout(
() => this.show = false,
this.timeout
)
}
}
}
</script>
<style>
.Alert {
padding: 10px;
position: relative;
}
.Alert__close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
}
.Alert--Info {
background: #e3e3e3;
}
.fade-transition {
transition: opacity 1s ease;
}
.fade-leave {
opacity: 0;
}
</style>
And in Book.vue I want to do like this:
// resources/assets/js/components/Book.vue
<template>
.....
<alert>A book was created successfully !!!</alert>
//Create book form
....
</template>
<script>
export default {
methods: {
submit: function () {
this.$http.post('/api/books/add', {
data: this.data,
}).then(function (response) {
this.$refs.alert
}, function (response) {
});
}
</script>
this JSfiddle does what you're looking for: https://jsfiddle.net/mikeemisme/s0f5xjxu/
I used a button press rather than a server response to trigger the alert, and changed a few method names, but principle is the same.
The alert component is nested inside the button component. Button passes a showalert prop to the alert component with the sync modifier set.
<alert :showalert.sync="showalert" type="default" :important="true">A book was saved successfully</alert>
Press the button, showalert is set to 'true', 'true' passed to alert as prop, alert displays as v-show condition is now true,
data() {
//by default we're not showing alert.
//will pass to alert as a prop when button pressed
//or when response from server in your case
return {
showalert: false
};
},
a watch on the showalert prop in alert component sees a change and triggers a method that sets showalert back to 'false' after whatever many seconds set in timeout property.
//this method is triggered by 'watch', below
//when 'showalert' value changes it sets the timeout
methods: {
triggerTimeout: function() {
//don't run when detect change to false
if (this.showalert === true) {
setTimeout(
() => this.showalert = false,
this.timeout
)
}
},
},
watch: {
// detect showalert being set to true and run method
'showalert': 'triggerTimeout',
}
Because this prop is synched back to parent, button state updated too.
It works but using watch etc. feels overblown. Vue may have a better way to handle this. I'm new to Vue so somebody with more knowledge might chime in.
Add a data property
alertShow: false
Next, in the callback:
this.alertshow = true;
When you want to remove it, set it to false.
In the component add a directive:
v-show="alertshow"
Update:
Add a components attribute to block component.
components: {Alert},
Next outside of the component, import the Alert component file:
import Alert from './directory/Alert.vue'
The above is if you are using vueify. Otherwise, add a component using
Vue.component
Check out the docs.
Update 2:
Your code, with the changes:
<script>
import Alert from './directory/alert.vue';
export default {
components: {
Alert
},
methods: {
submit: function () {
this.$http.post('/api/books/add', {
data: this.data,
}).then(function (response) {
this.$refs.alert
}, function (response) {
});
}

Setting Array Item Property Value in Callback in Polymer

I have an application that is using Polymer. In this application, I am binding an array of items to the UI. The user can click a button. When that button is clicked, a task associated with a third-party library is called. When that task is completed, it returns a status. I need to bind that status to a property of an item in my array. The third-party library allows me to use a callback function. For that reason, I'll demonstrate my challenge using JavaScript's baked in setTimeout function.
my-component.html
<dom-module id="view-tests">
<template>
<table>
<tbody>
<template is="dom-repeat" items="{{ items }}" as="item">
<tr>
<td>[[ item.name ]]</td>
<td><item-status status="[[ item.status ]]"></item-status></td>
</tr>
</template>
</tbody>
</table>
<button on-click="bindClick">Bind</button>
</template>
<script>
Polymer({
is: "my-component",
properties: {
items: {
type: Array,
notify: true,
value: function() {
return [
new Item({ name:'Item 1', status:'In Stock' }),
new Item({ name:'Item 2', status:'Sold Out' })
];
}
},
},
bindClick: function() {
var items = items;
setTimeout(function() {
this.set('items.1.status', 'In Stock');
}, 1000);
}
});
</script>
</dom-module>
As shown in the code snippet above, there is another component item-status.
item-status.html
<dom-module id="test-status">
<template>
<span class$="{{ statusClass }}">{{ status }}</span>
</template>
<script>
Polymer({
is: "item-status",
properties: {
status: {
type: String,
value: '',
observer: '_statusChanged'
}
},
_statusChanged: function(newValue, oldValue) {
alert(newValue);
if (newValue === 'In Stock') {
this.statusClass = 'green';
} else if (newValue === 'Sold Out') {
this.statusClass = 'red';
} else {
this.statusClass = 'black';
}
}
});
</script>
</dom-module>
When a user clicks the "Bind" button, the status does not get updated in the UI. I noticed the alert that I added for debugging purposes appears when the view initially loads. However, the alert window does not appear when the "Bind" button is clicked. This implies that the observer function is not firing. My callback actually looks something like this:
getStatus(1, function(status) {
this.set('items.1.status', status);
});
How do I set the property of an array item from a callback?
setTimeout has its own scope. '.bind(this)' can be used to bind the Polymer element scope to the callback function. Below bindClick function should work
bindClick: function() {
setTimeout(function() {
this.set('items.1.status', 'In Stock');
}.bind(this), 1000);
}
Working jsbin: http://jsbin.com/mehovu/edit?html,output

Categories

Resources