Search code examples
javascriptjquerysummernote

Summernote Editor - Add repeater field to dialog


I would like to use a repeater field inside my summernote dialog box. I am using the jquery.repeater library.

Find below my minimum viable example:

$(document).ready(function() {
  $('.summernote').summernote({
    height: 300,
    tabsize: 2,
    toolbar: [
      ['insert', ['synonym', 'codeview']]
    ],
  });
});

(function(factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else if (typeof module === 'object' && module.exports) {
    // Node/CommonJS
    module.exports = factory(require('jquery'));
  } else {
    // Browser globals
    factory(window.jQuery);
  }
}(function($) {

  $.extend($.summernote.plugins, {
    'synonym': function(context) {
      var self = this;
      var ui = $.summernote.ui;
      var $editor = context.layoutInfo.editor;
      var options = context.options;

      context.memo('button.synonym', function() {
        return ui.button({
          contents: '<i class="fa fa-snowflake-o">',
          tooltip: 'Create Synonym',
          click: context.createInvokeHandler('synonym.showDialog')
        }).render();
      });

      self.initialize = function() {
        var $container = options.dialogsInBody ? $(document.body) : $editor;

        var body = '<div class="form-group">' +
          'IF(<input type="text" name="text-input" value="A"/>) <input type="text" name="text-input" value="A"/>' +
          '<div data-repeater-list="group-a">' +
          '<div data-repeater-item>' +
          '<select name="select-input">' +
          '<option value="A" selected>ELSE</option>' +
          '<option value="B">ELSEIF</option>' +
          '</select>' +
          '(<input type="text" name="text-input" value="A"/>)' +
          '<input type="text" name="text-input" value="A"/>' +
          '<input data-repeater-delete type="button" value="Delete"/>' +
          '</div>' +
          '</div>' +
          '<input data-repeater-create type="button" value="Add"/>' +
          '<br/>' +
          '@ENDIF'
        var footer = '<button href="#" class="btn btn-primary ext-ifElse-btn">OK</button>';

        self.$dialog = ui.dialog({
          title: 'minimal dialog title',
          fade: options.dialogsFade,
          body: body,
          footer: footer
        }).render().appendTo($container);
      };

      self.destroy = function() {
        self.$dialog.remove();
        self.$dialog = null;
      };

      self.showDialog = function() {
        self
          .openDialog()
          .then(function(data) {
            // [workaround] hide dialog before restore range for IE range focus
            ui.hideDialog(self.$dialog);
            context.invoke('editor.restoreRange');
            self.insertToEditor(data);
            // do something with dialogData
            console.log("dialog returned: ", data)
            // ...
          })
          .fail(function() {
            context.invoke('editor.restoreRange');
          });

      };

      self.openDialog = function() {
        return $.Deferred(function(deferred) {
          var $dialogBtn = self.$dialog.find('.ext-synonym-btn');
          var $elemInput = self.$dialog.find('#input-element')[0];
          var $synonymInput = self.$dialog.find('#input-synonym')[0];

          var selectedText = $.selection()
          $('#input-element').val(selectedText);
          console.log("show dialog: " + selectedText)

          ui.onDialogShown(self.$dialog, function() {
            context.triggerEvent('dialog.shown');

            $dialogBtn
              .click(function(event) {
                event.preventDefault();

                deferred.resolve({
                  element: $elemInput.value,
                  synonym: $synonymInput.value
                });
              });
          });

          ui.onDialogHidden(self.$dialog, function() {
            $dialogBtn.off('click');

            if (deferred.state() === 'pending') {
              deferred.reject();
            }
          });

          ui.showDialog(self.$dialog);
        });
      };

      this.insertToEditor = function(data) {
        console.log("synonym: " + data.synonym)

        var $elem = $('<synonym>', {
          words: data.synonym
        });;

        $elem.text(data.element)

        context.invoke('editor.insertNode', $elem[0]);
      };
    }
  });
}));
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.css" />

<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.selection/1.0.1/jquery.selection.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.repeater/1.2.1/jquery.repeater.min.js"></script>

<script type="text/javascript">
  $(document).ready(function() {
    // repeater field
    $(document).ready(function() {

      $('.repeater').repeater({
        defaultValues: {
          'textarea-input': 'foo',
          'text-input': 'bar',
          'select-input': 'B'
        },
        show: function() {
          $(this).slideDown();
        },
        hide: function(deleteElement) {
          /*
          if(confirm('Are you sure you want to delete this element?')) {
              $(this).slideUp(deleteElement);
          }
          */
        },
        ready: function(setIndexes) {

        }
      });

      window.outerRepeater = $('.outer-repeater').repeater({
        isFirstItemUndeletable: true,
        defaultValues: {
          'text-input': 'outer-default'
        },
        show: function() {
          console.log('outer show');
          $(this).slideDown();
        },
        hide: function(deleteElement) {
          console.log('outer delete');
          $(this).slideUp(deleteElement);
        },
        repeaters: [{
          isFirstItemUndeletable: true,
          selector: '.inner-repeater',
          defaultValues: {
            'inner-text-input': 'inner-default'
          },
          show: function() {
            console.log('inner show');
            $(this).slideDown();
          },
          hide: function(deleteElement) {
            console.log('inner delete');
            $(this).slideUp(deleteElement);
          }
        }]
      });
    });
  });
</script>
<div class="container">
  <div class="summernote">
    <p>Hello World!</p>
    This text should be replaced by the dialog. </div>
</div>

	<!-- START -->
      IF(<input type="text" name="text-input" value="A"/>) <input type="text" name="text-input" value="A"/>
      <div data-repeater-list="group-a">
        <div data-repeater-item>
		          <select name="select-input">
            <option value="A" selected>ELSE</option>
            <option value="B">ELSEIF</option>
          </select> 
          (<input type="text" name="text-input" value="A"/>)
          <input type="text" name="text-input" value="A"/>
          <input data-repeater-delete type="button" value="Delete"/>
        </div>
      </div>
      <input data-repeater-create type="button" value="Add"/>
	  <br>
	  @ENDIF
	  <!-- START -->

As you can see when pressing the snowflake button, the repeater field does not work. So when pressing Add no further fields get added in the dialog box.

Any suggestions what I am doing wrong?

I appreciate your replies!


Solution

  • You have to assign repeater in the wrapper which you want to repeated. Test the below code scroll to bottom.

    $(document).ready(function() {
      $('.summernote').summernote({
        height: 300,
        tabsize: 2,
        toolbar: [
          ['insert', ['synonym', 'codeview']]
        ],
      });
    });
    
    (function(factory) {
      if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
      } else if (typeof module === 'object' && module.exports) {
        // Node/CommonJS
        module.exports = factory(require('jquery'));
      } else {
        // Browser globals
        factory(window.jQuery);
      }
    }(function($) {
    
      $.extend($.summernote.plugins, {
        'synonym': function(context) {
          var self = this;
          var ui = $.summernote.ui;
          var $editor = context.layoutInfo.editor;
          var options = context.options;
    
          context.memo('button.synonym', function() {
            return ui.button({
              contents: '<i class="fa fa-snowflake-o">',
              tooltip: 'Create Synonym',
              click: context.createInvokeHandler('synonym.showDialog')
            }).render();
          });
    
          self.initialize = function() {
            var $container = options.dialogsInBody ? $(document.body) : $editor;
    
            var body = '<div class="form-group">' +
              'IF(<input type="text" name="text-input" value="A"/>) <input type="text" name="text-input" value="A"/>' +
              '<div data-repeater-list="group-a">' +
              '<div data-repeater-item>' +
              '<select name="select-input">' +
              '<option value="A" selected>ELSE</option>' +
              '<option value="B">ELSEIF</option>' +
              '</select>' +
              '(<input type="text" name="text-input" value="A"/>)' +
              '<input type="text" name="text-input" value="A"/>' +
              '<input data-repeater-delete type="button" value="Delete"/>' +
              '</div>' +
              '</div>' +
              '<input data-repeater-create type="button" value="Add"/>' +
              '<br/>' +
              '@ENDIF'
            var footer = '<button href="#" class="btn btn-primary ext-ifElse-btn">OK</button>';
    
            self.$dialog = ui.dialog({
              title: 'minimal dialog title',
              fade: options.dialogsFade,
              body: body,
              footer: footer
            }).render().appendTo($container);
          };
    
          self.destroy = function() {
            self.$dialog.remove();
            self.$dialog = null;
          };
    
          self.showDialog = function() {
            self
              .openDialog()
              .then(function(data) {
                // [workaround] hide dialog before restore range for IE range focus
                ui.hideDialog(self.$dialog);
                context.invoke('editor.restoreRange');
                self.insertToEditor(data);
                // do something with dialogData
                console.log("dialog returned: ", data)
                // ...
              })
              .fail(function() {
                context.invoke('editor.restoreRange');
              });
    
          };
    
          self.openDialog = function() {
            return $.Deferred(function(deferred) {
              var $dialogBtn = self.$dialog.find('.ext-synonym-btn');
              var $elemInput = self.$dialog.find('#input-element')[0];
              var $synonymInput = self.$dialog.find('#input-synonym')[0];
    
              var selectedText = $.selection()
              $('#input-element').val(selectedText);
              console.log("show dialog: " + selectedText)
    
              ui.onDialogShown(self.$dialog, function() {
                context.triggerEvent('dialog.shown');
    
                $dialogBtn
                  .click(function(event) {
                    event.preventDefault();
    
                    deferred.resolve({
                      element: $elemInput.value,
                      synonym: $synonymInput.value
                    });
                  });
              });
    
              ui.onDialogHidden(self.$dialog, function() {
                $dialogBtn.off('click');
    
                if (deferred.state() === 'pending') {
                  deferred.reject();
                }
              });
    
              ui.showDialog(self.$dialog);
            });
          };
    
          this.insertToEditor = function(data) {
            console.log("synonym: " + data.synonym)
    
            var $elem = $('<synonym>', {
              words: data.synonym
            });;
    
            $elem.text(data.element)
    
            context.invoke('editor.insertNode', $elem[0]);
          };
        }
      });
    }));
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.css" />
    
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.10/summernote.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.selection/1.0.1/jquery.selection.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.repeater/1.2.1/jquery.repeater.min.js"></script>
    
    
    <script type="text/javascript">
      $(document).ready(function() {
        // repeater field
        $(document).ready(function() {
    
          $('.repeater').repeater({
            defaultValues: {
              'textarea-input': 'foo',
              'text-input': 'bar',
              'select-input': 'B'
            },
            show: function() {
              $(this).slideDown();
            },
            hide: function(deleteElement) {
              /*
              if(confirm('Are you sure you want to delete this element?')) {
                  $(this).slideUp(deleteElement);
              }
              */
            },
            ready: function(setIndexes) {
    
            }
          });
    
          window.outerRepeater = $('.outer-repeater').repeater({
            isFirstItemUndeletable: true,
            defaultValues: {
              'text-input': 'outer-default'
            },
            show: function() {
              console.log('outer show');
              $(this).slideDown();
            },
            hide: function(deleteElement) {
              console.log('outer delete');
              $(this).slideUp(deleteElement);
            },
            repeaters: [{
              isFirstItemUndeletable: true,
              selector: '.inner-repeater',
              defaultValues: {
                'inner-text-input': 'inner-default'
              },
              show: function() {
                console.log('inner show');
                $(this).slideDown();
              },
              hide: function(deleteElement) {
                console.log('inner delete');
                $(this).slideUp(deleteElement);
              }
            }]
          });
        });
      });
    </script>
    
    <div class="container">
      <div class="summernote">
        <p>Hello World!</p>
        This text should be replaced by the dialog. </div>
    </div>
    <div class="repeater">
      <!-- START -->
      IF(<input type="text" name="text-input" value="A" />) <input type="text" name="text-input" value="A" />
      <div data-repeater-list="group-a">
        <div data-repeater-item>
          <select name="select-input">
            <option value="A" selected>ELSE</option>
            <option value="B">ELSEIF</option>
          </select>
          (<input type="text" name="text-input" value="A" />)
          <input type="text" name="text-input" value="A" />
          <input data-repeater-delete type="button" value="Delete" />
        </div>
      </div>
      <input data-repeater-create type="button" value="Add" />
      <br> @ENDIF
    </div>
    <!-- START -->