Создание своего типа реестров в Alvex
Этот пост скопирован сюда из блога компании ITD Systems. Оригинал искать по адресу http://ru.blog.itdhq.com/post/70277827209/2013-12-17-create-new-registry-type.
Этот пост кратко описывает, как создать свой тип реестров в Alvex. Эти навыки будут полезны как для создания специфичных типов реестров с нуля, так и для добавления пары новых полей в стандартные типы. В качестве примера в этом посте будем создавать “Дистрибьюторский контракт”, который сделан на базе стандартного типа реестров “Договоры” и содержит несколько новых полей.
Примечание: Данный пост не описывает все возможности Alfresco и Alvex в области создания своих типов объектов системы. В него вошла только самая важная информация, которую нужно знать при создании своего типа реестров. Воспринимайте его как “Краткое руководство к действию”.
Процесс создания своего типа реестров состоит из следующих частей:
- Описание набора атрибутов реестра (content modeling, непосредственно создание типа реестра) (пример)
- Установка своего типа реестров в систему
- Конфигурация форм (в каком порядке поля будут видны пользователю, какие показывать в таблице, а какие только в окне документа итд) (пример)
- Установка конфигурации форм в систему
- Интернационализация заголовков полей реестра (если систему будут использовать люди, говорящие на разных языках, то правильным будет перевести названия реестра и всех полей на несколько языков)
Схематично это можно представить следующим образом:
Описание набора атрибутов реестра
Набор атрибутов описывается в файле, называемом моделью. Этот файл формата XML содержит описание одного или нескольких типов, объединённых по какому-то признаку (например, все типы модели описывают бухгалтерские документы). Можно для каждого типа реестров создавать новую модель.
Структура модели всегда одинакова и задается метамоделью объектов Alfresco:
<model xmlns="http://www.alfresco.org/model/dictionary/1.0" name="alvexcoursedocs:documents_model"> <description>...</description> <author>...</author> <version>...</version> <imports> <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" /> ... </imports> <namespaces> <namespace uri="http://alvexcore.com/prefix/alvexcoursedocs" prefix="alvexcoursedocs"/> </namespaces> <constraints>...</constraints> <types>...</types> <aspects>...</aspects> </model>
Namespace (пространство имен)
<namespaces> <namespace uri="http://alvexcore.com/prefix/alvexcoursedocs" prefix="alvexcoursedocs"/> </namespaces>
Namespace - пространство имен и префикс новой модели. Эти данные должны быть уникальны, то есть у Вас не может быть две модели с одинаковыми областями (uri) или префиксами (prefix). Все новые элементы создаваемой модели (свойства, ассоциации, ограничения) должны начинаться с префикса, указанного здесь. Целиком тег <namespace/> используется только для проверки уникальности модели.
Имя модели
<model name="alvexcoursedocs:documents_model" xmlns="http://www.alfresco.org/model/dictionary/1.0">
Имя модели является обязательным элементом. Имя состоит из префикса (например, alvexcoursedocs) и названия (например, documents_model). Используемый префикс должен совпадать с префиксом, указанным в namespace. xmlns указывает на http://www.alfresco.org/model/dictionary/1.0, чтобы Alfresco распознала файл как модель объекта и использовала его.
Примеры префиксов стандартных моделей
Префикс | Название модели |
---|---|
alf | alfresco |
app | application |
cm | contentmodel |
d | dictionary |
sys | system |
alvexdt | company_documents_model |
Импорт других моделей
В блоке <imports/> должны быть указаны пространства имён всех других моделей, элементы которых будут использоваться в типах данной модели. Обязательно нужно импортировать следующие модели:
- <import uri=”http://alvexcore.com/prefix/alvexdt” prefix=”alvexdt” /> - базовая модель реестров, наши новые типы будут являться “дочерними” для одного из базовых типов, а значит эту модель нужно импортировать.
- <import uri=”http://www.alfresco.org/model/dictionary/1.0” prefix=”d” /> - эта строка позволит нам создавать поля базовых типов: текстовое поле, дата, число и других.
- Если вы хотите позволить пользователям прикреплять файлы из библиотеки документов к документам реестра, а также указывать для документов “владельцев”, “менеджеров” и других пользователей, то необходимо также импортировать модель стандартных типов Alfresco: <import uri=”http://www.alfresco.org/model/content/1.0” prefix=”cm” />.
<imports> <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" /> <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/> <import uri="http://alvexcore.com/prefix/alvexdt" prefix="alvexdt"/> </imports>
Создание типа реестра
<types> <type name="alvexcoursedocs:document_partner_agreement"> <title>Новый тип</title> <parent>...</parent> <properties>...</properties> <associations>...</associations> <mandatory-aspects>...</mandatory-aspects> </type> ..... </types>
Типы описываются в блоке <types/>. Модель может содержать один или несколько типов. Каждый тип имеет своё уникальное имя, начинающееся с префикса модели и содержащее слово document в названии (например, alvexcoursedocs:document_partner_agreement). Если в названии типа не будет слова document, то он будет загружен в систему, как обычный тип списка данных, и не будет виден в списке доступных для создания реестров.
Для отображения названия типа пользователям используется атрибут <title/>, который может быть локализован на несколько языков (об интернационализации моделей - чуть позже).
Создаваемые типы основываются на уже существующих в Alfresco и Alvex типах объектов. Атрибут <parent/> указывает родителя нового типа. Если вы хотите добавить пару новых полей и скрыть несколько стандартных из уже существующего типа реестров, то укажите здесь имя родительского типа. Стандартные типы Alvex, которые можно использовать в качестве “родителя”:
- alvexdt:received - реестр входящих
- alvexdt:sent - реестр исходящих
- alvexdt:agreement - реестр договоров
- alvexdt:appendix - реестр приложений
- alvexdt:invoice - реестр счетов
- alvexdt:administrative - реестр распорядительных документов
- alvexdt:background - реестр информационно-справочных документов
- alvexdt:attorney - реестр доверенностей
Если же вы хотите создать свой тип реестра “с нуля”, то укажите здесь alvexdt:object.
Все остальные блоки являются необязательными, но без них тип объекта не содержит никаких атрибутов кроме унаследованных от “родителя”. Свойства (<properties/>) добавляют новые атрибуты к метаданным типа (текстовые поля, даты, числа), а ассоциации (<associations/>) - ссылки на другие объекты репозитория.
Свойства
<properties> <property name=alvexcoursedocs:reviewDate"> <title>Review Date</title> <type>d:date</type> <protected>true/false</protected> <mandatory>true/false</mandatory> <multiple>true/false</multiple> <default>...</default> </property> </properties>
Каждое свойство имеет уникальное имя, начинающееся с префикса модели, и необязательное название (<title />), которое будет отображаться пользователю в интерфейсе. Для каждого свойства нужно указать его тип: дата, текст, число или любой другой из числа стандартных типов системы.
Стандартные типы данных
Название типа | Значение типа |
d:text | текст, строка |
d:int | целое число (эквивалент java.lang.Integer) |
d:long | целое число (эквивалент java.lang.Long) |
d:float | вещественное число (эквивалент java.lang.Float) |
d:double | вещественное число (эквивалент java.lang.Double) |
d:date | дата |
d:datetime | дата и время |
d:boolean | логическое значение (0 или 1) |
d:context | произвольный длинный текст или бинарный поток |
d:mltext | многоязыковой текст |
d:any | любое произвольное значение |
Дополнительные теги свойств:
Если вы хотите добавить для свойства возможность выбирать несколько значений, то ему добавляется тег <multiple>true</multiple>. Эта возможность не реализована в интерфейсе Share по умолчанию, она будет добавлена для реестров в Alvex 2.0.2 (2014.01).
Если свойство является обязательным для заполнения, то ему добавляется тег <mandatory>true</mandatory>.
Если у свойства должно быть значение по умолчанию, которое уже будет введено в поле при создании элемента, то стандартное значение можно задать тегом <default>значение</default>.
Если значение свойства заполняется каким-либо скриптом или тегом <default>, и пользователю должно быть запрещено изменять значение, то для этого используется тег <protected>true</protected>.
Ассоциации
<associations> <association name="alvexcoursedocs:contractManager"> <title>Contract Manager</title> <source> <mandatory>false</mandatory> <many>true</many> </source> <target> <class>cm:person</class> </target> </association> </associations>
Ассоциации позволяют устанавливать связи между объектами в репозитории. Ассоциации бывают двух типов: равные (или peer association) и дочерние (или child association). В случае использования равных ассоциаций объекты по обе стороны ассоциации равны между собой. В случае использования дочерних ассоциаций родительский объект является главным и, при его удалении, дочерний объект тоже удаляется.
Приведённый выше пример показывает создание равной ассоциации текущего объекта с пользователем системы. В блок <target /> кроме <class /> могут быть добавлены теги <mandatory /> и <many />, отвечающие соответственно за обязательность выбора ассоциации при создании объекта и за возможность создания ассоциации с несколькими объектами. Эти теги могут принимать значения true и false.
Число допустимых ассоциаций | mandatory & many |
0 или 1 | false & false |
1 | true & false |
0 или больше | false & true |
1 или больше | true & true |
Примером дочерней ассоциации является взаимоотношение каталога и хранящихся в нем файлов:
<associations> <child-association name="cm:contains"> <title>Contains files</title> <source> <mandatory>false</mandatory> <many>true</many> </source> <target> <class>sys:base</class> <mandatory>false</mandatory> <many>true</many> </target> <duplicate>false<duplicate> </child-association> </associations>
Ограничения
На значения свойств могут накладываться ограничения. Например, статус обработки документа может быть выбран из списка значений, а приоритет не может быть отрицательным. Для создания ограничений используется блок constraints, который должен располагаться в файле модели до описания типов.
В системе существует четыре типа ограничений:
- LENGTH определяет минимальное и максимальное допустимое количество символов в значении текстового свойства;
- MINMAX накладывает ограничение по минимальному и максимальному значению для числовых свойств;
- LIST позволяет явно задать список допустимых значений для свойства;
- REGEX используется для ограничения свойства по регулярному выражению.
Тип LENGTH позволяет ограничить строку по длине при помощи параметров minLength и maxLength. Использование обоих параметров в одном ограничение не является обязательным, можно ограничить только минимальное число символов в строке или только максимальное.
<constraints> <constraint name="alvexcoursedocs:partnerPhoneNumber" type="LENGTH"> <parameter name="minLength"><value>10</value></parameter> <parameter name="maxLength"><value>12</value></parameter> </constraint> </constraints>
Тип MINMAX ограничивает минимальное и максимальное значение числового свойства. Для этого используются параметры minValue и maxValue аналогично параметрам типа LENGTH.
<constraints> <constraint name="alvexcoursedocs:discountConstraint" type="MINMAX"> <parameter name="minValue"><value>5</value></parameter> <parameter name="maxValue"><value>25</value></parameter> </constraint> </constraints>
Тип LIST позволяет задать список допустимых значений. Значения будут доступны пользователю в формате выпадающего списка, из которого требуется выбрать нужный вариант.
<constraints> <constraint name="alvexcoursedocs:statusContraint" type="LIST"> <parameter name="allowedValues"> <list> <value>New</value> <value>In process</value> <value>Closed</value> </list> </parameter> </constraint> </constraints>
Тип REGEX является средством для ограничения и проверки значений свойств по регулярному выражению. Например, приведённый ниже пример ограничения проверяет значение, введённое пользователем, на соответствие формату адреса электронной почты.
<constraints> <constraint name="alvexcoursedocs:email" type="REGEX"> <parameter name="expression"> <value><![CDATA[^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,4}$]]></value> </parameter> </constraint> </constraints>
Для проверки своих регулярных выражений можно использовать консольные утилиты (grep, sed).
Для прикрепления созданного ограничения к свойству необходимо добавить в блок свойства информацию о наложенном ограничении:
<properties> <property name="alvexcoursedocs:partnerDiscount"> <type>d:int</type> <constraints> <constraint ref="alvexcoursedocs:discountConstraint"/> </constraints> </property> </properties>
Аспекты
В случае, если вы создаете несколько типов, вам приходится создавать одинаковые свойства для разных типов реестров (например, номер документа). В таких случаях бывает удобно создать некоторые свойства и ассоциации отдельно от конкретного типа, а затем подключить созданный набор к нужным типам. Для этого используются аспекты.
В стандартных типах реестров документов атрибуты “Номер документа” и “Дата регистрации” - это единожды созданный аспект, который подключен ко всем типам реестров.
Аспекты имеют ту же иерархию, что и типы. Они включают свойства и ассоциации.
<aspects> <aspect name="alvexcoursedocs:withVendorInfo"> <properties> <property name="alvexcoursedocs:vendorCompanyName"> <type>d:text</type> <index enabled="true"> <atomic>true</atomic> <stored>true</stored> <tokenised>both</tokenised> </index> </property> <property name="alvexcoursedocs:vendorWebSite"> <type>d:text</type> <index enabled="true"> <atomic>true</atomic> <stored>true</stored> <tokenised>both</tokenised> </index> </property> </properties> </aspect> </aspects>
Для подключения аспекта к типу реестров в описание типа добавляется блок <mandatory-aspects />, в котором перечисляются все аспекты, свойства и ассоциации которых мы хотим видеть как часть типа.
<types> <type name="alvexcoursedocs:document_partner_agreement"> <parent>alvexdt:agreement</parent> <properties> ... </properties> <associations> ... </associations> <mandatory-aspects> <aspect>alvexcoursedocs:withVendorInfo</aspect> </mandatory-aspects> </type> </types>
Кроме собственных аспектов можно подключать к своим типам аспекты, созданные в импортированных моделях. Например, для добавления к своему типу реестров полей “Номер документа” и “Дата регистрации” необязательно создавать для них свойства. Можно подключить в блок <mandatory-aspects /> аспект alvexdt:identified. Полный список аспектов, которые есть в стандартной модели реестров Alvex, можно посмотреть в модели стандартных реестров Alvex по ссылке https://github.com/ITDSystems/alvex/blob/master/core/documents-registers/repo/config/alfresco/extension/models/alvex-default-documents-types-model.xml.
Индексирование свойств
Каждое свойство модели объекта индексируется для возможности дальнейшего поиска объекта по свойству. Например, все свойства документов стандартного типа (cm:content) индексируются автоматически со следующими параметрами:
<type name="cm:content"> <title>Content</title> <parent>cm:object</parent> <properties> <property name="cm:content"> <type>d:content</type> <mandatory>false</mandatory> <index enabled="true"> <atomic>true</atomic> <stored>false</stored> <tokenised>true</tokenised> </index> </property> </properties> </type>
Для включения индексации свойства своей модели объекта необходимо добавить тег <index enabled=”true”> в нужное свойство. Тег <atomic /> отвечает за то, производится ли индексация в приоритетном режиме (значение true) или в фоновом (значение false). Тег <tokenised /> указывает на то, требуется ли разбивать строковые свойства на токены (значение true) или хранить их единым блоком (значение false), значение both допускает оба варианта.
Наследование и замещение
Создаваемый тип объекта наследует все свойства, ассоциации и ограничения родительского типа. То есть, если в родительском типе есть свойство или ассоциация, которые вас устраивает (например, alvexdt:files (файлы документа)), то его не нужно создавать заново. При настройке пользовательского интерфейса (ниже) вы сможете выбрать, какие элементы родительского типа вы хотите отображать в атрибутах объекта вашего типа.
Если вас устраивает некоторое свойство родительского типа, но вы хотите что-то в нем изменить (например, для родительского типа свойство был необязательным, а вы хотите его сделать таковым), то вы можете использовать замещение тегов свойства. Для этого используется блок <overrides />, добавляемый в описание типа после блока <properties />. Внутри блока <overrides /> описываются свойства родительского типа с изменёнными тегами. Следующие теги могут быть изменены:
- значение тега <mandatory /> можно изменить с false на true;
- можно добавить или изменить значение тега ;
- свойству можно добавить новые ограничения, используя тег <constraints />, но существующие ограничения не могут быть удалены.
Проверка модели
После того, как файл модели написан, его можно проверить на правильность синтаксиса и на соблюдение порядка тегов. В Linux и MacOS для этого можно использовать команду:
xmllint —schema /opt/alfresco/tomcat/webapps/alfresco/WEB-INF/classes/alfresco/model/modelSchema.xsd alvex-course-examples-model.xml, где в качестве параметра —schema указывается путь до схемы модели, которую можно найти в Alfresco по пути, указанному выше.
Пример описания набора полей
<?xml version="1.0" encoding="UTF-8"?> <model name="alvexcoursedocs:documents_model" xmlns="http://www.alfresco.org/model/dictionary/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.alfresco.org/model/dictionary/1.0 modelSchema.xsd"> <imports> <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/> <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/> <import uri="http://www.alfresco.org/model/system/1.0" prefix="sys"/> <import uri="http://www.alfresco.org/model/datalist/1.0" prefix="dl"/> <import uri="http://alvexcore.com/prefix/alvexdt" prefix="alvexdt"/> </imports> <namespaces> <namespace uri="http://alvexcore.com/prefix/alvexcoursedocs" prefix="alvexcoursedocs"/> </namespaces> <constraints> <constraint name="alvexcoursedocs:discountConstraint" type="MINMAX"> <parameter name="minValue"><value>5</value></parameter> <parameter name="maxValue"><value>25</value></parameter> </constraint> </constraints> <types> <type name="alvexcoursedocs:document_partner_agreement"> <parent>alvexdt:agreement</parent> <properties> <property name="alvexcoursedocs:partnerDiscount"> <type>d:int</type> <constraints> <constraint ref="alvexcoursedocs:discountConstraint"/> </constraints> </property> </properties> <associations> <association name="alvexcoursedocs:contractManager"> <title>Contract Manager</title> <source> <mandatory>false</mandatory> <many>true</many> </source> <target> <class>cm:person</class> </target> </association> </associations> <mandatory-aspects> <aspect>alvexcoursedocs:withVendorInfo</aspect> </mandatory-aspects> </type> </types> <aspects> <aspect name="alvexcoursedocs:withVendorInfo"> <properties> <property name="alvexcoursedocs:vendorCompanyName"> <type>d:text</type> <index enabled="true"> <atomic>true</atomic> <stored>true</stored> <tokenised>both</tokenised> </index> </property> <property name="alvexcoursedocs:vendorWebSite"> <type>d:text</type> <index enabled="true"> <atomic>true</atomic> <stored>true</stored> <tokenised>both</tokenised> </index> </property> </properties> </aspect> </aspects> </model>
Установка своего типа реестров в систему
Установить созданную модель в систему можно тремя способами:
-
Динамически без перезагрузки. Для этого нужно войти в систему, используя учётную запись администратора, перейти в репозитории в каталог Data Dictionary > Models (или Словарь данных > Модели) и добавить в него свою модель. После этого требуется изменить в метаданных загруженного документа статус модели (Is active) на true. Если после обновления страницы статус изменился, то модель установлена. Если нет, то в логах Alfresco (catalina.out или alfresco.log) можно найти ошибку, почему это произошло.
-
На файловую систему сервера. Для этого нужно скопировать свою модель в каталог tomcat > shared > classes > alfresco > extensions. Чтобы не запутаться в большом количестве файлов, может быть удобно создать в каталоге extensions папку models и складывать свои модели в нее. Посе этого нужно написать файл контекста, которой будет подключать новую модель к системе. Файл контекста нужно положить в каталог extensions, его название должно обязательно заканчиваться фразой -context.xml. Проще всего назвать файл также, как вы назвали файл с моделью, добавив -context. Пример файла контекста:
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans> <bean id="alvex-course-examples" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap,alvex-documents-registers-deployer"> <property name="models"> <list> <value>alfresco/extension/models/alvex-course-examples-model.xml</value> </list> </property> </bean> </beans>
В качестве значения укажите путь до файла вашей модели, начиная от tomcat > shared > classes. Обратите внимание, что идентификаторы “bean” должны быть уникальны. То есть, если вы создаете несколько моделей и несколько файлов контекста, то в каждом файле ID нужно менять на новый, иначе система не запустится.
После того, как все будет готово, перезагрузите Alfresco.
-
JAR-файл с вашими моделями реестров. За пример можно взять эту структуру каталогов. Подробнее сборку JAR-файлов рассматривать не будем, так как это тема отдельного поста.
После установки модели реестров в систему, в списке доступных для добавления на сайт реестров должен появиться новый тип. Если этого не произошло, значит что-то пошло не так. Самые распространенные ошибки:
- Вы установили модель в систему первым способом и забыли включить флаг “Is active” в состояние true. Либо флаг не переключился, и вам нужно искать ошибки в логах.
- Вы установили модель в систему вторым или третьим способом и забыли перезапустить Alfresco.
- Если система не запускается после перезагрузки, значит в модели есть какая-то ошибка, в файле контекста указан неверный путь до файла модели или вы забыли указать depends-on=”dictionaryBootstrap,alvex-documents-registers-deployer” в зависимостях bean-а. В логах (catalina.out) можно найти причину проблемы и строку в файле модели, где была допущена ошибка, если проблема в этом.
Описание форм
Формы для нового типа реестров описываются в отдельном файле XML, как и модель.
Файл начинается с открытия тега <alfresco-config> и заканчивается его закрытием (</alfresco-config>).
Для каждого типа реестров нужно описать 3 формы:
- табличная форма (какие поля видны в таблице)
- форма добавления нового документа в реестр (какие поля можно заполнять, а какие нет, как расположены поля в окне)
- форма просмотра и изменения документа (как расположены поля в окне, какие поля можно изменять в окне изменения документа)
Описание первых двух форм (табличная и форма добавления) находится в блоке <config evaluator=”model-type” condition=”alvexcoursedocs:document_partner_agreement” />. Описание третьей формы (просмотра и изменения) находится в блоке <config evaluator=”node-type” condition=”alvexcoursedocs:document_partner_agreement” />. В блоке condition указывается имя вашего типа реестров, которое было задано в модели.
Общая структура файла с конфигурацией форм будет такой:
<alfresco-config> <config evaluator="model-type" condition="alvexcoursedocs:document_partner_agreement"> <forms> <form id="datagrid"> ... <create-form template="/alvex-form.ftl" /> </form> <form> </form> </forms> </config> <config evaluator="node-type" condition="alvexcoursedocs:document_partner_agreement"> <forms> <form> ... <view-form template="/alvex-form.ftl" /> <edit-form template="/alvex-form.ftl" /> </form> </forms> </config> </alfresco-config>
Описание формы, имеющее идентификатор “datagrid” (<form id=”datagrid”/>), является конфигурацией табличной формы.
Описание, в котором указано использование шаблона Alvex для формы создания (<create-form template=”/alvex-form.ftl”/>), относится к форме создания документа.
Описание, в котором указано использование шаблона Alvex для форм редактирования и просмотра (<edit-form template=”/alvex-form.ftl”/> и <view-form template=”/alvex-form.ftl”/>), относится к форме изменения и просмотра документа.
Видимость полей
По умолчанию, на формах видны все поля созданного типа реестра, его родителя, родителя его родителя итд. То есть полей ОЧЕНЬ много, и среди них явно есть ненужные.
Используя блок field-visibility, мы задаем, какие именно поля будут видны на каждой из форм. Есть два режима, которые может использовать для этого блока:
-
Перечисление полей, которые будут показаны пользователю в форме. В таком случае все поля по умолчанию скрыты и будут показаны только те, которые вы укажете:
<show id=”alvexcoursedocs:vendorCompanyName” />, где id=”имя свойства или ассоциации в нашем типе или его родителе”.
-
Перечисление полей, которые будут скрыты. Если вас устраивает обилие полей, которое отображается по умолчанию, и вы хотите только скрыть некоторые из них, то можно использовать строки следующего вида:
<hide id=”alvexcoursedocs:vendorCompanyName” />, где id=”имя свойства или ассоциации в нашем типе или его родителе”.
Комбинировать эти режимы нельзя. Первым элементом в блоке <field-visibility> вы включаете один из режимов.
Пример конфигурации для всех форм:
<alfresco-config> <config evaluator="model-type" condition="alvexcoursedocs:document_partner_agreement"> <forms> <form id="datagrid"> <field-visibility> <show id="alvexdt:id" /> <show id="alvexdt:company" /> <show id="alvexdt:contractor" /> <show id="alvexcoursedocs:vendorCompanyName" /> <show id="alvexcoursedocs:partnerDiscount" /> <show id="alvexdt:signingDate" /> <show id="alvexdt:expiryDate" /> </field-visibility> </form> <form> <field-visibility> <show id="alvexdt:id" /> <show id="alvexdt:registerDate" /> <show id="alvexdt:company" /> <show id="alvexdt:agreementType" /> <show id="alvexdt:agreementSummary" /> <show id="alvexdt:contractor" /> <show id="alvexdt:relatedDocuments" /> <show id="alvexdt:documentManager" /> <show id="alvexcoursedocs:vendorCompanyName" /> <show id="alvexcoursedocs:vendorWebSite" /> <show id="alvexcoursedocs:partnerDiscount" /> <show id="alvexdt:signingDate" /> <show id="alvexdt:expiryDate" /> <show id="alvexdt:signatory" /> <show id="alvexdt:renew" /> <show id="alvexdt:location" /> <show id="alvexdt:files" /> </field-visibility> <create-form template="/alvex-form.ftl" /> </form> </forms> </config> <config evaluator="node-type" condition="alvexcoursedocs:document_partner_agreement"> <forms> <form> <field-visibility> <show id="alvexdt:id" /> <show id="alvexdt:registerDate" /> <show id="alvexdt:company" /> <show id="alvexdt:agreementType" /> <show id="alvexdt:agreementSummary" /> <show id="alvexdt:contractor" /> <show id="alvexdt:relatedDocuments" /> <show id="alvexdt:documentManager" /> <show id="alvexcoursedocs:vendorCompanyName" /> <show id="alvexcoursedocs:vendorWebSite" /> <show id="alvexcoursedocs:partnerDiscount" /> <show id="alvexdt:signingDate" /> <show id="alvexdt:expiryDate" /> <show id="alvexdt:signatory" /> <show id="alvexdt:renew" /> <show id="alvexdt:location" /> <show id="alvexdt:files" /> </field-visibility> <view-form template="/alvex-form.ftl" /> <edit-form template="/alvex-form.ftl" /> </form> </forms> </config> </alfresco-config>
Внешний вид полей
Для форм создания и изменения/просмотра нужно задать не только список полей, которые будут видны, но и то, как именно эти поля будут отображаться. Для этого используется блок <appearance>. Например, здесь указывается, что поле ассоциации с пользователем должно отображаться как кнопка “Выбрать из оргструктуры”, а к полю “Номер документа” должна быть привязана автонумерация. Здесь же задается раскладка формы: какие поля показываются по три в строку, а какое поле само занимает несколько строк.
Блок <appearance> следует после <field-visibility> в конфигурациях всех форм кроме табличной. Сначала задается раскладка формы. Это делается при помощи тегов <set>. Каждый такой “set” описывает одну или несколько строк, имеет свой идентификатор (id) и шаблон, который и указывает раскладку. Шаблоны, которые можно использовать:
<set id="SET" template="/org/alfresco/components/form/2-column-set.ftl"/> <field set="SET" id="alvexcoursedocs:vendorCompanyName" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field> <field set="SET" id="alvexcoursedocs:vendorWebSite" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field>
<set id="SET" template="/org/alfresco/components/form/3-column-set.ftl"/> <field set="SET" id="alvexcoursedocs:vendorCompanyName" /> <field set="SET" id="alvexcoursedocs:vendorWebSite" /> <field set="SET" id="alvexcoursedocs:partnerDiscount" />
<set id="SET" template="/alvex-2-column-set-wide-left.ftl"/> <field set="SET" id="alvexcoursedocs:vendorCompanyName" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field> <field set="SET" id="alvexcoursedocs:vendorWebSite" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field>
<set id="SET" template="/alvex-2-column-set-wide-right.ftl"/> <field set="SET" id="alvexcoursedocs:vendorCompanyName" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field> <field set="SET" id="alvexcoursedocs:vendorWebSite" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field>
Если вы хотите, чтобы в строке было только одно поле, для него все равно желательно создать set, не указывая для него шаблон. Создавая в блоке <appearance> несколько set-ов, вы указываете их порядок на форме.
Как, наверно, уже понятно из примеров, приведенных выше, каждому полю соответствует тег <field>. Этот тег имеет следующие атрибуты:
- id (имя свойства или ассоциации) (не забывайте указывать префикс элемента в id)
- set (имя сэта, в котором будет отображаться поле)
- template (шаблон)
- read-only (делает поле доступным только для чтения)
- mandatory(делает поле обязательным для заполнения на этой форме)
Обязательным атрибутом является только id.
Поведение полей контролируется шаблонами FreeMarker. Используя блок control внутри тега field, можно отображать многострочное поле для ввода текста вместо обычного, использовать окно выбора пользователя из оргструктуры, подключать к полю автоматическую нумерацию документов и использовать другие расширения системы.
Кнопка и окно выбора пользователя из оргструктуры для ассоциации с пользователем:
<field id="alvexdt:documentManager"> <control template="/orgchart-picker.ftl" /> </field>
Автоматическая вставка текущей даты в поле типа “d:date”:
<field id="alvexdt:registerDate"> <control template="/alvex-autodate.ftl"/> </field>
Добавление возможности подключить справочник к текстовому полю (сам справочик выбирается в настройке реестра в пользовательском интерфейсе):
<field id="alvexdt:company"> <control template="/alvex-masterData-select.ftl"/> </field>
Загрузчик файлов для ассоциации с файлами (cm:content):
<field id="alvexdt:files"> <control template="/alvex-uploader.ftl"> <control-param name="uploadDirectory">uploads</control-param> <control-param name="createUploadDirectory">true</control-param> <control-param name="viewType">mini</control-param> </control> </field>
Подключение автонумерации к текстовому полю (формат номера документа настраивается в настройках реестра в пользовательском интерфейсе):
<field id="alvexdt:id"> <control template="/alvex-auto-numberer.ftl"/> </field>
Выбор из списка сайта Share для текстового поля.
<field id="alvexdt:location"> <control template="/share-site-picker.ftl"/> </field>
Сделать поле растягивающимся и широким:
<field set="vendor" id="alvexcoursedocs:vendorCompanyName" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field>
Пример описания форм
Пример ниже описывает конфигурацию форм для реестра дистрибьюторских контрактов. Формы содержат несколько полей созданного типа реестров, все поля родительского типа и поле alvexdt:files, унаследованное от родителя родителя.
<alfresco-config> <config evaluator="model-type" condition="alvexcoursedocs:document_partner_agreement"> <forms> <form id="datagrid"> <field-visibility> <show id="alvexdt:id" /> <show id="alvexdt:company" /> <show id="alvexdt:contractor" /> <show id="alvexcoursedocs:vendorCompanyName" /> <show id="alvexcoursedocs:partnerDiscount" /> <show id="alvexdt:signingDate" /> <show id="alvexdt:expiryDate" /> </field-visibility> </form> <form> <field-visibility> <show id="alvexdt:id" /> <show id="alvexdt:registerDate" /> <show id="alvexdt:company" /> <show id="alvexdt:agreementType" /> <show id="alvexdt:agreementSummary" /> <show id="alvexdt:contractor" /> <show id="alvexdt:relatedDocuments" /> <show id="alvexdt:documentManager" /> <show id="alvexcoursedocs:vendorCompanyName" /> <show id="alvexcoursedocs:vendorWebSite" /> <show id="alvexcoursedocs:partnerDiscount" /> <show id="alvexdt:signingDate" /> <show id="alvexdt:expiryDate" /> <show id="alvexdt:signatory" /> <show id="alvexdt:renew" /> <show id="alvexdt:location" /> <show id="alvexdt:files" /> </field-visibility> <create-form template="/alvex-form.ftl" /> <appearance> <set id="id" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/> <set id="company" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/> <set id="vendor" appearance="" label="" template="/org/alfresco/components/form/2-column-set.ftl"/> <set id="date" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/> <set id="renew"/> <set id="summary" appearance="" label="" /> <set id="files" appearance="" label=""/> <set id="related" appearance="" label="" template="/org/alfresco/components/form/2-column-set.ftl"/> <field set="id" id="alvexdt:id"> <control template="/alvex-auto-numberer.ftl"/> </field> <field set="id" id="alvexdt:registerDate"/> <field set="id" id="alvexdt:agreementType"> <control template="/alvex-masterData-select.ftl"/> </field> <field set="company" id="alvexdt:company"> <control template="/alvex-masterData-select.ftl"/> </field> <field set="company" id="alvexdt:contractor"> <control template="/alvex-masterData-select.ftl"/> </field> <field set="company" id="alvexcoursedocs:vendorCompanyName"> <control template="/orgchart-picker.ftl" /> </field> <field set="vendor" id="alvexcoursedocs:vendorCompanyName" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field> <field set="vendor" id="alvexcoursedocs:vendorWebSite" > <control template="/org/alfresco/components/form/controls/textfield.ftl"> <control-param name="style">width: 98%</control-param> </control> </field> <field set="vendor" id="alvexcoursedocs:partnerDiscount" /> <field set="date" id="alvexdt:signingDate"/> <field set="date" id="alvexdt:expiryDate"/> <field set="date" id="alvexdt:signatory"> <control template="/orgchart-picker.ftl" /> </field> <field set="renew" id="alvexdt:renew"/> <field set="summary" id="alvexdt:agreementSummary"> <control template="/alvex-mltext.ftl"> <control-param name="style">width: 98%</control-param> </control> </field> <field set="files" id="alvexdt:files"> <control template="/alvex-uploader.ftl"> <control-param name="uploadDirectory">uploads</control-param> <control-param name="createUploadDirectory">true</control-param> <control-param name="viewType">mini</control-param> </control> </field> <field set="related" id="alvexdt:relatedDocuments"/> <field set="related" id="alvexdt:location"> <control> <control-param name="style">width: 98%</control-param> </control> </field> </appearance> </form> </forms> </config> <config evaluator="node-type" condition="alvexcoursedocs:document_partner_agreement"> <forms> <form> <field-visibility> <show id="alvexdt:id" /> <show id="alvexdt:registerDate" /> <show id="alvexdt:company" /> <show id="alvexdt:agreementType" /> <show id="alvexdt:agreementSummary" /> <show id="alvexdt:contractor" /> <show id="alvexdt:relatedDocuments" /> <show id="alvexdt:documentManager" /> <show id="alvexcoursedocs:vendorCompanyName" /> <show id="alvexcoursedocs:vendorWebSite" /> <show id="alvexcoursedocs:partnerDiscount" /> <show id="alvexdt:signingDate" /> <show id="alvexdt:expiryDate" /> <show id="alvexdt:signatory" /> <show id="alvexdt:renew" /> <show id="alvexdt:location" /> <show id="alvexdt:files" /> </field-visibility> <view-form template="/alvex-form.ftl" /> <edit-form template="/alvex-form.ftl" /> <appearance> <set id="id" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/> <set id="company" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/> <set id="date" appearance="" label="" template="/org/alfresco/components/form/3-column-set.ftl"/> <set id="renew"/> <set id="summary" appearance="" label="" /> <set id="files" appearance="" label=""/> <set id="related" appearance="" label="" template="/org/alfresco/components/form/2-column-set.ftl"/> <field set="id" id="alvexdt:id"> <control template="/alvex-auto-numberer.ftl"/> </field> <field set="id" id="alvexdt:registerDate"/> <field set="id" id="alvexdt:agreementType"> <control template="/alvex-masterData-select.ftl"/> </field> <field set="company" id="alvexdt:company"> <control template="/alvex-masterData-select.ftl"/> </field> <field set="company" id="alvexdt:contractor"> <control template="/alvex-masterData-select.ftl"/> </field> <field set="company" id="alvexdt:documentManager"> <control template="/orgchart-picker.ftl" /> </field> <field set="vendor" id="alvexcoursedocs:vendorCompanyName" /> <field set="vendor" id="alvexcoursedocs:vendorWebSite" /> <field set="vendor" id="alvexcoursedocs:partnerDiscount" /> <field set="date" id="alvexdt:signingDate"/> <field set="date" id="alvexdt:expiryDate"/> <field set="date" id="alvexdt:signatory"> <control template="/orgchart-picker.ftl" /> </field> <field set="renew" id="alvexdt:renew"/> <field set="summary" id="alvexdt:agreementSummary"> <control template="/alvex-mltext.ftl"> <control-param name="style">width: 98%</control-param> </control> </field> <field set="files" id="alvexdt:files"> <control template="/alvex-uploader.ftl"> <control-param name="uploadDirectory">uploads</control-param> <control-param name="createUploadDirectory">true</control-param> <control-param name="viewType">mini</control-param> </control> </field> <field set="related" id="alvexdt:relatedDocuments"/> <field set="related" id="alvexdt:location"> <control> <control-param name="style">width: 98%</control-param> </control> </field> </appearance> </form> </forms> </config> </alfresco-config>
Установка конфигурации форм в систему
Опять-таки есть 3 варианта установки конфигурации в систему:
-
Положить конфигурацию в файл tomcat/shared/classes/alfresco/web-extension/share-config-custom.xml. В нем уже присутствуют открывающий и закрывающий теги <alfresco-config>, так что нужно просто скопировать все <config …> внутрь. Этот способ самый простой, но неудобный, когда у вас хотя бы 5 типов реестров.
-
Аналогично установке модели создать файл контекста, который подключит новый файл конфигурации в систему. В таком случае нужно скопировать свой файл в каталог tomcat/shared/classes/alfresco/web-extension/ и положить рядом файл, название которого заканчивается на -context.xml. Пример файла контекста:
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans> <bean id="alvex-course-examples-configs-deployer" class="org.springframework.extensions.config.ConfigBootstrap" init-method="register" depends-on=""> <property name="configService" ref="web.config" /> <property name="configs"> <list> <value>classpath:alfresco/web-extension/alvex-course-examples-config.xml</value> </list> </property> </bean> </beans>
-
Запаковать все в JAR, как и для файла модели.
Интернационализация заголовков полей реестра
Все типы, свойства, аспекты, ассоциации и ограничения имеют заголовок (title) и описание (description). Заголовки мы задавали, а описания совершенно игнорировали при создании модели, хотя оба значения можно задать в XML-файле, описывающем модель. В таком случае система будет поддерживать только один язык: язык, на котором введены эти значения.
Для поддержки интернационализации необходимо выделить значения, поддающиеся локализации, в отдельный файл, и подключить его к системе как стандартный пакет ресурсов в Java.
Основной пакет ресурсов обычно создаётся со значениями на английском языке. Если модель сохранялась в файл alvex-course-examples.xml, то основной пакет ресурсов обычно называется alvex-course-examples.properties, а пакеты для разных языков соответственно: alvex-course-examples_ru.properties для русского, alvex-course-examples_de.properties для немецкого и так далее.
Строка локализации названия и описания модели имеет формат:
<model_prefix>_<model_name>.[title|description]=Имя или описание модели
Строка локализации всех элементов модели кроме ограничений имеет формат:
<model_prefix>_<model_name>.<feature>.<feature_prefix>_<feature_name>.[title|description]=текст
А строка локализации значений ограничений имеет формат:
listconstraint.<feature_prefix>_<feature_name>.<constraint_value>=текст
- model_prefix - префикс модели
- model_name - название модели
- feature - “type”, “aspect”, “property” или “association”
- feature_prefix - префикс элемента (обычно совпадает с префиксом модели)
- feature_name - название элемента (то, что после префикса)
- constraint_value - значение ограничения, указанное в файле XML
Итого, файлы в пакете ресурсов для созданной выше модели будут выглядеть примерно так:
alvexcoursedocs_documents_model.type.alvexcoursedocs_document_partner_agreement.title=Distribution agreement alvexcoursedocs_documents_model.aspect.alvexcoursedocs_withVendorInfo.title=With vendor info alvexcoursedocs_documents_model.property.alvexcoursedocs_partnerDiscount.title=Partner discount, % alvexcource_docs_documents_model.association.alvexcoursedocs_documentManager.title=Contract Manager alvexcoursedocs_documents_model.property.alvexcoursedocs_vendorCompanyName.title=Vendor company name alvexcoursedocs_documents_model.property.alvexcoursedocs_vendorWebSite.title=Vendor web site
Если файл локализации содержит не-ASCII символы (например, русские буквы), то перед установкой в систему его нужно перевести в ASCII, чтобы не было проблем с кодировками в некоторых браузерах. Это делается командой native2ascii из состава дистрибутива Java, либо можно использовать сайт http://native2ascii.net/.
Для установки пакета ресурсов в систему складываем все файлы (alvex-course-examples.properties, alvex-course-examples_ru.properties и другие) в каталог tomcat/shared/classes/alfresco/messages/ или в messages внутри структуры вашего JAR-файла.
Для того, чтобы система знала, где искать ваши пакеты ресурсов, добавляем в файл контекста, созданный ранее для модели, свойство с именем labels. В пути до пакета ресурсов указываем общую для всех файлов пакета часть имени файла (до .properties и _ru.properties).
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans> <bean id="alvex-course-examples" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap,alvex-documents-registers-deployer"> <property name="labels"> <list> <value>alfresco.extension.messages.alvex-course-examples</value> </list> </property> <property name="models"> <list> <value>alfresco/extension/models/alvex-course-examples-model.xml</value> </list> </property> </bean> </beans>
Пример типа реестра, который был рассмотрен в этом посте, можно взять на Github. Будут предложения, какие еще вопросы можно вынести сюда, как “жизненно-важные” при создании своего типа реестров, - пишите в комментарии.
Комментарии
20/12/2013 - 09:07
20/12/2013 - 16:51
Прошу прощения, и правда не работает. В одной из форм шаблон для ассоциации (orgchart-picker) подключен к текстовому полю. И в префиксе было подчеркивание, а Вы уже в соседней теме написали, что так не бывает. ;) Кстати, другие спецсимволы можно, это только с подчеркиваниями такая проблема есть. Теперь исправила, все должно работать.
22/12/2013 - 11:51
Мало того Вы везде исправили...ЕЩЕ РАЗ СПАСИБО ОГРОМНОЕ!!!
14/01/2014 - 12:41
Один вопрос. Я скачала alvex-2013.10-for-4.2.c (community) для альфреско 4.2.с Попробовала добавить два поля. Все работает отлично, но есть одна проблема.
Я создала собственный тип, наследующий alvexdt:received В конфиге же поля списков рисуются шаблоном alvex-masterData-select.ftl. Тем не менее, списки, кроме списка статуса, не рисуются списками, а отображаются как простое текстовое поле. Это ограничение коммьюнити или же я что-то делаю не так?
14/01/2014 - 13:13
А в настройке реестра эти поля появляются доступными для подключения классификатора (http://docs.alvexcore.com/ru-RU/Alvex/2.0/html/Admin_Guide/registries-masterdata-internal.html)?
14/01/2014 - 13:44
Я почему растерялась: стандартный альвексовский реест входящих изначально (без настройки классификаторов) показывает пустой список, а мой реестр без настройки показывал текстовое поле, и я почему-то даже не полезла в настройки. Сейчас все отлично!
14/01/2014 - 13:48
15/01/2014 - 12:06
Не помню где читала, что при 46 тыс строк в дата листах при поиске скорость настолько падает, что в вэб скриптах происходит тайм аут. Насколько я понимаю, реализация здесь также через дата листы...
26/02/2014 - 17:11
26/02/2014 - 20:07
Вы не сделали файлы локализации. Или сделали, но они лежат не в tomcat/shared/classes/alfresco/extension/messages и называются не alvex-course-example.properties, alvex-course-example_ru.properties итп.
См. Can't find bundle for base name alfresco.extension.messages.alvex-course-examples, locale en_US
У вас система не может найти пакет ресурсов (локализацию). Либо положите ее в правильное место и назовите правильно, либо уберите блок про локализацию из файла контекста.
27/02/2014 - 02:46
Спасибо! Разобрался, просто я новичок и это первое дополнение которое пытаюсь сделать) (путался с каталогами и путями) В файле контексте был неправильно прописан путь к локализации.
27/02/2014 - 03:47
27/02/2014 - 21:03
Встречный вопрос: а почему нельзя создать разные сайты для всех филиалов? И в список участников сайтов добавить сотрудников конкретного филиала + бухгалтерию.
28/02/2014 - 12:11
Хотя я пока и не уверен, что проще - прикрутить описанные выше права или сделать какое-то консолидированное отображение документов со всех доступных пользователю сайтов. Так что если правда будет тикет - дайте в нем ссылку на этот тред, чтобы было понятно, какую задачу в конечном счете решаем.
03/03/2014 - 15:24
25/03/2014 - 12:52
Каким образом я могу использовать webscript для списка данных?
У меня задача такая: отправлять PDF файлы на FTP и по правилу обрабатывать при помощи web-скрипта, который будет создавать задачу для согласования документа для конкретных лиц и если решение принято положительно, то добавлять запись в конкретный список данных на основании полученных результатов согласования. Возможна-ли такая реализация и в какую сторону копать?
27/03/2014 - 09:45
> отправлять PDF файлы на FTP и по правилу обрабатывать при помощи web-скрипта
> [skip]
Эм. Возможно, я чего-то не понимаю. Загрузка в папку по FTP и срабатывание правила на папке - это ок. Но где в этой схеме веб-скрипт?
> добавлять запись в конкретный список данных на основании полученных результатов
> согласования. Возможна-ли такая реализация и в какую сторону копать?
Можно просто создавать элементы через API для datalist-ов. Отдельные API для реестров появятся, но чуть позже, когда будут обновлены классификаторы.
28/03/2014 - 09:21
28/03/2014 - 12:06
28/03/2014 - 20:31
28/03/2014 - 23:00
29/03/2014 - 00:14
Насчет сайтов. А проблема именно в создании? Или все-таки в использовании дальше (типа http://www.ossportal.ru/technologies/alfresco/blogs/894#comment-5506). Если второе, то "мы работаем над этим" (с) - см. http://issues.itdhq.com/browse/ALV-698. Хотя вот это в ближайшем релизе не обещаю. Скорее всего, через один.
Хотелка про разграничение доступа на уровне свойств время от времени всплывает, но она тянет за собой серию неудобных вопросов и тонких проблем, из-за чего постоянно откладывается.
Офтоп - а вы географически откуда? Что скажете насчет придти на http://www.ossportal.ru/technologies/alfresco/news/949 и пообщаться вживую?
29/03/2014 - 09:52
Очень интересно это.
29/03/2014 - 09:58
30/03/2014 - 20:09
31/03/2014 - 10:54
Интересно а есть-ли визуальный конструктор в Netbeans например или Eclipse для такого рода дел. А то запутаться очень легко.
31/03/2014 - 12:28
Пока нет, хотя у нас разработка такого конструктора есть в планах.
31/03/2014 - 14:40
31/03/2014 - 16:51
- Скорее всего, будет web вместо IDE - так сильно проще, потому что IDE много и разных.
- Плотно возьмемся за это после EE 2.1, т.е. в конце мая. Где-то в июне будет роадмап и сроки.
31/03/2014 - 13:16
31/03/2014 - 16:47
- Задание ширины колонки в конфиге Share
- Возможность потаскать ее мышкой в интерфейсе
Если есть мысли, что еще нужно - рассказывайте, будем думать. :-)
01/04/2014 - 07:55
01/04/2014 - 16:13
01/04/2014 - 16:19
01/04/2014 - 08:00
01/04/2014 - 08:28
01/04/2014 - 10:22
01/04/2014 - 10:27
01/04/2014 - 10:37
01/04/2014 - 11:41
26/06/2014 - 06:37
29/06/2014 - 20:41