简体   繁体   中英

KnockoutJS: RangeError: Maximum call stack size exceeded;

When registering a product, the user can customize the URL of it! As the user goes by typing the Tipo de produto , Nome or Link , the website will show you how will the URL for this product

作品

Full url: http://i.stack.imgur.com/jZg7G.png

Note that the field "Tipo de produto" also modifies the URL!!

For this, I created a helper in KnockoutJS

Code

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 )

The only purpose of this helper is to generate a valid URL and display it in the span .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 will add an attribute with that name and value. For example, the property Name will generate the HTML:

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

Url.cshtml

The next step would be to add the markup data-bind="url: Link" in all fields of type 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

Finally, and most simple step would be to build the form =):

<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>

Problem

Whenever typed simple words like: "my product name" everything works perfectly!
But words like meu prodúto côm açênto the error below is displayed!

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

Your bindingHandler is causing recursive updates, as you are accessing the value:

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

and later setting it:

valueAccessor()(link)

If link ends up being identical to its current value, then the chain would stop (observables don't notify on identical (===) values).

When you pass: meu prodúto côm açênto

It becomes: meu-prod%C3%BAto%20c%C3%B4m%20a%C3%A7%C3%AAnto

When setting the observable it re-triggers the same binding. So, it calls encodeURI again and now it is double-encoded like:

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

The observable is set again and since this value is new it triggers it again (and again and again) until you get the call stack error.

Some options to handle this would be to not write back to the observable and just use the binding to encode the URL.

Otherwise a good choice would be to use a writeable computed observable to intercept writes to the value and manipulate it in the model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM