Handling arbitrary options with metadata from serv

2019-03-06 01:49发布

I have a view-model where some properties values available for selection are dictated by other properties, this is set via the requires field:

var clusterOptions = [{
    name: "None",
    sku: "0",
    price: 0,
}, {
    name: "Standard MySQL Cluster",
    sku: "4101",
    requires: ["MySQL1"],
    price: 10,
}, {
    name: "Enterprise MS SQL Cluster",
    sku: "4102",
    requires: ["402"],
    price: 5,
}, {
    name: "NoSQL Sharding",
    sku: "4103",
    requires: ["403","404"],
    price: 10,
}];

The code works (you can see how clicking different options changes dependent options database and database clustering): http://jsfiddle.net/g18c/DTdyM/

This code is statically typed, and i am trying to convert this so that arbitrary data (and requires mappings) can be sent from the server viewmodel created with ko.mapping.

The code to calculate available options for selection is in a helper function (example shown below for two dependent properties from my initial statically defined example):

self.availableDatabases = myutils.ko.createComputedDepdency(this.selectedOs, this.dbOptions);
self.availableClusteringOptions = myutils.ko.createComputedDepdency(this.selectedDb, this.dbClusteringOptions);

I have rewritten my serverData and the only things i need to know form my model are the selected items of the arrays passed in dynamically from the server, in this case it is the options array: selectedServerOption, selectedOsOption, selectedDatabaseOption and selectedClusterOption

I am stuck handling the mapping and unsure how to work with the requiresMapping array.

How can I handle the mapping of the option fields below?

var serverData = {
    options: [serverOptions, osOptions, databaseOptions, clusterOptions],

    requiresMappings: [
        {target: "selectedOs", options: "dbOptions"},
        {target: "selectedDb", options: "dbClusteringOptions"}
    ]
}

var mappingScheme = {
    'options' : {
        create: function(options){
            console.log("creating sku: " + options.data.sku);
            // 1) create dependency using requiresMappings property
            // myutils.ko.createComputedDepdency(this.selectedOs, this.dbOptions);

            // 2) subscribe to updates
            // self.availableDatabases.subscribe(function () {self.selectedDb(self.availableDatabases()[0].sku);});
        }
    },
    // ignore these mappings we don't want to observe them, they are used to define mappings for the creation function above
    'ignore' : ["requiresMappings"]
}

var viewModel = ko.mapping.fromJS(serverData, mappingScheme);

My current fiddle is here: http://jsfiddle.net/g18c/DTdyM/5/

1条回答
太酷不给撩
2楼-- · 2019-03-06 02:19

After reviewing Automate mapping of dependent properties in knockout answer and the code from @PW Kad, it made sense to write a simple custom mapper.

I chose to write my own mapper over ko.mapping as i couldn't work out how to reference the mapping for the requires (with ko.mapping), and in particular how to make multiple properties from the create mapping function (as i could see i could only return a single new property, not multiple as i would need).

It works seemingly well, but would appreciate comments or an alternative using ko.mapping just to help my understanding especially if i have reinvented the wheel!

Fiddle is here: http://jsfiddle.net/g18c/DTdyM/26/

var myutils = myutils || {}; // if namespace is not defined, make it equal to an empty object

myutils.ko = (function(){
    var createComputedDepdency = function(targetDependency, options){
        var computedProperty = ko.computed(function(){
            var targetValue = targetDependency();

            if(typeof targetValue === "undefined")
                return [];

            return ko.utils.arrayFilter(options, function(opt){
                if(typeof opt.requires === "undefined")
                    return true;
                else
                    return opt.requires && opt.requires.indexOf(targetValue) > -1;
            });
        });        
        return computedProperty;
    };

    var createProductViewModel = function(options){

        var viewModel = {};      
        var length = options.length; 

        console.log(length);

        for (var i = 0; i < length; i++) {
            var option = options[i];

            console.log("creating property: " + option.selectedName + ", options: " + option.optionsName);

            // create the property for selected value, i.e. object.firstName = ko.observable();
            viewModel[option.selectedName] = ko.observable();

            if(option.requires)
            {
                var computedOptions = createComputedDepdency(viewModel[option.requires.target],option.data);
                viewModel[option.optionsName] = computedOptions;

                console.log("making callback scope object for: " + option.optionsName );
                var callbackScope = {
                    callbackName: option.optionsName,
                    options: computedOptions,
                    selectedValue: viewModel[option.selectedName]
                };

                // when the list of available options changes, set the selected property to the first option
                computedOptions.subscribe(function () {
                    var scope = this;
                    console.log("my object: %o", scope);   
                    scope.selectedValue(scope.options()[0].sku);
                    console.log("in subscribe function for..." + scope.callbackName);
                },callbackScope);
            }
            else
            {
                // create the property holding values, i.e. object.nameOptions = serverData.names;
                viewModel[option.optionsName] = option.data; 
            } 
        }

        // now all options have been created, loop through the array one last time and set the dependent options.
        // note that this should be done last, as dependent calculated properties will subscribed to these events
        // and update their default first options
        for (var x = 0; x < length; x++) {
            var option = options[x];

            // only need to do this to non-calculated values
            if(!option.requires)
            {
                viewModel[option.selectedName](viewModel[option.optionsName][0].sku);
            }
        }

        return viewModel;
    };

    return{
        createProductViewModel: createProductViewModel
    };
}());

var serverOptions = [{
    name: "DELL R210",
    price: 100,
    sku: 1001,
},{
    name: "DELL R710",
    price: 200,
    sku: 1002,
},{
    name: "DELL R720 Dual CPU",
    price: 300,
    sku: 1003,
}];

var osOptions = [{
    name: "Windows Standard",
    sku: "201",
    price: 1,
}, {
    name: "Windows Enterprise",
    sku: "202",
    price: 2,
}, {
    name: "CentOS",
    sku: "203",
    price: 0,
}, {
    name: "Debian",
    sku: "204",
    price: 4,
}];

var databaseOptions = [{
    name: "None",
    sku: "0",
    price: 0,
}, {
    name: "SQL Express",
    sku: "401",
    requires: ["201", "202"],
    price: 10,
}, {
    name: "SQL Standard",
    sku: "402",
    requires: ["202"],
    price: 5,
}, {
    name: "MySQL",
    sku: "MySQL1",
    requires: ["201", "202", "203"],
    price: 11,
}, {
    name: "RavenDb",
    sku: "403",
    requires: ["203"],
    price: 12,
}, {
    name: "MongoDB",
    sku: "404",
    requires: ["204"],
    price: 13,
}];

var databaseClusterOptions = [{
    name: "None",
    sku: "0",
    price: 0,
}, {
    name: "Standard MySQL Cluster",
    sku: "4101",
    requires: ["MySQL1"],
    price: 10,
}, {
    name: "Enterprise MS SQL Cluster",
    sku: "4102",
    requires: ["402"],
    price: 5,
}, {
    name: "NoSQL Sharding",
    sku: "4103",
    requires: ["403","404"],
    price: 10,
}];

var serverData = {
    options: [
        { data: serverOptions, selectedName: "selectedServer", optionsName: "serverOptions" },
        { data: osOptions, selectedName: "selectedOs", optionsName: "osOptions" },
        { data: databaseOptions, selectedName: "selectedDb", optionsName: "availableDatabases", requires: { target: "selectedOs" } },
        { data: databaseClusterOptions, selectedName: "selectedDbCluster", optionsName: "availableClusteringOptions", requires: { target: "selectedDb" } }
    ]
};

var viewModel = myutils.ko.createProductViewModel(serverData.options);

ko.applyBindings(viewModel);
查看更多
登录 后发表回答