Compare with Joomla Native

Extension Generator

Windwalker has a powerful extension generator, you can generate components, modules and plugins by default template or customize your templates.

Windwalker

$ php bin/windwalker generator init com_flower sakura.sakuras [--client] [--tmpl]

Joomla

# None...

Component Entry

Simpler component entry than Joomla native. You can override component initialise process in your Component object.

Windwalker

// No direct access
defined('_JEXEC') or die;

include_once JPATH_COMPONENT_ADMINISTRATOR . '/src/init.php';

echo with(new FlowerComponent)->execute();
                    

Joomla

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

// require helper files
JLoader::register('FlowerHelper', dirname(__FILE__) . '/helpers/flower.php');

$controller = JControllerLegacy::getInstance('Flower');

$controller->execute(JFactory::getApplication()->input->get('task'));

$controller->redirect();
                    

Controller

Tasks like: display, save, update, reorder and delete etc. They are all has default controllers in Windwalker. You don't need to write them out, just override it if you require a new process.

Windwalker

// Use default controller
                    

Joomla

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

class FlowerControllerflower extends JControllerAdmin
{
    public function getModel($name = 'flowers', $prefix = 'FlowerModel')
    {
        $model = parent::getModel($name, $prefix, array('ignore_request' => true));

        return $model;
    }
}
                    

View Prepare Data

Tired to write getItem(), getPagination() and other methods again and again? Windwalker auto loaded them in View object. You can also override default behavior.

Windwalker

protected function prepareData()
{
    // Use default
}
                    

Joomla

public function display($tpl = null)
{
    $this->state      = $this->get('State');
    $this->items      = $this->get('Items');
    $this->pagination = $this->get('Pagination');

    WeblinksHelper::addSubmenu('flowers');

    // Check for errors.
    if (count($errors = $this->get('Errors')))
    {
        JError::raiseError(500, implode("\n", $errors));
        return false;
    }

    $this->addToolbar();

    $this->sidebar = JHtmlSidebar::render();

    parent::display($tpl);
}
                    

View Toolbar

Setting toolbar in Joomla is very bother, Windwalker prepared a set of default buttons. You can just configure them to decide which should show on page. Also, the ACL has auto handled.

Windwalker

protected function configureToolbar($buttonSet = array(), $canDo = null)
{
    // Get default button set.
    $buttonSet = parent::configureToolbar($buttonSet, $canDo);

    // In debug mode, we remove trash button but use delete button instead.
    if (JDEBUG)
    {
        $buttonSet['trash']['access']  = false;
        $buttonSet['delete']['access'] = true;
    }

    return $buttonSet;
}
                    

Joomla

protected function addToolbar()
{
    require_once JPATH_COMPONENT . '/helpers/flower.php';

    $state = $this->get('State');
    $canDo = JHelperContent::getActions('com_weblinks', 'category', $state->get('filter.category_id'));
    $user  = JFactory::getUser();

    $bar = JToolBar::getInstance('toolbar');

    JToolbarHelper::title(JText::_('COM_WEBLINKS_MANAGER_WEBLINKS'), 'link weblinks');
    if (count($user->getAuthorisedCategories('com_weblinks', 'core.create')) > 0)
    {
        JToolbarHelper::addNew('weblink.add');
    }
    if ($canDo->get('core.edit'))
    {
        JToolbarHelper::editList('weblink.edit');
    }
    if ($canDo->get('core.edit.state')) {

        JToolbarHelper::publish('weblinks.publish', 'JTOOLBAR_PUBLISH', true);
        JToolbarHelper::unpublish('weblinks.unpublish', 'JTOOLBAR_UNPUBLISH', true);

        JToolbarHelper::archiveList('weblinks.archive');
        JToolbarHelper::checkin('weblinks.checkin');
    }
    if ($state->get('filter.state') == -2 && $canDo->get('core.delete'))
    {
        JToolbarHelper::deleteList('', 'weblinks.delete', 'JTOOLBAR_EMPTY_TRASH');
    } elseif ($canDo->get('core.edit.state'))
    {
        JToolbarHelper::trash('weblinks.trash');
    }
    // Add a batch button
    if ($user->authorise('core.create', 'com_weblinks') && $user->authorise('core.edit', 'com_weblinks') && $user->authorise('core.edit.state', 'com_weblinks'))
    {
        JHtml::_('bootstrap.modal', 'collapseModal');
        $title = JText::_('JTOOLBAR_BATCH');

        // Instantiate a new JLayoutFile instance and render the batch button
        $layout = new JLayoutFile('joomla.toolbar.batch');

        $dhtml = $layout->render(array('title' => $title));
        $bar->appendButton('Custom', $dhtml, 'batch');
    }
    if ($user->authorise('core.admin', 'com_weblinks'))
    {
        JToolbarHelper::preferences('com_weblinks');
    }

    JToolbarHelper::help('JHELP_COMPONENTS_WEBLINKS_LINKS');
}
                    

Filter and Batch Configuration

Windwalekr Grid system is very smart, you can configure search, filter and batch in XML files. Save you from the query building hell.

Windwalker

<form>
    <!-- Batch -->
    <fields name="batch">
        <field name="access"
                type="accesslevel"
                label="JLIB_HTML_BATCH_ACCESS_LABEL"
                description="JLIB_HTML_BATCH_ACCESS_LABEL_DESC"
                labelclass="control-label"
                class="input-xlarge inputbox"
                >
            <option>JLIB_HTML_BATCH_NOCHANGE</option>
        </field>

        <field name="language"
                type="contentlanguage"
                label="JLIB_HTML_BATCH_LANGUAGE_LABEL"
                description="JLIB_HTML_BATCH_LANGUAGE_LABEL_DESC"
                labelclass="control-label"
                class="input-xlarge inputbox"
                >
            <option>JLIB_HTML_BATCH_LANGUAGE_NOCHANGE</option>
        </field>

        <field name="created_by"
                type="winduser"
                label="JAUTHOR"
                description="JLIB_HTML_BATCH_USER_LABEL_DESC"
                labelclass="control-label"
                class="input-xlarge inputbox"
                >
            <option>JSELECT</option>
        </field>

        <field name="catid"
                type="category"
                label="JLIB_HTML_BATCH_MENU_LABEL"
                description=""
                extension="com_flower"
                labelclass="control-label"
                class="input-xlarge inputbox"
                action="true"
                >
            <option>JOPTION_SELECT_CATEGORY</option>
        </field>

        <field name="task"
                type="radio"
                label="JLIB_RULES_ACTION"
                description=""
                labelclass="control-label"
                class="combo btn-group"
                default="sakuras.batch.move"
                >
            <option value="sakuras.batch.move">JLIB_HTML_BATCH_MOVE</option>
            <option value="sakuras.batch.copy">JLIB_HTML_BATCH_COPY</option>
        </field>
    </fields>
</form>
                    

Joomla

// No, you have to use php to write Batch in controller yourself
                    

Model List

Windwalekr provides a powerful QueryHelper and FilterHelper, you don't need to configure so much terrible things in ModelList.

Windwalker

class FlowerModelSakuras extends ListModel
{
    /**
     * Some default filter fields will be merged to
     * the auto-generated fields
     */
	protected $filterFields = array('sakura.start_date');

	protected function configureTables()
	{
		$queryHelper = $this->getContainer()->get('model.sakuras.helper.query', Container::FORCE_NEW);

		$queryHelper->addTable('sakura', '#__flower_sakuras')
			->addTable('category',  '#__categories', 'sakura.catid = category.id')
			->addTable('user',      '#__users',      'sakura.created_by = user.id')
			->addTable('viewlevel', '#__viewlevels', 'sakura.access = viewlevel.id')
			->addTable('lang',      '#__languages',  'sakura.language = lang.lang_code');

        // Auto generate filter fields, you can also write it manually
		$this->filterFields = array_merge(
            $this->filterFields,
            $queryHelper->getFilterFields()
        );
	}

	protected function populateState($ordering = 'id', $direction = 'ASC')
	{
        // All filter and search will be handled by parent.
		parent::populateState($ordering, $direction);
	}

	protected function processFilters(\JDatabaseQuery $query, $filters = array())
	{
		if (!isset($filters['sakura.state']) && property_exists($this->getTable(), 'state'))
		{
			$query->where($query->quoteName('sakura.state') . ' >= 0');
		}

		return parent::processFilters($query, $filters);
	}

    protected function configureFilters($filterHelper)
	{
		// Override filter behavior here
	}

	protected function configureSearches($searchHelper)
	{
		// Override search behavior here
	}
}
                    

Joomla

class FlowerModelSakuras extends JModelList
{
	public function __construct($config = array())
	{
		if (empty($config['filter_fields']))
		{
			$config['filter_fields'] = array(
				'id', 'a.id',
				'title', 'a.title',
				'alias', 'a.alias',
				'checked_out', 'a.checked_out',
				'checked_out_time', 'a.checked_out_time',
				'catid', 'a.catid', 'category_title',
				'state', 'a.state',
				'access', 'a.access', 'access_level',
				'created', 'a.created',
				'created_by', 'a.created_by',
				'ordering', 'a.ordering',
				'featured', 'a.featured',
				'language', 'a.language',
				'hits', 'a.hits',
				'publish_up', 'a.publish_up',
				'publish_down', 'a.publish_down',
				'url', 'a.url',
			);
		}

		parent::__construct($config);
	}

	protected function populateState($ordering = null, $direction = null)
	{
		$search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
		$this->setState('filter.search', $search);

		$accessId = $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access', null, 'int');
		$this->setState('filter.access', $accessId);

		$published = $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'string');
		$this->setState('filter.state', $published);

		$categoryId = $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id', '');
		$this->setState('filter.category_id', $categoryId);

		$language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '');
		$this->setState('filter.language', $language);

		$tag = $this->getUserStateFromRequest($this->context . '.filter.tag', 'filter_tag', '');
		$this->setState('filter.tag', $tag);

		$params = JComponentHelper::getParams('com_Sakuras');
		$this->setState('params', $params);

		parent::populateState('a.title', 'asc');
	}

	protected function getListQuery()
	{
		// Create a new query object.
		$db = $this->getDbo();
		$query = $db->getQuery(true);
		$user = JFactory::getUser();

		$query->select(
            'a.id, a.title, a.alias, a.checked_out, a.checked_out_time, a.catid,' .
            'a.hits,' .
            'a.state, a.access, a.ordering,' .
            'a.language, a.publish_up, a.publish_down'
        );

		$query->from($db->quoteName('#__flower_sakuras') . ' AS a');

		$query->select('l.title AS language_title')
			->join('LEFT', $db->quoteName('#__languages') . ' AS l ON l.lang_code = a.language')
            ->select('uc.name AS editor')
			->join('LEFT', '#__users AS uc ON uc.id=a.checked_out')
            ->select('ag.title AS access_level')
			->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access')
            ->select('c.title AS category_title')
			->join('LEFT', '#__categories AS c ON c.id = a.catid');

		if ($access = $this->getState('filter.access'))
		{
			$query->where('a.access = ' . (int) $access);
		}

		if (!$user->authorise('core.admin'))
		{
			$groups = implode(',', $user->getAuthorisedViewLevels());
			$query->where('a.access IN (' . $groups . ')');
		}

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

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

		$categoryId = $this->getState('filter.category_id');

		if (is_numeric($categoryId))
		{
			$query->where('a.catid = ' . (int) $categoryId);
		}

		$search = $this->getState('filter.search');

		if (!empty($search))
		{
			if (stripos($search, 'id:') === 0)
			{
				$query->where('a.id = ' . (int) substr($search, 3));
			}
			else
			{
				$search = $db->quote('%' . $db->escape($search, true) . '%');
				$query->where('(a.title LIKE ' . $search . ' OR a.alias LIKE ' . $search . ')');
			}
		}

		if ($language = $this->getState('filter.language'))
		{
			$query->where('a.language = ' . $db->quote($language));
		}

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

		if ($orderCol == 'a.ordering' || $orderCol == 'category_title')
		{
			$orderCol = 'c.title ' . $orderDirn . ', a.ordering';
		}

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

		return $query;
	}
}

                    

Quick Start

$ php composer.phar create-project windwalker/joomla-rad libraries/windwalker 2.*

Documentation

This document is for Windwalker Joomla RAD, if you are finding Windwalker PHP framework, please see: Windwalker Framework