I am applying a binding like this in a restartless add-on:
var css = '.findbar-container { -moz-binding:url("' + self.path.chrome + 'findbar.xml#matchword") }';
var cssEnc = encodeURIComponent(css);
var newURIParam = {
aURL: 'data:text/css,' + cssEnc,
aOriginCharset: null,
aBaseURI: null
}
cssUri = Services.io.newURI(newURIParam.aURL, newURIParam.aOriginCharset, newURIParam.aBaseURI);
myServices.sss.loadAndRegisterSheet(cssUri, myServices.sss.USER_SHEET);
findbar.xml contents are:
<?xml version="1.0"?>
<bindings xmlns="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="matchword">
<content>
<children/>
<xul:toolbarbutton anonid="matchwordbtn" accesskey="w" class="tabbable" label="Whole Word Only" tooltiptext="Match only whole words" oncommand="console.log('hi')" type="checkbox"/>
</content>
</binding>
</bindings>
This just adds a button to the FindBar labeled "Whole Word Only". But now to remove it, I am just unregistering the stylesheet with myServices.sss.unregisterSheet(cssUri, myServices.sss.USER_SHEET);, however this is not unbinding it.
An answer on ask.mozilla.org told me this is expected behavior, but offered no solution.
I was thinking maybe I should dynamically add the binding rather than via CSS, I didn't test this but it doesn't fit the 3 reasons for XBL updates:
A bound element matches a style rule that specifies a different binding
The element is removed from the bound document
The element is destroyed (e.g., by closing the document)
The answer told me it's expected yet funky behavior.
Well, I just remembered that I have some working code that does (re)bind different XBL bindings, essentially.
It goes like this:
There is a base binding, or not (in your case the original binding of .findbar-container).
Then I have multiple classes that define different -moz-bindings.
These classes are set and removed at runtime.
Since that works for me, it should in theory work for you:
In your style, do not have a rule for the element itself, but for a class, e.g.
.findbar-container.myaddonclass { moz-binding: ... }
In your code, on load add that new class, e.g.
Array.forEach(
document.querySelectorAll(".findbar-container"),
e => e.classList.add("myaddonclass")
);
In your code, on unload remove the class again:
Array.forEach(
document.querySelectorAll(".findbar-container"),
e => e.classList.remove("myaddonclass")
);
This should force a CSS-rule reevaluation, and bindings reevaluation with that and hence fits the "A bound element matches a style rule that specifies a different binding" rule.
Of course, this sucks when not all elements you want to rebind are already present on load of your add-on, but MutationObserver could help with that...
Related
This question already has answers here:
Advantages of createElement over innerHTML?
(5 answers)
Closed 6 years ago.
I'm making divs with several sub elements something like this:
<div class="item" data-id="28">
<div class="action-btns">
<button class="add"></button>
<button class="rmv"></button>
</div>
<div class="info">
<h3>Title here</h3>
<span>caption here</span>
</div>
</div>
And im giving functions to those two buttons on click. I'm wondering which method of creating these items is better - createElement or innerHTML?
As far as createElement goes i like it because i can bind the onclick while creating the button inside the element and then append it. Also im wondering if appending this "item" to the parent div is faster / better than updating innerHTML += "something";
As far as innerHTML goes there are fewer lines of code to write, but also i have to either write the onclick="myFunction()" inside the buttons instead of adding it dynamically.
Please no jQuery or anyting but pure Js. Thanks for your time :)
https://jsperf.com/innerhtml-vs-createelement-test (Note i did not write this)
Results in chrome is about 60% ish percent slower using createElement. As per (#Sagar V and #Hajji Tarik) answer consider more then just speed.
There are several advantages to using createElement instead of modifying innerHTML (as opposed to just throwing away what's already there and replacing it) besides safety, like Pekka already mentioned:
Preserves existing references to DOM elements when appending elements
When you append to (or otherwise modify) innerHTML, all the DOM nodes inside that element have to be re-parsed and recreated. If you saved any references to nodes, they will be essentially useless, because they aren't the ones that show up anymore.
Preserves event handlers attached to any DOM elements
This is really just a special case (although common) of the last one. Setting innerHTML will not automatically reattach event handlers to the new elements it creates, so you would have to keep track of them yourself and add them manually. Event delegation can eliminate this problem in some cases.
Could be simpler/faster in some cases
If you are doing lots of additions, you definitely don't want to keep resetting innerHTML because, although faster for simple changes, repeatedly re-parsing and creating elements would be slower. The way to get around that is to build up the HTML in a string and set innerHTML once when you are done. Depending on the situation, the string manipulation could be slower than just creating elements and appending them.
Additionally, the string manipulation code may be more complicated (especially if you want it to be safe).
Here's a function I use sometimes that make it more convenient to use createElement.
function isArray(a) {
return Object.prototype.toString.call(a) === "[object Array]";
}
function make(desc) {
if (!isArray(desc)) {
return make.call(this, Array.prototype.slice.call(arguments));
}
var name = desc[0];
var attributes = desc[1];
var el = document.createElement(name);
var start = 1;
if (typeof attributes === "object" && attributes !== null && !isArray(attributes)) {
for (var attr in attributes) {
el[attr] = attributes[attr];
}
start = 2;
}
for (var i = start; i < desc.length; i++) {
if (isArray(desc[i])) {
el.appendChild(make(desc[i]));
}
else {
el.appendChild(document.createTextNode(desc[i]));
}
}
return el;
}
If you call it like this:
make(["p", "Here is a ", ["a", { href:"http://www.google.com/" }, "link"], "."]);
you get the equivalent of this HTML:
<p>Here is a link.</p>
The answer of #Matthew Crumley
innerHTML just put the plain text. Whereas createElement creates the element object and adds to Parent.
since browser convert tags in plain text to HTML element, it is converted to tags. It is not recommended.
createElement is the recommended method
I’m writing a custom <select> element using native DOM (no Polymer).
I’m trying to use my element with a <label> element and correctly trigger click events to my element when the <label> is clicked, i.e.:
<label>
My Select:
<my-select placeholder="Please select one">...</my-select>
</label>
or
<label for='mySelect1'>My Select:</label>
<my-select id='mySelect1' placeholder="Please select one">...</my-select>
However, this behavior doesn’t seem to work out of the box, even if I add a tabindex to make it focusable.
Here’s a stripped down version of the code and a JSFiddle with some basic debugging:
var MySelectOptionProto = Object.create(HTMLElement.prototype);
document.registerElement('my-select-option', {
prototype: MySelectOptionProto
});
var MySelectProto = Object.create(HTMLElement.prototype);
MySelectProto.createdCallback = function() {
if (!this.getAttribute('tabindex')) {
this.setAttribute('tabindex', 0);
}
this.placeholder = document.createElement('span');
this.placeholder.className = 'my-select-placeholder';
this.appendChild(this.placeholder);
var selected = this.querySelector('my-select-option[selected]');
this.placeholder.textContent = selected
? selected.textContent
: (this.getAttribute('placeholder') || '');
};
document.registerElement('my-select', {
prototype: MySelectProto
});
Only the phrasing content elements can be targeted by <label>.
So you'll have to manage the focus action by yourself if you want to use a non-standard (autonomous custom) element.
Instead you can choose to define a Customized built-in element that will extend the <select> element, as in the following example:
https://jsfiddle.net/h56692ee/4/
var MySelectProto = Object.create( HTMLSelectElement.prototype )
//...
document.registerElement('my-select', { prototype: MySelectProto, extends: "select" } )
You'll need to use the is attribute notation for HTML:
<label>
My Select:
<select is="my-select" placeholder="Please select one">
<option>...</option>
</select>
</label>
Update More explanations in these 2 posts: here and there.
But the standard is not finished, and maybe in the future you’ll be able to use the built-in semantic with autonomous custom elements, too.
— Supersharp, Jul 15, 2016 at 16:05
We live in the future now.
The standard has significantly changed and we have more capable custom elements now.
Tl;dr:
Labels work iff the constructor associated with your custom element has a formAssociated property with the value true.
When looking at the documentation, at first sight, you might conclude that custom elements are not allowed as <label> targets.
The docs say:
Elements that can be associated with a <label> element include <button>, <input> (except for type="hidden"), <meter>, <output>, <progress>, <select> and <textarea>.
But custom elements are not mentioned.
You try your luck anyway and try to enter this HTML fragment in an HTML validator:
<label>Label: <my-custom-element></my-custom-element></label>
or:
<label for="my-id">Label: </label><my-custom-element id="my-id"></my-custom-element>
and both of these are valid HTML snippets!
However, trying this HTML fragment:
<label for="my-id">Label: </label><span id="my-id"></span>
shows this error message:
Error: The value of the for attribute of the label element must be the ID of a non-hidden form control.
But why is a random <my-custom-element> considered a “non-hidden form control”?
In the validator’s GitHub issues you then find the already fixed issue #963:
label for doesn’t allow custom elements as targets
The spec allows form-associated custom elements as for targets
https://html.spec.whatwg.org/multipage/forms.html#attr-label-for
This currently triggers an error:
<label for="mat-select-0">Select:</label>
<mat-select role="listbox" id="mat-select-0"></mat-select>
Easy to check if element name contains a dash.
Much harder to check that custom element definition includes a static formAssociated property returning true, since this is likely buried deep in an external framework .js file:
https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example
There is indeed one peculiar addendum in the WHATWG specification which enumerates the same “labelable elements” as the documentation plus form-associated custom elements.
There is an example in the specification, and researching this problem today yields this Q&A post in the results as well as blog articles such as “More capable form controls” by Arthur Evans (archived link), which show what the solution is.
The solution to enable <label> clicking targeting custom elements: formAssociated
All that needs to be done to enable the <label> action is to add a formAssociated property with the value true to the constructor of the custom element.
However, the code in the question needs to be ported to the current Web Components API.
The new formAssociated property is part of the new ElementInternals API (see attachInternals) which is used for richer form element control and accessibility.
Nowadays, custom elements are defined with classes and customElements.define, and ShadowRoots are used.
The static formAssociated = true; field can then be added to the class which you consider to belong to the “form-associated custom elements”.
Having this property makes your custom element dispatch click events when clicking the <label>; now we’re getting somewhere!
In order to turn this click into a focus action, there’s one more thing we need in the attachShadow call: the property delegatesFocus set to true.
// Rough translation of your original code to modern code, minus the `tabIndex` experimentation.
class MySelectOptionProto extends HTMLElement{}
customElements.define("my-select-option", MySelectOptionProto);
class MySelectProto extends HTMLElement{
#shadowRoot;
#internals = this.attachInternals();
static formAssociated = true;
constructor(){
super();
this.#shadowRoot = this.attachShadow({
mode: "open",
delegatesFocus: true
});
this.#shadowRoot.replaceChildren(Object.assign(document.createElement("span"), {
classList: "my-select-placeholder"
}));
this.#shadowRoot.firstElementChild.textContent = this.querySelector("my-select-option[selected]")?.textContent ?? (this.getAttribute("placeholder") || "");
}
}
customElements.define("my-select", MySelectProto);
<label>My label:
<my-select placeholder="Hello">
<my-select-option>Goodbye</my-select-option>
<my-select-option>world</my-select-option>
</my-select>
</label>
<label for="my-id">My label:</label>
<my-select id="my-id">
<my-select-option>Never gonna give you</my-select-option>
<my-select-option selected>up</my-select-option>
</my-select>
<label for="my-id">My other label</label>
delegatesFocus will focus the first focusable child of the shadow root, when some non-focusable part of the custom element has been clicked.
If you need more control, you can remove the property, and add an event listener to the custom element instance.
In order to be sure that the click came from the <label>, you can check if the event’s target is your custom element, i.e. this, and that the element at the x and y position on the screen was a <label> targeting your custom element.
Fortunately, this can be done quite reliably by passing x and y to document.elementFromPoint.
Then, simply calling focus on your desired element will do the job.
The list of labels that target a custom element is another thing that the ElementInternals API provides via the labels property.
class MySelectProto extends HTMLElement{
// …
constructor(){
// …
this.addEventListener("click", ({ target, x, y }) => {
const relatedTarget = document.elementFromPoint(x, y);
if(target === this && new Set(this.#internals.labels).has(relatedTarget)){
console.log("Label", relatedTarget, "has been clicked and", this, "can now become focused.");
// this.focus(); // Or, more likely, some target within `this.#shadowRoot`.
}
});
}
}
Is there a way in <select> list, for example, to make onClick activate a JavaScript function that will show the title of the element just as pattern in HTML 5 does?
I want to do that when you click on the <select>, it will activate a JavaScript function that under some condition (doesn’t matter—some if expression) will show a sentence (that I wrote) in a bubble like place (the place that the pattern shows the title when something isn’t according to the pattern (pattern in HTML5)).
You can set a custom validity error on a select element by calling the setCustomValidity method, which is part of the constraint validation API in HTML5 CR. This should cause an error to be reported, upon an attempt at submitting the form, in a manner similar to reporting pattern mismatches. Example:
<select onclick="this.setCustomValidity('Error in selection');
title="Select a good option">
(In practice, you would probably not want to use onclick but onchange. But the question specifically mentions onClick.)
There are problems, though. This only sets an error condition and makes the element match the :invalid selector, so some error indicator may happen, but the error message is displayed only when the form data is being validated due to clicking on a submit button or something similar. In theory, you could use the reportValidity method to have the error shown immediately, but browsers don’t support it yet.
On Firefox, the width of the “bubble” is limited by the width of the select element and may become badly truncated if the longest option text is short. There is a simple CSS cure to that (though with a possible impact on the select menu appearance of course).
select { min-width: 150px }
You might also consider the following alternative, which does not affect the select element appearance in the normal state but may cause it to become wider when you set the custom error:
select:invalid { min-width: 150px }
There is also the problem that Firefox does not include the title attribute value in the bubble. A possible workaround (which may or may not be feasible, depending on context) is to omit the title attribute and include all the text needed into the argument that you pass to setCustomValidity.
A possible use case that I can imagine is a form with a select menu such that some options there are not allowed depending on the user’s previous choices. Then you could have
<select onchange="if(notAllowed(this)) setCustomValidity('Selection not allowed')" ...>
where notAllowed() is a suitable testing function that you define. However, it is probably better usability to either remove or disable options in a select as soon as some user’s choices make them disallowed. Admittedly, it might mean more coding work (especially since you would need to undo that if the user changes the other data so that the options should become allowed again).
In my opinion Jukka's solution is superior however, its fairly trivial to do something approaching what you're asking for in JavaScript. I've created a rudimentary script and example jsFiddle which should be enough to get you going.
var SelectBoxTip = {
init : function(){
SelectBoxTip.createTip();
SelectBoxTip.addListeners();
},
addListeners : function(){
var selects = document.getElementsByTagName("select");
for (var i = 0; i < selects.length; i++){
var zis = selects[i];
if(zis.getAttribute('title')){//only if it has a title
zis.addEventListener("focus", SelectBoxTip.showTip, false);
zis.addEventListener("blur", SelectBoxTip.hideTip, false);
}
}
},
createTip : function(){
tip = document.createElement("div");
tip.id = "tip";
tip.style.position = "absolute";
tip.style.bottom = "100%";
tip.style.left = "0";
tip.style.backgroundColor = "yellow";
document.body.appendChild(tip);
},
showTip : function(e){
this.parentNode.appendChild(tip);
tip.innerHTML=this.title;
tip.style.display="block";
},
hideTip : function(e){
tip.style.display="none";
}
};
SelectBoxTip.init();
attempting to have my webpage be a bit more dynamic by having the background change on some elements when a checkbox is clicked. I am trying to do this via class change and a CSS sheet. I have the following which is kicking out an error that my onclick function ins not defined (in IE9). More importantly will the webpage update if I only change the class of the object which would have a different class in the CSS file. Whats a better alternative if this does not work?
my elemenet and function
UPDATE
I made updates to both my HTML and CSS file as suggested by many. I am still getting no change in my webpage but the console is claiming that my function called from the onclick event is not defined which is a bit odd since it is. Also does this type for scripting belong in the HTML or should I pull it out and put in a seperate file. I figured since it was creating elements it belongs in the main html. Is there a cleaner more compact way of accomplishing this and not making my home screen html huge?
<tr class= 'tr.notchosen'><td><input type='checkbox' onclick='handleClick(this.id)'/></td></tr>
function handleClick(cb) {
var currentColumn = cb.parentNode
var currentRow = currentColumn.parentNode
if (currentRow.className === "chosen")
{
currentRow.className = "notchosen";
}
else
{
currentRow.className = "chosen";
}
}
and my css file is the following
tr.chosen
{
background-color:rgba(255,223,0,0.75);
}
tr.notchosen
{
background-color:rgba(255,223,0,0);
}
There are a couple of things going on here. First, your css selector is not quite right. In fact, I would suggest making the class name just "chosen" or "not chosen" and then selecting tr elements with that class.
<tr class='notchosen'>
And then you can target it from css (which was probably the original intention)
tr.notchosen
{
background-color:rgba(255,223,0,0);
}
Further, although I would not suggest using inline javascript, using your example, you should pass this if you want to work with the element and not this.id which would pass a string.
onclick='handleClick(this)'
The last part would be to sync up your javascript with the class name change
if (currentRow.className == "chosen")
{
currentRow.className = "notchosen";
}
else
{
currentRow.className = "chosen";
}
I'm looking for a way to add a inline span element with attributes to a selection.
The hard part of this is getting it working with selections that pass over multiple block level elements.
I was looking in the sourcecode of the StyleCombobox and found this line.
var style = styles[ value ],
elementPath = editor.elementPath();
editor[ style.checkActive( elementPath ) ? 'removeStyle' : 'applyStyle' ]( style );
This way it already works on multiple block level elements.
The only thing is that i would like to apply attributes to the span that is made around the multiple selections for different block level elements instead of applying a style element.
Does anyone know how this can be done?
I used this as solution.
It is indeed possible to set attributes and element type.
this wasn't defined in the api. I found this in the CKEditor 3.0 api (older version)
var style = new CKEDITOR.style({attributes: {name:"changed"}});
editor.applyStyle(style);
The latest Solution for your Problem.
Get Selected Text:
editor.getSelection().getSelectedText();
Put tags and attributes
editor.applyStyle(new CKEDITOR.style({
element : 'span',
attributes : {'class':'YourClass','data-Otherattr':'otherattrvalue'},
style : {'background-color':'gray'}
});
);