说服脚本一起玩得很好

时间:2016-02-21 12:30:39

标签: javascript jquery

我一直在玩javascript / jquery,试图让各种各样的脚本很好地一起玩(例如参见我最近在Populate an input with the contents of a custom option (data-) attribute提出的问题)。

我现在得到了大量有用的代码。它应该做4件事:

  1. 填充级联下拉列表,其中第二个下拉列表中的选项根据第一个下拉列表中选择的内容而有所不同。
  2. 克隆第一行(或者它可能是最后一行),因此可以向表单添加其他行(在实际版本中,页面首次加载时可能有任意数量的行)
  3. 保持在第3个下拉列表中选择的总计值
  4. 根据在第二个下拉列表中选择的值填充文本输入
  5. 第一行一切正常。但是,对于克隆的行,步骤1和4停止工作。我怀疑它是因为我没有唯一地识别每个克隆的实例。是对的吗?那我该怎么做呢?

    
    
        function cloneRow() {
          var row = document.getElementById("myrow"); // find row to copy
          var table = document.getElementById("mytable"); // find table to append to
          var clone = row.cloneNode(true); // copy children too
          clone.id = "newID"; // change id or other attributes/contents
          table.appendChild(clone); // add new row to end of table
        }
    
        function createRow() {
          var row = document.createElement('tr'); // create row node
          var col = document.createElement('td'); // create column node
          var col2 = document.createElement('td'); // create second column node
          row.appendChild(col); // append first column to row
          row.appendChild(col2); // append second column to row
          col.innerHTML = "qwe"; // put data in first column
          col2.innerHTML = "rty"; // put data in second column
          var table = document.getElementById("tableToModify"); // find table to append to
          table.appendChild(row); // append row to table
        }
    
    
    
    window.sumInputs = function() {
        var inputs = document.getElementsByName('hours'),
            result = document.getElementById('total'),
            sum = 0;
    
        for(var i=0; i<inputs.length; i++) {
            var ip = inputs[i];
    
            if (ip.name && ip.name.indexOf("total") < 0) {
                sum += parseFloat(ip.value) || 0;
            }
    
        }
    
        result.value = sum;
    }
    
    
    var myJson =
    {
       "listItems":[
          {
             "id":"1",
             "project_no":"1001",
             "task":[
                {
                   "task_description":"Folding stuff",
                   "id":"111",
                   "task_summary":"Folding",
                },
                {
                   "task_description":"Drawing stuff",
                   "id":"222",
                   "task_summary":"Drawing"
                }
             ]
          },
          {
             "id":"2",
             "project_no":"1002",
             "task":[
                {
                   "task_description":"Meeting description",
                   "id":"333",
                   "task_summary":"Meeting"
                },
                {
                   "task_description":"Administration",
                   "id":"444",
                   "task_summary":"Admin"
                }
             ]
          }
       ]
    }
    
    $(function(){
      $.each(myJson.listItems, function (index, value) {
        $("#project").append('<option value="'+value.id+'">'+value.project_no+'</option>');
      });
    
        $('#project').on('change', function(){
          $('#task').html('<option value="000">-Select Task-</option>');
          for(var i = 0; i < myJson.listItems.length; i++)
          {
            if(myJson.listItems[i].id == $(this).val())
            {
               $.each(myJson.listItems[i].task, function (index, value) {
                  $("#task").append('<option value="'+value.id+'" data-description="'+value.task_description+'">'+value.task_summary+'</option>');
              });
            }
          }
      });
    });
    
    $('#task').change(function() {
      $('#taskText').val( $(this).find('option:selected').data('description') )
    })
    &#13;
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <html>
    <form>
     <table id='mytable'>
      <tr>
       <td>Project</td>
       <td>Workstage</td>
       <td>Hours</td>
       <td>Description</td>
      </tr>
      <tr>
       <td></td>
       <td></td>
       <td><input size=3 id='total' disabled='disabled'/></td>
       <td></td>
      </tr>
       <tr id='myrow'>
       <td>
                      <select id="project" name="">
                          <option value="">Select One</option>
                        </select>
    </td><td>
                       <select id="task" name="" onchange="updateText('task')">>
                           <option value="">Select One</option>
                      </select>
       </td>
       <td>
         <select name = 'hours' onmouseup="sumInputs()">
         <option>1.0</option>
         <option>1.5</option>
         <option>2.0</option>
         </select>
       <td><input type="text" value="" id="taskText" /></td>
      </tr>
     </table>
       <input type="button" onclick="cloneRow()" value="Add Row" />
    </form>
    &#13;
    &#13;
    &#13;

    如果我错误地插入了代码段,请致歉。

7 个答案:

答案 0 :(得分:11)

我要走出困境并提出一些完全不同的建议。我知道你的问题是用jQuery标记的,但我想建议一个不同的,我相信更好的方法来解决问题。

我认为你在这里混合了你的 DOM 和你的 JavaScript ,而使用绑定框架可能更简单。这些绑定框架将您的演示文稿从基础数据中分离出来。有一些可供选择,这里只是几个:

  • 敲除
  • 阵营

我个人非常了解Knockout,所以这就是我如何创建类似于你使用Knockout制作的东西。在这里,我将代码减少了大约50%,我相信我已经从JavaScript中删除了所有 DOM 的奖励,显着提高了可读性

请注意,通过使用 Knockout.mapping 插件并调用ko.toJS(vm.jobs) <,您可以轻松获取JSON视图(我的结构可能与您的结构不完全匹配,但应该会提供您的想法) / p>

var vm = {};

vm.projects = ko.observableArray([]);
vm.projects.push({
  id: 1001,
  name: "Project A",
  stages: [{ name: "folding", description: "folding stuff" }, 
           { name: "drawing", description: "drawing shapes" }]
});
vm.projects.push({
  id: 1002,
  name: "Project B",
  stages: [{ name: "meeting", description: "Talking" }, 
           { name: "admin", description: "everyday things" }]
});

vm.jobs = ko.observableArray([]);
vm.totalHours = ko.computed(function() {
  var sum = 0;
  for (var i = 0; i < vm.jobs().length; i++) {
    sum += vm.jobs()[i].time();
  }
  return sum;
})

createJob = function() {
  var job = {};

  // Set fields on the job
  job.project = ko.observable();
  job.stage = ko.observable();
  job.stages = ko.computed(function() {
    if (job.project()) return job.project().stages;
    return [];
  });
  job.stage.subscribe(function() {
    job.description(job.stage().description);
  });
  job.description = ko.observable();
  job.time = ko.observable(1);

  vm.jobs.push(job);
};

createJob();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<form>
  <table id='mytable'>
    <tr>
      <td>Project</td>
      <td>Workstage</td>
      <td>Hours</td>
      <td>Description</td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td>
        <input size=3 id='total' disabled='disabled' data-bind="value: totalHours" />
      </td>
      <td></td>
    </tr>
    <!-- ko foreach: jobs -->
    <tr id='myrow'>
      <td>
        <select class="project" data-bind="options: $root.projects, optionsText: 'name', value: project, optionsCaption: 'Select a Project'"></select>
      </td>
      <td>
        <select class="stage" data-bind="options: stages, value: stage, optionsText: 'name', optionsCaption: 'Select a Stage', enable: project()"></select>
      </td>
      <td>
        <select class="hours" data-bind="options: [1.0, 1.5, 2.0], value: time, enable: stage()"></select>
      </td>
      <td>
        <input type="text" data-bind="value: description, enable: stage()" />
      </td>
    </tr>
    <!-- /ko -->
  </table>
  <input type="button" onclick="createJob()" value="Add Row" />
</form>

这里的一般想法是observableobservableArray都使用Knockout绑定到 DOM 。它们会自动保持彼此同步。

computed字段本质上是一个计算字段,所以我用它来生成你的总数,也用它来提供其中一个下拉菜单 - 你可能会使用不同的方法,但这看起来很简单。 / p>

最后有一个手册subscribe,旨在更新您手头的job的默认说明。这允许您更新字段,而不必使用其他可观察或计算字段来覆盖描述,如果用户设置了一个。

由于您的身份证也存在问题,我还要提一下如何解决这些问题。使用Knockout,我们可以很容易地使用另一个计算字段为我们的 DOM 创建客户端唯一ID,并在数组中返回它的顺序:

job.id = ko.computed(function() {
   return vm.jobs.indexOf(job); 
});

您甚至可以在 DOM 中反映出这一点(请注意ID不能以数字开头),如下所示:

<td data-bind="attr: { id: 'job_' + id() }"></td>

这会生成DOM,如:

<td id="job_0"></td>

答案 1 :(得分:3)

这是一个非常肮脏的代码和平。我用同样的脏样式解决了你的问题,但我建议你以适当的方式写出全新的东西。 我通过将所有id更改为类属性来解决问题。每行都有自己唯一的ID。

&#13;
&#13;
	   var rowNum =1;
		
	   function cloneRow() {
		  var row = document.getElementById("row0"); // find row to copy
		  var table = document.getElementById("mytable"); // find table to append to
		  var clone = row.cloneNode(true); // copy children too
		  clone.id = "row"+rowNum; // change id or other attributes/contents
		  table.appendChild(clone); // add new row to end of table
		  initProject(clone.id);
		  rowNum++; 
		}

		function createRow() {
		  var row = document.createElement('tr'); // create row node
		  var col = document.createElement('td'); // create column node
		  var col2 = document.createElement('td'); // create second column node
		  row.appendChild(col); // append first column to row
		  row.appendChild(col2); // append second column to row
		  col.innerHTML = "qwe"; // put data in first column
		  col2.innerHTML = "rty"; // put data in second column
		  var table = document.getElementById("tableToModify"); // find table to append to
		  table.appendChild(row); // append row to table
		}



	window.sumInputs = function() {
		var inputs = document.getElementsByName('hours'),
			result = document.getElementById('total'),
			sum = 0;

		for(var i=0; i<inputs.length; i++) {
			var ip = inputs[i];

			if (ip.name && ip.name.indexOf("total") < 0) {
				sum += parseFloat(ip.value) || 0;
			}

		}

		result.value = sum;
	}


	var myJson =
	{
	   "listItems":[
		  {
			 "id":"1",
			 "project_no":"1001",
			 "task":[
				{
				   "task_description":"Folding stuff",
				   "id":"111",
				   "task_summary":"Folding",
				},
				{
				   "task_description":"Drawing stuff",
				   "id":"222",
				   "task_summary":"Drawing"
				}
			 ]
		  },
		  {
			 "id":"2",
			 "project_no":"1002",
			 "task":[
				{
				   "task_description":"Meeting description",
				   "id":"333",
				   "task_summary":"Meeting"
				},
				{
				   "task_description":"Administration",
				   "id":"444",
				   "task_summary":"Admin"
				}
			 ]
		  }
	   ]
	}

	function initProject(rowId){
		
	console.log(rowId);
		if(rowId == 'row0'){
		 $.each(myJson.listItems, function (index, value) {
		  
			$("#"+rowId+" .project").append('<option value="'+value.id+'">'+value.project_no+'</option>');
		  });
		}
		$('#'+rowId+' .project').on('change', function(e){
			rowElem = e.target.closest(".row");
			
			
		 $('#'+rowId+' .task').html('<option value="000">-Select Task-</option>');
		  for(var i = 0; i < myJson.listItems.length; i++)
		  {
			
			if(myJson.listItems[i].id == $(this).val())
			{
			   $.each(myJson.listItems[i].task, function (index, value) {
				  $('#'+rowId+' .task').append('<option value="'+value.id+'" data-description="'+value.task_description+'">'+value.task_summary+'</option>');
			  });
			}
		  }
	  });
	  
	  $('#'+rowId+' .task').change(function() {
	  $('#'+rowId+' .taskText').val( $(this).find('option:selected').data('description') )
	})
	}
	initProject('row0');
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<html>
<head>

</head>
<body>
<form>
	 <table id='mytable'>
	  <tr>
	   <td>Project</td>
	   <td>Workstage</td>
	   <td>Hours</td>
	   <td>Description</td>
	  </tr>
	  <tr>
	   <td></td>
	   <td></td>
	   <td><input size=3 id='total' disabled='disabled'/></td>
	   <td></td>
	  </tr>
	   <tr id='row0' class="row">
	   <td>
					  <select class="project" name="">
						  <option value="">Select One</option>
						</select>
	</td><td>
					   <select class="task" name="" onchange="updateText('task')">
						   <option value="">Select One</option>
					  </select>
	   </td>
	   <td>
		 <select name = 'hours' onmouseup="sumInputs()">
		 <option>1.0</option>
		 <option>1.5</option>
		 <option>2.0</option>
		 </select>
	   <td><input type="text" value="" class="taskText" /></td>
	  </tr>
	 </table>
	   <input type="button" onclick="cloneRow()" value="Add Row" />
	</form>
  
  </body>
&#13;
&#13;
&#13;

答案 2 :(得分:2)

另一个带有简单模板系统的版本,这次没有jquery。

有趣的是看它是否难以转换,但事实并非如此,将jQuery代码映射到现代浏览器的普通JS的大部分内容都可以在这里找到 - YOU MIGHT NOT NEED JQUERY

以下是代码:

var myJson =
{
   "listItems":[
      {
         "id":"1",
         "project_no":"1001",
         "task":[
            {
               "task_description":"Folding stuff",
               "id":"111",
               "task_summary":"Folding",
            },
            {
               "task_description":"Drawing stuff",
               "id":"222",
               "task_summary":"Drawing"
            }
         ]
      },
      {
         "id":"2",
         "project_no":"1002",
         "task":[
            {
               "task_description":"Meeting description",
               "id":"333",
               "task_summary":"Meeting"
            },
            {
               "task_description":"Administration",
               "id":"444",
               "task_summary":"Admin"
            }
         ]
      }
   ]
}

var template = function(target) {
  var _parent = target.parentNode;
  var _template = _parent.getAttribute('dataTemplate');
  if (!_template) {
    // no template yet - save it and remove the node from HTML
    target.style.display = '';
    target.classList.add('clone');
    _template = target.outerHTML;
    _parent.setAttribute('dataTemplate', JSON.stringify(_template));
    _parent.removeChild(target);
  } else {
    // use saved template
    _template = JSON.parse(_template);
  }
  return {
    populate: function(data) {
      var self = this;
      this.clear();
      data.forEach(function(value) {
        self.clone(value);
      });
    },
    clone: function(value) {
      var clone = target.cloneNode(true);
      _parent.appendChild(clone);
      var html = _template;
      if (value) {
        for (var key in value) {
          html = html.replace('{'+key+'}', value[key]);
        }
      }
      clone.outerHTML = html;
      clone = _parent.lastChild;
      if (value) {
        clone.setAttribute('dataTemplateData', JSON.stringify(value));
      }
      return clone;
    },
    clear: function() {
      var clones = _parent.querySelectorAll('.clone')
      Array.prototype.forEach.call(clones, function(el) {
        _parent.removeChild(el);
      });
    }
  };
};

function createRow() {
  var clone = template(document.querySelector('.myrow-template')).clone();
  template(clone.querySelector('.project-option-tpl')).populate(myJson.listItems);
  updateHours();
  bindEvents();
}

function bindEvents() {
  var elements = document.querySelectorAll('#mytable .project');
  Array.prototype.forEach.call(elements, function(elem) {
    elem.addEventListener('change', function() {
      var data = JSON.parse(this.options[this.selectedIndex].getAttribute('dataTemplateData'));
      template(this.parentNode.parentNode.querySelector('.task-option-tpl')).populate(data.task);
    });
  });
  elements = document.querySelectorAll('#mytable .task');
  Array.prototype.forEach.call(elements, function(elem) {
    elem.addEventListener('change', function() {
      var data = JSON.parse(this.options[this.selectedIndex].getAttribute('dataTemplateData'));
      this.parentNode.parentNode.querySelector('.task-text').value = data.task_description;
    });
  });
  elements = document.querySelectorAll('#mytable .hours');
  Array.prototype.forEach.call(elements, function(elem) {
    elem.addEventListener('mouseup', function() {
      updateHours();
    });
  });
}

function updateHours() {
  var total = 0;
  var hours = document.querySelectorAll('.hours');
  Array.prototype.forEach.call(hours, function(item) {
    if (item.parentNode.parentNode.style.display.length == 0) {
      total += parseFloat(item.value) || 0;
    }
  });
  document.getElementById('total').value = total;
}
function ready(fn) {
  if (document.readyState != 'loading'){
    fn();
  } else {
    document.addEventListener('DOMContentLoaded', fn);
  }
}

ready(function(){
  createRow();
  document.querySelector('.add-row').addEventListener('click', function() {
    createRow();
  });
});
    <form>
        <table id='mytable'>
            <tr>
                <td>Project</td>
                <td>Workstage</td>
                <td>Hours</td>
                <td>Description</td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td><input size=3 id='total' disabled='disabled'/></td>
                <td></td>
            </tr>
            <tr class='myrow-template' style='display:none'>
                <td>
                    <select class="project" name="">
                        <option value="">Select One</option>
                        <option class='project-option-tpl' value="{id}" style='display:none'>{project_no}</option>
                    </select>
                </td>
                <td>
                    <select class="task" name="">
                        <option value="000">-Select Task-</option>
                        <option class='task-option-tpl' value="{id}" data-description="{task_description}" style='display:none'>{task_summary}</option>
                    </select>
                </td>
                <td>
                    <select class='hours'>
                        <option>1.0</option>
                        <option>1.5</option>
                        <option>2.0</option>
                    </select>
                </td>
                <td><input type="text" value="" class="task-text" /></td>
            </tr>
        </table>
        <input type="button" class="add-row" value="Add Row" />
    </form>

答案 3 :(得分:1)

请参阅其他答案为什么当前代码未按预期工作。 (简而言之:id可能只出现一次)。

我花了一些时间重组你的代码。就个人而言,它看起来并不可维护。我确实使用过物体来吸引东西。你也可以使用像angular等框架。但是自己的JavaScript也可以非常好。对于那些小的东西来说可能已经足够了。

一些注意事项:

  • 我的示例中的第一行是display:none并且称为模板(这样我们总是可以使用“干净”模板)。您只能在Javascript中使用此模板并按需插入,但这种方式可以帮助您快速编辑设计。
  • 我的代码中有4个组
    • 数据(myjson)
    • 表对象声明(这样你也可以创建多个表)
    • 行对象声明
    • 初始准备
  • 原型非常有用,因为它不会复制每一行的代码 (宾语)。所以每个select都会执行相同的功能。但由于我们每次使用不同的数据时都会使用对象。
  • 某些函数类似于hours.click(function(){tableElement.updateTime()});这个内部函数是必需的,因为它将函数的范围保持为对象而不是click事件。通常,当您捕获单击事件并设置函数时,在此函数中this是单击事件。我使用了对象,因此this应该是对象,而不是事件。

var myJson = {
  "listItems": [{
    "id": "1",
    "project_no": "1001",
    "task": [{
      "task_description": "Folding stuff",
      "id": "111",
      "task_summary": "Folding",
    }, {
      "task_description": "Drawing stuff",
      "id": "222",
      "task_summary": "Drawing"
    }]
  }, {
    "id": "2",
    "project_no": "1002",
    "task": [{
      "task_description": "Meeting description",
      "id": "333",
      "task_summary": "Meeting"
    }, {
      "task_description": "Administration",
      "id": "444",
      "task_summary": "Admin"
    }]
  }]
};

/*
  Table
*/
function projectPlan(tableElement) { // Constructor
  this.tableElement = tableElement;
  this.totalTimeElement = tableElement.find('tr td input.total');
  this.rows = [];
};

projectPlan.prototype.appendRow = function(template) { // you could provide different templates
  var newRow = template.clone().toggle(); // make a copy and make it visible
  this.tableElement.append(newRow);
  this.rows.push( new workRow(newRow, this) );
  
  // update the time right away
  this.updateTime();
};

projectPlan.prototype.updateTime =  function() {
  var totalWork = 0;
  for(var i = 0; i < this.rows.length; i++) totalWork += this.rows[i].hours.val()*1; // *1 makes it a number, default is string
  this.totalTimeElement.val(totalWork);
};

/*
  Row
*/
function workRow(rowElement, tableElement) { // Constructor
    // set the object attributes with "this"
    this.rowElement = rowElement;
    this.tableElement = tableElement;
    this.projects = rowElement.find( "td select.projects" );
    this.tasks = rowElement.find( "td select.tasks" );
    this.hours = rowElement.find( "td select.hours" );
    this.taskText = rowElement.find( "td input.taskText" );
  
    // set all the event listeners, don't use this since the "function(){" will have a new scope
    var self = this;
    this.projects.change(function(){self.updateTasks()});
    this.tasks.change(function(){self.updateTaskText()});
    this.hours.change(function(){tableElement.updateTime()});
  
}

workRow.prototype.updateTasks =  function() {
  // delete the old ones // not the first because it's the title
  this.tasks.find('option:not(:first-child)').remove();
  
  if(this.projects.val() != "-1") {
    var tmpData;
    for (var i = 0; i < myJson.listItems[this.projects.val()].task.length; i++) {
      tmpData = myJson.listItems[this.projects.val()].task[i];
      this.tasks.append('<option value="' + i + '">' + tmpData.task_summary + '</option>');
    }
  }
  
  this.taskText.val('');
  
}

workRow.prototype.updateTaskText =  function() {
  if(this.tasks.val() == "-1") this.taskText.val('');
  else this.taskText.val( myJson.listItems[ this.projects.val() ].task[ this.tasks.val() ].task_description );
  
}

/* 
  Setup 
*/

// Prepare the template (insert as much as possible now)
rowTemplate = $('#rowTemplate');
var projectList = rowTemplate.find( "td select.projects" );
var tmpData;
for (var i = 0; i < myJson.listItems.length; i++) {
  tmpData = myJson.listItems[i];
  projectList.append('<option value="' + i + '">' + tmpData.project_no + '</option>');
}

// setup table
var projectPlan = new projectPlan( $('#projectPlan') );

// Print the first row
projectPlan.appendRow(rowTemplate);
$('#buttonAddRow').click(function(){ projectPlan.appendRow(rowTemplate) });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
  <table id="projectPlan">
    <tr>
      <td>Project</td>
      <td>Workstage</td>
      <td>Hours</td>
      <td>Description</td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td><input size=3 class='total' disabled='disabled' /></td>
      <td></td>
    </tr>
    
    <tr id='rowTemplate' style="display:none">
      
      <td>
        <select class="projects">
           <option value="-1">Select One</option>
        </select>
      </td>
      <td>
        <select class="tasks">>
           <option value="-1">Select One</option>
        </select>
      </td>
      <td>
        <select class='hours'>
         <option>1.0</option>
         <option>1.5</option>
         <option>2.0</option>
        </select>
      </td> 
      <td>
        <input class="taskText" type="text"/>
      </td>
      
    </tr>
    
  </table>
  <input type="button" id="buttonAddRow" value="Add Row" />
</form>

答案 4 :(得分:1)

我赞成了关于Angular / Knokout / React的答案 - 这实际上是我强烈建议在实际应用中使用的。

就像练习一样,如果你使用一个简单的模板系统,这就是代码的样子。

这个想法是你不应该“手动”构建javascript,而是在HTML中声明模板。例如,项目选择可以如下所示:

<select class="project" name="">                                                                 
    <option value="">Select One</option>                                                         
    <option class='project-option-tpl' value="{id}" style='display:none'>{project_no}</o
</select>                                                                                        

此处option是项目选项的隐形模板。它对整个“行”和“任务”选择的工作方式类似,这里是实现逻辑的完整代码:

function createRow() {
  var $clone = template($('.myrow-template')).clone();
  template($clone.find('.project-option-tpl')).populate(myJson.listItems);
  updateHours();
}

function updateHours() {
  var total = 0;
  $('.hours:visible').each(function (index, item) {
    total += parseFloat($(item).val()) || 0;
  });
  $('#total').val(total);
}

$(function() {
  createRow();  // create the first row
  $('#mytable').on('change', '.project', function() {
    // handle project change - get tasks for the selected project
    // and populate the tasks template
    var data = $(this).find(':selected').data('template-data');
    template($(this).parent().parent().find('.task-option-tpl')).populate(data.task);
  });
  $('#mytable').on('change', '.task', function() {
    // task change - update the task description
    var data = $(this).find(':selected').data('template-data');
    $(this).parent().parent().find('.task-text').val(data.task_description);
  });
  $('#mytable').on('mouseup', '.hours',function() {
    updateHours(); // re-calculate total hours
  });
  $('.add-row').on('click', function() {
    createRow(); // add one more row
  });
});

“魔术”在template函数中实现:

var template = function($target) {
  $target = $($target.get(0));
  return {
    populate: function(data) {
      // for each item in the data array - clone and populate the
      // item template
      var self = this;
      this.clear();
      $.each(data, function (index, value) {
        self.clone(value);
      });
    },
    clone: function(value) {
      // clone a template for a single item and populate it with data
      var $clone = $target.clone();
      $clone.addClass('clone').appendTo($target.parent()).fadeIn('slow');
      if (value) {
        var html = $clone.get(0).outerHTML;
        for (var key in value) {
          html = html.replace('{'+key+'}', value[key]);
        }
        $clone.get(0).outerHTML = html;
        $clone = $target.parent().find(':last')
        $clone.data('template-data', value);
      }
      return $clone;
    },
    clear: function() {
      // remove cloned templates
      $target.parent().find('.clone').remove();
    }
  };
};

这是完整的可运行示例:

var myJson =
{
   "listItems":[
      {
         "id":"1",
         "project_no":"1001",
         "task":[
            {
               "task_description":"Folding stuff",
               "id":"111",
               "task_summary":"Folding",
            },
            {
               "task_description":"Drawing stuff",
               "id":"222",
               "task_summary":"Drawing"
            }
         ]
      },
      {
         "id":"2",
         "project_no":"1002",
         "task":[
            {
               "task_description":"Meeting description",
               "id":"333",
               "task_summary":"Meeting"
            },
            {
               "task_description":"Administration",
               "id":"444",
               "task_summary":"Admin"
            }
         ]
      }
   ]
}

var template = function($target) {
  $target = $($target.get(0));
  return {
    populate: function(data) {
      var self = this;
      this.clear();
      $.each(data, function (index, value) {
        self.clone(value);
      });
    },
    clone: function(value) {
      var $clone = $target.clone();
      $clone.addClass('clone').appendTo($target.parent()).fadeIn('slow');
      if (value) {
        var html = $clone.get(0).outerHTML;
        for (var key in value) {
          html = html.replace('{'+key+'}', value[key]);
        }
        $clone.get(0).outerHTML = html;
        $clone = $target.parent().find(':last')
        $clone.data('template-data', value);
      }
      return $clone;
    },
    clear: function() {
      $target.parent().find('.clone').remove();
    }
  };
};

function createRow() {
  var $clone = template($('.myrow-template')).clone();
  template($clone.find('.project-option-tpl')).populate(myJson.listItems);
  updateHours();
}

function updateHours() {
  var total = 0;
  $('.hours:visible').each(function (index, item) {
    total += parseFloat($(item).val()) || 0;
  });
  $('#total').val(total);
}

$(function(){
  createRow();
  $('#mytable').on('change', '.project', function() {
    var data = $(this).find(':selected').data('template-data');
    template($(this).parent().parent().find('.task-option-tpl')).populate(data.task);
  });
  $('#mytable').on('change', '.task', function() {
    var data = $(this).find(':selected').data('template-data');
    $(this).parent().parent().find('.task-text').val(data.task_description);
  });
  $('#mytable').on('mouseup', '.hours',function() {
    updateHours();
  });
  $('.add-row').on('click', function() {
    createRow();
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <form>
        <table id='mytable'>
            <tr>
                <td>Project</td>
                <td>Workstage</td>
                <td>Hours</td>
                <td>Description</td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td><input size=3 id='total' disabled='disabled'/></td>
                <td></td>
            </tr>
            <tr class='myrow-template' style='display:none'>
                <td>
                    <select class="project" name="">
                        <option value="">Select One</option>
                        <option class='project-option-tpl' value="{id}" style='display:none'>{project_no}</option>
                    </select>
                </td>
                <td>
                    <select class="task" name="">
                        <option value="000">-Select Task-</option>
                        <option class='task-option-tpl' value="{id}" data-description="{task_description}" style='display:none'>{task_summary}</option>
                    </select>
                </td>
                <td>
                    <select class='hours'>
                        <option>1.0</option>
                        <option>1.5</option>
                        <option>2.0</option>
                    </select>
                </td>
                <td><input type="text" value="" class="task-text" /></td>
            </tr>
        </table>
        <input type="button" class="add-row" value="Add Row" />
    </form>

答案 5 :(得分:1)

而angular.js版本,这里是完整的应用程序代码,其余部分在HTML中声明:

angular.module('MyApp', [])
   .controller('TasksController', function() {
     var self = this;
     this.newRow = function() {
       return { project: null, task: null, hours: 1.0 };
     };
     this.total = 0;
     this.hours = [1.0, 1.5, 2.0];
     this.projects = projects;
     this.rows = [ this.newRow() ];
     this.total = function() {
       return this.rows.reduce(function(prev, curr) {
         return prev + parseFloat(curr.hours);
       }, 0);
     };
     this.addRow = function() {
       this.rows.push(this.newRow());
     };
   });

完整代码:

&#13;
&#13;
var projects = [{
   "id":"1",
   "project_no":"1001",
   "task":[ {
         "task_description":"Folding stuff",
         "id":"111",
         "task_summary":"Folding",
      }, {
         "task_description":"Drawing stuff",
         "id":"222",
         "task_summary":"Drawing"
      } ]
}, {
   "id":"2",
   "project_no":"1002",
   "task":[ {
         "task_description":"Meeting description",
         "id":"333",
         "task_summary":"Meeting"
      }, {
         "task_description":"Administration",
         "id":"444",
         "task_summary":"Admin"
      } ]
}];

 angular.module('MyApp', [])
   .controller('TasksController', function() {
     var self = this;
     this.newRow = function() {
       return { project: null, task: null, hours: 1.0 };
     };
     this.total = 0;
     this.hours = [1.0, 1.5, 2.0];
     this.projects = projects;
     this.rows = [ this.newRow() ];
     this.total = function() {
       return this.rows.reduce(function(prev, curr) {
         return prev + parseFloat(curr.hours);
       }, 0);
     };
     this.addRow = function() {
       this.rows.push(this.newRow());
     };
   });
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="MyApp">
    <form ng-controller="TasksController as ctrl">
        <table id='mytable'>
            <tr>
                <td>Project</td>
                <td>Workstage</td>
                <td>Hours</td>
                <td>Description</td>
            </tr>
            <tr>
                <td></td>
                <td></td>
                <td><input size=3 id='total' value="{{ctrl.total()}}" disabled='disabled'/></td>
                <td></td>
            </tr>
            <tr ng-repeat="row in ctrl.rows">
                <td>
                    <select ng-model="row.project" ng-options="project as project.project_no for project in ctrl.projects">
                        <option value="">Select One</option>
                    </select>
                </td>
                <td>
                    <select ng-model="row.task" ng-options="task as task.task_summary for task in row.project.task">
                        <option value="">Select Task</option>
                    </select>
                </td>
                <td>
                  <select ng-model='row.hours' ng-options="hour as (hour | number : 1) for hour in ctrl.hours">
                    </select>
                </td>
                <td><input type="text" value="{{row.task.task_description}}" class="task-text" /></td>
            </tr>
        </table>
        <input type="button" ng-click="ctrl.addRow()" value="Add Row" />
    </form>
</body>
&#13;
&#13;
&#13;

答案 6 :(得分:1)

这是一个使用普通javascript的版本,它不需要/使用元素id,将克隆存储为变量,禁用任务选择,除非选择了项目。

这个代码示例当然可以优化,包含在类等中,尽管我选择不这样做,以便尽可能简单地遵循代码流并查看正在进行的操作。

&#13;
&#13;
var myJson =
    {
      "listItems":[
        {
          "id":"1",
          "project_no":"1001",
          "task":[
            {
              "task_description":"Folding stuff",
              "id":"111",
              "task_summary":"Folding",
            },
            {
              "task_description":"Drawing stuff",
              "id":"222",
              "task_summary":"Drawing"
            }
          ]
        },
        {
          "id":"2",
          "project_no":"1002",
          "task":[
            {
              "task_description":"Meeting description",
              "id":"333",
              "task_summary":"Meeting"
            },
            {
              "task_description":"Administration",
              "id":"444",
              "task_summary":"Admin"
            }
          ]
        }
      ]
    }

var template = function(target) {
  return {
  
    clone: function(value) {
      if (!template.clone_item) {
        
        // create clone variable
        template.clone_item = target.cloneNode(true);
        
        return target;
      } else {
        
        // return/append clone variable
        return target.parentNode.appendChild(template.clone_item.cloneNode(true));
      }
    },
    
    init: function(value) {

      // first select (projects)        
      var sel = target.querySelector('select');
      sel.addEventListener('change', function() {

        // second select (tasks)
        var sel = target.querySelectorAll('select')[1];
        sel.addEventListener('change', function() {

          var selvalues = this.options[this.selectedIndex].value.split('|');
          var data = value[selvalues[0]].task[selvalues[1]].task_description;

          // last inout (tasks descript.)        
          var inp = target.querySelector('input');
          inp.value = data;

        });

        // clear last used task select options
        for (i=sel.length-1;sel.length >1;i--) {
          sel.remove(i);
        }

        // clear last used task descript.
        var inp = target.querySelector('input');
        inp.value = '';

        // disable task select
        sel.disabled = true;

        // add task select options
        var selvalue = this.options[this.selectedIndex].value;
        if (selvalue != '') {
          sel.disabled = false;
          var data = value[selvalue].task;
          var index = 0;
          for (var key in data) {
            createOption(sel,selvalue + '|' + index++,data[key].task_summary);
          }
        }

      });
      var index = 0;

      // add project select options
      for (var key in value) {
        createOption(sel,index++,value[key].project_no);
      }

      // hours
      var inp = target.querySelector('.hours');
      inp.addEventListener('change', function() {
        updateHours();
      });
      updateHours();
        
    }
  };
};

function createRow() {
  var clone = template(document.querySelector('.myrow')).clone();
  template(clone).init(myJson.listItems);
}

function createOption(sel,val,txt) {
  var opt = document.createElement('option');
  opt.value = val;
  opt.text = txt;
  sel.add(opt);
}

function updateHours() {
  var total = 0;
  var hours = document.querySelectorAll('#mytable .hours');
  for (i = 0; i < hours.length;i++) {
    total += parseFloat(hours[i].value) || 0;
  }
  document.getElementById('total').value = total;  
}

function ready(fn) {
  if (document.readyState != 'loading'){
    fn();
  } else {
    document.addEventListener('DOMContentLoaded', fn);
  }
}

ready(function(){
  createRow();
  document.querySelector('.add-row').addEventListener('click', function() {
    createRow();
  });
});
&#13;
<form>
  <table id='mytable'>
    <tr>
      <td>Project</td>
      <td>Workstage</td>
      <td>Hours</td>
      <td>Description</td>
    </tr>
    <tr>
      <td></td>
      <td></td>
      <td><input size=3 id='total' disabled='disabled'/></td>
      <td></td>
    </tr>
    <tr class='myrow'>
      <td>
        <select class="project" name="">
          <option value="">Select One</option>
        </select>
      </td>
      <td>
        <select class="task" name="" disabled=disabled>
          <option value="000">-Select Task-</option>
        </select>
      </td>
      <td>
        <select class='hours'>
          <option>1.0</option>
          <option>1.5</option>
          <option>2.0</option>
        </select>
      </td>
      <td><input type="text" value="" class="task-text" /></td>
    </tr>
  </table>
  <input type="button" class="add-row" value="Add Row" />
</form>
&#13;
&#13;
&#13;