I want to extend the custom options feature of Magento. I cant use any of the existing ones like sku or title. I need a completely new one, called "path". What are the steps to add it? It seems adding a row to catalog_product_option is part of it, but I think there is much more to do?
I did something super simple in the past that you may or may not find useful. Our situation was the need for an Auto Complete field in new text options. So in the design\adminhtml\default\default\template\catalog\product\edit\options\type\text.phtml, this is the new javascript var for OptionTemplateText:
OptionTemplateText = '<table class="border" cellpadding="0" cellspacing="0">'+
'<tr class="headings">'+
'<th class="type-price"><?php echo Mage::helper('catalog')->__('Price') ?></th>'+
'<th class="type-type"><?php echo Mage::helper('catalog')->__('Price Type') ?></th>'+
'<th class="type-sku"><?php echo Mage::helper('catalog')->__('SKU') ?></th>'+
'<th class="type-last last"><?php echo Mage::helper('catalog')->__('Max Characters') ?> </th>'+
'<th class="type-last last"><?php echo Mage::helper('catalog')->__('Auto Complete') ?> </th>'+
'</tr>'+
'<tr>'+
'<td><input type="text" class="input-text validate-number product-option-price" id="product_option_{{option_id}}_price" name="product[options][{{option_id}}][price]" value="{{price}}"></td>'+
'<td><?php echo $this->getPriceTypeSelectHtml() ?>{{checkboxScopePrice}}</td>'+
'<td><input type="text" class="input-text" name="product[options][{{option_id}}][sku]" value="{{sku}}"></td>'+
'<td class="type-last last"><input type="text" class="input-text validate-zero-or-greater" name="product[options][{{option_id}}][max_characters]" value="{{max_characters}}"></td>'+
'<td class="type-last last"><input type="text" class="input-text" name="product[options][{{option_id}}][auto_complete]" value="{{auto_complete}}"> * service URL</td>'+
'</tr>'+
'</table>';
Then we overwrote the Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option with our own quick class using this rewrite:
<global>
<blocks>
<adminhtml>
<rewrite>
<catalog_product_edit_tab_options_option>MyNameSpace_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option</catalog_product_edit_tab_options_option>
</rewrite>
</adminhtml>
</blocks>
</global>
And here's the whole class.
class MyNameSpace_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Options_Option {
public function __construct() {
parent::__construct();
$this->setTemplate('catalog/product/edit/options/option.phtml');
}
public function getOptionValues() {
$optionsArr = array_reverse($this->getProduct()->getOptions(), true);
if (!$this->_values) {
$values = array();
$scope = (int) Mage::app()->getStore()->getConfig(Mage_Core_Model_Store::XML_PATH_PRICE_SCOPE);
foreach ($optionsArr as $option) {
/* #var $option Mage_Catalog_Model_Product_Option */
$this->setItemCount($option->getOptionId());
$value = array();
$value['id'] = $option->getOptionId();
$value['item_count'] = $this->getItemCount();
$value['option_id'] = $option->getOptionId();
$value['title'] = $this->htmlEscape($option->getTitle());
$value['type'] = $option->getType();
$value['is_require'] = $option->getIsRequire();
$value['sort_order'] = $option->getSortOrder();
if ($this->getProduct()->getStoreId() != '0') {
$value['checkboxScopeTitle'] = $this->getCheckboxScopeHtml($option->getOptionId(), 'title', is_null($option->getStoreTitle()));
$value['scopeTitleDisabled'] = is_null($option->getStoreTitle())?'disabled':null;
}
if ($option->getGroupByType() == Mage_Catalog_Model_Product_Option::OPTION_GROUP_SELECT) {
$i = 0;
$itemCount = 0;
foreach ($option->getValues() as $_value) {
/* #var $_value Mage_Catalog_Model_Product_Option_Value */
$value['optionValues'][$i] = array(
'item_count' => max($itemCount, $_value->getOptionTypeId()),
'option_id' => $_value->getOptionId(),
'option_type_id' => $_value->getOptionTypeId(),
'title' => $this->htmlEscape($_value->getTitle()),
'price' => $this->getPriceValue($_value->getPrice(), $_value->getPriceType()),
'price_type' => $_value->getPriceType(),
'sku' => $this->htmlEscape($_value->getSku()),
'sort_order' => $_value->getSortOrder(),
);
if ($this->getProduct()->getStoreId() != '0') {
$value['optionValues'][$i]['checkboxScopeTitle'] = $this->getCheckboxScopeHtml($_value->getOptionId(), 'title', is_null($_value->getStoreTitle()), $_value->getOptionTypeId());
$value['optionValues'][$i]['scopeTitleDisabled'] = is_null($_value->getStoreTitle())?'disabled':null;
if ($scope == Mage_Core_Model_Store::PRICE_SCOPE_WEBSITE) {
$value['optionValues'][$i]['checkboxScopePrice'] = $this->getCheckboxScopeHtml($_value->getOptionId(), 'price', is_null($_value->getstorePrice()), $_value->getOptionTypeId());
$value['optionValues'][$i]['scopePriceDisabled'] = is_null($_value->getStorePrice())?'disabled':null;
}
}
$i++;
}
} else {
$value['price'] = $this->getPriceValue($option->getPrice(), $option->getPriceType());
$value['price_type'] = $option->getPriceType();
$value['sku'] = $this->htmlEscape($option->getSku());
$value['max_characters'] = $option->getMaxCharacters();
$value['auto_complete'] = $option->getAutoComplete();
$value['file_extension'] = $option->getFileExtension();
$value['image_size_x'] = $option->getImageSizeX();
$value['image_size_y'] = $option->getImageSizeY();
if ($this->getProduct()->getStoreId() != '0' && $scope == Mage_Core_Model_Store::PRICE_SCOPE_WEBSITE) {
$value['checkboxScopePrice'] = $this->getCheckboxScopeHtml($option->getOptionId(), 'price', is_null($option->getStorePrice()));
$value['scopePriceDisabled'] = is_null($option->getStorePrice())?'disabled':null;
}
}
$values[] = new Varien_Object($value);
}
$this->_values = $values;
}
return $this->_values;
}
}
The last piece to this puzzle was this little bitty sql statement:
ALTER TABLE `catalog_product_option` ADD COLUMN `auto_complete` VARCHAR(255) NULL DEFAULT NULL AFTER `max_caharacters`;
Related
Hello i want to Generate the View Page like this using looping concept,
No CType PNum
1 Cap 12
2 Bottle 23
here is my View Page
#for (int i = 1; i < (Enumerable.Count(#ViewBag.TestComponent_ComponentType)); i++)
{
foreach (string component_type in ViewBag.TestComponent_ComponentType)
{
foreach (string part_number in ViewBag.TestComponent_ComponentPNumb)
{
<td>#i) </td>
<td> #Html.Raw(component_type)</td>
<td>(Part #:#Html.Raw(part_number))</td>
i = i + 1;
}
}
}
and here is my controller
ViewBag.TestComponent_ComponentType = context.Test_Component.Where(x => x.TestRequestId == id).Select(x => x.ComponentType).ToList();
ViewBag.TestComponent_ComponentPNumb = context.Test_Component.Where(x => x.TestRequestId == id).Select(x => x.PartNumber).ToList();
please help me.
First of all, since both collections are with values from the properties of the same object you don't have to build two collections.
I would suggest you try this:
In your controller:
ViewBag.ComponentData = context.Test_Component.Where(x => x.TestRequestId == id).ToList();
In your view:
#{
List<Test_Component> compList = ViewBag.ComponentData;
int count = 1;
}
<table>
<tr>
<th>No</th>
<th>cType</th>
<th>pNum</th>
</tr>
#foreach(Test_Component t in compList)
{
<tr>
<td>#count</td>
<td>#t.ComponentType</td>
<td>#t.PNum</td>
</tr>
count++;
}
</table>
I am using Telerik MVC Grid control to show a data grid. The detail of my grid is calling the following Client Detail template:
<script id="client-template" type="text/x-kendo-template"> <%: Html.Kendo().Grid<ASML_Scheduler.ScheduleService.AgentViewData>()
.Name("grid_#=WorkgroupName#")
.DataSource(dataSource =>
dataSource.Ajax().Read(read => read.Action("Agents_Read", "Home", new {workgroupname= "#=WorkgroupName#", name = ViewData["LoggedInUser"] }))
)
.Columns(columns =>
{
columns.Bound(product => product.AgentName).ClientTemplate("<strong>\\#:AgentName\\#</strong>");
//columns.Bound(product => product.IsLoggedOn);
//columns.Bound(product => product.IsLoggedOn).ClientTemplate("<div class='mystyle'>\\#:IsLoggedOn\\#</div>");
columns.Bound(product => product.IsLoggedOn).ClientTemplate(
"# if (IsLoggedOn != false) { #" +
"<div class='mystyle'>\\#:IsLoggedOn\\#</div>" +
"# } else { #" +
"<div>\\#:IsLoggedOn\\#</div>" +
"# } #"
);
columns.Bound(product => product.IsScheduled);
})
.ToClientTemplate()
%>
</script>
The problem I have is with the IsLoggedOn client template as it does not recognise the IsLoggedOn != false.
Can anyone see what I havedone wrong here.
personally with this sort of thing I prefer doing this.
change this:
columns.Bound(product => product.IsLoggedOn).ClientTemplate(
"# if (IsLoggedOn != false) { #" +
"<div class='mystyle'>\\#:IsLoggedOn\\#</div>" +
"# } else { #" +
"<div>\\#:IsLoggedOn\\#</div>" +
"# } #"
);
to
columns.Bound(product => product.IsLoggedOn)
.ClientTemplate("#=StyleLogin(data.IsLoggedOn)#");
then create a javascript function
function StyleLogin(IsLoggedOn)
{
var value = '';
if (IsLoggedOn != false) {
value = '<div class="mystyle">' + IsLoggedOn + '</div>';
} else {
value = '<div>' + IsLoggedOn + '</div>';
}
return value;
}
This way you can easily debug the code and also reuse the function elsewhere if needed.
My automatic generated base form class is as follows:
abstract class BaseGestorForm extends BaseFormDoctrine {
public function setup() {
$this->setWidgets(array(
'persona_fk' => new sfWidgetFormInputHidden(),
'unitat_fk' => new sfWidgetFormInputHidden(),
'baixa' => new sfWidgetFormDateTime(),
));
$this->setValidators(array(
'persona_fk' => new sfValidatorChoice(array('choices' => array($this->getObject()->get('persona_fk')), 'empty_value' => $this->getObject()->get('persona_fk'), 'required' => false)),
'unitat_fk' => new sfValidatorChoice(array('choices' => array($this->getObject()->get('unitat_fk')), 'empty_value' => $this->getObject()->get('unitat_fk'), 'required' => false)),
'baixa' => new sfValidatorDateTime(array('required' => false)),
));
$this->widgetSchema->setNameFormat('gestor[%s]');
$this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
$this->setupInheritance();
parent::setup();
}
public function getModelName() {
return 'Gestor';
}
}
In the form I added two extra fields (totes_unitats and unitats_a_gestionar). The first field is a drop down list where users select one or more choices and using jquery when a user press a button the options selected are added to unitats_a_gestionar drop down list. At the same time, these options are removed from totes_unitats list.
class GestorForm extends BaseGestorForm {
public function configure() {
unset($this['baixa']);
$this->widgetSchema['persona_fk'] = new sfWidgetFormChoice(array(
'choices' => UsuariLdap::getAllUsuaris()
));
$this->widgetSchema['totes_unitats'] = new sfWidgetFormChoice(array(
'choices' => UnitatTable::findAllUnitatsPerOrdreArray(),
'multiple' => true
));
$this->widgetSchema['unitats_a_gestionar'] = new sfWidgetFormChoice(array(
'choices' => array(),
'multiple' => true
));
$this->widgetSchema->setLabels(array(
'persona_fk' => 'Gestor',
'unitat_fk' => 'Unitat',
'totes_unitats' => 'Totes les unitats',
'unitats_a_gestionar' => 'Unitats a gestionar'
));
$this->validatorSchema->setOption('allow_extra_fields', true);
$this->validatorSchema['persona_fk'] = new sfValidatorString(array('required' => true), array('required' => 'Requerit'));
}
}
Where I find the problem is in the actions file. First of all, I call the executeNouGestor method that renders the form. Then when the user press to proceed and create the Gestor object, it calls executeValidaGestor that validates the form. This last method calls processFormGestor where there is no way to retrieve the unitats_a_gestionar extra field.
public function executeNouGestor(sfWebRequest $request) {
$this->gestorForm = new GestorForm();
}
public function executeValidaGestor(sfWebRequest $request) {
$this->forward404Unless($request->isMethod(sfRequest::POST));
$this->gestorForm = new GestorForm();
$this->processFormGestor($request, $this->gestorForm);
$this->setTemplate('nouGestor');
}
protected function processFormGestor(sfWebRequest $request, sfForm $gestorForm) {
$gestorForm->bind($request->getParameter($gestorForm->getName()), $request->getFiles($gestorForm->getName()));
if ($gestorForm->isValid()) {
var_dump($_POST);
var_dump($request->getParameterHolder()->getAll());
...
}
}
These two var_dump shows me the following information:
var_dump($_POST):
array(2) {
["gestor"]=>
array(2) {
["persona_fk"]=>
string(3) "330"
["_csrf_token"]=>
string(32) "91e18aa0570bfc7558d21ebb4b98f512"
}
["Desar"]=>
string(5) "Desar"
}
var_dump($request->getParameterHolder()->getAll()):
array(4) {
["gestor"]=>
array(2) {
["persona_fk"]=>
string(3) "330"
["_csrf_token"]=>
string(32) "91e18aa0570bfc7558d21ebb4b98f512"
}
["Desar"]=>
string(5) "Desar"
["module"]=>
string(13) "administracio"
["action"]=>
string(12) "validaGestor"
}
So, as you can see, in ["gestor"] there is no track neither totes_unitats nor unitats_a_gestionar extra form fields. I have no idea why. The way I show the form fields in the template is as usual:
<?php echo $gestorForm['persona_fk']->renderLabel(); ?>
<div class="input"><?php echo $gestorForm['persona_fk']->render(); ?></div>
<div class="error-input"><?php echo $gestorForm['persona_fk']->renderError(); ?></div>
<?php echo $gestorForm['totes_unitats']->renderLabel(); ?>
<div class="input multiple"><?php echo $gestorForm['totes_unitats']->render(); ?></div>
<div class="error-input"><?php echo $gestorForm['totes_unitats']->renderError(); ?></div>
<?php echo $gestorForm['unitats_a_gestionar']->renderLabel(); ?>
<div class="input multiple"><?php echo $gestorForm['unitats_a_gestionar']->render(); ?></div>
<div class="error-input"><?php echo $gestorForm['unitats_a_gestionar']->renderError(); ?></div>
I also add the jquery code that manage the options added or removed between the two drop down lists with multiple selection:
function afegirTreureUnitats() {
var boto_afegir = $("#btn-multiple-afegir");
var boto_treure = $("#btn-multiple-treure");
boto_afegir.click(function() {
var selectedItems = $("#gestor_totes_unitats option:selected");
var output = [];
$.each(selectedItems, function(key, e)
{
output.push('<option value="' + e.value + '">' + e.text + '</option>');
});
$("#gestor_unitats_a_gestionar").append(output.join(""));
ordenaOptionsSelect("gestor_unitats_a_gestionar");
selectedItems.remove();
});
boto_treure.click(function() {
var selectedItems = $("#gestor_unitats_a_gestionar option:selected");
var output = [];
$.each(selectedItems, function(key, e)
{
output.push('<option value="' + e.value + '">' + e.text + '</option>');
});
$("#gestor_totes_unitats").append(output.join(""));
ordenaOptionsSelect("gestor_totes_unitats");
selectedItems.remove();
});
}
function ordenaOptionsSelect(idSelect)
{
var options = $('#' + idSelect + ' option');
options.sort(function(a, b)
{
if (a.text > b.text)
return 1;
else if (a.text < b.text)
return -1;
else
return 0;
});
$('#' + idSelect).empty().append(options);
}
$(document).ready(function() {
afegirTreureUnitats();
});
The form rendered has this appearance:
https://drive.google.com/file/d/0B0Mz720p9Q_DN1RnYWIyR0pXOTQ/edit?usp=sharing
I have also found a curious fact. If I select one of the options in the drop down list, either totes_unitats or unitats_a_gestionar they are sent through POST method, but only one (if I select more than one I only can get one of the options selected).
When you use <select> elements on the form, or <input> of type radio or checkbox the browser will send their values on form submission only when the fields have any options selected.
The list of options in the <select> tag is not sent back to the server. Only the values of the options which are actually selected.
You can resolve your problem in two ways:
Either create a JS which will modify your form just before sending it and will select all the items on both your lists. This way your form will send the values and you will be able to work with them on the server side.
Other option is to add two hidden fields which will keep the lists of the options and modify these lists together with the <select> fields.
I have HTML Code 2 input file in view of a action.
<form action="#" id="appForm" name="appForm"
enctype="multipart/form-data" method="post">
<input type="file" name="picture" id="picture">
<input type="file" name="attach_file" id="attach_file">
</form>
After that, i submit this form to a action of controller. Code in action to get Post
if($this->getRequest()->isPost()) {
$item = array_merge_recursive($this->params()->fromPost(), $this->params()->fromFiles());
$item['id'] = $this->params()->fromRoute('id');
$item['action'] = 'edit';
$validator = new ArticleValidator($item);
if($validator->isError()) {
$error = array_merge($error, $validator->getError());
$item = $validator->getData();
}else{
echo ' save data';
die();
}
}
And code in ArticleValidator
namespace Admin\Form;
class ArticleValidator {
protected $_arrError = null;
protected $_arrData;
public function __construct($arrParam = array(), $options = null) {
// avatar
if (!empty($arrParam['picture']['name'])) {
$size = new \Zend\Validator\File\Size(array('min' => 1, 'max' => 2097152));
$ext = new \Zend\Validator\File\Extension('gif,jpg,jpeg,png');
$adapterPicture = new \Zend\File\Transfer\Adapter\Http();
$adapterPicture->setValidators(array($size, $ext), $arrParam['picture']['name']);
if (!$adapterPicture->isValid()) {
$message = $adapterPicture->getMessages();
$this->_arrError['picture'] = current($message);
$arrParam['picture'] = '';
}
}
// attach_file
if (!empty($arrParam['attach_file']['name'])) {
$size = new \Zend\Validator\File\Size(array('min' => 1, 'max' => 5242880));
$ext = new \Zend\Validator\File\Extension('rar,zip,gz,docx,pdf,xlsx');
$adapterFile = new \Zend\File\Transfer\Adapter\Http();
$adapterFile->setValidators(array($size, $ext), $arrParam['attach_file']['name']);
if (!$adapterFile->isValid()) {
$message = $adapterFile->getMessages();
$this->_arrError['attach_file'] = current($message);
$arrParam['attach_file'] = '';
}
}
}
}
When i post a valid image into 'picture' field. $this->_arrError will have "File '' was not uploaded". I check and know that, $adapterPicture also check 'attach_file' field. Because i do not post a file into 'attach_file', so it say not uploaded ( for attach_file).
So, how i can check only for 'picture' field and then check only for 'attach_file'.
PS: I'm not use Zend\Form. Just a HTML code with 2 input type=file. And then, check file use \Zend\File\Transfer\Adapter\Http
I want to get different URLs for different languages.
For example:
http://www.example.com/en/users/create
http://www.example.com/es/usuarios/crear
I have g11n enabled and I tried this:
Router::connect('/users/create',array('controller'=>'User','action'=>'add'));
Router::connect('/usuarios/crear',array('controller'=>'User','action'=>'add'));
But,... in header.html.php where I have the menu code:
<ul id="nav">
<li class="menu-item ">
<?php echo $this->html->link('New User','/user/add') ?>
</li>
</ul>
I need a function that returns the link:
...
when the current language is Spanish.
or
...
when the current language is English.
Sorry for my english...
Edit:
I have solved the problem overriding \lithium\template\helper\Html class.
<?php
namespace app\extensions\helper;
use lithium\core\Environment;
function getLanguage($locale)
{
$res = array_shift(explode('_', $locale));
if(!$res)return '?';
else return $res;
}
class CustomHtml extends \lithium\template\helper\Html {
private static $_mapping = array(
'Users' => array(
'Add' => array(
'es' => '/es/usuarios/crear',
'en' => '/en/users/create',
)
)
);
/**
* Override Helper::link
*/
public function link($title, $url = null, array $options = array()) {
if(is_array($url))
{
if(key_exists('controller', $url))
{
$controller = $url['controller'];
if(key_exists('action', $url))
{
$action = $url['action'];
$locale = key_exists('locale', $url) ? $url['locale'] : getLanguage(Environment::get('locale'));
$new_url = $this->_matchUrl($controller, $action, $locale);
if($new_url)
{
return parent::link($title, $new_url,$options);
}
else
{
die('?');
}
}
}
}
return parent::link($title,$url,$options);
}
private function _matchUrl($controller, $action, $locale)
{
if(isset(self::$_mapping[$controller][$action][$locale]))
{
return self::$_mapping[$controller][$action][$locale];
}
return null;
}
}
Now in template I can use this function to get the correct link:
<?php echo $this->CustomHtml->link('Create user',array('controller'=>'Users', 'action' => 'add')) ?>
I am sure it isn't the best solution. But it works...
Here is an example that includes locale-based routing: https://gist.github.com/nateabele/1512502