web-dev-qa-db-fra.com

Knockout.js Rendre chaque objet imbriqué observable

J'utilise Knockout.js comme bibliothèque MVVM pour lier mes données à certaines pages. Je suis en train de construire une bibliothèque pour faire REST appels à un service Web. Mon service Web RESTful renvoie une structure simple:

{
    id : 1,
    details: {
        name: "Johnny",
        surname: "Boy"
    }
}

J'ai un parent principal observable, myObject. Quand je fais

myObject(ko.mapping.fromJS(data))

les observables dans myObject sont:

  • id
  • name
  • surname

Comment puis-je rendre details (et théoriquement n'importe quel objet de la structure un observable)? J'ai besoin de ce comportement pour pouvoir définir un observable calculé sur les détails et être remarqué dès que l'une des données internes change.

J'ai mis en place une fonction récursive de base qui devrait faire l'affaire. Ce n'est pas, bien sûr, myObject.details ne devient pas observable.

// Makes every object in the tree an observable.
var makeAllObservables = function () {
    makeChildrenObservables(myObject);
};
var makeChildrenObservables = function (object) {
    // Make the parent an observable if it's not already
    if (!ko.isObservable(object)) {
        if ($.isArray(object))
            object = ko.observableArray(object);
        else
            object = ko.observable(object);
    }
    // Loop through its children
    for (var child in object()) {
        makeChildrenObservables(object()[child]);
    }
};

Je suis presque sûr qu'il s'agit de références incorrectes, mais comment puis-je résoudre ce problème? Je vous remercie.

34
frapontillo

Je ne pense pas que le knockout ait une manière intégrée d'observer les changements des éléments enfants. Si je comprends votre question, lorsque quelqu'un change le nom, vous voulez qu'un changement aux détails en tant qu'entité soit remarqué. Pouvez-vous donner un exemple concret de la façon dont vous utiliseriez cela? Souhaitez-vous utiliser un abonnement aux détails observables pour effectuer une action?

La raison pour laquelle votre code ne rend pas les détails observables est que javascript est passé par valeur, donc changer la valeur de l'argument 'objet' dans votre fonction ne change pas la valeur réelle que vous avez passée, seulement la valeur de l'argument à l'intérieur de votre une fonction.

Modifier

Si les changements se propageront automatiquement aux parents, cela devrait rendre tous les enfants observables je pense, mais votre racine que vous passez la première fois devrait déjà être observable .

// object should already be observable
var makeChildrenObservables = function (object) {
    if(!ko.isObservable(object)) return;

    // Loop through its children
    for (var child in object()) {
        if (!ko.isObservable(object()[child])) {
            object()[child] = ko.observable(object()[child]);
        }
        makeChildrenObservables(object()[child]);
    }
};
14
Jason Goemaat

J'utiliserais le plugin de mappage knockout .

var jsonData = {
    id : 1,
    details: {
        name: "Johnny",
        surname: "Boy"
    }
}

var yourMapping = {
    'details': {
        create: function(options) {
            return Details(options.data);
        }
    }
}

function Details(data) {
    ko.mapping.fromJS(data, {}, this);
}

function YourObjectName() {
    ko.mapping.fromJS(jsonData, yourMapping, this);
}

Cela créera votre hiérarchie d'objets avec tous les enfants comme observables.

23
Paolo del Mundo

En utilisant Knockout-Plugin nous pouvons rendre les éléments enfants observables. Nous avons beaucoup d'options pour gérer la façon dont nous voulons que nos données soient observables.

Voici un exemple de code:

var data = {
    people: [
        {
            id: 1,
            age: 25,
            child : [
                {id : 1,childname : "Alice"},
                {id : 2,childname : "Wonderland"}
            ]
        }, 
        {id: 2, age: 35}
    ],
    Address:[
        {
            AddressID : 1,
            City : "NewYork",
            cities : [
                {
                    cityId : 1,
                    cityName : "NewYork"
                },
                {
                    cityId :2,
                    cityName : "California"
                }
            ]
        },
        {
            AddressID : 2,
            City : "California",
            cities : [
                {
                    cityId :1,
                    cityName : "NewYork"
                },
                {
                    cityId :2,
                    cityName : "California"
                }
            ]
        }
    ],
    isSelected : true,
    dataID : 6
};
var mappingOptions = {
    people: {
        create: function(options) {
            console.log(options);
            return ko.mapping.fromJS(options.data, childmappingOptions);
        }
    },
    Address: {
        create: function(options) {
            console.log(options);
            return ko.mapping.fromJS(options.data, childmappingOptions);
        }
    }
};
var childmappingOptions = {
    child: {
        create: function(options) {
            return ko.mapping.fromJS(options.data, { observe: ["id","childname"]});
        }
    },
    cities :{
        create: function(options) {
            return ko.mapping.fromJS(options.data, { observe: ["cityId","cityName"]});
        }
    }
};
var r = ko.mapping.fromJS(data, mappingOptions);

J'ai joint un violon fonctionnel: http://jsfiddle.net/wmqTx/5/

3
AR M

D'après ce que j'ai vécu, ko.mapping.fromJS ne fait pas un objet observable.

Disons que vous avez ce constructeur ViewModel:

var VM = function(payload) {
  ko.mapping.fromJS(payload, {}, this);
}

et cet objet de données:

var data1 = {
  name: 'Bob',
  class: {
    name: 'CompSci 101',
    room: 112
  }

}

et vous utilisez data1 pour créer VM1:

var VM1 = new VM(data1);

Alors VM1.class ne sera pas un ko.observable, c'est un simple objet javascript.

Si vous créez ensuite un autre modèle de vue à l'aide d'un objet de données avec un membre de classe nul, c'est-à-dire:

var data2 = {
  name: 'Bob',
  class: null
}
var VM2 = new VM(data2);

alors VM2.class est un ko.observable.

Si vous exécutez ensuite:

ko.mapping(data1, {}, VM2)

alors VM2.class reste un ko.observable.

Ainsi, si vous créez un ViewModel à partir d'un objet de données d'origine où les membres d'objet sont nuls, puis les remplissez avec un objet de données rempli, vous aurez des membres de classe observables.

Cela entraîne des problèmes, car parfois les membres de l'objet sont observables, et parfois ils ne le sont pas. Les liaisons de formulaire fonctionneront avec VM1 et ne fonctionneront pas avec VM2. Ce serait bien si ko.mapping.fromJS faisait toujours de tout un ko.observable donc c'était cohérent?

3
Beans

Je vais étendre la réponse de Paolo del Mundo (qui, je pense, pourrait facilement être la meilleure et la seule solution en ce moment) avec mon exemple de solution.

Considérez l'objet original de frapontillo:

{
    id : 1,
    details: {
        name: "Johnny",
        surname: "Boy"
    }
}

La propriété details elle-même est un objet et en tant que telle NE PEUT PAS être observable. Il en va de même pour la propriété User dans l'exemple ci-dessous, qui est également un objet. Ces deux objets ne peuvent pas être observables mais leurs propriétés LEAF peuvent être.

Chaque propriété de feuille de votre arbre/modèle de données PEUT ÊTRE UN OBSERVABLE. La façon la plus simple d'y parvenir est que vous définissez correctement le modèle de mappage avant de le passer au plugin de mappage as paramètre.

Voir mon exemple ci-dessous.

EXEMPLE:

Disons que nous devons afficher une page/vue html où nous avons une liste d'utilisateurs sur une grille. À côté de la grille Utilisateurs, un formulaire de modification d'un utilisateur sélectionné dans la grille s'affiche.

ÉTAPE 1: DÉFINITION DES MODÈLES

function UsersEdit() {
    this.User = new User();                    // model for the selected user      
    this.ShowUsersGrid = ko.observable(false); // defines the grid's visibility (false by default)
    this.ShowEditForm = ko.observable(false);  // defines the selected user form's visibility (false by default)      
    this.AllGroups = [];                       // NOT AN OBSERVABLE - when editing a user in the user's form beside the grid a multiselect of all available GROUPS is shown (to place the user in one or multiple groups)
    this.AllRoles = [];                        // NOT AN OBSERVABLE - when editing a user in the user's form beside the grid a multiselect of all available ROLES is shown (to assign the user one or multiple roles)
}

function User() {
    this.Id = ko.observable();
    this.Name = ko.observable();
    this.Surname = ko.observable();
    this.Username = ko.observable();
    this.GroupIds = ko.observableArray(); // the ids of the GROUPS that this user belongs to
    this.RoleIds = ko.observableArray();  // the ids of the ROLES that this user has
}

ÉTAPE 2: CARTOGRAPHIE (POUR OBTENIR DES OBSERVABLES IMBRIQUÉS)

Disons que c'est votre modèle JSON brut avec des données que vous souhaitez mapper et obtenir un modèle KO avec des observables imbriqués.

var model = {
    User: {
        Id: 1,
        Name: "Johnny",            
        Surname = "Boy",
        Username = "JohhnyBoy",
        GroupIds = [1, 3, 4],
        RoleIds = [1, 2, 5]
    }
};

Maintenant que tout cela est défini, vous pouvez mapper:

var observableUserEditModel = ko.mapping.fromJS(model, new UsersEdit());

ET VOUS AVEZ FAIT!:)

L'observableUserEditModel contiendra tous vos observables, même imbriqués. Maintenant, la seule chose dont vous devez vous occuper pour le tester est de lier l'objet observableUserEditModel à votre code HTML. Astuce: utilisez la liaison with et testez la structure de données observable observableUserEditModel en l'insérant dans votre vue HTML:

<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
2
AlexRebula

Peut-être que dans une future version, il pourrait y avoir une option de configuration qui fait que ko.mapping.fromJS crée toujours des observables. Il peut être activé pour les nouveaux projets ou après la mise à jour des liaisons d'un projet existant.

Ce que je fais pour éviter ce problème, c'est de m'assurer que les graines du modèle ont toujours des propriétés de membre Objects remplies, à tous les niveaux. De cette façon, toutes les propriétés d'objet sont mappées en tant que POJO (Plain Old Javascript Objects), de sorte que le ViewModel ne les initialise pas en tant que ko.observables. Cela évite le problème "parfois observables, parfois non".

Cordialement, Mike

1
Beans