jQuery动态创建元素的“on create”事件

89

我需要能够动态创建 <select> 元素并将其转换为 jQuery .combobox()。这应该是元素创建事件,而不是一些“点击”事件,在这种情况下,我可以使用 jQuery 的 .on()

那么这样的东西存在吗?

$(document).on("create", "select", function() {
    $(this).combobox();
}

我不愿意使用livequery,因为它已经非常过时了。

更新 所提到的选择/下拉框是通过ajax加载到jQuery colorbox(模态窗口)中的,因此问题在于 - 我只能使用colorbox onComplete 来初始化下拉框,但是当一个下拉框发生变化时,另一个选择/下拉框必须被动态创建,因此我需要一种更通用的方式来检测元素的创建(在这种情况下是select)。

更新2 为了进一步解释问题 - 我递归地创建了select/combobox元素,.combobox()里也有很多初始化代码,因此如果我使用传统方法,比如@bipen的答案中所述的,我的代码将会非常臃肿。希望这样能更好地解释问题。

更新3 谢谢大家,我现在明白了,自从DOMNodeInserted被废弃以来,在DOM突变中留下了空白,没有解决这个问题的方法。我得重新思考我的应用程序。


1
可能使用ready事件吗? $(select).ready(function() { }); - Pablo Banderas
@PabloBanderas ready 用于: "指定一个函数,在 DOM 完全加载后执行。"(来自 jquery.com) - Caballero
2
为什么不在创建/插入时应用combobox()方法,而是之后再应用呢? - David Thomas
@DavidThomas 我在我的第二次更新中尝试解释原因。 - Caballero
那么是 $(document).on('onComplete', 'select', function(){ // bind here? }); 吗?(显然应该使用比 document 更接近的父元素。) - David Thomas
显示剩余4条评论
12个回答

63
你可以on DOMNodeInserted 事件来获取当它被你的代码添加到文档中时的事件。
$('body').on('DOMNodeInserted', 'select', function () {
      //$(this).combobox();
});

$('<select>').appendTo('body');
$('<select>').appendTo('body');

在这里编辑:http://jsfiddle.net/Codesleuth/qLAB2/3/

编辑后:阅读了一些资料后,我需要再次确认DOMNodeInserted不会在各个浏览器中出现问题。这个问题来自2010年,表明IE不支持该事件,请进行测试。

请参见此处:[链接] 警告!DOMNodeInserted事件类型在本规范中是为了参考和完整性而定义的,但是本规范废弃了使用该事件类型。


.delegate 不是自 jQuery 1.7 起就被弃用并被 .on 取代了吗? - Caballero
2
是的,您说得对。我会修改这个答案,但是我真的不确定您是否应该使用它,因为 DOMNodeInserted 现在已被弃用。 - Codesleuth
我在发布这个问题之前尝试过on('DOMNodeInserted'。很显然,它没有起作用。 - Caballero
25
既然您的问题中没有提到您尝试过,那么我认为这是不公平的评价。无论如何,我在这里无法帮助您。 - Codesleuth

29

如其他答案中提到的那样,mutation events已被弃用,因此您应该使用MutationObserver。由于还没有详细解释,所以在这里我来简单介绍一下...

基本JavaScript API

MutationObserver的API相当简单。虽然不像变异事件那么简单,但仍然可以接受。

function callback(records) {
  records.forEach(function (record) {
    var list = record.addedNodes;
    var i = list.length - 1;
    
 for ( ; i > -1; i-- ) {
   if (list[i].nodeName === 'SELECT') {
     // Insert code here...
     console.log(list[i]);
   }
 }
  });
}

var observer = new MutationObserver(callback);

var targetNode = document.body;

observer.observe(targetNode, { childList: true, subtree: true });
<script>
  // For testing
  setTimeout(function() {
    var $el = document.createElement('select');
    document.body.appendChild($el);
  }, 500);
</script>

让我们分解一下。

var observer = new MutationObserver(callback);

这将创建观察者。观察者目前尚未观察任何内容;这只是事件监听器附加的位置。

observer.observe(targetNode, { childList: true, subtree: true });

这会启动观察器。第一个参数是观察器将在其上观察更改的节点。第二个参数是要观察什么内容的选项

  • childList 表示我需要观察子元素的添加或删除。
  • subtree 是一个修饰符,将childList扩展到该元素子树中的任何位置(否则,它只会查看直接在targetNode中进行的更改)。

除了childList之外的另外两个主要选项是attributescharacterData,它们的意义与它们的名称相似。您必须使用其中之一。

function callback(records) {
  records.forEach(function (record) {

在回调函数中有一些棘手的问题。 回调函数接收一个MutationRecord数组。 每个MutationRecord可以描述一个类型的多个更改(childListattributescharacterData)。 由于我只告诉观察者监视childList,所以不需要检查类型。

var list = record.addedNodes;

在这里,我获取了一个包含所有添加的子节点的NodeList。对于没有添加节点的记录,这将为空(可能有许多这样的记录)。

从那里开始,我循环遍历所添加的节点,并查找其中任何一个是 <select> 元素。

没有真正复杂的内容。

jQuery

...但是您要求使用jQuery。好吧。

(function($) {

  var observers = [];

  $.event.special.domNodeInserted = {

    setup: function setup(data, namespaces) {
      var observer = new MutationObserver(checkObservers);

      observers.push([this, observer, []]);
    },

    teardown: function teardown(namespaces) {
      var obs = getObserverData(this);

      obs[1].disconnect();

      observers = $.grep(observers, function(item) {
        return item !== obs;
      });
    },

    remove: function remove(handleObj) {
      var obs = getObserverData(this);

      obs[2] = obs[2].filter(function(event) {
        return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
      });
    },

    add: function add(handleObj) {
      var obs = getObserverData(this);

      var opts = $.extend({}, {
        childList: true,
        subtree: true
      }, handleObj.data);

      obs[1].observe(this, opts);

      obs[2].push([handleObj.selector, handleObj.handler]);
    }
  };

  function getObserverData(element) {
    var $el = $(element);

    return $.grep(observers, function(item) {
      return $el.is(item[0]);
    })[0];
  }

  function checkObservers(records, observer) {
    var obs = $.grep(observers, function(item) {
      return item[1] === observer;
    })[0];

    var triggers = obs[2];

    var changes = [];

    records.forEach(function(record) {
      if (record.type === 'attributes') {
        if (changes.indexOf(record.target) === -1) {
          changes.push(record.target);
        }

        return;
      }

      $(record.addedNodes).toArray().forEach(function(el) {
        if (changes.indexOf(el) === -1) {
          changes.push(el);
        }
      })
    });

    triggers.forEach(function checkTrigger(item) {
      changes.forEach(function(el) {
        var $el = $(el);

        if ($el.is(item[0])) {
          $el.trigger('domNodeInserted');
        }
      });
    });
  }

})(jQuery);

使用jQuery特殊事件API,创建名为domNodeInserted的新事件。可以像这样使用它:

$(document).on("domNodeInserted", "select", function () {
  $(this).combobox();
});

我个人建议寻找一个类,因为有些库会创建 select 元素用于测试目的。

当然,你也可以使用 .off("domNodeInserted", ...) 或通过传递数据来微调观察:

$(document.body).on("domNodeInserted", "select.test", {
  attributes: true,
  subtree: false
}, function () {
  $(this).combobox();
});

每当body内的元素属性更改时,都会触发检查是否出现了select.test元素。

您可以在下面或jsFiddle上实时查看。

\

(function($) {
  $(document).on("domNodeInserted", "select", function() {
    console.log(this);
    //$(this).combobox();
  });
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<script>
  // For testing
  setTimeout(function() {
    var $el = document.createElement('select');
    document.body.appendChild($el);
  }, 500);
</script>

<script>
  (function($) {

    var observers = [];

    $.event.special.domNodeInserted = {

      setup: function setup(data, namespaces) {
        var observer = new MutationObserver(checkObservers);

        observers.push([this, observer, []]);
      },

      teardown: function teardown(namespaces) {
        var obs = getObserverData(this);

        obs[1].disconnect();

        observers = $.grep(observers, function(item) {
          return item !== obs;
        });
      },

      remove: function remove(handleObj) {
        var obs = getObserverData(this);

        obs[2] = obs[2].filter(function(event) {
          return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
        });
      },

      add: function add(handleObj) {
        var obs = getObserverData(this);

        var opts = $.extend({}, {
          childList: true,
          subtree: true
        }, handleObj.data);

        obs[1].observe(this, opts);

        obs[2].push([handleObj.selector, handleObj.handler]);
      }
    };

    function getObserverData(element) {
      var $el = $(element);

      return $.grep(observers, function(item) {
        return $el.is(item[0]);
      })[0];
    }

    function checkObservers(records, observer) {
      var obs = $.grep(observers, function(item) {
        return item[1] === observer;
      })[0];

      var triggers = obs[2];

      var changes = [];

      records.forEach(function(record) {
        if (record.type === 'attributes') {
          if (changes.indexOf(record.target) === -1) {
            changes.push(record.target);
          }

          return;
        }

        $(record.addedNodes).toArray().forEach(function(el) {
          if (changes.indexOf(el) === -1) {
            changes.push(el);
          }
        })
      });

      triggers.forEach(function checkTrigger(item) {
        changes.forEach(function(el) {
          var $el = $(el);

          if ($el.is(item[0])) {
            $el.trigger('domNodeInserted');
          }
        });
      });
    }

  })(jQuery);
</script>

注意事项

这段 jQuery 代码实现相当基础,当文档中其他地方的修改使您的选择器有效时,它不会触发。

例如,假设您的选择器是.test select,并且文档已经有一个<select>。将类test添加到<body>将使选择器有效,但由于我只检查record.targetrecord.addedNodes,因此事件不会触发。更改必须发生在您希望选择的元素本身。

这可以通过在发生变异时查询选择器来避免。我选择不这样做,以避免为已处理的元素产生重复事件。正确处理相邻通用兄弟组合器会使事情变得更加棘手。

有关更全面的解决方案,请参见https://github.com/pie6k/jquery.initialize,如Damien Ó Ceallaigh回答中所述。然而,该库的作者已经宣布该库是过时的,并建议您不要使用jQuery来实现此功能。


太棒了。谢谢! - jDelforge

22
你可以使用DOMNodeInserted变异事件(无需委托):
$('body').on('DOMNodeInserted', function(e) {
    var target = e.target; //inserted element;
});

编辑:Mutation events已被弃用,请使用mutation observer代替。


10

我刚想到了一个解决我所有ajax问题的方法。

现在,对于on ready事件,我使用以下内容:

function loaded(selector, callback){
    //trigger after page load.
    $(function () {
        callback($(selector));
    });
    //trigger after page update eg ajax event or jquery insert.
    $(document).on('DOMNodeInserted', selector, function () {
        callback($(this));
    });
}

loaded('.foo', function(el){
    //some action
    el.css('background', 'black');
});

而对于普通的触发事件,我现在使用这个:

$(document).on('click', '.foo', function () {
    //some action
    $(this).css('background', 'pink');
});

6

有一个插件叫做adampietrasiak/jquery.initialize,它基于MutationObserver实现了这个简单功能。

$.initialize(".some-element", function() {
    $(this).css("color", "blue");
});

4

这可以使用 DOM4 MutationObservers 实现,但目前仅在Firefox 14+/Chrome 18+中有效。

然而,有一个“史诗级黑科技”(作者的话不是我的!),它可以在所有支持CSS3动画的浏览器中使用:IE10、Firefox 5+、Chrome 3+、Opera 12、Android 2.0+、Safari 4+。请参见博客中的演示。该黑科技是使用具有给定名称的CSS3动画事件,在JavaScript中进行观察并采取行动。


4

对我来说,将绑定绑定到body上没有效果。使用jQuery.bind()绑定到文档上则有效。

$(document).bind('DOMNodeInserted',function(e){
             var target = e.target;
         });

"Mutation Events"(变动事件)例如“DOMNodeInserted”(已插入节点)已被弃用。 - showdev

4
一种看起来可靠的方法(虽然只在Firefox和Chrome中测试过)是使用JavaScript监听animationend(或其camelCased和前缀的同级animationEnd)事件,并对您计划添加的元素类型应用短暂的(在演示中为0.01秒)动画。当然,这不是一个onCreate事件,但在符合标准的浏览器中近似于onInsertion类型的事件。以下是一个概念验证:
$(document).on('webkitAnimationEnd animationend MSAnimationEnd oanimationend', function(e){
    var eTarget = e.target;
    console.log(eTarget.tagName.toLowerCase() + ' added to ' + eTarget.parentNode.tagName.toLowerCase());
    $(eTarget).draggable(); // or whatever other method you'd prefer
});

使用以下 HTML 代码:
<div class="wrapper">
    <button class="add">add a div element</button>
</div>

"而且(虽然在下面的 Fiddle 中有缩写和前缀版本删除)CSS: "
/* vendor-prefixed alternatives removed for brevity */
@keyframes added {
    0% {
        color: #fff;
    }
}

div {
    color: #000;
    /* vendor-prefixed properties removed for brevity */
    animation: added 0.01s linear;
    animation-iteration-count: 1;
}

JS Fiddle演示

显然,CSS可以根据相关元素的位置进行调整,以及在jQuery中使用的选择器(应尽可能靠近插入点)。

有关事件名称的文档:

Mozilla   |  animationend
Microsoft |  MSAnimationEnd
Opera     |  oanimationend
Webkit    |  webkitAnimationEnd
W3C       |  animationend

参考文献:

2

取代...

$(".class").click( function() {
    // do something
});

You can write...

$('body').on('click', '.class', function() {
    // do something
});

1

我认为值得一提的是,在某些情况下,这种方法是可行的:

$( document ).ajaxComplete(function() {
// Do Stuff
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接