I'm kind of new to Polymer and i'm having an issue lately. I create dynamically a certain amount of instances of a web-component of mine, and I'd like to be able to call a method on these instances from my parent-component but I can't figure out how to do it even with answers I found online.
Here's my parent method where I try to call the children method (the e.detail.id match the id of the specific instance of my children I'm trying to reach) :
childObj: function(e) {
var name = "selectObj"+e.detail.id;
this.$.name.hello();
},
And my child basic method :
hello: function() {
console.log("hello");
}
The ID that name gets exists well but still i get this error
TypeError: Polymer.dom(...).querySelector(...) is null
I also tried replacing this.$.name.hello() by this.$$('#selectObj'+e.detail.id) but still I get the same error.
Here's how I create my childrens elements :
newObj: function() {
var dynamicSelect = document.createElement("pbd-object-select");
dynamicSelect.num = this.nbObj;
var newId = "selectObj" + this.nbObj;
dynamicSelect.id = newId;
Polymer.dom(this.root).querySelector("#listeObjet").appendChild(dynamicSelect);
},
There are two issues with they way you are trying to query for that element. One of them is that by doing this:
this.$.name.hello();
You are basically looking for an element with the id "name", not one with the id equal to what you have in the variable name. Something like:
this.$[name].hello();
might work better in general, but it would still have some problems in your particular case. this.$ is just a "shortcut" to get elements by ID in an easy manner, but, it's just an object in which references to the elements that exist and have ids when the element is connected. Because of that it doesn't work with elements that are conditionally included (in dom-ifs for example), or that are dynamically generated, like in your case.
You can just use the getElementById method, like you would do in vanilla JS, just keeping in mind that you are querying in your element and not in document. So it would be something like:
this.shadowRoot.getElementById("selectObj"+e.detail.id).hello()
Related
In an angular project, I need to test that the displayed table width of the primeng data table is set to the maxWidth value i assign to it. To do so, i want to call the [style] attribute to get the width and see if its equal to my maxWidth. However, i do not know how to call attributes like this. How do i go about this? Currently i have no clue if I'm going in the correct direction.
I have tried several things but I am not sure of the syntax for it.
<p-table class="p-table" ... [style] = "{width: maxWidth}" >
it('should implement maxwidth', () => {
const widthDebug: DebugElement = fixture.debugElement;
const tableWidth = widthDebug.query(By.css('.ui-table .ui-widget'));
const ptable: HTMLElement = tableWidth.nativeElement;
expect(ptable.textContent).toContain("width: " + component.maxWidth);
});
expected: success (ptable.textContent contains "width: component.maxWidth")
actual: TypeError: cannot read property 'nativeElement' of null
I see that it's now two months after you asked your question, so it's probably too late for my answer to help, but I stumbled across this post while looking up something else about PrimeNG, so I might as well give it a shot.
The problem here is that nativeElement is defined on Dialog class instances of the Angular p-table component. It's not defined on any particular DOM element.
By.css('.ui-table .ui-widget') is going to find a DOM element for you, not an Angular class instance. In particular what will be found is a <div> inside the <p-dialog> DOM element, and it's this <div> that receives the style set via [style]=....
As your code is written above tableWidth.style.width would contain (as a string) the value of maxWidth that you're expecting to find.
In Polymer 1.x I was used to write a templatize code like this:
renderTemplate(query, properties) {
let element = this.shadowRoot.querySelector(query);
this.templatize(element);
var instance = this.stamp(properties);
return instance;
}
which worked well. But in Polymer 2.x there is a new error message A <template> can only be templatized once. Well it doesn't make sense, because I have 1 template which I want to redistribute multiple times with different properties.
I am giving here an example of how is my code
I have #template1 and #template2
I want to render #template1 then #template2 then #template1.
In steps how I render templates:
1) templatize #template1
2) stamp properties
3) templatize #template2
4) stamp properties
5 a) templatize #template1 => ERROR
5 b) skip templatize and stamp properties => #template2 is rendered....
How am i able to make this possible? calling stamp() after rendering #template2 will result in another #template2 render. I want #template1, but I can't templatize #template1 because it has been already templatized. And stamp is always "binded" to last templatized element.
Am I doing something wrong? I do really hate Polymer because of it's bad documentation and hard to google something usefull
I found a workaround which is propably not the best solution but it works. I tried to search in source code for some solutions but there wasn't anything usefull except the one property called __templatizeOwner. This property is set to all templatized elements. Removing this property from an element is the way.
renderTemplate(query, properties) {
let element = this.shadowRoot.querySelector(query);
if(element.__templatizeOwner) {
element.__templatizeOwner = null;
}
this.templatize(element);
var instance = this.stamp(properties);
return instance;
}
I am not sure what side effects this might have (more memory usage or something) but this is the only way I was able to find out.
I need to setup the behaviour of a polymer web-compontent at runtime. I tried to change the "behaviours" array by pushing the new behaviour, but it didn't work. Is there a proper way to do it?
I'm trying to create a table web-component with a pager at bottom. It should be extensible allowing the loading of data from a javascript array, a restful service or a custom source. Thus, I decided to create a behaviour for each one of these source and change it when the source changes. Is it a correct way to design it?
Here as example the source code of the behaviour to load data from an array. It has the following function:
itemsLoad: function(page, itemsPerPage, callback) {...
which is called from the web-component to load data of a specific page. My idea is that each behaviour based on the type of data source (e.g. CSV, JSON, etc.) will implement this method in a different way. Then, the behaviour will be registered at run-time, because is at run-time that the developers knows which is the source to use.
I don't think you will be able to change behaviours at run-time, because they are mixed into the element prototype.
What you can do is create a separate element for each of your cases (csv, json, etc) and create nodes dynamically as required. You could than place that element inside your grid
<table-component>
<json-data-source></json-data-source>
</table-component>
The <table-component> would look for a child element which implements itemsLoad to get the data.
EDIT
To work with child nodes you would use Polymer's DOM API. For example you could listen to added child nodes and select one that implements the itemsLoad method.
Polymer({
attached: function() {
Polymer.dom(this).observeNodes(function(info) {
var newNodes = info.addedNodes;
for(var i=0; i<newNodes.length; i++) {
var dataSource = newNodes[i];
if(dataSource.itemsLoad && typeof dataSource.itemsLoad === 'function') {
this.loadItems(dataSource);
break;
}
}
});
}
loadItems: function(dataSource) {
dataSource.itemsLoad().then(...);
}
});
You could replace Polymer.dom(this).observeNodes with simply iteration over Polymer.dom(this).children. Whichever works best for you.
I am trying to implement drag and drop files from desktop using knockout. The starting code is taken from html5rocks.
I tried to implement this using event binding, so my View looks something like this:
<div class="drop_zone" data-bind="event:{
dragover: function(data, e){ $root.dragover(e);},
drop: function(data, e){ $root.drop(e, $parent);},
dragenter: function(data, e){ $root.dragenter(e);},
dragleave: function(data, e){ $root.dragleave(e);}
}">Drop files here</div>
The $parent parameter was used in attempt to do something similar to my previous question, where parent was able to locate where exactly the element should be removed.
My ViewModel is an observableArray of observableArrays (many dropzones) and looks like this:
this.dropZones = ko.observableArray([{
'elements' : ko.observableArray([])
},{
'elements' : ko.observableArray([])
}]);
The full code can be found in jsFiddle, but the problem is that I can not properly add new files to the files element. Also I can not correctly highlight the element the person is dragEntering/Leaving.
I understand why I can not highlight the proper element (I just select every class, but I can not understand how to select the parent element), I am failing to understand why parent.elements.push(f.name); does not add the name of the file to the right parent.
Can anyone please tell me what is the problem and how can I fix it?
P.S. in jsFiddle I get the error:
TypeError: Cannot read property 'dataTransfer' of undefined
which tells me that I am passing wrong event, but the same code on my local server does not give me this problem. The error which I am getting on localhost is:
Uncaught TypeError: Cannot call method 'push' of undefined
which tells me that my idea of using parent was wrong.
1) { $root.drop(e, $data);} instead of { $root.drop(e, $parent);}
2) var files = e.dataTransfer.files; (without originalEvent)
Fiddle. (I've not fixed problem with css, do it youself :))
You should use $data instead of $parent because of elements is the property of each dropZone element. So, when you iterate with foreach: dropZones you have access to current element by $data, and you should send to $root.drop function current element (not parent) for get access to it elements array.
Update:
Solved CSS problem. (with help of $index() and jQuery .eq())
You could read about binding context (parent, data, index and etc.) here.
var parent = el.getParent();
parent.getElement('div[class=test]'); // return array
var parent1 = el.parentNode;
parent1.getElement('div[class=test]'); // error getElement is not a function
It seems parent1 doesn't have all element methods of MooTools, how to extend all element method of parent1, like in page
Note: I have to use parentNode.
parent.getElement('div[class=test]');
should really be
parent.getElement("div.test");
there's a substantial difference going to element.getParent() and element.parentNode - it boils down to Element prototype, which cannot be extended in old versions of IE.
mootools works around that by saving a reference to the methods directly on the elements instead as properties.
hence if you do element.getParent() and that returns an element, this will extend it to have all the prototypes. element.parentNode returns a simple element object, which will work in browsers where the Element.prototype is inherited correctly.
you can make the second method work in IE by doing:
var parent1 = el.parentNode;
$(parent1).getElement("div.test");
Subsequent references to parent1 do not need the $ (or document.id) as the element will already have been extended.
so to summarize the answer:
to make an element extended, you need to run it through a selector.
var parent = el.parentNode;
$(parent); // this extends it.
parent.getElements("div.test").something()
Both ways work just fine on an element, proof: http://jsfiddle.net/SuJn6/
I assume what you're doing wrong is your el is actually an Element Collection, not a single element. In which case you need to loop your first array, and only then use parentNode, example: http://jsfiddle.net/35Fxf/
Pro-tip: name your variable carefully, el and els - all makes a huge difference.