Archiv der Kategorie: Module

Drupal Bewertungsformular mit AJAX

Was in WordPress so schön funktioniert hat, sollte doch auch in Drupal zu machen sein: ein Bewertungsformular, das sich beim Abschicken einer neuen Bewertung selbst updated, eine genaue Beschreibung dazu findet man in diesem Artikel im Blog zum schwarzen Pinguin. Man kann Noten von sehr gut bis ungenügend vergeben, und es gibt einen Button zum Abschicken der Bewertung. Dazu werden die evtl. bereits vorhandenen Bewertungen und die Durchschnittsnote angezeigt, aussehen tut das Ganze so:

bewertungformular1
bewertungformular1

Voraussetzungen in Drupal

Ich werde dieses Formular nur für einen ausgesuchten Inhaltstypen anlegen, ich nehm mal die Artikel. Dazu brauchen wir einen Template Override – das hört sich schlimmer an als es ist.

Um die Bildschirmanzeige für alle Inhalte vom Typ Artikel zu ändern, klemmt man sich aus dem Verzeichnis ../themes/[dein theme]/templates die node.tpl.php und macht eine Kopie davon. Diese benennt man um in node–article.tpl.php (doppelter Bindestrich!). Dann sucht man sich die richtige Stelle nach dem Content aus, das ist in meinem Fall nach dem Block:

<div class="content clearfix"<?php print $content_attributes; ?>>
<?php
// We hide the comments and links now so that we can render them later.
hide($content['comments']);
hide($content['links']);
print render($content);
?>
</div>

Da ich das Formular nur in der Einzelansicht anzeigen möchte, füge ich hier eine if-Abfrage ein, die sieht erstmal so aus:

<?php
if ($view_mode == 'full'){

echo "<h1>Hier kommt das Formular hin</h1>";
}
?>

Cache leeren nicht vergessen, und nachgucken ob der Text an der richtigen Stelle angezeigt wird. Dann kann man hier den Funktionsaufruf reinpacken:

if ($view_mode == 'full'){

el_bew_ajax();

}

Ein eigenes Modul für das Formular

Das Formular selber packe ich in ein selbstgebautes Modul, zur Erstellung eines einfachen Moduls in Drupal kann man in diesem Artikel nachschlagen. Zuerst kommt mal das HTML für die Anzeige des Formulars rein, das wird in eine Funktion gesteckt:

function el_bew_ajax(){

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>";


}//ende function el_bew_ajax

Das habe ich 1:1 aus der oben zitierten WordPress-Lösung kopiert, funktioniert hier genauso. Das Javascript wird bei Klick auf den ‚absenden‘-Button aufgerufen, dazu gleich mehr.

Eine Tabelle für die Bewertungen

Auch diese sieht genau wie in der WordPress-Lösung aus und enthält drei Felder, eine Autowert-ID, ein Int für die nid und ein Int für die Note:

drupal_bewertungen
drupal_bewertungen

Die Logik für die Anzeige der bereits vorhandenen Bewertungen

Diese musste ich nur ein wenig auf Drupal-Verhältnisse anpassen, die Datenbankabfrage funktioniert hier mit db_query. Die aktuelle Beitragsid ist ein bisschen sperrig abzufragen, da suche ich noch eine hübschere Lösung:

//nid des aktuellen Beitrags bestimmen und in span schreiben
$nid = 0;
if (arg(0) == 'node' && is_numeric(arg(1))) {
$nid = arg(1);
}
echo 'NID dieses Beitrags: <span id = "beitragsid">'.$nid.'</span>';

//******aktuelle Benotungen abholen und Durchschnitt berechnen


$result = db_query('SELECT * FROM {bewertungen} WHERE nid = '.$nid.'');
$gefunden = $result->rowCount();


$durchschnitt = 0;
foreach ($result as $record) {
$durchschnitt = $durchschnitt + $record->note;

}
if ($gefunden != 0){ 
$durchschnitt = $durchschnitt / $gefunden;
}
echo "<span id = 'ausgabe'>".$gefunden." Bewertungen gefunden, Durchschnitt: ".$durchschnitt."<span>";

Das Javascript

…wird  am Anfang der .module-Datei eingebunden, und zwar so:

function ajax_el_init() { 

drupal_add_js(drupal_get_path('module', 'ajax_el') . '/test_ajax_el.js');

}

Die js-Datei liegt bei mir einfach im selben Verzeichnis wie das Modul. Sie sieht wird in einen Wrapper eingebunden, damit man $ für jQuery verwenden kann:

(function ($) {
function el_bew_ajax(){

//Hier kommt der Code

}//end function el_bew_ajax
})(jQuery);

Die ganze Funktion für den Ajax-Call sieht folgendermassen aus:

(function ($) {
function el_bew_ajax(){


//Beitragsid aus span holen
var akt_id = document.getElementById("beitragsid").innerHTML;

//Note aus den Radiobuttons holen
var akt_note = $("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;

}
}
xmlhttp.open("GET","../sites/all/modules/ajax_el/callback_ajax_el.php?id="+akt_id+"&note="+akt_note,true);

xmlhttp.send();



//**********************end ajax call
}//end function el_bew_ajax
})(jQuery);

Ich hole mir am Anfang die nid des Beitrags und die aktuelle Note aus dem Formular und gebe eine Meldung aus falls keine Benotung angewählt wurde.

Die Funktionalität für den Ajax-Call ist die selbe wie die, die wir schon für den Ajax-Autocomplete in diesem Beitrag verwendet haben, das drösel ich hier jetzt nicht nochmal auf.  Der einzige Unterschied ist, dass hier am Ende zwei Parameter statt nur einem an die Callbackfunktion übergeben werden.

Die Callback-Funktion

ist auch recht einfach. Ich hole mir die zwei Werte für die Note und die nid des Beitrags, mache einen Insert auf die Tabelle bewertungen und berechne den neuen Durchschnitt sowie die Anzahl der Bewertungen. Zurückgegeben werden die neuen Werte, die kriegt unser Script als  response und schreibt sie in die div ‚ausgabe‘ im Formular.

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

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

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

//Suche nach aktueller nid
$sql = "SELECT * FROM bewertungen where nid = ".$nid."";
$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 war schon der ganze Zauber! Man kann jetzt noch die Durchschnittsnoten runden und per switch case die entsprechenden Bezeichnungen vergeben (1=sehr gut… 6=ungenügend), aber das ist Feintuning, das spare ich mir jetzt.

Aussehen tut das Ganze jetzt so:

bewertungsformular2
bewertungsformular2

Tipp: damit man sich beim Testen nicht schwarz ärgert wenn was nicht gleich funktioniert: Cache leeren nach jeder Script-Änderung!

Rezeptsuche mit AJAX Autocomplete 2: das Javascript und die PHP-Datei

Das Javascript

Wir haben ja im vorigen Beitrag unsere Javascriptdatei rezepte_suche.js geladen, jetzt stelle ich sie hier mal in aller Kürze vor. 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:

function rezepte_suche(inhalt)
{
 if (inhalt=="")
 {
  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/drumenus/sites/all/modules/autocom/rezeptesuche.php?q="+inhalt,true);

 xmlhttp.send();
}

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/drumenus/sites/all/modules/autocom/rezeptesuche.php?q="+inhalt,true);

Ich hab hier den Pfad zur PHP-Datei noch 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=drumenus', '*****', '*****');

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

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

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

Das Formular ist jetzt scharfgeschaltet

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

salat
salat

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 autocom.module kommt nach dem Formular noch folgender Code:

if (isset($_POST['ausgesucht'])){
    
    echo "Vielen Dank! Ihre Auswahl: <h2>".$_POST['ausgesucht']."</h2><br>";

    $text = strstr($_POST['ausgesucht'], '#');
    $text=substr($text,1); 
    
    $options = array('absolute' => TRUE);
    $nid = $text; // Node ID
    $url = url('node/' . $nid, $options);
    
    echo "<h2><a href = ".$url.">Link zum Rezept</a></h2>";

    } //ende von if isset

Wenn der Benutzer einen Eintrag ausgewählt hat (If (isset…)), zerlege ich mir die Auswahl und hole mir nur die nid nach dem #. Zu der wiederum hole ich mir die URL des betreffenden Nodes, und gebe damit einen Link zum Rezept aus. Das wars! Aussehen tut das Ganze so:

link_zum_rezept
link_zum_rezept

Ich hab hier jetzt mal mit dem Link gearbeitet, man könnte hier natürlich auch mit der nid in die Tabelle field_data_body gehen und sich den body_value ausgeben lassen, das wäre der komplette Text des ausgewählten Rezeptes, aber das ist reine Geschmackssache.

Drupal-Puristen werden mich jetzt wahrscheinlich steinigen, weil man „das so nicht machen kann“, aber der Autocomplete funktioniert jedenfalls ohne allzu grossen Aufwand. Es gibt sicher hier und da noch ein paar Optimierungspotentiale, was man unbedingt noch angehen sollte wäre der fest verdrahtete Pfad zu der PHP-Datei in der rezepte_suche.js, das ist sicher nicht so besonders gut gelöst, aber für Demozwecke tuts das, finde ich. mir hats Spaß gemacht, und das Ergebnis ist ganz ansehnlich.

Rezeptsuche in Drupal mit AJAX Autocomplete

Einleitung

Wahrscheinlich werden da jetzt alle Drupal-Puristen Zetermordio schreien, dass man das so nicht machen kann, aber wenns funktioniert… ich bin da nicht so pingelig.

Das Vorhaben: Suche mit Autocomplete

Ich hab ja über 300 Rezepte in meinem Drupal-Kochbuch, und was in WordPress funktioniert hat, sollte auch in Drupal machbar sein: eine Suche mit Autocomplete bzw. auf Deutsch Auto-Vervollständigung. Das heisst, wenn der Benutzer in das Suchfeld einige Buchstaben eingibt, sollen per AJAX gematchte Einträge als Vorschläge in der Liste auftauchen. Dabei verwende ich die HTML5-Datalist, auch wenn die browserabhängig unterschiedliche Ergebnisse liefert, darüber habe ich mich im letzten Beitrag näher ausgelassen. Macht mir aber jetzt nicht wirklich viel aus, man muss es halt nur wissen.

Wir bauen uns ein Modul

Wir packen die ganze Sache natürlich in ein Modul, und das kriegt einen eigenen Menüeintrag, so gehts am Einfachsten. Zur Modulerstellung in Drupal sag ich jetzt nicht mehr viel, das hatten wir schon ausführlich in diesem Artikel und anderswo.

Das Modul heisst bei mir autocom, dazu brauchts erstmal die autocom.info, die sieht bei mir so aus:

name = Autocom
description = Dieses Modul soll Autocompleten
core = 7.x
package = "MyFunctions"
version = "7.1-1.1"

Und dann brauchen wir natürlich noch die autocom.module, zu der ist etwas mehr zu sagen, in der laden wir uns nämlich erstmal die für AJAX benötigte Javascript-Datei. Das passiert ganz oben im File:

function autocom_init() {  
     
     drupal_add_js(drupal_get_path('module', 'autocom') . '/rezepte_suche.js');
    
    }

Mit der Syntax [modulname]_init wird Drupal angewiesen, diese Anweisung beim Laden des Moduls auszuführen. drupal_add_js() ist für das Laden von Javascript zuständig, das drupal_get_path( ‚module‘, ‚[modulname]‘) holt uns den Pfad zum angegebenen Modul, und an den hängen wir unsere js-Datei mit dran, weil die mit im Modulverzeichnis liegt. Was die Scriptdatei macht, dazu später mehr.

Die Funktion für das Suchformular kommt als nächstes in die autocom.module, die sieht erstmal so aus:

function evi_auto(){
      
echo "<h1>Beispiel Rezeptsuche mit AJAX</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>";
}

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

  1. onkeyup = ruft die js-Funktion rezepte_suche() auf, die steckt in der rezepte_suche.js, die wir vorher geladen haben. 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.

Wenn man das Modul angelegt und aktiviert hat, sollte das Formular jetzt etwa so aussehen:

formular_leer
formular_leer

Es passiert natürlich noch nichts wenn man da etwas eingibt, da uns die js-Funktion und die zugehörige PHP-Datei noch fehlen, aber dazu gibts einen neuen Beitrag.

 

Es geht auch ohne Views: Anzahl Rezepte zu einer Kategorie ausgeben

Ich hab ja redlich versucht, mehr über die Parameterübergabe an Views herauszufinden, aber ganz ehrlich: das war mir alles zu esoterisch, und viel zu kompliziert – man muss ja nicht unbedingt mit Kanonen auf Spatzen schiessen. Ich möchte nach wie vor eine Ausgabe so in der Art:

316 Rezepte insgesamt

111 davon Vegetarisch

Jetzt hab ich einfach die Anforderung ein bisschen geändert: ich lasse den Benutzer auswählen, zu welcher Kategorie er die Anzahl der Rezepte ausgegeben haben möchte. Also dann, auf gehts!

Wie man Drupal eigenen PHP-Code unterschiebt

.. hab ich in diesem Artikel beschrieben. Dafür erstellt man sich ein einfaches Modul, in dem man seine eigenen Funktionen unterbringt. Dann kan man sich zum Beispiel auch einen eigenen Block definieren, in dem man schlicht und ergreifend eine eigene PHP-Funktion aufruft, und den kann man dann positionieren wo man ihn gern haben möchte.

Die Gesamtzahl der Rezepte

Das ist easy, wir selektieren alle Rezepte mit Status1 = veröffentlicht und holen uns die Anzahl mit rowCount:

//Datenbankabfrage für die Gesamtzahl der Rezepte
    $query = db_query("SELECT * FROM node WHERE type like 'rezept' AND status = 1");
    $anzahl = $query->rowCount();
    echo "<h2>".$anzahl." Rezepte insgesamt </h2><br>";

Auswahl der Kategorien über Dropdown-Feld

Dafür basteln wir uns ein Formular mit einer Options-Liste und füttern diese mit einer Datenbankabfrage.

//Datenbankabfrage für das Optionsfeld
    $query = db_query("SELECT * FROM taxonomy_term_data WHERE vid = 2");
    $records = $query->fetchAll();

Ich habe hier die Kategorie Rezepte mit der vid = 2 fest verdrahtet. Das Abfrageergebnis schieben wir so in das Formular rein:

//Beginn Formular    
        echo "<h3>Kategorie auswählen</h3>";

        echo "<form action='#' method='post'>";

        //Beginn des Dropdownmenü
        echo '<select name="Name">';
        
        //hier kommen nun die Werte für die Options rein
        foreach ($records as $record){
               echo '<option>'.$record->name.'</option>';
        }
        echo "</select>";

        echo "<input type='submit' name='absenden' value='Anzahl ausgeben'>";
        echo "</form>";
//End Formular

Am Ende kommt noch ein Submit-Button, und das war, wir haben jetzt unsere Dropdown-Liste mit den Kategorienamen gefüllt.

dropdown_kategorien
dropdown_kategorien

Wenn jetzt der Anwender eine Kategorie ausgewählt hat, muss er noch auf den Button „Anzahl ausgeben“ klicken, dann legen wir los.

auswahl
auswahl

Mit einem if(isset)… fragen wir ab, ob der Submit-Button gedrückt wurde. Dann holen wir uns den Wert aus dem Dropdown-Feld mit $_POST[‚Name‘] und machen zwei kleine Datenbankabfragen. In der ersten holen wir uns die tid zum ausgewählten Kategorienamen, in der zweiten zählen wir, wie oft diese tid mit einem Rezept verknüpft wurde:

if (isset($_POST['absenden'])){
// tid zum Namen der Kategorie holen
    $query = db_query("Select tid from taxonomy_term_data where name like '".$_POST['Name']."'");
    $akt_katid = $query->fetchField();
    
    //Zählen, wieviele Rezepte zur tid vorhanden sind
    $query = db_query("SELECT * FROM taxonomy_index where tid = ".$akt_katid."");
    $anzahl = $query->rowCount();
    
    echo $anzahl." Rezepte zur Kategorie ".$_POST['Name']."<br>";
} //End if isset absenden

Und am Ende geben wir unsere Ergebnisse noch aus.

Wenn man es ganz genau machen wollte, müsste man in die Abfrage für die Anzahl noch einen Join über die nid (Node ID) der Rezepte auf die Tabelle nodes machen und nur alle selektieren, die den Status 1=veröffentlicht haben, aber ich lass es mal so stehen. Meine Ausgabe sieht dann so aus:

111vegetarisch
111vegetarisch

Damit das ganze ein bisschen mehr Nährwert kriegt, legen wir auf den Namen der Kategorie jetzt noch einen Link, der uns dann die Seite der gewählten Kategorie aufruft. Die URL unserer Seite holen wir mit global $base_url; daraus bauen wir dann den Link:

global $base_url;
        
$html_string = '<a href=' . $base_url . '/taxonomy/term/'.$akt_katid.'>'.$_POST['Name'].'</a>';
    echo $html_string;

Das wars jetzt aber, das lass ich so stehen. Ich finde es für einen Block sogar recht gut, weil es nicht so viel Platz wegnimmt wie die View mit allen Kategorien untereinander, das Dropdown-Feld ist da deutlich kompakter.

mit_link_zur_kategorie
mit_link_zur_kategorie

 

Dropdown-Menüs: nur mit dem richtigen Theme

Als Drupal-Neuling stolpert man ganz schnell darüber, dass es erstmal nicht möglich ist, ein Dropdown-Menü einzurichten. Das ist im Standard-Theme Bartik und im ebenfalls mitgelieferten Theme Garland auch gar nicht vorgesehen! Man braucht ein Theme das diese Option von Haus aus mitliefert, eines wäre zum Beispiel Marinelli. Dann aber ist die Sache ganz einfach. Also, installieren sie sich Marinelli und Nice Menus und dann kanns losgehen.

Mega-Menu einschalten

Design->Marinelli->Einstellungen->Primary Menu settings Which kind of primary links do you want to use? auf Mega Drop Down stellen.

Dann ein paar Basic Pages mit Menüeinträgen erstellen, Reihenfolge und Anordnung können über Struktur->Menüs->Main Menu noch eingestellt werden.

untermenues
untermenues

Block positionieren

Den Block Nice Menu 1 auf die Position Utility Menu Top setzen.

nicemenu
nicemenu

Und so sieht das dann aus:

kochbuchmenu
kochbuchmenu

Drupal Formulare mit Webforms

Der angesagte Formulardesigner für Drupal ist wohl Webforms, und ich fand den angenehm unkompliziert, das machen wir jetzt im Schnelldurchgang. Webform benötigt die ctools und Views, aber ohne die beiden ist ohnehin keine Drupal-Seite komplett, die sollten zur Standardausstattung gehören.

Nach der Installation findet sich unter Inhalt hinzufügen der neue Inhaltstyp Webform. Man hat hier zunächst mal die Option, einen neuen Menüeintrag für das Formular  anzulegen. Dann landet man im Fenster Form Components, hier sollte man zuerst mal unter E-Mails eine eigene E-Mail-Adresse angeben, sonst werden keine Mails verschickt.

email_hinzufuegen
email_hinzufuegen

Dann kann man unter Form Components ganz unkompliziert Formularfelder hinzufügen, ich lass es mal ganz schlicht beim Namen und der Nachricht:

zwei_form_felder
zwei_form_felder

Wenn man nichts weiter macht, kommt diese kleine Formular dabei heraus:

nachricht_an_evi
nachricht_an_evi

Man kann jetzt noch unter Form Settings/Confirmatioen Message einen deutschen Text zur Bestätigung eingeben. Was ich nicht gefunden habe: wie man den Linktext „Go back to form“ eindeutscht… Man kann zwar unter Form Settings ->Redirection Location No redirect anwählen, dann wird nach dem Versand die Formularseite neu geladen, aber dafür bekommt man dann eine Drupal-Meldung, die ist auch nicht schöner:

versandt
versandt

Aber das führt mir jetzt ein bisschen zu weit, ich schau ein andermal, ob sich dafür eine schönere Lösung findet.

Formulare in Blocks

Wenn man das Formular in einem Block nutzen will, muss man unter Form Settings->erweiterte Einstellungen „Available as Block“ anhaken, dann taucht das Formular in der Liste der vefügbaren Blocks auf. Ich wüsste jetzt zwar nicht wo ich das nutzen sollte, es sei aber der Vollständigkeit halber erwähnt.

Formulare in Inhalten

Man kann jetzt auch bei den Inhaltstypen unter dem neuen Punkt „Webform“ die Option „Enable webform Functionality“ für den aktuellen Inhaltstyp anwählen, dann kann man beim neu Anlegen eines Inhalts auch gleich ein neues Formular mit anlegen.

Es wird auch mitprotokolliert

Die abgeschickten Formulare werden übrigens in der Datenbank gespeichert, man werfe mal einen Blick auf die Tabelle webform_submitted_data.

Was ich noch nicht gefunden habe: ein Formular automatisch am Ende jedes Beitrags

Wofür ich das brauche? Für mein Bewertungsformular! Ich möchte doch gern am Ende jedes Rezeptes mein kleines Bewertungsformular haben, damit mir mein geneigtes Publikum die Meinung zum Rezept mitteilen kann. Dafür werde ich wahrscheinlich einen Layout-Override brauchen, und dafür gibts einen neuen Beitrag.

 

 

 

Drupal-Nodes programmatisch erzeugen

Node erstellen: ganz basic

Da ich ja nach wie vor auf über 300 Rezepten sitze, die ich nicht per Copy&Paste nach Drupal bringen will, hab ich mal nach Importmöglichkeiten gegooglet, und bin hier bei Group 42 fündig geworden. Der folgende Code erstellt einen Node vom Typ article, das funktioniert schon mal ganz gut. Was nicht so schön ist, ist dass die Sprache auf LANGUAGE_NONE gestellt wird, aber genau genommen stört das auch nicht weiter. Mal sehen, wie weit wir damit kommen. Hier der leicht modifizierte Code von Group 42:

/* Basic Node Creation Example for Drupal 7
        *
        * This example:
        * - Assumes a standard Drupal 7 installation
        * - Does not verify that the field values are correct
        */
        $body_text = '<h1>Text des neuen Nodes.</h1>';

        $node = new stdClass();
        $node->type = 'article';
        node_object_prepare($node);

        $node->title    = 'Node mit NID Evi full html';
        $node->language = LANGUAGE_NONE;

        $node->body[$node->language][0]['value']   = $body_text;
        //$node->body[$node->language][0]['summary'] = text_summary($body_text);
        $node->body[$node->language][0]['format']  = 'full_html';
        $node->promote = 0; //nicht auf der Starseite
        
        // Alias erzeugen, evtl. aus WP übernehmen
        //$path = 'content/programmatically_created_node_' . date('YmdHis');
        //$node->path = array('alias' => $path);

        node_save($node);

        echo "Node erzeugt, ID= ".$node->nid;

Besonders nützlich macht sich hier das:

node_object_prepare()

das nimmt einem schon einen Haufen Arbeit ab, indem es den neuen Node mit etlichen validen Werten vorbelegt, wer will kann das in der Drupal 7 API genauer nachlesen.

Eigentlich muss man jetzt nur noch den body text und den title mit eigenen Inhalten füttern, das könnte klappen. Das Format habe ich auf full_html gestellt, weil ich ja meine Überschriften-Headings aus WordPress übernehmen möchte.

Schön wärs natürlich, wenn man den alias auch gleich vernünftig anlegt, dafür könnte der post_name aus WordPress herhalten, aber das sehen wir dann schon noch, ob das was wird.

Kriegen wir die Kategorien als Tags hier auch mit rein?

Ja, kriegen wir! Und zwar mit folgender Ergänzung:

//id of your taxonomy term
        $tid = 8;

        //add term to a node field
        //field_yourfield_name - machine name of your term reference field

        $node->field_tags[$node->language][0]['tid'] = $tid;

Das macht man für mehrere Tags im Zweifelsfall einfach n mal. Damit sind wir bestens ausgerüstet und machen mal einen kurzen Break, weil ich erst in Access das Mapping von den WordPress-Kategorien auf die Drupal-Terms erstellen und mir eine vernünftige Export-Datei basteln muss.

Inhaltsverzeichnis A-Z mit Links

Ich hatte im ersten Ansatz die Funktion drupal_get_path_alias($node) verwendet, noch g’wandter aber gehts mit url($node). Da die gesamte Inhaltsverzeichnis-Funktion nicht besonders kompliziert ist, hier mal der komplette Code:

function ivz_kompakt(){
    
    $alfa = array(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z);
    foreach ($alfa as $akt_buchstabe){
    
            $buchstabe = $akt_buchstabe;
            
            $anzahl = db_query("SELECT nid, title, type, status from node where title like '".$buchstabe."%' order by title");
            $records = $anzahl->fetchAll();
            $gefunden = count($records);
            
            if ($gefunden == 1) {
            echo "<h2>".strtoupper($buchstabe)." ".$gefunden." Rezept</h2><br>";
            }
            
            if ($gefunden > 1) {
            echo "<h2>".strtoupper($buchstabe)." ".$gefunden." Rezepte</h2><br>";    
            }
            
            if ($gefunden > 0){
                        
                foreach ($records as $record) {
            
                      $akt_id = $record->nid;
                      //echo $record->title."<br>";
                      $url = url('node/' . $akt_id);
                      //echo $url."<br>";
                      echo "<a href = '".$url."'>".$record->title."</a><br>";
                                        
                } //end foreach
            } // end gefunden > 0
    }    // end alfa
    
} //end function ivz_kompakt

Was hab ich gemacht? Ich bastel mir zuerst mal ein Array mit allen Buchstaben des Alfabets. Durch dieses Array steppe ich mit einem foreach durch, und der aktuelle Buchstabe kommt jeweils in unsere Query, und mit count() wird die Anzahl der gefundenen Datnsätze abgefragt:

 $anzahl = db_query("SELECT nid, title, type, status from node where title like '".$buchstabe."%' order by title");
$records = $anzahl->fetchAll();
            $gefunden = count($records);

Dann frage ich mit if ab, ob einer oder mehr als ein Artikel gefunden wurde und gebe dementsprechend aus „1 Rezept“ oder „X Rezepte“.
Wenn ich mehr als 0 Beiträge mit meiner Query gefunden habe, steppe ich durch diese wieder mit einem foreach durch. Ich hole mir mit der nid aus der Query die URL des aktuellen Beitrags:

$akt_id = $record->nid;
$url = url('node/' . $akt_id);

Und bastle mir daraus den Link zum Rezept.

echo "<a href = '".$url."'>".$record->title."</a><br>";

Das wars! Ich hab jetzt mal in der Query noch nicht die Einschränkung auf Where status=1 und type = blog gemacht, weil ich ein bisschen Material zum Ausgeben haben wollte und so einfach alle Nodes alfabetisch ausgebe, die Where-Klausel kann sich jeder selber anpassen. Mein Output sieht ganz sauber so aus:

ivz_kompakt
ivz_kompakt

Schicke Sache, nicht wahr? Das ist 1:1 mein Inhaltsverzeichnis wie im Original-Inselfisch-Kochbuch. Jetzt lege ich mir noch einen neuen Menüpunkt vom Typ einfache Seite an, stelle den Textfilter auf PHP-Code und platziere dort meinen Funktionsaufruf:

<?php ivz_kompakt();?>

Fertig! Also, ich muss sagen, das mit dem Einbinden eigener Funktionen über das simple Modul nach diesem Muster macht einem die Arbeit echt einfach, das gefällt mir sehr gut.

Jetzt wärs natürlich schick, wenn wir mehr Rezepte zum Ausgeben hätten, aber ich hack die nicht per Copy&Paste rein. Ich geh mal suchen, wie es mit den Import-Fähigkeiten von Drupal aussieht, und melde mich dann wieder mit einem neuen Beitrag.

 

Wie man Drupal eigenen PHP-Code unterschiebt

Das ist sehr einfach und nahezu überall möglich, man muss nur unter Module den PHP Filter aktivieren. Dann bekommt man bei den Editoroptionen unter Textformate die Option PHP-Code:

textformat_php_code
textformat_php_code

Und das wars auch schon. Beliebigen Code eingeben, php-Tags nicht vergessen:

<?php echo „Hallo Welt!“; ?>

Produziert die erwartete Ausgabe:

php_hallo_welt
php_hallo_welt

Weder besonders aufregend noch besonders handlich, das ist nicht besser und nicht schlechter als der Sourcerer in Joomla. Für wenige Codezeilen tuts das, für längere Scripte ist es völlig untauglich. Aber ich bin durch fleissiges Googlen auch auf einen eleganteren und doch simplen Weg gekommen, eigene PHP-Funktionen in Drupal einzubinden. Dazu basteln wir uns:

Das erste eigene Modul

Ich habe mich an diese Anleitung hier von BobbyMods gehalten, die Sache aber noch ein bisschen weiter vereinfacht. Um ein eigenes Modul zu erstellen, braucht es nicht viel. Man erzeugt ein neues Unterverzeichnis unter sites->all->modules und benennt es z.B. mit „myfunctionlib“. Da hinein kommen zwei Dateien, eine namens myfunctionlib.info, die enthält folgenden Code:

name = My Function Library
description = This module contains my PHP helper functions.
core = 7.x
package = "MyFunctions"
version = "7.1-1.1"

Dann brauchen wir noch die Datei myfunctionlib.module, die sieht ganz stark vereinfacht so aus:

<?php
/**
* Function Description
* @param function_parameter
* @returns function output description
*/

function sayhello($name){
    echo "Hallo liebe ".$name;
}

Wir haben nur eine Function namens sayhello(), die hat einen Parameter, den Namen. Jetzt mussen wir nur noch unser:

Modul aktivieren

es taucht nämlich unter Module bereits auf:

modul_myfunctions
modul_myfunctions

Wenn das passiert ist, hat man Zugriff auf die eigenen Funktionen, ich kann dann auf einer Seite beispielsweise meine sayhello-Funktion aufrufen (Textformat auf PHP Code stellen nicht vergessen):

<?php sayhello(„Evi Silvia“); ?>

Und der Output ist wie erwartet:

sayhello
sayhello

Das eröffnet natürlich jede Menge Möglichkeiten, ganz ähnlich wie die geniale PHP-Bridge für Joomla. Jetzt können wir, ich schau mal wie weit ich mit meinem schöner formatierten Inhaltsverzeichnis komme, aber dazu gibt es einen neuen Beitrag.