With the impending removal of the showModalDialog API from various browsers, our company like many others who provide large scale enterprise web applications are now faced with a significant dilemma.
Whilst we have centralised the calls to showModalDialog down to 3 lines of code, we extensively rely on this code to provide feedback from modal user prompts (a quick search of the solution reveals around 2400 instances).
We could rip out showModalDialog fairly easily and replace it with a Javascript/css based alternative, that's not a problem. The issue we face is that all of the calling code will no longer be blocking e.g.
if(doConfirm(...)) {
...
} else {
...
}
The above will simply fall through due to the introduction of a non-blocking alternative. We also cannot use the in-built blocking methods (alert, confirm) as the dialog buttons are customised in many cases and are also styled to fit in with our application.
Based on the above, are there any pragmatic workarounds/solutions that could be employed to avoid having to re-factor so much legacy previously blocking code?
You can avoid using callback functions by using my showModalDialog polyfill, which pauses execution of subsequent statements until the modal is closed. It does so by using generators, promises and the yield keyword. It works in the newest Opera and Google Chrome.
You won't get around using asynchronous, event-based code.
pragmatic workarounds to avoid having to re-factor the code manually?
You can try a javascript-to-javascript compiler that brings the await keyword to js. It should automatically transpile your code to an asynchronous version.
Disclaimer: I haven't used any of these
In jQuery's 1.11.4 version, there is a built-in <dialog> you can use, which also allows for capturing a callback parameter on close.
$("#dialog").dialog({
autoOpen: false,
width: 500,
height: 500,
modal: true
buttons: [
{
text: "Ok",
click: function () {
$(this).dialog("close");
}
},
{
text: "Cancel",
click: function () {
$(this).dialog("close");
}
}
]
});
Your value could be captured in the callback functions from the button click events.
You could even add HTML to append your own 'x' button to the existing "Close" button and hide the original, so you could do whatever you wanted:
$(document).ready(function () {
var closeHtml = '×';
$("button[title='Close']").css('display', 'none').parent().append(closeHtml);
});
then attach a click event to the dialog-close ID from the 'x' button:
var url = 'https://www.cnn.com';
// Link to open the dialog
$("#dialog-link").click(function (event) {
var dialog = $("#dialog");
dialog.dialog("open");
dialog.html('<iframe id="dialog-body" scrolling="yes" src="' + url + '" style="border: 0; width: 100%; height: 100%;"></iframe>');
$("#dialog-close").on('click', function (e) {
// ...do whatever you want here, then...
$("button[title='Close']").click();
//e.preventDefault();
//dialog.close();
});
event.preventDefault();
});
Only caveat, since this uses IFrame, it might not work if the security on the site prevents bringing in external sites to your own site, if you are using it in this way. Google, for example, prevents this use with their site.
This should be a cross-platform example - I've tested it in IE 11. "Polyfill", that I've seen others say is another way to do this, is NOT, and does NOT work in IE because it relies on Promise, which is not supported in IE, as it shows at the bottom of this page: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Related
hey guys i am very new to js and jquery in genenral and i was just going throught the plugin code of a gallery plugin , i can across the function called _loadevents , that had the following content , see below :
this.$navPrev.on('click.gallery', function (event) {
});
this.$navNext.on('click.gallery', function (event) {
});
this.$wrapper.on('webkitTransitionEnd.gallery transitionend.gallery OTransitionEnd.gallery', function (event) {
});
now $navPrev , $navNext , and $wrapper are obviously some HTML element , now my question is about another method i came across in the same plugin , look below :
destroy: function () {
// console.log('inside destroy');
this.$navPrev.off('.gallery');
this.$navNext.off('.gallery');
this.$wrapper.off('.gallery');
}
now i see that if this function is called all the event handlers will be taken off. now , can somebody tell me what is the necessacity of such a function , does it improve a plugins efficiency ? how or when does such a function get used and is it a common practice to write e destroy function for events in plugins ?
Thank you.
Alex-z .
Destroy functions in plugins enable a developer to reset or remove a plugin from an element, restoring the element to before the plugin was initialised. This is useful if, for example, you have a gallery plugin that works and looks fantastic on desktop, but you don't want it on mobile. You can listen to resize event on window and if the window size is smaller than e.g. 710px then destroy the plugin. This will remove all the added events, undo any DOM manipulation, and restore the html elements back to how they were before the plugin was first initialised (turn-wise, if the window size is larger than 710px then initialise the plugin).
They are generally considered good practice.
I have the following situation on a web application.
A dialog is built and opened on the fly when clicking on a link:
var dialogInitiator = $("<div id='dialog-initiator' style='background-color: "+bgcolor+";'>Loading...</div>");
dialogInitiator.appendTo(parentFrame);
dialogInitiator.dialog({
modal:true,
autoOpen: false
}).on('keydown', function(evt) {
if (evt.keyCode === $.ui.keyCode.ESCAPE) {
dialogInitiator.dialog('close');
}
evt.stopPropagation();
});
dialogInitiator.dialog('open');
Right after that, I load a new html page into the dialog, with an < object >, like this:
var objectFrame = $('<object style="border: 0;width:'+(dlwidth-30)+'px;min-height:'+(dlheight-46)+'px;" type="text/html" style="overflow:auto;" data="'+url+'"></object>');
dialogInitiator.html(objectFrame);
Now, the "url" variable contains a link to this new html document. When that page is ready, it will focus on an input field. This prevents the ESCAPE key from closing the dialog. So, I am trying to manually trigger the .dialog('close') event on escape keypress.
I do the following, from within the loaded document:
$('#dialog-initiator', window.parent.document).dialog('close');
It get the following error:
"Error: cannot call methods on dialog prior to initialization; attempted to call method 'close'"
Strange thing is, when i call:
console.log( $('#dialog-initiator', window.parent.document).html() );
it shows me the html from the dialog. So it actually IS initiated.
Well, I have tried to fix this error with the help of Google, but without success. I guess my situation is quite specific.
Note: we are forced to use the technique with loading this whole webpage into the dialog due to older functionality we used in modalDialogs. Since they are depricated in the latest Google Chrome, we've built a temporary solution with jQuery dialog.
I hope someone can help me out. I appreciate it!
You can try a global method created after the modal is created
dialogInitiator.dialog({
modal: true,
autoOpen: false,
create: funtion(e,ui) {
window.closeMyDialog = function() {
$('#dialog-initiator').dialog('close');
};
}
})...
Then call it by doing window.parent.closeMyDialog();.
Why not use JQuery UI? It's easier than making your own.
http://jqueryui.com/dialog/#default
From searching the internet and StackOF, I see that my question has been asked before or at least some variation of it; but I cannot identify any real solutions.
I have acquired some code that utilizes div tags to produce a modal popup window. While this seems to work adequately aesthetically, there is a small problem of acquiring and retaining focus on the popup that would allow the user to use the tab key to navigate to screen.
function PopUp() {
$('#<%= divPopUp.ClientID%>').modal(
{
overlayCss: {
backgroundColor: '#000'
},
onShow: function (d) { d.container.css({ position: 'absolute', top: '10px' }); }
});
window.location.hash = 'SubmitButton';
}
The div tag refers to a table element that contains some labels and a couple of asp LinkButtons. The function is called from the Server-side using the ScriptManager component to Register and display the popup.
So far I've tried to set the focus from the Server side code and also made a few attempts based on others suggestions from the Client side but nothing gives.
Is there anyone here who has struggled with this or a very similar issue that wouldn't mind sharing a solution? Or is this expected behavior under the circumstances that can only be circumvented via alternative methods?
I've included VB.Net as a tag on this post because that's the Server code language.
Thanks
JS
Inside the modal open or complete function , add this line
function PopUp() {
$('#<%= divPopUp.ClientID%>').modal(
{
focus:true,
overlayCss: {
backgroundColor: '#000'
},
.....//All other stuff
});
}
OR
Use this in onshow function
$('#<%= divPopUp.ClientID%>').focus();
I want to show a popup many on click. I want that many to be in a bubble. So I created a demo: here. But that Bubble generator plugin i use tends to keep tons of trash in the DOM each time it shows a popup. Well so I tried to destroy trash via
$('.grumble-text').remove();
$('.grumble').remove();
$('.grumble-button').remove();
But it somehow brakes it at all=( So how to change grumble-bubble popup plugin code to make it either keep DOM clean or at least make plugin independent of trash it creates?
I've recently updated the plugin to provide better control of positioning and angle. The update also persists the grumble, invoking the plugin more than once on an element will not create extra left over DOM.
Try updating to the latest code. The code below should now work as you expect.
var html = ''
+'Download me'
+'<br/>'
+'Edit me'
+'<br/>'
+'Delete me';
var $grumble = $('#grumble3');
$grumble.mouseup(function(eventObj) {
$grumble.grumble({
text: html ,
angle: (Math.random() * 360 + 150),
distance: 30,
hideOnClick: true,
onShow: function() {
$grumble.addClass("hilight");
},
onBeginHide: function() {
$grumble.removeClass("hilight");
}
});
}).mousedown(function() {
$grumble.addClass("hilight");
});
Thanks for your interest. If there are any further problems please raise them as bugs on the github page. https://github.com/jamescryer/grumble.js
Use the grumble and button parameters on the onHide callback like this:
$('#grumble').grumble({
text: 'Whoaaa, this is a lot of text that i couldn\'t predict',
angle: 85,
distance: 50,
showAfter: 4000,
hideAfter: 2000,
onHide: function(grumble, button) {
grumble.bubble.remove();
grumble.text.remove();
button && button.remove();
}
});
This allows you to remove only the "trash" (I prefer "leftovers") associated with that specific tooltip/popup/bubble. Note that button only exists if hasHideButton is true, hence the button && existence check.
Why do you want to remove it? Is the 'trash' causing problems with browser performance?
In general, the only way to do this is to dig into the plugin source and add a function to remove the plugin, if one is not already present. If you just remove the related DOM elements you will leave behind references to them and events handlers that access them.
I'm using ExtJS 3.2.1 and I need a component almost identical to the bundled HtmlEditor, with one exception: it must start editing the HTML source code directly. The reason I don't use a normal TextArea is that the user should be able to preview the result of his actions before submitting.
I've tried calling toggleSourceEdit(), as per ExtJS documentation, with no success. Debugging, I see that the editor object has the sourceEditMode property set to true, and the Source Edit button seems as if it was "pressed", but clicking on it does not render the typed HTML, and clicking it again goes to the Source Mode.
I've tried calling toggleSourceEdit() after the container show() method, on the container afterLayout listener and on the editor afterRender listener. I've tried also calling it on another button that I added to the container. The result is the same on every try.
The only other option I see is updating ExtJS to 3.3.0, but I haven't seem anything related on the changelogs. Either way, it's going to be my next step. EDIT: The app had another problems when updating, we'll make a bigger effort to update later. As of right now, we are using the HtmlEditor in its original setting.
Thanks!
ran into the same problem (using 3.3.0 by the way)
stumbled upon a fix by dumb luck. i have no idea why this works, but second time is the charm. call it twice in a row to achieve the desired effect..
HTMLEditor.toggleSourceEdit(true);
HTMLEditor.toggleSourceEdit(true);
hope that helps!
Rather calling toggleSourceEdit(), try to setup the configuration while you create HtmlEditor Object
Using toggleSourceEdit() caused some problems for me. One was that this seemed to put the editor somewhere in limbo between source edit and WYSIWYG mode unless I used a timeout of 250ms or so. It also puts the focus in that editor, and I don't want to start the form's focus in the editor, especially since it's below the fold and the browser scrolls to the focused html editor when it opens.
The only thing that worked for me was to extend Ext.form.HtmlEditor and then overwrite toggleSourceEdit, removing the focus command. Then adding a listener for toggling to the source editor when the component is initialized. This is for Ext 4.1 and up. For older versions, replace me.updateLayout() with me.doComponentLayout().
var Namespace = {
SourceEditor: Ext.define('Namespace.SourceEditor', {
extend: 'Ext.form.HtmlEditor',
alias: 'widget.sourceeditor',
initComponent: function() {
this.callParent(arguments);
},
toggleSourceEdit: function (sourceEditMode) {
var me = this,
iframe = me.iframeEl,
textarea = me.textareaEl,
hiddenCls = Ext.baseCSSPrefix + 'hidden',
btn = me.getToolbar().getComponent('sourceedit');
if (!Ext.isBoolean(sourceEditMode)) {
sourceEditMode = !me.sourceEditMode;
}
me.sourceEditMode = sourceEditMode;
if (btn.pressed !== sourceEditMode) {
btn.toggle(sourceEditMode);
}
if (sourceEditMode) {
me.disableItems(true);
me.syncValue();
iframe.addCls(hiddenCls);
textarea.removeCls(hiddenCls);
textarea.dom.removeAttribute('tabindex');
//textarea.focus();
me.inputEl = textarea;
} else {
if (me.initialized) {
me.disableItems(me.readOnly);
}
me.pushValue();
iframe.removeCls(hiddenCls);
textarea.addCls(hiddenCls);
textarea.dom.setAttribute('tabindex', -1);
me.deferFocus();
me.inputEl = iframe;
}
me.fireEvent('editmodechange', me, sourceEditMode);
me.updateLayout();
}
})
}
Then to use it:
Ext.create('Namespace.SourceEditor', {
/*regular options*/
listeners: {
initialize: function(thisEditor) {
thisEditor.toggleSourceEdit();
}
}
});
htmlEditor.toggleSourceEdit(true);
one time should be enough if you do this listening to the afterrender event of the editor.