Do any projects exist that implement the follow proposal?
http://www.w3.org/2008/webapps/wiki/Selector-based_Mutation_Events
Here's a better way that allows you to listen for any selector with actual events, vs dirty checking, mutation listening, and other non-performant ideas: https://github.com/csuwildcat/SelectorListener (it was also used as the basis of this highly rated solution: Alternative to DOMNodeInserted)
Here are a few examples:
// Listening for attribute value matches? Child's play.
document.addSelectorListener('.foo[bar="boom"]', function(){ ... });
// Matching elements on hashchange can be annoying, let's make it stupid simple
document.addSelectorListener('*:target', function(event){
alert('The hash-targeted element is:' + event.target);
});
// How about a more performant way to listen for custom tooltip nodes document wide?
document.addSelectorListener('.tooltip:hover', function(){ ... });
/*** Now that we have the new CSS 4 Selector spec, let's see what we can do: ***/
// Working with HTML5 sliders just got even easier
document.querySelector('#RandomForm').addSelectorListener('slider:out-of-range', function(){
alert('Your slider value is now out of range! Oh noes!');
Related
I'm starting to learn angularJS better, and I've noticed that AngularJS tries to make strong emphasis on separating the view from the controller and encapsulation. One example of this is people telling me DOM manipulation should go in directives. I kinda got the hang of it now, and how using link functions that inject the current element allow for great behavior functionality, but this doesn't explain a problem I always encounter.
Example:
I have a sidebar I want to open by clicking a button. There is no way to do this in button's directive link function without using a hard-coded javascript/jquery selector to grab the sidebar, something I've seen very frowned upon in angularJS (hard-coding dom selectors) since it breaks separation of concerns. I guess one way of getting around this is making each element I wish to manipulate an attribute directive and on it's link function, saving a reference it's element property into a dom-factory so that whenever a directive needs to access an element other than itself, it can call the dom-factory which returns the element, even if it knows nothing where it came from. But is this the "Angular way"?
I say this because in my current project I'm using hard-coded selectors which are already a pain to mantain because I'm constantly changing my css. There must be a better way to access multiple DOM elements. Any ideas?
There are a number of ways to approach this.
One approach, is to create a create a sidebar directive that responds to "well-defined" broadcasted messages to open/close the sidebar.
.directive("sidebar", function(){
return {
templateUrl: "sidebar.template.html",
link: function(scope, element){
scope.$root.$on("openSidebar", function(){
// whatever you do to actually show the sidebar DOM content
// e.x. element.show();
});
}
}
});
Then, a button could invoke a function in some controller to open a sidebar:
$scope.openSidebar = function(){
$scope.$root.$emit("openSidebar");
}
Another approach is to use a $sidebar service - this is somewhat similar to how $modal works in angularui-bootstrap, but could be more simplified.
Well, if you have a directive on a button and the element you need is outside the directive, you could pass the class of the element you need to toggle as an attribute
<button my-directive data-toggle-class="sidebar">open</button>
Then in your directive
App.directive('myDirective', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.element('.' + attrs.toggleClass).toggleClass('active');
}
};
}
You won't always have the link element argument match up with what you need to manipulate unfortunately. There are many "angular ways" to solve this though.
You could even do something like:
<div ng-init="isOpen = false" class="sidebar" ng-class="{'active': isOpen}" ng-click="isOpen = !isOpen">
...
</div>
The best way for directive to communicate with each other is through events. It also keeps with the separation of concerns. Your button could $broadcast on the $rootScope so that all scopes hear it. You would emit and event such as sidebar.open. Then the sidebar directive would listen for that event and act upon it.
I'm searching for a way to access an attribute on a Polymer custom element from the DOM
or to send data from Polymer.register to the DOM.
This really simple element below takes two values and multiplies them, placing the result in its result attribute.
How can I access this result from the outside?
<element attributes='value times result' name='value-box'>
<template>
<p>{{result}}</p>
</template>
<script>
Polymer.register(this, {
ready: function() {
if (this.value != null && this.times != null) {
this.result = this.value * this.times;
}
}
});
</script>
</element>
result is a property on your element just like times and value. You can access it from outside JS, as you would any property on a normal HTML element. For example:
<value-box value="2" times="10"></value-box>
<script>
document.querySelector('value-box').result;
</script>
Internal to your element, what you want is to keep the result computed property up to date as times/value change. There are a couple of ways to do that. One is to use <property>Changed watchers [1]:
<element name="value-box" attributes="value times result">
<template>
<p>result: {{result}}</p>
</template>
<script>
Polymer.register(this, {
valueChanged: function() {
this.result = this.value * this.times;
},
timesChanged: function() {
this.result = this.value * this.times;
}
});
</script>
</element>
Demo: http://jsbin.com/idecun/2/edit
Alternatively, you can use a getter for result:
Polymer.register(this, {
get result() {
return this.value * this.times;
}
});
Demo: http://jsbin.com/oquvap/2/edit
Note For this second case, if the browser doesn't support Object.observe, Polymer will setup a timer to dirty check result. This is why you see "here" printed in the console for this second example. Run the same thing in Chrome Canary with "Experimental WebKit features" enabled in about:flags, and you won't see the timer. Yet another reason why I can't wait for Object.observe to be everywhere! :)
Hope this helps.
Just wanted to add a useful follow up to this (Even though the question has been answered).
My follow up is in response to the following comment on the actual answer:
I'm curious as to why selection with jQuery didn't work. Does it not recognize Custom Elements? – CletusW Jul 8 '13 at 19:57
The most likely reason jQuery didn't see your element is because it was not fully formed by the browsers run time at that point.
I ran into this problem while developing my ASP.NET MVC + polymer js sample app on my github page, and essentially what I was trying to do was call methods and access properties on my polymer object before polymer had made everything usable.
Once I moved the code I was using into a button click (So I could trigger it manually after I visually could see my component was ready) everything worked fine.
For now, if you try to access anything too soon, EG: in your jQ doc.ready handler, there's a good chance you'll run into all sorts of daft problems like this.
If you can find a way of delaying your action, or even better using polymer signals to signal from the components ready handler to an outside agent, that sets a flag telling you the component is ready, then you can sort this easily.
I am trying to add an Event Listener to a Htm5-Canvas which is managed by Kineticjs (the Canvas was created via a Stage of KineticJS).
I tried out (using jQuery):
$(selector).keydown( function(e) {... } )
with the following Selectors:
window (it is working, but it is listening to the whole Window and thereby not good)
All Canvas-Elements $('canvas') <-- not working
The Container, where KineticJS and its Canvas are embedded <-- not working
The Container-Div of KineticJS (created by Kinetic) with $('.kineticjs-content').keydown( function() { ... } ) <-- not working
Only $(window) is working. After experimenting with plain Html5-Canvas i figured out, that the Canvas-Element has Built-in-Support for Keyboard-Events.
So i think, KineticJS is doing something magic around here. Wrong Selector-Usage can be excluded.
I checked every Selector with this code: console.log( $(selector).length )
Can anyone help here? Thx in advance!
I suggest using one of the Keyboard Plugins out there:
http://robertwhurst.github.io/KeyboardJS/
http://dmauro.github.io/Keypress/
They work well together with KineticJS.
If you can include javascript into it, here is the code:
if(keyIsPressed && keycode == somenumber){
doSomething();
}
As of right now On only supports mouse and touch events.
Each layer has its own canvas that you can grab and attach events to.
From the limited experience I have with this (2 days), I saw that each layer you add becomes a canvas, so if you have a reference to the topmost layer in a variable (i.e. topmostLayer), you can do
var canvas = $(topmostLayer.getContext().canvas);
With this in place, what #devnull69 suggested just works:
canvas.attr('tabindex', 0);
canvas.keydown(function (ev) { ... });
I have it in place in my application and works fine.
see link, you need:
var canvas=layer.getCanvas()._canvas;
$(canvas).attr('tabindex', 0); // increase tabindex if needed
canvas.focus();
$(canvas).keydown(function (e) {
console.log(e.keyCode); // your handler here
});
You'll have to make sure that the canvas element has the focus before it can accept keyboard events. Unfotunately the .focus() method didn't work for me in Firefox, so I tried this and voilà
$('canvas').attr('tabindex', 0);
$('canvas').keydown(function(e) {
alert(e.keyCode);
e.preventDefault(); // to stop the key events from bubbling up
});
Click the canvas and it will have the focus.
bangin' my head against this and it's starting to hurt.
I'm having trouble with adding an event to an element.
I'm able to add the event, and then call it immediately with element.fireEvent('click'), but once the element is attached to the DOM, it does not react to the click.
example code:
var el = new Element('strong').setStyle('cursor','pointer');
el.addEvent('click',function () { alert('hi!'); });
el.replaces(old_element); // you can assume old_element exists
el.fireEvent('click'); // alert fires
however, once I attach this to the DOM, the element is not reactive to the click. styles stick (cursor is pointer when I mouseover), but no event fires. tried mouseover as well, to no avail.
any clues here? am I missing something basic? I am doing this all over the place, but in this one instance it doesn't work.
EDIT----------------
ok here's some more code. unfortunately I can't expose the real code, as it's for a project that is still under tight wraps.
basically, the nodes all get picked up as "replaceable", then the json found in the rel="" attribute sets the stage for what it should be replaced by. In this particular instance, the replaced element is a user name that should pop up some info when clicked.
again, if I fire the event directly after attaching it, all is good, but the element does not react to the click once it's attached.
HTML-----------
<p>Example: <span class='_mootpl_' rel="{'text':'foo','tag':'strong','event':'click','action':'MyAction','params':{'var1': 'val1','var2': 'val2'}}"></span></p>
JAVASCRIPT-----
assumptions:
1. below two functions are part of a larger class
2. ROOTELEMENT is set at initialize()
3. MyAction is defined before any parsing takes place (and is properly handled on the .fireEvent() test)
parseTemplate: function() {
this.ROOTELEMENT.getElements('span._mootpl_').each(function(el) {
var _c = JSON.decode(el.get('rel'));
var new_el = this.get_replace_element(_c); // sets up the base element
if (_c.hasOwnProperty('event')) {
new_el = this.attach_event(new_el, _c);
}
});
},
attach_event: function(el, _c) {
el.store(_c.event+'-action',_c.action);
el.store('params',_c.params);
el.addEvent(_c.event, function() {
eval(this.retrieve('click-action') + '(this);');
}).setStyle('cursor','pointer');
return el;
},
Works just fine. Test case: http://jsfiddle.net/2GX66/
debugging this is not easy when you lack content / DOM.
first - do you use event delegation or have event handlers on a parent / the parent element that do event.stop()?
if so, replace with event.preventDefault()
second thing to do. do not replace an element but put it somewhere else in the DOM - like document.body's first node and see if it works there.
if it does work elsewhere, see #1
though I realsie you said 'example code', you should write this as:
new Element('strong', {
styles: {
cursor: "pointer"
},
events: {
click: function(event) {
console.log("hi");
}
}
}).replaces(old_element);
no point in doing 3 separate statements and saving a reference if you are not going to reuse it. you really ought to show the ACTUAL code if you need advice, though. in this snippet you don't even set content text so the element won't show if it's inline. could it be a styling issue, what is the display on the element, inline? inline-block?
can you assign it a class that changes it on a :hover pseudo and see it do it? mind you, you say the cursor sticks which means you can mouseover it - hence css works. this also eliminates the possibility of having any element shims above it / transparent els that can prevent the event from bubbling.
finally. assign it an id in the making. assign the event to a parent element via:
parentEl.addEvent("click:relay(strong#idhere)", fn);
and see if it works that way (you need Element.delegate from mootools-more)
good luck, gotta love the weird problems - makes our job worth doing. it wouldn't be the worst thing to post a url or JSFIDDLE too...
I'm looking for a definitive list of HTML elements which are allowed to take focus, i.e. which elements will be put into focus when focus() is called on them?
I'm writing a jQuery extension which works on elements that can be brought into focus. I hope the answer to this question will allow me to be specific about the elements I target.
There isn't a definite list, it's up to the browser. The only standard we have is DOM Level 2 HTML, according to which the only elements that have a focus() method are
HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement and HTMLAnchorElement. This notably omits HTMLButtonElement and HTMLAreaElement.
Today's browsers define focus() on HTMLElement, but an element won't actually take focus unless it's one of:
HTMLAnchorElement/HTMLAreaElement with an href
HTMLInputElement/HTMLSelectElement/HTMLTextAreaElement/HTMLButtonElement but not with disabled (IE actually gives you an error if you try), and file uploads have unusual behaviour for security reasons
HTMLIFrameElement (though focusing it doesn't do anything useful). Other embedding elements also, maybe, I haven't tested them all.
Any element with a tabindex
There are likely to be other subtle exceptions and additions to this behaviour depending on browser.
Here I have a CSS-selector based on bobince's answer to select any focusable HTML element:
a[href]:not([tabindex='-1']),
area[href]:not([tabindex='-1']),
input:not([disabled]):not([tabindex='-1']),
select:not([disabled]):not([tabindex='-1']),
textarea:not([disabled]):not([tabindex='-1']),
button:not([disabled]):not([tabindex='-1']),
iframe:not([tabindex='-1']),
[tabindex]:not([tabindex='-1']),
[contentEditable=true]:not([tabindex='-1'])
{
/* your CSS for focusable elements goes here */
}
or a little more beautiful in SASS:
a[href],
area[href],
input:not([disabled]),
select:not([disabled]),
textarea:not([disabled]),
button:not([disabled]),
iframe,
[tabindex],
[contentEditable=true]
{
&:not([tabindex='-1'])
{
/* your SCSS for focusable elements goes here */
}
}
I've added it as an answer, because that was, what I was looking for, when Google redirected me to this Stackoverflow question.
EDIT: There is one more selector, which is focusable:
[contentEditable=true]
However, this is used very rarely.
$focusable:
'a[href]',
'area[href]',
'button',
'details',
'input',
'iframe',
'select',
'textarea',
// these are actually case sensitive but i'm not listing out all the possible variants
'[contentEditable=""]',
'[contentEditable="true"]',
'[contentEditable="TRUE"]',
'[tabindex]:not([tabindex^="-"])',
':not([disabled])';
I'm creating a SCSS list of all focusable elements and I thought this might help someone due to this question's Google rank.
A few things to note:
I changed :not([tabindex="-1"]) to :not([tabindex^="-"]) because it's perfectly plausible to generate -2 somehow. Better safe than sorry right?
Adding :not([tabindex^="-"]) to all the other focusable selectors is completely pointless. When using [tabindex]:not([tabindex^="-"]) it already includes all elements that you'd be negating with :not!
I included :not([disabled]) because disabled elements can never be focusable. So again it's useless to add it to every single element.
The ally.js accessibility library provides an unofficial, test-based list here:
https://allyjs.io/data-tables/focusable.html
(NB: Their page doesn't say how often tests were performed.)
Maybe this one can help:
function focus(el){
el.focus();
return el==document.activeElement;
}
return value: true = success, false = failed
Reff:
https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/activeElement
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
There is a much more elegant way to handle this:
Extend the element prototype like the sample below.
Then you can use it like:
element.isFocusable()
*Returns true if "element" is focusable and false if not.
/**
* Determining if an element can be focused on
* #return {Boolean}
*/
HTMLElement.prototype.isFocusable = function () {
var current = document.activeElement
if (current === this) return true
var protectEvent = (e) => e.stopImmediatePropagation()
this.addEventListener("focus", protectEvent, true)
this.addEventListener("blur", protectEvent, true)
this.focus({preventScroll:true})
var result = document.activeElement === this
this.blur()
if (current) current.focus({preventScroll:true})
this.removeEventListener("focus", protectEvent, true)
this.removeEventListener("blur", protectEvent, true)
return result
}
// A SIMPLE TEST
console.log(document.querySelector('a').isFocusable())
console.log(document.querySelector('a[href]').isFocusable())
<a>Not focusable</a>
Focusable