2

I've been trying to develop an image widget plugin for WordPress, and I've almost successfully done that thanks to the following code:

<?php
/*
Plugin Name: Title Page Widget (PB)
Plugin URI: http://www.example.com
Description: Creates a full-screen title page for a post (designed to used with Site Origin Page Builder)
Author: Me
Version: 1.0
Author URI: http://www.example.com
*/

// Block direct requests
if ( !defined('ABSPATH') )
    die('-1');

add_action( 'widgets_init', function(){
    register_widget( 'Title_Page_Widget' );
}); 


/**
 * Adds Title_Page_Widget widget.
 */
class Title_Page_Widget extends WP_Widget {

    /**
     * Register widget with WordPress.
     */
    function __construct() {
        parent::__construct(
            'Title_Page_Widget', // Base ID
            __('Title Page Widget (PB)', 'text_domain'), // Name
            array('description' => __( 'Creates a full-screen title page - designed for use with Site Origin\'s Page Builder plugin', 'text_domain' ),) // Args
        );

        add_action( 'sidebar_admin_setup', array( $this, 'admin_setup' ) );

    }

    function admin_setup(){

        wp_enqueue_media();
        wp_register_script('tpw-admin-js', plugins_url('tpw_admin.js', __FILE__), array( 'jquery', 'media-upload', 'media-views' ) );
        wp_enqueue_script('tpw-admin-js');
        wp_enqueue_style('tpw-admin', plugins_url('tpw_admin.css', __FILE__) );

    }       

    /**
     * Back-end widget form.
     *
     * @see WP_Widget::form()
     *
     * @param array $instance Previously saved values from database.
     */
    public function form( $instance ) {


        $title_text = ( isset( $instance['title_text'] ) ) ? $instance['title_text'] : '';
        $title_image = ( isset( $instance['title_image'] ) ) ? $instance['title_image'] : '';


    ?>  

        <div class="titlepage_widget">

            <h3>Title</h3>
            <p>
                <div class="widget_input">
                    <label for="<?php echo $this->get_field_id( 'title_text' ); ?>"><?php _e( 'Text :' ); ?></label>    
                    <input class="title_text" id="<?php echo $this->get_field_id( 'title_text' ); ?>" name="<?php echo $this->get_field_name( 'title_text' ); ?>" value="<?php echo $title_text ?>" type="text"><br/>
                </div>
                <div class="widget_input">
                    <label for="<?php echo $this->get_field_id( 'title_image' ); ?>"><?php _e( 'Image :' ); ?></label>  
                    <input class="title_image" id="<?php echo $this->get_field_id( 'title_image' ); ?>" name="<?php echo $this->get_field_name( 'title_image' ); ?>" value="<?php echo $title_image ?>" type="text"><button id="title_image_button" class="button" onclick="image_button_click('Choose Title Image','Select Image','image','title_image_preview','<?php echo $this->get_field_id( 'title_image' );  ?>');">Select Image</button>            
                </div>
                <div id="title_image_preview" class="preview_placholder">
                <?php 
                    if ($title_image!='') echo '<img src="' . $title_image . '">';
                ?>
                </div>
            </p>    

        </div>

        <?php 
    }

    /**
     * Sanitize widget form values as they are saved.
     *
     * @see WP_Widget::update()
     *
     * @param array $new_instance Values just sent to be saved.
     * @param array $old_instance Previously saved values from database.
     *
     * @return array Updated safe values to be saved.
     */
    public function update( $new_instance, $old_instance ) {

        $instance = array();
        $instance['title_text'] = ( ! empty( $new_instance['title_text'] ) ) ? strip_tags( $new_instance['title_text'] ) : '';
        $instance['title_image'] = ( ! empty( $new_instance['title_image'] ) ) ? strip_tags( $new_instance['title_image'] ) : '';
        return $instance;
    }

} // class My_Widget

And the following is the jQuery which accompanies the previous code:

var custom_uploader;

function image_button_click(dialog_title, button_text, library_type, preview_id, control_id) {

    event.preventDefault();

    //If the uploader object has already been created, reopen the dialog
    //if (custom_uploader) {
     //   custom_uploader.open();
    //    return;
    //}

    //Extend the wp.media object
    custom_uploader = wp.media.frames.file_frame = wp.media({
        title: dialog_title,
        button: {
            text: button_text
        },
        library : { type : library_type },            
        multiple: false
    });

    //When a file is selected, grab the URL and set it as the text field's value
    custom_uploader.on('select', function() {

        attachment = custom_uploader.state().get('selection').first().toJSON();
        jQuery('#' + control_id).val(attachment.url);

        var html = '';

        if (library_type=='image') {
            html = '<img src="' + attachment.url + '">';
        }

        if (library_type=='video') {
            html = '<video autoplay loop><source src="' + attachment.url + '" type="video/' + get_extension( attachment.url ) + '" /></video>';
        }

        jQuery('#' + preview_id).empty();
        jQuery('#' + preview_id).append(html);
    });

    //Open the uploader dialog
    custom_uploader.open();

}

function get_extension( url ){

    return url.substr((url.lastIndexOf('.') + 1));

}

You can can find the whole thing right here.

The problem is that the selected image is only showed after I click on the Save button, and it would be great if it would be shown in the preview field right after the image is selected, without having to click on the Save button, just like the Featured Image widget does in the Post Edit screen.

Any ideas? :)

1 Answers1

2

First thing I suggest is to rewrite the whole thing a bit better. You're writing widgets, and say you want multiple of them, you'll have multiple same id's which isn't a good thing.

So create clear and understandable classes, that you can point to on your click events.

I've made something that works, but it can be enhanced greatly (which I trust is something you'll do :D).

I've added plugin header so that I can install it, and I've had to register widget so that I can see it in my backend as well. I've added remove image button that will remove image and clear the input field on click.

php part:

<?php
/*
Plugin Name: Title_Page_Widget
Plugin URI:
Description: Title_Page_Widget
Version: 1.0.0
Author:
Author
License: GPL
*/


/**
 * Adds Title_Page_Widget widget.
 */
class Title_Page_Widget extends WP_Widget {

    /**
     * Register widget with WordPress.
     */
    function __construct() {
        parent::__construct(
            'Title_Page_Widget', // Base ID
            __('Title Page Widget (PB)', 'text_domain'), // Name
            array('description' => __( 'Creates a full-screen title page - designed for use with Site Origin\'s Page Builder plugin', 'text_domain' ),) // Args
        );

        add_action( 'sidebar_admin_setup', array( $this, 'admin_setup' ) );

    }

    function admin_setup(){

        wp_enqueue_media();
        wp_register_script('tpw-admin-js', plugins_url('tpw_admin.js', __FILE__), array( 'jquery', 'media-upload', 'media-views' ) );
        wp_enqueue_script('tpw-admin-js');
        wp_enqueue_style('tpw-admin', plugins_url('tpw_admin.css', __FILE__) );

    }

    /**
     * Back-end widget form.
     *
     * @see WP_Widget::form()
     *
     * @param array $instance Previously saved values from database.
     */
    public function form( $instance ) {


        $title_text = ( isset( $instance['title_text'] ) ) ? $instance['title_text'] : '';
        $title_image = ( isset( $instance['title_image'] ) ) ? $instance['title_image'] : '';


    ?>

        <div class="titlepage_widget">

            <h3>Title</h3>
            <p>
                <div class="widget_input">
                    <label for="<?php echo $this->get_field_id( 'title_text' ); ?>"><?php _e( 'Text :' ); ?></label>
                    <input class="title_text" id="<?php echo $this->get_field_id( 'title_text' ); ?>" name="<?php echo $this->get_field_name( 'title_text' ); ?>" value="<?php echo $title_text ?>" type="text"><br/>
                </div>
                <div class="widget_input">
                    <label for="<?php echo $this->get_field_id( 'title_image' ); ?>"><?php _e( 'Image :' ); ?></label>
                    <input class="title_image" id="<?php echo $this->get_field_id( 'title_image' ); ?>" name="<?php echo $this->get_field_name( 'title_image' ); ?>" value="<?php echo $title_image ?>" type="text"><button id="title_image_button" class="button" onclick="image_button_click('Choose Title Image','Select Image','image','title_image_preview','<?php echo $this->get_field_id( 'title_image' );  ?>');">Select Image</button>
                    <div class="button remove_image_button" >Remove Image</div>
                </div>
                <div id="title_image_preview" class="preview_placholder">
                <?php
                    if ($title_image!='') echo '<img style="width:100%" src="' . $title_image . '">';
                ?>
                </div>
            </p>

        </div>

        <?php
    }

    /**
     * Sanitize widget form values as they are saved.
     *
     * @see WP_Widget::update()
     *
     * @param array $new_instance Values just sent to be saved.
     * @param array $old_instance Previously saved values from database.
     *
     * @return array Updated safe values to be saved.
     */
    public function update( $new_instance, $old_instance ) {

        $instance = array();
        $instance['title_text'] = ( ! empty( $new_instance['title_text'] ) ) ? strip_tags( $new_instance['title_text'] ) : '';
        $instance['title_image'] = ( ! empty( $new_instance['title_image'] ) ) ? strip_tags( $new_instance['title_image'] ) : '';
        return $instance;
    }

} // class My_Widget

function twp_widget(){
    register_widget('Title_Page_Widget');
}

add_action('widgets_init', 'twp_widget');

JS part:

var custom_uploader;

function image_button_click(dialog_title, button_text, library_type, preview_id, control_id) {

    event.preventDefault();

    //If the uploader object has already been created, reopen the dialog
    //if (custom_uploader) {
     //   custom_uploader.open();
    //    return;
    //}

    //Extend the wp.media object
    custom_uploader = wp.media.frames.file_frame = wp.media({
        title: dialog_title,
        button: {
            text: button_text
        },
        library : { type : library_type },
        multiple: false
    });

    //When a file is selected, grab the URL and set it as the text field's value
    custom_uploader.on('select', function() {

        attachment = custom_uploader.state().get('selection').first().toJSON();
        jQuery('#' + control_id).val(attachment.url);

        var html = '';

        if (library_type=='image') {
            html = '<img src="' + attachment.url + '">';
        }

        if (library_type=='video') {
            html = '<video autoplay loop><source src="' + attachment.url + '" type="video/' + get_extension( attachment.url ) + '" /></video>';
        }

        var uploaded_image = jQuery('.uploaded_image');

        if (uploaded_image.length) {
            uploaded_image.remove();
        }

        jQuery('.title_image').before('<img class="uploaded_image" src="'+attachment.url+'" style="width:100%";>');

        jQuery('#' + preview_id).empty();
        jQuery('#' + preview_id).append(html);
    });

    //Open the uploader dialog
    custom_uploader.open();

}

jQuery(document).on('click','.remove_image_button', function(e){
    e.preventDefault();
    jQuery('.uploaded_image').remove();
    jQuery('#title_image_preview img').remove();
    jQuery('.title_image').val('');
});

function get_extension( url ){

    return url.substr((url.lastIndexOf('.') + 1));

}

Here I've added:

    var uploaded_image = jQuery('.uploaded_image');

    if (uploaded_image.length) {
        uploaded_image.remove();
    }

    jQuery('.title_image').before('<img class="uploaded_image" src="'+attachment.url+'" style="width:100%";>');

and for remove button:

jQuery(document).on('click','.remove_image_button', function(e){
    e.preventDefault();
    jQuery('.uploaded_image').remove();
    jQuery('#title_image_preview img').remove();
    jQuery('.title_image').val('');
});

I've tested it and it works, but as I've said, it needs work. You'll need to see from which widget the click originated, and where to remove the image, and so on. I suspect that if you have multiple of them, once you've click on the remove, you'd remove all the images. As I've said, needs more work.

Hope this gets you in the right direction.

dingo_d
  • 11,160
  • 11
  • 73
  • 132
  • It worked just like I needed it to! Thank you so much, @dingo_d! :) My actual code looks a bit more complicated than the one I posted, but this one was good enough to be used as an example. Could you give me a hand on the last part you mentioned? How can I pass the `$this->get_field_id['title_image']` as a JS variable so I can make use of it in the scripts file? – Rodrigo D'Agostino Mar 06 '16 at 02:53
  • Well just target the DOM element as a JS object like: `var my_element = jQuery('#your_element_id');`. Because you are using the `$this->get_field_id['title_image']` in your input field `id` you can target it easily (If I understood you correctly) and use it. – dingo_d Mar 06 '16 at 06:56
  • The thing is that the assigned ID for that element is dynamic, so I can't target a static element. For example, for `$this->get_field_id['title_image']` I would get something like `widget-title_page_widget-1-title_image`, being `1` the assigned number for a specific widget. This makes multiple widget instances possible. So is there any way I can get that PHP variable transmitted to the JS file? – Rodrigo D'Agostino Mar 06 '16 at 19:44
  • 1
    Hmmm, off the top of my head nothing comes to mind. You could embed the js code inside your php, and there use the same code for the id. This will create a small js code with each widget, but it would work. Write inside the ` – dingo_d Mar 06 '16 at 20:19
  • That worked perfectly, @dingo_d! :) I kept my JS code in the JS file, but I provided a couple of variables directly in the PHP file like you suggested. Thank you so much for your help, mate! :) – Rodrigo D'Agostino Mar 07 '16 at 17:57
  • No problem, glad I could help :) – dingo_d Mar 07 '16 at 21:36
  • I need a little bit more of your help, @dingo_d! Could you tell why there is a `jQuery(document)` right before the `on('click')` method? And why is the trigger class declared inside the method instead of being inside? I tried declaring it before it, like so: `jQuery('.remove_image_button').on('click', function(e){ ... }`, but that just won't work :S – Rodrigo D'Agostino Mar 23 '16 at 20:23
  • 1
    It's just an event delegation. A good explanation of this can be found [here](http://stackoverflow.com/a/14879213/629127). Usually it's better to bind through the closest parent, but I know that if you are adding elements dynamically, you'll need to bind to the document object. So this could be the case... – dingo_d Mar 24 '16 at 06:46
  • Ohhu, I see! I really appreciate that you are so patiently explaining all of these to me :) Ok, so you shed some light over that topic, but there's still something that I can't figure out. I'm trying to use a variable name as the `selector`, like so: `jQuery(document).on('click','#' + mainImageID + '-remove', function(e) { ... }`, but it doesn't seem to work :( That variable is declared outside both functions, being the first the one assigning a value to it. The variable works properly inside the the second function, but it doesn't seem to do so as the selector's name. – Rodrigo D'Agostino Mar 24 '16 at 17:01
  • 1
    My guess is that your variable is [out of scope](http://www.w3schools.com/js/js_scope.asp). You need to be careful to have all your variables in a proper scope, so that you can use them. Be mindful of the fact that by adding widgets, you're adding stuff to the DOM dynamically (widgets are added with AJAX). – dingo_d Mar 24 '16 at 22:03
  • But I declared the variable outside both functions, so according to what I read in the link you shared with me, that variable should be a global one. I don't understand why it doesn't work as a selector. Here's the [code that works](https://jsfiddle.net/8rjw4aL8/), and here's the [code that I would like it to work](https://jsfiddle.net/6roLkwq7/). The only difference is in the line we were talking about. – Rodrigo D'Agostino Mar 24 '16 at 22:53
  • Ok, you were totally right. The variable was out of scope. I declared it directly in the PHP file, and that finally worked just like I need it to :) Thanks a lot again, mate! I wish I could give you more points for all you've done for me today ;) – Rodrigo D'Agostino Mar 25 '16 at 00:35
  • No problem, glad you got everything sorted out :) – dingo_d Mar 25 '16 at 06:39