使用AngularJS的某种俄罗斯玩偶容器的动态编辑表单

Jul*_*0sS 13 html javascript css loops angularjs

这是问题所在,

我实际上必须管理可以包含db中定义的其他对象的对象.所以,例如,我有5种盒子.一个红色的盒子,一个绿色的盒子,一个蓝色的盒子,一个黄色的盒子和一个黑盒子.

每个框可以包含一个框,也可以包含一个框,依此类推.

我收到的是这种对象:

{
    "id":1,
    "type":"black",
    "box":
    {
        "id":8,
        "type":"red",
        "box":
        {
            "id":15,
            "type":"green",
            "box":null
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以这个例子是:一个黑盒子,包含一个红色的盒子,里面有一个空的绿色盒子.(黑色 - >红色 - >绿色 - >空)

有条件:

  • 黑匣子只能包含蓝色,绿色和红色,
  • 红色框只能包含绿色和黄色,
  • 黄色的盒子什么都没有,
  • 其他框(绿色和蓝色)可以包含任何内容

我需要做的是某种"盒子集编辑器",我收到一个盒子对象,这是一个复杂与否(意味着它只能有一个盒子级别,或几个).我必须在选择框的列表中表示它,因此,对于我写的示例,它将显示:

<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>
Run Code Online (Sandbox Code Playgroud)

这必须通过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>
Run Code Online (Sandbox Code Playgroud)

请注意该ng-click部分.该setCurrentBox()函数在控制器中定义,并设置$scope.currentBox从" BoxService" 接收的框对象.

单击该行将调用BoxService,检索所选框的json对象(完全!包含在其中的框,如在线程顶部所写),并将其分配给$scope.currentBox变量.

更改框选择值应"清空"下一个可能的选择(将"无"设置为选中并添加可能的选项作为选项),如果有子框,则只需擦除它们(在我的示例中将黑色选项更改为红色 - > red-> green-> empty会给red-> empty(None-selected-以及绿色和黄色选项).

就我而言,我只能直接访问$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)
Run Code Online (Sandbox Code Playgroud)

当用户在表中选择框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
            }
        }
    }
}
}
Run Code Online (Sandbox Code Playgroud)

必须将此对象显示到可编辑的选择框中,如下所示:

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>        
Run Code Online (Sandbox Code Playgroud)

具体示例:用户操作1:

如果用户将框2设置为NO CHOICE OR YELLOW(因为黄色框不能包含任何框),则当前框对象应如下所示:

{
    "id":"1",
    "type":"black",
    "box":{
        "id":"8",
        "type":"red",
        "box":{
            "id":"15",
            "type":"green",
            "box":null
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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>        
Run Code Online (Sandbox Code Playgroud)

具体示例:用户操作1:

如果用户将框1设置为BLUE,则当前框对象应如下所示:

{
    "id":"1",
    "type":"black",
    "box":{
        "id":"3",
        "type":"blue",
        "box":null
    }
}
Run Code Online (Sandbox Code Playgroud)

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>        
Run Code Online (Sandbox Code Playgroud)

请注意,我可以获得一个盒子的可能选择,或者来自的任何盒子的所有可能选择BoxService.这是来自BoxService.这个数据可能很大,在这个例子中它很小,但是这可以是一个很长的对象列表,可以包含在另一个对象中.

希望这个例子可以让我的问题更清楚.

谢谢阅读

Dyn*_*tch 1

基于您的 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([]);
      }
    }
  };
}]);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

我知道您说过您更喜欢使用树结构中的框,而不是框数组,但是您需要一个平面数组才能使用ng-repeat. 不管怎样,一旦你有了这个盒子,就很容易从树结构转换为平面数组;我已经修改了您的BoxService.getBoxesAsTab函数,通过将对象引用复制到新数组中来实现此目的:

getBoxesAsTab: function(box, callback) {
  var boxesArray = [];
  var currentBox = box;

  while (currentBox) {
    boxesArray.push(currentBox);
    currentBox = currentBox.box;
  }

  callback(boxesArray);
}
Run Code Online (Sandbox Code Playgroud)

希望有帮助。如果您有任何疑问,请告诉我。谢谢!


更新:我更新了上面的代码,进行了以下更改:

  • 每个<select>现在都有一个默认的“NO CHOICE”选项,它应该按预期工作。注意:ng-options让您可以使用“单个硬编码<option>元素”作为选项null
  • “包含”值(在表中)现在会在您更改选择时动态初始化和更新。当然,如果你把“第一选择”改为“黑色”以外的东西,它就不会再更新了。
  • editForm通过删除并直接在and中ng-init使用来简化 HTML 模板。item.box.idng-modelng-change
  • 我添加了更多代码注释,以便更清楚地解释代码。

只是为了澄清:当用户更改<select>框值时,$scope.updateSelection将调用该函数。除此之外,函数会更新$scope.currentSelection(盒子的平面数组),但它也会item.box根据需要更新(引用链中的下一个盒子),因此您应该看到$scope.currentBox(树结构中的盒子)也更新了。这是有效的,因为最终,$scope.currentSelection和 都包含对内存中相同$scope.currentBox盒子对象的引用。

希望有帮助。谢谢。