Data binding on JavaScript HTMLElement property in Polymer - javascript

Making an SPA using Polymer, and I need my custom components to all use a common custom component which represents my backend API and is responsible of GET-ting/POST-ing data from/to the API. It also serves as a "cache" and holds the data to display. This way, all the components that have access to this single element will share the same data.
So what I want to do is this... :
<my-api
users="{{users}}"
products="{{products}}">
</my-api>
...but programmatically, as <my-api> is not declared in all of my components but once in the top one and then passed down through the hierachy by JavaScript:
Polymer({
is: 'my-component',
properties: {
api: {
observer: '_onApiChanged',
type: HTMLElement
},
products: {
type: Array
},
users: {
type: Array
}
},
_onApiChanged: function(newVal, oldVal) {
if (oldVal)
oldVal.removeEventListener('users-changed', this._onDataChanged);
// Listen for data changes
newVal.addEventListener('users-changed', this._onDataChanged);
// Forward API object to children
this.$.child1.api = newVal;
this.$.child2.api = newVal;
...
},
_onDataChanged: function() {
this.users = this.api.users; // DOESN'T WORK as 'this' === <my-api>
this.products = this.api.products; // Plus I'd have to repeat for every array
}
});
Does Polymer offers a built-in way to do this ? Can I create a double curly braces binding programmatically ?

I would likely architect this slightly differently: passing down the products/users arrays declaratively taking advantage of Polymer's binding system. Or you could write your my-api element in such a way that they all share state and the first declared one is the primary while future declared ones are replicas. This would let you declare them wherever you need them and bind to the values via Polymer's normal ways.
But to answer your question, there's currently no way to easily programmatically setup the same kind of binding without using private Polymer APIs.
To avoid repeating as much and for the binding issue you were having you could use Polymer's built-in listen and unlisten methods:
Polymer({
is: 'my-component',
properties: {
api: {
observer: '_onApiChanged',
type: HTMLElement
},
products: {
type: Array
},
users: {
type: Array
}
},
_onApiChanged: function(newVal, oldVal) {
var apiProperties = ['users', 'products'];
if (oldVal) {
apiProperties.forEach(function(prop) {
this.unlisten(oldVal, prop + '-changed', '_onDataChanged');
});
}
// Listen for data changes
apiProperties.forEach(function(prop) {
this.listen(newVal, prop + '-changed', '_onDataChanged');
});
// Forward API object to children
this.$.child1.api = newVal;
this.$.child2.api = newVal;
...
},
_onDataChanged: function() {
this.users = this.api.users; // `this` should be the element now
this.products = this.api.products;
}
});
Given how this is a common pattern you're doing, you could probably get a lot of benefit out of extracting some of these things into a Behavior that abstracts away the binding/unbinding and API element forwarding.
Another optimization you may could make work would be to to look at the event passed to _onDataChanged to see if you can infer which value changed and update your corresponding property. This could prevent you needing to add a line for every property.

I ended up using an other solution. Instead of manually passing the top <my-api> element down the hierarchy any element that needs access to this shared data declares its own <my-api>.
Then in the <my-api> element's declaration I made that all instances use the same arrays references. So whenever I update one they all get updated, and I don't have to pass anything down the HTML hierarchy, which makes things a LOT simpler.

Related

Access data from template to Vue Instance

I'm very new to vue, so this is properly a basic question. I have a value in my template from an parsed object prop like this:
<h1>{{myval.theme}}</h1>
This gives and display the value in the browser. However, I need to obtain the value and store it in the data section of the instance. How can I store the data in the "getTheValue" data string? This is what I currenct have, and this does not work:
props: {
myval: Object
},
data() {
return {
getTheValue: this.myval.theme
};
},
The best way its to use computed here
computed: {
getTheValue() {
return this.myval.theme
}
}
and call it same way:
this.getTheValue
its best solutions - cached, non-reactive
also if you still want to use data you can assign in lifetime-hooks like:
mounted() {
this.getTheValue = this.myval.theme
}
But its crude solution, and I highly recommend to use computed variant

How to reference data around function in object without traversing entire object?

Having an object similar to:
const data = {
tasks: {
projects: [
name:'Project Name',
filters: [
{
name:'First Project Filter',
checked:false,
onChange:(event) => {
console.log(this.checked)
}
},
...
],
...
],
...
},
...
}
The problem at hand is how to reference the checked property without drilling through the entire object.
In the case above, it throws an error because this is undefined, so referencing this.checked is invalid.
I could extract from the event data properties so that I can get the
whole reference such as tasks.projects[0].filters[0].checked, but I
am wondering if an easier method is available.
The ideal solution would be a way to reference the surrounding properties of the function without traversing the entire object. Surely the function has a way to know that it is inside of an object so maybe something like parent().checked ?
If relative: I am using node.js and react to use this object to render a filtered sidebar that works with context to filter the data-set. I don't think that is relative as this seems like a pure JavaScript OOP situation.

Deep watch in not working on object Vue

I have a watcher setup on an array and I have deep watch enabled on it, however the handler function does not trigger when the array changes, applications is defined in the object returned in data. Here's the code:
watch: {
applications: {
handler: function(val, oldVal) {
console.log('app changed');
},
deep: true,
},
page(newPage) {
console.log('Newpage', newPage);
},
},
Vue cannot detect some changes to an array such as when you directly set an item within the index:
e.g. arr[indexOfItem] = newValue
Here are some alternative ways to detect changes in an array:
Vue.set(arr, indexOfItem, newValue)
or
arr.splice(indexOfItem, 1, newValue)
You can find better understanding of Array Change Detection here
If you reset your array with arr[ index ] = 'some value', Vue doesn't track to this variable. It would better to use Vue array’s mutation method. These methods used to track array change detection by Vue.
It is worked for me.

Adding an item to array does not re-render polymer template

I am trying to render a dynamic list of items using a template of dom-repeat like this:
<template is="dom-repeat" items={{numbers}} as="anumber" >
<div>
{{anumber}}
<paper-button class="deleteThisNumber" index={{index}}></paper-button>
</div>
</template>
<paper-button id="addNumber"></paper-button>
Each item has a button which will delete this item.
There is also a button outside of the dom-repeat template that tries to add an entry to array numbers. The JS looks like this:
Polymer ({
is: "something",
properties: {
numbers: {
type: Array,
value: ["1"]
}
},
removeByIndex: function (array, index) {
return array.filter(function (elem, _index) {
return index != _index;
});
},
attached: function () {
var myself = this;
$(this).on('click', '.deleteThisNumber', {}, function (e) {
myself.numbers = myself.removeByIndex(myself.numbers, this.index)
});
this.$.addNumber.addEventListener("click", function (e) {
myself.numbers.push("123");
})
},
...
});
The result is: deleting works, but adding does not.
By saying "works", I mean the list reflects the change by adding/removing an entry in the DOM. I checked the property numbers it is correctly modified all the time. So why does Polymer not reflect changes of an array property to a template if the change is addition(array.push)? How should I fix this? (I am open to any suggestions other than manually adding divs.)
My Polymer version is 1.X
Change the code for array push to :
this.$.addNumber.addEventListener("click", function(e) {
myself.push("numbers", "123");
})
There has to be an observable change in order to render the updated property or subproperty. An observable change is a data change that Polymer can associate with a path.
If you manipulate an array using the native methods (like Array.prototype.push), you must notify Polymer after the fact. OR, use the Polymer methods for array mutations.
When modifying arrays, a set of array mutation methods are provided on
Polymer element prototypes which mimic Array.prototype methods, with
the exception that they take a path string as the first argument. The
path argument identifies an array on the element to mutate, with the
following arguments matching those of the native Array methods.
These methods perform the mutation action on the array, and then
notify other elements that may be bound to the same array of the
changes. You must use these methods when mutating an array to ensure
that any elements watching the array (via observers, computed
properties, or data bindings) are kept in sync.
Every Polymer element has the following array mutation methods
available:
push(path, item1, [..., itemN])
pop(path)
unshift(path, item1, [...,
itemN])
shift(path)
splice(path, index, removeCount, [item1, ..., itemN])
Learn More
I found out the solution is to force a notifyPath with a little more:
myself.numbers.push("123"); //before only has this
myself.notifyPath('numbers', myself.numbers.slice()); //added
Referred to https://github.com/Polymer/polymer/issues/2068#issuecomment-120767748
Answer from #miyconst
More than one way you can fix your code.
1) The way you've already been using when you delete an item.
this.$.addNumber.addEventListener("click", function (e) {
myself.numbers.push("123");
myself.numbers = myself.numbers.slice();
})
2) Answer from yourself (with slight changes)
this.$.addNumber.addEventListener("click", function (e) {
myself.numbers.push("123");
myself.notifyPath("numbers");
})
3) Answer from #Ofisora
this.$.addNumber.addEventListener("click", function(e) {
myself.push("numbers", "123");
})
Here's why the fixes work, https://www.polymer-project.org/1.0/docs/devguide/data-system#observable-changes

Updating DOM according to Firebase Object

I want to update my DOM element everytime the value on the Firebase changes. I've seen that Angularfire handles three-way data binding, but from what I understood it only works if you take elements from $firebaseArray directly from the DOM.
What I have is an Element on the DOM (chart) that depends on some of the data on a $firebaseArray, but my element gets the data from a function instead of directly from the $firebaseArray. That means I have to do some pre-processing on the $firebaseArray before my element can use it.
This is what I have:
<pie-chart ng-repeat="chart in myCtrl.charts"
data="chart.data"
options="chart.options"></pie-chart>
This is my controller:
function MyCtrl($firebaseArray) {
let myRef = new Firebase(refUrl);
let chartsFirebase = $firebaseArray(myRef);
let getCharts = function() {
let charts = [];
distanceGoals.$loaded().then(function() {
// push some things from chartsFirebase on the charts array
charts.push({
options: { ... },
data: [ ... ]
});
}
return charts;
}
this.charts = getCharts();
}
Turns out that in this way this.charts is only updated one time, after modifications on the data in Firebase I have to refresh the browser.
Has anyone an idea of what I could do to achieve this behavior?
You can add a child-changed event listener to your ref like this:
// Get a reference to our posts
var ref = new Firebase("https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts");
// Get the data on a post that has changed
ref.on("child_changed", function(snapshot) {
var changedPost = snapshot.val();
console.log("The updated post title is " + changedPost.title);
});
This will get called everytime something changes in the location you put the listener on.
For more info take a look at https://www.firebase.com/docs/web/guide/retrieving-data.html and https://www.firebase.com/docs/web/api/query/on.html.
From the AngularFire documentation on $loaded()(emphasis mine):
Returns a promise which is resolved when the initial array data has been downloaded from the database.
That explains the behavior you're seeing.
To solve this, you should extend the $firebaseArray as documented here: https://www.firebase.com/docs/web/libraries/angular/guide/extending-services.html#section-firebasearray
Some related questions:
AngularFire extending the service issue
Joining data between paths based on id using AngularFire (includes a full example by the author of AngularFire)

Categories

Resources