I want to add the <nuxeo-tree> component to my Polymer v1 app, but I'm seeing an error in the console. This is the code I've tried:
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/nuxeo-ui-elements/nuxeo-tree/nuxeo-tree.html">
<link rel="import" href="./myVerySpecialLib-import.html">
<dom-module id="my-app">
<template>
tree:<br/>
<nuxeo-tree data="[ title: 'root', children: [ { title: 'a', children: [] }, { title: 'b', children: [ {title: 'x'}, {title: 'y'} ] } ]]]" controller="[[controller]">
<template>
<template is="dom-if" if="[[!opened]]">
<iron-icon icon="hardware:keyboard-arrow-right" toggle></iron-icon>
</template>
<template is="dom-if" if="[[opened]]">
<iron-icon icon="hardware:keyboard-arrow-down" toggle></iron-icon>
</template>
<span select>My title is: [[item.title]]</span>
<span>Am I a leaf? [[isLeaf]]</span>
</template>
</nuxeo-tree>
</template>
<script>
Polymer({
is: 'my-app',
properties: {
data: {
type: String,
value: "[ title: 'root', children: [{ title: 'a',children: []},{title: 'b',children: [{title: 'x'},{title: 'y'}]}]]",
},
opened: {
type: Boolean,
value: true,
},
},
controller: {
// How to get children of a node. Returns a promise.
getChildren: function(node) {
return Promise.resolve(node.children);
},
// Logics you may want to have to control if a node is a leaf.
isLeaf: function(node) {
return node.children.length === 0;
}
},
});
</script>
</dom-module>
And the myVerySpecialLib-import.html file:
controller = {
// How to get children of a node. Returns a promise.
getChildren: function(node) {
return Promise.resolve(node.children);
},
// Logics you may want to have to control if a node is a leaf.
isLeaf: function(node) {
return node.children.length === 0;
}
};
This is the console error:
TypeError: this.controller.isLeaf is not a function
I tried to add the JSON data as a property and also directly into the data field, but neither had a positive effect. How do I fix this?
The myVerySpecialLib-import.html seems to contain a global variable declaration, but that doesn't really help you because <nuxeo-tree> expects controller on the container element (not in a global variable).
Also, your data binding for <nuxeo-tree>.controller is malformed (it's missing a ] at the end):
<nuxeo-tree controller="[[controller]">
And controller probably should be declared as a property if you're binding it. It's currently declared outside the properties object.
// DON'T DO THIS
/*
properties: {...},
controller: {...}
*/
// DO THIS
properties: {
controller: {...}
}
I recommend setting this.controller in the ready() callback of the parent element of <nuxeo-tree> (where this is the container). You could also set <nuxeo-tree>.data via a binding to simplify your HTML template, and that property could be initialized in ready() as well.
ready: function() {
this.data = /* insert data object here */;
this.controller = /* insert controller object here */;
}
demo
Related
After some research the following suggestion by Mr. Evan You was found:
https://github.com/vuejs/vue/issues/7349#issuecomment-354937350
So without any hesitation I gave it a try:
Component template
<template>
<div v-on='{ click: dataType === `section` ? toggleSectionElements : null }'>
... magic
</div>
<template>
JS Logic
<script>
export default {
name: `product-section`,
props: [`section`, `sectionName`, `depth`],
methods: {
toggleSectionElements() {
... magic
}
},
computed: {
dataType() {
if (this.$props.section.sections || this.$props.depth === 0) {
return `section`
} else {
return `element`
}
}
}
}
</script>
But for described case it results in error during rendering:
[Vue warn]: Invalid handler for event "click": got null
Can someone please suggest what has been done wrong? :thinking:
Update
The way Data Model looks like:
DataModel: {
mainSectionA: {
sections: {
sectionA: {
sections: {
elementA: { values: { ... } },
elementB: { values: { ... } }
}
values: { ... }
}
sectionB: {
elementA: { values: { ... } },
elementB: { values: { ... } }
}
},
values: { ... }
},
mainSectionB: {
sections: {
elementA: { values: { ... } },
elementB: { values: { ... } },
elementC: { values: { ... } },
... elements
},
values: { ... }
}
}
Just change it to the below and it will work
v-on="condition ? { mouseover: handler } : {}"
or, if your handler is called mouseover
v-on="condition ? { mouseover } : {}"
Instead of polluting your template with ternary logic, you should actually perform the check inside the click handler instead. It not only makes your template more readable, but also makes maintaining the code easier since all logic has been abstracted and delegated to the event handler's callback instead.
Quick solution
Therefore the quick solution is to actually ensure that the toggleSectionElements() will only work when a correct dataType is present. This can be achieved by using a guard clause:
toggleSectionElements() {
// Guard clause to prevent further code execution
if (this.dataType() !== 'section')
return;
// Magic here
}
Even better, is that if separate handlers should be assigned to each dataType: you can then create a factory function for that purpose:
methods: {
// This is just a factory function
toggleElements() {
switch (this.dataType()) {
case 'section':
return this.toggleSectionElements;
case 'element':
// Something else...
}
},
toggleSectionElements() {
// Magic for section element
}
}
Suggestion: using atomic components
Since it might be costly to bind click event handlers to elements that end up doing nothing, you can also break down your component to be more atomic. The collection element will be responsible of receiving an array of "section" or "element", and each "section"/"element" will have its own component, something like this:
You have a collection component, say <my-collection>, that holds all "section" and "element" components
"section" component will use the <my-section> component
"element" component will use the <my-element> component
This is when VueJS becomes really powerful: you can use dynamic component inside <my-collection> to determine which component to use depending on the dataType encountered.
This is done by running a v-for through the collection, and then using v-bind:is="..." to determine whether a specific collection item should be using "section" or "element". I understand that this is probably going to go out of scope of your original question, but it's a worthwhile design to consider:
const collectionComponent = Vue.component('my-collection', {
template: '#my-collection-component',
data: function() {
return {
collection: [{
dataType: 'section',
description: 'Hello I am section 1'
}, {
dataType: 'element',
description: 'Hello I am element 1'
}, {
dataType: 'section',
description: 'Hello I am section 2'
}, {
dataType: 'element',
description: 'Hello I am element 2'
}]
}
},
methods: {
componentToUse(dataType) {
return 'my-' + dataType;
}
}
});
const sectionComponent = Vue.component('my-section', {
template: '#my-section-component',
props: ['itemData'],
methods: {
toggle() {
console.log('Doing some magic.');
}
}
});
const elementComponent = Vue.component('my-element', {
template: '#my-element-component',
props: ['itemData']
});
new Vue({
el: '#app'
});
.box {
border: 1px solid #999;
cursor: pointer;
margin: 10px;
padding: 10px;
}
.box:hover {
background-color: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-collection />
</div>
<script type="text/x-template" id="my-collection-component">
<div>
<component
v-for="(item, i) in collection"
v-bind:key="i"
v-bind:is="componentToUse(item.dataType)"
v-bind:itemData="item" />
</div>
</script>
<script type="text/x-template" id="my-section-component">
<div #click="toggle" class="box">
<h1>{{ itemData.dataType }}</h1>
<p>{{ itemData.description }}</p>
<p>Clicking on me will invoke a section-specific logic</p>
</div>
</script>
<script type="text/x-template" id="my-element-component">
<div class="box">
<h1>{{ itemData.dataType }}</h1>
<p>{{ itemData.description }}</p>
<p>Clicking on me will do nothing</p>
</div>
</script>
here:
click: dataType === `section` ? toggleSectionElements : null
in the not-equal case you pass null, but the value on click expects a function. you can try an emptry function:
click: dataType === `section` ? toggleSectionElements : ()=>{}
In Vue 3 you can pass null to the listener. Combining it with optional chaining you can do this:
#click="handler?.() || null"
Same for old browsers:
#click="handler ? handler() : null"
I'm using a dom-repeat template in my element, and I want to use the property typeElement of that element (<custom-element>) in a new element (<media-element>) from within dom-repeat:
<dom-module id="custom-element">
<template>
<template is="dom-repeat" items="{{array}}" as="file">
<media-element some-prop="{{typeElement}}" file="{{file}}"></media-element>
</template>
</template>
<script>
Polymer({
is: 'custom-element',
properties: {
typeElement: Number,
array: {
type: Array,
value: function() { return[]; }
}
}
});
</script>
</dom-module>
How can I do that?
I'm not sure what you mean by "use the property", so I'm making assumptions below.
Assuming you want to set <media-element>.someProp to the value of <custom-element>.typeElement, then your data binding is correct. Whenever the value of typeElement changes, someProp will be set to the same value. You could access the value in a method of <media-element> with this.someProp. Example:
<dom-module id="media-element">
<template>...</template>
<script>
Polymer({
is: 'media-element',
properties: {
someProp: Number
},
foo: function() {
console.log(this.someProp);
}
});
</script>
</dom-module>
Assuming you also want the changes to <media-element>.someProp to update <custom-element>.typeElement, then you'd need to set notify: true on the property declaration of someProp:
// media-element
properties: {
someProp: {
type: Number,
notify: true // <-- only needed for upward notifications (two-way data binding and observers)
}
}
HTMLImports.whenReady(() => {
Polymer({
is: 'custom-element',
properties: {
typeElement: {
type: Number,
value: 100,
observer: '_typeElementChanged'
}
},
_typeElementChanged: function(typeElement) {
console.log('new typeElement', typeElement);
}
});
Polymer({
is: 'media-element',
properties: {
someProp: {
type: Number,
notify: true
}
},
_logSomeProp: function() {
console.log('someProp', this.someProp);
},
_incrementSomeProp: function() {
this.someProp++;
}
});
});
<head>
<base href="https://polygit.org/polymer+1.8.1/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="polymer/polymer.html">
</head>
<body>
<custom-element></custom-element>
<dom-module id="custom-element">
<template>
<media-element some-prop="{{typeElement}}"></media-element>
</template>
</dom-module>
<dom-module id="media-element">
<template>
<button on-tap="_logSomeProp">Log someProp</button>
<button on-tap="_incrementSomeProp">Incremeent someProp</button>
</template>
</dom-module>
</body>
codepen
I recommend reading Polymer Data Binding.
because settings property like:
typeElement: Number won't work. You have to define it like you defined array property. so:
typeElement: {
type: Number,
value: Number,
}
i don't know what value you want to have. After this it should work
Ok i figured myself, the problem isn't here. The problem was on the child element (<media-element>). So i'll post a new question te resolve the problem:
how do the properties of an element been initialized before the element creation? Or, how to trigger the dom-if recalling my condition function after propertie change/init?
My code:
<dom-module id="media-element">
<template>
<template is="dom-if" if="[[isType(0)]]">
....
</template>
</template>
<script>
Polymer({
is: 'media-element',
properties: {
myType: {
type: Number,
value: 0
}
},
isType: function(t){return (this.myType===t);}
});
</script>
</dom-module>
UPDATE: I sent my question and receive a goode answer: here
I have a host element binding array of object to child element which has a paper-input to edit its properties. I don't see the value change on input reflected in the host div element. Even though on debug I can see that the host object has the latest edited name. What should I do to get this automatically wired ?
<!-- Host element -->
<dom-module id="host-item">
<template>
<div>
<div>[[selectedEmployee.name]]</div>
<template is="dom-repeat" items="[[employees]]" as="employee">
<item-edit item="[[employee]]"></item-edit>
</template>
</div>
</template>
<script>
Polymer({
is: 'host-item',
properties: {
selectedEmployee: {
type: Object
},
employees: {
type: Array,
value = [ { name: 'Name 1'}, { name: 'Name 2'}, { name: 'Name 2'}]
}
},
ready: function() {
this.selectedEmployee = this.employees[0];
}
});
</script>
</dom-module>
<!-- Child element -->
<dom-module id="item-edit">
<template>
<paper-input id="input" value="{{item.name}}" error-message="Invalid name!"></paper-input>
</template>
<script>
Polymer({
is: 'item-edit',
properties: {
item: {
type: Object
}
}
});
</script>
</dom-module>
Use {{employee}} for 2 way binding. [[...]] is for one way only.
Use notify: true on property definition.
Child element should be defined before the parent.
Here is the working example Plunk, and similar Plunk
<item-edit item="{{employee}}"></item-edit>
...
employee: {
type: Object,
notify: true,
value: function () { return {name: 'Test' }; }
}
Update:
Now "employees" data is in form of an array of objects.
Check out this question for working with arrays:
Polymer, issue with binding array to paper slider value
Plunk
Docs: Binding to array items
I found a cool project (RoboJS), and I forked it: Forked Repo. My plan was to try to add a nice front end with Polymer 1.0 and learn a little in the process.
What I am having trouble with is getting the binding to show in my component. I've built a really simple "robot" component to show the status of the robot during the game.
To start, all I want to do is to show the name in the title, but it comes out blank. Here's the component:
<dom-module id="robojs-robot-status">
<template>
<div>Robot Name <span>[[robot]]</span><span>{{test}}</span></div>
</template>
</dom-module>
<script>
Polymer({
is: "robojs-robot-status",
properties: {
robot: {
type: String,
value: "testing"
},
test: {
type: String,
value: "testing2"
}
},
ready: function() {
},
init: function() {
console.log(this.robot);
console.log(this.test);
}
});
</script>
On the parent component, I set the robot attribute:
Here's the attribute:
<link rel="import" href="robojs-robot-status.html">
<robojs-robot-status robot="{{robot}}"></robojs-robot-status>
And, I have a script that, for now, sets the value on the ready event:
Polymer({
is: "robojs-arena",
properties: {
robot: {
type: String,
value: "hello"
}
},
ready: function() {
this.games = window.roboJS.games;
console.log(this.games);
//this.robot = {name: "hello"};
this.robot = "hello";
},
init: function() {
console.log("******* init *******");
console.log(this.robot);
document.querySelector("robojs-robot-status").init();
},
pause: function() {
window.roboJS.pause();
},
start: function() {
console.log(window.roboJS);
window.roboJS.resume();
}
});
[[robot]] is blank. {{test}} binds to "testing2".
Using {{robot}} or [[robot]] doesn't make a difference. So, that doesn't have an impact.
If I remove, the "robot" attribute in the parent component, the value works. It shows "testing". So, it is binding, but not with the actual value.
Beyond figuring out what I am doing wrong in this instance, is there a good way to troubleshoot? I am having similar issues in other places in the app.
If this were Angular + jQuery, I would do something like this:
$('robotjs-robot-status').scope().$eval("robot")
I could type that into the developer console in Chrome and see what it said and troubleshoot. I could also use the Batarang extension in Chrome.
With Polymer, I am not sure where to start. Any help/ideas?
If the parent snippet is posted here exactly as it appears in the code, then it's probably to blame. The
<link rel="import" href="robojs-robot-status.html">
should be outside , like
<dom-module id="robojs-robot">
<link rel="import" href="robojs-robot-status.html">
<template>
<robojs-robot-status robotname="{{robotname}}"></robojs-robot-status>
</template>
<script>
Polymer({
is: "robojs-robot",
ready: function() {
console.log('setting to Dilly');
this.robotname = "Dilly";
},
properties: {
robotname: {
type: String,
value: "hello"
}
},
});
</script>
</dom-module>
and then if status is
<dom-module id="robojs-robot-status">
<template>
<div>Robot Name <span>[[robotname]]</span></div>
</template>
<script>
Polymer({
is: "robojs-robot-status",
properties: {
robotname: {
type: String,
value: "testing",
observer: '_robotnameChanged'
}
},
_robotnameChanged: function(newValue, oldValue) {
console.log('_robotnameChanged: newValue='+newValue+' oldValue='+oldValue)
}
});
</script>
</dom-module>
everything works for me.
PS: properties seem to be not really needed here as binding is unidirectional.
I'm trying to figure out how to use the 2-way notifications in polymer described here
But while this explains that there is some mechanism for notifying if an object on a child element has changed:
When a sub-property of a property configured with type: Object
changes, an element fires a non-bubbling -changed DOM event
with a detail.path value indicating the path on the object that
changed.
But it doesn't give any clue (that I can sort out) how the syntax would look to script some behaviour based on this change.
For example if I have a parent:
<dom-module id="parent-element">
<template>
<child-element obj="{{myObj}}"></child-element>
</template>
<script>
Polymer({
is: "parent-element",
myObj: {
type: Object
},
parentClick: function(){
this.myName = "Parent";
},
myObjChanged: function(){ //How to handle this event?
console.log("I have no idea what I'm doing")
}
});
</script>
and I have a child element:
<dom-module id="child-element">
<template>
<span on-click="childClick">Click me</span>
</template>
<script>
Polymer({
is: 'child-element',
properties: {
obj: {
type: Object,
notify: true
}
},
ready: function(){
this.obj = {foo: "bar"}
},
childClick: function(){
this.obj.foo = "baz"
}
});
</script>
When the child is clicked, I expect some event to fire and get picked up by the parent, but I have no clue how to script for that event at the parent. What am I missing?
I recently had this problem and did this by firing my own event. You can see the documentation for that here (please note that this documentation is for 0.5 but I have done this with version 1.0). In your child element you could change your childClick function to fire the custom event as follows:
childClick: function(){
this.obj.foo = "baz"
this.fire("child-element-click"); // this can be anything you want...
}
and then in your parent element you want to add a listener to the child-element:
<dom-module id="parent-element">
<template>
<child-element obj="{{myObj}}" on-child-element-click="myObjChanged"></child-element>
</template>
<script>
Polymer({
is: "parent-element",
myObj: {
type: Object
},
parentClick: function () {
this.myName = "Parent";
},
myObjChanged: function () {
console.log("I have no idea what I'm doing")
}
});
</script>