Przejdź do głównej treści

Mechanizm pól połączonych


Czym jest mechanizm pól połączonych

Jest to mechanizm, który wprowadza zmiany na drugim polu, po wprowadzonej zmianie przez użytkownika na pierwszym polu.

Przykład na rejestrach

Użycie znacznika pola zdefiniowanego jako lista wyboru, SQL u innej listy spowoduje jej automatyczne odświeżanie/filtrowanie.

  1. Zastosowanie w rejestrach:
    1. Tworzymy nowy rejestr (jak to zrobić znajdziesz tutaj OpenApi) lub edytujemy już istniejący.
    2. Przechodzimy na zakładkę "Pola".
    3. Dodajemy nowe pole:
      1. W polu "Nazwa pola w bazie" ustawiamy "grupa", chociaż może być każda inna nazwa.
      2. W polu "Nazwa kolumny na liście" ustawiamy nazwę kolumny, która będzie widoczna na liście,
      3. W polu "Etykieta na formularzu" ustawiamy etykiete pola na formularzu dodania nowego wpisu.
      4. W polu "Parametry" wprowadzamy
{"sql":"SELECT grp_id,grpnam FROM groups"}
  1. Dodajemy kolejne nowe pole:
    1. W polu "Nazwa pola w bazie", ustawiamy "pracownik", chociaż może być każda inna nazwa.
    2. W polu "Nazwa kolumny na liście" ustawiamy nazwę kolumny, która będzie widoczna na liście,
    3. W polu "Etykieta na formularzu" ustawiamy etykiete pola na formularzu dodania nowego wpisu.
    4. W polu "Parametry" wprowadzamy
{
   "sql": "SELECT usr_id,usrnam FROM users WHERE is_del IS NOT TRUE AND (CASE WHEN {cregisters.creg_cats.grupa} = '' THEN true ELSE usr_id IN (SELECT usr_id FROM users_link_group WHERE grp_id = nullif({cregisters.creg_cats.grupa},'')::int) END)"
}

Wyjaśnienie powyższego sql-a

  • Token z przykładu {cregisters.creg_cats.grupa} składa się z:
    • nazwy tabeli rejestru, tutaj jest to "cregisters.creg_cats",
    • nazwa pola z bazy danych rejestru, tutaj w przykładzie jest to "grupa",

Taka konfiguracja spowoduje przeładowanie listy pracowników przy każdej zmianie grupy.

Przykład na własnym Dialogu

Na własnym Dialogu podczas tworzenia własnego formularza dodajemy w metodzie create() widgety (listę znajdziesz tutaj), lecz w naszym przykładzie użyliśmy LookupWidget2.

W poniższym przykładzie jest użyty widget LookupWidget2, który posiada możliwość ustawienia eventu na zmianie wartości poprzez metodę setOnChange, do której przekazujemy jako parametr wartość w formie stringa z nazwą funkcji js-owej, która będzie uruchamiana przy każdej zmianie wartości na tym konkretnie polu formularza.

$this->contid->setOnChange('App.'.$this->name.'changeUser(this);');

Następnie trzeba utworzyć funkcję w js-ie i ją dodać, używamy \JScript::add(), możemy ją dodać w metodzie toHtml() lub tak jak jest w przykładzie w metodzie create()

\JScript::add('App.'.$this->name.'changeUser = function(obj) {
    if (obj.value) {
        asyncLibrary.execute(\'./scripts/Test/CustomUsersLookupManager.inc\',
        \'CustomUsersLookupManager\',\'\', 
        \'getUsers\', ({cont: obj.value, name__:\''.$this->user->getName().'\'}).toJSONString(), 
        \'if(text)$(\\\''.$this->user->getName(TRUE).'\\\').innerHTML=text;\', null, FAST);
    }
}');

Możemy też dodać odwołanie do stałej w klasie, gdzie będziemy mieć tak samo jak wyżej JS-a, który będzie odpytywał jakaś klasę i metodę, a że dobrym nawykiem jest grupowanie metod, które są odpowiedzialne za tą samą przestrzeń biznesową w jednej klasie, dodaliśmy tutaj właśnie takie odwołanie.

CustomUsersLookupManager::manage($this->copeid, ['contid' => $this->contid->name]);

W Js-ie dodajemy, asynchroniczne wykonanie (syncLibrary.execute), określonej klasy (CustomUsersLookupManager) oraz metody (getUsers) z podanymi parametrami w formie JSON-a. Więcej informacji jak wygląda ów manager znajdziesz tutaj

Następnie dodajemy kolejne pole, które określa wybraną osobę kontaktową, używamy już tutaj istniejącego silnika wyszukiwania ContactPersonsSearchEngine

$this->copeid = new LookupWidget2($this->name.'_copeid', new ContactPersonsSearchEngine(), FALSE, TRUE);
$this->copeid->setFilterString('defcnt IS NOT NULL');
$this->copeid->setLabel(Translator::translate('Osoba kontaktowa'));

Na końcu tworzymy ostatnie pole, które będzie wyświetlało opcje, które już są przefiltrowane wg. wyboru z pierwszego pola, które wykorzystuje naszego SearchEngine UsersSearchEngine, którego znajdziesz tutaj,

$this->user = new LookupWidget2($this->name.'_user', new UsersSearchEngine(), FALSE, TRUE);
$this->user->setLabel(Translator::translate('Opiekun klienta'));

Przykładowy Dialog

Kod
<?php

namespace ReadyApp\Test;

require_once('./classes/LookupWidget/LookupWidget2.inc');

use Application;
use ContactPersonsLookupManager;
use ContactPersonsSearchEngine;
use ContactSearchEngine;
use CustomUsersLookupManager;
use DialogComposer;
use DialogForm;
use LookupWidget2;
use Translator;
use WidgetException;

class TestTicketDialog extends DialogForm
{
    /**
     * Lista method do których możemy się odwołać przez JS-a
     * @var string[]
     */
    protected static $cMethods = [
        'toHtml' => 'toHtml',
    ];
    /**
     * @var mixed
     */
    private $dscrptToCopy;
    /**
     * @var bool
     */
    private $ticketIsHelp = FALSE;

    private $dscrptFromEmail = FALSE;

    /**
     * TicketDialog constructor.
     *
     * @param      $name
     * @param null $caption
     * @param int  $dstyle
     */
    public function __construct($name, $caption = NULL, $dstyle = BS_DIALOG) {

        parent::__construct($name, NULL, BS_DIALOG);
        $this->path = Application::makeRelativePath(__FILE__);
        $this->HWND = $this->createHWND();
        $this->withoutCaption = TRUE;
        $this->width = '500px';
        $this->height = '650px';

        if (($this->isOpened()) && (!$this->noNeedForCreate())) {
            $this->create();
        }
    }

    /**
     * @return bool[]
     */
    protected function getSupportedParams(): array {
        return [];
    }

    /**
     * @param array $params
     * @param array $data
     *
     * @return bool|void
     */
    public function open($params = NULL, &$data = NULL) {
        $data = [];
        parent::open();
        $this->create();
    }

    /**
     * @return bool|void
     * @throws WidgetException
     * @throws \Exception
     */
    public function save() {
        $sData = $this->getSData();
        $wData = $this->getWData();

        // Wywołanie walidacji danych
        $this->validate($wData);
        $this->jsClose(TRUE);
    }

    /**
     * @return false|void
     */
    protected function create() {
        /**
         * Sprawdzamy czy dialog został już wcześniej otworzony
         */
        if (!$this->isOpened()) {
            return FALSE;
        }
        $data = $this->getSData();

        $composer = new DialogComposer($this);
        $composer->addModernHeader(Translator::translate('Utwórz nowy ticket'), 'eic eic-e-service', '#2c3e50');

        $this->contid = new LookupWidget2($this->name.'contid', new ContactSearchEngine(), FALSE, 1);
        $this->contid->setLabel(Translator::translate('Klient'));
        $this->contid->width = 'calc( 100% - '.(2 * DialogForm::SPACE_LEFT).'px )';
        $this->contid->setOnChange('App.'.$this->name.'changeUser(this);');

        $composer->addNext($this->contid, 'contid', DialogForm::P_TO_LEFT);

        /* Osoba kontaktowa z maila */
        $this->copeid = new LookupWidget2($this->name.'_copeid', new ContactPersonsSearchEngine(), FALSE, TRUE);
        $this->copeid->setFilterString('defcnt IS NOT NULL');
        $this->copeid->setLabel(Translator::translate('Osoba kontaktowa'));
        CustomUsersLookupManager::manage($this->copeid, ['contid' => $this->contid->name]);
        $composer->addNext($this->copeid, 'copeid');

        $this->user = new LookupWidget2($this->name.'_user', new UsersSearchEngine(), FALSE, TRUE);
        $this->user->setLabel(Translator::translate('Opiekun klienta'));

        $composer->addNext($this->user, 'usr');

        \JScript::add('App.'.$this->name.'changeUser = function(obj) {
            if (obj.value) {
               asyncLibrary.execute(\'./scripts/Test/CustomUsersLookupManager.inc\',
                \'CustomUsersLookupManager\',\'\', 
                \'getUsers\', ({cont: obj.value, name__:\''.$this->user->getName().'\'}).toJSONString(), 
                \'if(text)$(\\\''.$this->user->getName(TRUE).'\\\').innerHTML=text;\', null, FAST);
            }
        }');

        /**
         * Dodanie przycisków Zapisu i Anuluj
         */
        $composer->addModernButtonsPanel([
            'bSave',
            'bCancel',
        ]);
        $this->add($composer);
    }

    /**
     * Walidaca danych z formularza
     * @param array $data
     *
     * @return void
     */
    private function validate(array $data) {
        // W tym miejscu zrob wywolanie walidatora
    }
}

Custom Users Lookup Manager

Przykład kodu
<?php

require_once('./classes/LookupWidget/LookupWidget2.inc');
        
use ReadyApp\Test\UsersSearchEngine;

class CustomUsersLookupManager
{
    public static $cMethods = [
        'getUsers',
    ];

    public function setByPKey($params) {

        $data = JSON::toArray($params);
        $userId = isset($data['usr_id']) ? $data['usr_id'] : FALSE;
        $contid = $data['contid'];

        $searchEngine = new UsersSearchEngine();
        $lookup = new LookupWidget2('', $searchEngine);
        $lookup->autoClear = TRUE;

        $sqlFilter = [];

        if ($contid) {
            $db = \PgManager::getInstance();
            $macrtk = $db->select('contacts','macrtk','contid = '.$contid.'');
            $sqlFilter[] = 'usr_id = '.$macrtk.'';
        }

        if ($sqlFilter) {
            $searchEngine->append(' AND ('.implode(' AND ', $sqlFilter).')');
            $lookup->search('');

            return $lookup->toHtml('fast');
        }

        return FALSE;
    }

    public function getUsers($params) {
        $data = JSON::toArray($params);

        $contid = (int)$data['cont'];
        $name__ = $data['name__'];

        $db = \PgManager::getInstance();
        $macrtk = $db->select('contacts','macrtk','contid = '.$contid.'', false, PGSQL_ASSOC);
        $searchEngine = new UsersSearchEngine();
        $lookup = new LookupWidget2($name__, $searchEngine);
        $lookup->autoClear = TRUE;

        if (is_numeric($contid) && isset($macrtk[0]['macrtk'])) {
            $searchEngine->append(' AND usr_id = '.$macrtk[0]['macrtk'].'');
            $lookup->search('');
            return $lookup->toHtml('fast');
        }

        return false;
    }

    public static function manage(LookupWidget2 $lookup, $params = array(), $onlyDropFun = FALSE): void {
        JScript::add('App.'.$lookup->getName().'onAdd = function(usr_id) {
            asyncLibrary.execute(\'./script/Test/CustomUsersLookupManager.inc\',
            \'\\ReadyApp\\Test\\CustomUsersLookupManager\',
            \'\', 
            \'setByPKey\', 
             ({usr_id:usr_id,names:\''.$lookup->getName().'\'}).toJSONString(), 
             \'if(text)$(\\\''.$lookup->getName(TRUE).'\\\').innerHTML=text;\', null,FAST);
        }');
    }
}

Users Search Engine

Przykład kodu
<?php

namespace ReadyApp\Test;

class UsersSearchEngine extends \AbsSearchEngine
{
    /**
     * Nazwa kolumny z identyfikatorem rekordu
     *
     * @var string
     * @access public
     */
    public $keyCol = 'usr_id';

    /**
     * Nazwa kolumny, której zawartość wyświetli się w polu Lookup`a po wyszukaniu frazy
     *
     * @var string
     * @access public
     */
    public $labelCol = 'names';

    /**
     * Kwerenda, służąca do zasilenia silnika w rekordy
     *
     * @var string
     * @access public
     */
    public $select = 'SELECT usr_id, firnam || \' \' || lasnam AS names FROM public.users';

    /**
     * Lista pól, po których będzie można szukać
     *
     * @var array
     * @access public
     */
    public $fields = ['usr_id', 'firnam','lasnam'];

    /**
     * Lista pól, po których lista zostanie posortowana
     *
     * @var string
     * @access public
     */
    public $order = 'lasnam';

    /**
     * Czy pokazać ikonę po wybraniu rekordu (z lewej strony)
     * Ikona musi być zdefiniowana w MapService (lub CustomMapService)
     *
     * @var bool
     * @access public
     */
    public $showInfoIcon = FALSE;

    /**
     * Clsnam z MapService (lub CustomMapService)
     * @var string
     */
    public $gicon = '';

    /**
     * @access public
     * @return void
     */
    public function __construct() {
        $this->infoMessage = &$_SESSION[get_class($this)]['infoMessage'];
        if (empty($this->infoMessage)) {
            $this->infoMessage = \Translator::translate('Wybrany użytkownik nie został znaleziony.');
        }
        parent::__construct();
        $this->append(' AND ent_id = '.\SysContext::$ent_id.' AND is_del IS NOT TRUE');
    }
}