Bedingtes Laden von JavaScript / CSS für Shortcodes

37

Ich habe ein Plugin veröffentlicht, das einen Shortcode erstellt und eine JavaScript-Datei und eine CSS-Datei benötigt, um auf jede Seite geladen zu werden, die diesen Shortcode enthält. Ich könnte das Skript / den Stil einfach auf alle Seiten laden lassen, aber das ist nicht die beste Vorgehensweise. Ich möchte nur die Dateien auf Seiten laden, die den Shortcode aufrufen. Ich habe zwei Methoden gefunden, aber beide haben Probleme.

Methode 1 setzt ein Flag in der Shortcode-Handler-Funktion auf true und überprüft diesen Wert dann in einem wp_footerRückruf. Wenn dies zutrifft, wird wp_print_scripts()JavaScript geladen. Das Problem dabei ist, dass es nur für JavaScript und nicht für CSS funktioniert, da CSS im Inneren deklariert werden sollte <head>, was Sie nur während eines frühen Hooks wie initoder tun können wp_head.

Methode 2 wird vorzeitig ausgelöst und überprüft, ob der Shortcode im aktuellen Seiteninhalt vorhanden ist. Ich mag diese Methode viel besser als die erste, aber das Problem damit wird nicht erkannt, wenn die Vorlage aufruft do_shortcode().

Daher tendiere ich dazu, die zweite Methode zu verwenden und dann zu ermitteln, ob eine Vorlage zugewiesen ist, und wenn ja, den Shortcode zu analysieren. Zuvor wollte ich jedoch prüfen, ob jemand eine bessere Methode kennt.

Update: Ich habe die Lösung in mein Plugin integriert. Wenn jemand neugierig ist, es in einer Live-Umgebung zu sehen, können Sie es herunterladen oder durchsuchen .

Update 2: Ab WordPress 3.3 ist es jetzt möglich, einen Shortcode-Callback direkt aufzurufenwp_enqueue_script() , und die JavaScript-Datei wird in der Fußzeile des Dokuments aufgerufen. Das ist technisch auch für CSS-Dateien möglich, sollte aber als schlechte Praxis angesehen werden, da die Ausgabe von CSS außerhalb des <head>Tags gegen die W3C-Spezifikationen verstößt, unter Umständen FOUC verursacht und den Browser dazu zwingt, die Seite erneut zu rendern.

Ian Dunn
quelle
Ich habe in der Vergangenheit eine Variante von Methode 1 verwendet. Lade JS für den Shortcode in die Fußzeile, lade CSS für den Shortcode in der Zeile. Es funktioniert, ist aber hackisch. Freue mich auf eine bessere Lösung.
EAMann,
Die Hauptprobleme bei Inline-CSS sind, dass es schwierig ist, andere Stile zu überschreiben, und dass die Methode wp_enqueue_styles () nicht verwendet wird.
Ian Dunn
Wie viel CSS hast du?
Mfields
Wie viele Zeilen Javascript?
Mfields
2
Das Javascript ist der einfache Teil. Am besten geht das unter "Jedi" hier: scribu.net/wordpress/optimal-script-loading.html
mfields

Antworten:

11

Aufgrund meiner eigenen Erfahrung habe ich eine Kombination aus Methode 1 und 2 verwendet - die Architektur und die Fußzeilenskripte von 1 und die Vorausschau-Technik von 2.

Für die Vorausschau verwende ich jedoch Regex anstelle von stripos; persönliche Präferenz, schneller und kann nach 'falschem' Shortcode suchen;

preg_match( '#\[ *shortcode([^\]])*\]#i', $content );

Wenn Sie befürchten, dass Autoren do_shortcodemanuell verwendet werden, würde ich sie anweisen, einen Aktionsaufruf zu verwenden, um Ihren vorregistrierten Stil manuell in die Warteschlange zu stellen.

UPDATE : Für den faulen Autor, der nie RTFM verwendet, wird eine Nachricht ausgegeben, um den Fehler auf ihren Wegen hervorzuheben ;)

function my_shortcode()
{
    static $enqueued;
    if ( ! isset( $enqueued ) )
        $enqueued = wp_style_is( 'my_style', 'done' ); // cache it so we don't repeat if called over and over

    // do shortcode
    $output = '';

    if ( ! $enqueued )
        // you can output the message on first occurence only by wrapping it in the previous if
        $output .= <<<HTML
<p>Attention! You must enqueue the shortcode stylesheet yourself if calling <code>do_shortcode()</code> directly!</p>
<p>Use <code>wp_enqueue_style( 'my_style' );</code> before your <code>get_header()</code> call inside your template.</p>
HTML;

    return $output;
}
TheDeadMedic
quelle
Das ist ein guter Punkt. Idealerweise möchte ich, dass es funktioniert, ohne dass sie etwas extra tun müssen - weil die Hälfte der Zeit, in der sie die FAQ wahrscheinlich nicht zuerst lesen, sie einfach annehmen, dass sie kaputt ist -, aber ich werde das möglicherweise tun. Ich könnte die Skripte auf jeder Seite registrieren, aber nur dann in die Warteschlange stellen, wenn ich einen Shortcode finde. Anschließend können Benutzer sich bei Bedarf in init einbinden und die Enqueue-Funktionen in bestimmten Vorlagen aufrufen, sofern die Ausführung zu diesem Zeitpunkt noch nicht zu spät ist. In WP ist auch get_shortcode_regex () integriert.
Ian Dunn
3
Wenn die Benutzer mit der Implementierung do_shortcode()vertraut sind, kann man dann nicht davon ausgehen, dass sie auch mit den folgenden Anweisungen zum Einreihen des Shortcode-Stils vertraut sind?
Chip Bennett
1
Stimmt, aber es wird den regulären Ausdruck für alle Kurzwahlen erhalten, nicht nur für Ihre;) "Ich könnte die Skripte auf jeder Seite registrieren" Wahrscheinlich auch die bessere Methode! Beachten Sie, dass sie sich nicht einhängen müssen init, nur irgendwo zuvor wp_head. Überprüfen wp_style_is( 'my_style_handle', 'done' )Sie für den faulen Entwickler in Ihrem Shortcode. Wenn dies nicht der Fall ist, drucken Sie einen sichtbaren Fehler aus, der Sie anweist, was zu tun ist.
TheDeadMedic
@Chip - Ich mache mir keine Sorgen, dass sie nicht in der Lage sind, die Anweisungen zu befolgen , nur, dass sie nicht wissen, dass sie es sollen, da Sie 99% der Zeit nichts mehr tun müssen.
Ian Dunn
1
@Ian Ich war der Meinung, dass das Hinzufügen do_shortcode()zu der Vorlage bereits "etwas Zusätzliches tut" - und Benutzer, die dies tun würden, wüssten entweder bereits, dass der Stil in eine Warteschlange gestellt werden muss, oder sie wären eher bereit / wahrscheinlich besondere Anweisungen befolgen.
Chip Bennett
8

Ich beantworte diese Frage zu spät, aber seit Ian diesen Thread auf der Liste der WP-Hacker gestartet hat, war es mir ein Anliegen, sie zu beantworten, insbesondere angesichts der Tatsache, dass ich geplant habe, einigen Plugins, an denen ich arbeite, eine solche Funktion hinzuzufügen.

Ein zu berücksichtigender Ansatz besteht darin, beim Laden der ersten Seite zu überprüfen, ob der Shortcode tatsächlich verwendet wird, und dann den Shortcode-Verwendungsstatus in einem Post-Metaschlüssel zu speichern. Hier ist wie:

Schritt-für-Schritt-Anleitung

  1. Setzen Sie eine $shortcode_usedFlagge auf 'no'.
  2. In der Shortcode-Funktion selbst setzen Sie das $shortcode_usedFlag auf 'yes'.
  3. Stellen Sie eine 'the_content'Hook-Priorität ein 12, nachdem WordPress Shortcodes verarbeitet hat, und überprüfen Sie das Post-Meta ''mit der Taste auf a "_has_{$shortcode_name}_shortcode". (Ein Wert von ''wird zurückgegeben, wenn für die Beitrags-ID kein Beitrags-Metaschlüssel vorhanden ist.)
  4. Verwenden Sie einen 'save_post'Haken, um das Post-Meta zu löschen und das persistente Flag für diesen Post zu löschen, falls der Benutzer die Verwendung des Shortcodes ändert.
  5. Ebenfalls im 'save_post'Hook verwenden wp_remote_request(), um ein nicht blockierendes HTTP-GET an den eigenen Permalink des Posts zu senden, um das Laden der ersten Seite und das Setzen des persistenten Flags auszulösen.
  6. Schließlich setzt ein 'wp_print_styles'und überprüft Post meta für einen Wert von 'yes', 'no'oder mit ''der Taste "_has_{$shortcode_name}_shortcode". Wenn der Wert 'no'nicht ist, dienen Sie dem Äußeren. Wenn der Wert ist 'yes'oder ''weitermachen und dem Äußeren dienen.

Und das sollte es tun. Ich habe ein Beispiel-Plugin geschrieben und getestet, um zu zeigen, wie das alles funktioniert.

Beispiel Plugin Code

Das Plugin wird durch einen [trigger-css]Shortcode aktiviert, der die <h2>Elemente auf der Seite auf Weiß auf Rot setzt, damit Sie leicht sehen können, wie sie funktionieren. Es wird davon cssausgegangen, dass ein Unterverzeichnis eine style.cssDatei mit folgendem CSS enthält:

/*
 * Filename: css/style.css
 */
h2 {
  color: white;
  background: red;
}

Und unten ist der Code in einem funktionierenden Plugin:

<?php
/**
 * Plugin Name: CSS on Shortcode
 * Description: Shows how to conditionally load a shortcode
 * Author: Mike Schinkel <[email protected]>
 */
class CSS_On_Shortcode {

  /**
   * @var CSS_On_Shortcode
   */
  private static $_this;

  /**
   * @var string 'yes'/'no' vs. true/false as get_post_meta() returns '' for false and not found.
   */
  var $shortcode_used = 'no';

  /**
   * @var string
   */
  var $HAS_SHORTCODE_KEY = '_has_trigger-css_shortcode';
  /**
   *
   */
  function __construct() {
    self::$_this = $this;
    add_shortcode( 'trigger-css', array( $this, 'do_shortcode' ) );
    add_filter( 'the_content', array( $this, 'the_content' ), 12 ); // AFTER WordPress' do_shortcode()
    add_action( 'save_post', array( $this, 'save_post' ) );
    add_action( 'wp_print_styles', array( $this, 'wp_print_styles' ) );
  }

  /**
   * @return CSS_On_Shortcode
   */
  function this() {
    return self::$_this;
  }

  /**
   * @param array $arguments
   * @param string $content
   * @return string
   */
  function do_shortcode( $arguments, $content ) {
    /**
     * If this shortcode is being used, capture the value so we can save to post_meta in the 'the_content' filter.
     */
    $this->shortcode_used = 'yes';
    return '<h2>THIS POST WILL ADD CSS TO MAKE H2 TAGS WHITE ON RED</h2>';
  }

  /**
   * Delete the 'has_shortcode' meta value so that it can be regenerated
   * on first page load in case shortcode use has changed.
   *
   * @param int $post_id
   */
  function save_post( $post_id ) {
    delete_post_meta( $post_id, $this->HAS_SHORTCODE_KEY );
    /**
     * Now load the post asynchronously via HTTP to pre-set the meta value for $this->HAS_SHORTCODE_KEY.
     */
    wp_remote_request( get_permalink( $post_id ), array( 'blocking' => false ) );
  }

  /**
   * @param array $args
   *
   * @return array
   */
  function wp_print_styles( $args ) {
    global $post;
    if ( 'no' != get_post_meta( $post->ID, $this->HAS_SHORTCODE_KEY, true ) ) {
      /**
       * Only bypass if set to 'no' as '' is unknown.
       */
      wp_enqueue_style( 'css-on-shortcode', plugins_url( 'css/style.css', __FILE__ ) );
    }
   }

  /**
   * @param string $content
   * @return string
   */
  function the_content( $content ) {
    global $post;
    if ( '' === get_post_meta( $post->ID, $this->HAS_SHORTCODE_KEY, true ) ) {
      /**
       * This is the first time the shortcode has ever been seen for this post.
       * Save a post_meta key so that next time we'll know this post uses this shortcode
       */
      update_post_meta( $post->ID, $this->HAS_SHORTCODE_KEY, $this->shortcode_used );
    }
    /**
     * Remove this filter now. We don't need it for this post again.
     */
    remove_filter( 'the_content', array( $this, 'the_content' ), 12 );
    return $content;
  }

}
new CSS_On_Shortcode();

Beispiel-Screenshots

Hier ist eine Reihe von Screenshots

Basic Post Editor, kein Inhalt

Nachanzeige, kein Inhalt

Einfacher Post Editor mit [trigger-css]Shortcode

[trigger-css]Nachanzeige mit Kurzwahl

Nicht sicher, ob es 100% ist

Ich glaube, dass das oben Genannte in fast allen Fällen funktionieren sollte, aber da ich gerade diesen Code geschrieben habe, kann ich nicht 100% sicher sein. Wenn Sie Situationen finden, in denen es nicht funktioniert, würde ich gerne wissen, ob ich den Code in einigen Plugins korrigieren kann, die ich soeben hinzugefügt habe. Danke im Voraus.

MikeSchinkel
quelle
Also lösen fünf Plugins, die Ihren Ansatz verwenden, jedes Mal, wenn ein Beitrag gespeichert wird, fünf Remote-Anfragen aus? Ich würde lieber einen regulären Ausdruck verwenden post_content. Und was ist mit Shortcodes in Widgets?
Fuxia
@toscho Das Auslösen des Nachladens ist eigentlich optional; Es ist nur dazu da, um sicherzustellen, dass ein Benutzer nicht die erste Seite mit geladenen externen Elementen laden muss. Es ist auch ein nicht blockierender Anruf, so dass Sie ihn theoretisch nicht bemerken sollten. Für unseren Code verwenden wir eine Basisklasse, sodass die Basisklasse nur einmal damit umgehen kann. Wir könnten den Hook 'pre_http_request'haken und mehrere Aufrufe an dieselbe URL deaktivieren, während der 'save_post'Hook aktiv ist, aber ich möchte warten, bis wir wirklich eine Notwendigkeit dafür gesehen haben, nein? Widgets können zwar verbessert werden, aber es ist noch kein Anwendungsfall, den ich mir angesehen habe.
MikeSchinkel
1
@toscho - Sie können auch nicht sicher sein, ob der Shortcode vorhanden ist, da ein anderer Hook ihn möglicherweise löscht. Sie können sich nur sicher sein, ob die Shortcode-Funktion tatsächlich ausgelöst wird. Der Regex-Ansatz ist also nicht zu 100% zuverlässig.
MikeSchinkel
Ich kenne. Es gibt keine kugelsichere Möglichkeit, CSS für Shortcodes einzufügen (außer mit <style>).
Fuxia
Ich arbeite mit einer Implementierung, die auf dieser Lösung basiert, und wollte nur darauf hinweisen, dass diese Methode keine Post- / Seitenvorschau erfordert.
Mor7ifer
5

Googeln fand eine mögliche Antwort für mich . Ich sage "Potenzial", wie es gut aussieht, sollte funktionieren, aber ich bin nicht zu 100% davon überzeugt, dass es der beste Weg ist, dies zu tun:

add_action( 'wp_print_styles', 'yourplugin_include_css' );
function yourplugin_include_css() {
    // Check if shortcode exists in page or post content
    global $post;

    // I removed the end ' ] '... so it can accept args.
    if ( strstr( $post->post_content, '[yourshortcode ' ) ) {
        echo $csslink;
    }
}

Dies sollte in der Lage sein, zu überprüfen, ob der aktuelle Beitrag einen Shortcode verwendet, und dem <head>Element ein Stylesheet entsprechend hinzuzufügen . Aber ich denke nicht, dass es für eine Indexseite (dh mehrere Posts in der Schleife) funktionieren wird ... Es stammt auch aus einem 2-jährigen Blog-Post, daher bin ich nicht einmal sicher, ob es mit WP 3.1.X funktionieren wird .

EAMann
quelle
Dies funktioniert nicht, wenn der Shortcode Argumente enthält. Wenn Sie diesen langsamen Weg wirklich gehen möchten, verwenden Sie WPs get_shortcode_regex()für die Suche.
onetrickpony
Wie ich schon sagte, "potentielle" Antwort, die ich noch nicht getestet habe ... :-)
EAMann
Das ist im Grunde dasselbe wie Methode 2, aber es überprüft die Vorlage immer noch nicht auf do_shortcode () -Aufrufe.
Ian Dunn
Warum müsste es das tun? Wenn Sie do_shortcode()die Vorlage manuell aufrufen, wissen Sie bereits, dass Sie den
Shortcode ausführen
Ich bin nicht derjenige, der die Kurzwahl anruft, sondern der Benutzer. Dies ist für ein verteiltes Plugin gedacht, nicht für ein privates.
Ian Dunn
2

Unter Verwendung einer Kombination aus der Antwort von TheDeadMedic und der Dokumentation get_shortcode_regex () (in der meine Shortcodes tatsächlich nicht gefunden wurden) habe ich eine einfache Funktion erstellt, mit der Skripts für mehrere Shortcodes in die Warteschlange gestellt werden. Da das wp_enqueue_script () in Shortcodes nur zur Fußzeile hinzugefügt wird, kann dies hilfreich sein, da es sowohl Kopf- als auch Fußzeilenskripts verarbeiten kann.


function add_shortcode_scripts() {
    global $wp_query;   
    $posts = $wp_query->posts;
    $scripts = array(
        array(
            'handle' => 'map',
            'src' => 'http://maps.googleapis.com/maps/api/js?sensor=false',
            'deps' => '',
            'ver' => '3.0',
            'footer' => false
        ),
        array(
            'handle' => 'contact-form',
            'src' => get_template_directory_uri() . '/library/js/jquery.validate.min.js',
            'deps' => array( 'jquery' ),
            'ver' => '1.11.1',
            'footer' => true
        )   
    );

    foreach ( $posts as $post ) {
        foreach ( $scripts as $script ) {
            if ( preg_match( '#\[ *' . $script['handle'] . '([^\]])*\]#i', $post->post_content ) ) {
                // enqueue css and/or js
                if ( wp_script_is( $script['handle'], 'registered' ) ) {
                    return;
                } else {
                    wp_register_script( $script['handle'], $script['src'], $script['deps'], $script['ver'], $script['footer'] );
                    wp_enqueue_script( $script['handle'] );
                }
            }
        }
    }
}
add_action( 'wp', 'add_shortcode_scripts' );
Sean Michaud
quelle
1

Schließlich habe ich auch eine Lösung für das bedingte Laden von CSS gefunden, die für mein Plugin www.mapsmarker.com funktioniert und die ich gerne mit Ihnen teilen möchte. Es wird geprüft, ob mein Shortcode in der aktuellen Vorlagendatei und in der Datei header / footer.php verwendet wird. Wenn ja, wird das benötigte Stylesheet in die Kopfzeile eingefügt:

  function prefix_template_check_shortcode( $template ) {
    $searchterm = '[mapsmarker';
    $files = array( $template, get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'header.php', get_stylesheet_directory() . DIRECTORY_SEPARATOR . 'footer.php' );
    foreach( $files as $file ) {
        if( file_exists($file) ) {
            $contents = file_get_contents($file);
            if( strpos( $contents, $searchterm )  ) {
                wp_enqueue_style('
leafletmapsmarker', LEAFLET_PLUGIN_URL . 'leaflet-dist/leaflet.css');
                  break; 
            }
        }
    }
  return $template;
  }  
  add_action('template_include','prefix_template_check_shortcode' );
robertharm
quelle
ein bisschen abseits, aber geht das nicht davon aus, dass die Leute header.php und footer.php benutzen. Was ist mit den unter scribu.net/wordpress/theme-wrappers.html beschriebenen Themenumbruchmethoden ? oder themen wie wurzeln, die ihre vorlagenteile woanders behalten?
Orionrush
1

Für mein Plugin habe ich festgestellt, dass Benutzer manchmal einen Theme Builder haben, dessen Shortcode in Post-Metadaten gespeichert ist . Hier ist, was ich verwende, um festzustellen, ob mein Plugin-Shortcode in aktuellen Post- oder Post-Metadaten vorhanden ist :

function abcd_load_my_shorcode_resources() {
       global $post, $wpdb;

       // determine whether this page contains "my_shortcode" shortcode
       $shortcode_found = false;
       if ( has_shortcode($post->post_content, 'my_shortcode') ) {
          $shortcode_found = true;
       } else if ( isset($post->ID) ) {
          $result = $wpdb->get_var( $wpdb->prepare(
            "SELECT count(*) FROM $wpdb->postmeta " .
            "WHERE post_id = %d and meta_value LIKE '%%my_shortcode%%'", $post->ID ) );
          $shortcode_found = ! empty( $result );
       }

       if ( $shortcode_found ) {
          wp_enqueue_script(...);
          wp_enqueue_style(...);
       }
}
add_action( 'wp_enqueue_scripts', 'abcd_load_my_shorcode_resources' );
zdenekca
quelle
0

weil CSS in deklariert werden sollte <head>

Für CSS-Dateien können Sie diese in Ihre Shortcode-Ausgabe laden:

<style type="text/css">
  @import "path/to/your.css"; 
</style>

Legen Sie eine Konstante oder etwas anderes fest MY_CSS_LOADED(schließen Sie das CSS nur ein, wenn die Konstante nicht festgelegt ist).

Beide Methoden sind langsamer als auf diese Weise.

Für JS-Dateien können Sie dasselbe tun, wenn das Skript, das Sie laden, eindeutig ist und keine externen Abhängigkeiten aufweist. Wenn dies nicht der Fall ist, laden Sie es in die Fußzeile, aber verwenden Sie die Konstante, um zu bestimmen, ob geladen werden muss oder nicht ...

ein Trickpony
quelle
3
Das Laden von CSS außerhalb des <head>Elements ist nicht korrekt. Die Validierung ist zwar nur eine Richtlinie, aber wenn wir versuchen, diese Richtlinie einzuhalten, ist das Laden des Stylesheets innerhalb der Shortcode-Ausgabe eine schlechte Idee.
EAMann,
Inline-CSS-Blöcke sind gültige Markups, auch in XHTML, soweit ich mich erinnere. Es gibt keinen Grund, sie nicht zu verwenden, wenn Sie keine andere akzeptable Alternative haben
onetrickpony
1
Laut W3C Validierungstool: <style type="text/css"> The element named above was found in a context where it is not allowed. This could mean that you have incorrectly nested elements -- such as a "style" element in the "body" section instead of inside "head". Inline-Stile ( <element style="..."></element>) sind also gültig, Inline- <style>Elemente jedoch nicht.
EAMann,
1
mach es filterbar und jedes andere Plugin oder Theme kann damit machen was es will. Wenn der Filter so konfiguriert ist, dass eine leere Zeichenfolge zurückgegeben wird, wird nichts gedruckt.
Mfields
1
Sie haben keine objektiven Gründe gegen diese Praxis genannt. Egal, es spielt keine Rolle. Ich sehe hier nur zwei Optionen: Immer CSS / Skripte laden (Größe optimieren) oder bedingte Inline-Stile
onetrickpony
0

Script Logic ist ein WordPress-Plugin, mit dem Sie die volle Kontrolle über alle JavaScript- und CSS-Dateien haben. Mit diesem Plugin können Sie CSS- und JS-Dateien nur auf Seiten laden, auf denen dies erforderlich ist.

http://wordpress.org/plugins/script-logic/

Tahir Yasin
quelle
Das ist nicht wirklich relevant für diese Frage, bei der es darum geht, das Enqueueing aus dem Plugin heraus zu steuern. Ihr Ansatz würde erfordern, den Benutzer zu bitten, ein anderes Plugin zu installieren und dann zu verstehen, wie es richtig konfiguriert wird.
Ian Dunn