您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 1浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

我对这个页面做了什么?

寒冰屋 发布时间:2019-04-12 21:43:29 ,浏览量:1

目录

介绍

背景

开发代码

辅助功能问题

兴趣点

介绍

按照说明将jQuery自动完成小部件实现到ASP.NET页面并不困难。但是,如果使用partial将多个自动完成小部件添加到页面中,则需要执行一些额外的工作。当必须使用Web可访问性时也是如此。

背景

它是一个ASP.NET Core MVC Razor视图页面。

最初,Selectize将四个文本输入框转换为选择列表框。其中两个是经理用的,因为项目的数量不多,所以选择框是可以的。另外两个是针对成百上千的员工,而选择框并不是一个好的参与者。

开发代码

以下是作为MVC部分页面的自动完成小部件的第一个版本。

_AutoComplete.cshtml:

@using System.Web
@{
    Layout = null;
}

@* The reason to change the jquery built-in autocomplete styles is that the page is Bootstrapped *@

    .ui-autocomplete {
        position: absolute;
        z-index: 1000;
        cursor: pointer;
        padding: 0;
        margin-top: 2px;
        list-style: none;
        background-color: #ffffff;
        border: 1px solid #ccc;
        -webkit-border-radius: 5px;
        -moz-border-radius: 5px;
        border-radius: 5px;
        -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
        -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
        box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
    }

        .ui-autocomplete > li {
            padding: 3px 20px;
        }

            .ui-autocomplete > li.ui-state-focus {
                background-color: #DDD;
            }

    .ui-helper-hidden-accessible {
        display: none;
    }

    .fixed-height {
        padding: 3px 10px;
        max-height: 200px;
        max-width: 18%;
        overflow: auto;
        background-color: #ecf0f1;
        cursor: pointer;
    }

    div.widgetChrome {
        background-color: #f2fbfa;
        padding: 5px;
    }



    var acTarget = $('#' + '@Html.Raw(ViewData["TargetId"])');
    var acTargetHiddenValueElement = $('#' + '@Html.Raw(ViewData["TargetHiddenValueElementId"])');
    var source = JSON.parse('@Html.Raw
                 (HttpUtility.JavaScriptStringEncode(ViewData["Source"]?.ToString()))');
    var ajaxUrl = '@Html.Raw(ViewData["AjaxUrl"])';
    var valueIs = '@Html.Raw(ViewData["ValueIs"] ?? "Number")';

    if (acTarget.parent().find('img').length == 0) {
        acTarget.parent().append($("",
        {
            src: "/images/waiting-blue.gif",
            style: "margin-left:90%; margin-top:-60px; width:24px; height:24px"
        }));
    }
    acTarget.autocomplete({
        source: (function(){
            if (source) {
                return source;
            } else if (ajaxUrl) {
                return function (request, response) {
                    $.ajax({
                        url: ajaxUrl,
                        type: 'GET',
                        data: request,
                        dataType: "json",

                        success: function (data) {
                            response($.map(data, function (item) {
                                acTargetHiddenValueElement.val('');
                                if (item.Text == 'ERROR') {
                                    return {
                                        label: '',
                                        value: valueIs == 'Number' ? -1 : ''
                                    }
                                } else {
                                    $('.ui-autocomplete ui-menu-item a').css('color', '#1578b1');
                                    /*in case user just types in the name without using 
                                      the drop down list to select.*/
                                    if ($.trim(acTarget.val()).toString().toLowerCase() == 
                                                    $.trim(item.Text).toString().toLowerCase()) {
                                        acTargetHiddenValueElement.val(item.Value);
                                    }
                                    return {
                                        label: item.Text,
                                        value: item.Value
                                    }
                                }
                            }));
                        },

                        error: function (xhr, status, errorThrown) {
                            alert(errorThrown);
                        },

                        complete: function (xhr, status) {
                            acTarget.parent()
                                .find("img")
                                .fadeOut(400, function () {
                                    acTarget.parent().find("img").remove();
                                });
                        }
                    });
                }
            }
        })(),
        select: function(event, ui){
            event.preventDefault();
            if (ui.item.value == -1 || !ui.item.value) {
                return false;
            } else {
                acTargetHiddenValueElement.val(ui.item.value);
                acTarget.val(ui.item.label);
            }
        },
        focus: function(event, ui){
            event.preventDefault();
            acTargetHiddenValueElement.val(ui.item.value);
            acTarget.val(ui.item.label);
        },
        open: function(){
            $(this).autocomplete("widget").css({
                "width": ($(this).width() + "px")
            });
        }
    }).autocomplete("widget").addClass("fixed-height");

在主机页面中,可以像这样调用partial:




@await Html.PartialAsync("~/Views/Partials/_AutoCompletion.cshtml", new ViewDataDictionary(ViewData) {
   { "TargetId", "ManagerEmail" },
   { "TargetHiddenValueElementId", "ManagerContactId" },
   { "Source", JsonConvert.SerializeObject(Model.Contacts.Select
                             (x => new { value = x.Id, label = x.Name })) },
   // Or replace "Source" with 
   // { "AjaxUrl", Url.Action("GetByEmail", "Contact") }, 
   // for ajax call when data is bigger.
   { "ValueType", "String" }
})

如果源数据是通过 ViewData被传递给partial的,则partial将使用浏览器本地数据。如果没有,但是使用 AjaxUrl,将触发Ajax调用以从控制器操作或Web服务中提取数据。因此它适用于本地和远程数据。

但是如果z-index值不够大,菜单选项就会出现问题。在我的例子中,其中一个小部件在bootstrap modal 对话框中使用。这是截图:

要解决此问题,我们应该为其z-index设置一个大数字。它应该大于对话框的z-index。稍后会对此进行另一次讨论。这是我们应该通过jQuery UI复制此窗口小部件所需的样式的一个原因。

另一个原因是,由于我们使用其他库,如Bootstrap和jqGrid,因此可能会覆盖所需的CSS,因此窗口小部件可能没有预期的外观。

如果只有一个自动完成小部件,则此部分工作。如上所述,总共有4个。令我惊讶的是,一旦我应用了所有小部件,当我输入一个小部件时,菜单出现在另一个小部件下方。经过调查,我发现了一个问题。下面是第二个版本,它看起来像一个封装的部分,因此多个实例不会引起冲突。

_AutoComplete.cshtml 版本。2:

@using System.Web
@{
    Layout = null;
}

@{
    @* The reason to change the jquery built-in autocomplete styles is that the page is Bootstrapped *@
    var styles = @"

.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
z-index: {{maxZ}} !important;
/* The rest is same with Ver. 1 */
}
";
}

    (function(acTarget, acTargetHiddenValueElement, acSource, acAjaxUrl, acDataValueType) {
        var styleAdded = false;
        $.each($('head style'), function () {
            if (this.innerText.indexOf('.ui-autocomplete') > -1) {
                styleAdded = true;
                return false;
            }
        });
        if (!styleAdded) {
            // this comes from https://blog.csdn.net/butterfly5211314/article/details/79488229
            var maxZ = Math.max.apply(null, $.map($('body *'), function (e, n) {
                if ($(e).css('position') != 'static') return parseInt($(e).css('z-index')) || -1;
            }));
            $('head').append('@Html.Raw(styles.Replace(Environment.NewLine, ""))'.replace
                            ('{{maxZ}}', maxZ));
        }

        acTarget.autocomplete({
            // all the event handlers are the same as in version 1. omitted for brevity.
        });
    })(
        $('#' + '@Html.Raw(ViewData["TargetId"])'),
        $('#' + '@Html.Raw(ViewData["TargetHiddenValueElementId"])'),
        '@ViewData["Source"]' ? JSON.parse('@Html.Raw(HttpUtility.JavaScriptStringEncode
                                          (ViewData["Source"]?.ToString()))') : '',
        '@Html.Raw(ViewData["AjaxUrl"])',
        '@Html.Raw(ViewData["ValueType"] ?? "Number")'
    );

此版本适用于同一页面上的任意数量的小部件。此外,有两点值得一提:

  1. 窗口小部件所需的样式只添加一次而不重复。
  2. z-index值可以用非常大的数字进行硬编码999999。但是,我喜欢使用所列的计算方法。
辅助功能问题

我使用WAVE插件来评估Web可访问性。令人惊讶的是,有多种表格标签错误,乍一看似乎令人难以置信。

为什么?这是因为使用相同模型呈现的分部(不是自动完成窗口小部件,但是接触部分)的多个实例。例如,要添加和更新经理联系人,两个呈现的部分使用相同的管理器联系模型。员工联系人部分也是如此。

为了解决这个问题,我们需要在页面上绑定/呈现的相同模型之间区分表单标签的属性和字段id值。

直观地,人们可能倾向于编写JavaScript代码来更改属性的标签和相应的表单控件ID。它应该可以工作,但我没有尝试这种方法,因为我们可以利用ASP.NET Core MVC强大的标记帮助程序来编写更清晰,更高效的代码。

我就是这样做的。

标记助手:标签标记助手和输入标记助手。

[HtmlTargetElement("label", Attributes = "asp-for")]
public class ResolveMultipleAriaLabelsLabelTagHelper : LabelTagHelper
{
    public ResolveMultipleAriaLabelsLabelTagHelper(IHtmlGenerator generator) : base(generator)
    {
    }

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var viewData = ViewContext.ViewData;
        if (viewData["IdPrefixForMultipleAriaLabels"] != null)
        {
            var forAttribute = output.Attributes.FirstOrDefault
                               (attribute => attribute.Name.ToLower() == "for");
            if (forAttribute != null)
            {
                CreateOrMergeAttribute(string.Format("{0}_{1}", 
                     viewData["IdPrefixForMultipleAriaLabels"], forAttribute.Value), output);
            }
        }
        var hiddenAriaAttr = output.Attributes.FirstOrDefault
                      (attribute => attribute.Name.ToLower() == "aria-hidden");
        if (hiddenAriaAttr != null && hiddenAriaAttr.Value.ToString() == "true")
        {
            output.TagName = "div";
            #region keep the look and feel by design as much as possible
            output.Attributes.Add("class", "label-p");
            output.Attributes.Add("style", "cursor:pointer");
            #endregion
        }

        return base.ProcessAsync(context, output);
    }
    
    private void CreateOrMergeAttribute(string forName, TagHelperOutput output)
    {
        if (string.IsNullOrEmpty(forName)) return;
        var attribute = new TagHelperAttribute("for", forName);
        output.Attributes.SetAttribute(attribute);
    }
}
[HtmlTargetElement("input", Attributes = "asp-for")]
public class ResolveMultipleAriaLabelsInputTagHelper : InputTagHelper
{
    public ResolveMultipleAriaLabelsInputTagHelper(IHtmlGenerator generator) : base(generator)
    {
    }

    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var viewData = ViewContext.ViewData;
        if (viewData["IdPrefixForMultipleAriaLabels"] != null)
        {
            var nameAttribute = output.Attributes.FirstOrDefault
                  (attribute => attribute.Name.ToLower() == "name");
            CreateOrMergeAttribute(string.Format("{0}_{1}", 
                  viewData["IdPrefixForMultipleAriaLabels"], nameAttribute.Value), output);
        }
        return base.ProcessAsync(context, output);
    }

    private void CreateOrMergeAttribute(string id, TagHelperOutput output)
    {
        if (string.IsNullOrEmpty(id)) return;
        var attribute = new TagHelperAttribute("Id", id);
        output.Attributes.SetAttribute(attribute);
    }
}

背后的想法是在必要时更改标签的属性和输入ID,以便它们可以一对一匹配。

如果我们希望它以可分辨的label-for和input-id呈现,请按以下方式调用分部:

@await Html.PartialAsync("~/Views/Partials/_AddContact.cshtml", 
        new Contact { CanAssociateAccount = false }, new ViewDataDictionary(ViewData) 
        { { "IdPrefixForMultipleAriaLabels", "Manager" } })

IdPrefixForMultipleAriaLabels 变量由ViewContext中携带的ViewData传递到标记助手中。然后可以将Contact字段呈现为以下内容:

First Name

然后,标签的for属性仅与前缀为“ Manager_” 的输入id匹配。应用此功能后,WAVE将永远不会抱怨Contact部分中单个输入的多个标签。虽然辅助工具可能没有多个标签的问题,但项目所有者会对修复感到满意。

兴趣点
  1. 使用JavaScript函数在一个页面上隔离相同的部分
  2. jQuery自动完成小部件,用于使用本地或远程数据,具体取决于数据大小
  3. 使用ASP.NET Core MVC标记帮助程序来解决可访问性问题
  4. 用户数据从视图页面传递到标记助手以执行有条件的工作

 

原文地址:https://www.codeproject.com/Articles/1368309/What-Did-I-Do-with-this-Page

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0466s