2012-02-10 14 views
23

Supongamos que tengo plantilla knockout.js así:identificadores únicos en las plantillas knockout.js

<script type="text/html" id="mytemplate"> 
    <label for="inputId">Label for input</label> 
    <input type="text" id="inputId" data-bind="value: inputValue"/> 
</script> 

Si me hacen esta plantilla en varios lugares en la página termino con varias entradas con el mismo ID (y varias etiquetas con el mismo valor para), lo que tiene malas consecuencias. En particular, todos los códigos que dependen de los identificadores pueden no funcionar correctamente (en mi caso, uso el complemento jquery.infieldlabel que se confunde con varias entradas con el mismo ID). A mi modo de resolver este problema ahora es agrego atributo id único para el modelo que se unen a la plantilla:

<script type="text/html" id="mytemplate"> 
    <label data-bind="attr: {for: id}>Label for input</label> 
    <input type="text" data-bind="attr: {id: id}, value: inputValue"/> 
</script> 

Esto funciona, pero no es muy elegante ya que tiene que tener este atributo id artificial en mis modelos eso no se usa para nada más. Me pregunto si hay una mejor solución aquí.

Respuesta

61

Una alternativa que no se basa en el orden en el que están vinculados los campos es tener el conjunto de enlaces como una propiedad id en los datos, que debería ser observable.

ko.bindingHandlers.uniqueId = { 
    init: function(element, valueAccessor) { 
     var value = valueAccessor(); 
     value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter); 

     element.id = value.id; 
    }, 
    counter: 0, 
    prefix: "unique" 
}; 

ko.bindingHandlers.uniqueFor = { 
    init: function(element, valueAccessor) { 
     var value = valueAccessor(); 
     value.id = value.id || ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter); 

     element.setAttribute("for", value.id); 
    } 
}; 

Se podría utilizarlo como:

<ul data-bind="foreach: items"> 
    <li> 
     <label data-bind="uniqueFor: name">Before</label> 
     <input data-bind="uniqueId: name, value: name" /> 
     <label data-bind="uniqueFor: name">After</label> 
    </li> 
</ul> 

muestra: http://jsfiddle.net/rniemeyer/JjBhY/

Lo bueno de añadir un alojamiento a la función observable es que cuando se enciende en JSON para enviar de nuevo a el servidor, entonces desaparecerá naturalmente, ya que lo observable simplemente se convertirá en su valor desenvuelto.

+4

En segundo lugar. Pero, ¿puedo ser irritante y pedir consejo sobre una variante un poco más compleja del problema? Donde tienes un par de radios con, por ejemplo, "Sí" y "No" como las etiquetas, cada una vinculada a un solo observador booleano, p. "Está activo". Por el momento, los 4 elementos, las 2 radios y las 2 etiquetas, todos obtienen la misma identificación. Los pares de radios en sí aparecen varias veces, así que estoy generando un atributo de nombre único para cada par con un enlace Knockout, por lo que necesito poder prefijar ese nombre específico del par actual a los ID. –

+0

También podría usar data-bind = "attr: {for: 'status_' + $ index}" y data-bind = "attr: {id: 'status_' + $ index}" para ID únicos – viperguynaz

+0

También podría usar data-bind = "attr: {for: 'status_' + $ index}" y data-bind = "attr: {id: 'status_' + $ index}" para identificaciones únicas. $ index to se refiere al índice basado en cero del elemento del arreglo actual. El índice $ es observable y se actualiza cada vez que cambia el índice del elemento (por ejemplo, si se agregan o quitan elementos de la matriz). – viperguynaz

6

he hecho algo como esto en el pasado:

ko.bindingHandlers.uniqueId = { 
    init: function(element) { 
     element.id = ko.bindingHandlers.uniqueId.prefix + (++ko.bindingHandlers.uniqueId.counter); 
    }, 
    counter: 0, 
    prefix: "unique" 
}; 

ko.bindingHandlers.uniqueFor = { 
    init: function(element, valueAccessor) { 
     var after = ko.bindingHandlers.uniqueId.counter + (ko.utils.unwrapObservable(valueAccessor()) === "after" ? 0 : 1); 
      element.setAttribute("for", ko.bindingHandlers.uniqueId.prefix + after); 
    } 
}; 

Se podría usarlos como:

<ul data-bind="foreach: items"> 
    <li> 
     <label data-bind="uniqueFor: 'before'">Before</label> 
     <input data-bind="uniqueId: true, value: name" /> 
     <label data-bind="uniqueFor: 'after'">After</label> 
    </li> 
</ul> 

Así que sólo se mantiene el estado de la unión en sí incrementando ko.bindingHandlers.uniqueId.counter. Entonces, el enlace uniqueFor solo necesita saber si es antes o después del campo para saber cómo obtener la identificación correcta.

Muestra aquí: http://jsfiddle.net/rniemeyer/8KJD3/

Si las etiquetas no estaban cerca de sus campos (múltiples entradas atados ante cada etiqueta tal vez en filas separadas de una tabla), entonces usted tendría que mirar a una estrategia diferente.

+0

que estaba pensando algo en ese sentido también, y que sin duda trabajo en mi caso, pero lo que no me gusta de esta solución es que depende del orden en que se representan las etiquetas. Gracias por el código, definitivamente es una de las opciones. –

+0

Si la orden es una preocupación, aquí hay otra opción: http://jsfiddle.net/rniemeyer/JjBhY/.Esto es similar, pero tendría el conjunto de enlace como una propiedad "id" en lo que presumiblemente es observable. Cualquiera que sea el enlace que golpee primero actualizaría la identificación. Lo bueno de establecer una propiedad "id" en la función observable es que desaparecerá cuando la conviertas en JSON, ya que solo quedarías con el valor desenvuelto del observable. –

+0

¡Gracias, esto es exactamente lo que estaba buscando! ¿Te importa publicar esto como una respuesta para poder aceptarlo? –

3

No puedo responder a la respuesta seleccionada, pero tengo una versión mejorada del código que admite múltiples identificadores únicos por valor de entrada. Es en mi blog en http://drewp.quickwitretort.com/2012/09/18/0 y se repite aquí:

ko.bindingHandlers.uniqueId = { 
    /* 
     data-bind="uniqueId: $data" to stick a new id on $data and 
     use it as the html id of the element. 

     data-which="foo" (optional) adds foo to the id, to separate 
     it from other ids made from this same $data. 
    */ 
    counter: 0, 
    _ensureId: function (value, element) { 

    if (value.id === undefined) { 
     value.id = "elem" + (++ko.bindingHandlers.uniqueId.counter); 
    } 

    var id = value.id, which = element.getAttribute("data-which"); 
    if (which) { 
     id += "-" + which; 
    } 
    return id; 
    }, 
    init: function(element, valueAccessor) { 
     var value = valueAccessor(); 
     element.id = ko.bindingHandlers.uniqueId._ensureId(value, element); 
    }, 
}; 

ko.bindingHandlers.uniqueFor = { 
    /* 
     data-bind="uniqueFor: $data" works like uniqueId above, and 
     adds a for="the-new-id" attr to this element. 

     data-which="foo" (optional) works like it does with uniqueId. 
    */ 
    init: function(element, valueAccessor) { 
     element.setAttribute(
     "for", ko.bindingHandlers.uniqueId._ensureId(valueAccessor(), element)); 
    } 
}; 

Ahora usted puede tener varias casillas marcadas para un registro con las identificaciones automáticas:

<li data-bind="foreach: channel"> 
    <input type="checkbox" data-which="mute" data-bind="uniqueId: $data, checked: mute">  
    <label data-which="mute" data-bind="uniqueFor: $data">Mute</label> 

    <input type="checkbox" data-which="solo" data-bind="uniqueId: $data, checked: solo">  
    <label data-which="solo" data-bind="uniqueFor: $data">Solo</label> 
</li> 
Cuestiones relacionadas