Wakanda: how to change class of a custom widget on attribute change? - javascript

I'm creating a custom widget in Wakanda.
This widget must change a class in function of a property. For example, if the property shadowDepth=2 then I will set the class as mdl-shadow--2dp, else if shadowDepth=6 I will set the class as mdl-shadow--6dp.
How could I change the class this way?

The most used approach is the following:
Add an enum attribute to the custom Widget.
Consider the existing class at Widget initialisation.
Make an onChange trigger to apply your desired classes.
Let's view each step separately:
1. Add an enum custom attribute
I expect you know how to create a custom widget. Let's add the attribute to the widget definition in widget.js:
var myWidget = widget.create('myWidget', {
// widget attributes
});
for an enum attribute you can define it as it follows:
shadowDepth: widget.property({
type: 'enum',
description: 'Shadow depth for MDL widget',
values: {
'mdl-shadow--1dp': '1',
'mdl-shadow--2dp': '2',
'mdl-shadow--3dp': '3',
'mdl-shadow--4dp': '4',
'mdl-shadow--5dp': '5',
'mdl-shadow--6dp': '6'
},
defaultValue: 'mdl-shadow--2dp',
bindable: false
})
This way you have added your enum attribute which does nothing for the moment.
2. If the widget has already one of the classes, set the property attribute value (initialValue)
In order to retrieve the initial value of the widget, which can be assigned in the DOM, we define the defaultValueCallback function as it follows in the attribute property just after defaultValue:
defaultValueCallback: function() {
var r = /mdl-shadow--\ddp/.exec(this.node.className); // regex are cool
return r[r.length - 1] || 'mdl-shadow--2dp'; // return the latest occurrence of the class or the default value
},
3. the onChange trigger
In the init attribute declaration of the widget add the following code:
init: function() {
// first call to set the attribute
this._changeShadowDepth(this.shadowDepth());
// onChange event
this.shadowDepth.onChange(this._changeShadowDepth);
}
and add the _changeShadowDepth function as it follows:
_changeShadowDepth: function(value) {
var $node = $(this.node); // jQuery is included in the prototype, do not worry
[
'mdl-shadow--1dp', 'mdl-shadow--2dp', 'mdl-shadow--3dp',
'mdl-shadow--4dp', 'mdl-shadow--5dp', 'mdl-shadow--6dp'
].forEach(function(klass) {
$node.removeClass(klass);
});
if (value) {
$node.addClass(value);
}
},
This is the same exact approach used by the official Image widget to change the align (background-align) property.

Related

Tabulator: autoColumnsDefinitions using custom cell formatter and dynamically defined fields?

I have an ajax Tabulator in which I'd like to use a custom cell formatter on columns which are dynamically (but similarly) defined.
Let's say I have a dataset of People with "Event" columns, where the ajax response can contain up to 50 Event fields, named Event1,Event2...Event50.
I could manually repeat the definitions using the Column Definition Array or Field Name Lookup Object approaches, like:
autoColumnsDefinitions:{
PersonID: { title:"ID #", align:"right"},
Event1: {formatter: eventFormatter},
Event2: {formatter: eventFormatter},
...[variable # of Event cols here]...
},
...
function eventFormatter(cell){
// format event here
}
However, using a callback seems more appropriate. When I try something like this, my eventFormatter is never invoked, and no errors or warnings are raised.
autoColumnsDefinitions:function(definitions){
definitions.forEach((column) => {
if(column.field.match(/Event/)) {
column = {
formatter:eventFormatter
}
}
Is it possible to apply a custom cell formatter to fields that are not explicitly named prior to data loading?
Your must update 'column' with formatter as
autoColumnsDefinitions: function (definitions) {
return definitions.map((column) => {
if (column.field.match(/(name|title)/)) {
column.formatter = titleFormatter;
}
return column;
});
}
See JSFiddle

v-bind isn't detecting the change in array content (vue js)

I'm trying to change the id of an image after the user clicks on a button. Initially, the id of the element should be B0_h, but after the user clicks on a button, a value in an array should change to true. Initially all the array values are false, but once the value of the element in the array becomes true, the id should change to B0_v.
Using the Vue dev tools, I noticed that the value in the array is changing as expected, however, v-bind can't detect this change. From the v-bind perspective, the value of B[0] is still false. As a result, the id is still B0_h.
Here's my template:
<template>
<div>
<button type="button" id="button0" v-on:click="toggle('B0')"></button>
<img src="../assets/img1.png" alt="A" v-bind:id="[A[0] === true ? 'A0_v' : 'A0_h']" >
<img src="../assets/img2.png" alt="B" v-bind:id="[B[0] === true ? 'B0_v' : 'B0_h']">
</div>
</template>
Here's my script:
<script>
export default {
name: 'Demo',
props: {},
data: function(){
return{
A: [false,false,false,false,false,false,false,false,false],
B: [false,false,false,false,false,false,false,false,false],
playerTurn: true;
}
},
methods:
{
toggle(x)
{
if(x == 'B0' && this.playerTurn)
{
this.B[0] = true;
}
}
}
}
</script>
Any idea what I'm doing wrong in here?
This is due to the handling of changes by Vue in arrays and objects. Vue won't track the change you're making. For this purpose it offers a special method: set. It takes 3 arguments: the array (or the object) that has to be changed, the index, and the value that should be set.
In your example it'll look like this:
Vue.set(this.B, 0, true);
Put this line instead of:
this.B[0] = true;
For more information please check the official documentation. This is a short excerpt:
Vue.set( target, propertyName/index, value ) Arguments:
{Object | Array} target
{string | number} propertyName/index
{any} value
Returns: the set value.
Usage:
Adds a property to a reactive object, ensuring the new property is
also reactive, so triggers view updates. This must be used to add new
properties to reactive objects, as Vue cannot detect normal property
additions (e.g. this.myObject.newProperty = 'hi').

Vue-Form-Generator schema is not reactive to computed properties

I have computed property in my data this.coinPairingOptions that needs to render its radio buttons based on some of the other fields in this schema. I have reduced the amount of code in the file to save space.
data: function () {
return {
schema: {
{model: "symbolPair", type: "radios", label: "Pair with", values:
this.coinPairingOptions, required: true}
},
computed: {
coinPairingOptions() {
console.log("computing coinPairingOptions")
let coin = this.model.symbol.toUpperCase();
let options = [];
if (this.model.exchange === 'Coinbase') {
options = this.getCoinbasePairs
} else if (this.model.exchange === 'Binance') {
options = this.getBinancePairs
} else {
}
console.log(options.get(coin));
return options.get(coin);
},
}
In the dev tools I can see the computed property changing to the correct values however it is not changing in the data. Apparently, this is appropriate behavior, but what is a way around this? I have tried putting {{this.coinPairingOptions}} in the html and it errors because it's a computed property with not value initially.
Any help would be appreciated!
You can't use computed property in data, because data evaluates before the computed properties did.
You can use a watcher to achieve the intended result. Have a look at the documentation, you can add the argument immediate to trigger the callback immediately with the current value of the expression.
Computed properties are already accessible in the template by using {{}}. You don't need to put a this in front of the computed.

Is it possible to bind properties to a document.createElement element?

I want to write a custom element like this.
<dom-module id="custom-element">
<template>
<!-- This part I want to use document.createElement -->
<template is="dom-repeat" items="[[data]]">
<div>
<span>[[item.name]]</span>
<span>[[item.count]]</span>
</div>
</template>
</template>
</dom-module>
The count value may be changed by other element.
Is it possible to bind properties count to a document.createElement element?
class CustomElement extend Polymer.Element {
static get is () { return "custom-element" }
static get properties () {
return {
data: {
type: Array,
value: [{
"name": "item1",
"count": 0
}, {
"name": "item2",
"count": 0
}, {
"name": "item3",
"count": 0
}],
notify: true,
observer: "_dataChanged"
}
}
}
_dataChanged: (data) => {
data.map((item) => {
let div = document.createElemnt("div")
let itemName = document.createElement("span")
itemName.textContent = item.name
let itemCount = document.createElement("span")
// I want to bind value count here
itemCount.textContent = item.count
div.appendChild(itemName)
div.appendChild(itemCount)
this.shadowRoot.appendChild(div)
})
}
}
window.customElements.define(CustomElement.is, CustomElement)
When other element change the count value, the element which create by document.createElement's count will be change.
Is this possible?
So, data binding works, between two custom elements
Whether you use a dom-repeat with the bind, or bind it to an element like an input in the shadow DOM,
OR,
you use document.createElement to append an element to your shadowDOM, with the bound value,
does not matter
you use {{}} for a two way binding, and a [[]] for a one way binding.
So, If other-element changes adata property bound to custom-element, and you want custom-element to accommodate them, use one way
If you want the changes made by custom-elemnt to also change values in other-element, use two way.
If you are using the console to mutate data of one element, I am afraid, those mutations aren't reflected to the custom element.
If however, you are having a binding between custom-element and other-element on the property data, and
other-element mutates the property data as the result of an event, then, these changes are notified to all communicating elements.
Edit: I see that you have a subsequent query in the comments, which asks why is the plunk you made not working
While you are now correctly binding the elements two-way via data ,
there is a bug in polymer, that doesn't notify observers on data mutation if data is an array.
Solution for now: Shallow clone your data using slice after you change it
like so:
add (e) {
this.set(`data.${e.target.index}.count`, (this.data[e.target.index].count + 1))
this.data = this.data.slice();
}

How to set a default value for a dependent field based on the answer of the parent with Javascript?

I'm trying to have 4 sub fields, billing code, channel, subject code and name, autopopulate certain values based on the answer of their parent field, Event Type. In other words, if the answer to event type is "A", dropdown fields will appear for each sub field based on "A".
Let's say the first select-element of the document has the values
'Plant', 'Animal' and 'Ghost' and the next select-element should change
when the selected value of first ele changes, then this is how we do it:
var majorField = document.getElementsByTagName('select')[0]
var minorField = document.getElementsByTagName('select')[1]
// Define a dict, mapping each possible value to a function:
majorField.onChangeMap = {
Plant: function() { minorField.innerHTML = '<option>Cocoa</option><option>Cocos</option>' },
Animal: function() { minorField.innerHTML = '<option>Cat</option><option>Cow</option>' },
Ghost: function() { minorField.style.visibility = 'hidden' },
}
// When value changes, execute function of dict-map:
majorField.onchange = function(event) {
event.target.onChangeMap[event.target.value]()
}
// Make sure he minor-field has initially the correct state, by
// executing the mapped function for the major-field's current-value:
majorField.onChangeMap[majorField.value]()
Note that this is a very minimal illustration-example for the mapping only, as
one wouldn't set the options as an html-string, and hiding the minorField on
Ghost should be reversed on all other options with visibility = 'visible', here.

Categories