В статье представлен пример создания интерфейса для редактирования списка абстрактных элементов.

Постановка задачи


  • Есть платформа BILLmanager;
  • В файле /etc/myconf хранится список элементов, для редактирования которых необходимо создать интерфейс.

Порядок действий


  1. Создайте файл описания плагина /usr/local/mgr5/etc/xml/billmgr_mod_myconf.xml со следующим содержимым:

     <?xml version="1.0" encoding="UTF-8"?>
     <mgrdata>
          <handler name="myconf" type="cgi">
           <func name="myconf"/>
           <func name="myconf.edit"/>
           <func name="myconf.delete"/>
        </handler>
    
    <mainmenu level="30">
          <node name="tool">
            <node name="myconf"/>
          </node>
        </mainmenu>
    
    <metadata name="myconf" type="list" key="item">
          <toolbar>
            <toolbtn func="myconf.edit" type="new"  img="t-new" name="new"/>
            <toolbtn func="myconf.edit" type="edit" img="t-edit" name="edit" default="yes"/>
            <toolbtn func="myconf.delete" type="group" img="t-delete" name="delete"/>
          </toolbar>
          <coldata>
            <col sort="alpha" sorted="yes" name="item" type="data"/>
          </coldata>
        </metadata>
    
    <metadata name="myconf.edit" type="form">
          <form>
            <field name="item">
              <input type="text" name="item"/>
            </field>
          </form>
        </metadata>
    
    <lang name="en">
          <messages name="desktop">
            <msg name="menu_myconf">Test module</msg>
          </messages>
          <messages name="myconf">
            <msg name="title">Test module</msg>
            <msg name="item">Item from myconf</msg>
            <msg name="msg_myconf_delete">Delete item </msg>
            <msg name="hint_new">New item</msg>
            <msg name="hint_edit">Edit item</msg>
            <msg name="hint_delete">Delete item</msg>
          </messages>
          <messages name="myconf.edit">
            <msg name="title">Edit item</msg>
            <msg name="title_new">New item</msg>
            <msg name="item">Item value</msg>
            <msg name="hint_item">The value of the item from myconf</msg>
          </messages>
        </lang>
        <lang name="ru">
          <messages name="desktop">
            <msg name="menu_myconf">Тестовый модуль</msg>
          </messages>
          <messages name="myconf">
            <msg name="title">Тестовый модуль</msg>
            <msg name="item">Элемент из myconf</msg>
            <msg name="msg_myconf_delete">Удалить элемент </msg>
            <msg name="hint_new">Новый элемент</msg>
            <msg name="hint_edit">Редактировать элемент</msg>
            <msg name="hint_delete">Удалить элемент</msg>
          </messages>
          <messages name="myconf.edit">
            <msg name="title">Редактировать элемент</msg>
            <msg name="title_new">Новый элемент</msg>
            <msg name="item">Значение</msg>
            <msg name="hint_item">Значение элемента из myconf</msg>
          </messages>
        </lang>
     </mgrdata>
    CODE
  2. Создайте файл обработчик /usr/local/mgr5/addon/myconf.pl со следующим содержимым:

     #!/usr/bin/perl
     
     use CGI qw/:standard/;
     
     $Q = new CGI;
     $func = $Q->param(func);
     
     print "<doc>";
     
     if( $func eq "myconf" ){
     	&List;
     } elsif( $func eq "myconf.delete" ){
     	&Delete;
     } elsif( $func eq "myconf.edit" ){
     	if(	$Q->param( "sok" ) ){
     		if( $Q->param( "elid" ) ){
     			&Set;
     		} else{
     			&New;
     		}
     		print "<ok/>";
     	} else{
     		&Get;
     	}
     }
     
     print "</doc>";
     exit 0;
     
     sub List {
     	if( open( IN, "/etc/myconf" ) ){
     		while( <IN> ){
     			chomp;
     			print "<elem><item>$_</item></elem>";
     		}
     		close( IN );
     	}
     }
     
     sub Get {
     	$elid = $Q->param( "elid" );
     	print "<elid>$elid</elid><item>$elid</item>" if( $elid );
     }
     
     sub Set {
     	$elid = $Q->param( "elid" );
     	$item = $Q->param( "item" );
     	if( open( IN, "/etc/myconf" ) ){
     		if( open( OUT, ">/etc/myconf.new" ) ){
     			for( <IN> ){
     				chomp;
     				if( $_ eq $elid ){
     					print OUT "$item\n";
     					$ok = 1;
     				} else {
     					print OUT "$_\n";
     				}
     			}
     			close( OUT );
     		}
     		close( IN );
     	}
     
     	if( $ok ){
     		rename( "/etc/myconf.new", "/etc/myconf" );
     		print "<ok/>";
     	} else {
     		print "<error>Item hasn`t been updated</error>";
     	}
     }
     
     sub New {
     	$item = $Q->param( "item" );
     	if( open( ADD, ">>/etc/myconf" ) ){
     		print ADD "$item\n";
     		close( ADD );
     		print "<ok/>";
     	} else {
     		print "<error>Item hasn`t been added</error>";
     	}
     }
     
     sub Delete {
            @all_elem = split(", ", $Q->param( "elid" ));
     	if( open( IN, "/etc/myconf" ) ){
     		if( open( OUT, ">/etc/myconf.new" ) ){
     			for( <IN> ){
     				chomp;
                                    $found = "0";
                                    for my $elid (@all_elem) {
                                       if ($elem eq $_) {
                                         $found = "1";
                                         last;
                                       }
                                    }
     				print OUT "$_\n" if( $found ne "1" );
     			}
     			close( OUT );
     		}
     		close( IN );
     	}
     
     	rename( "/etc/myconf.new", "/etc/myconf" );
     	print "<ok/>";
     }
    CODE
  3. Установите права на файл обработчик:

    chmod 750 /usr/local/mgr5/addon/myconf.pl
    chown 0:0 /usr/local/mgr5/addon/myconf.pl
    CODE
  4. Перезапустите BILLmanager:

    /usr/local/mgr5/sbin/mgrctl -m billmgr exit
    CODE

Пояснения


Создайте описание скрипта-обработчик и укажите, что он будет выполняться при вызове собственных функций. Описание выглядит аналогично описанию событий, только вместо тега event используется тег func:

<handler name="myconf.pl" type="cgi">
   <func>myconf</func>
   <func>myconf.edit</func>
   <func>myconf.delete</func>
</handler>
CODE

Опишите ссылку в меню:

<mainmenu level="30">
   <node name="tool">
   <node name="myconf"/>
   </node>
</mainmenu>
CODE

Опишите, как будет выглядеть таблица с данными:

   <metadata name="myconf" type="list" key="item">
     <toolbar>
       <toolbtn func="myconf.edit" type="new"  img="t-new" name="new"/>
       <toolbtn func="myconf.edit" type="edit" img="t-edit" name="edit" default="yes"/>
       <toolbtn func="myconf.delete" type="group" img="t-delete" name="delete"/>
     </toolbar>
     <coldata>
       <col sort="alpha" sorted="yes" name="item" type="data"/>
     </coldata>
   </metadata>
CODE

В нашем примере, таблица с данными имеет одну колонку, а панель инструментов 3 кнопки: создать, изменить, удалить. Более подробное описание тегов и атрибутов смотрите в Описание списков.

Опишите, как будет выглядеть форма редактирования:

<metadata name="myconf.edit" type="form">
   <form>
     <field name="item">
       <input type="text" name="item"/>
     </field>
   </form>
</metadata>
CODE

В этом примере форма имеет одно поле ввода. Более подробное описание тегов и атрибутов см. в статье Описание форм.

Далее опишите текстовые сообщения для наших новых интерфейсов, для понимания каким образом формируются имена сообщений см. статью Правила именования сообщений

Скрипт обработчик

Получите данные и определите имя вызываемой функции:

 use CGI qw/:standard/;

$Q = new CGI;
 $func = $Q->param(func);
CODE

В зависимости от вызванной функции вызовите соответствующую процедуру:

if( $func eq "myconf" ){
	&List;
} elsif( $func eq "myconf.delete" ){
	&Delete;
} elsif( $func eq "myconf.edit" ){
	if(	$Q->param( "sok" ) ){
		if( $Q->param( "elid" ) ){
			&Set;
		} else{
			&New;
		}
		print "<ok/>";
	} else{
		&Get;
	}
}
CODE

Вывод списка данных:

sub List {
	if( open( IN, "/etc/myconf" ) ){
		while( <IN> ){
			chomp;
			print "<elem><item>$_</item></elem>";
		}
		close( IN );
	}
}
CODE

Считайте построчно файл с данными /etc/myconf и сформируйте XML-документ с данными. Тег elem — обозначает строку данных, и содержит в себе дочерние элементы описывающие данные по столбцам, имя вложенного тега item может быть произвольным, но должно соответствовать атрибуту name в описании столбца таблицы (тег col).

Чтение данных при открытии формы редактирования элемента:

sub Get {
	$elid = $Q->param( "elid" );
	print "<elid>$elid</elid><item>$elid</item>" if( $elid );
}
CODE

Данная функция будет вызвана при открытии формы редактирования, для случая создания и для редактирования имеющегося элемента. В случае если параметр elid не пустой, значит элемент редактируется. Данный параметр содержит значение из колонки списка с именем указанным в атрибуте key тега metadata при описании интерфейса таблицы с данными.

Поскольку в примере достаточно простой интерфейс и форма содержит всего одно поле данных, из файлов ничего не читается, а просто возвращается параметр запрашиваемого элемента. лучшим вариантом будет проверить наличие элемента в файле с данными и в случае его отсутствия вывести ошибку. Тег elid указывает ключевое значение для редактируемого элемента, оно же будет отображено в заголовке формы. Тег item в примере содержит значение, которое будет отображено в поле ввода с именем item.

Редактирование записи:

sub Set {
	$elid = $Q->param( "elid" );
	$item = $Q->param( "item" );
       .......
} 
CODE

При редактировании записи важны 2 параметра:

  • elid — содержит имя (ключевое поле) редактируемого элемента
  • item значение введенное пользователем в поле с именем item.

Далее нужно прочитать файл с данными, найти в нём элемент из параметра elid и заменить его значение на значение параметра item. Если искомый элемент не найден, то вывести сообщение об ошибке.

Создание нового элемента:

sub New {
	$item = $Q->param( "item" );
	if( open( ADD, ">>/etc/myconf" ) ){
		print ADD "$item\n";
		close( ADD );
		print "<ok/>";
	} else {
		print "<error>Item hasn`t been added</error>";
	}
}
CODE

Нужно взять значение из параметра item и добавить его в конец файла с данными. Если файл не удалось открыть по каким-то причинам, то вывести сообщение об ошибке.

Удаление элемента списка:

sub Delete {
	@all_elem = split(", ", $Q->param( "elid" ));
       ......
}
CODE

Поскольку операция удаления является групповой, то есть за один вызов можно удалить несколько элементов, то параметр elid может содержать несколько имен, разделенных символами ", " (запятая и пробел). Сформируйте массив удаляемых элементов, далее прочитайте файл с данными и удалите из него строки содержащиеся в массиве удаляемых элементов.