Prestashop - Translatable form field fails with HelperForm - prestashop

I'm trying to do a Prestashop module, but when trying to set a translatable form field at the configuration form, it fails.
This is the error I get, through the JS console:
[14:33:39.915] ReferenceError: defaultLanguage is not defined # http://localhost:8888/js/admin.js:173
I think that I have well configurated the languajes in the backoffice, so I'm not sure why is this happening.
This is how I try to create the form:
public function displayForm()
{
// Get default Language
$default_lang = (int)Configuration::get('PS_LANG_DEFAULT');
// Init Fields form array
$fields_form[0]['form'] = array(
'legend' => array(
'title' => $this->l('Settings'),
),
'input' => array(
array(
'type' => 'text',
'label' => $this->l('Título de la noticia'),
'name' => 'NOTICIA_TIT',
'size' => 30,
'required' => true,
'lang' => true
),
array(
'type' => 'text',
'label' => $this->l('Imagen de la noticia'),
'name' => 'NOTICIA_IMG',
'size' => 30,
'required' => true,
'enabled' => false
),
array(
'type' => 'file',
'label' => $this->l('Subir nueva imagen'),
'name' => 'NOTICIA_IMG_FILE',
'size' => 30
),
array(
'type' => 'textarea',
'label' => $this->l('Texto de la noticia'),
'name' => 'NOTICIA_TXT',
'required' => true,
'cols' => 30,
'rows' => 4,
'lang' => true
)
),
'submit' => array(
'title' => $this->l('Save'),
'class' => 'button'
)
);
$helper = new HelperForm();
// Module, token and currentIndex
$helper->module = $this;
$helper->name_controller = $this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;
// Language
$helper->default_form_language = $default_lang;
$helper->allow_employee_form_lang = $default_lang;
// Title and toolbar
$helper->title = $this->displayName;
$helper->show_toolbar = true; // false -> remove toolbar
$helper->toolbar_scroll = true; // yes - > Toolbar is always visible on the top of the screen.
$helper->submit_action = 'submit'.$this->name;
$helper->toolbar_btn = array(
'save' =>
array(
'desc' => $this->l('Save'),
'href' => AdminController::$currentIndex.'&configure='.$this->name.'&save'.$this->name.
'&token='.Tools::getAdminTokenLite('AdminModules'),
),
'back' => array(
'href' => AdminController::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminModules'),
'desc' => $this->l('Back to list')
)
);
// Load current value
$helper->fields_value['NOTICIA_TXT'] = Configuration::get('NOTICIA_TXT');
$helper->fields_value['NOTICIA_TIT'] = Configuration::get('NOTICIA_TIT');
$helper->fields_value['NOTICIA_IMG'] = Configuration::get('NOTICIA_IMG');
return $helper->generateForm($fields_form);
}
EDIT: I've seen this in the code.
<script type="text/javascript">
var module_dir = '/modules/';
var id_language = 1;
var languages = new Array();
var vat_number = 1;
// Multilang field setup must happen before document is ready so that calls to displayFlags() to avoid
// precedence conflicts with other document.ready() blocks
// we need allowEmployeeFormLang var in ajax request
allowEmployeeFormLang = 1;
displayFlags(languages, id_language, allowEmployeeFormLang);
$(document).ready(function() {
if ($(".datepicker").length > 0)
$(".datepicker").datepicker({
prevText: '',
nextText: '',
dateFormat: 'yy-mm-dd'
});
});
</script>
languajes variable is created as an empty array. However, this is function displayFlags:
function displayFlags(languages, defaultLanguageID, employee_cookie)
{
if ($('.translatable'))
{
$('.translatable').each(function() {
if (!$(this).find('.displayed_flag').length > 0) {
$.each(languages, function(key, language) {
if (language['id_lang'] == defaultLanguageID)
{
defaultLanguage = language;
return false;
}
});
var displayFlags = $('<div></div>')
.addClass('displayed_flag')
.append($('<img>')
.addClass('language_current')
.addClass('pointer')
.attr('src', '../img/l/' + defaultLanguage['id_lang'] + '.jpg')
.attr('alt', defaultLanguage['name'])
.click(function() {
toggleLanguageFlags(this);
})
);
var languagesFlags = $('<div></div>')
.addClass('language_flags')
.html('Choose language:<br /><br />');
$.each(languages, function(key, language) {
var img = $('<img>')
.addClass('pointer')
.css('margin', '0 2px')
.attr('src', '../img/l/' + language['id_lang'] + '.jpg')
.attr('alt', language['name'])
.click(function() {
changeFormLanguage(language['id_lang'], language['iso_code'], employee_cookie);
});
languagesFlags.append(img);
});
if ($(this).find('p:last-child').hasClass('clear'))
$(this).find('p:last-child').before(displayFlags).before(languagesFlags);
else
$(this).append(displayFlags).append(languagesFlags);
}
});
}
}

I fix the same error that you got but I get another one.
In order to define defaultLanguage, you should fill languages attribute of the helperform. You can do it this way:
$languages = Language::getLanguages(true);
$helper->languages = $languages;
I'm not sure whether you should put true or false for getLanguages... I tired both and I still get this error :
Uncaught SyntaxError: Unexpected token ILLEGAL
It happens here:
languages[0] = {
id_lang: 1,
iso_code: 'en',
name: 'English',
is_default: '<br />
So now, there probably more to do in order to have the property is_default defined... Have you find a way fix your problem?
EDIT :
By setting is_default on your own, it works. But it's ugly...
// Languages
$languages = Language::getLanguages(true);
for($i=0; $i<count($languages); $i++){
if($languages[$i]['id_lang'] == $default_lang){
$languages[$i]['is_default'] = 1;
}else{
$languages[$i]['is_default'] = 0;
}
}
$helper->languages = $languages;

Get the languages from the module controller:
$languages = $this->context->controller->getLanguages();

Related

how to add an admin tab without extending a controller in prestashop?

i would like to add admin tab, i defined a controller and install the tab with this function:
public function installTab($class_name, $tabname) {
$tab = new Tab();
// Define the title of your tab that will be displayed in BO
$tab->name[$this->context->language->id] = $tabname;
// Name of your admin controller
$tab->class_name = $class_name;
// Id of the controller where the tab will be attached
// If you want to attach it to the root, it will be id 0 (I'll explain it below)
$tab->id_parent = 0;
$tab->active = 1;
// Name of your module, if you're not working in a module, just ignore it, it will be set to null in DB
$tab->module = $this->name;
// Other field like the position will be set to the last, if you want to put it to the top you'll have to modify the position fields directly in your DB
$tab->add();
return true;
}
and my controller is defined like this:
class AdminBarCodeGeneratorAdminController extends AdminController
{
/** #var Smarty */
public $smarty;
public function __construct(){
parent::__construct();
}
public function initContent()
{
parent::initContent();
$scan_form=$this->renderForm();
$this->smarty->assign('scan_form',$scan_form);
$this->setTemplate(_PS_MODULE_DIR_.'BarCodeGenerator/views/templates/admin/tabs/scan.tpl');
}
// public function display(){
// $smarty = $this->context->smarty;
// $scan_form=$this->renderForm();
// $smarty->assign('scan_form',$scan_form);
// return $this->display(__FILE__, 'views/templates/admin/tabs/scan.tpl');
// }
protected function renderForm()
{
$this->loadAsset();
$helper = new HelperForm();
$helper->show_toolbar = false;
$helper->table = $this->table;
$helper->module = $this;
$helper->default_form_language = $this->context->language->id;
$helper->allow_employee_form_lang = Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG', 0);
$helper->identifier = $this->identifier;
$helper->submit_action = $action;
$helper->currentIndex = $this->context->link->getAdminLink('AdminModules', false)
.'&configure='.$this->name.'&module_name='.$this->name;
$helper->currentIndex .= '&id_BarCodeGenerator='.(int)Tools::getValue('id_BarCodeGenerator');
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->tpl_vars = array(
'fields_value' => $this->getConfigFormValues(), /* Add values for your inputs */
'languages' => $this->context->controller->getLanguages(),
'id_language' => $this->context->language->id,
);
return $helper->generateForm(array($this->getConfigForm()));
}
public function getConfigFormValues(){
return array(
'prefixe2'=>Tools::getValue('prefixe2'),
'reference'=>Tools::getValue('reference'),
'key'=>Tools::getValue('key')
);
}
protected function getConfigForm(){
return array(
'form' => array(
'legend' => array(
'title' => $this->l('Scan des codes barres'),
'icon' => 'icon-qrcode',
),
'input' => array(
array(
'col' => 3,
'type' => 'text',
//'prefix' => '<i class="icon icon-envelope"></i>',
'desc' => $this->l('Entrez le prefix du code barre'),
'name' => 'prefixe2',
'label' => $this->l('Prefixe'),
'required'=>true
),
array(
'col' => 3,
'type' => 'text',
//'prefix' => '<i class="icon icon-envelope"></i>',
'desc' => $this->l('Entrez la reférence de la commande'),
'name' => 'reference',
'label' => $this->l('Reférence commande'),
'required'=>true
),
array(
'col' => 3,
'type' => 'text',
//'prefix' => '<i class="icon icon-envelope"></i>',
'desc' => $this->l('Entrez la clé du code barre'),
'name' => 'key',
'label' => $this->l('key'),
'required'=>true
),
),
'submit' => array(
'title' => $this->l('Mettre à jour le statut'),
),
),
);
}
}
but the shop i want to deploy it has the particularity the overrides are disabled, so i tried to insert the controller with two methods:
by trying to install modulesRoutes hook
public function hookModuleRoutes($params){
return [
'module-BarCodeGenerator-AdminBarCodeGeneratorAdmin'=>[
'controller'=>'AdminBarCodeGeneratorAdmin'
]
];
}
in this case, the hook did not even install successfully
-by adding manually the controller in the config/routes.yml
scanTab:
path: BarCodeGenerator/demo
methods: [GET]
defaults:
_controller: 'BarCodeGenerator/controllers/admin/AdminBarCodeGeneratorAdminController::initContent'
_legacy_controller: 'AdminBarCodeGeneratorAdminController'
_legacy_link: 'AdminBarCodeGeneratorAdminController'
but none of these methods worked

Prestashop - fields_value added only single char

I have problem with prestashop and form helper. I have form with two input text, i could add default value with fields_value. Unfortunately it doesn't work. My form:
public function displayForm()
{
$default_lang = (int)Configuration::get('PS_LANG_DEFAULT');
$fields_form = array();
$fields_form[0]['form'] = array(
'legend' => array(
'title' => $this->l('My module settings')
),
'input' => array(
array(
'type' => 'text',
'label' => $this->l('Box Description: '),
'name' => 'description',
'lang' => true,
),
array(
'type' => 'text',
'label' => $this->l('Box Description: '),
'name' => 'test_name',
'lang' => true,
)
),
'submit' => array(
'title' => $this->l('Save'),
'class' => 'btn btn-default pull-right'
)
);
$languages = Language::getLanguages();
$helper = new HelperForm();
$helper->module = $this;
$helper->name_controller = $this->name;
$helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->default_form_language = $default_lang;
$helper->allow_employee_form_lang = $default_lang;
$helper->languages = $this->context->controller->getLanguages();
$helper->title = $this->l('tester2');
$helper->show_toolbar = true; // false -> remove toolbar
$helper->toolbar_scroll = true; // yes - > Toolbar is always visible on the top of the screen.
$helper->submit_action = 'homepage_settings';
$helper->tpl_vars = array(
'fields_value' => array('description' => $this->l('hello'), 'test_name' => 'tester2'),
'languages' => $this->context->controller->getLanguages(),
'id_language' => $this->context->language->id
);
return $helper->generateForm($fields_form);
}
When i install module, and go to module configuration then i see 2 form input text with single char instead my text
What am I doing wrong? Next question is what i can do, to my module to make my module work in multi store mode? In the final version, the data in the form will be supplemented from the database, but I want to find out why it does not complete the fields with the whole text.
Your fields are multi languages.
try this:
'fields_value' => array(
'description' => array(1 => $this->l('hello')), 'test_name' => array(1 => 'tester2')),

how to change a bool value in prestashop 1.6 back end by icon click

I want to change my printing status ,I am able to place the icon to change the status but the change is not occurring when I click the Print status icon shown in the image I have placed the callback as changePrintStatus
the code is reaching initProcess() when the change status icon is clicked,But after that what exactly must happen ??Or do i need to call any other function/override function ? (I want to do the same as in customers list where we change active status of customers ,similarly I want to do here for my custom module)
<?php
require_once(_PS_MODULE_DIR_.'eticketprinting/eticketprinting.php');
require_once(_PS_MODULE_DIR_.'eticketprinting/classes/Eticket.php');
class EticketController extends ModuleAdminController
{
public $module;
public $html;
public $tabName = 'renderForm';
public function __construct()
{
$this->tab = 'eticket';
$this->module = new eticketprinting();
$this->addRowAction('edit');
//$this->addRowAction('edit');
// $this->addRowAction('view'); // to display the view
//$this->addRowAction('delete');
$this->explicitSelect = false;
$this->context = Context::getContext();
$this->id_lang = $this->context->language->id;
$this->lang = false;
$this->ajax = 1;
$this->path = _MODULE_DIR_.'eticketprinting';
$this->default_form_language = $this->context->language->id;
$this->table = _DB_KITS_PREFIX_.'print_eticket';
$this->className = 'Eticket';
$this->identifier = 'id_print_eticket';
$this->allow_export = true;
$this->_select = '
id_print_eticket,
ticket_id,
ticket_no,
product_id,
print_status,
date_add,
date_upd
';
$this->name = 'EticketController';
$this->bootstrap = true;
$this->initTabModuleList();
$this->initToolbar();
$this->initPageHeaderToolbar();
// $this->initFieldList();
//$this->initContent();
parent::__construct();
$this->fields_list =
array(
'id_print_eticket' => array(
'title' => $this->l('E-Ticket Print ID'),
'width' => 25,
'type' => 'text',
),
'ticket_id' => array(
'title' => $this->l('Ticket- ID'),
'width' => 140,
'type' => 'text',
),
'ticket_no' => array(
'title' => $this->l('Ticket No'),
'width' => 140,
'type' => 'text',
),
'product_id' => array(
'title' => $this->l('Product ID'),
'width' => 100,
'type' => 'text',
),
'print_status' => array(
'title' => $this->l('Print Status'),
'align' => 'center',
'type' => 'bool',
'callback' => 'changePrintStatus',
'orderby' => false,
),
'date_add' => array(
'title' => $this->l('Date Add'),
'width' => 140,
'type' => 'text',
),
'date_upd' => array(
'title' => $this->l('Date Update'),
'width' => 140,
'type' => 'text',
),
);
}
public function changePrintStatus($value, $eticket)
{
return '<a class="list-action-enable '.($value ? 'action-enabled' : 'action-disabled').'" href="index.php?'.htmlspecialchars('tab=Eticket&id_print_eticket='
.(int)$eticket['id_print_eticket'].'&changePrintVal&token='.Tools::getAdminTokenLite('Eticket')).'">
'.($value ? '<i class="icon-check"></i>' : '<i class="icon-remove"></i>').
'</a>';
}
public function initProcess()
{
parent::initProcess();
//d($this->id_object);
if (Tools::isSubmit('changePrintVal') && $this->id_object) {
if ($this->tabAccess['edit'] === '1') {
//d("reached here");
$this->action = 'change_print_val';
} else {
$this->errors[] = Tools::displayError('You do not have permission to change this.');
}
}
}
public function postProcess()
{
//When generate pdf button is clicked
if (Tools::isSubmit('submitAddeticket')) {
if (!Validate::isDate(Tools::getValue('date_from'))) {
$this->errors[] = $this->l('Invalid "From" date');
}
if (!Validate::isDate(Tools::getValue('date_to'))) {
$this->errors[] = $this->l('Invalid "To" date');
}
if (!Validate::isInt(Tools::getValue('id_product')) || Tools::getValue('id_product')=='' ) {
$this->errors[] = $this->l('Invalid Product/select a product ');
}
if (!count($this->errors)) {
if (count(Ticket::getByProductNDateInterval(Tools::getValue('id_product'),Tools::getValue('date_from'), Tools::getValue('date_to')))) {
//d($this->context->link->getAdminLink('AdminPdf'));
Tools::redirectAdmin($this->context->link->getAdminLink('AdminPdf').'&submitAction=generateEticketPDF&id_product='.urlencode(Tools::getValue('id_product')).'&date_from='.urlencode(Tools::getValue('date_from')).'&date_to='.urlencode(Tools::getValue('date_to')));
}
$this->errors[] = $this->l('No tickets has been found or Ticket Generated Already for this period for Product ID:'.Tools::getValue('id_product').' (Change the Print Status generate the E-Ticket Again)');
}
} else {
parent::postProcess();
}
}
}
I was missing the process function :
**But note that $this->action = 'change_print_val';
and name f the process must be similar in my case processChangePrintVal
So if action is $this->action = 'change_printStatus_val'; then process name must be processChangePrintStatusVal**
I added Below function
/**
* Toggle the Eticket Print Status flag- Here the update action occurs
*/
public function processChangePrintVal()
{
$eticket = new Eticket($this->id_object);
if (!Validate::isLoadedObject($eticket)) {
$this->errors[] = Tools::displayError('An error occurred while updating Eticket Print Status information.');
}
$eticket->print_status = $eticket->print_status ? 0 : 1;
if (!$eticket->update()) {
$this->errors[] = Tools::displayError('An error occurred while Eticket Print Status customer information.');
}
Tools::redirectAdmin(self::$currentIndex.'&token='.$this->token);
}

Additional fields in Prestashop module - changes not displaing, parse error & class missing

I'm usually working with WP, but I need to manipulate a module blockcontactinfos on Prestashop - just simply add two more fields which will be shown on the front end, but somehow it's not working.
I have carrefully copied one of the fields, changed it everywhere but I when trying to clear the cache (in the Performance menu):
2 errors
blockcontactinfos (parse error in /modules/blockcontactinfos/blockcontactinfos.php)
blockcontactinfos (class missing in /modules/blockcontactinfos/blockcontactinfos.php)
Could anybody help me out of this? Thanks a lot in advance. Prestashop is 1.6. Fields are displayed correctly in settings, values saved, but there is the error above and I just can't force the web page to load changed template file.
blockcontactinfos.php (added the ones with _url), lines 31-141, changes marked with // KV:
<?php
if (!defined('_CAN_LOAD_FILES_'))
exit;
class Blockcontactinfos extends Module
{
protected static $contact_fields = array(
'BLOCKCONTACTINFOS_COMPANY',
'BLOCKCONTACTINFOS_ADDRESS',
'BLOCKCONTACTINFOS_ADDRESS_URL',
'BLOCKCONTACTINFOS_PHONE',
'BLOCKCONTACTINFOS_PHONE_URL',
'BLOCKCONTACTINFOS_EMAIL',
);
public function __construct()
{
$this->name = 'blockcontactinfos';
$this->author = 'PrestaShop';
$this->tab = 'front_office_features';
$this->version = '1.2.0';
$this->bootstrap = true;
parent::__construct();
$this->displayName = $this->l('Contact information block');
$this->description = $this->l('This module will allow you to display your e-store\'s contact information in a customizable block.');
$this->ps_versions_compliancy = array('min' => '1.6', 'max' => _PS_VERSION_);
}
public function install()
{
Configuration::updateValue('BLOCKCONTACTINFOS_COMPANY', Configuration::get('PS_SHOP_NAME'));
Configuration::updateValue('BLOCKCONTACTINFOS_ADDRESS', trim(preg_replace('/ +/', ' ', Configuration::get('PS_SHOP_ADDR1').' '.Configuration::get('PS_SHOP_ADDR2')."\n".Configuration::get('PS_SHOP_CODE').' '.Configuration::get('PS_SHOP_CITY')."\n".Country::getNameById(Configuration::get('PS_LANG_DEFAULT'), Configuration::get('PS_SHOP_COUNTRY_ID')))));
Configuration::updateValue('BLOCKCONTACTINFOS_ADDRESS_URL', Configuration::get('PS_SHOP_ADDRESS_URL'));
Configuration::updateValue('BLOCKCONTACTINFOS_PHONE', Configuration::get('PS_SHOP_PHONE'));
Configuration::updateValue('BLOCKCONTACTINFOS_PHONE_URL', Configuration::get('PS_SHOP_PHONE_URL'));
Configuration::updateValue('BLOCKCONTACTINFOS_EMAIL', Configuration::get('PS_SHOP_EMAIL'));
$this->_clearCache('blockcontactinfos.tpl');
return (parent::install() && $this->registerHook('header') && $this->registerHook('footer'));
}
public function uninstall()
{
foreach (Blockcontactinfos::$contact_fields as $field)
Configuration::deleteByName($field);
return (parent::uninstall());
}
public function getContent()
{
$html = '';
if (Tools::isSubmit('submitModule'))
{
foreach (Blockcontactinfos::$contact_fields as $field)
Configuration::updateValue($field, Tools::getValue($field));
$this->_clearCache('blockcontactinfos.tpl');
$html = $this->displayConfirmation($this->l('Configuration updated'));
}
return $html.$this->renderForm();
}
public function hookHeader()
{
$this->context->controller->addCSS(($this->_path).'blockcontactinfos.css', 'all');
}
public function hookFooter($params)
{
if (!$this->isCached('blockcontactinfos.tpl', $this->getCacheId()))
foreach (Blockcontactinfos::$contact_fields as $field)
$this->smarty->assign(strtolower($field), Configuration::get($field));
return $this->display(__FILE__, 'blockcontactinfos.tpl', $this->getCacheId());
}
public function renderForm()
{
$fields_form = array(
'form' => array(
'legend' => array(
'title' => $this->l('Settings'),
'icon' => 'icon-cogs'
),
'input' => array(
array(
'type' => 'text',
'label' => $this->l('Company name'),
'name' => 'BLOCKCONTACTINFOS_COMPANY',
),
array(
'type' => 'textarea',
'label' => $this->l('Address'),
'name' => 'BLOCKCONTACTINFOS_ADDRESS',
),
array(
'type' => 'text',
'label' => $this->l('URL na Google mapy'),
'name' => 'BLOCKCONTACTINFOS_ADDRESS_URL',
),
array(
'type' => 'text',
'label' => $this->l('Phone number'),
'name' => 'BLOCKCONTACTINFOS_PHONE',
),
array(
'type' => 'text',
'label' => $this->l('Telefonní číslo bez mezer'),
'name' => 'BLOCKCONTACTINFOS_PHONE_URL',
),
array(
'type' => 'text',
'label' => $this->l('Email'),
'name' => 'BLOCKCONTACTINFOS_EMAIL',
),
),
'submit' => array(
'title' => $this->l('Save')
)
),
);
$helper = new HelperForm();
$helper->show_toolbar = false;
$helper->table = $this->table;
$lang = new Language((int)Configuration::get('PS_LANG_DEFAULT'));
$helper->default_form_language = $lang->id;
$helper->allow_employee_form_lang = Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG') ? Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG') : 0;
$this->fields_form = array();
$helper->identifier = $this->identifier;
$helper->submit_action = 'submitModule';
$helper->currentIndex = $this->context->link->getAdminLink('AdminModules', false).'&configure='.$this->name.'&tab_module='.$this->tab.'&module_name='.$this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->tpl_vars = array(
'fields_value' => array(),
'languages' => $this->context->controller->getLanguages(),
'id_language' => $this->context->language->id
);
foreach (Blockcontactinfos::$contact_fields as $field)
$helper->tpl_vars['fields_value'][$field] = Tools::getValue($field, Configuration::get($field));
return $helper->generateForm(array($fields_form));
}
}
blockcontactinfos.tpl (added the ones with _url), lines 32-33:
{if $blockcontactinfos_address != ''}<li><pre> {$blockcontactinfos_address|escape:'html':'UTF-8'|nl2br}</pre></li>{/if}
{if $blockcontactinfos_phone != ''}<li>{l s='Tel' mod='blockcontactinfos'} {$blockcontactinfos_phone|escape:'html':'UTF-8'}</li>{/if}

dynamically adding text boxes in drupal form api

I want to have a add more button on clicking which i can add dynamically, textboxes in a drupal form api.. can someone help on this?
Thanks in advance
Take a look at Adding dynamic form elements using AHAH. It is a good guide to learn AHAH with Drupal's form API.
EDIT: For an example, install the Examples for Developers module, it has an AHAH example you can use to help you learn.
Here is an example how to solve this with Ajax in Drupal 7(If anyone want I can convert it also to Drupal 6 using AHAH(name before it became Ajax)).
<?php
function text_boxes_form($form, &$form_state)
{
$number = 0;
$addTextbox = false;
$form['text_lists'] = array
(
'#tree' => true,
'#theme' => 'my_list_theme',
'#prefix' => '<div id="wrapper">',
'#suffix' => '</div>',
);
if (array_key_exists('triggering_element', $form_state) &&
array_key_exists('#name', $form_state['triggering_element']) &&
$form_state['triggering_element']['#name'] == 'Add'
) {
$addTextbox = true;
}
if (array_key_exists('values', $form_state) && array_key_exists('text_lists', $form_state['values']))
{
foreach ($form_state['values']['text_lists'] as $element) {
$form['text_lists'][$number]['text'] = array(
'#type' => 'textfield',
);
$number++;
}
}
if ($addTextbox) {
$form['text_lists'][$number]['text'] = array(
'#type' => 'textfield',
);
}
$form['add_button'] = array(
'#type' => 'button',
'#name' => 'Add',
'#ajax' => array(
'callback' => 'ajax_add_textbox_callback',
'wrapper' => 'wrapper',
'method' => 'replace',
'effect' => 'fade',
),
'#value' => t('Add'),
);
return $form;
}
function ajax_add_textbox_callback($form, $form_state)
{
return $form['text_lists'];
}
function text_boxes_menu()
{
$items = array();
$items['text_boxes'] = array(
'title' => 'Text Boxes',
'description' => 'Text Boxes',
'page callback' => 'drupal_get_form',
'page arguments' => array('text_boxes_form'),
'access callback' => array(TRUE),
'type' => MENU_CALLBACK,
);
return $items;
}