Evento de cambio de selector de fecha de jQuery UI no detectado por KnockoutJS


134

Estoy tratando de usar KnockoutJS con jQuery UI. Tengo un elemento de entrada con un selector de fecha adjunto. Actualmente estoy ejecutándome knockout.debug.1.2.1.jsy parece que Knockout nunca detecta el evento de cambio. El elemento se ve así:

<input type="text" class="date" data-bind="value: RedemptionExpiration"/>

Incluso he intentado cambiar el valueUpdatetipo de evento pero fue en vano. Parece que Chrome causa un focusevento justo antes de que cambie el valor, pero IE no lo hace.

¿Existe algún método de Knockout que "vuelva a unir todos los enlaces"? Técnicamente, solo necesito cambiar el valor antes de enviarlo de vuelta al servidor. Entonces podría vivir con ese tipo de solución.

Creo que el problema es culpa del datepicker, pero no puedo entender cómo solucionarlo.

¿Algunas ideas?

Respuestas:


253

Creo que para el jQuery UI datepicker es preferible usar un enlace personalizado que leerá / escribirá con objetos Date usando las API proporcionadas por el datepicker.

El enlace podría verse así (de mi respuesta aquí ):

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {},
            $el = $(element);

        $el.datepicker(options);

        //handle the field changing by registering datepicker's changeDate event
        ko.utils.registerEventHandler(element, "changeDate", function () {
            var observable = valueAccessor();
            observable($el.datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $el.datepicker("destroy");
        });

    },
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            $el = $(element);

        //handle date data coming via json from Microsoft
        if (String(value).indexOf('/Date(') == 0) {
            value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
        }

        var current = $el.datepicker("getDate");

        if (value - current !== 0) {
            $el.datepicker("setDate", value);
        }
    }
};

Lo usarías como:

<input data-bind="datepicker: myDate, datepickerOptions: { minDate: new Date() }" />

Muestra en jsFiddle aquí: http://jsfiddle.net/rniemeyer/NAgNV/


21
Lo que me encanta es que no cortó esquinas en esta carpeta, como con la devolución de llamada de eliminación. ¡Un buen ejemplo a seguir en el camino hacia el dominio de KnockoutJS!
Dav

2
¿Y qué pasa con el selector de fechas vinculado a un elemento que se crea dinámicamente ... quiero decir, el selector de fechas con un controlador en vivo.
Phoenix_uy

66
Phoenix_uy: para que el selector de fechas funcione con objetos creados dinámicamente, asegúrese de no establecer el ID o el nombre de la entrada.
James Reategui

1
Estoy usando esto y funciona perfectamente, excepto por una pequeña cosa: si configuro minDate o maxDate igual a un observable, no se actualiza si ese observable se cambia (por ejemplo, si tengo dos fechadores donde la fecha máxima de la primero es el valor del segundo, si actualizo el segundo no actualiza la fecha máxima del primero) igual que esta pregunta stackoverflow.com/questions/14732204/…
PW Kad

2
parece que el nombre del evento es incorrecto, ko.utils.registerEventHandler (element, "changeDate", function () - debe ser ko.utils.registerEventHandler (element, "change", function ()
Adam Bilinski el

13

Aquí hay una versión de la respuesta de RP Niemeyer que funcionará con los scripts de validación de eliminación que se encuentran aquí: http://github.com/ericmbarnard/Knockout-Validation

ko.bindingHandlers.datepicker = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {};
        $(element).datepicker(options);

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            observable($(element).val());
            if (observable.isValid()) {
                observable($(element).datepicker("getDate"));

                $(element).blur();
            }
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).datepicker("destroy");
        });

        ko.bindingHandlers.validationCore.init(element, valueAccessor, allBindingsAccessor);

    },
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());

        //handle date data coming via json from Microsoft
        if (String(value).indexOf('/Date(') == 0) {
            value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
        }

        current = $(element).datepicker("getDate");

        if (value - current !== 0) {
            $(element).datepicker("setDate", value);
        }
    }
};

Los cambios se realizan en el controlador de eventos de cambio para pasar primero el valor ingresado y no la fecha a los scripts de validación, luego solo se establece la fecha en observable si es válida. También agregué validationCore.init que es necesario para los enlaces personalizados que se analizan aquí:

http://github.com/ericmbarnard/Knockout-Validation/issues/69

También agregué la sugerencia de rpenrose para que el cambio se difuminara para eliminar algunos escenarios molestos de selección de fechas que se interponían en el camino de las cosas.


2
No parece funcionar para mí, me sale TypeError: observable.isModified no es una función en la línea 313 de knockout.validation.js. Pequeño ejemplo aquí: frikod.se/~capitol/fel/test.html
Alexander Kjäll

La línea importante para que funcione con la biblioteca de validación es: ko.bindingHandlers.validationCore.init (element, valueAccessor, allBindingsAccessor);
CRice

11

He usado un enfoque diferente. Dado que knockout.js no parece activar el evento en el cambio, forcé al selector de fechas a llamar a change () para su entrada una vez cerrado.

$(".date").datepicker({
    onClose: function() {
        $(this).change(); // Forces re-validation
    }
});

1
$ ('. datepicker'). datepicker ({onSelect: function (dateText) {$ ("# date_in"). trigger ("change");}});
elsadek

9

Aunque todas estas respuestas me ahorraron mucho trabajo, ninguna de ellas funcionó completamente para mí. Después de seleccionar una fecha, el valor enlazado no se actualizará. Solo pude hacer que se actualice al cambiar el valor de la fecha usando el teclado y luego haciendo clic fuera del cuadro de entrada. Arreglé esto aumentando el código de RP Niemeyer con el código de syb para obtener:

ko.bindingHandlers.datepicker = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            //initialize datepicker with some optional options
            var options = allBindingsAccessor().datepickerOptions || {};

            var funcOnSelectdate = function () {
                var observable = valueAccessor();
                observable($(element).datepicker("getDate"));
            }

            options.onSelect = funcOnSelectdate;

            $(element).datepicker(options);

            //handle the field changing
            ko.utils.registerEventHandler(element, "change", funcOnSelectdate);

            //handle disposal (if KO removes by the template binding)
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).datepicker("destroy");
            });

        },
        update: function (element, valueAccessor) {

            var value = ko.utils.unwrapObservable(valueAccessor());
            if (typeof(value) === "string") { // JSON string from server
                value = value.split("T")[0]; // Removes time
            }

            var current = $(element).datepicker("getDate");

            if (value - current !== 0) {
                var parsedDate = $.datepicker.parseDate('yy-mm-dd', value);
                $(element).datepicker("setDate", parsedDate);
            }
        }
    };

Sospecho que pongo el observable ($ (elemento) .datepicker ("getDate")); declaración en su propia función y registrar eso con options.onSelect hizo el truco?


2
¡Un millón de gracias! Intenté cada ejemplo y luego encontré este en la parte inferior de la página y finalmente funciona. Solo tengo un pequeño ajuste en el mío para que el valor enlazado permanezca en el mismo formato "amigable para el servidor" en el que apareció. En su función funcOnSelectdate, use esto: observable ($. Datepicker.formatDate ('aa-mm-dd' , $ (elemento) .datepicker ('getDate')));
BrutalDev

Creo que si anula la onSelectfunción, no change
provocará

6

Gracias por este artículo, lo encontré muy útil.

Si desea que DatePicker se comporte exactamente como el comportamiento predeterminado de JQuery UI, le recomiendo agregar un desenfoque en el elemento en el controlador de eventos de cambio:

es decir

    //handle the field changing
    ko.utils.registerEventHandler(element, "change", function () {
        var observable = valueAccessor();
        observable($(element).datepicker("getDate"));

        $(element).blur();

    });

¿Esta respuesta no parece completa? ¿Es este un comentario sobre la respuesta de @ RPNiemeyer o la de otra persona?
rjmunro

3

Resolví este problema cambiando el orden de mis archivos de script incluidos:

<script src="@Url.Content("~/Scripts/jquery-ui-1.10.2.custom.js")"></script>
<script src="@Url.Content("~/Scripts/knockout-2.2.1.js")"></script>

Tuvo problemas similares con el modelo que no se actualizaba aunque la entrada mostraba la fecha elegida correctamente del selector de fechas. Comencé con la lista de sugerencias ... pero ... este fue definitivamente mi problema. Hmmm ... mi proyecto MVC ha tenido la secuencia de comandos KO por delante de las secuencias de comandos jquery y jquery UI durante mucho tiempo, tendrá que probar a fondo.
bkwdesign

2

Igual que RP Niemeyer, pero mejor soporte de WCF DateTime, Timezones y el uso de la propiedad DatePicker onSelect JQuery.

        ko.bindingHandlers.datepicker = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            //initialize datepicker with some optional options
            var options = allBindingsAccessor().datepickerOptions || {};

            var funcOnSelectdate = function () {
                var observable = valueAccessor();
                var d = $(element).datepicker("getDate");
                var timeInTicks = d.getTime() + (-1 * (d.getTimezoneOffset() * 60 * 1000));

                observable("/Date(" + timeInTicks + ")/");
            }
            options.onSelect = funcOnSelectdate;

            $(element).datepicker(options);

            //handle the field changing
            ko.utils.registerEventHandler(element, "change", funcOnSelectdate);

            //handle disposal (if KO removes by the template binding)
            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $(element).datepicker("destroy");
            });

        },
        update: function (element, valueAccessor) {
            var value = ko.utils.unwrapObservable(valueAccessor());

            //handle date data coming via json from Microsoft
            if (String(value).indexOf('/Date(') == 0) {
                value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
            }

            current = $(element).datepicker("getDate");

            if (value - current !== 0) {
                $(element).datepicker("setDate", value);
            }
        }
    };

Disfruta :)

http://jsfiddle.net/yechezkelbr/nUdYH/


1

Creo que se puede hacer mucho más fácil: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

Por lo tanto, no necesita manejo manual de cambios en la función init

Pero en este caso, su variable 'myDate' obtendrá solo un valor visible, no un objeto Date.


1

Alternativamente, puede especificar esto en el enlace:

Actualizar:

 function (element, valueAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor()),
        current = $(element).datepicker("getDate");

    if (typeof value === "string") {            
       var dateValue = new Date(value);
       if (dateValue - current !== 0)
           $(element).datepicker("setDate", dateValue);
    }               
}

2
Esto soluciona un problema cuando el valor de la fecha devuelta está en formato de cadena, es decir. "2013-01-20T05: 00: 00" en lugar del objeto de fecha. Me encontré con esto al cargar datos de la API web.
James Reategui

0

Basado en la solución de Ryan, myDate devuelve la cadena de fecha estándar, que no es la ideal en mi caso. Usé date.js para analizar el valor para que siempre devuelva el formato de fecha que desea. Eche un vistazo a este ejemplo de violín de ejemplo .

update: function(element, valueAccessor) {
    var value = ko.utils.unwrapObservable(valueAccessor()),
        current = $(element).datepicker("getDate");
    var d = Date.parse(value);
    if (value - current !== 0) {
        $(element).datepicker("setDate", d.toString("MM/dd/yyyy"));   
    }
}

0

Necesitaba actualizar repetidamente mis datos del servidor, pero no terminé el trabajo para compartir mis necesidades a continuación (mi formato de fecha / Fecha (1224043200000) /):

//Object Model
function Document(data) {
        if (String(data.RedemptionExpiration).indexOf('/Date(') == 0) {
            var newDate = new Date(parseInt(data.BDate.replace(/\/Date\((.*?)\)\//gi, "$1")));
            data.RedemptionExpiration = (newDate.getMonth()+1) +
                "/" + newDate.getDate() +
                "/" + newDate.getFullYear();
        }
        this.RedemptionExpiration = ko.observable(data.RedemptionExpiration);
}
//View Model
function DocumentViewModel(){
    ///additional code removed
    self.afterRenderLogic = function (elements) {
        $("#documentsContainer .datepicker").each(function () {
            $(this).datepicker();                   
        });
    };
}

Después de que el modelo se formatea correctamente para la salida, agregué una plantilla con la documentación knockoutjs :

<div id="documentsContainer">
    <div data-bind="template: { name: 'document-template', foreach: documents, afterRender: afterRenderLogic }, visible: documents().length > 0"></div>
</div>

//Inline template
<script type="text/html" id="document-template">
  <input data-bind="value: RedemptionExpiration" class="datepicker" />
</script>

0

Pocas personas pidieron opciones dinámicas de selector de fecha En mi caso, necesitaba un rango de fechas dinámico, por lo que la primera entrada define el valor mínimo del segundo y el segundo establece el valor máximo para el primero. Lo resolví extendiendo el controlador del RP Niemeyer. Entonces a su original:

ko.bindingHandlers.datepicker = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {},
            $el = $(element);

        $el.datepicker(options);

        //handle the field changing by registering datepicker's changeDate event
        ko.utils.registerEventHandler(element, "change", function() {
            var observable = valueAccessor();
            observable($el.datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            $el.datepicker("destroy");
        });

    },
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            $el = $(element);

        //handle date data coming via json from Microsoft
        if (String(value).indexOf('/Date(') == 0) {
            value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
        }

        var current = $el.datepicker("getDate");

        if (value - current !== 0) {
            $el.datepicker("setDate", value);
        }
    }
};

He agregado dos controladores más que corresponden a las opciones que quería modificar:

ko.bindingHandlers.minDate = {
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            current = $(element).datepicker("option", "minDate", value);
    }
};

ko.bindingHandlers.maxDate = {
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            current = $(element).datepicker("option", "maxDate", value);
    }
};

Y los usé así en mi plantilla:

<input data-bind="datepicker: selectedLowerValue, datepickerOptions: { minDate: minValue()}, maxDate: selectedUpperValue" />       
<input data-bind="datepicker: selectedUpperValue, datepickerOptions: { maxDate: maxValue()}, minDate: selectedLowerValue" />

0

El uso de enlaces personalizados proporcionados en respuestas anteriores no siempre es posible. Llamar $(element).datepicker(...)lleva un tiempo considerable, y si tiene, por ejemplo, unas pocas docenas, o incluso cientos de elementos para llamar a este método, debe hacerlo "perezoso", es decir, a pedido.

Por ejemplo, el modelo de vista se puede inicializar, el input correos electrónicos se insertan en el DOM, pero los marcadores de fecha correspondientes solo se inicializarán cuando un usuario haga clic en ellos.

Entonces, aquí está mi solución:

Agregue un enlace personalizado que permita adjuntar datos arbitrarios a un nodo:

KO.bindingHandlers.boundData = {
  init: function(element, __, allBindings) {
    element.boundData = allBindings.get('boundData');
  }
};

Use el enlace para attcah el observable utilizado para el inputvalor de 's:

<input type='text' class='my-date-input'
       data-bind='textInput: myObservable, boundData: myObservable' />

Y finalmente, al inicializar el selector de fechas, use su onSelectopción:

$('.my-date-input').datepicker({
  onSelect: function(dateText) {
    this.myObservable(dateText);
  }
  //Other options
});

De esta manera, cada vez que un usuario cambia la fecha con el selector de fecha, también se actualiza el observable Knockout correspondiente.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.