Блог

    Caché (Кашэ́) — промышленная высокопроизводительная, объектная система управления базами данных, интегрированная с технологией разработки веб-приложений. Единая архитектура данных Caché позволяет разработчикам использовать одновременно объектный, реляционный (SQL) и прямой (NoSQL) доступ к одним и тем же данным, хранение которых обеспечивается ориентированным на транзакции многомерным ядром СУБД.

    http://www.intersystems.ru/cache/

Последние записи


Теги

Информация

Новые визуальные HTML5 ZEN-компоненты в СУБД Caché 2013.2

добавлено: 24 май 13
понравилось:0
просмотров: 2194
комментов: 0

теги:

Автор: servit

Этот же пост доступен и на хабре.

Стоит отметить, что новые визуальные HTML5 компоненты, интерфейс которых оптимизирован в том числе и под мобильные устройства, и которые используют формат JSON для получения данных, появились уже в СУБД Caché версии 2013.1, но не все из них пока работают "в полную силу".

Это такие компоненты как:

  • %ZEN.Component.accordionMenu
  • %ZEN.Component.toolbar
  • %ZEN.Component.navigator
  • %ZEN.Component.lookup
  • %ZEN.Component.dataGrid
В справочнике классов вы можете найти всю документацию по этим компонентам. Здесь же будут приведены несколько вводных примеров их использования (со скриншотами).

Пример №1: <accordionMenu>

Данный компонент представляет собой специальное меню с максимально допустимым числом уровней равным три.
В примере используется два компонента: один получает данные с клиента, другой - с сервера.
+ <accordionMenu>
Код класса страницы
Class html5.test1 Extends %ZEN.Component.page
{

/// Этот блок Style содержит определение CSS-стиля страницы.
XData 
Style
{
<
style type="text/css">
</
style>
}

/// Этот XML блок описывает содержимое этой страницы.
XData 
Contents [ XMLNamespace "http://www.intersystems.com/zen" ]
{
<
page xmlns="http://www.intersystems.com/zen" title="">
<
jsonProvider id="json" OnGetArray="SrvGetData"/>
<
hgroup cellVAlign="top">

<!-- Получение данных с сервера -->
<accordionMenu controllerId="json" onselect="zenPage.selectList(key,action,targetId);" selectedIndex="2"/>

<!-- Получение данных с клиента -->
<accordionMenu ongetdata="return zenPage.getdata();" onselect="zenPage.selectList(key,action,targetId);"/>

</
hgroup>
</
page>
}

ClientMethod getdata() [ Language = javascript ]
{
 
return {children:[{key:'key0',
                     caption:
'caption0',
                     action:
'action0',
                     targetId:
'id0',
                     image:
'deepsee/add_64.png',
                     imageStyle:
'border:1px solid red;',
                     style:
'background:red;',
                     children:[{key:
'key01',
                                caption:
'caption01',
                                action:
'action01',
                                targetId:
'id01',
                                image:
'images/save.png'}]
                  }]
 };
}

Method SrvGetData(
  
ByRef pParameters,
  
Output pMetaData,
  
Output pDataAs %Status
{
  
Set pMetaData $LB("key","caption","action","targetId","image","imageStyle","style")

  
Set pData(1) = $LB("key1","caption1","action1","id1","")
  
Set pData(1,1) = $LB("key11","caption11","action11","id11","images/saveas.png")
  
Set pData(1,1,1) = $LB("key111","caption111","action111","id111","deepsee/cancel_48.png")
  
Set pData(1,1,2) = $LB("key112","caption112","action112","id112","deepsee/cancel_48.png")
  
Set pData(2) = $LB("key2","caption2","action2","id2","images/save.png","border-radius:10px;")
  
Set pData(2,1) = $LB("key21","caption21","action21","id21","images/saveall.png")
  
Set pData(2,1,1) = $LB("key211","caption211","action211","id211","deepsee/ds2_list_44.png")
  
Set pData(2,1,2) = $LB("key212","caption212","action212","id212","deepsee/ds2_list_44.png")
  
Set pData(3) = $LB("key3","caption3","action3","id3","")
  
Set pData(3,1) = $LB("key31","caption31","action31","id31","images/saveall.png")
  
Set pData(3,1,1) = $LB("key311","caption311","action311","id311","deepsee/add_64.png","border:1px solid white;")
  
Set pData(3,1,2) = $LB("key312","caption312","action312","id312","deepsee/add_64.png",,"background:blue;")

  
Quit $$$OK
}

ClientMethod selectList(
  
key,
  
action,
  
targetId) [ Language = javascript ]
{
  zenAlert(
'key = ',key,'\naction = ',action,'\ntargetId = ',targetId);
}

}
Скриншоты:
Картинка с другого сайта. Картинка с другого сайта. Картинка с другого сайта.

Пример №2: <toolbar>

Данный компонент представляет собой подвид меню с поддержкой разнообразных типов подпунктов.
В примере используется два компонента: один представляет собой простейший случай, другой - более расширенный с переопределением некоторых встроенных стилей.
+ <toolbar>
Код класса страницы
Class html5.test2 Extends %ZEN.Component.page
{

/// Этот блок Style содержит определение CSS-стиля страницы.
XData 
Style
{
<
style type="text/css">
.ztb-caption-1 {
  font-size: 12px;
  padding: 4px 10px 4px 10px;
}

.ztb-menuItemSelected-1 {
  background: white;
  border: 1px solid;
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
}

td.ztb-choiceSelected {
  background: white;
  color: black;
  opacity: 1.0;
  font-size:12px;
}
</
style>
}

/// Этот XML блок описывает содержимое этой страницы.
XData 
Contents [ XMLNamespace "http://www.intersystems.com/zen" ]
{
<
page xmlns="http://www.intersystems.com/zen" title="">
<
toolbar
ongetdata="return zenPage.getdata1();"
onselect="zenPage.selectList(key,action,targetId);"
/>
<
spacer height="10"/>
<
toolbar
ongetdata="return zenPage.getdata2();"
onselect="zenPage.selectList(key,action,targetId);"
onchange="zenPage.change(key,value,final);"
onpagechange="zenPage.pagechange(key,page);"
/>
</
page>
}

ClientMethod pagechange(
  
key,
  
page) [ Language = javascript ]
{
  zenAlert(
'key = ',key,'\npage = ',page);
}

ClientMethod change(
  
key,
  
value,
  
final) [ Language = javascript ]
{
  zenAlert(
'key = ',key,'\nvalue = ',value,'\nfinal = ',final);
}

ClientMethod selectList(
  
key,
  
action,
  
targetId) [ Language = javascript ]
{
  zenAlert(
'key = ',key,'\n action = ',action,'\ntargetId = ',targetId);
}

ClientMethod getdata1() [ Language = javascript ]
{
  
return {children:[{key:'key1',
                     caption:
'caption1',
                     action:
'action1',
                     targetId:
'id1',
                     image:
'',
                     children:[{key:
'key11',
                                 caption:
'caption11',
                                 action:
'action11',
                                 targetId:
'id11',
                                 image:
'images/save.png'}]
                      },
                    {key:
'key2',
                     caption:
'caption2',
                     action:
'action2',
                     targetId:
'id2',
                     image:
'deepsee/add_64.png',
                     children:[{key:
'key21',
                                 caption:
'caption21',
                                 action:
'action21',
                                 targetId:
'id21',
                                 image:
'images/save.png'}]
                      },
                     {key:
'key3',
                      caption:
'caption3',
                      action:
'action3',
                      targetId:
'id3',
                      image:
'deepsee/ds2_list_44.png',
                      children:[{key:
'key31',
                                  caption:
'caption31',
                                  action:
'action31',
                                  targetId:
'id31',
                                  image:
'images/save.png'}]
                      }]
          };
}

ClientMethod getdata2() [ Language = javascript ]
{
  
return {
    children:[
      {key:
'key1', caption:'caption1', action:'action1', targetId:'id1', image:'', type:'pages', minValue:1, maxValue:5},
      {key:
'key2', caption:'caption2', action:'action2', targetId:'id2', image:'deepsee/cancel_48.png', type:'tab'},
      {key:
'key3', caption:'caption3', action:'action3', targetId:'id3', image:'', type:'tab'},
      {key:
'key4', caption:'caption4', action:'action4', targetId:'id4', image:'', type:'item',
       children:[{key:
'key41', caption:'caption41', action:'action41', targetId:'id41', image:''},
                  {separator:
''},
                  {key:
'key42', caption:'caption42', action:'action42', targetId:'id42', image:''}]},
      {type:
'spacer', style:'width:50px;'},
      {key:
'key5', caption:'caption5', action:'action5', targetId:'id5', image:'images/save.png', type:'tab', selected:true},
      {key:
'key6', caption:'caption6', action:'action6', targetId:'id6', image:'', type:'choice', displayList:'a1,b1,c1', valueList:'a,b,c', value:'c', style:'width:90px;'},
      {key:
'key7', caption:'caption7', action:'action7', targetId:'id7', image:'', type:'field', value:'field'},
      {key:
'key8', caption:'caption8', action:'action8', targetId:'id8', image:'', type:'string', defaultValue:'defaultValue'},
      {key:
'key9', caption:'caption9', action:'action9', targetId:'id9', image:'', type:'message'}
    ]
  };
}

}
Скриншоты:
Картинка с другого сайта.

Картинка с другого сайта.

Пример №3: <navigator>

Данный компонент предоставляет богатый интерфейс, заточенный под мобильные устройства, где каждый элемент меню управляется определённым типом с настраиваемыми параметрами присущими данному типу.
Имеется возможность вставлять собственный html в подпункты, если встроенных типов недостаточно.
Некоторые типы элементов позволяют совершать переходы на следующие уровни, которые в свою очередь могут содержать свои элементы меню. Глубина вложенности для перехода типа drill неограничена.
На каждом уровне доступны свои собственные верхние и нижние колонтитулы.
Элементы меню можно передвигать, используя drag&drop; есть готовые методы для выбора цвета и шрифта.
Кроме того, доступны обработчики событий для "пальцевого" управления.
+ <navigator>
Код класса страницы
Class html5.test3 Extends %ZEN.Component.page
{

/// Этот блок Style содержит определение CSS-стиля страницы.
XData 
Style
{
  <
style type="text/css">
  </
style>
}

/// Этот XML блок описывает содержимое этой страницы.
XData 
Contents [ XMLNamespace "http://www.intersystems.com/zen" ]
{
<
page xmlns="http://www.intersystems.com/zen" title="">
  <
navigator
    
id="navigator"
    
footerHeight="40"
    
showDisclosure="true"
    
expanded="true"
    
ongetcontent="return zenPage.getContent(level,key,value);"
    
onselect="zenPage.selectItem(key,value,which);"
    
onchange="zenPage.dataChange(key,value,final);"
    
onbuttonclick="zenPage.buttonClick(key);"
    
onclosebuttonclick="zenPage.closeButtonClick(key);"
  
/>
</
page>
}

ClientMethod buttonClick(key) [ Language = javascript ]
{
  zenAlert(
'key = ',key);
}

ClientMethod closeButtonClick(key) [ Language = javascript ]
{
  zenAlert(
'(closebutton) key = ',key);
}

ClientMethod dataChange(
  
key,
  
value,
  
final) [ Language = javascript ]
{
  
if (final) zenAlert('key = ',key,'\nvalue = ',value,'\nfinal = ',final);
}

ClientMethod selectItem(
  
key,
  
value,
  
which) [ Language = javascript ]
{
  
if (which!='drill') zenAlert('key = ',key,'\nvalue = ',value,'\nwhich = ',which);
}

ClientMethod getContent(
  
level,
  
key,
  
value) [ Language = javascript ]
{
  
var content { title:'', items:[], headerButtons:[], footerButtons:[] };
  
if (key=='') {
    content.title
='Заголовок';
    
    content.headerButtons
=[{caption:'Caption1', key:'key1', image:'deepsee/add_64.png'},
                           {caption:
'Caption2', key:'key2', image:'deepsee/cancel_48.png'},
                           {caption:
'Caption3', key:'key3', image:'deepsee/calendar_48.gif'}];
    
    content.footerButtons
=[{caption:'Caption1', key:'key1', image:'deepsee/delete_24.png'},
                           {caption:
'Caption2', key:'key2', image:'deepsee/lamp_48.gif'},
                           {caption:
'Caption3', key:'key3', image:'deepsee/delete_24.png'}];
    
    content.items[content.items.length] 
{display:'caption', caption:'Select', action:'select',  style:'color:darkblue;', key:'keySelect', value:'value'};
    content.items[content.items.length] 
{display:'value', text:'Value', disabled:true};
    content.items[content.items.length] 
{display:'info', caption:'Info', help:'Help', image:'deepsee/add_64.png', text:'Text'};
    content.items[content.items.length] 
{display:'html', content:'<hr/><p>bla-bla-bla</p>'};
    content.items[content.items.length] 
{display:'section', caption:'Section', captionStyle:'color:red;', style:'background:blue;'};
    content.items[content.items.length] 
{display:'value-cells', style:'height:55px;', cellsPerRow:3, cells:[{caption:'C1', value:'V1'},
                                                                                                              {caption:
'C2', value:'V2'},
                                                                                                              {caption:
'C3', value:'V3'}]};
    content.items[content.items.length] 
{display:'caption-value-vt', caption:'Switch', value:false,  edit:'switch', key:''};
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'Switch', value:false,  edit:'switch', key:''};
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'String', value:'$$$bla', edit:'string', action:'drill', key:'keyText1'};
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'Slider', value:70,  edit:'slider', minValue:0, maxValue:100};
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'Slider-Toggle', value:70,  edit:'slider-toggle', minValue:0, maxValue:100, stepSize:10};
    content.items[content.items.length] 
{display:'caption-value-hz',
                                           caption:
'Choice',
                                           edit:
'choice',
                                           valueList:
's1,s2,s3,s4',
                                           displayList:
'd1,d2,d3,d4',
                                           value:
's2',
                                           valueStyle:
'font-size:12pt;font-weight:bold;',
                                           choiceStyles:
'color:red;^color:blue;^color:yellow;^color:black;'};
    content.items[content.items.length] 
{display:'caption-value-hz',
                                           caption:
'Choice-Multi',
                                           edit:
'choice-multi',
                                           valueList:
's1,s2,s3,s4',
                                           displayList:
'd1,d2,d3,d4',
                                           value:
's1,s4',
                                           valueStyle:
'font-size:12pt;font-weight:bold;',
                                           choiceStyles:
'color:red;^color:blue;^color:yellow;^color:black;'};
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'Image', edit:'string', action:'drill', key:'keyImage', value:'deepsee/add_64.png'};
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'Color', action:'drill', key:'keyColor'};
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'Font', action:'drill', key:'keyFont', value:'tahoma'};
    content.items[content.items.length] 
{display:'image-caption', caption:'Image-Caption'};
    content.items[content.items.length] 
{display:'image-caption-value',caption:'Image-Caption-Value', text:'Image-Caption-Value'};
    content.items[content.items.length] 
{display:'image-caption-value-hz',
                                           caption:
'Caption',
                                           text:
'Text',
                                           action:
'popup',
                                           url:
'mailto:test@gmail.com',
                                           key:
'key',
                                           canDrag:true,
                                           closeButton:true};
  }
else if(key=='keyImage'){
    content.title
='Изображение';
    
var list [];
    list[list.length] 
{ image:'', caption:'empty', value:'', style:''};
    list[list.length] 
{ image:value, caption:value, value:value};
    list[list.length] 
{ image:'deepsee/cancel_48.png', caption:'deepsee/cancel_48.png', value:'deepsee/cancel_48.png', style:''};
    content.html
=zen('navigator').getIconListHTML(list,key,value);
  }
else if(key=='keyColor'){
    content.title
='Цвет';
    content.html
=zen('navigator').getColorChooserHTML(key,value,'html');
  }
else if(key=='keyFont'){
    content.title
='Шрифт';
    content.html
=zen('navigator').getFontChooserHTML(key,value);
  }
else if(key=='keyText1'){
    content.title
='Текст1';
    content.footerButtons
=[{caption:'Caption1', key:'key1', image:'deepsee/add_64.png'},
                           {caption:
'Caption2', key:'key2', image:'deepsee/lamp_48.gif'},
                           {caption:
'Caption3', key:'key3', image:'deepsee/lamp_48.gif'}];
    content.items[content.items.length] 
{display:'caption-value-hz', caption:'String', value:'v2',  edit:'string', action:'drill', key:'keyText2'};
  }
else if(key=='keyText2'){
    content.title
='Текст2';
    content.headerButtons
=[{caption:'Caption1', key:'key1', image:'deepsee/add_64.png'},
                           {caption:
'Caption2', key:'key2', image:'deepsee/lamp_48.gif'},
                           {caption:
'Caption3', key:'key3', image:'deepsee/lamp_48.gif'}];
    
var list [
      { caption:
'Заголовок', value:'v1', hint:'Использовать заголовок'},
      { caption:
'Категория', value:'v2', hint:'Использовать категорию'},
      { caption:
'Тип', value:'v3', hint:'Использовать тип'},
    ];
    content.html 
zen('navigator').getChooserListHTML(list,key,value,'Текстовые теги','Это специальные теги.');
  }
  
return content;
}

/// Убеждаемся, что мы заставляем Internet Explorer использовать последний движок рендеринга.
Method 
%OnDrawHTMLMeta() As %Status
{
  
If $$$ZENISIE &html<<meta http-equiv="X-UA-Compatible" content="IE=edge" />>
  
Quit $$$OK
}

}
Скриншоты:
Картинка с другого сайта.
№ 1

Картинка с другого сайта.
№ 2

Картинка с другого сайта.
№ 3

Картинка с другого сайта.
№ 4

Картинка с другого сайта.
№ 5

Картинка с другого сайта.
№ 6

Картинка с другого сайта.
№ 7

Пример №4: <lookup>

Данный компонент представляет собой выпадающий список с возможностью быстрого поиска нужного элемента.
Элементы списка могут содержать несколько столбцов и картинки.
Поиск осуществляется по всем столбцам.
Данный компонент используется в следующем, более сложном компоненте.
+ <lookup>
Код класса страницы
Class html5.test4 Extends %ZEN.Component.page
{

/// Этот блок Style содержит определение CSS-стиля страницы.
XData 
Style
{
  <
style type="text/css">
  </
style>
}

/// Этот XML блок описывает содержимое этой страницы.
XData 
Contents [ XMLNamespace "http://www.intersystems.com/zen" ]
{
  <
page xmlns="http://www.intersystems.com/zen" title="">
    <
lookup
      
id="lookup"
      
idProperty="key"
      
textProperty="caption"
      
imageProperty="image"
      
styleList="color:red;,color:blue;"
      
propertyList="key,caption,a,b"
      
ongetdata="return zenPage.getdata(context);"
    
/>
  </
page>
}

ClientMethod getdata(context) [ Language = javascript ]
{
  
return [{key:'key1', caption:'caption1', image:'deepsee/add_16.png', a:'a1', b:'b1'},
          {key:
'key2', caption:'caption2', a:'a2', b:'b2'},
          {key:
'key3', caption:'caption3', a:'a3', b:'b3'},
          {key:
'key4', caption:'caption4', a:'a4', b:'b4'}
  ];
}

ClientMethod onloadHandler() [ Language = javascript ]
{
  zen(
'lookup').setValue('key3','caption3');
}

Method %OnDrawHTMLMeta() As %Status
{
  
If $$$ZENISIE &html<<meta http-equiv="X-UA-Compatible" content="IE=edge" />>
  
Quit $$$OK
}

}
Скриншоты:
Картинка с другого сайта. Картинка с другого сайта. Картинка с другого сайта.

Пример №5: <dataGrid>

Данный компонент представляет собой упрощённый аналог электронной таблицы для работы с данными, где столбцы могут иметь различные типы: картинки, выпадающие списки и т.д.
Также поддерживаются формулы и страницы.
+ <dataGrid>
Код класса страницы
Class html5.test5 Extends %ZEN.Component.page
{

Parameter JSINCLUDES As STRING = "zenCSLM.js";

/// Этот XML блок описывает содержимое этой страницы.
XData 
Contents [ XMLNamespace "http://www.intersystems.com/zen" ]
{
  <
page xmlns="http://www.intersystems.com/zen" title="">
    <
jsonProvider id="json" OnGetArray="SrvGetData"/>
    <
hgroup>
      <
button caption="Добавить строку" onclick="zenPage.addRow();"/>
      <
button caption="Добавить столбец" onclick="zenPage.addColumn();"/>
    </
hgroup>
    <
hgroup>
      <
dataGrid
        
controllerId="json"
        
selectMode="cells"
        
hasFormulas="false"
        
currRow="2"
        
currColumn="2"
        
gridTitle="Заголовок1"
        
multiSelect="false">
        <
summaryRow caption="Сумма"/>
      </
dataGrid>
      <
dataGrid
        
id="dg2"
        
selectMode="cells"
        
currRow="1"
        
currColumn="3"
        
gridTitle="Заголовок3"
        
showRowSelector="false"
        
multiSelect="true"
        
pageSize="3"
        
hasFormulas="true"
        
onaction="zenAlert('row = ',row,'\nname = ',name,'\nvalue = ',value);"
        
ongetlookupdata="return zenPage.getdata(context);">
        <
columnDescriptor caption="f1" value="=power(2,4)"/>
        <
columnDescriptor caption="f2" value="=concat(2,&quot;a&quot;,rowno())"/>
        <
columnDescriptor caption="f3" value="=sum(2,3,3,colno())"/>
        <
columnDescriptor caption="f4" value="=[$col#2].[$row#3]"/>
        <
columnDescriptor caption="a" value="6"/>
        <
columnDescriptor caption="b" value="5"/>
        <
columnDescriptor caption="c" value="4"/>
        <
columnDescriptor caption="d" type="button" name="n1" value="=[$col#3].[$row#1]"/>
        <
columnDescriptor caption="e" type="checkbox" name="n2"/>
        <
columnDescriptor caption="f" type="image" image="images/save.png"/>
        <
columnDescriptor caption="g" type="link" name="n3" value="link"/>
        <
columnDescriptor caption="h" type="lookup" name="n4" aux="aux4" value="qwerty"/>
        <
rowDescriptor caption="r1"/>
        <
rowDescriptor caption="r2"/>
        <
rowDescriptor caption="r3"/>
      </
dataGrid>
    </
hgroup>
  </
page>
}

Method SrvGetData(
  
ByRef pParameters,
  
Output pMetaData,
  
Output pDataAs %Status
{
  
Set pMetaData $LB(1,2,3)

  
Set pData(1) = $LB(1,2,3)
  
Set pData(2) = $LB(4,5,6)
  
Set pData(3) = $LB(7,8,9)

  
Quit $$$OK
}

ClientMethod getdata(context) [ Language = javascript ]
{
  zenAlert(ZLM.jsonStringify(context));
  
return [{id:'id1', text:'text1'},{id:'id2', text:'text2'},{id:'id3', text:'text3'},{id:'id4', text:'text4'}];
}

ClientMethod addRow() [ Language = javascript ]
{
  
var model zen('json').getContentObject();
  
var record={};
  
for (var in model.children[0]) {
    record[p] 
= 10;
  }
  model.children[model.children.length] 
record;
  zen(
'json').setContentObject(model);
  
  
var grid zen('dg2');
  
var rowDesc zenPage.createComponent('rowDescriptor');
  grid.rowDescriptors[grid.rowDescriptors.length] 
rowDesc;
  rowDesc.caption 
'r'+grid.rowDescriptors.length;
  grid.updateGrid(false);
}

ClientMethod addColumn() [ Language = javascript ]
{
  
var grid zen('dg2');
  
var colDesc zenPage.createComponent('columnDescriptor');
  grid.columnDescriptors[grid.columnDescriptors.length] 
colDesc;
  colDesc.caption 
'c'+grid.columnDescriptors.length;
  colDesc.value 
grid.columnDescriptors.length+3;
  grid.updateGrid(false);
  
  
var model zen('json').getContentObject();
  
for (var = 0; n model.children.length; n++) {
    
if (model.children[n]) {
      model.children[n][grid.getColumnCount()] 
n;
    }
  }
  zen(
'json').setContentObject(model);
}

}
Скриншоты:
Картинка с другого сайта.
№ 1

Картинка с другого сайта.
№ 2

Картинка с другого сайта.
№ 3
Скачать исходники всех примеров.
PS: конечно же, вы можете изменить стандартный внешний вид компонент под свой вкус, поменяв у них стили, или же можно создать собственные компоненты, унаследовавшись от существующих, если нужно расширить их функциональность, благо объектная СУБД легко позволяет это сделать.

Комментарии




Необходимо войти на сайт, чтобы оставлять комментарии