Archiv der Kategorie: AJAX

Gehts auch in Joomla? Bewertungsformular mit Ajax

Was in WordPress und Drupal so reibungslos geklappt hat, sollte doch euch in Joomla zu realisieren sein: ein kleines Bewertungsformular mit Notenvergabe am Ende jedes Artikels, das sich beim Abschicken einer neuen Benotung automatisch aktualisiert. Aussehen soll das Ganze in etwa so:

bewertungformular1

bewertungformular1

Wie kriegt man so etwas ans Ende jedes Artikels?

Mit einem Override, und der ist in Joomla 3 nicht weiter schwierig zu erstellen. Man geht zu Erweiterungen/Templates/Templates und klickt beim aktiven Template auf Details und Dateien, dann auf den Reiter Overrides erstellen. Unter Komponenten/com_content article anklicken, dann sollte eine solche Nachricht erscheinen:

Nachricht

Es wurde ein Override erstellt in \templates\protostar\html\com_content\article
Ein neuer Override wurde erstellt.

Man kann den Override jetzt direkt im eingebauten Editor bearbeiten, ich nehme lieber den Notepad++. Um die richtige Position für das Formular zu ermitteln, sucht man nach folgender Div und klemmt danach einen Platzhalter rein:

<div itemprop="articleBody">
 <?php echo $this->item->text; ?>
 </div>
<h1>Hier kommt das Formular hin</h1>

Dann sollte in jedem Artikel so etwas auftauchen:

override_formularposition

override_formularposition

OK, die richtige Position hätten wir jetzt. Aber was genau soll da hin? Das einzig senkrechte wäre natürlich ein eigenes Modul, das die gesamte Logik für die Benotung enthält, und dafür müssen wir ein bisschen ausholen.

Wie man ein einfaches Joomla-Modul erstellt

habe ich in diesem Artikel ausführlich beschrieben, da halten wir uns mal dran und basteln ein eigenes Modul mit Namen mod_el_bewertung. Das soll erstmal nichts machen ausser einen Platzhaltertext ausgeben, dafür sieht die helper.php anfangs so aus:

<?php
/**
 * Helper class for EL Bewertung! module
 * 
 * @package Joomla.Tutorials
 * @subpackage Modules
 * @link http://docs.joomla.org/J3.x:Creating_a_simple_module/Developing_a_Basic_Module
 * @license GNU/GPL, see LICENSE.php
 * mod_helloworld is free software. This version may have been modified pursuant
 * to the GNU General Public License, and as distributed it includes or
 * is derivative of works licensed under the GNU General Public License or
 * other free or open source software licenses.
 */
class ModHelloWorldHelper
{
 /**

 * Retrieves the hello message
 *
 * @param array $params An object containing the module parameters
 *
 * @access public
 */ 
 public static function getHello($params)
 {
 return 'Hier soll das Formular hin!';
 
 }

Wenn alles geklappt hat, kann man das neue Modul jetzt aktivieren. Dazu unter Erweiterungen/Verwalten/überprüfen das richtige Modul suchen und installieren, dann unter Erweiterungen/Module/neu den eigenen Modultyp anwählen und einen Titel vergeben.

Modul im Override platzieren

 Dafür fügt man im Override der default.php folgende Zeile ein:

<?php echo JHtml::_('content.prepare', '{loadposition bewertungsformular}'); ?>

Nochmal zurück ins neuerstellte Modul, bei Modulposition bewertungsformular eingeben, Schreibweise beachten! Jetzt sollte die Modulüberschrift und der Platzhaltertext am Ende jedes Artikels auftauchen:

platzhalter

platzhalter

So, das waren mal die Vorarbeiten. Jetzt gehts zur Sache.

Der Code für das Formular

kommt in die helper.php in die public static function getHello() (ich war hier bloß zu faul um die Funktions- und Klassenbezeichnungen anzupassen). Wichtig ist am Anfang, dass wir uns die ID des aktuellen Beitrags holen und in die Div mit der ID ‚beitragsid‘ packen.

public static function getHello($params)
 {
 //ID des aktuellen Artikels bestimmen';
 $id = JRequest::getVar('id');
 echo 'ID dieses Beitrags: <span id = "beitragsid">'.$id.'</span>';
 
 echo '<div style="border:1px dotted #000; text-align:center; padding:10px;">';
 echo '<h4>Dir gefällt dieser Beitrag?</h4>';
 echo '<p>Dann bewerte ihn!</p>';
 echo '</div>';
 
 echo " <form action = '' method='post'>";
 
 echo "<fieldset>
 <div id = 'noten' class = 'noten' style = 'border:2px solid blue; padding :4px;'>
 <input type='radio' name='note' value='1'>sehr gut
 <input type='radio' name='note' value='2'>gut
 <input type='radio' name='note' value='3'>befriedigend<br>
 <input type='radio' name='note' value='4'>ausreichend
 <input type='radio' name='note' value='5'>mangelhaft
 <input type='radio' name='note' value='6'>ungenügend
 
 <input type ='button' name = 'absenden' class = 'bew-button' value = 'abschicken' onclick='el_bew_ajax()'/>
 
 </div>
 </fieldset>";
 echo "</form>";
 
 
 }

Das Javascript für den onclick-Aufruf packen wir der Einfachheit halber an den Anfang der  helper.php in Script-Tags, dazu gleich mehr. Jetzt brauchen wir erst noch die Tabelle für die Bewertungen, die sieht genauso aus wie in WordPress oder Drupal und hat genau drei Felder:

bewertungentabelle

bewertungentabelle

id ist Autowert, beitragsid und note schreiben wir dann gleich rein. Jetzt lesen wir erst einmal die schon vorhandenen Benotungen aus.

Bereits vorhandene Anzahl Bewertungen und Durchschnitt ermitteln

Dabei kommt uns JDatabase zu Hilfe, das geht so:

//Bereits vorhandene Benotungen abholen
 $db = JFactory::getDBO();
 $query = "SELECT * FROM bewertungen where beitragsid = ".$id."";
 $db->setQuery($query);
 $result = $db->execute();
 $anzahl = $db->getNumRows();
 //echo $anzahl." Bewertungen gefunden";
 
 $results = $db->loadObjectList();
 $durchschnitt = 0;
 foreach ($results as $zeile) :
 $durchschnitt = $durchschnitt+ intval($zeile->note);
 endforeach;
 
 if ($anzahl != 0){ 
 $durchschnitt = $durchschnitt / $anzahl;
 }
 echo "<span id = 'ausgabe'>".$anzahl." Bewertungen gefunden, Durchschnitt: ".$durchschnitt."<span>";

Wichtig ist hier, dass man sich mit dem $db->loadObjectList(); die Ergebnisliste der SQL-Abfrage holt, über die kann man dann mit dem Foreach iterieren. So, aber jetzt endlich:

Das Javascript

Das kann man nahezu unverändert aus der WordPress-Lösung übernehmen, man stellt es einfach an den Anfang der helper.php, zunächst mal nur mit Debug-Ausgabe von Beitragsid und Note:

<script>
 function el_bew_ajax(){
 
 
 //Beitragsid aus span holen
 var akt_id = document.getElementById("beitragsid").innerHTML;
 
 //Note aus den Radiobuttons holen
 var akt_note = jQuery("input:radio[name=note]:checked").val();
 
 Debug-Ausgabe
 alert(akt_note + ' ' + akt_id);
 
 //Check ob keine Note angewählt, Abbruch
 if (typeof akt_note === 'undefined'){
 alert('Bitte eine Note wählen!');
 return;}
 
 //***********************Hier kommt der ajax-call hin
 
</script>

Die Callback-Funktion

Für den Ajax brauchen wir natürlich erstmal die Callback-Funktion, die stecken wir in eine separate PHP-Datei.

<?php

$id = '';
if ( isset($_GET['id']) )
{
  $id = $_GET['id'];
}
$note = '';
if ( isset($_GET['note']) )
{
  $note = $_GET['note'];
}

$pdo = new PDO('mysql:host=localhost;dbname=joomla', '*****', '*******');

//neue Note erstmal einfügen
$sql = "INSERT INTO bewertungen (beitragsid, note) VALUES (".$id.",".$note.")";
$neu = $pdo->query($sql);

//Suche nach aktueller id
$sql = "SELECT * FROM bewertungen where beitragsid = ".$id."";
$records = $pdo->query($sql);
$gefunden = $records->rowcount();

$durchschnitt = 0;
foreach ($records as $row) {
        
        $durchschnitt = $durchschnitt + $row['note'];
    }
if ($gefunden != 0){        
    $durchschnitt = $durchschnitt / $gefunden;
    }
echo $gefunden." Bewertungen vorhanden, Durchschnittsnote = ".$durchschnitt;

?>

Das ist genau das selbe straighte PHP, das ich auch in der Drupal-Lösung schon verwendet habe, man muss nur die Login-Informationen im PDO anpassen. Jetzt fehlt nur noch:

Das Script mit dem Ajax-Call

Ich hab mir dafür noch den Pfad zum Modulverzeichnis in eine versteckte Span geschrieben, das passiert in der helper.php vor dem Formular:

//Pfad zum Modulverzeichnis abholen
        $pfad = JURI::base().'modules/'.'mod_el_bewertung/';
        echo '<span id = "pfad" style = "display:none;">'.$pfad.'</span>';

Dann kann unser Script marschieren:

<script>
 function el_bew_ajax(){
            
            //Pfad zum Modulverzeichnis abholen
            var modulpfad = document.getElementById("pfad").innerHTML;
            
            //Beitragsid holen
            var akt_id = document.getElementById("beitragsid").innerHTML;
                        
            //Note aus den Radiobuttons holen
            var akt_note = jQuery("input:radio[name=note]:checked").val();
            
            //Debug-Ausgabe
            //alert(akt_note + ' ' + akt_id);
                        
            //Check ob keine Note angewählt, Abbruch
            if (typeof akt_note === 'undefined'){
            alert('Bitte eine Note wählen!');
            return;}
            
            //***********************ajax-call
            if (window.XMLHttpRequest)
             {
              // AJAX nutzen mit IE7+, Chrome, Firefox, Safari, Opera
              xmlhttp=new XMLHttpRequest();
             }
             else
             {
              // AJAX mit IE6, IE5
              xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
             }
             xmlhttp.onreadystatechange=function()
             {
              if (xmlhttp.readyState==4 && xmlhttp.status==200)
              {
               document.getElementById("ausgabe").innerHTML = xmlhttp.responseText;
               //alert(xmlhttp.responseText);
              }
             }
             xmlhttp.open("GET",""+modulpfad+"callback_ajax_el.php?id="+akt_id+"&note="+akt_note,true);
             
             xmlhttp.send();
            
            
            
            //**********************end ajax call
}//end function el_bew_ajax

</script>

Fertig! Der Call ist scharfgeschaltet, beim abschicken einer neuen Bewertung aktualisiert sich die Anzeige.

anzeige

anzeige

Fazit

In Joomla eine einfaches Modul zu erstellen ist erstmal gewöhnungsbedürftig, aber wenn man die Logik einmal überrissen hat, kann man viel damit machen. Was mir nicht besonders gefällt, ist dass ich noch keine einfache Möglichkeit gefunden habe, einen Override nur für eine bestimmte Kategorie anzulegen, ich hätte zum Beispiel das Bewertungsformular gerne nur in der Kategorie ‚Rezepte‘. Ich hab mir da schonmal damit beholfen, in die Override-Datei eine Datenbankabfrage einzubauen, die zur aktuellen Beitragsid die Kategorie abholt, die ging so:

<?php
    $db=JFactory::getDbo();
    $article_id = JFactory::getApplication()->input->get(‚id‘);
    echo „aktuelle Artikelid= „.$article_id;
    
    $db->setQuery(’select catid from #__content where id= ‚.$article_id.“);
    $catid=$db->loadResult();
    echo „aktuelle catid= „.$catid;
    
    if ($catid == 8){
        echo JHtml::_(‚content.prepare‘, ‚{loadposition bewertungsformular}‘);
    }
    else {
        echo „Dies ist kein Rezept, Kategorie: „.$catid;
        }
    ?>

Aber das ist meiner Ansicht nach eine ziemliche Krücke. Ich muss mal ein bisschen forschen, ob das nicht eleganter geht.

Ihre Meinung interessiert mich!

Wie hat Ihnen dieser Artikel gefallen?

sehr gutgutbefriedigendausreichendmangelhaftungenügend

HTML5 Datalist Value – ganz so einfach ist es nicht

Eigentlich gibt es in HTML ja die Option, einem Dropdownfeld zusätzlich zur Bezeichnung einen Value mitzugeben, der dann bei der Auswahl weitergegeben wird. In meinem Fall wäre die Bezeichnung der Titel des Rezeptes, und der Value die ID aus der Datenbank. Das würde dann etwa so aussehen:

...
<option value = '345'>Dampfnudeln wie bei Oma</option>"
...

Sobald der Benutzer den Eintrag Dampfnudeln ausgewählt und auf Return gedrückt hat, wird der Wert 345 übergeben – sollte zumindest. Diese Option funktioniert aber bei einer Datalist browserabhängig ganz unterschiedlich, wäre ja zu einfach.

In Chrome

…kriege ich die numerischen IDs in der Auswahlliste mit angezeigt, das ist nicht besonders schön, funktioniert aber wenigstens.

chrome_value

chrome_value

Es wird auch richtig bei der Auswahl die ID weitergegeben.

In Firefox Quantum

wird der Value-Wert einfach ignoriert, ich bekomme nur die Titel angezeigt und bei der Auswahl ausgegeben.

firefox_value

firefox_value

Im Internet Explorer 10

wirds besonders g’schmackig, da kann man gar nicht mehr nach dem Titel auswählen, der Autocomplete funktioniert erst wieder, wenn ich den Option Value ganz herausnehme.

Was nun? Ich mach mal kurzen Prozeß

Bevor ich mir hier für die unterschiedlichen Varianten der einzelnen Browserausgaben einen Wolf programmiere, verzichte ich ganz auf die Übergabe der ID und suche meine Rezepte schlicht nach dem Titel aus der Datenbank. Das kann ich mir erlauben, weil a) ich nur ein-eindeutige Titel habe und b) die Schreibweise aus dem Dropdownfeld identisch ist mit der Schreibweise in der wp_posts. Falls der User dann noch zusätzliche Zeichen eingibt (woran ihn nieman hindert) wird halt kein Rezept gefunden, aber damit kann ich leben. Um c) auszuschliessen, dass Revisions oder attachments mit reinrutschen, erweitere ich meinen Select einfach um post_type like ‚post‘ and post_status like ‚publish‘, aber das reicht dann.

Das Ganze klemme ich in eine if(isset($_POST)…Abfrage und packe es in den Shortcode für das Formular mit rein, das sieht dann so aus:

//*********Formular als Shortcode einbinden
 function asuche(){ 
    echo "<form action='#' method='post'>";
    echo "<input type = 'text' name = 'ausgesucht' size = '100' id = 'textfeld' autocomplete = 'off' onkeyup = 'myFunction(this.value);' list = 'suchliste'>";
    
    echo "<datalist id='suchliste'>";
    echo "<div id = 'liste'>";
    echo "</div>";    
    echo "</datalist>";
    echo "</form>";
     if (isset($_POST['ausgesucht'])){
             
    /*********Action*/     
    global $wpdb;
    $alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts 
where post_title like '".$_POST['ausgesucht']."'
 and post_status like 'publish' and post_type like 'post'");
    foreach($alleposts as $einpost){
        
        echo "<h1>".$einpost->post_title."</h1>";
        echo $einpost->post_content;
    }
    /*********End Action*/
    }    
    
 }
 add_shortcode('asuche', 'asuche');

Damit wird folgende Funktionalität realisiert: der User kann aus der Autocomplete-Liste einen Eintrag auswählen und mit Return in das Textfeld übernehmen. Bei nochmaligem Drücken von Return wird der komplette Text des ausgewählten Rezeptes angezeigt. Das ist mir schön genug, und eingängig in der Bedienung.

Nur der Internet-Explorer macht noch Zicken, der schlägt beharrlich nur Rezepte vor, die mit dem eingegebenen Suchbegriff anfangen, nicht alle in denen der Begriff irgendwo im Titeltext vorkommt. Sei es drum, in Chrome und Firefox funktioniert es jedenfalls wie beabsichtigt.

Ihre Meinung interessiert mich!

Wie hat Ihnen dieser Artikel gefallen?

sehr gutgutbefriedigendausreichendmangelhaftungenügend

WordPress AJAX Autocomplete für Minimalisten

Ich bin ja bekanntermassen ziemlich hartnäckig, und obwohl ich die Lösung für das Autocomplete-Feld von David Nash toll finde, hats mir doch keine Ruhe gelassen. Das muss doch noch ein bisschen einfacher gehen, dachte ich mir und hab den Codex durchforstet. Zum Thema Using Ajax in Plugins bin ich fündig geworden, da stand doch ein (Codex-unüblich nachvollziehbares) Beispiel drin, über das habe ich mich hergemacht. Das Javascript für den Ajax-Call wird hier nicht als externes Script enqueued, sondern in die Plugin-Datei integriert. Ist aber nicht schlimm, das Script ist nämlich sehr überschaubar – aber jetzt mal der Reihe nach.

Die Vorgabe

Ich möchte gern ein Textfeld, in das ich Suchbegriffe eingeben kann, und das soll mir dann aus der Datenbank alle Post Titles ausgeben, in denen der Suchbegriff vorkommt. Das soll etwa so aussehen:

schoko

schoko

Wir basteln uns ein Plugin

Natürlich brauchen wir dazu wieder ein Formular, und ich werde auch wieder die HTML5-Datalist verwenden. Wir packen das Ganze in ein Plugin, damit wir vom Theme unabhängig sind, und tun Formular und Javascript in einen Shortcode, damit wir das ganze in einem beliebigen Beitrag oder auf einer beliebigen Seite auch ausgeben können.

Zuerst mal nur das Plugin ohne JScript und Ajax-Funktion:

<?php
/*
Plugin Name: WordPressAjax Testplugin
Plugin URI: http://localhost/wp_ajax/wp-content/plugins/wp-ajax
Description: Zum Testen von Ajax mit WordPress
Version: 1.0
Author: Evi Leu
Author URI: http://www.evileu.de
*/


/*************************************************************/
//*********Formular als Shortcode einbinden
 function asuche(){
    
    echo "<input type = 'text' id = 'textfeld' autocomplete = 'off'
 onkeyup = 'myFunction(this.value);' list = 'suchliste'>";
    
    echo "<datalist id='suchliste'>";
    echo "<div id = 'liste'>";
    echo "</div>";    
    echo "</datalist>";
    
 }
 add_shortcode('asuche', 'asuche');

Ich hab hier schon mal den Funktionsaufruf bei onkeyup mit drin, der kriegt als Parameter den Inhalt des Textfeldes mit, Script kommt gleich. Die Verbindung vom Textfeld zur Datalist geht über die id suchliste. Autocomplete = ‚off‘ damit der Browser nicht reinpfuscht. In der Datalist hab ich eine weitere Div mit der id liste angelegt, in die schreibt nachher mein Ajax-Call seine Rückgabewerte.

Das integrierte Javascript

Wie gesagt, das Scripterl ist so kurz, dass man es nicht in eine externe Datei auslagern muss, wir nehmen es in die Funktion asuche mit rein, das sieht dann so aus:

function asuche(){ ?>
     
    <script>
        function myFunction(wert){
              
        var aktwert = wert;
        var data = {
            'action': 'my_action',
            'whatever': aktwert
            
        };

        // since 2.8 ajaxurl is always defined in the admin header and points to admin-ajax.php
        // wir sind aber nicht auf den Admin-Pages, deswegen der volle Pfad
        jQuery.post('http://localhost/wp_ajax/wp-admin/admin-ajax.php', data, function(response) {
        //    jQuery.post(ajaxurl, data, function(response) {
            
            document.getElementById("liste").innerHTML = response;
            
        });
        } //*********************End myFunction
        
    </script>
    <?php
...

Was passiert hier? Zuerstmal wird der als Parameter übergebene Wert des Textfeldes auf die Variable aktwert gelegt, diese kommt in die data-Parameter des Ajaxcalls mit rein. Der erste Parameter mit dem ‚action‘ definiert, welche Funktion nachher aufgerufen werden soll, die kommt gleich im Anschluss. Mit dem JQuery.post(…) wird die Anfrage an den admin-ajax.php weitergeleitet und die response definiert. Die macht nichts anderes als die Div mit der id liste mit dem Rückgabewert der aufgerufenen Funktion zu belegen, konkret füllt sie die Options-Liste unserer Datalist.

Was weniger schön ist: die Url zur admin-ajax.php steht hier noch als langer Rattenschwanz mit drin, das könnte man sicher schöner lösen. Wenn man zum Beispiel das JQuery-Script auslagern und per enqueue laden würde, könnte man mit wp_localize_script den Pfad als Variable mitgeben – aber das sind jetzt schon Feinheiten.

Damit der Funktionsaufruf klappt: Add Action

Ich habs zweimal drin, sowohl für die Admin-Ansicht als auch für nicht eingeloggte User:

add_action( 'wp_ajax_my_action', 'my_action' );
add_action( 'wp_ajax_nopriv_my_action', 'my_action' );

Jetzt fehlt uns noch die tatsächliche Funktion, die den Übergebenen Parameter aktwert entgegennimmt und und die passenden  Werte aus der Datenbank abruft.

Die PHP-Funktion

Da wir uns im Kontext von WordPress bewegen, brauchen wir keinen externen Datenbankzugriff mit mysqli oder PDO, sondern können wie gewohnt mit dem $wpdb-Objekt arbeiten. Die Funktion sieht so aus:

function my_action() {
    global $wpdb; 

    $whatever = $_POST['whatever'];
    
    /**********************************************/
    $alleposts = $wpdb->get_results( "SELECT * from ".$wpdb->prefix."posts 
where post_title like '%".$whatever."%' 
    and post_type like 'post' and post_status like 'publish'");
    foreach($alleposts as $einpost){
        
        echo "<option>".$einpost->post_title."</option>";
}
    /**********************************************/
    
    
    wp_die(); // this is required to terminate immediately and return a proper response
} //**********End function my_action

Unsere übergebene Variable whatever wird nicht anders verarbeitet, als ob sie aus einem Formular direkt käme, dafür sorgt der Call mit dem jQuery.post(…). Der Rest ist wirklich simpel, ich suche mir aus der Tabelle wp_posts alle Einträge, die unseren Suchbegriff matchen, gebe die gefundenen Post Titel mit der Foreach-Schleife aus und klebe noch die Option-Tags für unsere Datalist dran. Fertig ist unser Autocomplete-Feld!

suchwort_back

suchwort_back

Nachtrag: JS ausgelagert und Pfad übergeben

Wie ich oben schon angemerkt habe, ist es nicht so ideal dass im Javascript der volle Pfad zur admin-ajax.php steht. Das geht auch besser. Das Script kommt aus der Plugin-Datei raus, ich nenne es mal wp_ajax.js, und es sieht so aus:

function myFunction(wert){
                
        var aktwert = wert;
        var data = {
            'action': 'my_action',
            'whatever': aktwert
            };

        // der volle Pfad zur admin-ajax.php liegt auf my_ajaxurl
        jQuery.post(my_ajaxurl, data, function(response) {
                    
            document.getElementById("liste").innerHTML = response;
            
        });
        } //*********************End myFunction

Die Variable my_ajaxurl kriegt ihren Wert über wp_localize_script, das geht in einem Aufwasch mit dem Einbinden der externen Javascript-Datei, dazu stellt man folgenden Code an den Anfang des Plugins:

/*********Externes Script einbinden und Pfad zur admin-ajax.php übergeben*/
function mysite_js() {
      
    wp_enqueue_script('mysite-js', plugins_url().'/wp_ajax/wp_ajax.js', array('jquery'), false, false);
    
    wp_localize_script( 'mysite-js', 'my_ajaxurl', admin_url( 'admin-ajax.php' ) );
 
   }
add_action('wp_enqueue_scripts', 'mysite_js');

Dann kann das JS aus der Plugindatei raus, das sorgt für mehr Übersicht und ist wesentlich eleganter gelöst.

Was ich jetzt noch gern hätte

Wenn man einen Rezepttitel angewählt und auf Return gedrückt hat, möchte ich gerne das gewählte Rezept anzeigen lassen. Aber dazu muss ich mir noch ein, zwei Gedanken machen, meine bisherige Lösung mit der angehängten Rezept-ID ist nämlich eine ziemliche Krücke, das müsste eleganter gehen. Ich geh mal forschen…. und dafür gibts dann einen neuen Beitrag.

Ihre Meinung interessiert mich!

Wie hat Ihnen dieser Artikel gefallen?

sehr gutgutbefriedigendausreichendmangelhaftungenügend

WordPress Autocomplete AJAX deutsch – Übersetzung des Tutorials von David Nash

Übersetzung des Tutorials von David Nash

Wie ich im vorigen Artikel schon erwähnt hatte, hat David Nash in seinem Blog ein tolles Tutorial über Autocomplete (Auto-Vervollständigen) mit Hilfe von AJAX in WordPress geschrieben. Ich habe von David die Erlaubnis, es zu übersetzen und auf dieser Seite zur Verfügung zu stellen. Das mache ich doch mit dem grössten Vergnügen – viel Spaß beim Nachbauen, und viel Erfolg!

Einleitung

Dies wurde für ein Projekt mit einer umfangreichen Datenbank realisiert, mit ca. 14.000 eingetragenen Geschäftsadressen. Es gibt eine Anmeldungsseite, auf der die Benutzer auswählen können, für welche Firma sie arbeiten. 14.000 sind viel zu viele Optionen für ein SELECT (drop-down) Feld, und ich wollte auch nicht so viel Last auf der MySQL Datenbank erzeugen.

Meine Lösung: ein Autocomplete-Textfeld, das die WordPress-Datenbank ausliest.

Ich gehe schrittweise vor, und stelle sicher dass jeder Schritt korrekt läuft, ehe ich den nächsten beginne. Hier ist das schrittweise Vorgehen:

Front-End JavaScript

Wir benutzen ein Child-Theme, weil wir ein Premium-Theme anpassen. In die functions.php kommt folgender Code, um die JavaScript-Datei zu laden:

Wir legen im Child-Theme Verzeichnis die Datei /js/mysite.js an, die sieht so aus:

Wenn man jetzt irgendeine Seite auf der Site neu lädt, kommt „jQuery loaded“ in einer JavaScript-Alertbox, das funktioniert also. Man hätte auch console.log(‘hello’) benutzen und die Developer Console in Chrome überpüfen können.

Als nächstes habe ich mir jQuery-autocomplete heruntergeladen. Ich hätte auch das jQuery-UI autocomplete benutzen können, aber ich benutze es sonst nicht und belasse die Sache lieber leichtgewichtig.

Aus dem Download kopiere ich mir die Dateien jquery.auto-complete.css und jquery-autocomplete.js nach /js/ – das selbe Unterverzeichnis, in dem mysite.js liegt.

Dann wird die functions.php angepasst um die Bibliothek zu laden:

function mysite_js() {

wp_enqueue_script('autocomplete', get_stylesheet_directory_uri().'/js/jquery.auto-complete.js', array('jquery')); 

wp_enqueue_script('mysite-js', get_stylesheet_directory_uri().'/js/mysite.js', array('jquery', 'autocomplete')); 

wp_enqueue_style('autocomplete.css', get_stylesheet_directory_uri().'/js/jquery.auto-complete.css');
}

add_action('wp_enqueue_scripts', 'mysite_js');

Jetzt die Seite neu laden, und sicherstellen dass die Library in der Chrome Developer Console unter dem „Sources“-Tab auftaucht.

Der AJAX Code

Als nächstes wird die mysite.js upgedatet, um etwas sinnvolles zu tun:

jQuery(document).ready(function($) { 

$('#company_works_at').autoComplete({
source: function(name, response) {

$.ajax({
type: 'POST',
dataType: 'json',
url: '/wp-admin/admin-ajax.php',
data: 'action=get_listing_names&name='+name,
success: function(data) {

response(data);

}

});

}
});
});

Dabei ist #company_works_at das Textfeld auf der Registrierungsseite, das den Autocomplete bekommen soll. Ich bin dabei der Dokumentation auf der Autocomplete-Seite gefolgt – die Source-Funktion braucht ein „response“-Callback um zu funktionieren, dies ist die „success“-Funktion im Aufruf von jQuery $.ajax().

Das PHP, das die AJAX Daten von WordPress schickt

In der functions.php kommt Folgendes hinzu:

//get listings for 'works at' on submit listing page

add_action('wp_ajax_nopriv_get_listing_names', 'ajax_listings');
add_action('wp_ajax_get_listing_names', 'ajax_listings');

function ajax_listings() {

global $wpdb; //get access to the WordPress database object variable

//get names of all businesses

$name = $wpdb->esc_like(stripslashes($_POST['name'])).'%'; //escape for use in LIKE statement

$sql = "select post_title 

 from $wpdb->posts 

 where post_title like %s 

 and post_type='job_listing' and post_status='publish'"; 

$sql = $wpdb->prepare($sql, $name);

$results = $wpdb->get_results($sql); 

//copy the business titles to a simple array

$titles = array();

foreach( $results as $r )

$titles[] = addslashes($r->post_title); 

echo json_encode($titles); //encode into JSON format and output 

die(); //stop "0" from being output

}

Das JavaScript, das den AJAX Call absetzt schickt die Daten in „name“. Ich benutze stripslashes() damit Namen mit Apostrophen etc. auch funtionieren. Ich benutze esc_like() aus Sicherheitsgründen. Das ‚%‘ am Ende bewirkt, dass nur auf die führenden Buchstaben gematcht wird. Wenn zum Beipsiel ein Benutzer „The“ eingibt möchte ich „The Best Business“ sehen, und nicht „Not the best business“. Aber wenn „Not“ einegegeben wird, möchte ich „Not the best business“ sehen.

Update: Wenn man alle Posts sehen will, das „and post_type=‚job_listing'“ aus dem $sql oben entfernen.

Testen, ob es funktioniert

Ich klicke auf das Autocomplete-Feld, tippe drei Buchstaben und bekomme eine ziemlich rasche Reaktion. Es schlägt mehrere Firmen vor, aus denen der Benutzer auswählen kann.

Dies ist nur ein Textfeld, und es erlaubt es dem Benutzer auch einen Namen einer Firma einzugeben, die nicht in der Datenbank ist. Wenn man möchte könnte man mehr Restriktionen anlegen, und statt dem Text auch die ID der Firma speichern. Man könnte dann auch in einem anderen Template einen Link auf die Firma einfügen.

Das war’s – so sollte es es klappen

Soweit das Tutorial von David Nash. Hier noch einmal der Link zu seinem Blog. Es lohnt sich, dort auch einmal durch die Kommentare durchzuschauen, da sind noch viele wertvolle Tipps drin.

 

 

Ihre Meinung interessiert mich!

Wie hat Ihnen dieser Artikel gefallen?

sehr gutgutbefriedigendausreichendmangelhaftungenügend

Autocomplete mit AJAX in Joomla

Was mit WordPress und mit Drupal geht, das sollte sich doch auch mit Joomla hinkriegen lassen: eine Artikelsuche mit Autovervollständigen per AJAX. Ich habe dafür mal die Joomla 3 Standard-SampleSite genommen, die kann man bei der Installation als Option anwählen und sie hat schön viel Content.

Mein Arbeitspferd: die PHP-Bridge

Die geniale PHP-Bridge von Henry Schorradt ist wie immer mein Mittel der Wahl, wenn es darum geht Joomla eigenen PHP-Code unterzuschieben. Erster Schritt also: einen eigenen Menüeintrag erstellen, bei Modulzuweisung die PHP-Bridge auswählen und entsprechend anpassen. Doku hierzu im obigen Link auf Henrys Seite.

Was wir alles brauchen:

  • das Formular für die Texteingabe
  • das JavaScript für das Handling des AJAX-Requests mit den aus dem Formular übergebenen Daten
  • die PHP-Datei, die uns die Ergebnisse des Calls zurückliefert

Das Formular und das JavaScript stecken wir beide in die PHP-Datei, die mit unserer PHP-Bridge verknüpft ist. Das Formular steht dann innerhalb der Switch und sieht zum Beispiel so aus:

<?php
//$mode = Scripthandler

switch($mode){ case 'autocom':

echo     "<h1>Beispiel Artikelsuche mit AJAX in Joomla</h1>";
echo "<form action='#' method='post'>";
echo "<input type='text' size='80' name = 'ausgesucht' value='' autocomplete='off'
 onkeyup='rezepte_suche(this.value)' list = 'suchliste'>";

echo "<datalist id='suchliste'>";
 
 echo "<div id =liste>";
 echo "</div>";

 echo "</datalist>";

echo "</form>";

break;}//switch
?>

Eigentlich ein ganz normales Formular mit einem Eingabefeld vom Typ Text. Aber hier passieren noch vier eminent wichtige Dinge:

  1. onkeyup = ruft die js-Funktion rezepte_suche() auf, die steckt in der rezepte_suche.js, die wir nachher noch laden. Die Funktion kriegt als Parameter this.value mit, also den aktuellen Wert des Textfeldes. Onkeyup feuert, wenn der Benutzer eine Taste gedrückt und wieder losgelassen hat.
  2. Der Parameter list = ‚suchliste‚ des Textfeldes definiert, aus welchem HTML-Element die Datenliste für das Dropdownfeld genommen werden soll, nämlich aus der Datalist mit der id = „suchliste“
  3. Innerhalb der Datalist steckt eine Div mit der id= ‚liste‘, in die schreibt nachher unser AJAX-Call sein Ergebnis, dazu gleich mehr.
  4. Wichtige Ergänzung: das Texteingabefeld sollte unbedingt den Parameter autocomplete = ‚off‘ mitkriegen,  sonst pfuscht der Browser mit Einträgen hinein, die er aus anderen Formularen gecached hat.

Das Formular sollte jetzt in etwa so aussehen:

joomla_form

joomla_form

Das JavaScript

…packen wir mit in die PHP-Datei der Bridge, das kommt vor den Switch und sieht so aus:

<script type="text/javascript">


function rezepte_suche(inhalt)
{
 if (inhalt=="")
 {
  //Hinweis wenn der Feldinhalt leer ist;
  //document.getElementById("ausgabe").innerHTML="keine Eingabe";
  return;
 }
 if (window.XMLHttpRequest)
 {
  // AJAX nutzen mit IE7+, Chrome, Firefox, Safari, Opera
  xmlhttp=new XMLHttpRequest();
 }
 else
 {
  // AJAX mit IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
 }
 xmlhttp.onreadystatechange=function()
 {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
  {
   
   document.getElementById("liste").innerHTML=xmlhttp.responseText;
  
  }
 }

 xmlhttp.open("GET","http://localhost/joomla30/templates/protostar/php/rezeptesuche.php?q="+inhalt,true);
 xmlhttp.send();
}
</script>

Die ist nicht auf meinem Mist gewachsen, das ist eine Standardprozedur für einen AJAX-Call die ich mir zusammengegooglet und nach meinen Bedürfnissen angepaßt habe. Sie enthält genau eine Funktion, nämlich die rezepte_suche mit dem Parameter inhalt, in dem steckt der vom Benutzer eingegebene Inhalt des Textfeldes aus dem Formular.

Was passiert hier? Zuerst wird abgefragt, ob inhalt leer ist und in dem Fall nur ein return ausgelöst.

Wenn inhalt nicht leer ist, greifen zwei IF-Abfragen, die Browserabhängig die Variable xmlhttp unterschiedlich belegen. Wir schauen mal nur den ersten Zweig an:

xmlhttp=new XMLHttpRequest();

Hier wird ein neues Objekt vom Typ XMLHttpRequest angelegt, das wird im Folgenden weiterverarbeitet. Wer dazu Genaueres wissen möchte dem sei diese Seite der wikibooks empfohlen.

Wenn sich am Status des Formularfeldes etwas ändert, feuert folgende Funktion:

xmlhttp.onreadystatechange=function()
 {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
  {
   
   document.getElementById("liste").innerHTML=xmlhttp.responseText;
  }
 }

Die div mit der id liste kriegt einen neuen Inhalt, nämlich den Rückgabewert des Requests. Wir der aussieht, wird in unserer PHP-Datei geregelt, dazu gleich mehr.

Wichtig ist jetzt noch der open-Aufruf:

 xmlhttp.open("GET","http://localhost/joomla30/templates/protostar/php/rezeptesuche.php?q="+inhalt,true);

Ich hab hier den Pfad zur PHP-Datei  fest verdrahtet, das läßt sich sicher auch eleganter lösen, aber für Testzwecke tuts das. Die PHP-Datei kriegt als Parameter unseren inhalt mit, das ist immer noch der Inhalt des Formularfeldes so wie ihn der Benutzer eingegeben hat. Fehlt nur noch der Auslöser:

xmlhttp.send();

Und Schuß! Das setzt den Request ab.

Die aufgrufene PHP-Datei

Die benimmt sich nicht anders, als wenn sie von einem Formular aus aufgerufen würde, und sie übernimmt den Parameter inhalt in der Variablen q. Ansonsten ist sie recht übersichtlich:

<?php

$rezept = '';
if ( isset($_GET['q']) )
{
  $rezept = $_GET['q'];
}

$pdo = new PDO('mysql:host=localhost;dbname=joomla', '*******', '******');

//Suche nach enthaltenem String %suchstring%
$sql = "SELECT * FROM rrzol_content where title like '%".$rezept."%' order by title";
$records = $pdo->query($sql);

foreach ($records as $row) {
        
        echo "<option>".$row['title']." #".$row['id']."</option><br>";
        
    }


?>

Ich hole mir erstmal den Inhalt von q auf die Variable $rezept. Dann verbinde ich mich mit der Joomla-Datenbank und hole mir aus der content-Tabelle mit dem Select alle Datensätze die den Inhalt von $rezept im Titel enthalten. Innerhalb der Foreach-Schleife wirds dann nochmal interessant, ich klemme vor und nach dem Titel des Beitrags die <option>-Tags dran, die brauchen wir nachher im Formular für die Datalist. Ausserdem nehme ich noch die id mit, die brauchen wir dann für die Ausgabe des Beitrags. Das wars dann aber auch schon.

Das Formular ist jetzt scharfgeschaltet

Wenn der Benutzer jetzt zum Beispiel con in das Textfeld eingibt, erhält er eine Liste aller Beitragstitel, in denen der String con vorkommt:

auswahl_con

auswahl_con

Jetzt muss nur noch etwas passieren, wenn er einen der Einträge auswählt und auf Return drückt. Ich hab das so gelöst: in der PHP Bridge kommt nach dem Formular noch folgender Code:

if (isset($_POST['ausgesucht'])){
    
    $suchtitel = substr($_POST['ausgesucht'], 0, strpos($_POST['ausgesucht'], '#', 0));
    //echo $suchtitel;
    echo "Vielen Dank! Ihre Auswahl: <h2>".$suchtitel."</h2><br>";

    $text = strstr($_POST['ausgesucht'], '#');
    $text=substr($text,1);
        
    $url = JRoute::_('index.php?option=com_content&view=article&id='.$text);
    
    echo "<h2><a href = '".$url."'>Link zum Artikel</a></h2>";
    
    
} //ende von if isset

Ich zerlege zunächst einmal die Eingabe aus dem Textfeld und klemme für die Ausgabe des Titels alles vor dem # ab. Dann hole ich mir die Zahl nach dem #, das ist die ID des Beitrags. Zu dem hole ich mir mit dem JRoute::… die URL, aus der bastle ich mir den Link zum Beitrag. Fertig!

contacts

contacts

Das Ganze hat natürlich noch Optimierungspotential, zum Beispiel hält niemand den Benutzer davon ab, einfach irgendeinen String einzugeben und dann auf Return zu drücken, dann läuft natürlich der Link auf einen häßlichen Fehler. Aber das sind Details, die man noch verfeinern kann wenn man möchte, das sollte nicht allzu schwierig sein. Viel Spaß jedenfalls beim Nachbauen!

Ihre Meinung interessiert mich!

Wie hat Ihnen dieser Artikel gefallen?

sehr gutgutbefriedigendausreichendmangelhaftungenügend

WordPress und AJAX für Autocomplete – eine erste Annäherung

Einleitung

Ich hab mich kürzlich in einem Projekt mal ein bisschen mit den Grundlagen von AJAX beschäftigt, und finde es bietet wirklich faszinierende und in der Praxis gut anwendbare Möglichkeiten. Besonders g’wandt ist die Möglichkeit, zum Suchen bestimmter Datenbankeinträge ein Autocomplete oder Auto-Vervollständigen einzubauen. Der Benutzer tippt ein paar Buchstaben (in der Regel drei) in ein Textfeld ein, und man holt via AJAX die gematchten Einträge aus der Datenbank. Das, so hab ich mir gedacht, hätte ich gerne für eine komfortable Suche nach Rezepten im Inselfisch-Kochbuch, das sind nämlich mittlerweile über dreihundert und somit zuviel für ein Dropdown-Feld. Das könnte in etwa so aussehen:

form_leer

form_leer

Man stellt ein kleines Formular zur Verfügung, in das der Benutzer Text eingeben kann. Sobald in der Datenbank passende Einträge gefunden werden, werden diese als Auswahlliste angezeigt. Ich geb mal sal ein, hier ist das Ergebnis:

suche_sal

suche_sal

Das ist doch schon mal ganz schick, oder? Und nicht über die Nummern (#152) wundern, die brauchen wir später noch. Wenn der Benutzer einen Eintrag ausgewählt hat und auf Return drückt, soll dann das Rezept angezeigt werden, aber bis dahin ist es noch ein Stückchen weit. WordPress stellt nämlich eine ganz eigene Logik für die Verwendung von AJAX zur Verfügung, und die ist ein bisschen widerborstig. Aber professionelle Hilfe ist geboten:

Super Tutorial zum Thema

…von David Nash: https://davidnash.com.au/create-an-auto-complete-field-in-wordpress/

David erklärt wirklich Step by Step und fantastisch nachvollziehbar, auf was es ankommt. Das kann sich wirklich jeder selber reinziehen, das funktioniert direkt auf Anhieb.

Benötigtes Helferlein: jquery autocomplete

Kann man sich hier herunterladen: https://goodies.pixabay.com/jquery/auto-complete/demo.html

Man braucht nur die beiden Dateien jquery.auto-complete.css und jquery-autocomplete.js.

Kleine Ergänzungen zu Davids Tutorial

Das Eingabeformular

Um das Ganze auch ausprobieren zu können, braucht man natürlich ein Eingabeformular. Ich hab eins als Shortcode angelegt, da sieht in meiner functions.php so aus:

 function auto_form(){
    
    echo "<h3>Beispiel Rezeptsuche Autocomplete</h3>";
    
    echo "<form action='#' method='post'>";
    echo "<input type='text' size='40' name = 'auswahl' id = 'rezepte_auswahl'>";
    
    echo "</form>";
    
        
    if (isset($_POST['auswahl'])){
    echo "Vielen Dank! Ihre Auswahl :".$_POST['auswahl']."<br>";
    
   
    } //end if isset
 }
 add_shortcode('a_form', 'auto_form');

Wichtig ist hier die id des Eingabefeldes, die wird dann in unserer js-Datei referenziert:

jQuery(document).ready(function($) {    
    
    $('#rezepte_auswahl').autoComplete({
        source: function(name, response) {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                url: '/wordpress/wp-admin/admin-ajax.php',
                data: 'action=get_listing_names&name='+name,
                success: function(data) {
                    response(data);
                }
            });
        }
    });
 
});

Wichtig ist hier auch die URL der admin-ajax.php, die muss man gegebenenfalls anpassen.

Nachtrag: ich hab mir eine andere Lösung ergooglet

Man kann mithilfe der WordPress-Funktion wp_localize_script() den Pfad zum Admin-Verzeichnis als Variable an das Script übergeben. Dazu ergänzt man die function mysite_js() wie folgt:

function mysite_js() {
    wp_enqueue_script('autocomplete', get_stylesheet_directory_uri().'/js/jquery.auto-complete.js', array('jquery'), false, false);
    
    wp_enqueue_script('mysite-js', get_stylesheet_directory_uri().'/js/mysite.js', array('jquery', 'autocomplete'), false, false);
    
    wp_localize_script( 'mysite-js', 'my_ajaxurl', admin_url( 'admin-ajax.php' ) );
 
    wp_enqueue_style('autocomplete.css', get_stylesheet_directory_uri().'/js/jquery.auto-complete.css');
 
}
add_action('wp_enqueue_scripts', 'mysite_js');

Dann kann man im Script mysite.js für die URL einfach die Variable my_ajaxurl einsetzen:

jQuery(document).ready(function($) {    
    
    $('#rezepte_auswahl').autoComplete({
        source: function(name, response) {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                url: my_ajaxurl,
                //url: '/wordpress/wp-admin/admin-ajax.php',
                data: 'action=get_listing_names&name='+name,
                success: function(data) {
                    response(data);
                }
            });
        }
    });
 
});

Das aber nur als kleiner Tipp am Rande.

Ich hole mir noch die Post-ID

Dazu passe ich die Funktion ajax_listings() ein bisschen an:

function ajax_listings() {
    global $wpdb; //get access to the WordPress database object variable
 
    //get names of all businesses
    $name = $wpdb->esc_like(stripslashes($_POST['name'])).'%'; //escape for use in LIKE statement
    
    $sql = "select post_title, ID 
        from $wpdb->posts 
        where post_title like %s 
        and post_type='post' and post_status='publish'";
 
    $sql = $wpdb->prepare($sql, $name);
    
    $results = $wpdb->get_results($sql);
 
    //copy the business titles to a simple array
    $titles = array();
    foreach( $results as $r )
        $titles[] = addslashes($r->post_title." #".$r->ID);
        
    echo json_encode($titles); //encode into JSON format and output
    
    die(); //stop "0" from being output
}

In den Select kommt noch die ID mit rein, und die hänge ich in der Foreach-Schleife auch noch mit an die Ausgabe dran, mit einem # vorneweg.

Extrahieren der ID im Formular

Wenn der Benutzer einen Listeneintrag ausgewählt und auf Return gedrückt hat, soll ja etwas passieren: nämlich das gewählte Rezept ausgegeben werden. Das „Return-Drücken“ erwischt man mit einem If (ISSET…), dann zerlege ich mir die entsprechende Variable und hole mir nur die Zahl nach dem #.

function auto_form(){
    echo "<a name='form'></a>"; 
    echo "<h3>Beispiel Rezeptsuche Autocomplete</h3>";
    
    echo "<form action='#form' method='post'>";
    echo "<input type='text' size='40' name = 'auswahl' id = 'rezepte_auswahl'>";
   
    echo "</form>";
    
        
    if (isset($_POST['auswahl'])){
    echo "Vielen Dank! Ihre Auswahl :".$_POST['auswahl']."<br>";
    
    //***********Action
    $text = strstr($_POST['auswahl'], '#');
    $text=substr($text,1); 
    echo "Ich bin die Nummer ".$text;
    ///*****end Action
    } //end if isset
 }

Jetzt liegt die ID des gewählten Rezeptes auf der Variablen $text, mit der arbeiten wir weiter und geben einfach den post_content aus:

global $wpdb;
    $sql = "select post_content, ID 
        from $wpdb->posts 
        where ID like ".$text." 
        ";
    
    $results = $wpdb->get_results($sql);
    
    foreach ($results as $r){echo $r->post_content;}

Damit wird das Rezept angezeigt, sobald der Benutzer seine Auswahl getroffen und auf Return gedrückt hat:

salatbowle

salatbowle

Man könnte natürlich auch einen Link zum Rezept einbauen, aber ich lasse es mal so, dann muss der Benutzer nicht woanders hin wechseln, wenn er noch ein Rezept suchen möchte.

Man könnte auch noch die Suche ein bisschen in der Funktionalität verändern, wenn man zum Beispiel nicht nur nach Einträgen suchen möchte, die mit der eingegebenen Zeichenfolge beginnen, sondern alle, in denen die Zeichenfolge enthalten ist. Dann kann man in der function ajax_listings() die Suchvariable einfach anpassen und die Wildcard % noch vornedran setzen:

$name = '%'.$wpdb->esc_like(stripslashes($_POST['name'])).'%';

Das findet z.B. alle Einträge mit salat im Titel, auch nicht unpraktisch:

instring_salat

instring_salat

Alles in allem: eine superpraktische Funktionalität, und dank des grossartigen Tutorials von David Nash einfach umzusetzen. Gefällt mir ausserordentlich gut!

Kleiner Tipp am Ende:

Wenn es zwischendurch so aussieht, als ob WordPress die eigentlich geladenen js-Scripts nicht mehr kennt – Browsercache löschen!

 

Ihre Meinung interessiert mich!

Wie hat Ihnen dieser Artikel gefallen?

sehr gutgutbefriedigendausreichendmangelhaftungenügend