这是问题所在,
我实际上必须管理可以包含数据库中定义的其他对象的对象。例如,我有5种盒子。一个红盒子,一个绿色盒子,一个蓝色盒子,一个黄色盒子和一个黑盒子。
每个框可以包含一个框,也可以包含一个框,依此类推。
我收到的是这种对象:
{
"id":1,
"type":"black",
"box":
{
"id":8,
"type":"red",
"box":
{
"id":15,
"type":"green",
"box":null
}
}
}
所以这个例子是:一个黑盒子,包含一个红盒子,包含一个空的绿框。(黑色 -> 红色 -> 绿色 ->空(
有条件 :
- 黑匣子只能包含蓝色、绿色和红色,
- 红色框只能包含绿色和黄色,
- 黄色框不能包含任何内容,
- 其他框(绿色和蓝色(可以包含任何东西
我需要做的是某种"盒子集编辑器",我收到一个盒子对象,无论是否复杂(这意味着它只能有一个或几个盒子级别(。我必须在选择框列表中表示它,因此,对于我编写的示例,它将显示以下内容:
<select name="LEVEL_1">
<option value="0">NONE</option>
<option selected value="1">black</option>
<option value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
<option value="10">yellow</option>
</select>
<br/>
<select name="LEVEL_2">
<option value="0">NONE</option>
<option selected value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
</select>
<br/>
<select name="LEVEL_3">
<option value="0">NONE</option>
<option selected value="15">green</option>
<option value="10">yellow</option>
</select>
<br/>
<select name="LEVEL_4">
<option selected value="0">NONE</option>
<option value="15">green</option>
<option value="8">red</option>
<option value="3">blue</option>
<option value="10">yellow</option>
<option value="1">black</option>
</select>
这必须通过AngularJS来实现。
整个示例出现在一个表格上,因此框以这种方式显示为表格:
<table>
<thead style="font-weight:bold;">
<tr style="background-color:lightblue;">
<td>Id</td>
<td>Type</td>
<td>Contains (sum)</td>
</tr>
</thead>
<tbody>
<tr ng-click="setCurrentBox();" style="background-color:lightgreen;">
<td>1</td>
<td>black</td>
<td>2 boxes</td>
</tr>
</tbody>
</table>
请注意ng-click
部分。setCurrentBox()
函数在控制器中定义,它设置从"BoxService
"接收的盒子对象,如$scope.currentBox
。
单击该行将调用BoxService
,检索所选框的 json 对象(完全!其中包含包含的框,如线程顶部所写(,并将其分配给 $scope.currentBox
变量。
值应"清空"下一个可能的选项(将"无"设置为选中并将可能的选项添加为选项(,如果有子框,则只需擦除它们(在我的示例中将黑色选择更改为红色 black->red->green->empty 将给出红色>空(无 - 选择- 以及绿色和黄色选项(。
就我而言,我只能直接访问$scope.currentBox
。而"currentBox
"包含的框是属性。所以,不知何故,我认为我应该做一些if object.box!=null
然后阅读框......但我对此有点迷茫...
好吧,我不知道我的问题定义是否足够清楚,这里有一个简短的小提琴,应该在这种"俄罗斯娃娃"问题中"显示我想得到的地方"......
http://jsfiddle.net/z267dquk/2/
更新 1 : http://jsfiddle.net/0js7q638/
感谢您的阅读/帮助
更新2:这是我的问题/我想做什么/我错过的似乎不清楚的确切含义的例子。
具体示例 - 启动情况:
盒子对象 :
Box 0 (black one)
contains Box 1 (red one)
contains Box 2 (green one)
contains Box 3 (green one)
contains Box 4 (green one)
contains nothing (yet)
当用户在表中选择框 0 时,他会得到这个对象:
{
"id":"1",
"type":"black",
"box":{
"id":"8",
"type":"red",
"box":{
"id":"15",
"type":"green",
"box":{
"id":"15",
"type":"green",
"box":{
"id":"15",
"type":"green",
"box":null
}
}
}
}
}
此对象必须显示在可编辑的选择框中,如下所示:
Box 0 (all box colors choices available here!):
<!--This select contains all possible choices since it is the very first choice possible, no dependency-->
<select name="box0">
<option value="">NO CHOICE</option>
<option selected value="1">black</option>
<option value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
<option value="10">yellow</option>
</select>
<br/>Box 1 (contained in box 0 box property) :
<!--This select contains only boxes choices that a black box can get (since it depends of box 0 value)-->
<select name="box1">
<option value="">NO CHOICE</option>
<option selected value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
</select>
<br/>Box 2 (contained in box 1 box property) :
<!--This select contains only boxes choices that a red box can get (since it depends of box 1 value)-->
<select name="box2">
<option value="">NO CHOICE</option>
<option selected value="15">green</option>
<option value="10">yellow</option>
</select>
<br/>Box 3 (contained in box 2 box property) :
<!--This select contains only boxes choices that a green box can get (since it depends of box 2 value)-->
<select name="box3">
<option value="">NO CHOICE</option>
<option value="1">black</option>
<option value="8">red</option>
<option selected value="15">green</option>
<option value="3">blue</option>
<option value="10">yellow</option>
</select>
<br/>Box 4 (contained in box 3 box property) :
<!--This select contains only boxes choices that a green box can get (since it depends of box 3 value)-->
<select name="box4">
<option value="">NO CHOICE</option>
<option value="1">black</option>
<option value="8">red</option>
<option selected value="15">green</option>
<option value="3">blue</option>
<option value="10">yellow</option>
</select>
<br/>Box 5 (empty box ready to be filled in box 4 property) :
<!--This select contains only boxes choices that a green box can get (since it depends of box 4 value)-->
<!--This select has default selected value set as null since box4 box property is not set (box 4 box property is not a box, box 4 contains nothing)-->
<select name="box5">
<option value="" selected>NO CHOICE</option>
<option value="1">black</option>
<option value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
<option value="10">yellow</option>
</select>
具体示例:用户操作 1 :
如果用户将框 2 设置为"无选择"或"黄色"(因为黄色框不能包含任何框(,则当前框对象应如下所示:
{
"id":"1",
"type":"black",
"box":{
"id":"8",
"type":"red",
"box":{
"id":"15",
"type":"green",
"box":null
}
}
}
HTML部分应该变成这样:
Box 0 (all box colors choices available here!):
<!--This select contains all possible choices since it is the very first choice possible, no dependency-->
<select name="box0">
<option value="">NO CHOICE</option>
<option selected value="1">black</option>
<option value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
<option value="10">yellow</option>
</select>
<br/>Box 1 (contained in box 0 box property) :
<!--This select contains only boxes choices that a black box can get (since it depends of box 0 value)-->
<select name="box1">
<option value="">NO CHOICE</option>
<option selected value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
</select>
<br/>Box 2 (contained in box 1 box property) :
<!--This select contains only boxes choices that a red box can get (since it depends of box 1 value)-->
<select name="box2">
<option selected value="">NO CHOICE</option>
<option value="15">green</option>
<option value="10">yellow</option>
</select>
具体示例:用户操作 1 :
如果用户将框 1 设置为 BLUE,则当前框对象应如下所示:
{
"id":"1",
"type":"black",
"box":{
"id":"3",
"type":"blue",
"box":null
}
}
HTML部分应该变成这样:
Box 0 (all box colors choices available here!):
<!--This select contains all possible choices since it is the very first choice possible, no dependency-->
<select name="box0">
<option value="">NO CHOICE</option>
<option selected value="1">black</option>
<option value="8">red</option>
<option value="15">green</option>
<option value="3">blue</option>
<option value="10">yellow</option>
</select>
<br/>Box 1 (contained in box 0 box property) :
<!--This select contains only boxes choices that a black box can get (since it depends of box 0 value)-->
<select name="box1">
<option value="">NO CHOICE</option>
<option value="8">red</option>
<option value="15">green</option>
<option selected value="3">blue</option>
</select>
<br/>Box 2 (contained in box 1 box property) :
<!--This select contains only boxes choices that a blue box can get (since it depends of box 1 value)-->
<select name="box2">
<option selected value="">NO CHOICE</option>
<option value="15">green</option>
<option value="8">red</option>
<option value="3">blue</option>
<option value="10">yellow</option>
<option value="1">black</option>
</select>
请注意,我可以从BoxService
中获得一个盒子的可能选择,或者任何盒子的所有可能选择。这必须来自BoxService
.此数据可能很大,在本例中很小,但这可能是可以包含在另一个对象中的一长串对象。
希望这个例子能让我的问题更清楚。
感谢您的阅读
试试这个例子:http://jsfiddle.net/kevalbhatt18/0js7q638/1/
使用checkInnerObject函数,它将返回"框"的计数,如示例中所示
function checkInnerObject(obj) {
var i = 0;
var arg = Array.prototype.slice.call(arguments, 1);
start: while (obj) {
if (obj.hasOwnProperty(arg)) {
obj = obj[arg];
i = i + 1;
continue start;
}
}
return i - 1;
}
checkInnerObject(OBJECT,'key you want to find');
更新:
充足 : http://jsfiddle.net/kevalbhatt18/0js7q638/5/
基于您的 JSFiddle 代码,我想我让它按照您想要的方式工作:
var app = angular.module('myApp', []);
app.controller('BoxController', ['$scope', 'BoxService', function($scope, BoxService) {
$scope.currentBox = {};
$scope.currentSelection = [];
$scope.currentOptions = [];
$scope.defaultOptions = [{
"id": 1,
"type": "black"
}, {
"id": 8,
"type": "red"
}, {
"id": 15,
"type": "green"
}, {
"id": 10,
"type": "yellow"
}, {
"id": 3,
"type": "blue"
}];
// This object maps each box's ID to its length. For example,
// `boxLengths['1'] = 2` means that box with ID '1' contains 2 boxes.
$scope.boxLengths = {};
$scope.setCurrentBox = function(id) {
BoxService.getBoxItem(id, function(box) {
$scope.currentBox = box;
// Convert the box from a tree structure into a flat array `data`
BoxService.getBoxesAsTab(box, function(data) {
$scope.currentSelection = data;
$scope.currentOptions = [];
// We now know the current box contains `data.length - 1` boxes
// (subtract 1 so we don't count the first box in the `data` array)
$scope.boxLengths[id] = data.length - 1;
angular.forEach(data, function(item, index) {
BoxService.getBoxOptions(item.type, function(options) {
$scope.currentOptions[index] = options;
});
});
});
});
};
// This gets called whenever a `<select>` box changes value
$scope.updateSelection = function(index, choiceId) {
// Truncate the arrays down to the element at the specified `index`
// http://stackoverflow.com/a/6928247/5249519
$scope.currentSelection.length = index + 1;
$scope.currentOptions.length = index + 1;
// If the user selects "NO CHOICE", then `choiceId` will be `null`
if (choiceId === null) {
// Update the number of boxes that the current box contains
// (subtract 1 so we don't count the first box in the array).
// NOTE: If the user selects "NO CHOICE" for the 1st choice,
// then `$scope.currentBox.id` would be `null` at this point,
// but I'm not sure what you want to do in that case...
$scope.boxLengths[$scope.currentBox.id] = $scope.currentSelection.length - 1;
// Update the appropriate object reference in the chain
if (index === -1) {
$scope.currentBox = null;
} else {
$scope.currentSelection[index].box = null;
}
// Stop here and return
return;
}
// Otherwise, create the next item in the chain
var nextItem = {
id: choiceId,
type: '',
box: null
};
// Given the `id`, find the corresponding `type` name in the `defaultOptions` array
for (var i = 0; i < $scope.defaultOptions.length; i++) {
if ($scope.defaultOptions[i].id === nextItem.id) {
nextItem.type = $scope.defaultOptions[i].type;
break;
}
}
// Update the appropriate object reference in the chain
if (index === -1) {
$scope.currentBox = nextItem;
} else {
$scope.currentSelection[index].box = nextItem;
}
// Add the `nextItem` to the `currentSelection` array
$scope.currentSelection.push(nextItem);
// Get the options for the `nextItem` and add them to the `currentOptions` array
BoxService.getBoxOptions(nextItem.type, function(options) {
$scope.currentOptions.push(options);
});
// Update the number of boxes that the current box contains
// (subtract 1 so we don't count the first box in the array)
$scope.boxLengths[$scope.currentBox.id] = $scope.currentSelection.length - 1;
};
}]);
app.directive('editForm', function() {
return {
restrict: 'E',
template:
'1st choice : ' +
'<select ng-model="currentBox.id" ' +
' ng-options="obj.id as obj.type for obj in defaultOptions" ' +
' ng-change="updateSelection(-1, currentBox.id)"> ' +
' <option value="">NO CHOICE</option> ' +
'</select> ' +
'<div class="editor" ng-repeat="item in currentSelection"> ' +
' <br/><br/>Choice {{$index}} : ' +
' <div> Id : <label>{{item.id}}</label></div> ' +
' <div> Type : <label>{{item.type}}</label></div> ' +
' <div class="boxes" style="border:1px solid red;"> ' +
' Box : ' +
' <select ng-model="item.box.id" ' +
' ng-options="obj.id as obj.type for obj in currentOptions[$index]"' +
' ng-change="updateSelection($index, item.box.id)"> ' +
' <option value="">NO CHOICE</option> ' +
' </select> ' +
' </div> ' +
'</div> '
};
});
//This is the http service supposed to retrieve boxes data. HARDCODED for the example
app.factory('BoxService', ['$http', function($http) {
return {
getBoxItem: function(id, callback) {
callback({
"id": 1,
"type": "black",
"box": {
"id": 8,
"type": "red",
"box": {
"id": 15,
"type": "green",
"box": null
}
}
});
},
getBoxesAsTab: function(box, callback) {
var boxesArray = [];
var currentBox = box;
while (currentBox) {
boxesArray.push(currentBox);
currentBox = currentBox.box;
}
callback(boxesArray);
},
getBoxOptions: function(type, callback) {
if (type === 'black') {
callback([{
'id': 8,
'type': 'red'
}, {
'id': 3,
'type': 'blue'
}, {
'id': 15,
'type': 'green'
}]);
} else if (type === 'red') {
callback([{
'id': 15,
'type': 'green'
}, {
'id': 10,
'type': 'yellow'
}]);
} else if (type === 'blue') {
callback([{
'id': 1,
'type': 'black'
}, {
'id': 8,
'type': 'red'
}, {
'id': 15,
'type': 'green'
}, {
'id': 10,
'type': 'yellow'
}, {
'id': 3,
'type': 'blue'
}]);
} else if (type === 'green') {
callback([{
'id': 1,
'type': 'black'
}, {
'id': 8,
'type': 'red'
}, {
'id': 15,
'type': 'green'
}, {
'id': 10,
'type': 'yellow'
}, {
'id': 3,
'type': 'blue'
}]);
} else {
callback([]);
}
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="BoxController">
<p>Click on the table row (green line) to set "pre-defined" (hardcoded) data</p>
<table class='table'>
<thead>
<tr style="border:1px solid black;">
<td style="border:1px solid black;">id</td>
<td style="border:1px solid black;">type</td>
<td style="border:1px solid black;">contains</td>
</tr>
</thead>
<tbody>
<tr ng-click="setCurrentBox('1');" style="background-color:lightgreen;">
<td>1</td>
<td>Black</td>
<td ng-bind="boxLengths['1']"></td>
</tr>
</tbody>
</table>
<edit-form></edit-form>
<br/>
<br/>
<br/> CURRENT BOX : {{currentBox}}
<br/> CURRENT SELECTION : {{currentSelection}}
<br/> CURRENT OPTIONS : {{currentOptions}}
</div>
我知道你说你更喜欢使用树形结构中的盒子,而不是一个盒子数组,但你需要一个平面数组才能使用ng-repeat
。无论如何,一旦你有了盒子,就很容易从树结构转换为平面数组;我已经修改了您的BoxService.getBoxesAsTab
函数,通过将对象引用复制到一个新数组中来做到这一点:
getBoxesAsTab: function(box, callback) {
var boxesArray = [];
var currentBox = box;
while (currentBox) {
boxesArray.push(currentBox);
currentBox = currentBox.box;
}
callback(boxesArray);
}
希望有帮助。如果您有任何问题,请告诉我。谢谢!
更新:我使用以下更改更新了上面的代码:
- 每个
<select>
现在都有一个默认的"无选择"选项,该选项应按预期工作。注意:ng-options
允许您将"单个硬编码<option>
元素"用作null
选项。 - "contains"值(在表中(现在已初始化,并在您更改选择时动态更新。当然,如果您将"第一选择"更改为"黑色"以外的内容,它将不再更新。
- 我通过删除
ng-init
并直接在ng-model
和ng-change
中使用item.box.id
来简化editForm
模板 HTML。 - 我添加了更多代码注释以更清楚地解释代码。
澄清一下:当用户更改<select>
框值时,将调用$scope.updateSelection
函数。除此之外,该函数更新$scope.currentSelection
(框的平面数组(,但它也会根据需要更新item.box
(引用链中的下一个框(,因此您应该看到$scope.currentBox
(树结构中的框(也已更新。这是有效的,因为最终,$scope.currentSelection
和 $scope.currentBox
都包含对内存中相同 box 对象的引用。
希望有帮助。谢谢。
你的问题很长,如果我下面的回答不能满足你的所有条件,请原谅我。话虽如此,我认为如果您不必创建这种对象(俄罗斯娃娃(,那么我们就不必担心太多。
这样做:-
var reallyLengthyBoxObj = {
"id":"1",
"type":"black",
"box":{
"id":"8",
"type":"red",
"box":{
"id":"15",
"type":"green",
"box":{
"id":"15",
"type":"green",
"box":{
"id":"15",
"type":"green",
"box":null
}
}
}
}
}
$scope.boxObjArr = [],
$scope.selectedBoxes = {};
i = 0;
function recurseMe(boxObj){
i++;
$scope.selectedBoxes["level"+i] = null;
var obj = {};
obj.id = boxObj.id;
obj.type = boxObj.type;
obj.level = i;
try{
var haskeys = Object.keys(boxObj.box);
obj.isParent = true;
$scope.boxObjArr.push(obj);
recurseMe(boxObj.box);
}catch(e){
obj.isParent = false;
$scope.boxObjArr.push(obj);
return;
}
}
recurseMe(reallyLengthyBoxObj);
通过这种方式,您将获得一个包含所有框及其级别的数组。现在我假设重复的 id 不会(也不应该来自(您的服务器。否则我们的逻辑就会增长。
现在您已经准备好了 2 件事 - $scope.boxObjArr
和$scope.selectedBoxes
.
用 HTML 写这个:
<div ng-repeat="(key,value) in selectedBoxes">
<select ng-model="value" ng-if="key=='level1' || selectedBoxes[key.slice(0,key.length-1)+(key.slice(-1)-1)] != null">
<option ng-repeat="box in boxObjArr" ng-show="key=="level1" || box.level < selectedBoxes[key.slice(0,key.length-1)+(key.slice(-1)-1)].level">
</option>
</select>
</div>
javascript部分已经完成并正常工作。不确定我是否在 HTML 部分犯了任何错误。但我认为您已经知道如何以及为什么要形成$scope.selectedBoxes
和$scope.boxObjArr
.
希望它能以最快的方式解决您的问题。
谢谢