title: How to insert a placeholder element in Summernote?
body: <p>I'm developing a plugin for Summernote WYSIWYG editor (version 0.8.1) to insert iframe elements into the code.</p>
  
  <p>Working with the samples provided, I managed to get the plugin button in the menu, which opens a dialog where I can enter an URL and a title. It's no problem to add an iframe Tag to the source, but this is not what I want.</p>
  
  <p>I want to insert a placeholder into the code, with a markup like (or similar to) this: </p>
  
  <pre><code>&lt;div class="ext-iframe-subst" data-src="http://www.test.example" data-title="iframe title"&gt;&lt;span&gt;iframe URL: http://www.test.example&lt;/span&gt;&lt;/div&gt;
  </code></pre>
  
  <p>Now, summernote lets me edit the contents of the span, but I'd like to have a placeholder instead, that can't be modified in the editor. </p>
  
  <p>How can I insert a placeholder into the editor, that has the following properties:</p>
  
  <ul>
  <li>It is editable as a single block (can be deleted with a single delete)</li>
  <li>On click, I can open a popover similar to the link or image popover to adjust size f.i.</li>
  <li>The inner content is not modifyable</li>
  </ul>
  
  <p>This is what I have so far:</p>
  
  <pre><code>// Extends plugins for adding iframes.
  //  - plugin is external module for customizing.
  $.extend($.summernote.plugins, {
    /**
     * @param {Object} context - context object has status of editor.
     */
    'iframe': function (context) {
      var self = this;
  
      // ui has renders to build ui elements.
      //  - you can create a button with `ui.button`
      var ui = $.summernote.ui;
  
      var $editor = context.layoutInfo.editor;
      var options = context.options;
      var lang = options.langInfo;
  
      // add context menu button
      context.memo('button.iframe', function () {
        return ui.button({
          contents: '&lt;i class="fa fa-newspaper-o"/&gt;',
          tooltip: lang.iframe.iframe,
          click: context.createInvokeHandler('iframe.show')
        }).render();
      });
  
  
      // This events will be attached when editor is initialized.
      this.events = {
        // This will be called after modules are initialized.
        'summernote.init': function (we, e) {
          console.log('IFRAME initialized', we, e);
        },
        // This will be called when user releases a key on editable.
        'summernote.keyup': function (we, e) {
          console.log('IFRAME keyup', we, e);
        }
      };
  
      // This method will be called when editor is initialized by $('..').summernote();
      // You can create elements for plugin
      this.initialize = function () {
        var $container = options.dialogsInBody ? $(document.body) : $editor;
  
        var body = '&lt;div class="form-group row-fluid"&gt;' +
            '&lt;label&gt;' + lang.iframe.url + '&lt;/label&gt;' +
            '&lt;input class="ext-iframe-url form-control" type="text" /&gt;' +
            '&lt;label&gt;' + lang.iframe.title + '&lt;/label&gt;' +
            '&lt;input class="ext-iframe-title form-control" type="text" /&gt;' +
            '&lt;label&gt;' + lang.iframe.alt + '&lt;/label&gt;' +
            '&lt;textarea class="ext-iframe-alt form-control" placeholder="' + lang.iframe.alttext + '" rows=""10""&gt;&lt;/textarea&gt;' +
            '&lt;/div&gt;';
        var footer = '&lt;button href="#" class="btn btn-primary ext-iframe-btn disabled" disabled&gt;' + lang.iframe.insert + '&lt;/button&gt;';
  
        this.$dialog = ui.dialog({
          title: lang.iframe.insert,
          fade: options.dialogsFade,
          body: body,
          footer: footer
        }).render().appendTo($container);
      };
  
      // This methods will be called when editor is destroyed by $('..').summernote('destroy');
      // You should remove elements on `initialize`.
      this.destroy = function () {
        this.$dialog.remove();
        this.$dialog = null;
      };
  
  
      this.bindEnterKey = function ($input, $btn) {
        $input.on('keypress', function (event) {
          if (event.keyCode === 13) { //key.code.ENTER) {
            $btn.trigger('click');
          }
        });
      };
  
  
  
      this.createIframeNode = function (data) {
        var $iframeSubst = $('&lt;div class="ext-iframe-subst"&gt;&lt;span&gt;' + lang.iframe.iframe + '&lt;/span&gt;&lt;/div&gt;');
  
        $iframeSubst.attr("data-src", data.url).attr("data-title", data.title);
  
        return $iframeSubst[0];
      };
  
  
      this.show = function () {
        var text = context.invoke('editor.getSelectedText');
        context.invoke('editor.saveRange');
  
        console.log("iframe.getInfo: " + text);
  
        this
          .showIframeDialog(text)
          .then(function (data) {
            // [workaround] hide dialog before restore range for IE range focus
            ui.hideDialog(self.$dialog);
            context.invoke('editor.restoreRange');
  
            // build node
            var $node = self.createIframeNode(data);
  
            if ($node) {
              // insert iframe node
              context.invoke('editor.insertNode', $node);
            }
          })
          .fail(function () {
            context.invoke('editor.restoreRange');
          });
  
      };
  
      this.showIframeDialog = function (text) {
        return $.Deferred(function (deferred) {
          var $iframeUrl = self.$dialog.find('.ext-iframe-url');
          var $iframeTitle = self.$dialog.find('.ext-iframe-title');
          var $iframeBtn = self.$dialog.find('.ext-iframe-btn');
  
          ui.onDialogShown(self.$dialog, function () {
            context.triggerEvent('dialog.shown');
  
            $iframeUrl.val(text).on('input', function () {
              ui.toggleBtn($iframeBtn, $iframeUrl.val());
            }).trigger('focus');
  
            $iframeBtn.click(function (event) {
              event.preventDefault();
  
              deferred.resolve({ url: $iframeUrl.val(), title: $iframeTitle.val() });
            });
  
            self.bindEnterKey($iframeUrl, $iframeBtn);
          });
  
          ui.onDialogHidden(self.$dialog, function () {
            $iframeUrl.off('input');
            $iframeBtn.off('click');
  
            if (deferred.state() === 'pending') {
              deferred.reject();
            }
          });
  
          ui.showDialog(self.$dialog);
        });
      };
  
  
    }
  });
  
  // add localization texts
  $.extend($.summernote.lang['en-US'], {
      iframe: {
        iframe: 'iframe',
        url: 'iframe URL',
        title: 'title',
        insert: 'insert iframe',
        alt: 'Text alternative',
        alttext: 'you should provide a text alternative for the content in this iframe.',
        test: 'Test'
      }
  });
  </code></pre>