Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: jquery

  • Sharing Content with Static Sites Dynamically

    Sharing Content with Static Sites Dynamically

    When I wrote how to serve content to Hugo, I did so using something that was mostly static. You see, that code requires someone to push a new version of the Hugo site to rebuild the pages.

    Now let’s be serious, who wants to do that?

    The Concept

    Sadly, you can’t just include a PHP file in Hugo (or any static site builder) and have it echo content. Their whole point is to be static and not change. And my problem is that I immediately ran into a week where I knew the message on the header was going to be changing daily.

    Ew, right? Right. So I looked at that which I should be embracing deeply. Javascript. Or in this case, jQuery and the getJSON call. Yes, that’s right, with jQuery you can call JSON and output it where you want.

    I do not recommend doing this for full page content. This is only vaguely smart if you’re trying to output something small that loads fast and isn’t going to mess up your site if someone has javascript disabled.

    The Code

    <script>
    	$.getJSON( "https://example.com/wp-json/wp/v2/pages/14363", function (json) {
    	    var content = json.content.rendered;
    		document.querySelector('.wpcontent').innerHTML = content;
    	});
    </script>
    
    <div class="utility-bar">
    	<div class="wrap">
    		<section id="text-16" class="widget widget_text">
    			<div class="widget-wrap">
    				<div class="textwidget">
    					<div class="wpcontent"></div>
    				</div>
    			</div>
    		</section>
    	</div>
    </div>
    

    What that code does is it grabs the JSON, sets the variable content to the value of the content’s ‘rendered’ setting. Then using document.querySelector, it tosses in the HTML to my class for wpcontent and I’m done.

  • CMB2, Select2, and Taxonomies

    CMB2, Select2, and Taxonomies

    I’m going to start with “This is not my best work.”

    In using CMB2, I have created situations where it’s smarter to have the ‘normal’ WordPress taxonomy fields changed. Oh sure, they work most of the time for most things, but most is not all. In my situation, I had some custom taxonomies that I did not want people adding to from the post-edit screen.

    To get around the issue, most of the taxonomies were drop-downs using the taxonomy_select field type. That let me control the display and have the drop-down be the terms they could add. Anyone with admin access could add more, of course, but they’d see special notes about that. It gave me control.

    The problem really arose when I had a multicheck list of terms to add. Yes, I had 1 to 20 terms that might be added. And while I could use taxonomy_multicheck to do that, it wasn’t perfect. It made the screen very large.

    Select2 is Better

    Select2 is a jQuery replacement for select boxes. Using it, you can make a simple dropdown where you can have a single (or even multiple) selections, but also it has a nice interface for multiple selections:

    Select2 example: a single and a multicheck

    That looks much nicer than a list or grid of 20 options. You click on the box and you get a dropdown:

    Select2 - Showing the dropdown

    Select2 and CMB2

    Thankfully there’s already a plugin/add-on for this with CMB2. Phil Wylie made cmb-field-select2 which I pulled into my site and it works quite well. Except… You can’t use it to save Taxonomy data properly!

    This is due to a lot of complicated things, and while my first instinct was to complain to myself that core CMB2 could do it, and thus so could everyone, I know it’s not that simple. All the effort CMB2 put into making that work is little short of phenomenal. It was hard and it’s complex and it’s outright weird. I looked at the code and backed away slowly.

    But that doesn’t mean it’s impossible. It’s just a little weird and it’s not my best work. But it does work.

    Show the Taxonomies

    The first step is that you have to make a function to convert the taxonomy to something that can be used in a selection box. Thankfully Phil already did this and his example code works:

    /**
     * Get a list of terms
     *
     * Generic function to return an array of taxonomy terms formatted for CMB2.
     * Simply pass in your get_terms arguments and get back a beautifully formatted
     * CMB2 options array.
     *
     * @param string|array $taxonomies Taxonomy name or list of Taxonomy names
     * @param  array|string $query_args Optional. Array or string of arguments to get terms
     * @return array CMB2 options array
     */
    function iweb_get_cmb_options_array_tax( $taxonomies, $query_args = '' ) {
    	$defaults = array(
    		'hide_empty' => false
    	);
    	$args = wp_parse_args( $query_args, $defaults );
    	$terms = get_terms( $taxonomies, $args );
    	$terms_array = array();
    	if ( ! empty( $terms ) ) {
    		foreach ( $terms as $term ) {
    			$terms_array[$term->term_id] = $term->name;
    		}
    	}
    	return $terms_array;
    }
    

    Next you call that in your CMB2 code:

    // Field: Genre
    $field_genre = $cmb_notes->add_field( array(
    	'name'              => 'Genre',
    	'desc'              => 'Subject matter.',
    	'id'                => 'theshows_genre',
    	'taxonomy'          => 'my_genres',
    	'type'              => 'pw_multiselect',
    	'select_all_button' => false,
    	'remove_default'    => 'true',
    	'options'           => iweb_get_cmb_options_array_tax( 'my_genres' ),
    	'attributes'        => array(
    		'placeholder' => 'What kind of show...'
    ),
    

    And now you can add taxonomy items via Select2. But … It doesn’t save the taxonomy data.

    Saving The Taxonomy Data

    This is the part of code I’m not thrilled about. You see, the code in the previous section adds a new postmeta field for theshows_genre with an array of the IDs added. And that’s it. That isn’t what I wanted. I certainly could use the postmeta data to generate the output, but I used Taxonomies for a reason. They’re incredibly useful.

    In order to save the data, I needed to take the content from the post meta and copy it into the values for saved taxonomies, but only sometimes. After kicking around the options, I decided that I would give priority to the postmeta, not the taxonomies. That would allow me to have them save the taxonomies all the time unless the post meta was empty.

    function select2_taxonomy_process( $post_id, $postmeta, $taxonomy ) {
    
    	$get_post_meta = get_post_meta( $post_id, $postmeta, true );
    	$get_the_terms = get_the_terms( $post_id, $taxonomy );
    
    	if ( is_array( $get_post_meta ) ) {
    		// If we already have the post meta, then we should set the terms
    		$get_post_meta   = array_map( 'intval', $get_post_meta );
    		$get_post_meta   = array_unique( $get_post_meta );
    		$set_the_terms = array();
    
    		foreach( $get_post_meta as $term_id ) {
    			$term = get_term_by( 'id' , $term_id, $taxonomy );
    			array_push( $set_the_terms, $term->slug );
    		}
    
    		wp_set_object_terms( $post_id, $set_the_terms , $taxonomy );
    
    	} elseif ( $get_the_terms && ! is_wp_error( $get_the_terms ) ) {
    		// If there's no post meta, we force the terms to be the default
    		$get_post_meta = array();
    		foreach( $get_the_terms as $term ) {
    			$term_id = $term->term_id;
    			array_push( $get_post_meta, $term_id );
    		}
    		update_post_meta( $post_id, $postmeta, $get_post_meta );
    	}
    
    }
    

    This is not perfect code. It’s not even very good code, I don’t think. I’m not happy that I had to break the terms out instead of just using the the content from $get_post_meta but for some reason, wp_set_object_terms() wasn’t happy with an array of terms. It was fine with the slugs, so that’s the way I went.

    The logic is basic. If there’s postmeta and it’s an array, it ‘wins.’ If it’s not, take the taxonomy data and push it into the term.

    Triggering The Save

    But how to trigger that code? And where and when?

    I wrote code for my custom post type that triggered a check every time the page was loaded. Which is why I don’t like it.

    add_action( 'init', 'select2_taxonomy_save' );
    function select2_taxonomy_save() {
    	// Force saving data to convert select2 saved data to a taxonomy
    	$post_id   = ( isset( $_GET['post'] ) )? $_GET['post'] : 0 ;
    	
    	if ( $post_id !== 0 && is_admin() ) {
    		$post_type = ( isset( $_GET['post_type'] ) )? $_GET['post_type'] : 0 ;
    		switch ( $post_type ) {
    			case 'post_type_shows':
    				LP_CMB2_Addons::select2_taxonomy_save( $post_id, 'theshows_tropes', 'my_tropes' );
    				LP_CMB2_Addons::select2_taxonomy_save( $post_id, 'theshows_tvgenre', 'my_genres' );	
    				break;
    		}
    	}
    }
    

    Obviously it’s not the best code out there. It runs too often, though at least it’s only on page loads.. It would be better if it only ran on save, however that had a problem with race conditions. I would end up with a case where the postmeta might still be blank. So having it run before the page loaded appeared to be my only hope. I also don’t like having the data stored twice, but there was a limit to how far I wanted to run with this.

    Pull requests welcome!

  • Datepicker and a Widget

    Datepicker and a Widget

    Last week, I worked on making a plugin that would safely and smartly allow for a date selection, and not permit people to put in junk data. I had my code ‘clean’ and only accepted good data, there was one question remaining. How do I make it look GOOD?

    Let’s be honest, pretty data matters. If things look good, people use them. It’s that simple. This let me play with another aspect of coding that I don’t generally look at. Javascript.

    What Code Do I Need?

    There are a lot of ways to tackle this kind of problem. If you wanted to just list the months and days, and have those be drop-downs, you could do that. The option I went with was to have a calendar date-picker pop up, as I felt that would visually explain what the data should be.

    To do that I needed a date picker jQuery script (which is included in WordPress core) and a second script to format the output.

    My Script

    This part is really small:

    jQuery(function() {
        jQuery( ".datepicker" ).datepicker({
            dateFormat : "mm-dd"
        });
    });
    

    All it does is force the format to be “mm-dd” – so if you picked the date, that’s what it would be.

    Enqueuing the Scripts

    In order to make sure the scripts are only loaded on the widgets page, my enqueue function looks like this:

    	public function admin_enqueue_scripts($hook) {
    		if( $hook !== 'widgets.php' ) return;
    		wp_enqueue_script( 'byq-onthisday', plugins_url( 'js/otd-datepicker.js', __FILE__ ), array( 'jquery-ui-datepicker' ), $this->version, true );
    		wp_enqueue_style( 'jquery-ui', plugins_url( 'css/jquery-ui.css', __FILE__ ), array(), $this->version );
    	}
    

    The CSS is because, by default, WordPress doesn’t include the jquery UI CSS.

    Calling the Scripts

    In the widget class, I have a function for the form output. In there, I have an input field with a class defined as datepicker, which is used by the jquery I wrote above, to know “I’m the one for you!”

    	function form( $instance ) {
    		$instance = wp_parse_args( (array) $instance, $this->defaults );
    		?>
    		<p>
    			<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php _e( 'Title', 'bury-your-queers' ); ?>: </label>
    			<input type="text" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" value="<?php echo esc_attr( $instance['title'] ); ?>" class="widefat" />
    		</p>
    
    		<p>
    			<label for="<?php echo esc_attr( $this->get_field_id( 'date' ) ); ?>"><?php _e( 'Date (Optional)', 'bury-your-queers' ); ?>: </label>
    			<input type="text" id="<?php echo esc_attr( $this->get_field_id( 'date' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'date' ) ); ?>" class="datepicker" value="<?php echo esc_attr( $instance['date'] ); ?>" class="widefat" />
    			<br><em><?php _e( 'If blank, the date will be the current day.', 'bury-your-queers' ); ?></em>
    		</p>
    		<?php
    	}
    

    Making it Pretty

    To be honest, once I got the JS working, I left the default CSS alone. Why? Because I’m a monkey with a crayon when it comes to design. The default worked fine for me:

    The Default Date Picker

    It does make me think that it would be nice if WordPress included their own customize datepicker colors in the admin colors, but I understand why they don’t. Not everyone or even most people will ever need this.