I am working on an extension of Ext.Button that enables the showing/hiding of a button's menu on mouseover/mouseout. It is working perfectly for the button's immediate child menu, however I am running into an issue with having it behave properly for any secondary/tertiary/ect menus.
Right now, when the user moves over am item in the top menu that contains a menu, it will open the menu and the user can move the cursor into it with no problems, everything will stay open. If the user then moves the cursor out of the secondary menu into open space, all menus will close which is correct as well. BUT, sometimes if a user moves into a secondary menu, and then back into its parent menu, all the menus will close, which isn't what should happen, at the very least that parent menu that the cursor is now over should remain open.
From my initial debugging it looks to be an issue with how the events are firing, and their timing. It appears that the mouseenter event for a parent menu does not fire when moving from a child menu back into the parent menu. And secondly it looks to me like the the menu mouseover event does not fire reliably enough or often enough for it to cancel the delayed hide task after a mouseleave event on a child menu has fired.
Demo of the issue: http://qs1724.pair.com/users/autod1nx/EMPLOYEE/BDAMI/hoverbutton/index.html
And here's the code, does anything fundamentally wrong with it stand out?
Ext.define('Ext.HoverButton', {
extend: 'Ext.Button',
alias: 'widget.hoverButton',
isOver: false,
hideDelay: 250,
showDelay: 200,
applyListeners: function(menu, cfg) {
Ext.apply(menu, cfg);
Ext.each(menu.items, function(item, idx, allItems) {
if(item.menu) this.applyListeners(item.menu, cfg);
}, this);
},
initComponent: function() {
var config = {},
menuConfig = {},
me = this;
me.delayedShowMenu = new Ext.util.DelayedTask(function() {
if(!me.isOver) return;
me.showMenu();
}, this);
me.delayedHideMenu = new Ext.util.DelayedTask(function() {
if(me.isOver) return;
me.hideMenu();
});
if(Ext.isDefined(this.initialConfig.menu)) {
config = {
listeners: {
mouseover: {
scope: me,
fn: function(b) {
me.isOver = true;
me.delayedShowMenu.delay(me.showDelay);
}
},
mouseout: {
scope: me,
fn: function(b) {
me.isOver = false;
me.delayedHideMenu.delay(me.hideDelay);
}
}
}
};
menuConfig = {
listeners: {
mouseover: {
scope: me,
fn: function(menu, item, e) {
me.delayedHideMenu.cancel();
}
},
mouseenter: {
scope: me,
fn: function(menu, e) {
me.delayedHideMenu.cancel();
}
},
mouseleave: {
scope: me,
fn: function(menu, e) {
me.delayedHideMenu.delay(me.hideDelay);
}
}
}
};
//apply mouseover/leave listeners to all submenus recursively
me.applyListeners(me.menu, menuConfig);
}
Ext.apply(me, Ext.apply(me.initialConfig, config));
Ext.HoverButton.superclass.initComponent.apply(me, arguments);
}
});
I found this one works, and more simple.
Ext.define('Ext.HoverButton', {
extend : 'Ext.Button',
alias : 'widget.hoverButton',
listeners : {
mouseover : function() {
this.showMenu();
},
menushow : function() {
this.mouseLeaveMonitor = this.menu.el.monitorMouseLeave(100, this.hideMenu, this);
},
destroy : function(combo) {
combo.menu.el.un(combo.mouseLeaveMonitor);
}
}
});
I've been doing something a bit similar and I've solved the problem after taking a peek at http://www.quirksmode.org/dom/events/mouseover.html
It seems that DOM's event order should be mouseover -> mouseenter -> mouseout -> mouseleave which means that sometimes the cancel() will be called before the delay() is set. To solve the problem I keep the last entered in a variable:
mouseenter: {
scope: me,
fn: function(menu, e) {
presentlyInside = menu; /* << */
me.delayedHideMenu.cancel();
}
},
mouseleave: {
scope: me,
fn: function(menu, e) {
if(presentlyInside==menu) /* << */
me.delayedHideMenu.delay(me.hideDelay);
}
}
Hope it helps!
Related
I have a modal window in Angular 4 that works fine but if the user clicks on the background / parent page the modal is closed.
I have found some solutions that suggest using backdrop='static' and keyboard=false when opening the modal but our modal uses a local Dialog class with a BehaviorSubject object so is opened using the .next method. I've also tried setting these attributes using div config but to no avail.
Therefore I'm looking for another solution, maybe using CSS or another setting / attribute that can be directly applied to the parent page or modal HTML.
See below for some of the relevant code.
dialog.component.ts:
constructor(private location: PlatformLocation,
private _dialog: DialogService,
private router: Router) { }
open() {
this.showDialog = true;
const body = document.body;
body.classList.add('cell-modal-open');
}
close() {
this.dialog = undefined;
}
private handleDialog(d: Dialog) {
if (!d) {
this.close();
} else if (d.template) {
if (this.showDialog) {
this.close();
}
this.dialog = d;
this.open();
}
}
ngOnInit() {
this.subscription = this
._dialog
.getDialog()
.subscribe({
next: (d) => { this.handleDialog(d); console.log('subscribed dialog') },
error: (err) => this.handleDialogError(err)
});
this.initialiseRoutingEventListeners();
}
dialog.service.ts
private d: Dialog = { template: null, size: DialogSizeEnum.XLarge };
private dialogSubject = new BehaviorSubject<Dialog>({ template: null, size: DialogSizeEnum.XLarge });
constructor() { }
showDialog(template: TemplateRef<any>, size = DialogSizeEnum.XLarge, requiresAction = false) {
Object.assign(this.d, { template: template, size: size, requiresAction: requiresAction });
if (this.d !== null) {
this.dialogSubject.next(this.d);
}
}
getDialog(): BehaviorSubject<Dialog> {
return this.dialogSubject;
}
clear() {
this.dialogSubject.next(null);
}
Any suggested approaches are welcome!
Added flag to the close() method and adding condition to only set to undefined if true (i.e. from a valid location).
I want to alter how an asyncCommand is being hit (currently from a button), so I would need to access the asyncCommand from code. I don't want to have to alter what this asyncCommand is doing, it is dealing with payment details.
I have tried Googling but I cant find anything, I am also new to KO.
This is what I'm trying to achieve:
Click on a button (a separate button with its own asyncCommand method
which checks a flag) The 'execute' will do the following:
If (flag) - show modal
modal has two options - Continue / Cancel
If continue - hit asyncCommand command for original button (card payment one).
If cancel - go back to form
If (!flag)
Hit asyncCommand command for original button (card payment one).
Can this be done?
Thanks in advance for any help.
Clare
This is what I have tried:
FIRST BUTTON
model.checkAddress = ko.asyncCommand({
execute: function (complete)
{
makePayment.execute();
if (data.shippingOutOfArea === true || (data.shippingOutOfArea === null && data.billingOutOfArea === true)) {
model.OutOfArea.show(true);
}
complete();
},
canExecute: function (isExecuting) {
return !isExecuting;
}
});
ORIGINAL BUTTON
model.makePayment = ko.asyncCommand({
execute: function (complete) {
}})
MODAL
model.OutOfArea = {
header: ko.observable("Out of area"),
template: "modalOutOfArea",
closeLabel: "Close",
primaryLabel: "Continue",
cancelLabel: "Change Address",
show: ko.observable(false), /* Set to true to show initially */
sending: ko.observable(false),
onClose: function ()
{
model.EditEmailModel.show(false);
},
onAction: function () {
makePayment.execute();
},
onCancel: function ()
{
model.EditEmailModel.show(false);
}
};
You will have two async commands actually for this scenario. One to open up the modal and another one for the modal.
Eg:
showPaymentPromptCmd = ko.asyncCommand({
execute: function(complete) {
if (modalRequired) {
showModal();
} else {
makePayement();
}
complete();
},
canExecute: function(isExecuting) {
return !isExecuting;
}
});
//Called by Continue button on your modal.
makePaymentCmd = ko.asyncCommand({
execute: function(complete) {
makePayement();
complete();
},
canExecute: function(isExecuting) {
return !isExecuting;
}
});
var
function makePayement() {
//some logic
}
Need help..Unable to iterate thru auto suggestions using up and down arrow keys on keyboard here is little code snippet
dojo.require("dojo.NodeList-manipulate");
dojo.require("dojo.NodeList-traverse");
dojo.ready(function () {
var div = dojo.query("#list-of-items");
console.log(dojo.byId("search").getBoundingClientRect());
dojo.connect(dojo.byId("search"), "onkeyup", function (evt) {
if (dojo.byId("search").value.trim() === "") {
dojo.forEach(div.query("li"), function (elm, i) {
dojo.style(elm, {
"display": "block"
});
});
dojo.style(dojo.query("#list-of-items")[0], {
"display": "none"
});
if(evt.keyCode == 40){
return;
}else if(evt.keyCode == 38){
return;
}
} else {
dojo.style(dojo.query("#list-of-items")[0], {
"display": "inline-block"
});
}
searchTable(this.value, evt);
});
function searchTable(inputVal, e) {
console.log(inputVal);
var list = dojo.query('#list-of-items');
dojo.forEach(list.query('li'), function (elm, i) {
var found = false;
var regExp = new RegExp(inputVal, 'i');
if (regExp.test(elm.innerText)) {
found = true;
if(i===0){
dojo.attr(elm, { className: "hlight" });
}
dojo.style(elm, {
"display": "block"
});
return false;
}
if (found == true) {
dojo.style(elm, {
"display": "block"
});
} else {
dojo.style(elm, {
"display": "none"
});
}
});
}
});
and also highlight auto suggest using this css class
.hlight{
background:#faae00;
font-weight:bold;
color:#fff;
}
Please see working Fiddle here
Thanks
The best thing to do is to keep an index that contains the highlighted value, then increment/decrease that index every time the up/down arrow is pressed.
You will also have to send that index with your searchTable() function so that it can add the .hlight class to the correct elements.
The hardest part is to correct that index when someone uses the up arrow when you're already on the first element (or the down arrow when you're on the last arrow). I solved that by adding a class .visible to the elements that are visible (in stead of just adding display: block or display: none), this way you can easily query all items that are visible.
I rewrote your code a bit, ending up with this. But still, my original question is still left, why don't you use the dijit/form/ComboBox or dijit/form/FilteringSelect? Dojo already has widgets that do this for you, you don't have to reinvent the wheel here (because it probably won't be as good).
Here's my function:
function confirmFamilyMemDelete()
{
$('#dialog').attr('title', 'Warning').text('Are you sure?').dialog({ buttons:
[{
text: 'Yes',
click: function ()
{
$('#MainContent_cph_btnConfirmDelete').click();
$(this).dialog('close');
alert('Hello');
}
},
{
text: 'No',
click: function ()
{
$(this).dialog('close');
}
}
]
});
return false;
}
I've got a very weird problem. In my aspx page there's a button that gets an id 'MainContent_cph_btnConfirmDelete' after it gets rendered. I want to click it if Yes button is clicked in the jQuery UI dialog. However, I fail to do it. It just skips over that command and alerts 'Hello'. This means the rest of the code inside my Yes button gets executed. And if I take
$('#MainContent_cph_btnConfirmDelete').click();
out and put it just before return false; the button gets clicked. Is this a know issue with jQuery because I can't think of any logical explanation. If so, what is the workaround?
Here is what I think you need:
function confirmFamilyMemDelete()
{
$('#dialog').attr('title', 'Warning').text('Are you sure?').dialog({ buttons:
{
"Yes": function ()
{
$('#MainContent_cph_btnConfirmDelete').trigger('click');
$(this).dialog('close');
alert('Hello');
},
"No": function ()
{
$(this).dialog('close');
}
}
});
return false;
}
I have a little problem with my sliding panels, I have a Page with 2 sliding panels (right and left). These panels have a 'slide button' and you can reduce panels by clicking on it.
I use cookies to record state of panel, so when you change page panels stay collapsed or extended. But it doesn't work very well, in fact the state is recorded for the page. If I change page, panel will extend (default position) but if I go back on the page it will disapear. Is it possible to ignore the path in the cookie and use a cookie for all website?
Jquery code :
$('#rightfold').click(function () {
if ($('.menudroite').is(':visible')) {
$('.menudroite').hide("slide", { direction: "right" }, 400);
$.cookie('rightfold', 'collapsed');
$('.triggerdroite').animate({ backgroundColor: "#B2C9D1" }, 1000);
$('#rightfold').animate({ color: "#000000" }, 1000);
}
else {
$('.menudroite').show("slide", { direction: "right" }, 400);
$.cookie('rightfold', 'extended');
$('.triggerdroite').animate({ backgroundColor: "#6c7a7f" }, 1000);
$('#rightfold').animate({ color: "#d9f4ff" }, 1000);
}
});
$('#leftfold').click(function () {
if ($('.menugauche').is(':visible')) {
$('.menugauche').hide("slide", { direction: "left" }, 400);
$.cookie('leftfold', 'collapsed');
$('.triggergauche').animate({ backgroundColor: "#B2C9D1" }, 1000);
$('#leftfold').animate({ color: "#000000" }, 1000);
}
else {
$('.menugauche').show("slide", { direction: "left" }, 400);
$.cookie('leftfold', 'extended');
$('.triggergauche').animate({ backgroundColor: "#6c7a7f" }, 1000);
$('#leftfold').animate({ color: "#d9f4ff" }, 1000);
}
});
// COOKIES
var leftfold = $.cookie('leftfold');
var rightfold = $.cookie('rightfold');
// Set the user's selection for the left column
if (leftfold == 'collapsed') {
$('.menugauche').css("display", "none");
};
// Set the user's selection for the right column
if (rightfold == 'collapsed') {
$('.menudroite').css("display", "none");
};
Thanks for responses..
If you're using the JQuery cookie plugin I'm thinking of, you can set the cookie to be available everywhere like this.
$.cookie('leftfold', 'extended', {path: '/'});
I'm not entirely certain, but I think you want to set the cookie domain to / so that it can be used across the entire domain.
I'm not sure which cookie plugin you're using, so I can't give you the specifics on how to do that.