web-dev-qa-db-fra.com

evénement de changement de datepicker de l'interface utilisateur jQuery non capturé par KnockoutJS

J'essaie d'utiliser KnockoutJS avec jQuery UI. J'ai un élément d'entrée avec un datepicker attaché. Je suis en train d'exécuter knockout.debug.1.2.1.js et il semble que l'événement de changement n'est jamais capturé par Knockout. L'élément ressemble à ceci:

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

J'ai même essayé de changer le type d'événement valueUpdate mais en vain. Il semble que Chrome provoque un événement focus juste avant de changer la valeur, mais pas IE.

Existe-t-il une méthode Knockout qui "rebinde toutes les liaisons"? Techniquement, je n'ai besoin que de la valeur avant de la renvoyer au serveur. Donc, je pourrais vivre avec ce genre de solution de contournement.

Je pense que le problème est la faute du datte, mais je ne vois pas comment résoudre ce problème.

Des idées?

134
Jose

Je pense que pour le datepicker de l'interface utilisateur jQuery, il est préférable d'utiliser une liaison personnalisée qui lira/écrit avec des objets Date à l'aide des API fournies par le datepicker.

La liaison peut ressembler à (d'après ma réponse ici ):

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);
        }
    }
};

Vous l'utiliseriez comme:

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

Exemple dans jsFiddle ici: http://jsfiddle.net/rniemeyer/NAgNV/

253
RP Niemeyer

Voici une version de la réponse de RP Niemeyer qui fonctionnera avec les scripts de validation knockout disponibles ici: 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);
        }
    }
};

Les modifications concernent le gestionnaire d'événements change pour transmettre la valeur entrée et non la date aux scripts de validation, puis définissez uniquement la date sur l'observable si elle est valide. J'ai également ajouté le fichier validationCore.init nécessaire aux liaisons personnalisées décrites ici:

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

J'ai également ajouté la suggestion de Rpenrose visant à atténuer le changement afin d'éliminer certains scénarios fastidieux de trieurs de dates qui gênaient les choses.

13
Brad M

J'ai utilisé une approche différente. Comme knockout.js ne semble pas déclencher l'événement à la modification, j'ai forcé le sélecteur de date à appeler change () pour son entrée une fois fermé.

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

Bien que toutes ces réponses m'ont épargné beaucoup de travail, aucune d’entre elles n’a fonctionné pleinement pour moi. Après avoir sélectionné une date, la valeur liée ne serait pas mise à jour. Je ne pouvais le mettre à jour que lorsque je changeais la date avec le clavier puis en cliquant dans la zone de saisie. J'ai corrigé cela en augmentant le code de RP Niemeyer avec le code de syb pour obtenir:

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);
            }
        }
    };

Je suspecte de mettre l'observable ($ (élément) .datepicker ("getDate")); déclaration dans sa propre fonction et en enregistrant cela avec options.onSelect a fait le tour?

9
brudert

Merci pour cet article, je l'ai trouvé très utile. 

Si vous voulez que le DatePicker se comporte exactement comme le comportement par défaut de l'interface utilisateur JQuery, je vous recommande d'ajouter un flou à l'élément dans le gestionnaire d'événements change:

c'est à dire.

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

        $(element).blur();

    });
6
rpenrose

J'ai résolu ce problème en modifiant l'ordre de mes fichiers de script inclus:

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

Identique à RP Niemeyer, mais prise en charge améliorée de DateTime, des fuseaux horaires et de l’utilisation de la propriété DatePicker onSelect JQuery WCF.

        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);
            }
        }
    };

Prendre plaisir :)

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

2
syb

Je pense que cela peut être fait beaucoup plus facilement: <input data-bind="value: myDate, datepicker: myDate, datepickerOptions: {}" />

Ainsi, vous n'avez pas besoin de la gestion manuelle des modifications dans la fonction init.

Mais dans ce cas, votre variable 'myDate' n'aura qu'une valeur visible, pas un objet Date.

1
mot

Alternativement, vous pouvez spécifier ceci dans la liaison: 

Mettre à jour:  

 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);
    }               
}
1
Martin

Je devais mettre à jour plusieurs fois mes données du serveur, mais je n'ai pas terminé le travail pour le partage de mes besoins ci-dessous (format de date/Date (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();                   
        });
    };
}

Une fois que le modèle est correctement formaté pour la sortie, j'ai ajouté un modèle avec la documentation 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
Ken

L'utilisation de liaisons personnalisées fournies dans les réponses précédentes n'est pas toujours possible. Appeler $(element).datepicker(...) prend un temps considérable, et si vous avez, par exemple, quelques dizaines, voire des centaines d’éléments pour appeler cette méthode, vous devez le faire "paresseux", c’est-à-dire à la demande.

Par exemple, le modèle de vue peut être initialisé, la inputs étant insérée dans le DOM, mais les sélecteurs de date correspondants ne seront initialisés que lorsqu'un utilisateur clique dessus.

Alors, voici ma solution:

Ajoutez une liaison personnalisée permettant d'attacher des données arbitraires à un nœud:

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

Utilisez la liaison pour attcah l'observable utilisé pour la valeur de input:

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

Et enfin, lors de l’initialisation du sélecteur de date, utilisez l’option onSelect:

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

Ainsi, chaque fois qu'un utilisateur modifie la date avec le sélecteur de date, l'observable Knockout correspondant est également mis à jour.

0
ololoepepe

Basé sur la solution de Ryan, myDate renvoie la chaîne de date standard, qui n'est pas la chaîne idéale dans mon cas. J'ai utilisé date.js pour analyser la valeur afin qu'elle renvoie toujours le format de date souhaité. Jetez un oeil à cet exemple fiddle Example .

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
Princa

Peu de gens ont demandé des options de datepicker dynamiques. Dans mon cas, j'avais besoin d'une plage de dates dynamique. La première entrée définit la valeur minimale de la seconde et la seconde définit la valeur maximale pour la première. Je l'ai résolu en étendant le gestionnaire de RP Niemeyer. Donc à son 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);
        }
    }
};

J'ai ajouté deux autres gestionnaires correspondant aux options que je voulais modifier:

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);
    }
};

Et les utilisé comme ça dans mon template:

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