长期以来,我一直在考虑自定义JavaScript中典型的用户交互功能的外观-alert(),confirm()和提示符()(以下称为模式窗口)。确实,它们使用起来非常方便,但是在不同的浏览器中却有所不同,并且外观也很难看。终于,手伸了出来。问题是什么?发出对话框的常用方法(例如引导程序)不能像警报那样容易地使用,在警报中浏览器会安排JavaScript代码停止执行并等待用户的操作(单击关闭按钮)。引导程序中的模态将需要单独的事件处理-单击按钮,关闭模态窗口...尽管如此,我已经在游戏中使用警报的自定义功能将标准消息替换为与游戏设计风格相对应的消息。这很好,包括连接错误消息和其他系统情况。但是,如果用户需要等待响应,这将不起作用!
随着ECMAScript 6(ES6)中Promise的出现,一切皆有可能!我采用了将模式窗口和代码的设计分开的方法(警报(),确认()和提示())。但是您可以隐藏代码中的所有内容。吸引这种方法的原因是-可以在不同的项目中更改设计,但是可以仅在不同的页面上或根据情况进行更改。这种方法的坏点是需要在模式窗口的代码中甚至在全局范围内使用标记名称(id)。但这仅是原理的一个示例,因此我将不专注于此。获取警报代码
因此,让我们分析标记(字体图标的引导和Font Awesome)和警报代码(我使用jQuery): <div id="PromiseAlert" class="modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-exclamation-triangle text-warning"></i> <span>The app reports</span></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
window.alert = (message) => {
$('#PromiseAlert .modal-body p').html(message);
var PromiseAlert = $('#PromiseAlert').modal({
keyboard: false,
backdrop: 'static'
}).modal('show');
return new Promise(function (resolve, reject) {
PromiseAlert.on('hidden.bs.modal', resolve);
});
};
就像我上面说的,代码使用了全局名称PromiseAlert和html标记类。在代码的第一行中,警报功能参数传递到消息正文。之后,bootstrap方法将显示一个带有某些选项的模态窗口(它们使它更接近本机警报)。重要!模态窗口被记住在一个局部变量中,该变量在下面通过闭包使用。最后,它作为警报Promise的结果被创建并返回,在该警告中,由于关闭了模式窗口,因此该Promise被执行。现在让我们看看如何使用此警报: $('p a[href="#"]').on('click', async (e) => {
e.preventDefault();
await alert('Promise based alert sample');
});
在此示例中,单击段落中的空白链接时会显示一条消息。注意!为了符合规范,alert函数必须在关键字await之前,并且只能在函数内部与async关键字一起使用。这使您可以期望在该位置(脚本将停止,与本机警报一样),将关闭模式窗口。如果不这样做会怎样?取决于应用程序的逻辑(上图中的这种方法的示例)。如果这是代码的结尾,或者代码的进一步操作没有使页面超载,那么一切都会很好!模态窗口下垂,直到用户关闭它。但是,如果仍然存在模式窗口,或者如果页面重新加载,则转换到另一个页面,那么用户将根本看不到您的模式窗口,并且逻辑将被破坏。我可以说,根据经验,有关各种服务器错误(状态)的消息或来自代码库的消息与我们的新警报配合得很好,尽管它们不使用等待。我们开发一种确认方法
让我们走得更远。毫无疑问,confirm只能用于异步/等待绑定,因为 他必须告诉代码用户选择的结果。这也适用于提示。因此请确认: <div id="PromiseConfirm" class="modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-check-circle text-success"></i> <span>Confirm app request</span></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-dismiss="modal">OK</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
window.confirm = (message) => {
$('#PromiseConfirm .modal-body p').html(message);
var PromiseConfirm = $('#PromiseConfirm').modal({
keyboard: false,
backdrop: 'static'
}).modal('show');
let confirm = false;
$('#PromiseConfirm .btn-success').on('click', e => {
confirm = true;
});
return new Promise(function (resolve, reject) {
PromiseConfirm.on('hidden.bs.modal', (e) => {
resolve(confirm);
});
});
};
仅有一个区别-我们需要告知用户选择。这是使用闭包中的另一个局部变量完成的-确认。如果按下确认按钮,则变量将设置为true,默认情况下其值为false。好吧,当处理模式窗口的关闭时,resolve返回此变量。这是用法(异步/等待时需要): $('p a[href="#"]').on('click', async (e) => {
e.preventDefault();
if (await confirm('Want to test the Prompt?')) {
let prmpt = await prompt('Entered value:');
if (prmpt) await alert(`entered: «${prmpt}»`);
else await alert('Do not enter a value');
}
else await alert('Promise based alert sample');
});
继续前进-提示的方法
上面的逻辑也可以通过测试提示来实现。其标记和逻辑如下: <div id="PromisePrompt" class="modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-question-circle text-primary"></i> <span>Prompt request</span></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="PromisePromptInput"></label>
<input type="text" class="form-control" id="PromisePromptInput">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-dismiss="modal">OK</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
window.prompt = (message) => {
$('#PromisePrompt .modal-body label').html(message);
var PromisePrompt = $('#PromisePrompt').modal({
keyboard: false,
backdrop: 'static'
}).modal('show');
$('#PromisePromptInput').focus();
let prmpt = null;
$('#PromisePrompt .btn-success').on('click', e => {
prmpt = $('#PromisePrompt .modal-body input').val();
});
return new Promise(function (resolve, reject) {
PromisePrompt.on('hidden.bs.modal', (e) => {
resolve(prmpt);
});
});
};
逻辑和确认之间的差异很小。闭包中的另一个局部变量是prmpt。而且它没有逻辑值,而是用户输入的字符串。通过关闭,其价值得以解决。并且只有在按下确认按钮时(从输入字段)才为其分配一个值。顺便说一句,我在这里浪费了另一个全局变量PromisePromptInput,仅用于速记代码和替代代码。有了它,我就设置了输入焦点(尽管它可以用一种方法来完成,既可以用相同的方式,也可以用获取值的方式来完成)。您可以在这里尝试这种方法。该代码位于链接上。看起来像这样(尽管上面的链接更加多样化):
艾滋病
它们与本文的主题没有直接关系,但是它们揭示了该方法的全部灵活性。这包括引导主题。我在这里拍摄了免费的主题。根据浏览器语言使用自动安装切换语言。共有三种模式-自动(通过浏览器),俄语或英语(强制)。默认情况下已安装机器。Cookies(从这里开始)我曾经记住主题和语言切换。只需在上述站点上安装href css段即可切换主题: $('#themes a.dropdown-item').on('click', (e) => {
e.preventDefault();
$('#themes a.dropdown-item').removeClass('active');
e.currentTarget.classList.add('active');
var cur = e.currentTarget.getAttribute('href');
document.head.children[4].href = 'https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/' + cur + 'bootstrap.min.css';
var ed = new Date();
ed.setFullYear(ed.getFullYear() + 1);
setCookie('WebApplicationPromiseAlertTheme', cur, ed);
});
好吧,我记得在Cookies中可以在启动时进行恢复: var cookie = getCookie('WebApplicationPromiseAlertTheme');
if (cookie) {
$('#themes a.dropdown-item').removeClass('active');
$('#themes a.dropdown-item[href="' + cookie + '"]').addClass('active');
document.head.children[4].href = 'https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/' + cookie + 'bootstrap.min.css';
}
为了进行本地化,我使用了localization.json文件,在其中创建了英文键及其俄文值的字典。为了简单起见(尽管标记在某些地方变得更加复杂),我在翻译时只检查纯文本节点,而是从值中替换键。 var translate = () => {
$('#cultures .dropdown-toggle samp').text({ ru: ' ', en: ' English ' }[culture]);
if (culture == 'ru') {
let int;
if (localization) {
for (let el of document.all)
if (el.childElementCount == 0 && el.textContent) {
let text = localization[el.textContent];
if (text) el.textContent = text;
}
}
else int = setInterval(() => {
if (localization) {
translate();
clearInterval(int);
}
}, 100);
}
else location.reload();
};
if (culture == 'ru') translate();
因此在生产环境中做(在服务器上更好)几乎不是一件好事,但是在这里我可以在客户端上演示一切。我仅在从俄语更改为英语时才访问服务器-我只是重载了原始标记(location.reload)。后者是预期的,onbeforeunload中的消息是根据浏览器算法发出的,我们的确认不影响此操作。在代码的末尾,有此消息的注释版本-您可以在自己传输时尝试。