How to extend native DOM elements using "is"? - javascript

I am trying to using the custom elements spec to extend native DOM elements using the "is" attribute, but it doesn't seem to be doing anything at all. I am using Google Canary so I am able to create custom elements, but no effect in extending.
In my sample, I am trying to add a caption to the native img tag:
<img is="super-img" src="http://lorempixel.com/400/200/"
caption="This is my custom caption!" />
<script>
document.registerElement('super-img', {
prototype: Object.create(HTMLImageElement.prototype, {
createdCallback: function() {
var caption = this.attributes.getNamedItem('caption').value;
alert(caption);
}
})
});
</script>
http://jsbin.com/OMaPIVoV/3/
The createdCallback never fires. Any ideas on how to accomplish this?

Object.create(HTMLImageElement.prototype, {
createdCallback: function() {…}
})
Object.create does create a map of property descriptors as its second parameter - not plain values - like Object.defineProperties does.
This is also mentioned in the article you found at "Adding JS properties and methods":
Of course there are umpteen thousand ways to construct a prototype. If
you're not a fan of creating prototypes like this, here's a more
condensed version of the same thing:
[…] [This] first format allows for the use of ES5 Object.defineProperty.
The second allows the use of get/set.
(The strike-through was added by me as it's rubbish)
So what you want is either
var SuperImgProto = Object.create(HTMLImageElement.prototype);
SuperImgProto.createdCallback = function() { … };
var SuperImg = document.registerElement('super-img', {prototype: SuperImgProto});
or
document.registerElement('super-img', {
prototype: Object.create(HTMLImageElement.prototype, {
createdCallback: {value: function() { … } }
})
});

looks like you forgot to tell your web component, that it extends the native img element. Here's a running example based on your fiddle but broken down to the important bits: http://jsbin.com/OMaPIVoV/7/edit
hope this helps!

Related

"Enhance" existing JS class in ECMAScript 2017 (Firefox v55)?

Basically I'm wondering whether it is possible to redefine a method at class level, not instance level, as sought here.
Just to make clear, also, I want to be able to call the original method (on this) from within the enhanced/replacement method.
I tried implementing the chosen answer, "decorating the constructor", but it doesn't appear to work in FF55's "SpiderMonkey" version of ES2017: I get a message saying the class "requires new".
Doing a bit of googling I found this article. This man talks about a whole bunch of concepts which I wasn't even aware existed in Javascript! My knowledge of JS hasn't gone much beyond the Javascript Definitive Guide 6th Edition, although I do use Promises and async/await a lot (I'm hoping a new edition of the JS Def Guide will come out one day...).
So are there any experts out there who know of a way to, essentially, "enhance" (i.e. re-engineer, not extend) existing JS classes in ES2017?
Let say you have a class named Original, which has a render method, which you want to update.
class Original {
render() {
return 'Original';
}
}
You can update it by changing the render method of the prototype of the Original function like so:
Original.prototype.render = function() {
return 'Changed';
}
If you want to add a new method to your class this is how you do it:
Original.prototype.print = function() {
return 'Printing...';
}
Then you can use these methods as usual.
const changed = new Original().render();
const printed = new Original().print();
Whoops... thanks to Dovydas for this. I was obviously having a mental block!
const superObserve = MutationObserver.prototype.observe;
MutationObserver.prototype.observe = function( target, config ){
console.log( 'blip');
superObserve.call( this, target, config );
}

Converse.js render into a container

Is it possible to configure Converse.js to render it's boxes into custom div containers instead of adding them to the body of the page?
Yes, you can do this by writing a converse.js plugin in which you override the insertIntoPage method of ChatBoxView.
Refer to the plugin documentation I linked to above. In short, it would look something like this:
// The following line registers your plugin.
converse_api.plugins.add('myplugin', {
overrides: {
// If you want to override some function or a Backbone model or
// view defined inside converse, then you do that under this
// "overrides" namespace.
ChatBoxView: {
insertIntoPage: function (type, status_message, jid) {
// XXX Your custom code comes here.
// The standard code looks as follows:
this.$el.insertAfter(converse.chatboxviews.get("controlbox).$el);
return this;
}
},
}
UPDATE: Since recent versions of converse.js, the method to override is instead _ensureElement and not insertIntoPage.

How to extend 'old style' dojox widgets?

I've already written my own dijit widgets, as well as extended the existing one. It's simply making new declare with extended widget as argument, and using the own one instead of extended one.
However, I have a problem with dojox/form/Uploader, because it's that 'old-style' widget using old-style syntax. Instead of using the object returned by require, one should use the global object:
require(['dojox/form/Uploader'], function(Uploader){
var u = new dojox.form.Uploader({})
u.startup()
})
So, if I want to extend that widget, and using the child 'class' instead of the original, how should I actually do that?
Another thing I don't fully understand is, why whe need to use that 'old-style' syntax for dojox/form/Uploader, because it's created with the same syntax as 'normal' widget:
return declare("dojox.form.Uploader", [Base, Button, HTML5, IFrame, Flash], {
I believe you can extend it just like a 'new style' (AMD) widget, i.e.:
require([
"dojo/_base/declare",
"dojox/form/Uploader"
], function(decl) {
var MyUploader = decl(dojox.form.Uploader, {
buildRendering: function() {
this.inherited(arguments);
this.domNode.appendChild(
document.createTextNode(" ← awesome"));
}
});
new MyUploader({}).placeAt("x").startup();
});
Or am I misunderstanding your question? The reason there are traces of the 'old style' syntax in Uploader (and some other widgets) is probably just because nobody has had the time to port it to the new style yet (so it was probably automatically "converted").
Edit: Actually, the Uploader returns a 'new style' object in addition to setting the dojox.form.Uploader global. So you can actually change the above example to:
require([
"dojo/_base/declare",
"dojox/form/Uploader"
], function(decl, Uploader) {
var MyUploader = decl(Uploader, {
....
since Uploader === dojox.form.Uploader here.

jQuery UI Operator Overloading?

I'm attempting to figure out OOP Javascript, jQuery, and jQuery UI all at the same time. Basically, I want to create a custom "panel" component that I can reuse in various places throughout my web app. The panel consists of a title bar and then content below it.
So I'm using jQuery UI to accomplish this. I want to be able to make the component and then change its attributes (like the title bar text). Here's an example:
$(function()
{
$.widget("custom.ShinyPanel",
{
options: {
css:{},
title:""
},
_create:function()
{
$(this.element).addClass("shinyPanel").disableSelection();
this.titleBar = $(createElement("div")).addClass("shinyPanelTitleBar").appendTo(this.element);
this.topShine = $(createElement("div")).addClass("shinyPanelTopShine").appendTo(this.element);
this.leftShine = $(createElement("div")).addClass("shinyPanelLeftShine").appendTo(this.element);
this.content = $(createElement("div")).addClass("shinyPanelContent").appendTo(this.element);
this._refresh();
},
_refresh:function()
{
if (this.options.hasOwnProperty("title"))
$(this.titleBar).html(this.options.title);
}
});
});
// $("#divShotList").ShinyPanel({title:"Shot List"}); // this works
$("#divShotList").ShinyPanel();
$("#divShotList").title = "Shot List"; // this doesn't work
<div id="divShotList" style="position:absolute; top:5px; bottom:10px; width:250px;"></div>
Is there a way for me to overload the = operator or something to make this work with this syntax? I know that I could probably create an extra function, like setProperty or something, but it would be really cool if I could just keep this syntax and get it to work. Any idea how I can modify the widget to make this happen?
The element or jQuery wrapped element is not your widget:
$("#divShotList").data('ShinyPanel')._setOption('title', 'something');
But it is store in the .data() of the element.
Alternatively:
var shinyPanel = $("#divShotList").ShinyPanel().data('ShinyPanel');
shinyPanel.options.title = 'new title';
shinyPanel.refresh();
would also work.
Final Edit: To answer you question: No.

Get Child Element Value with Mootools

I'm trying to change a class by first discovering if it is the parent object to a particular image using Mootools (clients previous web developer seemed to have it in for me). I can't seem to find much good documentation on the subject.
<div class="textwidget">
<img src="share.jpg">
</div>
So far I've managed to locate all the divs with the class 'textwidget' using:
var obs = $$('.textwidget');
Now I need to cycle through them and discover which hosts a child node with the src listed above...
for(var i=0;i<obs.length;i++){
if(obs[i].childnode.src == 'share.jpg'){ // <-- non mootools syntax
obs[i].class = 'new class'; // <-- non mootools syntax.
}
}
i'd like to run a loop like this, but in mootools speak of course. Anyone familiar with the correct syntax?
Thanks
-J
I think what you want is something like:
for(var i=0;i<obs.length;i++){
if(obs[i].getChildren()[0].getProperty('src') == 'share.jpg'){ // <-- mootools syntax
obs[i].setProperty('class','new class'); // <-- mootools syntax.
}
}
You can find more details here:
http://mootools.net/docs/core/Element/Element
you could do something like this via a selector / parent combo:
document.getElements("div.textwidget img[src=share.jpg]").getParent("div.textwidget");
http://www.jsfiddle.net/MBc37/4/
or you can be more thorough / longwinded...
// css selector, divs array filtered by existance of a child img
results.mixedFilter = document.getElements("div.textwidget").filter(function(el) {
return el.getElement("img[src=share.jpg]");
});
// checking vs img src properties of all child imgs
results.longwinded = [];
document.getElements("div.textwidget").each(function(el) {
var imgs = el.getElements("img");
if (!imgs.length) return;
if (imgs.some(function(im) {
return im.get("src") == "share.jpg";
})) results.longwinded.push(el);
});
What you want to do here is filter the array of elements like this:
$$('.text-widget').filter(function(e) {
var child_img = e.getFirst('img');
return $defined(child_img) && child_img.get('src') == 'share.jpg'
});
If the function passed to 'filter' returns true the item gets included.
You could also use selectors as one of the other answers mentioned. Although there might be performance issues with using them in that way?
Is this what you are looking for:
$$('img[src=share.jpg]').getParent().set('class', 'newClass');

Categories

Resources