问题
So I've been at this back and forth for quite a long time now, so I'll just get to the point.
I am creating an application based on the HotTowel template, so I'm using knockout, breeze, Q etc. I'm using a breeze query to get some data from the server, and then I load that data into the first select (as options). This changes the selectedModel observable and the subscription triggers ( the subscription is the current "solution", I have used data-bind etc with the same results). The subscription then takes the new selectedModel value and uses it to get data for the second observable. This query also passes and the results are stored in the modelProperties observable array, yet the UI does NOT update the second select (it's empty). The strange thing is, pressing ctrl + F5 (i tried about 50 times in a row) always populates it correctly, but NEVER on the first load.
TLDR; 2 selects, selected option of the first one defines the query for the options of the second one. Queries work, observables get populated, first select is always populated, second never on first boot but always on the following ctrl+F5.
Here are some snippets regarding this piece of the application: The viewmodel:
define(['services/logger', 'services/datacontext'], function (logger, datacontext) {
var models = ko.observableArray();
var modelProperties = ko.observableArray();
var selectedModel = ko.observable();
var init = true;
function activate() {
logger.log('Search View Activated', null, 'search', true);
return datacontext.getModels(models);
}
var sub = selectedModel.subscribe(function (newValue) {
return datacontext.getModelProperties(modelProperties, newValue);
});
var vm = {
activate: activate,
models: models,
selectedModel: selectedModel,
modelProperties: modelProperties,
title: 'Search View'
};
return vm;
The view's code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body id="one">
<select id="entitySelect" class ="entitySelect" name="first"
data-bind="options: models, value: selectedModel">
</select>
<select id="propertySelect" class ="propertySelect" data-bind=
'options:modelProperties'></select>
</body>
</html>
And the methods in the datacontext:
var getModels = function (modelsObservable) {
var query = EntityQuery.from('Models');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
logger.log("Query succeeded", true);
logger.log(data);
if (modelsObservable) {
modelsObservable(data.results);
}
logger.log('Retrieved [Models] from remote data source',
data, true);
}
function queryFailed(data) {
logger.log(data.toString(), data, true);
}
};
var getModelProperties = function (propertiesObservable,modelName) {
var query = EntityQuery.from('ModelProperties').withParameters({ name: modelName });
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
logger.log("Query succeeded", null, true);
logger.log(data);
if (propertiesObservable) {
propertiesObservable(data.results);
}
logger.log('Retrieved [Properties] from remote data source',
data, true);
}
function queryFailed(data) {
logger.log(data.toString(), data, true);
}
};
Once again I'd like to point out that all the queries do pass, and the observableArrays do get populated with the results (checked with .peek() ).
Here are some screenshots to clarify the problem: On first boot from VS : http://i.imgur.com/8Gd53Yh.png
Then, ctrl+F5 http://i.imgur.com/vzO8d70.png
I have tried pretty much everything I can think of, and googled everything that came to mind, but to no avail. Any help would be greatly appreciated! Also, this is my first post here so I apologize for any mistakes!
回答1:
Here are a few comments -
Your view makes no sense. If you are using something like the HotTowel template then there is a lot of extra mark-up that is not needed there, considering you are most likely using Durandal.js for composition and the additional mark-up could be hurting you.
If you are indeed loading a 'subview' more or less then this is what it should look like -
<select id="entitySelect" class ="entitySelect" name="first" data-bind="options: models, value: selectedModel"></select>
<select id="propertySelect" class ="propertySelect" data-bind='options:modelProperties'></select>
Next, if you are using cascading dropdowns I have mentioned this method before and I strongly suggest it for everyone. Make your second dependent on your first with a ko.computed.
var models = ko.observableArray();
var modelProperties = ko.computed(function () {
var modelArr = ko.observableArray();
if (!selectedModel()) { return modelArr; }
datacontext.getModelProperties(modelArr, selectedModel().then(function() { return modelArr(); });
});
Now modelProperties will be null until you select a model, then it will go out and get the properties of that model.
It's also important to understand the difference between the function declarations you are using. var myFunc = function () {}; is a constructor function and will evaluate immediately on view model instantiation. function myFunc () {} is a standard function and will wait until called before evaluating.
回答2:
I'm a little late to this question, but here's how I was able to accomplish cascading dropdowns. I hope this helps anyone who has been struggling with this.
ViewModel
define(['durandal/system', 'durandal/app', 'jquery', 'knockout', 'services/projectdetailmanager'],
function (system, app, $, ko, pdm) {
var projectValues = ko.observableArray();
var taskValues = ko.observableArray();
activate = function () {
return pdm.getAllContracts(projectValues);
}
selectedProject.subscribe(function () {
pdm.getSelectedTasks(taskValues, selectedProject().iD()).then(function () {
return taskValues;
});
});
return {
activate: activate,
projectValues: projectValues,
selectedProject: selectedProject,
taskValues: taskValues,
selectedTask: selectedTask
};
});
HTML
<div>
<table>
<thead>
<tr>
<td style="font-weight:bold;">Projects</td>
<td style="font-weight:bold;">Tasks</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<select data-bind='options: projectValues, optionsText: "name", optionsCaption: "Select...", value: selectedProject'> </select>
</td>
<td>
<select data-bind='options: taskValues, optionsText: "name", optionsCaption: "Select...", value: selectedTask, event: { change: yourChangeEvent}'> </select>
</td>
</tr>
</tbody>
</table>
</div>
DataContext/Service
function getAllContracts(projectValues) {
var query = breeze.EntityQuery
.from("Contract");
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
system.log("Query succeeded");
system.log(data);
if (projectValues) {
projectValues(data.results);
}
}
function queryFailed(data) {
system.log("Query failed");
}
};
function getSelectedTasks(taskValuesArr, projectID) {
var query = breeze.EntityQuery
.from("Task")
.where("contractID", "==", projectID);
system.log("Within getSelectedTask; projectID = " + projectID);
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
system.log("Query succeeded");
system.log(data);
if (taskValuesArr) {
taskValuesArr(data.results);
}
}
function queryFailed(data) {
system.log("Query failed");
}
};
来源:https://stackoverflow.com/questions/18139120/using-knockout-and-breeze-with-cascading-dropdowns