Événement de changement de datepicker de l'interface utilisateur jQuery non intercepté par KnockoutJS

134

J'essaye d'utiliser KnockoutJS avec l'interface utilisateur jQuery. J'ai un élément d'entrée avec un datepicker attaché. Je suis actuellement en cours d'exécution knockout.debug.1.2.1.jset il semble que l'événement de changement ne soit 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 valueUpdatetype d'événement mais en vain. Il semble que Chrome provoque un focusévénement juste avant de modifier la valeur, mais pas IE.

Existe-t-il une méthode Knockout qui "relie toutes les liaisons"? Je n'ai techniquement besoin que de changer la valeur avant de la renvoyer au serveur. Je pourrais donc vivre avec ce genre de solution de contournement.

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

Des idées?

José
la source

Réponses:

253

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 les objets Date en utilisant les API fournies par le datepicker.

La liaison pourrait 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/

RP Niemeyer
la source
21
Ce que j'aime, c'est que vous n'avez pas coupé les coins ronds dans ce classeur, comme avec le rappel de disposition. Un bon exemple à suivre sur la voie de la maîtrise de KnockoutJS!
Dav
2
Et qu'en est-il du datepicker lié à un élément qui est créé dinamically ... je veux dire, le datepicker avec un gestionnaire en direct.
Phoenix_uy
6
Phoenix_uy: Pour que le sélecteur de date fonctionne avec des objets créés dynamiquement, assurez-vous de ne pas définir l'ID ou le nom de l'entrée.
James Reategui
1
J'utilise ceci et cela fonctionne parfaitement, sauf pour une petite chose - Si je règle le minDate ou le maxDate égal à un observable, il n'est pas mis à jour si cet observable est modifié (par exemple, si j'ai deux datepickers où la date maximale du le premier est la valeur du second, si je mets à jour le second, il ne met pas à jour la date max du premier) même que cette question stackoverflow.com/questions/14732204/...
PW Kad
2
semble que le nom de l'événement est incorrect, ko.utils.registerEventHandler (élément, "changeDate", function () - devrait être ko.utils.registerEventHandler (élément, "change", function ()
Adam Bilinski
13

Voici une version de la réponse de RP Niemeyer qui fonctionnera avec les scripts de validation knockout trouvés 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 sont apportées au gestionnaire d'événement de modification pour transmettre d'abord la valeur saisie et non la date aux scripts de validation, puis définir la date sur l'observable uniquement si elle est valide. J'ai également ajouté le validationCore.init qui est nécessaire pour les liaisons personnalisées décrites ici:

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

J'ai également ajouté la suggestion de rpenrose pour un flou sur le changement afin d'éliminer certains scénarios de datepicker embêtants qui gênaient les choses.

Brad M
la source
2
Cela ne semble pas fonctionner pour moi, j'obtiens TypeError: observable.isModified n'est pas une fonction sur la ligne 313 de knockout.validation.js. Petit exemple ici: frikod.se/~capitol/fel/test.html
Alexander Kjäll
La ligne importante pour le faire fonctionner avec la bibliothèque de validation est: ko.bindingHandlers.validationCore.init (element, valueAccessor, allBindingsAccessor);
CRice
11

J'ai utilisé une approche différente. Puisque knockout.js ne semble pas déclencher l'événement en cas de 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
    }
});
ThiagoPXP
la source
1
$ ('. datepicker'). datepicker ({onSelect: function (dateText) {$ ("# date_in"). trigger ("change");}});
elsadek
9

Bien que toutes ces réponses m'aient épargné beaucoup de travail, aucune 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 lors de la modification de la valeur de la date à l'aide du clavier, puis en cliquant hors de 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 soupçonne de mettre l'observable ($ (element) .datepicker ("getDate")); instruction dans sa propre fonction et l'enregistrer avec options.onSelect a fait l'affaire?

Brudert
la source
2
Mille mercis! J'ai essayé tous les exemples, puis j'ai trouvé celui-ci en bas de page et cela fonctionne enfin. J'ai juste un petit ajustement dans le mien pour que la valeur liée reste dans le même format "convivial pour le serveur" dans lequel elle est descendue. Dans votre fonction funcOnSelectdate, utilisez ceci: observable ($. Datepicker.formatDate ('aa-mm-jj' , $ (élément) .datepicker ('getDate')));
BrutalDev
Je pense que si vous remplacez la onSelectfonction, cela ne change
déclenchera
6

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 JQuery UI, je vous recommande d'ajouter un flou sur l'élément dans le gestionnaire d'événements de modification:

c'est à dire

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

        $(element).blur();

    });
rpenrose
la source
Cette réponse ne semble pas complète? Est-ce un commentaire sur la réponse de @ RPNiemeyer ou de quelqu'un d'autre?
rjmunro
3

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>
Susanna
la source
Il y a eu des problèmes similaires avec le modèle non mis à jour même si l'entrée a rendu la date correctement choisie à partir du sélecteur de date. J'ai commencé la liste des suggestions ... mais ... c'était vraiment mon problème. Hmmm .. mon projet MVC a eu le script KO avant les scripts d'interface utilisateur jquery et jquery pendant longtemps - devra tester à fond.
bkwdesign
2

Identique à RP Niemeyer, mais meilleure prise en charge de WCF DateTime, Timezones et Utilisation de la propriété 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);
            }
        }
    };

Prendre plaisir :)

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

syb
la source
1

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

Vous n'avez donc pas besoin de gestion manuelle des modifications dans la fonction init.

Mais dans ce cas, votre variable 'myDate' obtiendra uniquement une valeur visible, pas un objet Date.

mot
la source
1

Vous pouvez également le spécifier 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);
    }               
}
Martin
la source
2
Cela résout un problème lorsque la valeur de date renvoyée est au format chaîne, c'est-à-dire. "2013-01-20T05: 00: 00" au lieu de l'objet de date. J'ai rencontré cela lors du chargement de données depuis l'API Web.
James Reategui
0

Sur la base de la solution de Ryan, myDate renvoie la chaîne de date standard, ce qui n'est pas l'idéal dans mon cas. J'ai utilisé date.js pour analyser la valeur afin qu'elle renvoie toujours le format de date souhaité. Jetez un œil à cet exemple de violon Exemple .

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"));   
    }
}
Princa
la source
0

J'avais besoin de mettre à jour à plusieurs reprises mes données à partir du serveur, mais je n'ai pas tout à fait terminé le travail pour le partage de mes besoins ci-dessous (mon 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 le modèle 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>
Ken
la source
0

Peu de gens ont demandé des options de sélecteur de date dynamiques. Dans mon cas, j'avais besoin d'une plage de dates dynamique - donc 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 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 ai utilisés comme ça dans mon modèle:

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

L'utilisation de liaisons personnalisées fournies dans les réponses précédentes n'est pas toujours possible. Appel$(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é, le input s étant insérés 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 qui permet 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ée pour la inputvaleur de:

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

Et enfin, lors de l'initialisation du datepicker, utilisez son onSelectoption:

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

De cette façon, chaque fois qu'un utilisateur change la date avec le sélecteur de date, l'observable Knockout correspondant est également mis à jour.

ololoepepe
la source