通用函数创建子元素并附加到父节点

时间:2016-07-30 10:35:32

标签: javascript html function dom

我想创建和使用通用的javascript函数,使用它可以轻松,快速和灵活地为父节点创建新的子节点。

看看我的代码:

\App\Package

使用\App\Package\User,您无法获得预期的结果,而src/User.php中显示的指定调用可以正常工作:

<!DOCTYPE html>
<html>
<body>

<style>
div {
    border: 2px solid #eeeeee;
    background-color: #dff0d8;
}
ol {
    background-color: #dff0d8;
}
li {
    background-color: #eff0c8;
}
</style>

<script>
function addNewElement(newElementType,parentId) {
    var newElement = document.createElement(newElementType);
    newElement.innerHTML = 'new element';
    parentId.appendChild(newElement);
    // actually I want to use just this simple code, what makes this function universal, but it doesn't work..
    // while next commented lines work as it should
    /**
    if (parentId == "someThing"){
         someThing.appendChild(newElement);
    }
    if (parentId == "list"){
         list.appendChild(newElement);
    }
    **/
}
</script>

<p>In next example we can add new child element to this list:</p>
<ol id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ol>
<button onclick="addNewElement('li','list')">Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button onclick="addNewElement('div','someThing')">Add new div-element to this div</button>
</body>
</html>

我是JS的新手,所以我不完全理解为什么我不能使用parentId.appendChild(newElement)来获得相同的结果。

我想如果没有任何 jQuery 或其他库,它应该很简单。

所以我问你怎么能实现这个目标?

4 个答案:

答案 0 :(得分:1)

首先,您不应该多次使用相同的元素ID。

根据W3C

  

id属性指定HTML元素的唯一ID(该值在HTML文档中必须是唯一的。)

所以我更改了你的HTML,即从按钮中删除了ID并将所需的ID传递到addNewElement函数:

<p>In next example we can add new child element to this list:</p>
<ol id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ol>
<button onclick="addNewElement('li', 'list')">Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button onclick="addNewElement('div', 'someThing')">Add new div-element to this div</button>

然后我更新了addNewElement功能:

function addNewElement(elementType, parentId) {
    let parentElement = document.getElementById(parentId);
    let newElement = document.createElement(elementType);

    newElement.innerHTML = 'new element';
    parentElement.appendChild(newElement);
}

它有效。

请查看jsFiddle了解详情。

答案 1 :(得分:1)

虽然您已经接受了答案,但我觉得提供更具扩展性的方法可能是值得的,它允许您使用不引人注目的JavaScript(而不是依赖于onclick等内联事件处理程序)为了便于维护。

它也更具可扩展性和可定制性:

// a simple function to help derive the correct element
// from the supplied argument, 'needle':
function derive(needle) {

  // if the needle has a nodeType and if that nodeType is
  // exactly equal to 1:
  if (needle.nodeType && needle.nodeType === 1) {

    // the needle is then an element-node, and here
    // we convert that node into an Array of one:
    needle = [needle];

  // otherwise, if the needle is a string, and
  // document.getElementById() finds an element
  // with that id:
  } else if ('string' === typeof needle && document.getElementById(needle)) {
    // we find that element-node again, using the string
    // and again convert it to an Array of one:
    needle = [document.getElementById(needle)];

  // otherwise, if the needle is - again - a string, and
  // document.querySelectorAll() can find a collection
  // (of one or more) elements matching the selector that
  // the needle is implied to be then we retrieve those
  // elements and, using Array.from(), we convert the
  // collection into an Array:
  } else if ('string' === typeof needle && document.querySelectorAll(needle)) {
    needle = Array.from(document.querySelectorAll(needle));
  }

  // here we return the results to the calling context:
  return needle;
}


function addNewElement(opts) {

  // the default settings for the function:
  // append:  Boolean, true: the content will be
  //          inserted after the found sibling-
  //          node; false: the content will be
  //          inserted before the found sibling-
  //          node.
  // classes: String, a string of white-space
  //          separated class-names to add to
  //          the new contents,
  //          Array, an array of class-names to
  //          add to the new contents.
  // content: String, a string of HTML you wish
  //          to appear in the newly-added content.
  // count:   Number, the number of elements you
  //          wish to insert at once.
  // create:  String, the element-type to create
  //          null, if you want the function to
  //          'decide' for itself.
  // parent:  Node, the element to which you want
  //          to add new elements,
  //          String, the id of the element to
  //          which you want to add new elements,
  //          or a CSS selector by which you want
  //          find the element(s) in the document
  //          to add new elements to.
  // sibling: Node, the node beside which the new
  //          element(s) should be added.
  //          Null, the function will try to determine
  //          the desired element beside which the
  //          content should be added, based on
  //          the 'append' setting (above).
  var settings = {
      'append': true,
      'classes' : null,
      'content': 'Newly-added element.',
      'count': 1,
      'create': null,
      'parent': document.body,
      'sibling': null
    },

    // uninitialised variables for use later, primarily
    // to declare/instantiate variables in one place:
    parents,
    childType,
    created,
    sibling,
    clone,
    classes,
    count,

    // a documentFragment to enable the addition of multiple
    // elements at the same time without triggering (quite so)
    // many redraws of the document/page:
    fragment = document.createDocumentFragment();

  // using Object.keys to iterate over the opts Object, if
  // one is supplied or an empty object to avoid errors,
  // using the Array.prototype.forEach() method:
  Object.keys(opts || {}).forEach(function(key) {

    // here we update/overwrite the keys of the
    // settings object to the values held in those
    // properties of the opts Object:
    settings[key] = opts[key];
  });

  // we call the derive function to retrieve an array
  // of element(s):
  parents = derive(settings.parent);

  // checking, and then storing, the value of
  // settings.append; it it's equal to true the
  // assessment returns true, if it's equal to
  // false the assessment returns false (this
  // is a naive check, because it requires that
  // a Boolean is stored in that property):
  appendCheck = settings.append === true;

  // ensuring that the settings.count number
  // is a number by parsing the potential
  // String, other-based number, into base-10:
  count = parseInt(settings.count, 10);

  // iterating over each of the parents:
  parents.forEach(function(pater) {
    // 'pater' the first argument is a reference
    // to the current array-element of the array
    // over which we're iterating.

    // retrieving the element-type to be created,
    // if a value was supplied in settings.create
    // then we use that (we don't check it's a
    // valid element, or that it can be validly
    // contained in the nominated parent), otherwise
    // if the current element node has children
    // then we retrieve the localName of its
    // lastElementChild, if it has no children
    // the ternary returns null and we move to
    // the string of 'div':
    childType = settings.create || (pater.children.length > 0 ? pater.lastElementChild.localName : null) || 'div';

    // here we create the element:
    created = document.createElement(childType);

    // if the earlier assessment of settings.append
    // resulted in true:
    if (appendCheck === true) {

      // we find the sibling beside which to insert the
      // new content; if a node was supplied we use that,
      // otherwise we use the lastElementChild or lastChild:
      sibling = settings.sibling || pater.lastElementChild || pater.lastChild;
    } else if (appendCheck === false) {
      // otherwise, we use either the supplied value or
      // we use the firstElementChild or firstChild:
      sibling = settings.sibling || pater.firstElementChild || pater.firstChild
    }

    // assign the supplied - or default - content to the
    // created element:
    created.innerHTML = settings.content;

    // if any class-names have been supplied:
    if (settings.classes) {

      // we first check whether the settings.classes
      // variable is an Array (using Array.isArray),
      // which returns a Boolean (true or false); if
      // it returns true we simply use the Array otherwise
      // we assume it's a String and split that String
      // on its white-space characters (/\s+/):
      classes = Array.isArray(settings.classes) ? settings.classes : settings.classes.split(/\s+/);

      // iterating over the array of class-names:
      classes.forEach(function(cN) {
        // the first argument (cN) is a reference
        // to the current array-element of the
        // Array over which we're iterating.

        // here we use the Element.classList API to
        // add each of the class-names:
        created.classList.add(cN);
      });
    }

    // a simple for loop to add the desired
    // number of new elements (as supplied in
    // the settings.count, or opts.count
    // setting):
    for (var i = 0; i < count; i++) {

      // clone the created-element (and its
      // child elements):
      clone = created.cloneNode(true);

      // append the cloned node to the 
      // documentFragment we created
      // earlier:
      fragment.appendChild(clone);
    }

    // here we use parentNode.insertBefore() to insert
    // the new contents (held in fragment) either the
    // sibling.nextSibling (if appendCheck is true) or
    // before the sibling (if appendCheck is false):
    pater.insertBefore(fragment, (appendCheck ? sibling.nextSibling : sibling));
  });
}

// retrieving the <button> elements on the page, and converting
// to an Array, using Array.from():
var buttons = Array.from(document.querySelectorAll('button'));

// iterating over those <button> elements in the Array:
buttons.forEach(function(button) {

  // using the anonymous function of the addEventListener()
  // to call the addNewElement function, in which
  // we set the opts.parent setting to the
  // previousElementSibling of the button
  // firing the event:
  button.addEventListener('click', function() {
    addNewElement({
      'parent': button.previousElementSibling
    });
  });
});

function derive(needle) {
  if (needle.nodeType && needle.nodeType === 1) {
    needle = [needle];
  } else if ('string' === typeof needle && document.getElementById(needle)) {
    needle = [document.getElementById(needle)];
  } else if ('string' === typeof needle && document.querySelectorAll(needle)) {
    needle = Array.from(document.querySelectorAll(needle));
  }

  return needle;
}

function addNewElement(opts) {

  var settings = {
      'append': true,
      'classes': null,
      'create': null,
      'content': 'Newly-added element.',
      'count': 1,
      'parent': document.body,
      'sibling': null
    },
    parents,
    childType,
    created,
    sibling,
    clone,
    classes,
    fragment = document.createDocumentFragment();

  Object.keys(opts || {}).forEach(function(key) {
    settings[key] = opts[key];
  });

  parents = derive(settings.parent);
  appendCheck = settings.append === true;

  parents.forEach(function(pater) {
    childType = settings.create || (pater.children.length > 0 ? pater.lastElementChild.localName : null) || 'div';
    created = document.createElement(childType);
    if (appendCheck === true) {
      sibling = settings.sibling || pater.lastElementChild || pater.lastChild;
    } else if (appendCheck === false) {
      sibling = settings.sibling || pater.firstElementChild || pater.firstChild
    }

    created.innerHTML = settings.content;

    if (settings.classes) {
      classes = Array.isArray(settings.classes) ? settings.classes : settings.classes.split(/\s+/);
      classes.forEach(function(cN) {
        created.classList.add(cN);
      });
    }

    for (var i = 0; i < settings.count; i++) {
      clone = created.cloneNode(true);
      fragment.appendChild(clone);
    }

    pater.insertBefore(fragment, (appendCheck ? sibling.nextSibling : sibling));
  });
}

var buttons = Array.from(document.querySelectorAll('button'));

buttons.forEach(function(button) {
  button.addEventListener('click', function() {
    addNewElement({
      'parent': button.previousElementSibling
    });
  });
});
div {
  border: 2px solid #eeeeee;
  background-color: #dff0d8;
}
ol {
  background-color: #dff0d8;
}
li {
  background-color: #eff0c8;
}
<p>In next example we can add new child element to this list:</p>
<ol id="list">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ol>
<button>Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button>Add new div-element to this div</button>

JS Fiddle demo

参考文献:

答案 2 :(得分:0)

好吧,我找到了解决问题的简单方法,但我一直在寻找更基本的东西:

document.getElementById(parentId).appendChild(newElement);

修改 另一种方法:

<!DOCTYPE html>
<html>
<body>
<style>
div {
    border: 2px solid #eeeeee;
    background-color: #dff0d8;
}
ol {
    background-color: #dff0d8;
}
li {
    background-color: #eff0c8;
}
</style>
<script>
function addNewElement(newElementType,parentId,parentElementType) {
    //document.getElementById(clickedId).appendChild(newElement);
    var el = parentElementType + "[id=" + parentId + "]";
    el = document.querySelector(el);
    var newElement = document.createElement(newElementType);
    newElement.innerHTML = 'new element';
    el.appendChild(newElement);
}
</script>
<p>In next example we can add new child element to this list:</p>
<ol id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ol>
<button onclick="addNewElement('li','list','ol')">Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button onclick="addNewElement('div','someThing','div')">Add new div-element to this div</button>
</body>
</html>

但是现在我们需要在这个新例子中在addNewElement函数中传递父节点类型。或者我们也可以为ul和div元素定义类,并使用它们而不是id。 它是更先进的方式,但在某些情况下可能更有用。这是关于document.querySelectordocument.querySelectorAll的纪录片。

如果您想获得更多信息,请阅读此querySelector and querySelectorAll vs getElementsByClassName and getElementById in JavaScript

答案 3 :(得分:0)

我知道你已经有了一个适合你的答案,但我只是想添加一个使用配置对象而不是仅仅传入标签名称来更灵活地执行此操作的方法。为了使其更灵活,您可以传入对父对象的引用而不是id。此外,它还会返回对新创建元素的引用,以防您在将其添加到DOM后对其执行某些操作。

'use strict';

var addNewElement = function (configItems, elParent) {
    var newElements = [];

    if (!Array.isArray(configItems)) {
      // if configItems is not an array, and therefore a
      // single config object or string, turn it into
      // a single element array
      configItems = [configItems];
    }

    // If elParent is a string assume it is
    // the id of an element in the page and select it
    if (typeof elParent === 'string') {
      elParent = document.getElementById(elParent);
    }

    configItems.forEach(function (config) {
      var option,
        elChild;
      // if a string is passed in, assume it is
      // the tagName and create a default config object
      if (typeof config === 'string') {
        config = {tag: config};
      }


      elChild = document.createElement(config.tag);

      for (option in config) {
        if (config.hasOwnProperty(option)) {
          switch (option) {
            case 'tag':
              // do nothing, already used tag to create new element
              break;
            case 'html':
              // just a shortcut so we don't have to use
              // innerHTML in our config object
              elChild.innerHTML = config.html;
              break;
            case 'text':
              // another shortcut
              elChild.textContent = config.text;
              break;
            case 'class':
              // if we are passed an array convert it to a space delimited string
              elChild.className = Array.isArray(config.class) ?
                config.class.join(' ') : config.class;
              break;
            default:
              // if we haven't already handled it, assume it is
              // an attribute to add to the element
              elChild.setAttribute(option, config[option]);
          }
        }
      }

      // default text if none was specified
      if (elChild.innerHTML === '') {
        elChild.innerHTML = 'new element';
      }

      newElements.push(elChild);
      elParent.appendChild(elChild);
    });

    // return a reference to the new element(s)
    // in case you want to do something else with it
    // after it was inserted into the document
    // returns a single item or an array depending on how many
    // items you passed it in configItems
    return newElements.length === 1 ? newElements[0] : newElements;
};

用法如下:

// just add a new element with the default text by id
addNewElement('li', 'list');

var list = document.getElementById('list');

// a little fancier, this time using an element reference
addNewElement({
  tag: 'li',
  html: 'Custom List Item!',
  class: 'fancy'
}, list);

addNewElement({
  tag: 'input',
  placeholder: 'Type here',
  value: 'Delete me'
}, document.body); // attach to the body

// do something with the element
// after we create it
var houdini = addNewElement({
  tag: 'li',
  text: 'Now you see me.',
  class: ['houdini', 'show'],
}, list);

setTimeout(function () {
  houdini.textContent = "Now you don't";
  houdini.classList.remove('show');
}, 2000);

var checkElements = addNewElement([
  {
    tag: 'input',
    id: 'check',
    type: 'checkbox',
    checked: 'checked',
  },
  {
    tag: 'label',
    for: 'check',
    html: 'Uncheck me!'
  }
], document.body);

jsFiddle显示它的实际效果。

使用hasOwnProperty is necessary since we are using for in

'class'的情况是因为在ES3中你不能使用保留字作为带点符号的属性名称,所以在设计DOM API时,他们使用className来表示类属性。自ES5以来我们可以use reserved words as properties without quoting them。这允许我们添加'class'快捷方式属性。