I am trying to figure out a way to easily disable/enable buttons within dat.gui.
I have dat.gui set up to control an animation. When the animation reaches its end, I want the "Play" button to become disabled. I have tried adding a "disabled" attribute to the DOM elements of the button, but I am still seeing the corresponding function fire when the button is clicked after this attribute is set.
My current method is the following:
Locate the li element that corresponds to the button in the dat.gui interface
Create a new DOM element that is semi-transparent and black, and add this inside the li element to gray out the contents of the button.
In the function bound to this button, check for the existence of this "disabled" DOM element within the button, and if it exists, refrain from executing the rest of the function.
This is a hack, and I would love to know if there was some method for disabling a button built right into dat.gui, or some better method that I am not aware of.
In dat.GUI the FunctionController class is responsible for buttons. If you look at its source code, there is no conditional logic in there. The controller will listen to click events on the button and it will always call the function on click. This means that you won't get any help from the library here - you need to check in the handler whether the button is disabled. Something along these lines:
// Find the GUI controller listening to given property on given object
function getController(gui, object, property)
{
for (var i = 0; i < gui.__controllers.length; i++)
{
var controller = gui.__controllers[i];
if (controller.object == object && controller.property == property)
return controller;
}
return null;
}
...
object.button = function()
{
// Don't do anything if the button is disabled
if (getController(gui, this, "button").domElement.hasAttribute("disabled"))
return;
alert("Button clicked");
};
gui.add(object, "button");
...
// Disable button
getController(gui, object, "button").domElement.setAttribute("disabled", "");
Note that there is no special styling for disabled elements in dom.GUI, you would have to add you own styles for that. Given that what you see in case of a button is the property label rather than the actual button this isn't going to be quite trivial - I think you will have to place the disabled attribute on controller.domElement.parentNode rather than controller.domElement. Then you should be able to use the selector [disabled] > .property-name for your styles.
Edit: You can actually do this in a more generic way by extending FunctionController:
function blockEvent(event)
{
event.stopPropagation();
}
Object.defineProperty(dat.controllers.FunctionController.prototype, "disabled", {
get: function()
{
return this.domElement.hasAttribute("disabled");
},
set: function(value)
{
if (value)
{
this.domElement.setAttribute("disabled", "disabled");
this.domElement.addEventListener("click", blockEvent, true);
}
else
{
this.domElement.removeAttribute("disabled");
this.domElement.removeEventListener("click", blockEvent, true);
}
},
enumerable: true
});
This will add a property disabled to the controller that will catch click events so that the button handler isn't triggered. Disabling the button gets simpler then:
getController(gui, object, "button").disabled = true;
And the button handler can stay unchanged, it simply won't be triggered for disabled buttons.
Here the most straightforward way I could think of to disable a single element of dat GUI:
let gui = new dat.GUI();
let uiElement = gui.add(myObject, 'myPropertyName');
uiElement.__li.style = "opacity: 0.5; filter: grayscale(100%) blur(1px); pointer-events: none;";
Old browsers may not support pointer-events: none; so optionally you can add:
disableAll(uiElement.__li);
function disableAll(element){
for( var i = 0; i < element.childNodes.length; ++i){
let elt = element.childNodes[i];
disableAll(elt);
elt.disabled = true;
}
}
This may look "hacky" but in the official dat GUI API there is no such function and even if it was in there, it would most likely do something very similar.
Lastly, through the API you can entirely delete an element:
uiElement.remove();
Related
We have a tabulator column definition , where one of them is a button created by a formatter
{title:"input", field:"blank", width:30, frozen:true, responsive:0,formatter:customFormatter2}
Into formatter we create a button
var customFormatter2 = function (cell, formatterParams) {
var $button=$('<button>').text('Hola')
$button.click(function(){
$(cell.getElement()).trigger('contextmenu')
})
return $button.get(0);
}
Also we have a rowContextmenu created into tabulator.
I want call to menu that tabulator shows when we do right click in any row.
I tried call a trigger from cell,from row... and I dont know if the event is accessible ,or I dont know do it.
Thanks
I don't user jQuery often, but I believe the only thing missing is preventing the propagation of the click event after the contextmenu event, which hides the menu. Something like this should work, but I also had to add pageX and pageY to my custom event, so that Tabulator could calculate where to display the menu. I am not sure how I would do this in jQuery.
$button.click(function(event){
event.stopImmediatePropagation();
$(cell.getElement()).trigger('contextmenu');
});
Or without jQuery and definitely works,
function customFormatter(cell, formatterParams){
const button = document.createElement('button');
button.textContent = "Hola";
button.addEventListener('click', (event) => {
event.stopImmediatePropagation();
const myEvent = new Event('contextmenu');
myEvent.pageX = event.pageX;
myEvent.pageY = event.pageY;
cell.getRow().getElement().dispatchEvent(myEvent);
})
return button;
}
Here is a full example without jQuery.
https://jsfiddle.net/nrayburn/guxkw394/101/
Be careful with this. Because we are creating a custom event, it doesn't contain all of the normal properties that a real event would. If Tabulator starts relying on different event properties, it would break this code. (Maybe you could copy the original event from the click and pass those properties into the custom event. Not really sure how to do that.)
I have to hide same field on it's blur event.
Extjs 6 calls event delegation on component hide method.Event delegation revert focus to last field which had focus.
And, I don't want this revert focus. Is there any way I can stop event delegation while hiding elements in extjs ?
Event delegation comes with extjs 5 - Delegated Events and Gestures in Ext JS 5
Method using for hide - https://docs.sencha.com/extjs/6.0/6.0.1-classic/#!/api/Ext.Component-method-onHide
onHide() method from ExtJS source code - check revertFocus()
onHide: function(animateTarget, cb, scope) {
var me = this,
ghostPanel, fromSize, toBox;
if (!me.ariaStaticRoles[me.ariaRole]) {
me.ariaEl.dom.setAttribute('aria-hidden', true);
}
// Part of the Focusable mixin API.
// If we have focus now, move focus back to whatever had it before.
me.revertFocus(); // this revert focus making probelm
// Default to configured animate target if none passed
animateTarget = me.getAnimateTarget(animateTarget);
// Need to be able to ghost the Component
if (!me.ghost) {
animateTarget = null;
}
// If we're animating, kick off an animation of the ghost down to the target
if (animateTarget) {
toBox = {
x: animateTarget.getX(),
y: animateTarget.getY(),
width: animateTarget.dom.offsetWidth,
height: animateTarget.dom.offsetHeight
};
ghostPanel = me.ghost();
ghostPanel.el.stopAnimation();
fromSize = me.getSize();
ghostPanel.el.animate({
to: toBox,
listeners: {
afteranimate: function() {
delete ghostPanel.componentLayout.lastComponentSize;
ghostPanel.el.hide();
ghostPanel.setHiddenState(true);
ghostPanel.el.setSize(fromSize);
me.afterHide(cb, scope);
}
}
});
}
me.el.hide();
if (!animateTarget) {
me.afterHide(cb, scope);
}
},
You are doing it wrong, revertFocus() is a main problem source. The solution might be:
blurEventFunction:function(cmp){
cmp.previousFocus = null;
cmp.hide();
}
Use suspendEvents and resumeEvents in the function you are calling in the viewcontroller when the blur event fires:
It's not stopEvents is suspendEvents. My fault. :P
blurEventFunction:function(cmp){
cmp.suspendEvents();
cmp.hide();
camp.resumeEvents();
}
I got the same problem. (extjs 6.5.1 - using a modal window with closeAction: 'hide')
I was debugging the code and seems it happened because the latest field focused was in a panel and my modal window was not child of that panel.
(seems the extjs get the ancestor of the modal window to find the latest focused field, then, set the focus)
When I added the window to that panel, it worked fine. (when the modal window was closed, the focus was on the latest field focused before open the window).
Debugging the Ext.util.Focusable class, I saw a config called preventRefocus. If you add that config with value true to your modal window, the content of the revertFocus function won't be executed and you won't get the error.
revertFocus: function() {
var me = this,
focusEvent = me.focusEnterEvent,
activeElement = Ext.Element.getActiveElement(),
focusTarget, fromComponent, reverted;
// If we have a record of where focus arrived from,
// and have not been told to avoid refocusing,
// and we contain the activeElement.
// Then, before hiding, restore focus to what was focused before we were focused.
// --->>> THE IF BELOW: !me.preventRefocus <<<---
if (focusEvent && !me.preventRefocus && me.el.contains(activeElement)) {
I hope it also can help somebody in the future.
I'm trying to put a link on a selectize dropdown in order to allow the user make an operation other than select an item while still allowing that the user selects the item as main option.
Here is an example of what I want to achieve (but is not working as expected):
What I did is plainly insert links on the HTML. But it's not working, I suppose that for some kind of event propagation stop, is it possible to achieve with selectize?
Nobody did answer yet and I think there's more to say about, so, here is an example of what I did:
render: {
option: function(item) {
return '<div><span>'+item.label+'</span>'
+ '<div class="pull-right">'
+ 'Link'
+ '</div></div>';
}
}
As you can see, I did change the "option" renderization, and inserted a link in plain HTML. The problem is that -as shown on image- when I do click the link, the browser does not follow the link, but executes the default action for selectize, which is selecting the clicked element.
What I want to achieve is to make it follow the link when clicked.
Here is a fiddle of what I did: http://jsfiddle.net/uetpjpa9
The root problem is that Selectize has mousedown and blur handlers that are dismissing the dropdown before the mouseup event that would complete the click that your link is waiting for from ever occurring. Avoiding this without direct support from Selectize is not easy, but it is possible thanks to its plugin system and the amount of access it gives you to Selectize internals.
Here's a plugin that allows a dropdown element with the class clickable to be clicked on. (demo)
Selectize.define('option_click', function(options) {
var self = this;
var setup = self.setup;
this.setup = function() {
setup.apply(self, arguments);
var clicking = false;
// Detect click on a .clickable
self.$dropdown_content.on('mousedown click', function(e) {
if ($(e.target).hasClass('clickable')) {
if (e.type === 'mousedown') {
clicking = true;
self.isFocused = false; // awful hack to defuse the document mousedown listener
} else {
self.isFocused = true;
setTimeout(function() {
clicking = false; // wait until blur has been preempted
});
}
} else { // cleanup in case user right-clicked or dragged off the element
clicking = false;
self.isFocused = true;
}
});
// Intercept default handlers
self.$dropdown.off('mousedown click', '[data-selectable]').on('mousedown click', '[data-selectable]', function() {
if (!clicking) {
return self.onOptionSelect.apply(self, arguments);
}
});
self.$control_input.off('blur').on('blur', function() {
if (!clicking) {
return self.onBlur.apply(self, arguments);
}
});
}
});
To use it, you need to pass the plugin option to the selectize call (.selectize({plugins:['option_click']})) and add the clickable class to links in your dropdown template. (This is fairly specific. If there are nested elements, make sure clickable is on the one that will first see the mousedown event.)
Note that this is a fairly hackish approach that may have edge cases and could break at any time if something about how Selectize dispatches events changes. It would be better if Selectize itself would make this exception, but until the project catches up to its backlog and becomes more receptive to requests and PRs this may be the most practical approach.
code:
https://dl.dropboxusercontent.com/u/16952797/webdev/uppg1/kontakt.html
http://jsfiddle.net/v8uMJ/ (the result box does not succesfully render the page or reproduce the bug)
relevant code:
function addEvent(element, eventType, theFunction, capture)
{
if(element.addEventListener)
{
element.addEventListener(eventType, theFunction, capture);
}
else if(element.attachEvent)
{
element.attachEvent( "on" + eventType, theFunction);
}
}
function removeDefaultOption(event)
{
document.getElementById("selectSuggestion").options[0].style.display = "none"; // <-- looks awkward
//document.getElementById("selectSuggestion").remove(0); // <-- also looks awkward and needs boundaries so that all options don't get removed after each click
//delay(1000); // <-- tried delaying the thread but it didn't work..
//setInterval(function(){},1000); // <-- tried delaying the thread but it didn't work..
}
function addEventListeners()
{
...
addEvent(document.getElementById("selectSuggestion"), "click", removeDefaultOption, false);
}
context: So what I'm trying to do is whenever you click on the select element in the suggestion form (swe: Förslag) I want the first option (value: --Välj Förslag--) to disappear from the list. The problem is that the options are displayed "too fast" so I either need to a) delay the displaying of the options after setting the style.display of the first option or b) I need to prevent the default event of clicking on select and then override it with my own function so I can control when it should be run (I don't know the name of the native function that is run when you click on the select element).
Use the "focus" event instead of the "click" event. This fires the function direclty when it's in focus instead of "waiting" for the click to be completed.
Cleaned JS-fiddle: http://jsfiddle.net/vhS3p/1/
document.getElementById("selectSuggestion").addEventListener("focus", removeDefaultOption, false);
Try removing the option you want like so:
var index = 0;
var select = document.getElementById("selectSuggestion");
select.removeChild(select[index]);
I'm disabling groups of radio buttons when a user clicks a checkbox, just using raw javascript and the disabled property.
My function is trivial:
function toggleEnabled(elementId) {
e = document.getElementById(elementId)
e.disabled = !e.disabled;
}
and it is called with the onClick event like onClick="toggleEnabled('radio_div')"
It works great, but if the user clicks back, the browser seems to remember the state of the checkbox, but resets the state of the components in the div to whatever they originally were.
This is in IE7, and I do not want to use a JS library right now so please no suggestions to that effect.
Am I doing something wrong? Is there a solution to get the intended behaviour (remember the state of both the checkbox and the div on Back)?
You have to check each radio box value within your onload method:
var selects = document.getElementByTagName("select");
for ( var i = 0 ; i < selects.length ; i ++ )
{
//call your code if an option is checked
if ( selects.options[selects.selectedIndex] )
{
var selectedOption = selects.options[selects.selectedIndex];
//now you got your option and can enable the div.
toggleEnabled('radio_div');// <-- change this depending on selectedOption
}
}