Tesla Slacker Radio Is Half-Baked

As a tech geek I’m interested in the tech in my Model S.   I also am frustrated with the horribly outdated browser and the half-ass integration of my Premium Slacker Radio account with the in-dash Slacker interface.  I love both products but Slacker has been doing half-baked secondary features for years now.  Tesla got the media interface “good enough for most people”.  In the end Tesla Slacker Radio suffers.

I’m not “most people” especially when it comes to my streaming audio. I want more.  I’d like to see all my custom stations when I login to my Premium Slacker account in my Tesla.    If Slacker didn’t halfway drop the Favorites feature, the icon now appears on the IOS app but does nothing I could use that.   According to Slacker the “Favorites” feature to mark any station as a favorite is being dropped.  Tesla disagrees as it is a primary feature when bringing up Slacker stations on their interface.

I can create a custom Slacker Station and get it on my Sonos, through Alexa, and on my mobile apps.  I CANNOT get it in my Tesla.    I can mark Slacker stations as favorite in my Tesla, and they can be set to be “front and center” on the media app, but I cannot get to them anywhere else.    In short custom stations are completely off the radar when it comes to accessing them from the Model S.
Read More

Uncovering A WordPress Walker Class Inconsistency

Took an hour tonight to figure out why the new Store Locator Plus Premier Button Bar is not working properly.   The odd thing is this WAS working properly two weeks ago but both our own code for the base plugin and Premier add on changed as well as a new WordPress release.    Maybe something changed in WP Core to trigger the problem in our add on or maybe something we fixed elsewhere in the code exposed this weak spot.

The real issue here is undocumented parameters in a documented WP Core function and the use of a non-standard call to PHP’s func_get_args() to bypass a defined PHP class method’s parameters.   That’s not nice but as someone maintaining a legacy PHP app with tens-of-thousands of user, I get it.

Here is what I found , why it is “not nice” and how I fixed it.   Maybe it will help other plugin/theme devs with their custom Walker classes.
Read More

Adding Columns To WordPress Screen Options

Adding the Screen Options drop down to a custom WordPress admin page takes some trickery which was described in the prior Screen Options blog post.  Using the “magic sauce” of the per_page option and adding it to the class that renders your admin page and handles page interaction is an easy way to get that started.    Assuming you already have the Screen Options rendering with something on there, probably Pagination settings, you can now add a column selector.   This is especially useful for admin pages built on top of the WordPress WP_List_Table (for private use only but everyone-and-their-brother is using it).

Getting The List Of Columns

The easiest way to get the list of columns to appear is to add a manage_{$screen-id}_columns filter to your app.   In the Store Locator Plus location manager I am setting this up in the class constructor as the WP_Screen interface likes to know about the columns very early in the process; well before the page rendering happens.

First we need to get our current screen.  This makes it easy to ensure your screen->id needed for the filter is an exact match for what the filter trigger will be looking for.  This is managed in the get_wp_screen() method in my example.

We then need to tell WordPress to run our manage_{$screen-id}_columns code, which is the private manage_columns() method in this example.   It accepts a list of already-in-play column slugs and titles, usually empty, and should send back a named array with the key set to the slug and the value set to the title.

Since our class is a WP_List_Table derivative and we already wired up the screen, the screen_options() WordPress function will now see our list of column names as stored in the static variable managed in the WordPress Core screen.php function get_column_headers.

The Code

<?php
defined( 'ABSPATH'     ) || exit;
if ( ! class_exists( 'SLP_Admin_Locations' ) ) {
    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );

    class SLP_Admin_Locations extends WP_List_Table {
        private $wp_screen;

        /**
         * Called when this object is created.
         */
        function __construct( $args = array() ) {
            $screen = $this->get_wp_screen();
            add_filter( 'manage_' . $screen->id . '_columns' , array( $this , 'manage_columns') );
        }

       /**
        * Set the columns we will render on the manage locations page.
        */
       public function get_columns() {
          $this->columns = array(
             'sl_id'               => __( 'Actions', 'store-locator-le' ),
             'sl_store'            => __( 'Name', 'store-locator-le' ),
             'sl_address'          => __( 'Address', 'store-locator-le' ),
             'sl_address2'         => __( 'Address 2', 'store-locator-le' ),
             'sl_city'             => __( 'City', 'store-locator-le' ),
             'sl_state'            => __( 'State', 'store-locator-le' ),
             'sl_zip'              => __( 'Zip', 'store-locator-le' ),
             'sl_country'          => __( 'Country', 'store-locator-le' ),
             'sl_initial_distance' => __( 'Distance', 'store-locator-le' ),
             'sl_description'      => __( 'Description', 'store-locator-le' ),
             'sl_email'            => $this->slplus->WPML->get_text( 'label_email' ),
             'sl_url'              => $this->slplus->WPML->get_text( 'label_website' ),
             'sl_hours'            => $this->slplus->WPML->get_text( 'label_hours' ),
             'sl_phone'            => $this->slplus->WPML->get_text( 'label_phone' ),
             'sl_fax'              => $this->slplus->WPML->get_text( 'label_fax' ),
             'sl_image'            => $this->slplus->WPML->get_text( 'label_image' ),
          );

          return $this->columns;
       }

       /**
        * Get a list of all, hidden and sortable columns, with filter applied
        *
        * @return array
        */
       public function get_column_info() {
          if ( ! isset( $this->_column_headers ) ) {
             $this->_column_headers = array(
                $this->get_columns(),
                array(),
                array(),
                'sl_id',
             );
          }

          return $this->_column_headers;
       }

       /**
        * Get the wp_screen property.
        */
       private function get_wp_screen() {
          if ( empty( $this->wp_screen ) ) {
             $this->wp_screen = get_current_screen();
          }
          return $this->wp_screen;
       }

       /**
        * Set up our screen columns.
        *
        * Impacts screen options column list.
        *
        * @param   array   columns     the existing columns
        * @return  array               key = field slug, value = title
        */
       public function manage_columns( $columns ) {
          $this->get_column_info();
          return $this->_column_headers[0];
       }
    }
}

The User Experience

If everything is wired up properly you should see the screen options drop down with a new Columns header and list of check boxes that  let the user choose which columns to show on the interface.   Here is an example from the Manage Locations interface of Store Locator Plus when we have the Experience and Power add ons active.

Column Selector 2017-06-16_10-50-08.png

Wiring WordPress Media Uploader Into Your Web App

As predicted, the “premium” theme I purchased to make things happen on the HereMusic.Live site needs more tweaking.  Today I am diving into the proper way to wire the WordPress media uploader into a plugin after learning the theme not only cannot handle a media file with parenthesis in the name but it also is dropping the media title into the URL field on the form.  Ugh.

Most of the magic for the WordPress Media interface is managed via JavaScript.    I’m going to focus on the JavaScript portion of the code, assuming an app already has a basic form on an admin page that needs media uploaded and gets the URL put into a form element.   I’m also going to assume you know how to attach basic “on click do this” to an HTML element using jQuery or inline onClick methods.

Rip Out Thickbox

The old-school method of attaching to the WordPress media uploader was to tie into the Thickbox-driven media interface.   If you look into an existing WordPress plugin or theme and see the JavaScript littered with tb_show() and window.send_to_editor references there is a very good chance it is using the outdated Thickbox model.

I’m not sure where this model is breaking down on my WordPress 4.7 site but somehow the html parameter on the send_to_editor() call is only receiving a string containing the file title.

Time to rip it out and replace it with the thinner and generally better-designed wp.media model.  It was introduced eons ago (in modern web app terms) and should be the go-to method for implementing this feature.

Insert wp.media

As with many of the “black magic” features of WordPress, wp.media is not well documented on the new developer guides and function references.   Most of the knowledge we’ll need has to come from the “old” Codex wp.media pages.

The basic concept is simple:  When the user clicks the add button create a new wp.media JavaScript object and open it.

wp.media Details

We start by defining a media_frame variable in JavaScript at a global scope so we can make it a singleton; in essence don’t fire up a new popup wp.media form every time someone clicks the button.

Next we attach an anonymous function to the click event on the button that will do some general maintenance, set some properties for the wp.media object, and tell that object what we want to do when the user selects a file.   Remember that whether the user has just uploaded a new file or picked from an existing file the application considers this a ‘select’ action in either case.    The last step of this click function is to open the wp.media object which fires up the standard WordPress upload/select window.

Example Details

The initial implementation:

Always preventDefault() on these actions to prevent conflicts with other things that may be attached to the click event stack in  your web app or browser.

Check if the media_frame global variable is active.  If it is then our media form is open somewhere.   Re-open it (show it) and leave.  This cuts down on the overhead of re-creating the window object every time we click the button.

Create the wp.media object and attach it to the media_frame variable.  This is a single instance of the media selector.  Pass in some attributes for the window title, button label, and multiple selection options.   Later we’ll use localize_script() in the PHP side of the code so we have an app that is gettext() , and thus i18n/l10n, compatible.

Before we get into the frame  processing ,  grab a sibling field on the HTML form that contains the add button we clicked to fire up the form.  My example HTML form has an input text field with the class mea_box_upload_file where we put the URL for the file we are selecting.

The select is processed with an .on( ‘select’ … ) trigger.    When the user clicks the “Use This” button (our label for the select button) it will fire the anonymous function defined here.   The details of the file they selected is retrieve in the media_frame.state().get(‘selection’).first().toJSON() call.    I then use the variable I set earlier that points to my URL field on my HTML form and set the value to the file’s url property.

With all the triggers and properties set for the media window I can now open the media selector as the last step of the button click process.

 

Follow Up Feb 1 2017

Additional notes and a complete code example.

First – wp.media has a frames property where you can (should?) attach your active frame instead of managing it “by hand” within your JavaScript.

Second – return false from the function that you invoke the media from.   Depending on where you call your modal open() you may get the entire stack of media windows opening.  In my plugin I was getting my custom modal plus a default modal provided by WordPress on an admin setting page.

Third – I’ve refactored the default code you see on nearly EVERY example of doing the frame open where it first checks to see if the frame exists, if it does return frame.open() and if it does not do the setup then return frame.open().   Nearly every time you call the same method twice within a few lines of code it can be refactored to be more efficient.  That is included in my example.

New Example

This is from my Store Locator Plus code.    I use a block scope variable that I change every time a user clicks a button so I know which setting to update with the image URL the user selected.    This allows the on(‘select’) code for the window frame itself to remain static.  Better caching and code compression can happen with this setup.

The class the creates the HTML code.  Obviously a partial example as this web app uses all kinds of objects and inheritance to manage oft-repeated code segments.

 

JavaScript: wp.media

The Codex says… JavaScript Reference wp.media.

wp_enqueue_media()

This magic WordPress function will enqueue all of the JavaScript elements needed to work with the media uploader provided by WordPress core.

Needs to be fired in/after admin_enqueue_scripts in the call stack.

The Codex says… wp_enqueue_media() 

The Code Reference says… wp_enqueue_media().

Hacking WordPress – Articles About Coding Plugins and Themes

Recently I’ve begun building a music directory site.    I purchased a premium theme and the accompanying premium plugins.    Sadly, premium does not necessarily mean well-written.   Turns out this theme and the associate plugins do a few good things, look great on the surface and do a lot of really horrid things in the background.   If I were launching a personal site, like this bio blog, I’d not worry about it.   However the long term goal is to build a music directory that handles hundreds-of-thousands of page loads every month.   It needs to be fast without having to run it on IBM’s Watson to do so.

I’ve decided to fork the theme and the plugins and start hacking them into shape.    Coming soon will be the first in a series of “hacking WordPress” articles.   The first article will be about how to now load your plugin code on every single admin page on the site.

If you are writing WordPress Themes or plugins and have a question or an idea for a post, please let me know.   If I can’t answer the question myself I will try to find a guru that can.

 

%d bloggers like this: