ThemeFuse / Unyson

A WordPress framework that facilitates the development of WP themes
http://unyson.io
923 stars 218 forks source link

How can I add unyson option to widgets and retrieve their values? #568

Closed danyj closed 7 years ago

danyj commented 9 years ago

Could you please show me best possible way to add any unsyon option to widgets? I would like to add list of custom class names that user can pick to display different widgets style/ It is a simple select with list of class names.

This is what I have in mind but with unyson options http://prntscr.com/76c7vg

Priler commented 8 years ago

@danyj No, in_widget_form it's an action, look in Codex, mate. PolyLang plugin uses this action to output language selection.

https://developer.wordpress.org/reference/hooks/in_widget_form/

danyj commented 8 years ago

I know is an action , I made own plugin before and this worked fine

  add_action( 'in_widget_form', array(&$this, 'thz_aw_in_widget_form' ), 10, 3);

placing all my extra fields at the bottom

danyj commented 8 years ago
_filter_form_callback

is trowing that error for the second attribute ,

danyj commented 8 years ago

@moldcraft you think this is ok ?

add_action( 'in_widget_form', array($this, '_filter_form_callback' ), 10, 3);

public function _filter_form_callback( $instance, $empty, $values ) {

    $options = $this->get_options('test');
    $prefix = $instance->get_field_id('test');

    $this->print_widget_javascript('fw-ext-test-widget-options-'. $prefix);
    echo '<div class="fw-force-xs" id="fw-ext-test-widget-options-'. esc_attr($prefix) .'">';
    echo fw()->backend->render_options($options, $values, array(
        'id_prefix' => $prefix .'-',
        'name_prefix' => $prefix,
    ));
    echo '</div>';

    return $values;
}

that places the options at the bottom , you think there can be some issues with fw options?

Priler commented 8 years ago

I'm trying to make this work right from widgets. But, it disappears after saving.

Code:

<?php if ( ! defined( 'ABSPATH' ) ) { die( 'Tranquility - the highest manifestation of power!' ); }

class Fw_Theme_Widget_Friends extends WP_Widget {

  /**
   * Widget constructor.
   */
  function __construct() {

    $widget_ops = array( 'description' => __( 'Links list of your friendly websites.', 'unyson' ) );
    parent::__construct( false, __( 'Kronos / Our Friends', 'unyson' ), $widget_ops );
  }

  function widget( $args, $instance ) {
    extract( $args );
    $params = array();

    foreach ( $instance as $key => $value ) {
      $params[ $key ] = $value;
    }

    $title = $before_title . $params['widget-title'] . $after_title;
    unset( $params['widget-title'] );

    $filepath = dirname( __FILE__ ) . '/views/widget.php';

    $instance      = $params;
    $before_widget = str_replace( 'class="', 'class="tag-cloud-sidebar ', $before_widget );

    if ( file_exists( $filepath ) ) {
      include( $filepath );
    }
  }

  function update( $new_instance, $old_instance ) {
    $options = array(
        'demo1' => array(
            'type'  => 'text',
            'value' => 'default value'
        ),
        'demo2' => array(
            'type'=>'select',
            'choices' => array(
                'tiger' => 'Tiger',
                'puma' => 'Puma',
                'lynx' => 'Lynx',
                'pantera' => 'Pantera'
            )
        )
    );
    $prefix = 'our-friends';

    return array_merge(
        $values,
        fw_get_options_values_from_input($options, FW_Request::POST($prefix, array()))
    );
  }

  function form( $values ) {
    $options = array(
        'demo1' => array(
            'type'  => 'text',
            'value' => 'default value'
        ),
        'demo2' => array(
            'type'=>'select',
            'choices' => array(
                'tiger' => 'Tiger',
                'puma' => 'Puma',
                'lynx' => 'Lynx',
                'pantera' => 'Pantera'
            )
        )
    );

    $prefix = 'our-friends';

    $this->print_widget_javascript('fw-ext-test-widget-options-'. esc_attr($prefix));
    echo '<div class="fw-force-xs" id="fw-ext-test-widget-options-'. esc_attr($prefix) .'">';
    echo fw()->backend->render_options($options, $values, array(
        'id_prefix' => $prefix .'-',
        'name_prefix' => $prefix,
    ));
    echo '</div>';

    return $values;
  }

    private function print_widget_javascript($id) {
        ?><script type="text/javascript">
            jQuery(function($) {
                var selector = '#<?php echo esc_js($id) ?>', timeoutId;

                $(selector).on('remove', function(){ // ReInit options on html replace (on widget Save)
                    clearTimeout(timeoutId);
                    timeoutId = setTimeout(function(){ // wait a few milliseconds for html replace to finish
                        fwEvents.trigger('fw:options:init', { $elements: $(selector) });
                    }, 100);
                });
            });
        </script><?php
    }
}
ghost commented 8 years ago

Maybe because id="fw-ext-test-widget-options-'. esc_attr($prefix) .'" is exactly the same as id="..." in test extension?

Priler commented 8 years ago

@moldcraft Nope, my $prefix is different. But, anyway, I've already done my own solution, that adds my own fields to add them to Widgets, and even repeatable fields is supported.

Unyson can't provide normal way to layout widget settings this time, maybe in further it will be updated & official support for this will be added.

Priler commented 8 years ago

Nope, my $prefix is different. Anyway, I've done my own solution, which is quite ease to use and even supports repeatable fields.

With my solution, widget class code looks like this: http://pastebin.com/Yuj2VtcQ

ghost commented 8 years ago

https://github.com/ThemeFuse/Unyson/issues/568#issuecomment-193798651

The actions are close, it should be ok.

dinhtungdu commented 8 years ago

I've created successfully Widget with Unyson options from widget class without extension. Add options right from widget, work with all option types.

Only issue I can't resolve right now is: with some options which have javascript, when I click save button, I must reload to use them full functionally. Eg, addable option, when save button is clicked, I can't sort the item; after reloading, everything is okay.

There are some options work out of the box, such as select-multiple

@moldcraft, if you're interested in this, I can send you the code, right now I don't have enough time to resolve javascript issue. I think if the javascript problem is resolve, we can add a docs section for widget, creating widget with Unyson options is fast as light. :)

ghost commented 8 years ago

@dinhtungdu Have you tried the above @danyj solution?

dinhtungdu commented 8 years ago

@moldcraft: I tried. But @danyj solution uses action. The approach I'm using is inspired by Priler , I add options right from Widget class, no extension, no action or filter. Because I think add action from widget is more elegant and more suitable. Here is an example before saving http://prntscr.com/b3583n After saving: http://prntscr.com/b358ni

ghost commented 8 years ago

Give me your code and I will inspect it.

dinhtungdu commented 8 years ago

@moldcraft Here we go: Widget with Unyson options

ghost commented 8 years ago
/* This CSS need to force display option after saving */
.fw-theme-admin-widget-wrap .fw-backend-option, .fw-theme-admin-widget-wrap .fw-postbox {
    opacity: 1;
}

This css means that you are doing things wrong, option is not displayed because it is not initialized from javascript.


Your code is not using WP_Widget::get_field_name(...); and WP_Widget::get_field_id(...); which generates a unique/incremented name and id, to prevent multiple elements in html with same id/name.

Also you don't initialize the options in javascript (like in the above examples. have you read them?) with $this->print_widget_javascript($id);

Before and After

<?php if ( ! defined( 'ABSPATH' ) ) { die( 'Tranquility - the highest manifestation of power!' ); }

class Widget_Online_Support extends WP_Widget {

    /**
     * Widget constructor.
     */
    private $options;
    private $prefix;
    function __construct() {

        $widget_ops = array( 'description' => __( 'Display online support infomation', 'unyson' ) );
        parent::__construct( false, __( 'Online Support', 'unyson' ), $widget_ops );
        $this->options = array(
            'title' => array(
                'type' => 'text',
                'label' => __('Widget Title', 'unyson'),
            ),
            'block' => array(
                'type'  => 'addable-box',
                'label' => __('Apartment', 'unyson'),
                'box-options' => array(
                    'skype' => array( 'type' => 'text', 'label' => __('Skype', 'unyson'), ),
                    'tel' => array( 'type' => 'text', 'label' => __('Telephone', 'unyson'), ),
                    'desc' => array( 'type' => 'text', 'label' => __('Aparment name', 'unyson'), ),
                ),
                'box-controls' => array( // buttons next to (x) remove box button
                    'control-id' => '<small class="dashicons dashicons-smiley"></small>',
                ),
                'limit' => 0, // limit the number of boxes that can be added
                'add-button-text' => __('Add New', 'unyson'),
                'sortable' => true,
            ),

        );
        $this->prefix = 'online_support';
    }

    function widget( $args, $instance ) {
        extract( $args );
        $params = array();

        foreach ( $instance as $key => $value ) {
            $params[ $key ] = $value;
        }

        $filepath = dirname( __FILE__ ) . '/views/widget.php';

        $instance = $params;

        if ( file_exists( $filepath ) ) {
            include( $filepath );
        }
    }

    function update( $new_instance, $old_instance ) {
        return fw_get_options_values_from_input(
            $this->options,
            FW_Request::POST(fw_html_attr_name_to_array_multi_key($this->get_field_name($this->prefix)), array())
        );
    }

    function form( $values ) {

        $prefix = $this->get_field_id($this->prefix);
        $id = 'fw-widget-options-'. $prefix;

        echo '<div class="fw-force-xs fw-theme-admin-widget-wrap" id="'. esc_attr($id) .'">';
        $this->print_widget_javascript($id);
        echo fw()->backend->render_options($this->options, $values, array(
            'id_prefix' => $prefix .'-',
            'name_prefix' => $this->get_field_name($this->prefix),
        ));
        echo '</div>';

        return $values;
    }

    private function print_widget_javascript($id) {
        ?><script type="text/javascript">
            jQuery(function($) {
                var selector = '#<?php echo esc_js($id) ?>', timeoutId;

                $(selector).on('remove', function(){ // ReInit options on html replace (on widget Save)
                    clearTimeout(timeoutId);
                    timeoutId = setTimeout(function(){ // wait a few milliseconds for html replace to finish
                        fwEvents.trigger('fw:options:init', { $elements: $(selector) });
                    }, 100);
                });
            });
        </script><?php
    }

}
dinhtungdu commented 8 years ago

@moldcraft You're my hero! Worked perfectly! Next time, I will check my code more carefully before submit here. Thanks for pointing my mistakes!

pear1 commented 8 years ago

Hi there,

Just tested @moldcraft script and everything works very well except JavaScript is no initialized when widget is added and page is not reloaded. When I reload page, there are no issues and everything works.

I can't see any JavaScript or PHP errors/warnings.

EDIT: When pressing Save button twice it starts working (after first click options simply disappears, but after second click it loads again and JS starts working)

dinhtungdu commented 8 years ago

@pear1 In my updated version, when user add new widget, he just needs one click on Save button. This causes by the not matching id in javascript of that widget when it's added first time. I'm trying to make it work at the first time but at the moment no luck.

pear1 commented 8 years ago

@dinhtungdu At the moment only solution I can think of would be to trigger Save button click twice by JS, immediately after widget is added. Then content is fully reloaded and JS starts working. That could be a pretty bad solution, but at least everything would work as it should.

noesteijver commented 8 years ago

Does this have anything to do with the following problem?

http://blog.codebusters.pl/en/click-doesn-t-work-after-ajax-load-jquery/

danyj commented 8 years ago

@dinhtungdu I tested your latest gist just now and seems like is not working anymore ,

@moldcraft what is the 100% working code now since we mixed up extension that adds option and widgets with options

danyj commented 8 years ago

@moldcraft , @dinhtungdu , @Priler hm , we saved everything and all nice and sweet , but does any of you know how to retrieve the saved values per widget?

ghost commented 8 years ago

what is the 100% working code now since we mixed up extension that adds option and widgets with options

I will inspect this issue later.

we saved everything and all nice and sweet , but does any of you know how to retrieve the saved values per widget?

They should be somewhere in wp_options

Usually a widget is like a shortcode, the $atts are available only on render, in widget case it is in widget() method https://codex.wordpress.org/Widgets_API

toleabivol commented 7 years ago

This fixes the disappearing options in @dinhtungdu example.

jQuery(function($) {
    var timeoutId;
    $(document).on('widget-updated widget-added', function(){
        clearTimeout(timeoutId);
        timeoutId = setTimeout(function(){ // wait a few milliseconds for html replace to finish
            fwEvents.trigger('fw:options:init', { $elements: $('#widgets-right .fw-theme-admin-widget-wrap') });
                    }, 100);
        });
    });

Note: you should put this in a script file and load it once. Still it's not a clean script but it does it's job and no visual side effects for user. Waiting for an official fix though.

danyj commented 7 years ago

@moldcraft , I tried something else and seems to work , but need small help ,

most of us would have bunch of shortcodes that we want to have as widgets also , so instead of copy the code I did this

function __construct() {

    $widget_ops     = array( 'description' => __( 'Display online support infomation', 'unyson' ) );
    parent::__construct( false, __( 'Tabs', 'unyson' ), $widget_ops );

    $sh_options   = fw_get_variables_from_file(get_template_directory() .$this->_sh_path('tabs').'/options.php', array('options' => null));
    $sh_options   = $sh_options['options'];

    $this->options = array(

        'widget_options' => array(
            'type' => 'popup',
            'options' =>  $sh_options,
            'title' => __('Widget Options', '{domain}'),
            'modal-size' => 'large',
            'desc' => __('Customize Widget Options', '{domain}'),
        ),          

    );

    $this->prefix = 'online_support';
}

function _sh_path($shortcode){

    return '/inc/thzframework/extensions/shortcodes/shortcodes/'.$shortcode;
}   

function widget( $args, $instance ) {
    extract( $args );
    $params = array();

    foreach ( $instance as $key => $value ) {
        $params[ $key ] = $value;
    }

    $filepath = get_template_directory() .$this->_sh_path('tabs').'/views/view.php';

    $instance = $params;

    if ( file_exists( $filepath ) ) {
        include( $filepath );
    }
}

and in tabs shortcode just on top of view.php

if(isset($instance)){
    $atts = $instance;
}

now I have full tabs output in front and options in widget ,

but 2 things are bugging

  1. missing the static.php from shortcode in frontent with dynamic css
  2. the popup container hides tabs after the widget has been saved the first time

http://prntscr.com/efbmih

it would be great if you could help.

danyj commented 7 years ago

as of popup container , I can move that to popup option type and do this in view

if(isset($instance)){
    $atts = $instance['widget_options'];
}

but still need the static

toleabivol commented 7 years ago

This is my final solution to the IN WIDGET options using @moldcraft example above , add in a single enqueued to widgets page script :

/* 
    Enable FW options in widgets on add (without refresh)
    By simulating a save click after add and then initialize them with fw:options:init
 */
jQuery(function($) {
    let timeoutAddId;
    $(document).on('widget-added', function(ev, $widget){
        clearTimeout(timeoutAddId);
        timeoutAddId = setTimeout(function(){ // wait a few milliseconds for html replace to finish
            $widget.find('form input[type="submit"]').click();
        }, 300);
    });

    let timeoutUpdateId;
    $(document).on('widget-updated', function(ev, $widget){
        clearTimeout(timeoutUpdateId);
        timeoutUpdateId = setTimeout(function(){ // wait a few milliseconds for html replace to finish
            fwEvents.trigger('fw:options:init', { $elements: $widget });
        }, 100);
    });
});

You get the$widget object as the second parameter for events widget-added and widget-updated so no need to search for that.

mhsohag11 commented 7 years ago

Thanks @moldcraft , But i face another problem. When i add "icon-v2" field in widget area, in popup option no field are shown !!! How can i solve it ???

saddamwp commented 7 years ago

Hello, mhsohag11, you are a genius person. now, you are working for GitHub unison group. I didn't understand why you do not solve this. I hope you can try, you will be solved this problem.because you are a genius person.

yura-x commented 6 years ago

Be careful if you are using 'wp-editor' option type in your widget since WordPress version 4.9.6. You'll see error in your console. More info on WordPress trac It can be fixed in includes/option-types/wp-editor/static/scripts.js by wrapping code tinymce.ui.FloatPanel.zIndex = 100100; on line 148 in if statement like so: if ( tinymce.ui.FloatPanel !== undefined ) { tinymce.ui.FloatPanel.zIndex = 100100; } Please note that you'll loose this changes after Unyson plugin update.