带有 AngularJS 的某种俄罗斯娃娃容器的动态编辑表单

Posted

技术标签:

【中文标题】带有 AngularJS 的某种俄罗斯娃娃容器的动态编辑表单【英文标题】:Dynamic Edit form for some kind of Russian Dolls container(s) with AngularJS 【发布时间】:2016-03-06 21:01:09 【问题描述】:

问题来了,

我实际上必须管理可以包含 db 中定义的其他对象的对象。 例如,我有 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 将给出 red->empty(无 -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)

当用户在表格中选择框 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 设置为 NO CHOICE OR YELLOW(因为黄色框不能包含任何框),则当前框对象应如下所示:


    "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 设置为蓝色,则当前框对象应如下所示:


    "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。这个数据可能很大,在这个例子中很小,但这可能是一个长长的对象列表,可以包含在另一个对象中。

希望这个例子能让我的问题更清楚。

感谢阅读

【问题讨论】:

所以,在更新之后,您似乎拥有了您想要的东西。缺少什么? @MoshFeu 好吧,更新后选择框系统不会交互。需要使它动态和有点快。更改一个选择框时,框后应消失,只有一个子项应保持不选择,而不选择,但相应的选项。这是缺少的主要内容。选择 3 取决于 2 取决于 1... 然后,还有查询问题。解决这个问题的最佳方法是什么?将可能的选项一起检索客户端并获取选项以在数组中选择,或者从 $http 中获取...?很多东西都不见了.. 对我来说真的是为了做作业。 不是为了家庭作业,只是为了得到一些与嵌套对象一起工作的东西,并能够在几个项目中使用它,这在我看来比“家庭作业”更有趣 【参考方案1】:

试试这个例子: http://jsfiddle.net/kevalbhatt18/0js7q638/1/

使用 checkInnerObject 函数将返回 'box' 的计数,参见示例

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/

【讨论】:

感谢您的回答,但这不是唯一的问题......问题是让事情变得动态。当用户单击一行时,它检索一个框,包含或不包含另一个框,其中包含(或不包含)另一个框,依此类推...... 0 到 N 个框。这必须显示为一组选择框,从父框 (0) 到最后一个子框 (N) 排序。例如,当他更改选择 2(框 2)中的选择时,框 3 变为空(无选择)并且具有与框 2 选择可能性相对应的选项。 3 之后的方框被删除。认为我的问题不清楚,抱歉:/ @Julo0sS 相同的东西和它的动态只有你可以将 n 个嵌套对象传递给这个函数,它会给你结果计数。在您的代码中,您将使用相同的 json 进行响应,因此即使您传递相同的 id 1,它也会为您提供相同的结果。如果您需要任何帮助,此函数仅适用于不在数组中的对象 @Julo0sS 看到这个小提琴。点击类型它会给你盒子值的计数。所以从例子中你会知道它是如何工作的jsfiddle.net/kevalbhatt18/0js7q638/5 我现在用你的功能看看,我在上面开个聊天室【参考方案2】:

你的问题很长,如果我下面的回答不能满足你的所有条件,请原谅我。这么说,我想如果你不用创造这种物体(俄罗斯娃娃)那我们就不用太担心了。

这样做:-

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

希望它能以最短的方式解决您的问题。

谢谢

【讨论】:

【参考方案3】:

在您的 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://***.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);

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


更新:我用以下更改更新了我的上述代码:

现在每个&lt;select&gt; 都有一个默认的“NO CHOICE”选项,它应该可以按预期工作。注意:ng-options 允许您使用“单个硬编码的&lt;option&gt; 元素”作为null 选项。 “包含”值(在表中)现在会在您更改选择时进行初始化和动态更新。当然,如果您将“第一选择”更改为“黑色”以外的其他内容,它将不再更新。 我通过删除ng-init 并在ng-modelng-change 中直接使用item.box.id 来简化editForm 模板HTML。 我添加了更多代码 cmets 以更清楚地解释代码。

澄清一下:当用户更改&lt;select&gt; 框值时,会调用$scope.updateSelection 函数。除其他外,该函数更新$scope.currentSelection(盒子的平面数组),但它也会根据需要更新item.box(引用链中的下一个盒子),所以你应该看到$scope.currentBox(树结构中的盒子)也更新了。这是可行的,因为最终$scope.currentSelection$scope.currentBox 都包含对内存中相同 框对象的引用。

希望对您有所帮助。谢谢。

【讨论】:

嘿!很好的答案,很快就被接受了;)得到了几乎相同的解决方案jsfiddle.net/xx0arx0j。现在试图让它以相反的方式工作(从子到父而不是从父到子,这意味着对象“包含”在 box 属性中)。如果您有时间帮忙请告诉我... @Julo0sS,谢谢!很高兴我能帮上忙。抱歉,这周需要去看医生以及其他家庭问题,所以我没有时间。但反义应该是类似的,只是把所有的逻辑都颠倒过来。例如,我可能会使用$scope.currentSelection.reverse() 来就地反转数组。然后,在$scope.updateSelection 函数中,可能将$scope.currentSelection[index].box = nextItem 更改为nextItem.box = $scope.currentSelection[index]。反转之类的东西。希望对您有所帮助。

以上是关于带有 AngularJS 的某种俄罗斯娃娃容器的动态编辑表单的主要内容,如果未能解决你的问题,请参考以下文章

java 354.俄罗斯娃娃信封(#1).java

java 354.俄罗斯娃娃信封(#1).java

354 Russian Doll Envelopes 俄罗斯娃娃信封

[LeetCode] Russian Doll Envelopes 俄罗斯娃娃信封

LG专利新机器人超萌俄罗斯娃娃

网络用语套娃是啥意思 网络用语套娃的意思