正如标题中所写:当我将对象添加到 ko.observableArray 类型的属性时,knockoutjs 会更新视图,这对我来说很好。
问题是视图更新后视图本身距左侧约 100 像素。令人惊讶的是,当我通过鼠标左键单击选择整个视图时,视图修复并且左边距影响消失了。顺便说一句,这发生在 IE 8 中。
我也在 Chrome 中尝试过,同样的事情发生了:以某种方式将左边距应用于视图;相反,这次选择视图并不能解决我在前面段落中描述的问题。
这也可能与 jquery 有关,但我真的不确定。
您可能会看到下面的视图和视图模型(有点复杂。)我还放了与问题相关的屏幕截图(在 IE8 上)。
在我按下 Enter 键之前:“http://img560.imageshack.us/img560/6598/minqbeforeenter.png”
按 Enter 键后:“http://img221.imageshack.us/img221/1905/minqafterenter.png”
在我用鼠标左键选择后:“http://img689.imageshack.us/img689/882/minqafterselect.png”
我正在使用的脚本(当我在文本框 AdvancedSearchControlViewModel 的 searchValue_keydown 方法上按 Enter 键时)
function SearchControlViewModel(context) {
var normalizeSelectItems = function (selectItems) {
var normalizedItems = [];
if (!(selectItems && $.isArray(selectItems) && selectItems.length > 0))
return normalizedItems;
for (var index = 0; index < selectItems.length; index++) {
var item = selectItems[index];
if (item.Items && $.isArray(item.Items)) {
if (item.Items.length <= 0)
continue;
var group = { Value: item.Value, Description: item.Description };
for (var index2 = 0; index2 < item.Items.length; index2++) {
var subItem = item.Items[index2];
normalizedItems.push(new SelectItem({ Value: subItem.Value, Description: subItem.Description, Group: group }));
}
} else {
normalizedItems.push(new SelectItem({ Value: item.Value, Description: item.Description, Group: { Description: ''} }));
}
}
return normalizedItems;
};
var normalizedSelectItems = normalizeSelectItems(context.SelectItems);
context.SelectedValues = normalizeSelectItems(context.SelectedValues);
$.each(context.SelectedValues, function (selectedIndex, selectedItem) {
var selItem;
$.each(normalizedSelectItems, function (selectIndex, selectItem) {
if (selectItem.Value === selectedItem.Value) {
selItem = selectItem;
return false;
}
});
if (selItem) {
context.SelectedValues[selectedIndex] = selItem;
}
});
var self = this;
self.allOptions = 'All';
self.selectItems = ko.observableArray(normalizedSelectItems);
self.searchContext = new SearchContext(context);
self.DropDownCheckList = function (elements) {
$('select', elements).dropdownchecklist({
firstItemChecksAll: true,
forceMultiple: true,
onItemClick: function (checkBox, originalSelect) {
var selectedItem;
for (var selectedItemIndex = 0; selectedItemIndex < originalSelect.options.length; selectedItemIndex++) {
if (originalSelect.options[selectedItemIndex].value === checkBox.val()) {
selectedItem = originalSelect.options[selectedItemIndex];
break;
}
}
selectedItem.selected = checkBox.attr('checked') === 'checked';
},
maxDropHeight: 300,
width: 250,
emptyText: 'Please Select...'
/*,
textFormatFunction: function (options) {
return 'Filtre';
}*/
});
};
self.Validate = function () {
var check;
check = self.searchContext.searchValue();
if (check === null || check === undefined ||
check === '')
return false;
check = self.searchContext.selectedItems();
if (check === null || check === undefined ||
!$.isArray(check) || check.length === 0)
return false;
var hasAtLeastOneItem = false;
$.each(check, function (index) {
if (check[index]) {
hasAtLeastOneItem = true;
return false;
}
});
return hasAtLeastOneItem;
};
self.GetConvertedJSONObj = function () {
var obj = {
SelectedValues: [],
SearchValue: self.searchContext.searchValue()
};
var selectedItems = self.searchContext.selectedItems();
if (selectedItems) {
for (var i = 0; i < selectedItems.length; i++) {
var selItem = selectedItems[i];
if (selItem === null || selItem === undefined)
continue;
var pushArray;
if (selItem.Group.Description !== '') {
var group = undefined;
$.each(obj.SelectedValues, function (index, item) {
if (item.Items && $.isArray(item.Items)) {
if (item.Value === selItem.Group.Value) {
group = item;
return false;
}
}
});
if (group === undefined) {
pushArray = [];
obj.SelectedValues.push({
Value: selItem.Group.Value,
Items: pushArray
});
} else {
pushArray = group.Items;
}
} else {
pushArray = obj.SelectedValues;
}
pushArray.push({
Value: selItem.Value
});
}
}
return obj;
};
self.ConvertToJSON = function () {
var obj = self.GetConvertedJSONObj();
return JSON.stringify(obj);
};
}
function SearchContext(context) {
var self = this;
self.selectedItems = ko.observableArray(context.SelectedValues);
self.searchValue = ko.observable(context.SearchValue);
}
function SelectItem(context) {
var self = this;
self.Value = context.Value;
self.Text = context.Description;
self.Group = context.Group;
}
function SearchFilter(context) {
var self = this;
var getSelectedItemsDescription = function (selectedItems) {
var text = '';
var selectedValues = context.template.searchContext.selectedItems();
for (var i = 0; i < selectedValues.length; i++) {
var selectedValue = selectedValues[i];
text += selectedValue.Text;
if (i + 1 < selectedValues.length)
text += ', ';
}
return text;
};
self.selectedValuesDescription = getSelectedItemsDescription(
context.template.searchContext.selectedItems()
);
self.searchValueText = context.template.searchContext.searchValue();
self.filterCombineType = context.defaultCombineType.key;
self.GetConvertedJSONObj = function () {
var obj = context.template.GetConvertedJSONObj();
obj.CombineType = self.filterCombineType;
return obj;
};
self.ConvertToJSON = function () {
var obj = self.GetConvertedJSONObj();
return JSON.stringify(obj);
};
}
function AdvancedSearchControlViewModel(context) {
var self = this;
self.SearchControlViewModelTemplate = ko.observable(new SearchControlViewModel(context.data.TemplateSearch));
// key , value
self.SearchFilterCombineTypes = [];
for (var ct in context.data.SearchCombineTypes) {
self.SearchFilterCombineTypes.push({ key: ct, value: context.data.SearchCombineTypes[ct] });
}
self.searchFilters = [];
if ($.isArray(context.data.Searchs) && context.data.Searchs.length > 0) {
for (var i = 0; i < context.data.Searchs.length; i++) {
var search = context.data.Searchs[i];
var searchFilter;
$.each(self.SearchFilterCombineTypes, function (index, element) {
if (element.key === search.CombineType.toString()) {
searchFilter = element;
return false;
}
});
search.SelectItems = context.data.TemplateSearch.SelectItems;
self.searchFilters.push(
new SearchFilter({ template: new SearchControlViewModel(search), defaultCombineType: searchFilter })
);
}
}
self.searchFilters = ko.observableArray(self.searchFilters);
self.addSearchFilter = function () {
if (self.SearchControlViewModelTemplate().Validate()) {
self.searchFilters.push(new SearchFilter({ template: self.SearchControlViewModelTemplate(), defaultCombineType: self.SearchFilterCombineTypes[0] }));
self.SearchControlViewModelTemplate(new SearchControlViewModel(context.data.TemplateSearch));
}
};
self.searchValue_keydown = function (sender, event) {
if (event.keyCode === $.ui.keyCode.ENTER) {
// Code block to prevent submit
/*var fnDontSubmit = function () {
$(this).unbind('submit', fnDontSubmit);
return false;
};
$(event.currentTarget.form).bind('submit', fnDontSubmit);*/
self.SearchControlViewModelTemplate().searchContext.searchValue(event.srcElement.value);
self.addSearchFilter();
}
return true;
};
self.iButton = function (elements, bindings) {
var boundSearchFilter = bindings;
var elem = $(elements).filter('[type="checkbox"]');
if (boundSearchFilter.filterCombineType === self.SearchFilterCombineTypes[1].key)
elem.attr('checked', 'checked');
else
elem.removeAttr('checked');
elem.iButton({
labelOn: self.SearchFilterCombineTypes[1].value,
labelOff: self.SearchFilterCombineTypes[0].value,
change: function (sender, event) {
var selectedVal = self.SearchFilterCombineTypes[sender.is(':checked') ? 1 : 0];
boundSearchFilter.filterCombineType = selectedVal.key;
// if you remove this line and
// make filterCombineType an observable
// the iButton animation wont work. So We're binding the checkbox manually.
ko.applyBindings(self, $('#' + context.valueId)[0]);
}
});
var isLastElem = $(self.searchFilters()).last()[0] === boundSearchFilter;
if (isLastElem)
elem.iButton('disable');
};
self.ConvertToJSON = function () {
var jsonFilterArr = [];
for (var i = 0; i < self.searchFilters().length; i++) {
var searchFilter = self.searchFilters()[i];
jsonFilterArr.push(searchFilter.GetConvertedJSONObj());
}
return JSON.stringify({
Searchs: jsonFilterArr
});
};
}
if (!$.data(document, "BindKnockoutjsControl")) {
$.data(document, "BindKnockoutjsControl", function (context) {
switch (context.controlType) {
case 'SearchControl':
var viewModel = new SearchControlViewModel(context.dataForViewModel);
ko.applyBindings(viewModel, $('#' + context.controlDisplayIdToBind)[0]);
ko.applyBindings(viewModel, $('#' + context.controlValueIdToBind)[0]);
break;
case "AdvancedSearchControl":
var viewModel = new AdvancedSearchControlViewModel({ data: context.dataForViewModel, valueId: context.controlValueIdToBind });
ko.applyBindings(viewModel, $('#' + context.controlDisplayIdToBind)[0]);
ko.applyBindings(viewModel, $('#' + context.controlValueIdToBind)[0]);
break;
default:
throw 'Unknown controlType: ' + context.controlType;
break;
}
});
}
风景
<script id="AdvancedSearchControl_View" type="text/html">
<table style="display:inline-block; width: 25%;">
<thead data-bind="template: { name: 'SearchControl_View' }">
</thead>
<tbody data-bind="template: { name: 'AdvancedSearchControlItem_View', foreach: $data.searchFilters }">
</tbody>
</table>
</script>
<script id="SearchControl_View" type="text/html">
<tr>
<td>
@Html.LabelFor(model => model.AdvancedSearchViewModel, new { @class = "SearchControlCaption" })
</td>
<td></td>
<td></td>
</tr>
<tr>
<td style="padding-top:9px;">
<select multiple="multiple" data-bind="options: SearchControlViewModelTemplate().selectItems, selectedOptions: SearchControlViewModelTemplate().searchContext.selectedItems, optionsText: 'Text', optionsGroup: function(item){return item.Group !== '' ? item.Group.Description : ''}, optionsCaption: SearchControlViewModelTemplate().allOptions"></select>
</td>
<td>
<input type="text" class="SearchValue" data-bind="
value: SearchControlViewModelTemplate().searchContext.searchValue,
event: {
keydown: searchValue_keydown
}
"
/>
</td>
<td>
<input type="image" src="@Url.Content("~/Content/Images/" + Models.ResourceFiles.Resources._SearchButtonFileName)"
data-bind="enable: $data.searchFilters().length > 0" />
</td>
</tr>
</script>
<script id="AdvancedSearchControlItem_View" type="text/html">
<tr>
<td data-bind="text: $data.selectedValuesDescription"></td>
<td data-bind="text: $data.searchValueText"></td>
<td />
</tr>
<tr>
<td data-bind="template: { name: 'AdvancedSearchControlItemRadios_View', afterRender: $root.iButton }">
</td>
<td />
<td />
</tr>
</script>
<script id="AdvancedSearchControlItemRadios_View" type="text/html">
<input type="checkbox" name="AndOr" />
</script>