尝试让AJAX自动填充功能处理动态创建的订单项

时间:2018-02-12 03:26:26

标签: jquery ajax asp.net-mvc dynamic autocomplete

我有一个MVC项目,我正在尝试动态地将订单项添加到订单/发票中。我希望能够在每一行的服务类型字段上执行自动完成,因此当您开始键入时,服务将从数据库查询中提取,并将名称返回到文本字段,并将ID返回到隐藏字段订单项。

我遇到的问题我无法弄清楚如何访问动态创建的字段。

首先,这是我创建订单项的方式(从我的视图中调用AJAX到控制器中的一个函数,在我的主视图中呈现局部视图):

我有一个< ul> group(Create.cshtml):

<div class="form-group">
    <ul id="ServiceOrderLineEditor" style="list-style-type: none">
    </ul>
    <p><a id="addAnother" href="#">Add another</a>
</div>

在我的_Layout.cshtml文件中,我在底部有以下脚本(所以它在任何jquery脚本加载后运行)。

<script>
$("#addAnother").click(function () {
    $.get('ServiceOrderLineEntryRow', function (template) {
        $("#ServiceOrderLineEditor").append(template);
    });
}); 
</script>

在我的控制器中,我有以下功能:

public ActionResult ServiceOrderLineEntryRow()
    {
        return PartialView("CreateLineItem");
    }

我的部分视图创建了&lt; li>订单项:

@model CRM.EntityModels.SERVICE_ORDER_DETAIL


@using (Html.BeginForm()) 
{
@Html.AntiForgeryToken()


<li style="padding-bottom:15px">
    @using (Html.BeginCollectionItem("SOD"))
    {
        <img src="@Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt="" />

        @Html.LabelFor(model => model.SERVICE_TYPE.NAME)
        @Html.EditorFor(model => model.SERVICE_TYPE.NAME)
        @Html.HiddenFor(model => model.SERVICE_TYPE_ID)

        @Html.LabelFor(model => model.UNIT_PRICE)
        @Html.EditorFor(model => model.UNIT_PRICE)
        @Html.ValidationMessageFor(model => model.UNIT_PRICE)

        @Html.LabelFor(model => model.QUANTITY)
        @Html.EditorFor(model => model.QUANTITY)
        @Html.ValidationMessageFor(model => model.QUANTITY)

        @Html.LabelFor(model => model.EXTENDED_PRICE)
        @Html.EditorFor(model => model.EXTENDED_PRICE)
        @Html.ValidationMessageFor(model => model.EXTENDED_PRICE)

        @Html.LabelFor(model => model.TAXABLE)
        @Html.EditorFor(model => model.TAXABLE)
        @Html.ValidationMessageFor(model => model.TAXABLE)

        <a href="#" onclick="$(this).parent().remove();">Delete</a>
    }
</li>

}

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

我还有一个覆盖的HTML帮助器,它使用附加了GUID的ID创建了行项目:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;

public static class HtmlHelperExtensions
{
/// <summary>
/// Generates a GUID-based editor template, rather than the index-based template generated by Html.EditorFor()
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="html"></param>
/// <param name="propertyExpression">An expression which points to the property on the model you wish to generate the editor for</param>
/// <param name="indexResolverExpression">An expression which points to the property on the model which holds the GUID index (optional, but required to make Validation* methods to work on post-back)</param>
/// <param name="includeIndexField">
/// True if you want this helper to render the hidden &lt;input /&gt; for you (default). False if you do not want this behaviour, and are instead going to call Html.EditorForManyIndexField() within the Editor view. 
/// The latter behaviour is desired in situations where the Editor is being rendered inside lists or tables, where the &lt;input /&gt; would be invalid.
/// </param>
/// <returns>Generated HTML</returns>
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> propertyExpression, Expression<Func<TValue, string>> indexResolverExpression = null, bool includeIndexField = true) where TModel : class
{
    var items = propertyExpression.Compile()(html.ViewData.Model);
    var htmlBuilder = new StringBuilder();
    var htmlFieldName = ExpressionHelper.GetExpressionText(propertyExpression);
    var htmlFieldNameWithPrefix = html.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName);
    Func<TValue, string> indexResolver = null;

    if (indexResolverExpression == null)
    {
        indexResolver = x => null;
    }
    else
    {
        indexResolver = indexResolverExpression.Compile();
    }

    foreach (var item in items)
    {
        var dummy = new { Item = item };
        var guid = indexResolver(item);
        var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
        var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, propertyExpression.Parameters);

        if (String.IsNullOrEmpty(guid))
        {
            guid = Guid.NewGuid().ToString();
        }
        else
        {
            guid = html.AttributeEncode(guid);
        }

        if (includeIndexField)
        {
            htmlBuilder.Append(_EditorForManyIndexField<TValue>(htmlFieldNameWithPrefix, guid, indexResolverExpression));
        }

        htmlBuilder.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
    }

    return new MvcHtmlString(htmlBuilder.ToString());
}

/// <summary>
/// Used to manually generate the hidden &lt;input /&gt;. To be used in conjunction with EditorForMany(), when "false" was passed for includeIndexField. 
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <param name="html"></param>
/// <param name="indexResolverExpression">An expression which points to the property on the model which holds the GUID index (optional, but required to make Validation* methods to work on post-back)</param>
/// <returns>Generated HTML for hidden &lt;input /&gt;</returns>
public static MvcHtmlString EditorForManyIndexField<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, string>> indexResolverExpression = null)
{
    var htmlPrefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
    var first = htmlPrefix.LastIndexOf('[');
    var last = htmlPrefix.IndexOf(']', first + 1);

    if (first == -1 || last == -1)
    {
        throw new InvalidOperationException("EditorForManyIndexField called when not in a EditorForMany context");
    }

    var htmlFieldNameWithPrefix = htmlPrefix.Substring(0, first);
    var guid = htmlPrefix.Substring(first + 1, last - first - 1);

    return _EditorForManyIndexField<TModel>(htmlFieldNameWithPrefix, guid, indexResolverExpression);
}

private static MvcHtmlString _EditorForManyIndexField<TModel>(string htmlFieldNameWithPrefix, string guid, Expression<Func<TModel, string>> indexResolverExpression)
{
    var htmlBuilder = new StringBuilder();
    htmlBuilder.AppendFormat(@"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldNameWithPrefix, guid);

    if (indexResolverExpression != null)
    {
        htmlBuilder.AppendFormat(@"<input type=""hidden"" name=""{0}[{1}].{2}"" value=""{1}"" />", htmlFieldNameWithPrefix, guid, ExpressionHelper.GetExpressionText(indexResolverExpression));
    }

    return new MvcHtmlString(htmlBuilder.ToString());
}

public static IDisposable BeginCollectionItem<TModel>(this HtmlHelper<TModel> html, string collectionName)
{
    string itemIndex = GetCollectionItemIndex(collectionName);
    string collectionItemName = String.Format("{0}[{1}]", collectionName, itemIndex);

    TagBuilder indexField = new TagBuilder("input");
    indexField.MergeAttributes(new Dictionary<string, string>() {
    { "name", String.Format("{0}.Index", collectionName) },
    { "value", itemIndex },
    { "type", "hidden" },
    { "autocomplete", "off" }
});

    html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
    return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
}

private class CollectionItemNamePrefixScope : IDisposable
{
    private readonly TemplateInfo _templateInfo;
    private readonly string _previousPrefix;

    public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
    {
        this._templateInfo = templateInfo;

        _previousPrefix = templateInfo.HtmlFieldPrefix;
        templateInfo.HtmlFieldPrefix = collectionItemName;
    }

    public void Dispose()
    {
        _templateInfo.HtmlFieldPrefix = _previousPrefix;
    }
}

private static string GetCollectionItemIndex(string collectionIndexFieldName)
{
    Queue<string> previousIndices = (Queue<string>)HttpContext.Current.Items[collectionIndexFieldName];
    if (previousIndices == null)
    {
        HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queue<string>();

        string previousIndicesValues = HttpContext.Current.Request[collectionIndexFieldName];
        if (!String.IsNullOrWhiteSpace(previousIndicesValues))
        {
            foreach (string index in previousIndicesValues.Split(','))
                previousIndices.Enqueue(index);
        }
    }

    return previousIndices.Count > 0 ? previousIndices.Dequeue() : Guid.NewGuid().ToString();
}

}

当单击“添加另一个”时,助手类创建了一个新的输入字段。它将GUID附加到输入名称和id,因此输入将为:

<input class="text-box single-line" id="SOD_f0b321d2-932b-4aeb-a433-70100355d3c6__SERVICE_TYPE_NAME" name="SOD[f0b321d2-932b-4aeb-a433-70100355d3c6].SERVICE_TYPE.NAME" value="" type="text">

<input data-val="true" data-val-number="The field SERVICE_TYPE_ID must be a number." data-val-required="The SERVICE_TYPE_ID field is required." id="SOD_f0b321d2-932b-4aeb-a433-70100355d3c6__SERVICE_TYPE_ID" name="SOD[f0b321d2-932b-4aeb-a433-70100355d3c6].SERVICE_TYPE_ID" value="" type="hidden">

这部分工作正常。我点击“添加另一个”,然后添加订单项。

其次,我的问题是我有这个脚本,我希望能够将SERVICE TYPE列表绑定到动态服务类型名称字段:

<script>
    $(document).ready(function () {
    $("#ServiceName").autocomplete({
    source: function (request, response) {
        $.ajax({
            url: '@Url.Action("GetServiceList", "AccountServiceOrder")',
            datatype: "json",
            data: {
                term: request.term
            },
            success: function (data) {
                response($.map(data, function (val, item) {
                    return {
                        label: val.Name,
                        value: val.Name,
                        serviceID: val.ID
                    }
                }))
            }
        })
    },
    select: function (event, ui) {
        $("#ServiceID").val(ui.item.serviceID);
    }
    });
});
</script>

它在控制器中引用此函数:

public JsonResult GetServiceList(string term)
    {
        ServiceOrderDetailLogic serviceOrderDetail = new ServiceOrderDetailLogic();
        var ServiceList = serviceOrderDetail.GetServices(term);
        return Json(ServiceList, JsonRequestBehavior.AllowGet);
    }

我知道上面的脚本无效。它没有抛出错误,但我假设这些行:

$("#ServiceName").autocomplete({

$("#ServiceID").val(ui.item.serviceID);

需要说出类似的话:

$("#SOD_f0b321d2-932b-4aeb-a433-70100355d3c6__SERVICE_TYPE_NAME").autocomplete({

$("#SOD_f0b321d2-932b-4aeb-a433-70100355d3c6__SERVICE_TYPE_ID").val(ui.item.serviceID);

我是否在正确的轨道上?有没有办法在添加每个订单项时找到该动态ID,并为其设置帐户以自动完成并查找该服务列表?

1 个答案:

答案 0 :(得分:0)

我想出来了。

我找到了一种搜索特定文本项的方法,以识别使用GUID生成的HTML(请参阅下面代码中的第1条评论)。

我使用li:contains函数来获取元素的HTML(参见下面的第2条评论)。

然后,我循环遍历每个id属性以收集基于GUID的ID名称(请参阅下面的第3条评论)。这会找到类似的东西:

from time import sleep
import asyncio as aio
loop = aio.get_event_loop()

class Executor:
    """In most cases, you can just use the 'execute' instance as a
    function, i.e. y = await execute(f, a, b, k=c) => run f(a, b, k=c) in
    the executor, assign result to y. The defaults can be changed, though,
    with your own instantiation of Executor, i.e. execute =
    Executor(nthreads=4)"""
    def __init__(self, loop=loop, nthreads=1):
        from concurrent.futures import ThreadPoolExecutor
        self._ex = ThreadPoolExecutor(nthreads)
        self._loop = loop
    def __call__(self, f, *args, **kw):
        from functools import partial
        return self._loop.run_in_executor(self._ex, partial(f, *args, **kw))
execute = Executor()

...

def cpu_bound_operation(t, alpha=30):
    sleep(t)
    return 20*alpha

async def main():
    y = await execute(cpu_bound_operation, 5, alpha=-2)

loop.run_until_complete(main())

此时,我有我需要更新的文本字段的id节点列表。接下来,我填充服务文本框的自动完成功能(请参阅下面的第4条评论。我可以使用#+ svcName找到要自动完成的正确字段。

然后,我可以将查询结果映射到一些响应变量(参见下面的第5条评论)。

最后,我使用select函数将基于GUID的文本字段(和2复选框字段)设置为响应变量值(参见下面的第6条评论)。

我对此进行了测试,确实有效。

id="SOD_c1567fa3-b9da-4a76-a72f-6f2df36be4d8__SERVICE_TYPE_NAME"

Screenshot 1 - Drop Down Working

Screenshot 2 - Unite Price / Taxable filled in when Service is selected