Is it possible to create custom HTML attributes on CHtml::checkboxList?
For example, I want to generate an input like this, adding the custom attribute "data-input-x":
<input class="customClass" id="Model_inputX_0" value="1" name="Model[relationX][]" type="checkbox" data-input-x="3">
I already tried using the code bellow, but it not worked:
echo $form->checkboxList($model, 'relationX', $dataList, array('class'=>'checkboxFase refeicaoFaseComum', 'data-input-x'=>3));
If you run your code and inspect element it you will see values the values created by Yii, the difference. Echos under a foreach loop will work nicely..
You can extend CHtml like that:
In folder "components" you create a new file named MyCHtml. In there create the class MyCHtml and copy the core code of framework for checkBoxList (https://github.com/yiisoft/yii/blob/1.1.16/framework/web/helpers/CHtml.php#L1123).
class MyCHtml extends CHtml {
//Final method is provided below
}
Then you add the parameter $extraAttributes=array() after $htmlOptions=array().
The trick is to add those attributes and their values at $htmlOptions array of each input.
If all your configurations are correct and you have access to your componenets as normal, you can call the new checkBoxList function like this:
<?php
//Values can be created dynamically or statically depending on situation
//Each value corresponds to each checkbox value that you want to contain the extra attribute
$extraAttributes = array(
'data-input-x'=>array(
6=>'k',
11=>'a',
7=>'b'),
'data-input-y'=>array(
6=>'c',
2=>'d'),
);
echo MyCHtml::checkboxList(($name, $select, $data, $htmlOptions, $extraAttributes);
?>
The whole class is the following:
<?php
class MyCHtml extends CHtml
{
/**
* Generates a list box.
* ...
* #param array $extraAttributes extra HTML attributes corresponding on each checkbox
* ...
*/
public static function checkBoxList($name,$select,$data,$htmlOptions=array(), $extraAttributes=array())
{
$template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}';
$separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:self::tag('br');
$container=isset($htmlOptions['container'])?$htmlOptions['container']:'span';
unset($htmlOptions['template'],$htmlOptions['separator'],$htmlOptions['container']);
if(substr($name,-2)!=='[]')
$name.='[]';
if(isset($htmlOptions['checkAll']))
{
$checkAllLabel=$htmlOptions['checkAll'];
$checkAllLast=isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast'];
}
unset($htmlOptions['checkAll'],$htmlOptions['checkAllLast']);
$labelOptions=isset($htmlOptions['labelOptions'])?$htmlOptions['labelOptions']:array();
unset($htmlOptions['labelOptions']);
$items=array();
$baseID=isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : self::getIdByName($name);
unset($htmlOptions['baseID']);
$id=0;
$checkAll=true;
foreach($data as $value=>$labelTitle)
{
$checked=!is_array($select) && !strcmp($value,$select) || is_array($select) && in_array($value,$select);
$checkAll=$checkAll && $checked;
$htmlOptions['value']=$value;
$htmlOptions['id']=$baseID.'_'.$id++;
//********This does the trick
foreach($extraAttributes as $attributesKey => $attributesValue) {
$found = false;
foreach($attributesValue as $subAttributesKey => $subAttributesValue) {
if ($value === $subAttributesKey) {
$htmlOptions[$attributesKey] = $subAttributesValue;
$found = true;
break;
}
}
if (!$found) {
$htmlOptions[$attributesKey] = '';
}
}
//********All the rest is the same with core method
$option=self::checkBox($name,$checked,$htmlOptions);
$beginLabel=self::openTag('label',$labelOptions);
$label=self::label($labelTitle,$htmlOptions['id'],$labelOptions);
$endLabel=self::closeTag('label');
$items[]=strtr($template,array(
'{input}'=>$option,
'{beginLabel}'=>$beginLabel,
'{label}'=>$label,
'{labelTitle}'=>$labelTitle,
'{endLabel}'=>$endLabel,
));
}
if(isset($checkAllLabel))
{
$htmlOptions['value']=1;
$htmlOptions['id']=$id=$baseID.'_all';
$option=self::checkBox($id,$checkAll,$htmlOptions);
$beginLabel=self::openTag('label',$labelOptions);
$label=self::label($checkAllLabel,$id,$labelOptions);
$endLabel=self::closeTag('label');
$item=strtr($template,array(
'{input}'=>$option,
'{beginLabel}'=>$beginLabel,
'{label}'=>$label,
'{labelTitle}'=>$checkAllLabel,
'{endLabel}'=>$endLabel,
));
if($checkAllLast)
$items[]=$item;
else
array_unshift($items,$item);
$name=strtr($name,array('['=>'\\[',']'=>'\\]'));
$js=<<<EOD
jQuery('#$id').click(function() {
jQuery("input[name='$name']").prop('checked', this.checked);
});
jQuery("input[name='$name']").click(function() {
jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
});
jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
EOD;
$cs=Yii::app()->getClientScript();
$cs->registerCoreScript('jquery');
$cs->registerScript($id,$js);
}
if(empty($container))
return implode($separator,$items);
else
return self::tag($container,array('id'=>$baseID),implode($separator,$items));
}
public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=array())
{
self::resolveNameID($model,$attribute,$htmlOptions);
$selection=self::resolveValue($model,$attribute);
if($model->hasErrors($attribute))
self::addErrorCss($htmlOptions);
$name=$htmlOptions['name'];
unset($htmlOptions['name']);
if(array_key_exists('uncheckValue',$htmlOptions))
{
$uncheck=$htmlOptions['uncheckValue'];
unset($htmlOptions['uncheckValue']);
}
else
$uncheck='';
$hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false);
$hidden=$uncheck!==null ? self::hiddenField($name,$uncheck,$hiddenOptions) : '';
return $hidden . self::checkBoxList($name,$selection,$data,$htmlOptions);
}
/**
* Generates a push Html button that can submit the current form in POST method.
* #param string $label the button label
* #param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {#link normalizeUrl} for more details.
* #param array $ajaxOptions AJAX options (see {#link ajax})
* #param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
* attributes are also recognized (see {#link clientChange} and {#link tag} for more details.)
* #return string the generated button
*/
public static function ajaxSubmitHtmlButton($label,$url,$ajaxOptions=array(),$htmlOptions=array())
{
$ajaxOptions['type']='POST';
$htmlOptions['type']='submit';
return self::ajaxHtmlButton($label,$url,$ajaxOptions,$htmlOptions);
}
}
Related
I'm building the admin for a Magento2 store (currently on 2.1.7, they want to use the newest version until we go live and then want to stabilize a particular version). The module in question is supposed to display all existing orders, with an actionsColumn that contains links to cancel, edit, and open a detailed overview of the purchased items associated with that order. The order detail page contains a grid view that should display all order items associated with an order number passed in the URL.
In order to filter out Order Items that don't relate to the specific Order Number, I've extended the \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult class. This works except for one weird caveat. If, in the addFieldToFilter call, I replace $ordNum with, say, '10000', it grabs the correct data. When using $ordNum to call this dynamically, however, it returns no rows at all. This despite trying all sorts of casting and === checks to ensure that there's no difference between the hardcoded and dynamic values. Is this a Magento bug? I can't at all figure out why this would be the case.
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$ordNum = $request->getParam('order_num');
$this->addFieldToFilter('order_num', ['eq' => $ordNum]); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
I just fixed it by using mentioned below steps
Store param value in session in controller
public function execute() {
$this->_catalogSession->setTokenId($this->request->getParam('entity_id'));
$this->_view->loadLayout();
$this->_view->loadLayoutUpdates();
$this->_view->getPage()->getConfig()->getTitle()->set(__('Redeem Token History'));
$this->_view->renderLayout();
}
Use session value in dataprovider
$tokensCollection->addFieldToFilter('token_id', ['eq' => $this->_catalogSession->getTokenId()]);
Enjoy :)
Try this in place of the getParam statement:
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
Just to make sure we are on the same page, this is the full code:
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
$this->addFieldToFilter('order_num', $ordNum); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
We have solved this issue by doing the following :
/**
* CcCustompriceProductListingDataProvider constructor.
* #param string $name
* #param string $primaryFieldName
* #param string $requestFieldName
* #param \Magento\Framework\Api\Search\ReportingInterface $reporting
* #param \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder
* #param \Magento\Framework\App\RequestInterface $request
* #param \Magento\Framework\Api\FilterBuilder $filterBuilder
* #param array $meta
* #param array $data
* #throws \Exception
*/
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
ReportingInterface $reporting,
SearchCriteriaBuilder $searchCriteriaBuilder,
RequestInterface $request,
FilterBuilder $filterBuilder,
array $meta = [],
array $data = []
) {
$data['config']['filter_url_params']['product_id'] = $request->getParam('cppc_product_id', 0);
parent::__construct($name, $primaryFieldName, $requestFieldName, $reporting, $searchCriteriaBuilder, $request, $filterBuilder, $meta, $data);
}
You do not need to use any other function. The reason why this is is because it is also updated with an update URL and that does not have that parameter. By using adding that to the data it also parses that into the update url.
You can see that here (Parent function)
/**
* #return void
*/
protected function prepareUpdateUrl()
{
if (!isset($this->data['config']['filter_url_params'])) {
return;
}
foreach ($this->data['config']['filter_url_params'] as $paramName => $paramValue) {
if ('*' == $paramValue) {
$paramValue = $this->request->getParam($paramName);
}
if ($paramValue) {
$this->data['config']['update_url'] = sprintf(
'%s%s/%s/',
$this->data['config']['update_url'],
$paramName,
$paramValue
);
$this->addFilter(
$this->filterBuilder->setField($paramName)->setValue($paramValue)->setConditionType('eq')->create()
);
}
}
}
I'm trying to implement a category attribute meant to link the current categories with other categories for a project specific usage.
For that I created a varchar attribute meant to store a coma separated list of category IDs, but I'd like to have the small picker icon next to the field which would display the category chooser, like the one in the condition section of the promotion admin screen.
I don't really see the kind of renderer I should implement to achieve that, I hope you guys will give me a hint.
Thanks for helping.
I made it - finally - so here it goes:
FYI: My category attribute is named "category_top_searches" and is a coma separated list of category IDs. The picker is meant to help contributing this list.
1 - Add a tab using an observer
A . Observer declaration
<adminhtml_catalog_category_tabs>
<observers>
<add_topsearches_tab>
<class>mymodule/observer</class>
<method>addTopSearchesTab</method>
</add_topsearches_tab>
</observers>
</adminhtml_catalog_category_tabs>
B . Observer implementation
public function addTopSearchesTab(Varien_Event_Observer $observer)
{
$tabs = $observer->getTabs();
$tabs->addTab(
'top_searches_tab', 'mymodule/catalog_category_edit_tab_topsearches'
);
}
2 - Define the block for the Tab
Need to implement the tab interface methods and the methods to fetch checked categories when initializing the tree
class MyNamespace_Adminhtml_Block_Catalog_Category_Edit_Tab_Topsearches
extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Categories
implements Mage_Adminhtml_Block_Widget_Tab_Interface
{
/**
* Specify template to use
*/
public function __construct()
{
parent::__construct();
$this->setTemplate('catalog/category/edit/categorypicker.phtml');
}
/**
* Checks when this block is readonly
*
* #return bool
*/
public function isReadonly()
{
return false;
}
/**
* getCategoryIds method
* #return array
*/
protected function getCategoryIds()
{
$category = Mage::registry('current_category');
$ids = explode(',', $category->getCategoryTopSearches());
return $ids;
}
/**
* getIdsString
*
* #return string
*/
public function getIdsString()
{
$category = Mage::registry('current_category');
return $category->getCategoryTopSearches();
}
/**
* Return Tab label
*
* #return string
*/
public function getTabLabel()
{
return $this->__('Top Searches');
}
/**
* Return Tab title
*
* #return string
*/
public function getTabTitle()
{
return $this->__('Top Searches');
}
/**
* Can show tab in tabs
*
* #return boolean
*/
public function canShowTab()
{
return true;
}
/**
* Tab is hidden
*
* #return boolean
*/
public function isHidden()
{
return false;
}
}
3 - Define the tree template
I copy pasted the core template of the category tree of product edit page app/design/adminhtml/default/default/template/catalog/product/edit/categories.phtml into app/design/adminhtml/default/mytheme/template/catalog/category/edit/categorypicker.phtml and made some slight changes :
Added a text field to display my IDs:
<div class="entry-edit">
<div class="entry-edit-head">
<h4 class="icon-head head-edit-form fieldset-legend"><?php echo Mage::helper('catalog')->__('Linked Categories') ?></h4>
</div>
<fieldset id="linked_cat">
<input type="text" name="linked_categories_list" id="linked_categories_list" value="<?php echo $this->getIdsString() ?>">
</fieldset>
</div>
And modified the tree header to be more adequate with my context
<div class="entry-edit">
<div class="entry-edit-head">
<h4 class="icon-head head-edit-form fieldset-legend"><?php echo Mage::helper('catalog')->__('Category picker') ?></h4>
</div>
<fieldset id="grop_fields">
<input type="hidden" name="linked_categories" id="linked_categories" value="<?php echo $this->getIdsString() ?>">
<div id="product-categories" class="tree"></div>
</fieldset>
</div>
I had to modify slightly the beforeLoad function to set the id param:
categoryLoader.on("beforeload", function(treeLoader, node) {
treeLoader.baseParams.category = node.attributes.id;
treeLoader.baseParams.id = node.attributes.id;
});
I added a function to implement a array_unique feature
Array.prototype.unique = function(){
'use strict';
var im = {}, uniq = [];
for (var i=0;i<this.length;i++){
var type = (this[i]).constructor.name,
// ^note: for IE use this[i].constructor!
val = type + (!/num|str|regex|bool/i.test(type)
? JSON.stringify(this[i])
: this[i]);
if (!(val in im)){uniq.push(this[i]);}
im[val] = 1;
}
return uniq;
}
And then modified the categoryAdd and categoryRemove functions to update my text field with the newly checked/unchecked ID
function categoryAdd(id) {
var ids = $('linked_categories').value.split(',');
ids.push(id);
var idList = ids.unique().join(',').replace(/^,/, '');
document.getElementById("linked_categories_list").value = idList;
$('linked_categories').value = ids.join(',');
}
function categoryRemove(id) {
var ids = $('linked_categories').value.split(',');
// bug #7654 fixed
while (-1 != ids.indexOf(id)) {
ids.splice(ids.indexOf(id), 1);
}
var idList = ids.unique().join(',').replace(/^,/, '');
document.getElementById("linked_categories_list").value = idList;
$('linked_categories').value = ids.join(',');
}
4 - Implement an observer to the category save event to inject the new value into the real attribute
A. Declaration
<catalog_category_save_before>
<observers>
<set_top_searches>
<class>mymodule/observer</class>
<method>setTopSearches</method>
</set_top_searches>
</observers>
</catalog_category_save_before>
B. Implementation
/**
* takes the fake attribute value and insert it into the real category_top_searches attribute
*
* #param Varien_Event_Observer $observer Observer
*
* #return void
*/
public function setTopSearches(Varien_Event_Observer $observer)
{
/** #var $category Mage_Catalog_Model_Category */
$category = $observer->getEvent()->getCategory();
$params = Mage::app()->getRequest()->getParams();
$ids = $params['linked_categories_list'];
$category->setData('category_top_searches', $ids);
}
5 - Finally I hid my original attribute by updating it in a setup script:
(I changed its group otherwise its dedicated tab would be empty but still displayed)
$installer->updateAttribute("catalog_category", "category_top_searches", 'group', "General");
$installer->updateAttribute("catalog_category", "category_top_searches", 'is_visible', false);
Dunno if anybody will use it since I'm pretty alone on this thread but I would had loved to find such a post when trying to achieve this feature... :D
If anyone is interested, I also implemented this category picker in configuration fields as a new frontend_type, it's pretty great !
I'm trying to achieve the following scenario:
1. user display the page addBook.php
2. user starts filling the form
3. but when he wants to select the book Author from the Author combo box, the Author is not yet created in the database so the user clicks a link to add a new Author
5. user is redirected to addAuthor.php
6. the user fill the form and when he submits it, he goes back to addBook.php with all the previous data already present and the new Author selected.
The things is: I have scenarios where there is more than one level of recursion. (Example: Add Book => Add Author => Add Country)
How can I do that?
At step #3, the link submit the form so that I can save it in session.
To handle recursion, I can use a Stack and push the current from on the Stack each time I click a link. And pop the last form of the Stack when the user completes the action correctly or click a cancel button.
My problem is:
How can I handle the back button of the browser?
If instead of clicking the "cancel" button, the user click on the back button, how could I kown that I need to pop the last element?
Do you known some common pattern to achieve that?
You must use javascript on the client and hook into the window unload event, serialize the form and send the answer to the server, which saves it in the session.
$(window).unload(function() {
$.ajax({
url : 'autosave.php',
data : $('#my_form').serialize()
});
});
on server
// autosave.php
$_SESSION['autosave_data'] = $_POST['autosave_data'];
// addbook.php
if (isset($_SESSION['autosave_data'])) {
// populate the fields
}
This is the solution I developed to answer my problem.
As the problem was not a client side problem but truly a server side one. Following the php classes I used in my project:
First the main class of the stack functionality. The inclusion need to be done before the session_start as the object will be stored in the session
class Stack {
private $stack;
private $currentPosition;
private $comeFromCancelledAction = false;
public function __construct() {
$this->clear();
}
/* ----------------------------------------------------- */
/* PUBLICS METHODS */
/* ----------------------------------------------------- */
/**
* Clear the stack history
*/
public function clear() {
$this->stack = array();
$this->currentPosition = -1;
}
/**
* get the current position of the stack
*/
public function getCurrentPosition() {
return $this->currentPosition;
}
/**
* Add a new element on the stack
* Increment the current position
*
* #param $url the url to add on the stack
* #param $data optionnal, the data that could be stored with this $url
*/
public function add($url, &$data = array()) {
if (count($this->stack) != $this->currentPosition) {
// the currentPosition is not the top of the stack
// need to slice the array to discard dirty urls
$this->stack = array_slice($this->stack, 0, $this->currentPosition+1);
}
$this->currentPosition++;
$this->stack[] = array('url' => $url, 'data' => $data, 'previousData' => null, 'linked_data' => null);
}
/**
* Add the stack position parameter in the URL and do a redirect
* Exit the current script.
*/
public function redirect() {
header('location:'.$this->addStackParam($this->getUrl($this->currentPosition)), 301);
exit;
}
/**
* get the URL of a given position
* return null if the position is not valid
*/
public function getUrl($position) {
if (isset($this->stack[$position])) {
return $this->stack[$position]['url'];
} else {
return null;
}
}
/**
* get the Data of a given position
* return a reference of the data
*/
public function &getData($position) {
if (isset($this->stack[$position])) {
return $this->stack[$position]['data'];
} else {
return null;
}
}
/**
* Update the context of the current position
*/
public function storeCurrentData(&$data) {
$this->stack[$this->currentPosition]['data'] = $data;
}
/**
* store some data that need to be fixed in sub flow
* (for example the id of the parent object)
*/
public function storeLinkedData($data) {
$this->stack[$this->currentPosition]['linked_data'] = $data;
}
/**
* Update the context of the current position
*/
public function storePreviousData(&$data) {
$this->stack[$this->currentPosition]['previousData'] = $data;
}
/**
* Compute all linked data for every positions before the current one and return an array
* containing all keys / values
* Should be called in sub flow to fixed some data.
*
* Example: if you have tree pages: dad.php, mum.php and child.php
* when creating a "child" object from a "dad", the dad_id should be fixed
* but when creating a "child" object from a "mum", the mum_id should be fixed and a combo for choosing a dad should be displayed
*/
public function getLinkedData() {
$totalLinkedData = array();
for($i = 0; $i < $this->currentPosition; $i++) {
$linkedData = $this->stack[$i]['linked_data'];
if ($linkedData != null && count($linkedData) > 0) {
foreach($linkedData as $key => $value) {
$totalLinkedData[$key] = $value;
}
}
}
return $totalLinkedData;
}
/**
* Main method of the Stack class.
* Should be called on each page before any output as this method should do redirects.
*
* #param $handler StackHandler object that will be called at each step of the stack process
* Let the caller to be notified when something appens.
* #return the data
*/
public function initialise(StackHandler $handler) {
if (!isset($_GET['stack']) || !ctype_digit($_GET['stack'])) {
// no stack info, acces the page directly
$this->clear();
$this->add($this->getCurrentUrl()); //add the ?stack=<position number>
$this->storeLinkedData($handler->getLinkedData());
$this->redirect(); //do a redirect to the same page
} else {
// $_GET['stack'] is set and is a number
$position = $_GET['stack'];
if ($this->currentPosition == $position) {
// ok the user stay on the same page
// or just comme from the redirection
if (!empty($_POST['action'])) {
// user submit a form and need to do an action
if ($_POST['action'] == 'cancel') {
$currentData = array_pop($this->stack);
$this->currentPosition--;
$handler->onCancel($currentData);
// redirect to the next page with ?stack=<current position + 1>
$this->redirect();
} else {
// store the action for future use
$this->stack[$this->currentPosition]['action'] = $_POST['action'];
$currentData = $this->getData($this->currentPosition);
list($currentData, $nextUrl) = $handler->onAction($currentData, $_POST['action']);
// store current form for future use
$this->storeCurrentData($currentData);
// add the new page on the stack
$this->add($nextUrl);
// redirect to the next page with ?stack=<current position + 1>
$this->redirect();
}
} else if (isset($this->stack[$this->currentPosition]['action'])) {
// no action, and an action exists for this position
$currentData = $this->getData($this->currentPosition);
$action = $this->stack[$this->currentPosition]['action'];
if ($this->comeFromCancelledAction) {
//we return from a cancelled action
$currentData = $handler->onReturningFromCancelledAction($action, $currentData);
$this->comeFromCancelledAction = false;
} else {
$previousData = $this->getPreviousData();
if ($previousData != null) {
//we return from a sucessful action
$currentData = $handler->onReturningFromSuccesAction($action, $currentData, $previousData);
$this->resetPreviousData();
}
}
$this->storeCurrentData( $currentData );
}
$currentData = $this->getData($this->currentPosition);
if ($currentData == null) {
$currentData = $handler->getInitialData();
$this->storeCurrentData( $currentData );
}
return $currentData;
} else if ($this->getUrl($position) == $this->getCurrentUrl()) {
// seems that the user pressed the back or next button of the browser
// set the current position
$this->currentPosition = $position;
return $this->getData($position);
} else {
// the position does not exist or the url is incorrect
// redirect to the last known position
$this->redirect();
}
}
}
/**
* call this method after completing an action and need to redirect to the previous page.
* If you need to give some data to the previous action, use $dataForPreviousAction
*/
public function finishAction($dataForPreviousAction = null) {
$pop = array_pop($this->stack);
$this->currentPosition--;
$this->storePreviousData($dataForPreviousAction);
$this->redirect();
}
/* ----------------------------------------------------- */
/* PRIVATE METHODS */
/* ----------------------------------------------------- */
/**
* get the previous data for the current position
* used when a sub flow finish an action to give some data to the parent flow
*/
private function &getPreviousData() {
if (isset($this->stack[$this->currentPosition])) {
return $this->stack[$this->currentPosition]['previousData'];
} else {
return null;
}
}
/**
* get the current url without the stack parameter
*
* Attention: this method calls "basename" on PHP_SELF do strip the folder structure
* and assume that every pages are in the same directory.
*
* The "stack" parameter is removed from the query string
*
* Example: for the page "http://myserver.com/path/to/a.php?id=1&stack=2"
* PHP_SELF will be: /path/to/a.php
* QUERY_STRING wille be: id=1&stack=2
* This method will return: "a.php?id=1"
*/
private function getCurrentUrl() {
$basename = basename($_SERVER['PHP_SELF']);
if ($_SERVER['QUERY_STRING'] != '') {
return $basename.$this->removeQueryStringKey('?'.$_SERVER['QUERY_STRING'], 'stack');
} else {
return $basename;
}
}
/**
* add the "stack" parameter in an url
*/
private function addStackParam($url) {
return $url . (strpos($url, '?') === false ? '?' : '&') . 'stack=' . $this->currentPosition;
}
/**
* Usefull private method to remove a key=value from a query string.
*/
private function removeQueryStringKey($url, $key) {
$url = preg_replace('/(?:&|(\?))'.$key.'=[^&]*(?(1)&|)?/i', "$1", $url);
return $url != '?' ? $url : '';
}
/**
* reset the previous data so that the data are not used twice
*/
private function resetPreviousData() {
$this->stack[$this->currentPosition]['previousData'] = null;
}
}
Then define the abstract StackHandler class
abstract class StackHandler {
/**
* return the initial data to store for this current page
*/
public function &getInitialData() {
return null;
}
/**
* return an array containing the key/values that need to be fixed in sub flows
*/
public function getLinkedData() {
return null;
}
/**
* user ask to go to a sub page
*/
public function onAction(&$currentData, $action) {
$currentData = $_POST;
$nextUrl = $_POST['action'];
return array($currentData, $nextUrl);
}
public function onCancel(&$currentData) {
}
public function onReturningFromCancelledAction($action, &$currentData) {
}
public function onReturningFromSuccesAction($action, &$currentData, $previousData) {
}
}
Then add the following lines at the top of your pages. Adapt the handler it to fit your needs.
// be sure that a stack object exist in the session
if (!isset($_SESSION['stack'])) {
$_SESSION['stack'] = new Stack();
}
$myDad = $_SESSION['stack']->initialise(new DadStackHandler());
class DadStackHandler extends StackHandler {
/**
* return the initial data to store for this current page
*/
public function &getInitialData() {
if(! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])){
// update
$myDad = new Dad($_GET['id_dad']);
} else {
// creation
$myDad = new Dad();
}
return $myDad;
}
/**
* return an array containing the key/values that need to be fixed in sub flows
*/
public function getLinkedData() {
$linkedData = array();
if (! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])) {
$linkedData['id_dad'] = $_GET['id_dad'];
}
return $linkedData;
}
/**
* user ask to go to a sub page
*/
public function onAction(&$myDad, $action) {
//in order not to loose user inputs, save them in the current data
$myDad->name = $_POST['name'];
$nextUrl = null;
// find the next url based on the action name
if ($action == 'child') {
$nextUrl = 'child.php';
}
return array($myDad, $nextUrl);
}
public function onCancel(&$myDad) {
// probably nothing to do, leave the current data untouched
// or update current data
return $myDad;
}
public function onReturningFromCancelledAction($action, &$myDad) {
// probably nothing to do, leave the current data untouched
// called when returning from child.php
return $myDad;
}
public function onReturningFromSuccesAction($action, &$myDad, $newId) {
// update the id of the foreign field if needed
// or update the current data
// not a good example as in real life child should be a list and not a foreign key
// $myDad->childId = $newId;
$myDad->numberOfChildren++;
return $myDad;
}
}
...
if (user submit form and all input are correct) {
if ($myDad->save()) {
// the user finish an action, so we should redirect him to the previous one
if ($_SESSION['stack']->getCurrentPosition() > 0) {
$_SESSION['stack']->finishAction($myDad->idDad);
} else {
// default redirect, redirect to the same page in view more or redirect to a list page
}
}
}
I hope this could help others.
How to add a link category_id added to the admin? (Joomla 2.5)
You could write in the function JToolBarHelper::addNew('select.add'); or other functions...
For example,
index.php?option=com_pictures&view=select&layout=edit&category_id=14
Help, please. Thanks in advance
Reply David F:
Hi, David F. I almost got it.
After clicking "Add" appears category_id=14, and the rest did not work after clicking "Edit", "Save", "Save Close" ... - category_id=0
I've been programming. Here's an example:
...
protected $catid;
public function __construct($config = array()) {
parent::__construct($config);
if (empty($this->catid)) {
$this->catid = JRequest::getInt('category_id', 0);
}
}
protected function allowAdd($data = array()) {
$user = JFactory::getUser();
$categoryId = JArrayHelper::getValue($data, 'catid', JRequest::getInt('filter_category_id'), 'int');
$allow = null;
if ($categoryId) {
$allow = $user->authorise('core.create', $this->option . '.category.' . $categoryId);
}
if ($allow === null) {
return parent::allowAdd($data);
} else {
return $allow;
}
}
protected function allowEdit($data = array(), $key = 'id') {
$recordId = (int) isset($data[$key]) ? $data[$key] : 0;
$categoryId = 0;
if ($recordId) {
$categoryId = (int) $this->getModel()->getItem($recordId)->catid;
}
if ($categoryId) {
return JFactory::getUser()->authorise('core.edit', $this->option . '.category.' . $categoryId);
} else {
return parent::allowEdit($data, $key);
}
}
protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id') {
$append = parent::getRedirectToItemAppend($recordId);
$append .= '&category_id=' . $this->category_id;
return $append;
}
protected function getRedirectToListAppend() {
$append = parent::getRedirectToListAppend();
$append .= '&category_id=' . $this->category_id;
return $append;
}
I believe that the functions that you are looking for are part of JController and should be added to your controller. In your case, this would likely be the select.php controller based on the view name in your url.
You can see a good example of this in the categories component, specifically at administrator/components/com_categories/controllers/category.php.
The following is the code in com_categories. You would want to rename extension to 'category_id' and may want to grab the value from JInput instead of the current class:
/**
* Gets the URL arguments to append to an item redirect.
*
* #param integer $recordId The primary key id for the item.
* #param string $urlVar The name of the URL variable for the id.
*
* #return string The arguments to append to the redirect URL.
*
* #since 1.6
*/
protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
{
$append = parent::getRedirectToItemAppend($recordId);
$append .= '&extension=' . $this->extension;
return $append;
}
/**
* Gets the URL arguments to append to a list redirect.
*
* #return string The arguments to append to the redirect URL.
*
* #since 1.6
*/
protected function getRedirectToListAppend()
{
$append = parent::getRedirectToListAppend();
$append .= '&extension=' . $this->extension;
return $append;
}
*EDIT:
You also have to add it as part of the form. This is the easy, but important part. Just make sure the form has a hidden input with the value or add it to the url in the action section of the form:
<form action="<?php echo JRoute::_('index.php?option=com_component&layout=edit&id='.(int) $this->item->id . '&category_id='.$this->category_id); ?>" method="post" name="adminForm">
or use this:
<input type="hidden" name="category_id" value="<?php echo $this->category_id; ?>" />
To further explain what happens, when you click an item to go to the form, you likely add on the variable that you need. Then you add it to the form so that when one of the items is clicked, it will get submitted with the form. Joomla processes this form and either saves it or not depending on the toolbar button clicked. Then Joomla redirects, either back to the form or to the list view. Without the functions in your controller, your variable gets lost.
Can someone help me find a way when i type a text in an Input text (eg: cars ), and the name of cars (bmw, Chevrolet, etc..) is listed in a select box.
do some one have an idea or an example?
If you are familiar with jQuery and Ajax, this can be performed very simply. You watch the input of the text box and fire off an Ajax request once it reaches a stage you are happy with. The return of the Ajax query is them used to populate a drop down box.
Below is an example of this in practice. You'll need to either have jquery on your server, or reference a copy held within a CDN (such as Google). I've had to edit it a bit in order to protect the innocent, but with a couple of more lines it'll work fine.
The only item you'll need to figure out yourself, it by what means should Ajax be called and therefore your drop down list be populated. I've used it after so many characters, once a text pattern has been recognised, and as well as a hoover off event. The choice is yours.
JAVASCRIPT
<script language="JavaScript" src="./js.jquery.inc.js"></script>
<script language="JavaScript" type="text/javascript">
// This is the pure ajax call, needs some way of being called.
$.ajax({
url: "ajaxCode.php",
cache: false,
type: "POST",
data: ({carID : $("#CARS").val()}),
dataType: "xml",
success: function(data){
populateCars(data);
},
error: function(data){
// Something in here to denote an error.
alert("Error no cars retrieved.");
}
});
function populateCars(data)
{
$(data).find("node").each(function()
{
var carStr = $(this).find("String").text()
var carKey = $(this).find("Key").text()
$("<option value='"+carKey+"'>"+carStr+"</option>").appendTo("#CARSLOOKUP");
});
}
</script>
HTML CODE
<input type="text" id="CARS" value="" >
<select id="CARSLOOKUP">
</select>
AJAXCODE.PHP CODE
<?php
// Post code will come in as a POST variable!
$carCode = $_POST['carID'];
// Need to build up an array of cars to return, based upon the example below.
// Preferably based upon the input given within the car code variable.
foreach($carList as $cars)
{
$returnArray[] = array("Key" => "Vectra", "String" => "Vauxhall Vectra");
}
// Output the headers and data required.
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/xml');
// This simply converts the array and returns formatted XML data (functions listed below).
$xml = new array2xml('cars');
$xml->createNode($returnArray);
echo $xml;
exit(0);
class array2xml extends DomDocument
{
public $nodeName;
private $xpath;
private $root;
private $node_name;
/**
* Constructor, duh
*
* Set up the DOM environment
*
* #param string $root The name of the root node
* #param string $nod_name The name numeric keys are called
*
*/
public function __construct($root='root', $node_name='node')
{
parent::__construct();
/*** set the encoding ***/
$this->encoding = "ISO-8859-1";
/*** format the output ***/
$this->formatOutput = true;
/*** set the node names ***/
$this->node_name = $node_name;
/*** create the root element ***/
$this->root = $this->appendChild($this->createElement( $root ));
$this->xpath = new DomXPath($this);
}
/*
* creates the XML representation of the array
*
* #access public
* #param array $arr The array to convert
* #aparam string $node The name given to child nodes when recursing
*
*/
public function createNode( $arr, $node = null)
{
if (is_null($node))
{
$node = $this->root;
}
foreach($arr as $element => $value)
{
$element = is_numeric( $element ) ? $this->node_name : $element;
$child = $this->createElement($element, (is_array($value) ? null : $value));
$node->appendChild($child);
if (is_array($value))
{
self::createNode($value, $child);
}
}
}
/*
* Return the generated XML as a string
*
* #access public
* #return string
*
*/
public function __toString()
{
return $this->saveXML();
}
/*
* array2xml::query() - perform an XPath query on the XML representation of the array
* #param str $query - query to perform
* #return mixed
*/
public function query($query)
{
return $this->xpath->evaluate($query);
}
} // end of class
?>
jQuery UI has an autocomplete module:
http://jqueryui.com/demos/autocomplete/