Cross-DOM observers in Polymer - javascript

I have developed a component for i18n in Polymer.
Based on <iron-localstorage> it stores and changes the locale.
<iron-localstorage name="marvin-locale-ls"
value="{{locale}}"
on-iron-localstorage-load-empty="initializeDefaultLocale"
></iron-localstorage>
<script>
MarvinLocaleLS = Polymer({
is: 'marvin-locale-ls',
properties: {
locale: {type: String},
...
Also I have a translator component that makes a translation based on this locale.
I want to make something like this:
<script>
Polymer({
is: 'marvin-translate',
ls: new MarvinLocaleLS(),
properties: {
key: {
type: String,
notify: true
},
locale: {
type: Polymer.dom().querySelector('marvin-locale-ls').properties.locale,
observer: '_localeObserver'
}
},
ready: function(){
this.key = this.textContent;
var t = this.ls.getTranslation(this.key); // get translation from Local Storage
this.textContent = (t) ? t : this.key; // show translation or key if there is no translation
},
_localeObserver: function(){
console.log('locale changed')
}
});
</script>
In other words I want to create the observer in 'marvin-translate' for a property in 'marvin-locale-ls'. Is it possible?

Take a look at this component, it will enable you to share variable on a Key/Value basis and access them wherever you want. It also supports data-binding (which iron-meta does not yet): https://github.com/akc42/akc-meta

Related

How to pass data into Vue instance so that subsequent updates are reflected?

I'm trying to dynamically update a Vue JS prop after the view has been loaded and the custom has been initialised. I'm building a custom Vue plugin and am using props to pass options, one of which is a object which I need to dynamically update the value passed after the component has loaded, e.g:
<div id="app">
<script>
var seedData = {
percent: 50,
name: 'Smith'
}
setInterval(() => {
seedData = {
percent: Math.random(),
name: 'Smith'
}
}, 1000)
</script>
<offers :parent-data="seedData"></offers>
</div>
Vue.component('offers', {
template: '<h1>Parent Data: {{ parentData.percent }}</h1>',
props: {
parentData: {
default: () => ({
percent: 0,
name: 'John'
}),
type: Object
},
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
This will load the initial name/values from offersData, however, the new values on the setInterval doesn't get passed through.
I've tried adding a watcher inside of my custom Vue plugin that gets loaded through <offers> but this doesn't seem to work either:
watch: {
parentData: function (newVal) {
this.parentData = newVal
}
}
UPDATE
The following is my implementation:
Code Pen -> https://codepen.io/sts-ryan-holton/pen/VwYNzdZ
There are multiple problems with your code
Everything inside <div id="app"> is treated by the Vue as a template and compiled - see the docs
If neither render function nor template option is present, the in-DOM HTML of the mounting DOM element will be extracted as the template. In this case, Runtime + Compiler build of Vue should be used.
Including <script> tag there is wrong. Just try to include vue.js (debug build) instead of vue.min.js (minified production build of Vue) and you will see bunch of errors (btw its always good idea to use debug build for development as it gives you lots of useful errors and warnings)
The fact that it "somehow works" in prod build (ie the initial values are shown on the page) doesn't mean it's supported...
So the inside of <div id="app"> is template for Vue. As I said before in the comments, all data referenced by template must be in the context of Vue instance. You cannot pass some global variable into props. So moving <script> outside of <div id="app"> won't help
[Vue warn]: Property or method "seedData" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initialising the property.
What you can do is to pass seedData object into root Vue instance like this:
var vm = new Vue({
el: '#app',
data: {
seedData: seedData
}
});
Now the errors are gone but data changes is not reflected still. Reason for that is not Vue specific. Its simple JavaScript. Object are passed by reference in JS. Look at this code:
var a = { name: 'John' }
var b = a
a = { name: 'Smith' }
// b is still pointing to "John" object
To workaround it, don't replace whole object. Just mutate it's properties (beware of Vue reactivity caveats)
setInterval(function() {
seedData.name = 'John';
seedData.percent = Math.random();
}, 1000)
Whole solution:
Vue.component('offers', {
template: '<h1>{{ parentData.name }}: {{ parentData.percent }}</h1>',
props: {
parentData: {
default: () => ({
percent: 0,
name: 'John'
}),
type: Object
},
}
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data: {
seedData: seedData
}
});
<script>
var seedData = {
percent: 60,
name: 'Smith'
}
setInterval(function() {
seedData.name = 'John';
seedData.percent = Math.random();
}, 1000)
</script>
<div id="app">
<offers :parent-data="seedData"></offers>
</div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>

Vue not updating input that depends on another

I'm building something like this, where the country I pick will dictate the phone country code automatically.
Both the country and countryCode are stored in a customer object, and when I'm changing the country's value, the trigger is correctly called and I can see the country code changing in Vue dev tools, however the related input does not update. This is my code:
data: function () {
return {
customer: {},
countries: this.$store.state.settings.countries,
}
},
created: function() {
var defaultCountry = _.find(this.countries, { default: true });
this.customer.country = defaultCountry.name;
this.customer.countryCode = defaultCountry.code;
},
methods: {
updateCountryCode: function(country) {
this.customer.countryCode = country.code;
},
}
And this is the relevant HTML:
<vSelect
label="name"
v-model="customer.country"
:options="countries"
:onChange="updateCountryCode">
</vSelect>
<input type="text" disabled :value="customer.countryCode">
What am I doing wrong? Why do I see the data being updated on dev tools but it doesn't act as reactive and my country code input stays the same?
You should define your customer object like so customer:
{
country: null, // or some other default value
countryCode: null,
},
You can find more details in the documentation here
This is how you shoul do it or #Nora's method of initializing the object properties to null
updateCountryCode: function(country) {
this.$set(this.customer, 'countryCode', country.code)
},
And the reason is because of change detection caveats in vue reactivity

Data Binding between Polymer Templatizer instance and host

I am trying to use the Polymer templatizer to create a single instance of a template, append it into a div and get data binding to work between the host and this instance but am having difficulty getting this to work.
The most simple example I have tried:
HTML
<dom-module id="test-app">
<paper-input label="host" value="{{test}}"></paper-input>
<template id="template">
<paper-input label="instance" value="{{test}}"></paper-input>
</template>
<div id="placehere"></div>
</dom-module>
JS
Polymer({
is: "test-app",
behaviors: [Polymer.Templatizer],
properties: {
test: {
type: String,
value: 'hello',
notify: true,
},
},
ready: function() {
this.templatize(this.$.template);
var clone = this.stamp({test: this.test});
Polymer.dom(this.$.placehere).appendChild(clone.root);
},
});
The idea above is to create the instance of the template, place it into "placehere" and have the two input text boxes keep in sync.
When the page loads, the instance is created successfully and the value in both textboxes is "hello" but changing either input box does nothing.
The documentation on the polymer page seems a bit lightweight:
https://www.polymer-project.org/1.0/docs/api/Polymer.Templatizer
but it mentions the use of _forwardParentProp and _forwardParentPath. How exactly am I supposed to implement them in my situation?
As you have already figured out, you need to implement some of the Templatizer's methods. Particularly the _forwardParentProp and _forwardParentPath methods.
But before I begin, I must also point out one additional error in your custom elements definition. In your dom-module element, you have the element's contents defined without the template. It is essential to wrap everything within a template element. The fixed version of your custom element would be like this:
<dom-module id="test-app">
<template>
<paper-input label="host" value="{{test}}"></paper-input>
<template id="template">
<paper-input label="instance" value="{{test}}"></paper-input>
</template>
<div id="placehere"></div>
</template>
</dom-module>
As for the implementation of the Templatizer methods, you first need to store the stamped instance. After that, both methods that need implementations are more or less simple one-liners.
Here is the full JavaScript part of the custom element:
Polymer({
is: "test-app",
behaviors: [Polymer.Templatizer],
properties: {
test: {
type: String,
value: 'hello',
notify: true,
},
},
ready: function() {
this.templatize(this.$.template);
var clone = this.stamp({test: this.test});
this.stamped = clone.root.querySelector('*'); // This line is new
Polymer.dom(this.$.placehere).appendChild(clone.root);
},
// This method is new
_forwardParentProp: function(prop, value) {
if (this.stamped) {
this.stamped._templateInstance[prop] = value;
}
},
// This method is new
_forwardParentPath: function(path, value) {
if (this.stamped) {
this.stamped._templateInstance.notifyPath(path, value, true);
}
},
});
Here is a working JSBin demo: http://jsbin.com/saketemehi/1/edit?html,js,output

Is there a nice way to make a Kendo custom widget, using Typescript classes?

The documentation for creating custom Kendo widgets is good enough, and leads to something like:
declare var kendo: kendo;
// To be able to get types, we can express the widget as this interface
interface ICustomDatePicker {
options: () => kendo.ui.DatePickerOptions;
}
; (function ($:JQueryStatic, window:any, document:Document, undefined?) {
var CustomDatePicker: ICustomDatePicker = (<any>kendo.ui.DatePicker).extend({
init: function (element, options:kendo.ui.DatePickerOptions) {
var self = this;
// base call to initialize widget
(<any>kendo.ui.DatePicker).fn.init.call(self, element, options);
},
options: {
// the name is what it will appear as off the kendo namespace (i.e. kendo.ui.CustomDatePicker).
// The jQuery plugin would be jQuery.fn.kendoCustomDatePicker.
name: "CustomDatePicker"
}
});
// This makes it work as a jQuery plugin
(<any>kendo.ui).plugin(CustomDatePicker);
})(jQuery, window, document);
A typescript file with that above, let's me do things like: $("#datePicker").kendoCustomDatePicker({}); and it all works beautifully.
My question is, is there a better way to write this in class form? My original thought is this:
module Foo {
class CustomDatePicker extends kendo.ui.DatePicker {
constructor(element, options) {
super(element, options);
}
}
(<any>kendo.ui).plugin(CustomDatePicker);
}
But that doesn't work (when calling the same $("#datePicker").kendoCustomDatePicker({});. This Gist gets closer, but I think the syntax is a bit funky - that the class doesn't extend the control directly. Any ideas?
Update 1
Looking at this answer, I'm trying to find a way to clean up setting the options by having it IN the class. I've gotten the following to semi-work, though it breaks down for some options and not others:
constructor(element: Element, options?: kendo.ui.TimePickerOptions) {
super(element, options);
$.extend(this.options, <kendo.ui.TimePickerOptions>{
format: "hh:mm tt",
parseFormats: ["HH:mm", "hh:mm tt"]
});
}
In this case, it respects that format and you can see it working. If you try and do the following:
$.extend(this.options, <kendo.ui.TimePickerOptions>{
format: "hh:mm tt",
parseFormats: ["HH:mm", "hh:mm tt"]
});
.. it doesn't work, doesn't parse the input at all.
There is a way but I am not 100% sure it qualifies as "nice". Here is some code which I wrote today and works:
class MyTreeView extends kendo.ui.TreeView
{
constructor(element: Element, options?: kendo.ui.TreeViewOptions) {
super(element, options);
}
static fn;
}
// kendo.ui.plugin relies on the fn.options.name to get the name of the widget
MyTreeView.fn = MyTreeView.prototype;
MyTreeView.fn.options.name = "MyTreeView";
// kendo.ui.plugin which comes with the Kendo TypeScript definitions doesn't include this overload
module kendo.ui {
export declare function plugin(widget: any, register?: Object, prefix?: String);
}
// register the plugin
kendo.ui.plugin(MyTreeView);
// extend the jQuery interface with the plugin method (needed to be used later)
interface JQuery {
kendoMyTreeView(options?: kendo.ui.TreeViewOptions): JQuery;
}
// use the plugin
$(function () {
$("#content").kendoMyTreeView({
dataSource: [
{
text: "Root",
items: [
{ text: "Child" }
]
}
]
});
});

Accessing Ext Designer JsonStore from another file

I want to access an JsonStore created from the ext designer from another panels user js file.
The file store file generated from the designer looks like this
myJsonStore = Ext.extend(Ext.data.JsonStore, {
constructor: function(cfg) {
cfg = cfg || {};
CoaJsonStore.superclass.constructor.call(this, Ext.apply({
storeId: 'myJsonStore',
url: '/server.json',
restful: true,
autoLoad: true,
autoSave: false,
fields: [
{
name: 'id'
},
{
name: 'code'
},
{
name: 'name'
}
]
}, cfg));
}
});
new myJsonStore();
what i am doing right now is using a hidden combo and assign the store to the combo, this allows me to access it via autoRef (with. combo.getStore(), it gives me an object type of Store). Ideally i want to be able to do it without the hidden combo.
i have tried referring to it with storeId, but it doesn't work, if i log the storeId to the console this is what i get.
function (cfg) {
cfg = cfg || {};
CoaJsonStore.superclass.constructor.call(this, Ext.apply({
storeId: 'myJsonStore',
url: '/coas.json',
restful: true,
........
so i was just wondering whether this is even possible. if so some direction on how to get it done would be greatly appreciated . thanks
The new myJsonStore(); only creates a new store. In order to reference the store elsewhere in your code ( same file or another file) you need to use a variable. Create the store like this:
var myStore = new myJsonStore();
And to bind it to the comobobox use the variable name myStore with the store property.

Categories

Resources