KnockoutJS: RangeError: превышен максимальный размер стека вызовов; - PullRequest
1 голос
/ 17 марта 2012

При регистрации продукта пользователь может настроить его URL!Когда пользователь наберет Tipo de produto, Nome или Link, веб-сайт покажет вам, как будет выглядеть URL для этого продукта

Works

Полныйurl: http://i.stack.imgur.com/jZg7G.png

Обратите внимание, что поле "Tipo de produto" также изменяет URL-адрес !!

Для этого я создал помощника в KnockoutJS

Код

KnockoutJS

ko.bindingHandlers.url =
    update: (element, valueAccessor, allBindingsAccessor, viewModel) ->
        link = ko.utils.unwrapObservable(valueAccessor())
        if link
            link = link.toLowerCase().trim().replaceAll(" ", "-")
            link = encodeURI(link)
        else
            link = ""
        valueAccessor()(link)
        $(element).nextAll(".link-exibicao").text(link).effect("highlight", { color: "#FDBB30" }, 800 )

Единственная цель этого помощника - создать действительный URL-адрес и отобразить его в диапазоне .link-exibicao

ViewModel

public class ProdutoViewModel
{
    [AdditionalMetadata("data-bind", "event: { change: function(data) { Link(data.Nome());  }}")]
    public string Nome { get; set; }

    [DataType(DataType.Url)]
    [AdditionalMetadata("Prefixo", "Produto/")]
    public string Link { get; set; }

    [Display(Name = "Descrição")]
    [DataType(DataType.MultilineText)]
    public string Descricao { get; set; }

    public int? Ordem { get; set; }
}

AdditionalMetadata добавит атрибут с этим именем и значением.Например, свойство Name сгенерирует HTML-код:

<input data-bind="value: Nome, event: { change: function(data) { Link(data.Nome());  }}" id="Nome" name="Nome" type="text" value="">

Url.cshtml

Следующим шагом будет добавление разметки data-bind="url: Link" ввсе поля типа URL:

@model string
@{

    var values = ViewData.ModelMetadata.AdditionalValues;
    object objDatabind;
    string data_bind = "";
    if (values.TryGetValue("data-bind", out objDatabind))
    {
        data_bind = objDatabind.ToString();
    }

    var nomeCampo = Html.IdForModel();

    var objPrefixo = values["Prefixo"];
    string prefixo = objPrefixo.ToString();
    string separador = "/";
    if (!string.IsNullOrWhiteSpace(prefixo))
    {
        if (prefixo.EndsWith("/") || prefixo.EndsWith("#"))
        {
            separador = prefixo[prefixo.Length - 1].ToString();
            prefixo = prefixo.Substring(0, prefixo.Length - 1);
        }   
    }
}

@Html.TextBoxFor(p => Model, new { data_bind = "value: " + nomeCampo + ", url: " + nomeCampo + (string.IsNullOrWhiteSpace(data_bind) ? "" : ", " + data_bind) })
@Request.Url.Host/<span class="link-prefixo">@prefixo</span><span class="link-separador">@separador</span><span class="link-exibicao"></span>

ProdutoViewModel.cshtml

Наконец, и самым простым шагом будет создание формы =):

<div class="editor-label">
    <label>Tipo de produto</label>
</div>
<div class="editor-field">
    <select data-bind="options: Tipos, optionsText: 'Nome', value: TipoSelecionado, optionsCaption: 'Selecione...'"></select>
</div>

<div class="editor-label">
    @Html.LabelFor(p => p.Nome)
</div>
<div class="editor-field">
    @Html.EditorFor(p => p.Nome)
    @Html.ValidationMessageFor(p => p.Nome)
</div>

<div class="editor-label">
    @Html.LabelFor(p => p.Link)
</div>
<div class="editor-field">
    @Html.EditorFor(p => p.Link)
    @Html.ValidationMessageFor(p => p.Link)
</div>

<div class="editor-label">
    @Html.LabelFor(p => p.Descricao)
</div>
<div class="editor-field">
    @Html.EditorFor(p => p.Descricao)
    @Html.ValidationMessageFor(p => p.Descricao)
</div>

<div class="editor-label">
    @Html.LabelFor(p => p.Ordem)
</div>
<div class="editor-field">
    @Html.EditorFor(p => p.Ordem)
    @Html.ValidationMessageFor(p => p.Ordem)
</div>

Проблема

Всякий раз, когда набираются простые слова, такие как: «название моего продукта», все работает отлично!
Но такие слова, как meu prodúto côm açênto, отображается сообщение об ошибке ниже

Uncaught Error: Unable to parse bindings.
Message: RangeError: Maximum call stack size exceeded;
Bindings value: value: Link, url: Link

1 Ответ

11 голосов
/ 17 марта 2012

Ваш bindingHandler вызывает рекурсивные обновления, поскольку вы получаете доступ к значению:

link = ko.utils.unwrapObservable(valueAccessor())

и позже устанавливаете его:

valueAccessor()(link)

Если link окажется идентичным текущему значению, цепочка прекратится (наблюдаемые не уведомляют об идентичных (===) значениях).

Когда вы передаете: meu prodúto côm açênto

Становится: meu-prod%C3%BAto%20c%C3%B4m%20a%C3%A7%C3%AAnto

При установке наблюдаемого он повторно запускает ту же привязку.Таким образом, он снова вызывает encodeURI, и теперь он кодируется дважды, как:

meu-prod%25C3%25BAto%2520c%25C3%25B4m%2520a%25C3%25A7%25C3%25AAnto

Наблюдаемое снова устанавливается, и, поскольку это значение является новым, оно запускает его снова (и сноваи снова) до тех пор, пока вы не получите ошибку стека вызовов.

Некоторые варианты для обработки этого состоят в том, чтобы не записывать обратно в наблюдаемое и просто использовать привязку для кодирования URL.

В противном случае хорошим вариантом будет использование writeable computed observable для перехвата записи значения и манипулирования им в модели.

...