J3.x

J3.x: Entwicklung einer MVC Komponente/Ebenen hinzufügen

From Joomla! Documentation

< J3.x:Developing an MVC Component
This page is a translated version of the page J3.x:Developing an MVC Component/Adding Levels and the translation is 100% complete.

Other languages:
Deutsch • ‎English • ‎español • ‎français
Joomla! 
3.x
Tutorial
Entwicklung einer MVC Komponente


Dies ist eine Artikel-Serie mit Tutorials über die Entwicklung einer Model-View-Controller Komponente für Joomla! VersionJoomla 3.x.

Beginne mit der Einführung und navigiere durch die Artikel dieser Serie mit Hilfe des Buttons am Ende der Seite oder der Box auf der rechten Seite ("Artikel in dieser Serie").



Dieses Tutorial ist ein Teil der Developing an MVC Component for Joomla! 3.2 Artikel-Serie. Es ist sehr sinnvoll die vorherigen Teile des Tutorials zu lesen, bevor du hier fortfährst.

In diesem Schritt fügen wir Ebenen zu unserer hallo Welt Komponenten hinzu, was im Wesentlichen bedeutet, dass wir eine Baumstruktur in unserem hallo Welt Datensatz implementieren.

Du kannst hier ein Begleitvideo sehen: Adding levels.

Einführung

Joomla! führt Menüeinträge und Kategorien als Baumstrukturen. Dass heißt beispielsweise, dass Menüeinträge in einem Menü Untermenüs aus ihnen heraus haben können (wie man im Joomla! Administratormenü sehen kann) und diese Untermenüeinträge wiederum Untermenüs aus ihnen heraus haben können, und so weiter. Die Untermenüs sind geordnet, auf diese Weise kann man die Reihenfolge, in der die Untermenüeinträge erscheinen, festlegen. Ein wenig Fachvokabular:

  1. jeder unserer Einträge ist ein Knotenpunkt innerhalb der Baumstruktur
  2. es gibt einen Rootknotenpunkt, welcher die höchste Position in der Hierarchie einnimmt; innerhalb von Joomla! definieren wir diesen Rootknotenpunkt als sich auf der Ebene 0 befindlich
  3. der Rootknotenpunkt hat eine Anzahl von Kindern, diese werden auf Ebene 1 sein. Der Rootknotenpunkt ist für jeden dieser Kinder das Elternelement.
  4. diese Kinder können wiederum selber Kinder haben, diese dann auf Ebene 2, und so weiter.
Nested Set Model
Node Left Right
Clothing 1 22
Men's 2 9
Suits 3 8
Slacks 4 5
Jackets 6 7
Women's 10 21
Dresses 11 16
Evening Gowns 12 13
Sun Dresses 14 15
Skirts 17 18
Blouses 19 20

Um diese Baumstruktur innerhalb einer relationalen Datenbank umzusetzen, verwendet Joomla! das Nested Set Model. Dieses bezieht die Zuweisung von einem Feld links (in Joomla! lft) und von einem Feld rechts (in Joomla! rgt) von jedem Eintrag ein, wie es auch im Diagramm und der verbundenen Tabelle zu sehen ist. Man sollte in der Lage sein zu sehen, wie die Werte lft und rgt benutzt werden, um für jeden Knoten seine Ebene im Baum, sein Elternelement und für Knoten mit dem selben Elternelement die Reihenfolge dieser Knoten festzulegen.

Während die Verfügbarkeit der lft- und rgt-Werte für das Defnieren eines vollständigen Baums in der Theorie ausreichend sein mag, bieten das Hinzufügen von zusätzlichen Feldern in der Praxis mehr Gelegenheit, im Baum effektiv zu navigieren, und in Joomla! gibt es tatsächlich fünf Felder, dei mit einer Baumstruktur verknüpft sind.

  • lft- und rgt-Felder
  • Eltern ID - die ID vom Elternknoten
  • Ebene - die Ebene in der Hierarchie, Ebene 0 ("Kleidung" im Beispiel oben) ist dabei der höchste Pfad.

Der Pfad ist wie der Pfad in einer Verzeichnisstruktur und ist eine Verbindung der Aliaswerte, die vom Rootknoten bis zum fraglichen Knoten reichen, dabei trennt ein Slash die Aliase. Wenn zum Beispiel, bezogen auf obiges Diagramm, Kleidung den Rootknoten darstellt, wäre der Pfad für Jacken Men-s/Suits/Jackets.

Beachte, wie uns die zusätzlichen Felder mit effizienten Mechanismen zur Navigation im Baum versorgen. Wir können beispielsweise durch den Befehl WHERE in einer Datenbankabfrage diejenigen Einträge wählen, wo das Elternelement suits ist, und durch den Befehl ORDER BY lft sortieren. Das erzeugt die sortierte Ausgabe von Slacks und Jackets. Diese Art von Vorgängen werden üblicherweise im Frontend durchgeführt, wo wir die Webseite perfomant wissen wollen. Im Gegensatz dazu können Administratortätigkeiten viele Updates beinhalten. Wenn wir beispielsweise Blouses als ersten Nachfolger unter Men's bewegen, beinhaltet das ein Update der lft- und rgt-Werte von fast allen Datensätzen der Datenbanktabelle.

Wie du vielleicht schon erwartet hast, bietet Joomla! Bibliotheksfunktionen die helfen einen neuen Knoten im Datenbaum zu positionieren, einen Datensatz erneut einem anderen unterzuordnen, einen Datensatz zu löschen (und alle seine Nachfolger), Datensätze neu anzuordnen, etc.

Funktionsweise

Als ersten Schritt wollen wir die Installation des Suchbaums in unserer hallo Welt Datenbank durchführen.

In der Administratoransicht auf hallo Welt setzen wir die Eintragebene durch das Einrücken des Titels, genau so wie das bei Kategorien und Menüelementen passiert. Außerdem werden wir unsere neuen Feldwerte auf dem Bildschirm ausgeben - wirklich nur zu eigenen Diagnosezwecken, weil man das in einer wirklichen Anwendung nicht machen würde.

Im vorhergehenden Schritt haben wir eine Funktionsweise eingeführt, die es dem Administrator ermöglichte, Einträge durch Verschieben neu anzuordnen. Wir werden diesen Mechanismus weiterhin nutzen, aber das Neuanordnen wird auf Einträge begrenzt, die sich auf derselben Vererbungsebene befinden (dass heißt Geschwister), und die Datensätze sollen zuerst in aufsteigender Reihenfolge geordnet werden (das Sortieren duch "Aufsteigend ordnen" wird nicht funktionieren).

Falls wir einen Eintrag löschen, steigen alle seinen Nachfolger in der Hierarchie auf, deren übergeordnetes Element dem Übergeordneten des Gelöschten entspricht.

In der Administrator Bearbeitungsansicht erlauben wir den Adminstrator:

  • ein neues übergeordnetes Element für den Datensatz zu definieren und
  • die Position von diesem Datensatz innerhalb der Reihenfolge seiner Geschwister anzugeben. (wenngleich, falls wir das übergeordnete Element ändern, wird das neue Geschwister erst nach einem Speichern erscheinen).

Wir werden auch sicherstellen, dass die Funktionen Neu und Speichere als Kopie wie erwartet funktionieren.

Im Frontend werden wir die Seite ändern, die einen hallo Welt Eintrag zeigen, um sowohl das Elternelement als auch die Nachfolger von diesem Eintrag darzustellen.

Wir werden auch das Frontendformular aktualisieren, um einen neuen hallo Welt Eintrag zu erstellen in einer Weise, dass das übergeordnete Element abgefragt wird.

Vorgehensweise

Zunächst ist es notwendig unsere Datenbank hierarchisch aufzusetzen. Wir wollen den Eintrag mit der ID=1 als Baumwurzel festlegen, falls es einen bestehenden Eintrag mit der ID=1 gibt werden wir ihm eine neue Zahl geben, und wir werden jeden damit verbundenen Eintrag in der Datenbestands- und Verbindungentabelle ändern. Nachdem der Wurzeleintrag positioniert ist, setzen wird die anderen hallo Welt-Einträge als direkte Erben der Wurzel fest, und legen die neuen Felder entsprechend fest. Da es zu kompliziert ist, dies in einem SQL-Skript zu bewerkstellingen, programmieren wir das innerhalb der Installations script.php Datei.

Der Kode, um Operationen in Feldern einer hierachisch aufgebauten Datenbank durchzuführen ist innerhalb von JTableNested, also ändern wir unsere hallo Welt Tabellenklasse derart, dass von ihr aus statt von JTable vererbt wird. Wir werden eine Anzahl von Methoden innerhalb dieser Klasse verwenden, um Datenbankänderungen, die durch Administratortätigkeiten entstanden sind, anzupassen:

  • setLocation($referenceId, $position) verwenden, um die Position des derzeitigen Eintrags (zur Sortierung) bezüglich seiner Geschwister zu definieren - wir werden das verwenden, wenn der Administrator das hallo Welt Bearbeitungsformular benutzt und das übergeordnete Element oder die Sortierung des Elements hinsichtlich seiner Geschwister ändert.
  • saveorder($idArray, $lft_array) benutzen um eine geänderte Reihenfolge der Geschwister zu speichern - wir werden dies verwenden, wann immer der Administrator die Eintrag verschieben Funktion benutzt, um diesen zu bewegen.
  • store($updateNulls) anwenden um einen neuen oder aktualisierten Eintrag zu speichern - dies wird durch die save() Prozedur in JModelAdmin aufgerufen.
  • delete($pk, $children) nutzen, um einen Eintrag und (optional) alle seine Nachfolger zu löschen - wir ziehen dies heran wann immer der Administrator einen oder mehr hall Welt Datensätze löscht und wir die vererbten Nachfolger NICHT löschen wollen.
  • rebuild(...) wird verwendet um die lft-, rgt- und Pfadangaben der hallo Welt Datensätze neu zu berechnen - wir setzen dies ein nachdem wir einen Datensatz repositionieren indem wir das übergeordnete Element oder im Bearbeitungsformular die Sortierung verändern.

In unserer hallo Welt Layoutdatei werden die neuen Felder geradlinig dargestellt. Die weiteren Veränderungen beinhalten das Folgende.

  1. Um die Einträge gemäß ihrer Ebene einzurücken (so wie in Menüs und Kategorien) verwenden wir das Joomla! Standardlayout in layouts/joomla/html/treeprefix.php
  2. Damit das Ziehen von Zeilen um diese neu anzuordnen unterstützt wird, verwenden wir den Javascriptcode in sortablelist.js, auf welchen wir im vorhergehenden Schritt Adding Ordering gestoßen sind, erneut. Um allerdings diese verschachtelten Mengen zu übergeben, hängen wir verschiedene Parameter an die Funktion und verschiedene Attribute an das Element an.

In unserem hallo Welt (Bearbeitungs-) Layout, werden die Eingabeelemente, die erfasst werden sollen, dargestellt

  • das übergeordnete Element eines Datensatzes - dies ist eine Liste von all den hallo Welt Datensätzen, allerdings wird der Datensatz selbst (weil ein Datensatz sich nicht selbst als Elternelement haben kann) und dessen vererbte Elemente ausgeschlossen (weil dies eine Schleife im Datenbaum formen würden), und,
  • seine Stellung in Beziehung zu den Geschwistern der Datensätze - zur Ausgabe der Geschwister in einer Reihenfolge.

Wegen der spezifischen Anforderungen an diese Auswahllisten, kann keins der Beiden unter Verwendung eines standardmäßigem Joomla! Formularfeld und XML ausgeführt werden. Wir definieren ein angepasstes Formularfeld für jeden und die Liste von möglichen Werten dynamisch.

Im Frontend benutzen wir eine weitere Funktion für verschachtelte Mengen getTree() um die Abkömmlinge des Datensatzes, der auf dem Bildschirm ausgeben wir, zu finden. Und in dem Frontendformular zur Erstellung eines neuen Datensatzes, verwenden wir das angepasste Administrator Formularfeld, um eine Liste der Datensätze darzustellen um ein Elternelement auszuwählen.

Im vorhergehenden Schritt benutzten wir zur Sortierung das ordering Datenbankfeld, in diesem Schritt werden wir das lft Datenbankfeld verwenden. Damit gibt es eine globale Veränderung innerhalb von sowohl unserem Administrator- als auch Webseitencode.

Datenbank und Installation

Aktualisierte SQL Installationsdatei:

admin/sql/install.mysql.utf8.sql

DROP TABLE IF EXISTS `#__helloworld`;

CREATE TABLE `#__helloworld` (
	`id`       INT(11)     NOT NULL AUTO_INCREMENT,
	`asset_id` INT(10)     NOT NULL DEFAULT '0',
	`created`  DATETIME    NOT NULL DEFAULT '0000-00-00 00:00:00',
	`created_by`  INT(10) UNSIGNED NOT NULL DEFAULT '0',
	`checked_out` INT(10) NOT NULL DEFAULT '0',
	`checked_out_time` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
	`greeting` VARCHAR(25) NOT NULL,
	`alias`  VARCHAR(40)  NOT NULL DEFAULT '',
	`language`  CHAR(7)  NOT NULL DEFAULT '*',
	`parent_id`	int(10)    NOT NULL DEFAULT '1',
	`level`	int(10)    NOT NULL DEFAULT '0',
	`path`	VARCHAR(400)    NOT NULL DEFAULT '',
	`lft`	int(11)    NOT NULL DEFAULT '0',
	`rgt`	int(11)    NOT NULL DEFAULT '0',
	`published` tinyint(4) NOT NULL DEFAULT '1',
	`catid`	    int(11)    NOT NULL DEFAULT '0',
	`params`   VARCHAR(1024) NOT NULL DEFAULT '',
	`image`   VARCHAR(1024) NOT NULL DEFAULT '',
	`latitude` DECIMAL(9,7) NOT NULL DEFAULT 0.0,
	`longitude` DECIMAL(10,7) NOT NULL DEFAULT 0.0,
	PRIMARY KEY (`id`)
)
	ENGINE =MyISAM
	AUTO_INCREMENT =0
	DEFAULT CHARSET =utf8;

CREATE UNIQUE INDEX `aliasindex` ON `#__helloworld` (`alias`, `catid`);

INSERT INTO `#__helloworld` (`greeting`,`alias`,`language`, `parent_id`, `level`, `path`, `lft`, `rgt`) VALUES
('helloworld root','helloworld-root-alias','en-GB', 0, 0, '', 0, 5),
('Hello World!','hello-world','en-GB', 1, 1, 'hello-world', 1, 2),
('Goodbye World!','goodbye-world','en-GB', 1, 1, 'goodbye-world', 3, 4);

Für die Aktualisierung definieren wir die neue Datenbankstruktur in nachstehender Datei ...

/admin/sql/updates/mysql/0.0.26.sql

ALTER TABLE `#__helloworld` DROP COLUMN `ordering`;
ALTER TABLE `#__helloworld` ADD COLUMN `parent_id` INT(10) NOT NULL DEFAULT '1' AFTER `language`;
ALTER TABLE `#__helloworld` ADD COLUMN `level`	int(10)    NOT NULL DEFAULT '0' AFTER `parent_id`;
ALTER TABLE `#__helloworld` ADD COLUMN `path`	varchar(400)    NOT NULL DEFAULT '' AFTER `level`;
ALTER TABLE `#__helloworld` ADD COLUMN `lft`	int(11)    NOT NULL DEFAULT '0' AFTER `path`;
ALTER TABLE `#__helloworld` ADD COLUMN `rgt`	int(11)    NOT NULL DEFAULT '0' AFTER `lft`;
UPDATE `#__helloworld` SET `path` = `alias`;

Aber um den Baum zu erbauen, verwenden wir eine Installationsdatei script.php. Dieser Programmcode bewirkt Folgendes:

  1. es prüft, ob es bereits einen Wurzeldatensatz mit der id=1 gibt. Falls dem so ist, geht es davon aus, dass der Baum bereits aufgebaut wurde und bricht ohne irgendeine Veränderung ab.
  2. falls dem nicht so ist und es gibt einen einfachen Datensatz mit der id=1, verändert es seine ID, welche dann um eins größer ist als die höchste ID in der Datenbanktabelle. Anschließend verändert es jeden verbundenen Datensatz in den Datenbestands- und Verbindungentabelle (wir sahen uns in Adding Associations an, wie der Schlüssel in der Verbindungentabelle unter Verwendung von einem md5 hash erzeugt wird. Falls der hallo Welt Datensatz mit der id=1 diese neue ID ursprünglich hatte, wäre der Schlüssel der Verbindungentabelle, der aus dem md5 hash entstand, verschieden gewesen, das macht tatsächlich aber nichts. Tatsächlich, wenn der md5 hash erzeugt wird, falls die Verbindungen in dem Datenraster in einer verschiedenen Reihenfolge sind, wäre sowieso ein unterschiedlicher Schlüssel erzeugt worden).
  3. folgendes erzeugt den Wurzeldatensatz, mit id=1, und legt die lft- und rgt-Werte dafür fest (basierend auf der Gesamtzahl der Datensätze in der Datenbanktabelle).
  4. aktualisiert die lft- und rgt-Werte aller existierender hallo Welt Datensätze in der Datenbanktabelle.

script.php

<?php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');

/**
 * Script file of HelloWorld component.
 *
 * The name of this class is dependent on the component being installed.
 * The class name should have the component's name, directly followed by
 * the text InstallerScript (ex:. com_helloWorldInstallerScript).
 *
 * This class will be called by Joomla!'s installer, if specified in your component's
 * manifest file, and is used for custom automation actions in its installation process.
 *
 * In order to use this automation script, you should reference it in your component's
 * manifest file as follows:
 * <scriptfile>script.php</scriptfile>
 *
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2015 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
class com_helloWorldInstallerScript
{
    /**
     * This method is called after a component is installed.
     *
     * @param  \stdClass $parent - Parent object calling this method.
     *
     * @return void
     */
    public function install($parent) 
    {
        $parent->getParent()->setRedirectURL('index.php?option=com_helloworld');
    }

    /**
     * This method is called after a component is uninstalled.
     *
     * @param  \stdClass $parent - Parent object calling this method.
     *
     * @return void
     */
    public function uninstall($parent) 
    {
        echo '<p>' . JText::_('COM_HELLOWORLD_UNINSTALL_TEXT') . '</p>';
    }

    /**
     * This method is called after a component is updated.
     *
     * @param  \stdClass $parent - Parent object calling object.
     *
     * @return void
     */
    public function update($parent) 
    {
        echo '<p>' . JText::sprintf('COM_HELLOWORLD_UPDATE_TEXT', $parent->get('manifest')->version) . '</p>';
    }

    /**
     * Runs just before any installation action is preformed on the component.
     * Verifications and pre-requisites should run in this function.
     *
     * @param  string    $type   - Type of PreFlight action. Possible values are:
     *                           - * install
     *                           - * update
     *                           - * discover_install
     * @param  \stdClass $parent - Parent object calling object.
     *
     * @return void
     */
    public function preflight($type, $parent) 
    {
        echo '<p>' . JText::_('COM_HELLOWORLD_PREFLIGHT_' . $type . '_TEXT') . '</p>';
    }

    /**
     * Runs right after any installation action is preformed on the component.
     *
     * @param  string    $type   - Type of PostFlight action. Possible values are:
     *                           - * install
     *                           - * update
     *                           - * discover_install
     * @param  \stdClass $parent - Parent object calling object.
     *
     * @return void
     */
    function postflight($type, $parent) 
    {
		$db = JFactory::getDbo();
		
		echo '<p>Checking if the root record is already present ...</p>';
		
		$query = $db->getQuery(true);
		$query->select('id');
		$query->from('#__helloworld');
		$query->where('id = 1');
		$query->where('alias = "helloworld-root-alias"');
		$db->setQuery($query);
		$id = $db->loadResult();
		
		if ($id == '1')
		{   // assume tree structure already built
			echo '<p>Root record already present, install program exiting ...</p>';
			return;
		}

		echo '<p>Checking if there is a record with id = 1 ...</p>';
		
		$query = $db->getQuery(true);
		$query->select('id');
		$query->from('#__helloworld');
		$query->where('id = 1');
		$db->setQuery($query);
		$id = $db->loadResult();
			
		if ($id)
		{
			echo '<p>Record with id = 1 found</p>';
			
			// get new id
			$query = $db->getQuery(true)
				->select('max(id) + 1')
				->from('#__helloworld');
			$db->setQuery($query);
			$newid = $db->loadResult(); 
			echo "<p>Changing id to $newid</p>";
			
			// update id in helloworld table
			$query = $db->getQuery(true)
				->update('#__helloworld')
				->set("id = $newid")
				->where("id = $id");
			$db->setQuery($query);
			$result = $db->execute();
			if ($result)
			{
				$nrows = $db->getAffectedRows();
				echo "<p>Id in helloworld table changed, records updated: $nrows</p>";
			}
			else
			{
				echo "<p>Error: Id in helloworld table not changed</p>";
				var_dump($result);
			}
			
			// update id in the associations table
			$query = $db->getQuery(true)
				->update('#__associations')
				->set("id = $newid")
				->where("id = $id")
				->where('context = "com_helloworld.item"');
			$db->setQuery($query);
			$result = $db->execute();
			if ($result)
			{
				$nrows = $db->getAffectedRows();
				echo "<p>Id in associations table changed, records updated: $nrows</p>";
			}
			else
			{
				echo "<p>Error: Id in associations table not changed</p>";
				var_dump($result);
			}
			
			// update id in the assets table
			$query = $db->getQuery(true)
				->update('#__assets')
				->set('name = "com_helloworld.helloworld.' . $newid . '"')
				->where('name = "com_helloworld.helloworld.' . $id . '"');
			$db->setQuery($query);
			$result = $db->execute();
			if ($result)
			{
				$nrows = $db->getAffectedRows();
				echo "<p>Id in assets table changed, records updated: $nrows</p>";
			}
			else
			{
				echo "<p>Error: Id in assets table not changed</p>";
				var_dump($result);
			}
		}
		else 
		{
			echo '<p>No record with id = 1 found</p>';
		}
		
		// find number of records in helloworld table
		$query = $db->getQuery(true)
			->select('count(*)')
			->from('#__helloworld');
		$db->setQuery($query);
		$total = $db->loadResult(); 
		
		// insert root record
		$columns = array('id','greeting','alias','parent_id','rgt');
		$values = array(1, 'helloworld root','helloworld-root-alias',0, 2 * (int)$total + 1);

		$query = $db->getQuery(true)
			->insert('#__helloworld')
			->columns($db->quoteName($columns))
			->values(implode(',', $db->quote($values)));
		$db->setQuery($query);
		$result = $db->execute();
		if ($result)
		{
			$nrows = $db->getAffectedRows();
			echo "<p>$nrows inserted into helloworld table</p>";
		}
		else
		{
			echo "<p>Error creating root record</p>";
			var_dump($result);
		}
		
		// update lft and rgt for each of the other records (ie not root)
		$query = $db->getQuery(true)
			->select('id')
			->from('#__helloworld')
			->where('id > 1');
		$db->setQuery($query);
		$ids = $db->loadColumn(); 
		for ($i = 0; $i < $total; $i++)
		{
			$lft = 2 * (int)$i + 1;
			$rgt = 2 * (int)$i + 2;
			$query = $db->getQuery(true)
				->update('#__helloworld')
				->set("lft = {$lft}")
				->set("rgt = {$rgt}")
				->where("id = {$ids[$i]}");
			$db->setQuery($query);
			$result = $db->execute();
			if ($result)
			{
				$nrows = $db->getAffectedRows();
				echo "<p>$nrows updated in helloworld table, for id = {$ids[$i]}</p>";
			}
			else
			{
				echo "<p>Error updating record</p>";
				var_dump($result);
			}
		}
    }
}

hallo Welt MVC

Aktualisierte Dateiversion, um die neuen Felder aufzunehmen. Die vorgegebene Sortierung ist festgelegt worden, um auf der Baumstruktur begründet zu sein, weil es vertretbar scheint, mehr angebracht zu sein, als auf der Grußformel begründet.

admin/models/helloworlds.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// No direct access to this file
defined('_JEXEC') or die('Restricted access');

/**
 * HelloWorldList Model
 *
 * @since  0.0.1
 */
class HelloWorldModelHelloWorlds extends JModelList
{
        /**
         * Constructor.
         *
         * @param   array  $config  An optional associative array of configuration settings.
         *
         * @see     JController
         * @since   1.6
         */
        public function __construct($config = array())
        {
                if (empty($config['filter_fields']))
                {
                        $config['filter_fields'] = array(
                                'id',
                                'greeting',
                                'author',
                                'created',
                                'language',
                                'lft',
                                'category_id',
                                'association',
                                'published'
                        );
                }

                parent::__construct($config);
        }

        protected function populateState($ordering = 'lft', $direction = 'asc')
        {
                $app = JFactory::getApplication();

                // Adjust the context to support modal layouts.
                if ($layout = $app->input->get('layout'))
                {
                        $this->context .= '.' . $layout;
                }

                // Adjust the context to support forced languages.
                $forcedLanguage = $app->input->get('forcedLanguage', '', 'CMD');
                if ($forcedLanguage)
                {
                        $this->context .= '.' . $forcedLanguage;
                }

                parent::populateState($ordering, $direction);
        
                // If there's a forced language then define that filter for the query where clause
                if (!empty($forcedLanguage))
                {
                        $this->setState('filter.language', $forcedLanguage);
                }
        }

        /**
         * Method to build an SQL query to load the list data.
         *
         * @return      string  An SQL query
         */
        protected function getListQuery()
        {
                // Initialize variables.
                $db    = JFactory::getDbo();
                $query = $db->getQuery(true);

                // Create the base select statement.
                $query->select('a.id as id, a.greeting as greeting, a.published as published, a.created as created, 
                          a.checked_out as checked_out, a.checked_out_time as checked_out_time, a.catid as catid,
                          a.lft as lft, a.rgt as rgt, a.parent_id as parent_id, a.level as level, a.path as path,
                          a.image as imageInfo, a.latitude as latitude, a.longitude as longitude, a.alias as alias, a.language as language')
                          ->from($db->quoteName('#__helloworld', 'a'));

                // Join over the categories.
                $query->select($db->quoteName('c.title', 'category_title'))
                        ->join('LEFT', $db->quoteName('#__categories', 'c') . ' ON c.id = a.catid');
        
                // Join with users table to get the username of the author
                $query->select($db->quoteName('u.username', 'author'))
                        ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON u.id = a.created_by');

                // Join with users table to get the username of the person who checked the record out
                $query->select($db->quoteName('u2.username', 'editor'))
                        ->join('LEFT', $db->quoteName('#__users', 'u2') . ' ON u2.id = a.checked_out');

                // Join with languages table to get the language title and image to display
                // Put these into fields called language_title and language_image so that 
                // we can use the little com_content layout to display the map symbol
                $query->select($db->quoteName('l.title', 'language_title') . "," .$db->quoteName('l.image', 'language_image'))
                        ->join('LEFT', $db->quoteName('#__languages', 'l') . ' ON l.lang_code = a.language');

                // Join over the associations - we just want to know if there are any, at this stage
                if (JLanguageAssociations::isEnabled())
                {
                        $query->select('COUNT(asso2.id)>1 as association')
                                ->join('LEFT', '#__associations AS asso ON asso.id = a.id AND asso.context=' . $db->quote('com_helloworld.item'))
                                ->join('LEFT', '#__associations AS asso2 ON asso2.key = asso.key')
                                ->group('a.id');
                }

                // Filter: like / search
                $search = $this->getState('filter.search');

                if (!empty($search))
                {
                        $like = $db->quote('%' . $search . '%');
                        $query->where('greeting LIKE ' . $like);
                }

                // Filter by published state
                $published = $this->getState('filter.published');

                if (is_numeric($published))
                {
                        $query->where('a.published = ' . (int) $published);
                }
                elseif ($published === '')
                {
                        $query->where('(a.published IN (0, 1))');
                }

                // Filter by language, if the user has set that in the filter field
                $language = $this->getState('filter.language');
                if ($language)
                {
                        $query->where('a.language = ' . $db->quote($language));
                }

                // Filter by categories
                $catid = $this->getState('filter.category_id');
                if ($catid)
                {
                        $query->where("a.catid = " . $db->quote($db->escape($catid)));
                }

                // exclude root helloworld record
                $query->where('a.id > 1');

                // Add the list ordering clause.
                $orderCol       = $this->state->get('list.ordering', 'lft');
                $orderDirn      = $this->state->get('list.direction', 'asc');

                $query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));

                return $query;
        }
}

In unserer Ansichtendatei erstellen wir eine Zuordnung von Eltern-ID zu den IDs seiner Erben. Das ermöglicht uns, die nachfolgenden Eltern eines Datensatzes aufsteigender Hierarchie leichter zu finden, was wir in der Layoutdatei durchführen werden.

admin/views/helloworlds/view.html.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');

/**
 * HelloWorlds View
 *
 * @since  0.0.1
 */
class HelloWorldViewHelloWorlds extends JViewLegacy
{
        /**
         * Display the Hello World view
         *
         * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
         *
         * @return  void
         */
        function display($tpl = null)
        {
                // Get application
                $app = JFactory::getApplication();

                // Get data from the model
                $this->items                    = $this->get('Items');
                $this->pagination               = $this->get('Pagination');
                $this->state                    = $this->get('State');
                $this->filterForm       = $this->get('FilterForm');
                $this->activeFilters    = $this->get('ActiveFilters');
        
                // What Access Permissions does this user have? What can (s)he do?
                $this->canDo = JHelperContent::getActions('com_helloworld');

                // Check for errors.
                if (count($errors = $this->get('Errors')))
                {
                        JError::raiseError(500, implode('<br />', $errors));

                        return false;
                }
        
                // Set the sidebar submenu and toolbar, but not on the modal window
                if ($this->getLayout() !== 'modal')
                {
                        HelloWorldHelper::addSubmenu('helloworlds');
                        $this->addToolBar();
                }
                else
                {
                        // If it's being displayed to select a record as an association, then forcedLanguage is set
                        if ($forcedLanguage = $app->input->get('forcedLanguage', '', 'CMD'))
                        {
                                // Transform the language selector filter into an hidden field, so it can't be set
                                $languageXml = new SimpleXMLElement('<field name="language" type="hidden" default="' . $forcedLanguage . '" />');
                                $this->filterForm->setField($languageXml, 'filter', true);

                                // Also, unset the active language filter so the search tools is not open by default with this filter.
                                unset($this->activeFilters['language']);
                        }
                }

                // Prepare a mapping from parent id to the ids of its children
                $this->ordering = array();
                foreach ($this->items as $item)
                {
                        $this->ordering[$item->parent_id][] = $item->id;
                }

                // Display the template
                parent::display($tpl);

                // Set the document
                $this->setDocument();
        }

        /**
         * Add the page title and toolbar.
         *
         * @return  void
         *
         * @since   1.6
         */
        protected function addToolBar()
        {
                $title = JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLDS');

                if ($this->pagination->total)
                {
                        $title .= "<span style='font-size: 0.5em; vertical-align: middle;'>(" . $this->pagination->total . ")</span>";
                }

                JToolBarHelper::title($title, 'helloworld');
                if ($this->canDo->get('core.create')) 
                {
                        JToolBarHelper::addNew('helloworld.add', 'JTOOLBAR_NEW');
                }
                if ($this->canDo->get('core.edit')) 
                {
                        JToolBarHelper::editList('helloworld.edit', 'JTOOLBAR_EDIT');
                }
                if ($this->canDo->get('core.delete')) 
                {
                        JToolBarHelper::deleteList('', 'helloworlds.delete', 'JTOOLBAR_DELETE');
                }
                if ($this->canDo->get('core.edit') || JFactory::getUser()->authorise('core.manage', 'com_checkin'))
                {
                        JToolBarHelper::checkin('helloworlds.checkin');
                }
                if ($this->canDo->get('core.admin')) 
                {
                        JToolBarHelper::divider();
                        JToolBarHelper::preferences('com_helloworld');
                }
        }
        /**
         * Method to set up the document properties
         *
         * @return void
         */
        protected function setDocument() 
        {
                $document = JFactory::getDocument();
                $document->setTitle(JText::_('COM_HELLOWORLD_ADMINISTRATION'));
        }
}

In unserer Layoutdatei schließen wir neue Spalten für die Felder lft, rgt, Ebene und übergeordnetes Element ein (wenngleich mit Überschriften, die eher nur die Datenbankfeldnamen sind als übersetzte Zeichenfolgen), und nehmen den Pfad in Kleinbuchstaben unter dem Alias unter der Grußformel. Wir ändern auch die Parameter für den Javascriptcode in sortablelist.js, der das Verschieben der Zeilen aktiviert um diejenigen Zeilen neu anzuordnen, die dasselbe übergeordnete Elternelement haben.

Dieser Javaskriptcode versteckt all die Vererbungen (nicht nur die unmittelbaren Kinder), wenn das Verschieben ausgeführt wird, so gibt es für jede Reihe wenig Aufwand, um eine Liste von nachfolgenden Elternelementen dieser Reihe zu erschaffen, wenn man sich in der Baumhierarchie nach oben bewegt, welche dann in das Elternelementeattribut des <tr> Elements gelegt wird. Wann eine Reihe mit der id=xxx verschoben wird, versteckt der Javaskriptcode jede Reihe, in welcher die id xxx erscheint, in diesem Elternelementeattribut.

admin/views/helloworlds/tmpl/default.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted Access');

use Joomla\Registry\Registry;

JHtml::_('formbehavior.chosen', 'select');

$listOrder     = $this->escape($this->state->get('list.ordering'));
$listDirn      = $this->escape($this->state->get('list.direction'));
$user = JFactory::getUser();
$userId = $user->get('id');
$saveOrder = ($listOrder == 'lft' && strtolower($listDirn) == 'asc');
if ($saveOrder)
{
        $saveOrderingUrl = 'index.php?option=com_helloworld&task=helloworlds.saveOrderAjax&tmpl=component';
        // pass true as parameter 7 to indicate that we have a nested set
        JHtml::_('sortablelist.sortable', 'helloworldList', 'adminForm', strtolower($listDirn), $saveOrderingUrl, false, true);
}
$assoc = JLanguageAssociations::isEnabled();
$authorFieldwidth = $assoc ? "10%" : "25%";
JLoader::register('JHtmlHelloworlds', JPATH_ADMINISTRATOR . '/components/com_helloworld/helpers/html/helloworlds.php');
?>
<form action="index.php?option=com_helloworld&view=helloworlds" method="post" id="adminForm" name="adminForm">
        <div id="j-sidebar-container" class="span2">
                <?php echo JHtmlSidebar::render(); ?>
        </div>
        <div id="j-main-container" class="span10">
        <div class="row-fluid">
            <div class="span6">
                <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_FILTER'); ?>
                <?php
                    echo JLayoutHelper::render(
                        'joomla.searchtools.default',
                        array('view' => $this)
                    );
                ?>
            </div>
        </div>
        <table class="table table-striped table-hover" id="helloworldList">
            <thead>
            <tr>
                <th width="1%">
                    <?php echo JHtml::_('searchtools.sort', '', 'lft', $listDirn, $listOrder, null, 'asc', 'JGRID_HEADING_ORDERING', 'icon-menu-2'); ?>
                </th>
                <th width="1%"><?php echo JText::_('COM_HELLOWORLD_NUM'); ?></th>
                <th width="1%">
                    <?php echo JHtml::_('grid.checkall'); ?>
                </th>
                <th width="10%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLDS_NAME', 'greeting', $listDirn, $listOrder); ?>
                </th>
                <th width="10%">
                    <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_POSITION'); ?>
                </th>
                <th width="10%">
                    <?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_IMAGE'); ?>
                </th>
                <th width="5%">
                    <?php echo "lft"; ?>
                </th>
                <th width="5%">
                    <?php echo "rgt"; ?>
                </th>
                <th width="5%">
                    <?php echo "level"; ?>
                </th>
                <th width="5%">
                    <?php echo "parent"; ?>
                </th>
                <?php if ($assoc) : ?>
                    <th width="10%">
                        <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLDS_ASSOCIATIONS', 'association', $listDirn, $listOrder); ?>
                    </th>
                <?php endif; ?>
                <th width="<?php echo $authorFieldwidth; ?>">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_AUTHOR', 'author', $listDirn, $listOrder); ?>
                </th>
                <th width="10%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_LANGUAGE', 'language', $listDirn, $listOrder); ?>
                </th>
                <th width="10%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_CREATED_DATE', 'created', $listDirn, $listOrder); ?>
                </th>
                <th width="5%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_PUBLISHED', 'published', $listDirn, $listOrder); ?>
                </th>
                <th width="2%">
                    <?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_ID', 'id', $listDirn, $listOrder); ?>
                </th>
            </tr>
            </thead>
            <tfoot>
                <tr>
                    <td colspan="5">
                        <?php echo $this->pagination->getListFooter(); ?>
                    </td>
                </tr>
            </tfoot>
            <tbody>
                <?php if (!empty($this->items)) : ?>
                    <?php foreach ($this->items as $i => $row) :
                        $link = JRoute::_('index.php?option=com_helloworld&task=helloworld.edit&id=' . $row->id);
                        $row->image = new Registry;
                        $row->image->loadString($row->imageInfo);
                        // create a list of the parents up the hierarchy to the root 
                        if ($row->level > 1)
                        {
                            $parentsStr = '';
                            $_currentParentId = $row->parent_id;
                            $parentsStr = ' ' . $_currentParentId;
                            for ($j = 0; $j < $row->level; $j++)
                            {
                                foreach ($this->ordering as $k => $v)
                                {
                                    $v = implode('-', $v);
                                    $v = '-' . $v . '-';
                                    if (strpos($v, '-' . $_currentParentId . '-') !== false)
                                    {
                                        $parentsStr .= ' ' . $k;
                                        $_currentParentId = $k;
                                        break;
                                    }
                                }
                            }
                        }
                        else
                        {
                            $parentsStr = '';
                        }
                    ?>
                        <tr class="row<?php echo $i % 2; ?>" sortable-group-id="<?php echo $row->parent_id; ?>" item-id="<?php echo $row->id; ?>" parents="<?php echo $parentsStr; ?>" level="<?php echo $row->level; ?>">
                            <td><?php
                                $iconClass = '';
                                $canReorder  = $user->authorise('core.edit.state', 'com_helloworld.helloworld.' . $row->id);
                                if (!$canReorder)
                                {
                                    $iconClass = ' inactive';
                                }
                                elseif (!$saveOrder)
                                {
                                    $iconClass = ' inactive tip-top hasTooltip" title="' . JHtml::_('tooltipText', 'JORDERINGDISABLED');
                                }
                                ?>
                                <span class="sortable-handler<?php echo $iconClass ?>">
                                    <span class="icon-menu" aria-hidden="true"></span>
                                </span>
                                <?php if ($canReorder && $saveOrder) : ?>
                                    <input type="text" style="display:none" name="order[]" size="5" value="<?php echo $row->lft; ?>" class="width-20 text-area-order" />
                                <?php endif; ?>
                            </td>
                            <td><?php echo $this->pagination->getRowOffset($i); ?></td>
                            <td>
                                <?php echo JHtml::_('grid.id', $i, $row->id); ?>
                            </td>
                            <td>
                                <?php $prefix = JLayoutHelper::render('joomla.html.treeprefix', array('level' => $row->level)); ?>
                                <?php echo $prefix; ?>
                                <?php if ($row->checked_out) : ?>
                                    <?php $canCheckin = $user->authorise('core.manage', 'com_checkin') || $row->checked_out == $userId; ?>
                                    <?php echo JHtml::_('jgrid.checkedout', $i, $row->editor, $row->checked_out_time, 'helloworlds.', $canCheckin); ?>
                                <?php endif; ?>
                                <a href="<?php echo $link; ?>" title="<?php echo JText::_('COM_HELLOWORLD_EDIT_HELLOWORLD'); ?>">
                                    <?php echo $row->greeting; ?>
                                </a>
                                <span class="small break-word">
                                        <?php echo JText::sprintf('JGLOBAL_LIST_ALIAS', $this->escape($row->alias)); ?>
                                </span>
                                <div class="small">
                                    <?php echo JText::_('JCATEGORY') . ': ' . $this->escape($row->category_title); ?>
                                </div>
                                <div class="small">
                                    <?php echo 'Path: ' . $this->escape($row->path); ?>
                                </div>
                            </td>
                            <td align="center">
                                <?php echo "[" . $row->latitude . ", " . $row->longitude . "]"; ?>
                            </td>
                            <td align="center">
                                <?php
                                    $caption = $row->image->get('caption') ? : '' ;
                                    $src = JURI::root() . ($row->image->get('image') ? : '' );
                                    $html = '<p class="hasTooltip" style="display: inline-block" data-html="true" data-toggle="tooltip" data-placement="right" title="<img width=\'100px\' height=\'100px\' src=\'%s\'>">%s</p>';
                                    echo sprintf($html, $src, $caption);  ?>
                            </td>
                            <td align="center">
                                <?php echo $row->lft; ?>
                            </td>
                            <td align="center">
                                <?php echo $row->rgt; ?>
                            </td>
                            <td align="center">
                                <?php echo $row->level; ?>
                            </td>
                            <td align="center">
                                <?php echo $row->parent_id; ?>
                            </td>
                            <?php if ($assoc) : ?>
                                <td align="center">
                                    <?php if ($row->association) : ?>
                                        <?php echo JHtml::_('helloworlds.association', $row->id); ?>
                                    <?php endif; ?>
                                </td>
                            <?php endif; ?>
                            <td align="center">
                                <?php echo $row->author; ?>
                            </td>
                            <td align="center">
                                <?php echo JLayoutHelper::render('joomla.content.language', $row); ?>
                            </td>
                            <td align="center">
                                <?php echo substr($row->created, 0, 10); ?>
                            </td>
                            <td align="center">
                                <?php echo JHtml::_('jgrid.published', $row->published, $i, 'helloworlds.', true, 'cb'); ?>
                            </td>
                            <td align="center">
                                <?php echo $row->id; ?>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                <?php endif; ?>
            </tbody>
        </table>
        <input type="hidden" name="task" value=""/>
        <input type="hidden" name="boxchecked" value="0"/>
        <?php echo JHtml::_('form.token'); ?>
    </div>
</form>

Wir müssen auch die Option der "Sortierung" innerhalb den Filterfeldern ändern, um das lft Datenbankfeld statt des Feldes ordering zu benutzen, welches wir im vorhergehenden Schritt hatten, und außerdem muss die Standardsortierung auf lft geändert werden.

admin/models/forms/filter_helloworlds.xml

<?xml version="1.0" encoding="utf-8"?>
<form>
	<fields name="filter">
		<field
			name="search"
			type="text"
			label="COM_BANNERS_SEARCH_IN_TITLE"
			hint="JSEARCH_FILTER"
			class="js-stools-search-string"
		/>
		<field
			name="published"
			type="status"
			label="JOPTION_SELECT_PUBLISHED"
			description="JOPTION_SELECT_PUBLISHED_DESC"
			onchange="this.form.submit();"
			>
			<option value="">JOPTION_SELECT_PUBLISHED</option>
		</field>
		<field
			name="language"
			type="contentlanguage"
			label="JOPTION_FILTER_LANGUAGE"
			description="JOPTION_FILTER_LANGUAGE_DESC"
			onchange="this.form.submit();"
			>
			<option value="">JOPTION_SELECT_LANGUAGE</option>
			<option value="*">JALL</option>
		</field>
		<field
			name="category_id"
			type="category"
			label="JOPTION_FILTER_CATEGORY"
			extension="com_helloworld"
			onchange="this.form.submit();"
			published="0,1,2"
			>
			<option value="">JOPTION_SELECT_CATEGORY</option>
		</field>
	</fields>
	<fields name="list">
		<field
			name="fullordering"
			type="list"
			label="COM_HELLOWORLD_LIST_FULL_ORDERING"
			description="COM_HELLOWORLD_LIST_FULL_ORDERING_DESC"
			onchange="this.form.submit();"
			default="lft ASC"
			>
			<option value="">JGLOBAL_SORT_BY</option>
			<option value="lft ASC">COM_HELLOWORLD_ORDERING_ASC</option>
			<option value="lft DESC">COM_HELLOWORLD_ORDERING_DESC</option>
			<option value="greeting ASC">COM_HELLOWORLD_GREETING_ASC</option>
			<option value="greeting DESC">COM_HELLOWORLD_GREETING_DESC</option>
			<option value="id ASC">JGRID_HEADING_ID_ASC</option>
			<option value="id DESC">JGRID_HEADING_ID_DESC</option>
			<option value="published ASC">COM_HELLOWORLD_PUBLISHED_ASC</option>
			<option value="published DESC">COM_HELLOWORLD_PUBLISHED_DESC</option>
			<option value="author ASC">COM_HELLOWORLD_AUTHOR_ASC</option>
			<option value="author DESC">COM_HELLOWORLD_AUTHOR_DESC</option>
			<option value="created ASC">COM_HELLOWORLD_CREATED_ASC</option>
			<option value="created DESC">COM_HELLOWORLD_CREATED_DESC</option>
			<option value="language ASC">COM_HELLOWORLD_LANGUAGE_ASC</option>
			<option value="language DESC">COM_HELLOWORLD_LANGUAGE_DESC</option>
			<option value="association ASC">COM_HELLOWORLD_ASSOCIATION_ASC</option>
			<option value="association DESC">COM_HELLOWORLD_ASSOCIATION_DESC</option>
		</field>
		<field
			name="limit"
			type="limitbox"
			class="input-mini"
			default="25"
			label="COM_CONTENT_LIST_LIMIT"
			description="COM_HELLOWORLD_LIST_LIMIT_DESC"
			onchange="this.form.submit();"
		/>
	</fields>
</form>

Hallo Welt Bearbeitungsformular

Das Formular wurde aktualisiert, um die zwei neuen Eingabelemente zur Erfassung der übergeordneten Elemente und der Sortierung aufzunehmen.

Das hallo Welt Elternfeld wird einen jform['parent_id'] Parameter zur Folge haben, der durch HTTP POST von dem Formular weitergeleitet wird, und dies wird an das parent_id Feld des Datensatzes angehängt.

Das hallo Welt Sortierungsfeld wird einen jform['helloworldordering'] Parameter ergeben, der die ID des folgenden Geschwisters enthält, wo dieser Datensatz platziert werden soll, oder einen besonderen Wert von -1 (um es als das erste Kind festzulegen) oder -2 (um es als das letzte Kind festzusetzen). Dieses Feld bildet nicht auf ein Feld in der Datenbank ab, wird aber benutzt werden um den Ort des bearbeiteten Datensatzes festzulegen.

admin/models/forms/helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<form
				addrulepath="/administrator/components/com_helloworld/models/rules"
>
	<fieldset
				name="details"
				label="COM_HELLOWORLD_HELLOWORLD_DETAILS"
	>
		<field
				name="id"
				type="hidden"
				/>
		<field
				name="greeting"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_GREETING_DESC"
				size="40"
				class="inputbox validate-greeting"
				validate="greeting"
				required="true"
				default=""
				/>
		<field 
				name="alias" 
				type="text" 
				label="JFIELD_ALIAS_LABEL"
				description="JFIELD_ALIAS_DESC"
				hint="JFIELD_ALIAS_PLACEHOLDER"
				size="40" 
				/>
		<field
				name="catid"
				type="category"
				extension="com_helloworld"
				class="inputbox"
				default=""
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC"
				required="true"
		>
				<option value="0">JOPTION_SELECT_CATEGORY</option>
		</field>
		<field
				name="latitude"
				type="number"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_DESC"
				min="-90.0"
				max="90.0"
				class="inputbox"
				required="true"
				default="0.0"
				/>
		<field
				name="longitude"
				type="number"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_DESC"
				min="-180.0"
				max="180.0"
				class="inputbox"
				required="true"
				default="0.0"
				/>
		<field
				name="language" 
				type="contentlanguage" 
				label="JFIELD_LANGUAGE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_LANGUAGE_DESC"
				>
				<option value="*">JALL</option>
		</field>
		<field
				name="parent_id"
				type="helloworldparent"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC"
				default="1"
				filter="int">
		</field>
		<field
				name="helloworldordering"
				type="helloworldordering"
				label="JFIELD_ORDERING_LABEL"
				description="JFIELD_ORDERING_DESC"
				filter="int"
				size="1">
		</field>
	</fieldset>
	<fields name="imageinfo">
		<fieldset
			name="image-info"
			label="COM_HELLOWORLD_IMAGE_FIELDS"
		>
			<field
				name="image"
				type="media"
				preview="tooltip"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_DESC" />
			<field name="alt"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_DESC"
				size="30"/>
			<field name="caption"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_DESC"
				size="30"/>
		</fieldset>
	</fields>
	<fields name="params">
		<fieldset
				name="params"
				label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS"
		>
			<field
					name="show_category"
					type="list"
					label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
					description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
					default=""
			>
				<option value="">JGLOBAL_USE_GLOBAL</option>
				<option value="0">JHIDE</option>
				<option value="1">JSHOW</option>
			</field>
		</fieldset>
	</fields>
	<fieldset
			name="accesscontrol"
			label="COM_HELLOWORLD_FIELDSET_RULES"
	>
    	<field
				name="asset_id"
				type="hidden"
				filter="unset"
				/>
    	<field
				name="rules"
				type="rules"
				label="COM_HELLOWORLD_FIELD_RULES_LABEL"
				filter="rules"
				validate="rules"
				class="inputbox"
				component="com_helloworld"
				section="message"
				/>
    </fieldset>
</form>

Die zwei neuen Eingabeelemente sind beides angepasste Felder, also werden wir Definitionen von ihnen in zwei neuen Dateien innerhalb des admin/models/fields Verzeichnisses zur Verfügung stellen müssen. Zuerst das Elternelement:

admin/models/fields/helloworldparent.php

<?php
/**
 * Class associated with displaying an input field to capture the parent of a helloworld record
 */

defined('JPATH_BASE') or die;

JFormHelper::loadFieldClass('list');

class JFormFieldHelloworldParent extends JFormFieldList
{
	protected $type = 'HelloworldParent';

	/**
	 * Method to return the field options for the parent
	 *
	 */
	protected function getOptions()
	{
		$options = array();

		$db = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('DISTINCT(a.id) AS value, a.greeting AS text, a.level, a.lft')
			->from('#__helloworld AS a');
		
		// Prevent parenting to children of this record, or to itself
		// If this record has lft = x and rgt = y, then its children have lft > x and rgt < y
		if ($id = $this->form->getValue('id'))
		{
			$query->join('LEFT', $db->quoteName('#__helloworld') . ' AS h ON h.id = ' . (int) $id)
				->where('NOT(a.lft >= h.lft AND a.rgt <= h.rgt)');
		}
		
		$query->order('a.lft ASC');
		
		$db->setQuery($query);

		try
		{
			$options = $db->loadObjectList();
		}
		catch (RuntimeException $e)
		{
			JError::raiseWarning(500, $e->getMessage());
		}
		
		// Pad the option text with spaces using depth level as a multiplier.
		for ($i = 0; $i < count($options); $i++)
		{
			$options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->text;
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;

	}
}

Und als Zweites die Sortierung

admin/models/fields/helloworldordering.php

<?php
/**
 * Class for displaying the Ordering field in the helloworld edit layout
 */

defined('JPATH_BASE') or die;

JFormHelper::loadFieldClass('list');

class JFormFieldHelloworldOrdering extends JFormFieldList
{
	protected $type = 'HelloworldOrdering';

	/**
	 * Method to return the options for ordering the helloworld record
	 * This is the list of siblings the record's siblings - ie those records with the same parent.
	 * The method requires that parent id be set.
	 */
	protected function getOptions()
	{
		$options = array();

		// Get the parent
		$parent_id = $this->form->getValue('parent_id', 0);

		if (empty($parent_id))
		{
			return false;
		}

		$db = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('a.id AS value, a.greeting AS text')
			->from('#__helloworld AS a')
			->where('a.parent_id =' . (int) $parent_id);

		$query->order('a.lft ASC');

		// Get the options.
		$db->setQuery($query);

		try
		{
			$options = $db->loadObjectList();
		}
		catch (RuntimeException $e)
		{
			JError::raiseWarning(500, $e->getMessage());
		}

		$options = array_merge(
			array(array('value' => '-1', 'text' => JText::_('COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_FIRST'))),
			$options,
			array(array('value' => '-2', 'text' => JText::_('COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_LAST')))
		);

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * This method returns the input element except if a new record is being created, in which case a text string is output
	 */
	protected function getInput()
	{
		if ($this->form->getValue('id', 0) == 0)
		{
			return '<span class="readonly">' . JText::_('COM_HELLOWORLD_ITEM_FIELD_ORDERING_TEXT') . '</span>';
		}
		else
		{
			return parent::getInput();
		}
	}
}

Der Umgang mit Administrator Datensatzaktualisierungen

Unser hallo Welt Modell und Tabellencode bearbeiten die Aktualisierungen zusammen, die durch Adminstratortätigkeiten im hallo Welt Bearbeitungsformular und der hallo Welt Ansicht entstehen. Bezüglich der zwei folgenden aktualisierten Daten, wird hier beschrieben, wie die Aktualisierungen durchgeführt werden.

Ziehen einer Zeile zur Neusortierung

  • Das Ziehen wird innerhalb des Javaskriptcodes in sortablelist.js ausgeführt, und die aktualisierte Reihenfolge wird zum Server über einen Ajaxaufruf von task=helloworlds.saveOrderAjax gesendet.
  • Der Programmcode innerhalb der Admin-Controller-Methode saveOrderAjax() ruft unsere musterhafte saveorder() Funktion auf.
  • Unsere modellhafte saveorder() Funktion ruft im Gegenzug den Befehl saveorder() innerhalb von JTableNested auf.
  • JTableNested::saveorder() führt die Datenbankaktualisierung aus.

Datensätze löschen

  • Ein Klick auf den Löschenknopf in der hallo World Ansicht bewirkt einen HTTP POST mit task=helloworlds.delete und die Löschung der Liste von Datensatz-IDs (von den Markierungsfeldern die markiert worden sind).
  • Der Adminkontroller delete() ruft die delete() Funktion im Modell auf, und reicht den Datenbereich von zu löschenden IDs weiter.
  • Weil wir keine ausdrückliche delete()-Funktion in unserem hallo Welt Modell haben, führt das dazu, dass delete() im übergeordneten Adminmodell aufgerufen wird. Diese Operation erhält die Tabelleninstanz vom Modell und für jede ID im Datenfeld ruft sie die Tabellen delete()-Methode auf, indem sie die ID als den einzigen Parameter weitergibt.
  • Die aktivierte Tabellen delete()-Methode entspricht dem delete() in unserer hallo Welt Tabellenklasse. Wir haben die JNestedTable delete() Methode außer Kraft gesetzt, weil, falls nur ein einziger Parameter an JNestedTable::delete() weitergegeben wurde, dann löscht es die Kinder (Vererbungen) als auch den Datensatz. Wir setzen es außer Kraft, sodass der Standard lautet, die Kinder (Vererbungen) NICHT zu löschen.
  • JNestedTable::delete() löscht den Datensatz, und verschiebt jeden seiner Kinder (Vererbungen) in der Hierarchie um eine Stufe hinauf, um auf dieselbe Vererbungsstufe des gelöschten Datensatzes zu gelangen.

Datensatz bearbeiten um das übergeordnete Element zu wechseln

  • Ein Klick auf Speichern bewirkt einen HTTP POST mit task=helloworld.save, der durch JControllerForm::save() ausgeführt wird. Diese Funktion ruft das Modell save() auf.
  • Dies aktiviert unser hallo Welt Modell save(), welches wiederum das parent::save() aufruft, welches sich in JModelAdmin befindet.
    • JModelAdmin::save() ruft Tabelle load() auf um die bestehenden Werte zu laden, dann Tabelle bind() um die neuen Werte daran zu binden.
      • Dies ruft unsere hallo Welt Tabellen bind()-Funktion auf, die prüft ob die neuen Eltern (übergeordneten Elemente) die gleichen sind, wie die bestehenden Eltern, und wenn dem nicht so ist, führt sie Tabelle setLocation() aus, um die Position als letztes Kind von diesem Elter festzulegen.
      • JTableNested::setLocation() speichert den Elter und die Position (dass heißt 'letztes Kind') in Instanzvariablen.
    • Zurück in JModelAdmin::save() ruft es Tabelle store() auf.
    • Das hier führt JTableNested::store() aus, welches den bearbeiteten Datensatz und all seine Abhängigkeiten unter den neuen Elterndatensatz schiebt, basierend auf den kürzlich zuvor gespeichten Instanzvariablen. Es repariert die lft- und rgt-Werte von all den Datensätzen in der hallo Welt Datenbanktabelle. Es bringt allerdings das Pfadfeld der zuvor bewegten Datensätze nicht in Ordnung.
  • Zurück in unserer hallo Welt Modell save() rufen wir den JTableNested rebuild() Befehl auf um die gesamte Datenbanktabelle neu aufzubauen. Dies stellt eine relativ grobe Methode dar, die Pfadfelder der zuvor bewegten Datensätze zu reparieren. Dennoch, falls wir in unserem Code den Pfadwert nicht verwenden, dann können wir diesen Schritt auslassen.

Datensatz bearbeiten um die Sortierung zu ändern (aber nicht den Elter)

  • Ein Klick auf Speichern bewirkt einen HTTP POST mit task=helloworld.save und jform['helloworldordering'], der auf die ID von dem folgendem Feld gesetzt ist, auf welches wir diesen Datensatz positionieren (oder auf -1 setzten (erstes Kind) oder -2 (letztes Kind) als spezielle Fälle). Dieser POST wird von JControllerForm::save() gesteuert, der das Modell save() aufruft.
  • Dies aktiviert unser hallo Welt Modell save(), welches wiederum das parent::save() aufruft, welches sich in JModelAdmin befindet.
    • JModelAdmin::save() ruft Tabelle load() auf um die bestehenden Werte zu laden, dann Tabelle bind() um die neuen Werte daran zu binden.
      • Das hier ruft unsere hallo Welt Tabellen bind() Funktion auf, die prüft, ob der neue Elterndatensatz derselbe ist wie der bestehende Elterndatensatz, und wenn dies zutrifft wird Tabelle setLocation() aufgerufen um die Position festzulegen gemäß der Datensatz ID im 'halloWeltSortierungs' Element des Datenfelds (und der Umgang mit den speziellen Fällen von -1 oder -2).
      • JTableNested::setLocation() speichert den (ungeänderten) Elterndatensatz und die Position in Instanzvariablen.
    • Zurück in JModelAdmin::save() ruft es Tabelle store() auf.
    • Der Aufruf von JTableNested::store() bewegt den bearbeiteten Datensatz auf die neue Position, gespeichert in seiner Instanzvariable. Dies repariert die lft- und rgt-Werte von all den Datensätzen in der hallo Welt Datensatztabelle.
  • Zurück in unserem hallo Welt Modell save() rufen wir den JTableNested rebuild() Befehl auf, um die gesamte Datenbanktabelle neu aufzubauen. Das ist aber sehr ineffizient, weil in diesem Fall kein Bedarf darin besteht, die Pfade erneut aufzubauen.

Neu und Speichern als Kopie

  • Beides hat in einen HTTP POST, der von JControllerForm::save() gesteuert wird, der das Modell save() aufruft, zur Folge.
  • Dies aktiviert unser hallo Welt Modell save(), welches wiederum das parent::save() aufruft, welches sich in JModelAdmin befindet.
    • JModelAdmin::save() ruft Tabelle load() auf um die bestehenden Werte zu laden, dann Tabelle bind() um die neuen Werte daran zu binden.
      • Aufruf unserer hallo Welt Tabelle bind() Funktion, die das 'ID' Element der Wertetabelle prüft, um herauszufinden, ob es sich um einen neuen Datensatz handelt. Trifft dies zu, wird Tabelle setLocation() aufgerufen um die Position als letztes Kind vom Datensatz, der im 'parent_id' Wertetabellelement definiert ist, festzulegen.
      • JTableNested::setLocation() speichert den Elterndatensatz und die Position (letztes Kind) in Instanzvariablen.
    • Zurück in JModelAdmin::save() ruft es Tabelle store() auf.
    • Dies ruft JTableNested::store() auf, was die lft- und rgt-Werte in der Tabelle aktualisiert, um Platz zu schaffen für den neuen Datensatz, um diesen an der Stelle einzufügen, die durch die Instanzvariablen, welche wiederum durch den setLocation() Aufruf gesetzt wurden, definiert sind. Es repariert die lft- und rgt-Werte aller Datensätze in der hallo Welt Datenbanktabelle, setzt aber den Pfad von unserem neuen Datensatz nicht.
  • Zurück in unserem hallo Welt Modell save() rufen wir den Befehl JTableNested rebuild() auf, um die gesamte Datenbanktabelle neu aufzubauen. Dies ist erneut ziemlich unpraktisch, weil wir den Befehl JTableNested rebuildPath() nur ausführen sollten, um den Pfad eines neuen Datensatzes festzulegen.

Befassen mit dem Problem des Wiederaufbaus Das unpraktische Vorgehen in Verbindung mit dem Zwang, den rebuild() Befehl auszuführen, ist wesentlich, zumal es darauf hinausläuft, dass in der gesamten hallo Welt Tabelle alle aktualisierten Datensätze verarbeitet werden, selbst wenn die Aktualisierung nicht von der Neujustage der Position eines Datensatzes im Datenbaum betroffen ist. Es gibt eine Vielzahl von Alternativen, das Problem anzugehen:

  • Falls du das Pfadfeld in deinem Code nicht verwendest, kannst du es einfach ignorieren und musst dir nicht die Mühe machen, rebuild() aufzurufen, um es zu reparieren.
  • Wie in dem Begleitvideo zu diesem Tutorialschritt beschrieben, kannst du dem Beispiel von com_content folgen und den Code für save() in das hallo Welt Modell hineinlegen, eher als das übergeordnete Admin Modell save() auszuführen, um diese Arbeit zu erledigen.
  • In das hallo Welt Modell save() kannst du für eine Datensatzaktualisierung den bestehenden Datensatz aus der Datenbank laden, seine ElternID mit den Daten aus dem Formular vergleichen und die Funktion rebuild() aufrufen, aber nur, falls die ElernID verschieden ist.

Unser aktualisierte Modell folgt hier. Beachte dass der Code von prepareTable() entfernt worden ist, weil die Definiton der Reihenfolge der Datensätze jetzt vom Befehl bind() von unserer hallo Welt Tabelle erledigt wird

admin/models/helloworld.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');

use Joomla\Registry\Registry;

/**
 * HelloWorld Model
 *
 * @since  0.0.1
 */
class HelloWorldModelHelloWorld extends JModelAdmin
{
	// JModelAdmin needs to know this for storing the associations 
	protected $associationsContext = 'com_helloworld.item';

	/**
	 * Method to override getItem to allow us to convert the JSON-encoded image information
	 * in the database record into an array for subsequent prefilling of the edit form
	 * We also use this method to prefill the associations
	 */
	public function getItem($pk = null)
	{
		$item = parent::getItem($pk);
		if ($item AND property_exists($item, 'image'))
		{
			$registry = new Registry($item->image);
			$item->imageinfo = $registry->toArray();
		}

		// Load associated items
		if (JLanguageAssociations::isEnabled())
		{
			$item->associations = array();

			if ($item->id != null)
			{
				$associations = JLanguageAssociations::getAssociations('com_helloworld', '#__helloworld', 'com_helloworld.item', (int)$item->id);

				foreach ($associations as $tag => $association)
				{
					$item->associations[$tag] = $association->id;
				}
			}
		}
		return $item; 
	}

	/**
	 * Method to get a table object, load it if necessary.
	 *
	 * @param   string  $type    The table name. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  JTable  A JTable object
	 *
	 * @since   1.6
	 */
	public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array())
	{
		return JTable::getInstance($type, $prefix, $config);
	}

	/**
	 * Method to get the record form.
	 *
	 * @param   array    $data      Data for the form.
	 * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
	 *
	 * @return  mixed    A JForm object on success, false on failure
	 *
	 * @since   1.6
	 */
	public function getForm($data = array(), $loadData = true)
	{
		// Get the form.
		$form = $this->loadForm(
			'com_helloworld.helloworld',
			'helloworld',
			array(
				'control' => 'jform',
				'load_data' => $loadData
			)
		);

		if (empty($form))
		{
			return false;
		}

		return $form;
	}

	/**
	 * Method to preprocess the form to add the association fields dynamically
	 *
	 * @return     none
	 */
	protected function preprocessForm(JForm $form, $data, $group = 'helloworld')
	{
		// Association content items
		if (JLanguageAssociations::isEnabled())
		{
			$languages = JLanguageHelper::getContentLanguages(false, true, null, 'ordering', 'asc');

			if (count($languages) > 1)
			{
				$addform = new SimpleXMLElement('<form />');
				$fields = $addform->addChild('fields');
				$fields->addAttribute('name', 'associations');
				$fieldset = $fields->addChild('fieldset');
				$fieldset->addAttribute('name', 'item_associations');

				foreach ($languages as $language)
				{
					$field = $fieldset->addChild('field');
					$field->addAttribute('name', $language->lang_code);
					$field->addAttribute('type', 'modal_helloworld');
					$field->addAttribute('language', $language->lang_code);
					$field->addAttribute('label', $language->title);
					$field->addAttribute('translate_label', 'false');
				}

				$form->load($addform, false);
			}
		}
		parent::preprocessForm($form, $data, $group);
	}

	/**
	 * Method to get the script to be included on the form
	 *
	 * @return string	Script files
	 */
	public function getScript() 
	{
		return 'administrator/components/com_helloworld/models/forms/helloworld.js';
	}

	/**
	 * Method to get the data that should be injected in the form.
	 *
	 * @return  mixed  The data for the form.
	 *
	 * @since   1.6
	 */
	protected function loadFormData()
	{
		// Check the session for previously entered form data.
		$data = JFactory::getApplication()->getUserState(
			'com_helloworld.edit.helloworld.data',
			array()
		);

		if (empty($data))
		{
			$data = $this->getItem();
		}

		return $data;
	}
	/**
	 * Method to override the JModelAdmin save() function to handle Save as Copy correctly
	 *
	 * @param   The helloworld record data submitted from the form.
	 *
	 * @return  parent::save() return value
	 */
	public function save($data)
	{
		$input = JFactory::getApplication()->input;

		JLoader::register('CategoriesHelper', JPATH_ADMINISTRATOR . '/components/com_categories/helpers/categories.php');

		// Validate the category id
		// validateCategoryId() returns 0 if the catid can't be found
		if ((int) $data['catid'] > 0)
		{
			$data['catid'] = CategoriesHelper::validateCategoryId($data['catid'], 'com_helloworld');
		}

		// Alter the greeting and alias for save as copy
		if ($input->get('task') == 'save2copy')
		{
			$origTable = clone $this->getTable();
			$origTable->load($input->getInt('id'));

			if ($data['greeting'] == $origTable->greeting)
			{
				list($greeting, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['greeting']);
				$data['greeting'] = $greeting;
				$data['alias'] = $alias;
			}
			else
			{
				if ($data['alias'] == $origTable->alias)
				{
					$data['alias'] = '';
				}
			}
			// standard Joomla practice is to set the new record as unpublished
			$data['published'] = 0;
		}

		$result = parent::save($data);
		if ($result)
		{
			$this->getTable()->rebuild(1);
		}
		return $result;
	}
	/**
	 * Method to check if it's OK to delete a message. Overrides JModelAdmin::canDelete
	 */
	protected function canDelete($record)
	{
		if( !empty( $record->id ) )
		{
			return JFactory::getUser()->authorise( "core.delete", "com_helloworld.helloworld." . $record->id );
		}
	}
	/**
	 * Prepare a helloworld record for saving in the database
	 */
	protected function prepareTable($table)
	{
	}

	/**
	 * Save the record reordering after a record is dragged to a new position in the helloworlds view
	 */
	public function saveorder($idArray = null, $lft_array = null)
	{
		// Get an instance of the table object.
		$table = $this->getTable();

		if (!$table->saveorder($idArray, $lft_array))
		{
			$this->setError($table->getError());

			return false;
		}

		return true;
	}
}

admin/tables/helloworld.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// No direct access
defined('_JEXEC') or die('Restricted access');

/**
 * Hello Table class
 *
 * @since  0.0.1
 */
class HelloWorldTableHelloWorld extends JTableNested
{
	/**
	 * Constructor
	 *
	 * @param   JDatabaseDriver  &$db  A database connector object
	 */
	function __construct(&$db)
	{
		parent::__construct('#__helloworld', 'id', $db);
	}
	/**
	 * Overloaded bind function
	 *
	 * @param       array           named array
	 * @return      null|string     null is operation was satisfactory, otherwise returns an error
	 * @see JTable:bind
	 * @since 1.5
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['params']) && is_array($array['params']))
		{
			// Convert the params field to a string.
			$parameter = new JRegistry;
			$parameter->loadArray($array['params']);
			$array['params'] = (string)$parameter;
		}

		if (isset($array['imageinfo']) && is_array($array['imageinfo']))
		{
			// Convert the imageinfo array to a string.
			$parameter = new JRegistry;
			$parameter->loadArray($array['imageinfo']);
			$array['image'] = (string)$parameter;
		}

		// Bind the rules.
		if (isset($array['rules']) && is_array($array['rules']))
		{
			$rules = new JAccessRules($array['rules']);
			$this->setRules($rules);
		}

		if (isset($array['parent_id']))
		{
			if (!isset($array['id']) || $array['id'] == 0)
			{   // new record
				$this->setLocation($array['parent_id'], 'last-child');
			}
			elseif (isset($array['helloworldordering']))
			{
				// when saving a record load() is called before bind() so the table instance will have properties which are the existing field values
				if ($this->parent_id == $array['parent_id'])
				{
					// If first is chosen make the item the first child of the selected parent.
					if ($array['helloworldordering'] == -1)
					{
						$this->setLocation($array['parent_id'], 'first-child');
					}
					// If last is chosen make it the last child of the selected parent.
					elseif ($array['helloworldordering'] == -2)
					{
						$this->setLocation($array['parent_id'], 'last-child');
					}
					// Don't try to put an item after itself. All other ones put after the selected item.
					elseif ($array['helloworldordering'] && $this->id != $array['helloworldordering'])
					{
						$this->setLocation($array['helloworldordering'], 'after');
					}
					// Just leave it where it is if no change is made.
					elseif ($array['helloworldordering'] && $this->id == $array['helloworldordering'])
					{
						unset($array['helloworldordering']);
					}
				}
				// Set the new parent id if parent id not matched and put in last position
				else
				{
					$this->setLocation($array['parent_id'], 'last-child');
				}
			}
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Method to compute the default name of the asset.
	 * The default name is in the form `table_name.id`
	 * where id is the value of the primary key of the table.
	 *
	 * @return	string
	 * @since	2.5
	 */
	protected function _getAssetName()
	{
		$k = $this->_tbl_key;
		return 'com_helloworld.helloworld.'.(int) $this->$k;
	}
	/**
	 * Method to return the title to use for the asset table.
	 *
	 * @return	string
	 * @since	2.5
	 */
	protected function _getAssetTitle()
	{
		return $this->greeting;
	}
	/**
	 * Method to get the asset-parent-id of the item
	 *
	 * @return	int
	 */
	protected function _getAssetParentId(JTable $table = NULL, $id = NULL)
	{
		// We will retrieve the parent-asset from the Asset-table
		$assetParent = JTable::getInstance('Asset');
		// Default: if no asset-parent can be found we take the global asset
		$assetParentId = $assetParent->getRootId();

		// Find the parent-asset
		if (($this->catid)&& !empty($this->catid))
		{
			// The item has a category as asset-parent
			$assetParent->loadByName('com_helloworld.category.' . (int) $this->catid);
		}
		else
		{
			// The item has the component as asset-parent
			$assetParent->loadByName('com_helloworld');
		}

		// Return the found asset-parent-id
		if ($assetParent->id)
		{
			$assetParentId=$assetParent->id;
		}
		return $assetParentId;
	}

	public function check()
	{
		$this->alias = trim($this->alias);
		if (empty($this->alias))
		{
			$this->alias = $this->greeting;
		}
		$this->alias = JFilterOutput::stringURLSafe($this->alias);
		return true;
	}

	public function delete($pk = null, $children = false)
	{
		return parent::delete($pk, $children);
	}
}

hallo Welt Frontenddarstellung

Um die Elterndatensätze und die Vererbungen auf der hallo Welt Seite anzuzeigen, bedarf es dreier Änderungen:

  1. Beziehe die Daten in dieser Ansicht für die Elterndatensätze und für die Vererbungen unter Verwendung von Funktionsaufrufen an das Modell
  2. Stelle in dem Modell die Daten für diese Funktionsaufrufe zur Verfügung
  3. Gib im Layout das html aus, welches die Elterndatensatz- und Vererbungsdaten enthält.

Beachte, dass wir den Modellcode verändert haben, um einen Parameter an getItem() zu ermöglichen. Das hat den Nebeneffekt der Veränderung der this->item Variable innerhalb der Modellinstanz, um auf das zuletzt abgefragte Objekt zu verweisen. Weil die Funktionsweise innerhalb von getMapParams() (aufgerufen von addMap()) this->item verwendet, um den Breiten-, Längengrad und so weiter für den gegenwärtigen Datensatz zu erhalten, sollten wir sicherstellen, dass der Aufruf von addMap() vor dem neuen Code geschieht, den wir jetzt einführen.

Beachte. Falls du einen Menüeintrag hattest, der auf deinen hallo World Datensatz mit der ID=1 deutete, dann wirst du diesen Menüeintrag bearbeiten müssen, damit er auf die neue ID deutet.

site/views/helloworld/view.html.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');

/**
 * HTML View class for the HelloWorld Component
 *
 * @since  0.0.1
 */
class HelloWorldViewHelloWorld extends JViewLegacy
{
	/**
	 * Display the Hello World view
	 *
	 * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
	 *
	 * @return  void
	 */
	function display($tpl = null)
	{
		// Assign data to the view
		$this->item = $this->get('Item');

		// Check for errors.
		if (count($errors = $this->get('Errors')))
		{
			JLog::add(implode('<br />', $errors), JLog::WARNING, 'jerror');

			return false;
		}

		$this->addMap();

		$model = $this->getModel();
		$this->parentItem = $model->getItem($this->item->parent_id);
		$this->children = $model->getChildren($this->item->id);
		// getChildren includes the record itself (as well as the children) so remove this record
		unset($this->children[0]);

		// Display the view
		parent::display($tpl);
	}

	function addMap() 
	{
		$document = JFactory::getDocument();

		// everything's dependent upon JQuery
		JHtml::_('jquery.framework');

		// we need the Openlayers JS and CSS libraries
		$document->addScript("https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.6.4/ol.js");
		$document->addStyleSheet("https://cdnjs.cloudflare.com/ajax/libs/openlayers/4.6.4/ol.css");

		// ... and our own JS and CSS
		$document->addScript(JURI::root() . "media/com_helloworld/js/openstreetmap.js");
		$document->addStyleSheet(JURI::root() . "media/com_helloworld/css/openstreetmap.css");

		// get the data to pass to our JS code
		$params = $this->get("mapParams");
		$document->addScriptOptions('params', $params);
	}
}

site/models/helloworld.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');

JLoader::register('HelloworldHelperRoute', JPATH_ROOT . '/components/com_helloworld/helpers/route.php');

/**
 * HelloWorld Model
 *
 * @since  0.0.1
 */
class HelloWorldModelHelloWorld extends JModelItem
{
	/**
	 * @var object item
	 */
	protected $item;

	/**
	 * Method to auto-populate the model state.
	 *
	 * This method should only be called once per instantiation and is designed
	 * to be called on the first call to the getState() method unless the model
	 * configuration flag to ignore the request is set.
	 *
	 * Note. Calling getState in this method will result in recursion.
	 *
	 * @return	void
	 * @since	2.5
	 */
	protected function populateState()
	{
		// Get the message id
		$jinput = JFactory::getApplication()->input;
		$id     = $jinput->get('id', 1, 'INT');
		$this->setState('message.id', $id);

		// Load the parameters.
		$this->setState('params', JFactory::getApplication()->getParams());
		parent::populateState();
	}

	/**
	 * Method to get a table object, load it if necessary.
	 *
	 * @param   string  $type    The table name. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  JTable  A JTable object
	 *
	 * @since   1.6
	 */
	public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array())
	{
		return JTable::getInstance($type, $prefix, $config);
	}

	/**
	 * Get the message
	 * @return object The message to be displayed to the user
	 */
	public function getItem($id = null)
	{
		if (!isset($this->item) || !is_null($id)) 
		{
			$id    = is_null($id) ? $this->getState('message.id') : $id;
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true);
			$query->select('h.greeting, h.params, h.image as image, c.title as category, h.latitude as latitude, h.longitude as longitude,
						h.id as id, h.alias as alias, h.catid as catid, h.parent_id as parent_id, h.level as level')
				  ->from('#__helloworld as h')
				  ->leftJoin('#__categories as c ON h.catid=c.id')
				  ->where('h.id=' . (int)$id);

			if (JLanguageMultilang::isEnabled())
			{
				$lang = JFactory::getLanguage()->getTag();
				$query->where('h.language IN ("*","' . $lang . '")');
			}

			$db->setQuery((string)$query);
		
			if ($this->item = $db->loadObject()) 
			{
				// Load the JSON string
				$params = new JRegistry;
				$params->loadString($this->item->params, 'JSON');
				$this->item->params = $params;

				// Merge global params with item params
				$params = clone $this->getState('params');
				$params->merge($this->item->params);
				$this->item->params = $params;

				// Convert the JSON-encoded image info into an array
				$image = new JRegistry;
				$image->loadString($this->item->image, 'JSON');
				$this->item->imageDetails = $image;
			}
			else
			{
				throw new Exception('Helloworld id not found', 404);
			}
		}
		return $this->item;
	}

	public function getMapParams()
	{
		if ($this->item) 
		{
			$url = HelloworldHelperRoute::getAjaxURL();
			$this->mapParams = array(
				'latitude' => $this->item->latitude,
				'longitude' => $this->item->longitude,
				'zoom' => 10,
				'greeting' => $this->item->greeting,
				'ajaxurl' => $url
			);
			return $this->mapParams; 
		}
		else
		{
			throw new Exception('No helloworld details available for map', 500);
		}
	}

	public function getMapSearchResults($mapbounds)
	{
		try 
		{
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true);
			$query->select('h.id, h.alias, h.catid, h.greeting, h.latitude, h.longitude')
			   ->from('#__helloworld as h')
			   ->where('h.latitude > ' . $mapbounds['minlat'] . 
				' AND h.latitude < ' . $mapbounds['maxlat'] .
				' AND h.longitude > ' . $mapbounds['minlng'] .
				' AND h.longitude < ' . $mapbounds['maxlng']);

			if (JLanguageMultilang::isEnabled())
			{
				$lang = JFactory::getLanguage()->getTag();
				$query->where('h.language IN ("*","' . $lang . '")');
			}

			$db->setQuery($query);
			$results = $db->loadObjectList(); 
		}
		catch (Exception $e)
		{
			$msg = $e->getMessage();
			JFactory::getApplication()->enqueueMessage($msg, 'error'); 
			$results = null;
		}

		if (JLanguageMultilang::isEnabled())
		{
			$query_lang = "&lang={$lang}";
		}
		else
		{
			$query_lang = "";
		}

		for ($i = 0; $i < count($results); $i++) 
		{
			$results[$i]->url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $results[$i]->id . 
				":" . $results[$i]->alias . '&catid=' . $results[$i]->catid . $query_lang);
		}

		return $results; 
	}

	public function getChildren($id)
	{
		$table = $this->getTable();
		$children = $table->getTree($id);
		return $children;
	}
}

site/views/helloworld/tmpl/default.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');
$lang = JFactory::getLanguage()->getTag();
if (JLanguageMultilang::isEnabled() && $lang)
{
    $query_lang = "&lang={$lang}";
}
else
{
    $query_lang = "";
}
?>
<h1><?php echo $this->item->greeting.(($this->item->category and $this->item->params->get('show_category'))
                                      ? (' ('.$this->item->category.')') : ''); ?>
</h1>
<?php
    $src = $this->item->imageDetails['image'];
    if ($src)
    {
        $html = '<figure>
                    <img src="%s" alt="%s" >
                    <figcaption>%s</figcaption>
                </figure>';
        $alt = $this->item->imageDetails['alt'];
        $caption = $this->item->imageDetails['caption'];
        echo sprintf($html, $src, $alt, $caption);
    } ?>

<?php if ($this->parentItem->id > 1) : ?>
	<h1><?php echo JText::_('COM_HELLOWORLD_PARENT') ?>
	</h1>
	<h3>
		<?php $url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $this->parentItem->id . ':' . $this->parentItem->alias . '&catid=' . $this->parentItem->catid . $query_lang); ?>
		<a href="<?php echo $url; ?>"><?php echo $this->parentItem->greeting; ?></a>
	</h3>
<?php endif; ?>

<?php if ($this->children) : 
		$baseLevel = $this->item->level; ?>
		<h1><?php echo JText::_('COM_HELLOWORLD_CHILDREN') ?>
		</h1>
		<?php foreach ($this->children as $i => $child) : ?>
			<h3>
				<?php $prefix = JLayoutHelper::render('joomla.html.treeprefix', array('level' => $child->level - $baseLevel)); ?>
				<?php echo $prefix; ?>
				<?php $url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $child->id . ':' . $child->alias . '&catid=' . $child->catid . $query_lang); ?>
				<a href="<?php echo $url; ?>"><?php echo $child->greeting; ?></a>
			</h3>
	<?php endforeach; ?>
<?php endif; ?>

<div id="map" class="map"></div>
<div class="map-callout map-callout-bottom" id="greeting-container"></div>
<div id="searchmap">
    <?php echo '<input id="token" type="hidden" name="' . JSession::getFormToken() . '" value="1" />'; ?>
    <button type="button" class="btn btn-primary" onclick="searchHere();">
        <?php echo JText::_('COM_HELLOWORLD_SEARCH_HERE_BUTTON') ?>
    </button>
    <div id="searchresults">
    </div>
</div>

hallo Welt Frontendformular

Um das Erfassen des Elternelements im Frontendformular zu unterstützen verwenden wir das angepasste Administratorformularfeld in der add-form xml-Definition.

Beachte, dass wir in der Funktionsweise, einen neuen hallo Welt Datensatz im Frontend zu erstellen, keinen Tabellen rebuild() aufgenommen haben, sodass die neuen Datensätze daran scheitern, das path Feld, weches in die Datenbank geschrieben wurde, zu erhalten. Der neue Datensatz wird dennoch richtig in den Baum eingefügt werden, weil das Frontend den Backend Tabellencode auf Administratorebene verwendet. So wird es in den verschachtelten Mengen vererbt, inklusive seiner bind() Überbrückung etc. Das bedeutet auch, dass wir den Code in dem Modell prepareTable(), der vor Kurzem die Sortierung des neuen Datensatzes definierte, entfernen sollten.

site/models/forms/add-form.xml

<?xml version="1.0" encoding="utf-8"?>
<form
    addrulepath="/administrator/components/com_helloworld/models/rules"
    addfieldpath="/administrator/components/com_helloworld/models/fields"
    >
    <fieldset
				name="details"
				label="COM_HELLOWORLD_HELLOWORLD_DETAILS"
	>
		<field
				name="id"
				type="hidden"
				/>
		<field
				name="greeting"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_GREETING_DESC"
				size="40"
				class="inputbox"
				validate="greeting"
				required="true"
				hint="COM_HELLOWORLD_HELLOWORLD_GREETING_HINT"
				default=""
				/>
		<field 
				name="alias" 
				type="text" 
				label="JFIELD_ALIAS_LABEL"
				description="JFIELD_ALIAS_DESC"
				hint="JFIELD_ALIAS_PLACEHOLDER"
				size="40" 
				/>
		<field
				name="catid"
				type="category"
				extension="com_helloworld"
				class="inputbox"
				default=""
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC"
				required="true"
				>
				<option value="0">JOPTION_SELECT_CATEGORY</option>
		</field>
		<fields name="imageinfo" label="COM_HELLOWORLD_HELLOWORLD_IMAGE_LABEL">
			<field
				name="image"
				type="file"
				label="COM_HELLOWORLD_HELLOWORLD_PICTURE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_PICTURE_DESC" 
				accept="image/*"
				>
			</field>
			<field
 				name="caption"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_CAPTION_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_CAPTION_DESC"
				size="40"
				class="inputbox"
				>
			</field>
			<field
				name="alt"
				type="text"
				label="COM_HELLOWORLD_HELLOWORLD_ALTTEXT_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_ALTTEXT_DESC"
				size="40"
				class="inputbox"
				>
			</field>
		</fields>
		<field
				name="language"
				type="contentlanguage"
				label="JFIELD_LANGUAGE_LABEL"
				description="JFIELD_LANGUAGE_DESC"
				class="inputbox">
		</field>
		<field
				name="message"
				type="textarea"
				rows="5"
				cols="80"
				label="COM_HELLOWORLD_HELLOWORLD_MESSAGE_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_MESSAGE_DESC"
				hint="COM_HELLOWORLD_HELLOWORLD_MESSAGE_HINT"
				required="true"
				>
        </field>
        <field
				name="captcha"
				type="captcha"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC"
				validate="captcha"
                >
		</field>
        <fields name="params">
            <field
                    name="show_category"
                    type="list"
                    label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
                    description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
                    default=""
                    useglobal="true"
            >
                <option value="0">JHIDE</option>
                <option value="1">JSHOW</option>
            </field>
        </fields>
		<field
				name="parent_id"
				type="helloworldparent"
				label="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL"
				description="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC"
				default="1"
				filter="int">
		</field>
    </fieldset>
</form>

site/models/form.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_helloworld
 *
 * @copyright   Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access to this file
defined('_JEXEC') or die('Restricted access');

/**
 * HelloWorld Model
 *
 * @since  0.0.1
 */
class HelloWorldModelForm extends JModelAdmin
{

	/**
	 * Method to get a table object, load it if necessary.
	 *
	 * @param   string  $type    The table name. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  JTable  A JTable object
	 *
	 * @since   1.6
	 */
	public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array())
	{
		return JTable::getInstance($type, $prefix, $config);
	}

    /**
	 * Method to get the record form.
	 *
	 * @param   array    $data      Data for the form.
	 * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
	 *
	 * @return  mixed    A JForm object on success, false on failure
	 *
	 * @since   1.6
	 */
	public function getForm($data = array(), $loadData = true)
	{
		// Get the form.
		$form = $this->loadForm(
			'com_helloworld.form',
			'add-form',
			array(
				'control' => 'jform',
				'load_data' => $loadData
			)
		);

		if (empty($form))
		{
			$errors = $this->getErrors();
			throw new Exception(implode("\n", $errors), 500);
		}

		return $form;
	}

	/**
	 * Method to get the data that should be injected in the form.
	 * As this form is for add, we're not prefilling the form with an existing record
	 * But if the user has previously hit submit and the validation has found an error,
	 *   then we inject what was previously entered.
	 *
	 * @return  mixed  The data for the form.
	 *
	 * @since   1.6
	 */
	protected function loadFormData()
	{
		// Check the session for previously entered form data.
		$data = JFactory::getApplication()->getUserState(
			'com_helloworld.edit.helloworld.data',
			array()
		);

		return $data;
	}
    
	/**
	 * Method to get the script that have to be included on the form
	 * This returns the script associated with helloworld field greeting validation
	 *
	 * @return string	Script files
	 */
	public function getScript() 
	{
		return 'administrator/components/com_helloworld/models/forms/helloworld.js';
	}

	/**
	 * Prepare a helloworld record for saving in the database
	 */
	protected function prepareTable($table)
	{
	}
}

Frontend Kategorienansicht

Wir müssen nur die Sortierung ändern, um das lft Datenbankfeld statt des ordering Feldes zu benutzen.

site/models/category.php

<?php
/**
 * Model for displaying the helloworld messages in a given category
 */

defined('_JEXEC') or die;

class HelloworldModelCategory extends JModelList
{
	public function __construct($config = array())
	{
		if (empty($config['filter_fields']))
		{
			$config['filter_fields'] = array(
				'id',
				'greeting',
				'alias',
				'lft',
			);
		}

		parent::__construct($config);
	}
    
	protected function populateState($ordering = null, $direction = null)
	{
		parent::populateState($ordering, $direction);
        
		$app = JFactory::getApplication('site');
		$catid = $app->input->getInt('id');

		$this->setState('category.id', $catid);
	}
    
	protected function getListQuery()
	{
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true);

		$catid = $this->getState('category.id'); 
		$query->select('id, greeting, alias, catid')
			->from($db->quoteName('#__helloworld'))
			->where('catid = ' . $catid);

		if (JLanguageMultilang::isEnabled())
		{
			$lang = JFactory::getLanguage()->getTag();
			$query->where('language IN ("*","' . $lang . '")');
		}

		$orderCol	= $this->state->get('list.ordering', 'lft');
		$orderDirn 	= $this->state->get('list.direction', 'asc');

		$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));

		return $query;	
	}

	public function getCategoryName()
	{
		$catid = $this->getState('category.id'); 
		$categories = JCategories::getInstance('Helloworld', array());
		$categoryNode = $categories->get($catid);   
		return $categoryNode->title; 
	}
    
	public function getSubcategories()
	{
		$catid = $this->getState('category.id'); 
		$categories = JCategories::getInstance('Helloworld', array());
		$categoryNode = $categories->get($catid);
		$subcats = $categoryNode->getChildren(); 
        
		$lang = JFactory::getLanguage()->getTag();
		if (JLanguageMultilang::isEnabled() && $lang)
		{
			$query_lang = "&lang={$lang}";
		}
		else
		{
			$query_lang = '';
		}
        
		foreach ($subcats as $subcat)
		{
			$subcat->url = JRoute::_("index.php?view=category&id=" . $subcat->id . $query_lang);
		}
		return $subcats;
	}
}

site/models/forms/filter_category.xml

<?xml version="1.0" encoding="utf-8"?>
<form>
	<fields name="list">
        <field
			name="fullordering"
			type="list"
			onchange="this.form.submit();"
			default="lft ASC"
			>
			<option value="">COM_HELLOWORLD_SORT_BY</option>
			<option value="lft ASC">COM_HELLOWORLD_ORDERING_ASC</option>
			<option value="lft DESC">COM_HELLOWORLD_ORDERING_DESC</option>
			<option value="greeting ASC">COM_HELLOWORLD_GREETING_ASC</option>
			<option value="greeting DESC">COM_HELLOWORLD_GREETING_DESC</option>
			<option value="id ASC">COM_HELLOWORLD_ID_ASC</option>
			<option value="id DESC">COM_HELLOWORLD_ID_DESC</option>
			<option value="alias ASC">COM_HELLOWORLD_ALIAS_ASC</option>
			<option value="alias DESC">COM_HELLOWORLD_ALIAS_DESC</option>
		</field>
		<field
			name="limit"
			type="limitbox"
			class="input-mini"
			default="10"
			onchange="this.form.submit();"
		/>
	</fields>
</form>

Aktualisierte Sprachzeichenfolgen

site/language/en-GB/en-GB.com_helloworld.ini

; add new message form
COM_HELLOWORLD_LEGEND_DETAILS="New Helloworld Message Details"
COM_HELLOWORLD_HELLOWORLD_CREATING="Add message"
COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE="Sorry, you have an error"
COM_HELLOWORLD_HELLOWORLD_DETAILS="Message details"
COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL="Greeting"
COM_HELLOWORLD_HELLOWORLD_GREETING_DESC="Please specify the greeting to add"
COM_HELLOWORLD_HELLOWORLD_GREETING_HINT="Any characters allowed"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL="Category"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC="Please select the associated category"
COM_HELLOWORLD_HELLOWORLD_MESSAGE_LABEL="Reason"
COM_HELLOWORLD_HELLOWORLD_MESSAGE_DESC="Please say why you're adding this greeting"
COM_HELLOWORLD_HELLOWORLD_MESSAGE_HINT="No HTML tags!"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL="Spam protection"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC="Prove you're a real person!"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL="Display category or not?"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC="Select if you want the category displayed too"
COM_HELLOWORLD_HELLOWORLD_IMAGE_LABEL="Image information"
COM_HELLOWORLD_HELLOWORLD_PICTURE_LABEL="Image file to upload"
COM_HELLOWORLD_HELLOWORLD_PICTURE_DESC="Select the file with the image to upload"
COM_HELLOWORLD_HELLOWORLD_CAPTION_LABEL="Caption"
COM_HELLOWORLD_HELLOWORLD_CAPTION_DESC="Text to use as a caption for the image"
COM_HELLOWORLD_HELLOWORLD_ALTTEXT_LABEL="Alt text"
COM_HELLOWORLD_HELLOWORLD_ALTTEXT_DESC="Text to display if image cannot be shown"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL="Parent"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC="Select the record which is to be the parent"
; save and cancel confirmation messages
COM_HELLOWORLD_ADD_SUCCESSFUL="New greeting successfully saved"
COM_HELLOWORLD_ADD_CANCELLED="New greeting cancelled ok"
; file upload error conditions
COM_HELLOWORLD_ERROR_FILEUPLOAD="PHP Error %s encountered when uploading file"
COM_HELLOWORLD_ERROR_FILETOOLARGE="Upload file exceeds max size configured in Joomla"
COM_HELLOWORLD_ERROR_BADFILENAME="Upload file has an invalid filename"
COM_HELLOWORLD_ERROR_FILE_EXISTS="Upload file already exists"
COM_HELLOWORLD_ERROR_UNABLE_TO_UPLOAD_FILE="Error creating uploaded file"
; helloworld greeting page
COM_HELLOWORLD_PARENT="Parent"
COM_HELLOWORLD_CHILDREN="Children"
COM_HELLOWORLD_SEARCH_HERE_BUTTON="Search here"
; Ajax handling errors
COM_HELLOWORLD_ERROR_NO_RECORDS="Didn't get any records"
COM_HELLOWORLD_ERROR_NO_MAP_BOUNDS="Error: no map bounds"
; category view, search and ordering fields and headings
COM_HELLOWORLD_SORT_BY="Sort by ..."
COM_HELLOWORLD_ORDERING_ASC="Ordering asc"
COM_HELLOWORLD_ORDERING_DESC="Ordering desc"
COM_HELLOWORLD_GREETING_ASC="Greeting asc"
COM_HELLOWORLD_GREETING_DESC="Greeting desc"
COM_HELLOWORLD_ID_ASC="id asc"
COM_HELLOWORLD_ID_DESC="id desc"
COM_HELLOWORLD_ALIAS_ASC="alias asc"
COM_HELLOWORLD_ALIAS_DESC="alias desc"
COM_HELLOWORLD_HELLOWORLD_ALIAS_LABEL="Alias"
COM_HELLOWORLD_HELLOWORLD_FIELD_URL_LABEL="URL"
COM_HELLOWORLD_HEADER_SUBCATEGORIES="Subcategories"

admin/language/en-GB/en-GB.com_helloworld.ini

; Joomla! Project
; Copyright (C) 2005 - 2018 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8

COM_HELLOWORLD_ADMINISTRATION="HelloWorld - Administration"
COM_HELLOWORLD_ADMINISTRATION_CATEGORIES="HelloWorld - Categories"
COM_HELLOWORLD_NUM="#"
COM_HELLOWORLD_HELLOWORLDS_FILTER="Filters"
COM_HELLOWORLD_AUTHOR="Author"
COM_HELLOWORLD_LANGUAGE="Language"
COM_HELLOWORLD_CREATED_DATE="Created"
COM_HELLOWORLD_PUBLISHED="Published"
COM_HELLOWORLD_HELLOWORLDS_NAME="Name"
COM_HELLOWORLD_HELLOWORLDS_POSITION="Position"
COM_HELLOWORLD_HELLOWORLDS_IMAGE="Image"
COM_HELLOWORLD_HELLOWORLDS_ASSOCIATIONS="Associations"
COM_HELLOWORLD_ID="Id"

COM_HELLOWORLD_HELLOWORLD_CREATING="HelloWorld - Creating"
COM_HELLOWORLD_HELLOWORLD_DETAILS="Details"
COM_HELLOWORLD_HELLOWORLD_EDITING="HelloWorld - Editing"
COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE="Some values are unacceptable"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC="The category the messages belongs to"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL="Category"
COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_DESC="This message will be displayed"
COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_LABEL="Message"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL="Show category"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC="If set to Show, the title of the message&rsquo;s category will show."
COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_LABEL="Latitude"
COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_DESC="Enter the position latitude, between -90 and +90 degrees"
COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_LABEL="Longitude"
COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_DESC="Enter the position longitude, between -180 and +180 degrees"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL="Parent"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC="Select the parent record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_FIRST="-- First record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_LAST="-- Last record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_TEXT="Ordering will be available after saving."
COM_HELLOWORLD_HELLOWORLD_FIELD_LANGUAGE_DESC="Select the appropriate language"
COM_HELLOWORLD_IMAGE_FIELDS="Image details"
COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_LABEL="Select image"
COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_DESC="Select an image from the library, or upload a new one"
COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_LABEL="Alt text"
COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_DESC="Alternative text (if image cannot be displayed)"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_LABEL="Caption"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_DESC="Provide a caption for the image"
COM_HELLOWORLD_HELLOWORLD_HEADING_GREETING="Greeting"
COM_HELLOWORLD_HELLOWORLD_HEADING_ID="Id"
COM_HELLOWORLD_MANAGER_HELLOWORLD_EDIT="HelloWorld manager: Edit Message"
COM_HELLOWORLD_MANAGER_HELLOWORLD_NEW="HelloWorld manager: New Message"
COM_HELLOWORLD_MANAGER_HELLOWORLDS="HelloWorld manager"
COM_HELLOWORLD_EDIT_HELLOWORLD="Edit message"
COM_HELLOWORLD_N_ITEMS_DELETED_1="One message deleted"
COM_HELLOWORLD_N_ITEMS_DELETED_MORE="%d messages deleted"
COM_HELLOWORLD_N_ITEMS_PUBLISHED="%d message(s) published"
COM_HELLOWORLD_N_ITEMS_UNPUBLISHED="%d message(s) unpublished"
COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL="Greeting"
COM_HELLOWORLD_HELLOWORLD_GREETING_DESC="Add Hello World Greeting"
COM_HELLOWORLD_SUBMENU_MESSAGES="Messages"
COM_HELLOWORLD_SUBMENU_CATEGORIES="Categories"
COM_HELLOWORLD_CONFIGURATION="HelloWorld Configuration"
COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_LABEL="Messages settings"
COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_DESC="Settings that will be applied to all messages by default"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL="Captcha"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC="Select Captcha to use on front end form"
COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_LABEL="User to email"
COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_DESC="Select user to email when a new message is entered on front end"
COM_HELLOWORLD_FIELDSET_RULES="Message Permissions"
COM_HELLOWORLD_FIELD_RULES_LABEL="Permissions"
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to edit this message?"
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to delete this message?"
COM_HELLOWORLD_TAB_NEW_MESSAGE="New Message"
COM_HELLOWORLD_TAB_EDIT_MESSAGE="Message Details"
COM_HELLOWORLD_TAB_PARAMS="Parameters"
COM_HELLOWORLD_TAB_ASSOCIATIONS="Associations"
COM_HELLOWORLD_TAB_PERMISSIONS="Permissions"
COM_HELLOWORLD_TAB_IMAGE="Image"
COM_HELLOWORLD_LEGEND_DETAILS="Message Details"
COM_HELLOWORLD_LEGEND_PARAMS="Message Parameters"
COM_HELLOWORLD_LEGEND_ASSOCIATIONS="Message Associations"
COM_HELLOWORLD_LEGEND_PERMISSIONS="Message Permissions"
COM_HELLOWORLD_LEGEND_IMAGE="Image info"
; Column ordering in the Helloworlds view
COM_HELLOWORLD_ORDERING_ASC="Ordering ascending"
COM_HELLOWORLD_ORDERING_DESC="Ordering descending"
COM_HELLOWORLD_GREETING_ASC="Greeting ascending"
COM_HELLOWORLD_GREETING_DESC="Greeting descending"
COM_HELLOWORLD_AUTHOR_ASC="Author ascending"
COM_HELLOWORLD_AUTHOR_DESC="Author descending"
COM_HELLOWORLD_CREATED_ASC="Creation date ascending"
COM_HELLOWORLD_CREATED_DESC="Creation date descending"
COM_HELLOWORLD_PUBLISHED_ASC="Unpublished first"
COM_HELLOWORLD_PUBLISHED_DESC="Published first"
COM_HELLOWORLD_LANGUAGE_ASC="Language ascending"
COM_HELLOWORLD_LANGUAGE_DESC="Language descending"
COM_HELLOWORLD_ASSOCIATION_ASC="Association ascending"
COM_HELLOWORLD_ASSOCIATION_DESC="Association descending"
; Helloworld menuitem - selecting a greeting via modal
COM_HELLOWORLD_MENUITEM_SELECT_MODAL_TITLE="Select greeting"
COM_HELLOWORLD_MENUITEM_SELECT_HELLOWORLD="Select"
COM_HELLOWORLD_MENUITEM_SELECT_BUTTON_TOOLTIP="Select a helloworld greeting"
; Checking in records
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_0="No records checked in."
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_1="%d record successfully checked in."
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_MORE="%d records successfully checked in."

Die Komponenten verpacken

Der Inhalt deines Codeverzeichnisses. Jeder folgende Dateilink führt dich zu dem Schritt im Tutorial, der die aktuellste Version dieser Quellcodedatei aufweist.

helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.0" method="upgrade">

	<name>COM_HELLOWORLD</name>
	<!-- The following elements are optional and free of formatting constraints -->
	<creationDate>January 2018</creationDate>
	<author>John Doe</author>
	<authorEmail>john.doe@example.org</authorEmail>
	<authorUrl>http://www.example.org</authorUrl>
	<copyright>Copyright Info</copyright>
	<license>License Info</license>
	<!--  The version string is recorded in the components table -->
	<version>0.0.26</version>
	<!-- The description is optional and defaults to the name -->
	<description>COM_HELLOWORLD_DESCRIPTION</description>

	<!-- Runs on install/uninstall/update; New in 2.5 -->
	<scriptfile>script.php</scriptfile>

	<install> <!-- Runs on install -->
		<sql>
			<file driver="mysql" charset="utf8">sql/install.mysql.utf8.sql</file>
		</sql>
	</install>
	<uninstall> <!-- Runs on uninstall -->
		<sql>
			<file driver="mysql" charset="utf8">sql/uninstall.mysql.utf8.sql</file>
		</sql>
	</uninstall>
	<update> <!-- Runs on update; New since J2.5 -->
		<schemas>
			<schemapath type="mysql">sql/updates/mysql</schemapath>
		</schemas>
	</update>

	<!-- Site Main File Copy Section -->
	<!-- Note the folder attribute: This attribute describes the folder
		to copy FROM in the package to install therefore files copied
		in this section are copied from /site/ in the package -->
	<files folder="site">
		<filename>index.html</filename>
		<filename>helloworld.php</filename>
		<filename>controller.php</filename>
		<filename>router.php</filename>
		<folder>controllers</folder>
		<folder>views</folder>
		<folder>models</folder>
		<folder>helpers</folder>
	</files>

		<languages folder="site/language">
			<language tag="en-GB">en-GB/en-GB.com_helloworld.ini</language>
			<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.ini</language>
		</languages>

	<media destination="com_helloworld" folder="media">
		<filename>index.html</filename>
		<folder>images</folder>
		<folder>js</folder>
		<folder>css</folder>
	</media>

	<administration>
		<!-- Administration Menu Section -->
		<menu link='index.php?option=com_helloworld' img="../media/com_helloworld/images/tux-16x16.png">COM_HELLOWORLD_MENU</menu>
		<!-- Administration Main File Copy Section -->
		<!-- Note the folder attribute: This attribute describes the folder
			to copy FROM in the package to install therefore files copied
			in this section are copied from /admin/ in the package -->
		<files folder="admin">
			<!-- Admin Main File Copy Section -->
			<filename>index.html</filename>
			<filename>config.xml</filename>
			<filename>helloworld.php</filename>
			<filename>controller.php</filename>
			<filename>access.xml</filename>
			<!-- SQL files section -->
			<folder>sql</folder>
			<!-- tables files section -->
			<folder>tables</folder>
			<!-- models files section -->
			<folder>models</folder>
			<!-- views files section -->
			<folder>views</folder>
			<!-- controllers files section -->
			<folder>controllers</folder>
			<!-- helpers files section -->
			<folder>helpers</folder>
		</files>
		<languages folder="admin/language">
			<language tag="en-GB">en-GB/en-GB.com_helloworld.ini</language>
			<language tag="en-GB">en-GB/en-GB.com_helloworld.sys.ini</language>
			<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.ini</language>
			<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.sys.ini</language>
		</languages>
	</administration>

</extension>

Mitwirkende