Is dynamic forms/entries possible in Widget?

I am new to WordPress Plugins development, and I just started working with the widget. My question is: What are the limitations of WordPress Widget?

I tried to use jQuery to make the Widget Form more interactive/dynamic, but it doesn’t seem to work at all. For example, I have a button that when clicked will add a string of “Hello!” to a div that has id “here”.

<div id="here"></div> 
<input type="button" id="add" value="Click Me!" " />

<script>

    jQuery(document).ready(function(){ 
             //call when the button is click
             $(document).on('click', '#add', function() {
                  $('#here').append('<p>Hello</p>');
             });
        });


</script>
  1. Why doesn’t the paragraph of “Hello” show up on the form? Is it because widget form prevent it from doing so?

  2. I also heard about Backbone.js that refreshes the front-end, but I am not sure if that will work.

  3. If possible, please explain how the whole wordpress widget class work. Thank you in advance!

Update: I already tried what are suggested, but it still doesn’t work. Here are my code.

repeat.php

     <?php
    /**
     * Plugin Name: Repeat Field
     *
     */



     class repeat_field_widget extends WP_Widget {

        /**
         * Sets up the widgets name etc
         */
        public function __construct() {
            $widget_ops = array(
                'classname' => 'repeat_field_widget',
                'description' => 'Repeat Field for Name input',
            );
            parent::__construct( 'repeat_field_widget', 'Repeat Field', $widget_ops );
        }

        /**
         * Outputs the content of the widget
         *
         * @param array $args
         * @param array $instance
         */
        public function widget( $args, $instance ) {
            // outputs the content of the widget
        }

        /**
         * Outputs the options form on admin
         *
         * @param array $instance The widget options
         */
        public function form( $instance ) {
            // outputs the options form on admin
    ?>
        <div id="here"></div>
        <input type="button" id="addRepeat" value="Click Me!" />

      <?php
        }

        /**
         * Processing widget options on save
         *
         * @param array $new_instance The new options
         * @param array $old_instance The previous options
         */
        public function update( $new_instance, $old_instance ) {
            // processes widget options to be saved
        }
    }


    function register_repeat_field_widget() {
        register_widget( 'repeat_field_widget' );
    }
    add_action( 'widgets_init', 'register_repeat_field_widget' );

    function repeat_field_widget_scripts() {
    wp_enqueue_script( 'repeat-field-widget-scripts', get_template_directory_uri() . '/repeat.js', array( 'jquery' ), '1.0.0', true );
}
add_action( 'wp_enqueue_scripts', 'repeat_field_widget_scripts' );

repeat.js

    jQuery(document).ready(function($){
       //call when the button is click
       $("#addRepeat").click(function() {
           $('#here').append('<p>Hello</p>');
       });
    });

Solutions Collecting From Web of "Is dynamic forms/entries possible in Widget?"

Widget forms are tricky for JS manipulation. In general you should never use IDs to detect elements in the form, but a combination of classes and find(), parent() etc.

The reason is that there might be more than one form on the HTML page. For sure on the widget admin page there is a “template” form that is not displayed and IIRC is used to generate the form for a new widget. This means that there will be at least two element with the same ID, and an attempt to access them by DOM APIs will not give a consistent result.

Thank you Dave Romsey and Mark Kaplun for pointing out about not using HTML ID in WordPress Widget. I finally find a solution for my need.

Solution:

We need to add a preset variables into all of the 3 functions (widget, form, update). In my codes, they are called “spotlight_image_link1, spotlight_image_link2, etc”. Then in the “form function” give their id using php code. Please read my code below for more information.

What can my solution do?

Basically, it is not fully dynamic because we need to predefine all the spotlights beforehand. In my case, I only predefined two spotlights to make the code simple to read.

Here what it looks like:
enter image description here

Logic behind the scene:

  • whenever the “Click me!” button is clicked, a spotlight will be added.
  • There is a tracker array called “tracker” that keep track of which predefined spotlights are already used. When all spotlights are used, disable the “Click me!” button.
  • When the “delete spotlight” is clicked, remove that specific spotlight, and re-enable the “Click me!” button. Note that the name of each delete button is append with number. This lets us know which spotlight to be deleted.
  • The spotlight is saved only if and only if the input is not empty. That condition is in the “while” loop of the “form” function.

Limitation:

As of now, whenever, we clicked “Save”, the spotlight order is rearranged from 1 to n. It’s not super user-friendly, but I plan to use Ajax to sync the front-end to the back-end. I am not sure how just yet.

Source code:

  • As of WordPress 4.7.2, it works without any error.
  • Put all the source code into “repeat.php” file in your plugin directory. If you don’t know how, please google it.

repeat.php

    <?php
/**
 * Plugin Name: Repeat Field
 *
 */

 class repeat_field_widget extends WP_Widget {

    /**
     * Sets up the widgets name etc
     */
    public function __construct() {
        $widget_ops = array(
            'classname' => 'repeat_field_widget',
            'description' => 'Repeat Field for Name input'
        );
        parent::__construct( 'repeat_field_widget', 'Repeat Field', $widget_ops );
    $tracker1;
    }

    /**
     * Outputs the content of the widget
     *
     * @param array $args
     * @param array $instance
     */
    public function widget( $args, $instance ) {
        // outputs the content of the widget
    $spotlight_add_row = ! empty( $instance['spotlight_add_row'] ) ? $instance['spotlight_add_row'] : '';
    $spotlight_row_appender = ! empty( $instance['spotlight_row_appender'] ) ? $instance['spotlight_row_appender'] : '';

    /**============== Spotlight 1 =========================*/
    $spotlight_image_link1 = ! empty( $instance['spotlight_image_link1'] ) ? $instance['spotlight_image_link1'] : '';
    $spotlight_add_image1 = ! empty( $instance['spotlight_add_image1'] ) ? $instance['spotlight_add_image1'] : '';
    $spotlight_image_preview1 = ! empty( $instance['spotlight_image_preview1'] ) ? $instance['spotlight_image_preview1'] : '';
    $spotlight_name1 = ! empty( $instance['spotlight_name1'] ) ? $instance['spotlight_name1'] : '';
    $spotlight_description1 = ! empty( $instance['spotlight_description1'] ) ? $instance['spotlight_description1'] : '';
    $spotlight_link1 = ! empty( $instance['spotlight_link1'] ) ? $instance['spotlight_link1'] : '';

    /**============== Spotlight 2 =========================*/
    $spotlight_image_link2 = ! empty( $instance['spotlight_image_link2'] ) ? $instance['spotlight_image_link2'] : '';
    $spotlight_add_image2 = ! empty( $instance['spotlight_add_image2'] ) ? $instance['spotlight_add_image2'] : '';
    $spotlight_image_preview2 = ! empty( $instance['spotlight_image_preview2'] ) ? $instance['spotlight_image_preview2'] : '';
    $spotlight_name2 = ! empty( $instance['spotlight_name2'] ) ? $instance['spotlight_name2'] : '';
    $spotlight_description2 = ! empty( $instance['spotlight_description2'] ) ? $instance['spotlight_description2'] : '';
    $spotlight_link2 = ! empty( $instance['spotlight_link2'] ) ? $instance['spotlight_link2'] : '';
    }

    /**
     * Outputs the options form on admin
     *
     * @param array $instance The widget options
     */
    public function form( $instance ) {
        // outputs the options form on admin
    $instance = wp_parse_args( (array) $instance, array( 'spotlight_add_row' => '', 'spotlight_row_appender' => '', 'spotlight_image_link1' => '', 'spotlight_add_image1' => '', 'spotlight_image_preview1' => '', 'spotlight_name1' => '', 'spotlight_description1' => '', 'spotlight_link1' => '',
    'spotlight_image_link2' => '', 'spotlight_add_image2' => '', 'spotlight_image_preview2' => '', 'spotlight_name2' => '', 'spotlight_description2' => '', 'spotlight_link2' => '' ));

    //Create Add and delete button
    $spotlight_add_row = $instance['spotlight_add_row'];
    $spotlight_row_appender = $instance['spotlight_row_appender'];

    /**================== Spotlight 1 ==============*/
    $spotlight_image_link1 = $instance['spotlight_image_link1'];
    $spotlight_add_image1 = $instance['spotlight_add_image1'];
    $spotlight_image_preview1 = $instance['spotlight_image_preview1'];
    $spotlight_name1 = $instance['spotlight_name1'];
    $spotlight_description1 = $instance['spotlight_description1'];
    $spotlight_link1 = $instance['spotlight_link1'];

    /**================== Spotlight 2 ==============*/
    $spotlight_image_link2 = $instance['spotlight_image_link2'];
    $spotlight_add_image2 = $instance['spotlight_add_image2'];
    $spotlight_image_preview2 = $instance['spotlight_image_preview2'];
    $spotlight_name2 = $instance['spotlight_name2'];
    $spotlight_description2 = $instance['spotlight_description2'];
    $spotlight_link2 = $instance['spotlight_link2'];

    $starter = 1; //Store which number to continue adding spotlight.
    $num = 1;
    $max_spotlight = 2; //number of spotlight allowed.
    static $tracker = array(0,0); //setup a tracker for each spotlight, zero mean none active.

    while($num <= $max_spotlight){
      $tempImage = 'spotlight_image_link' . $num;

       if ($$tempImage != ''){
        $starter++;
        $tracker[$num - 1] = 1;
?>
        <!-- Image input -->
        <div>
        <p class="spotlight-para">Spotlight <?php echo $num; ?></p>
        <p> <?php $tempImage = 'spotlight_image_link' . $num;  $tempDeleteName = 'deletespotlight_'. $num;?> <!-- store the combined name. -->
            <label for="<?php echo esc_attr( $this->get_field_id( $tempImage ) ); ?>"><?php esc_attr_e( 'Image\'s link:', 'text_domain' ); ?></label>
            <input
                   class="widefat"
                   id="<?php echo $this->get_field_id($tempImage); ?>"
                   name="<?php echo $this->get_field_name($tempImage); ?>"
                   type="text"
                   value="<?php echo esc_attr($$tempImage); ?>"
                   />
           <input style="float:right;" id="delete-spotlight" name="<?php echo $tempDeleteName; ?>" type="button" value="Delete Spotlight" class="button"/>
           <br />
        </p>
        </div>
<?php
      }
     $num++;
    }
    $id_prefix = $this->get_field_id(''); //get the widget prefix id.
?>
    <span id="<?php echo $this->get_field_id('spotlight_row_appender'); ?>"> </span>
    <div>
      <br />
      <input
            class="button"
            type="button"
            id="<?php echo $this->get_field_id('spotlight_add_row'); ?>"
            value="Click Me!"
            onclick="repeater.uploader('<?php echo $this->id;?>', '<?php echo $id_prefix;?>'); return false;"
            />
    </div>

    <script>
    jQuery(document).ready(function($){
      var tracker = <?php echo json_encode($tracker); ?>;
      var c1 = <?php echo json_encode($starter - 1); ?>;//index of the array.

      //disbale add button when reaches max spotlight.
      if(tracker.every(x => x > 0)){
        $('#' + '<?php echo $id_prefix; ?>' + 'spotlight_add_row').attr("disabled",true);
      }

      repeater = {
          //TRY to mass Number into this function........
          uploader :function(widget_id, widget_id_string){
            //Find the non active element
            var i;
            for (i = 0; i < <?php echo $max_spotlight; ?>; i++){
              if ( tracker[i] == 0){
                c1 = i;
                break;
              }
            }
            c1++;
            //alert(c1);



            $("#" + widget_id_string + "spotlight_row_appender").append('<div> <p class="spotlight-para">Spotlight '+c1+'</p><p> <label for="<?php echo esc_attr( $this->get_field_id( "spotlight_image_link'+c1+'")); ?>"><?php esc_attr_e( 'Image\'s link:', 'text_domain' ); ?></label>  <input  class="widefat" id="<?php echo $this->get_field_id("spotlight_image_link'+c1+'"); ?>"  name="<?php echo $this->get_field_name("spotlight_image_link'+c1+'"); ?>" type="text" value="" />  <input style="float:right;"id="delete-spotlight" name="deletespotlight_'+c1+'" type="button" value="Delete Spotlight" class="button"/><br /> </p></div>');
            //check element as active
            tracker[c1-1] = 1;

            //if all element is > 0, disable the deleteButton.
            if(tracker.every(x => x > 0)){
              $('#' + '<?php echo $id_prefix; ?>' + 'spotlight_add_row').attr("disabled",true);
            }
            //alert(c1);
            return false;
          }
      };

      $(document).on('click', '#delete-spotlight', function() {

        $(this).parent().parent().remove(); //remove the field.
        $('#' + '<?php echo $id_prefix; ?>' + 'spotlight_add_row').removeAttr("disabled"); //reset add button.

        //Get the name, and parse to get the ID.
        var deleteButtonName = this.name;
        var stringDeleteButton = deleteButtonName.split("_").pop();
        var deleteButtonID = parseInt(stringDeleteButton);

        tracker[deleteButtonID-1] = 0; //reset element
        //alert(tracker);
      });

    });
    </script>
  <?php
    }

    /**
     * Processing widget options on save
     *
     * @param array $new_instance The new options
     * @param array $old_instance The previous options
     */
    public function update( $new_instance, $old_instance ) {
        // processes widget options to be saved
    $instance = $old_instance;
    $instance['spotlight_add_row'] = sanitize_text_field($new_instance['spotlight_add_row']);
    $instance['spotlight_row_appender'] = sanitize_text_field($new_instance['spotlight_row_appender']);

    $increment = 1;
    while ( $increment <= 2 ){
      //increment variables
      $increment_image_link = 'spotlight_image_link' . $increment;
      $increment_add_image = 'spotlight_add_image' . $increment;
      $increment_image_preview = 'spotlight_image_preview' . $increment;
      $increment_description = 'spotlight_description' . $increment;
      $increment_name = 'spotlight_name' . $increment;
      $increment_link = 'spotlight_link' . $increment;

      $instance[$increment_image_link] = sanitize_text_field( $new_instance[$increment_image_link] );
      $instance[$increment_add_image] = sanitize_text_field( $new_instance[$increment_add_image] );
      $instance[$increment_image_preview] = sanitize_text_field( $new_instance[$increment_image_preview]);
      $instance[$increment_name] = sanitize_text_field( $new_instance[$increment_name] );
      $instance[$increment_description] = sanitize_text_field( $new_instance[$increment_description] );
      $instance[$increment_link] = sanitize_text_field( $new_instance[$increment_link] );

      $increment++;
    }
    $starter = 1;
    $num = 1;

    return $instance;
    }
}


function register_repeat_field_widget() {
    register_widget( 'repeat_field_widget' );
}
add_action( 'widgets_init', 'register_repeat_field_widget' );

Quick Note:

I know that this is not the most clean, secure, and best code, but I am still learning. I hope that helps anyone who faces the same issues as me.