KnockoutJS: RangeError: Maximum call stack size exceeded;-Collection of common programming errors
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:
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/@prefixo@separador
ProdutoViewModel.cshtml
Finally, and most simple step would be to build the form =):
Tipo de produto
@Html.LabelFor(p => p.Nome)
@Html.EditorFor(p => p.Nome)
@Html.ValidationMessageFor(p => p.Nome)
@Html.LabelFor(p => p.Link)
@Html.EditorFor(p => p.Link)
@Html.ValidationMessageFor(p => p.Link)
@Html.LabelFor(p => p.Descricao)
@Html.EditorFor(p => p.Descricao)
@Html.ValidationMessageFor(p => p.Descricao)
@Html.LabelFor(p => p.Ordem)
@Html.EditorFor(p => p.Ordem)
@Html.ValidationMessageFor(p => p.Ordem)
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.
Originally posted 2013-11-15 09:09:49.