Про js и 1С или как я ужа с ежом скрещивал

Опыт создания динамического списка для обычной формы на js в html и подводных камнях, которые обязательно встретятся при написании оного.

Однажды тихими вечером… Впрочем, отбросим литературные изыски.  В нашей организации довольно много различных бизнес-процессов, которые различаются реквизитами, документами и при этом используют один вид задач для движения по точкам. Задачи показываются в одном списке, пользователи их видят, нажимают кнопочки, исполняют, все как обычно. Колонки в списке задач были жестко заданы, показывая информацию из различных процессов. До определенного момента, пока количество ключевых параметров в бизнес-процессах примерно соответствовало, это всех устраивало. Но в один "прекрасный" момент у нас появились задачи с неизвестным заранее количеством ключевых реквизитов (как пример сведения об исполнителях групповых задач) и список перестал устраивать. Попытка пойти по пути СКД не увенчалась успехом — слишком долго перерисовывалась картинка при обновлении. Динамический список в обычной форме отсутствует, а управляемую форму в обычном клиенте нельзя (или я не знаю как) закрепить на экране. Такие мелкие казалось бы недостатки привлекали внимание руководства и обе идеи забраковали. Остался один вариант — создать заполняемый HTMLДокумент. Опыт создания такого документа, а точнее способ избежать все ошибки, которые я совершил при его создании, предлагаю уважаемым читателям. Сразу отмазка ака дисклеймер. Все справедливо и проверено для windows и обычных форм. Динамические не тестировались, линукс тем более.

При  написании списка использовалась публикация Инфостарта: Javascript и 1С. Кросс-платформенное взаимодействие. Спасибо автору и участникам дискуссии.

Итак, сначала советы, куда без них.

Совет первый: Обязательно ориентируйтесь на версии IE, установленные на клиентских машинах, которые предполагают использование вашей разработки. Фрик-шоу стандартов от Микрософт, которая как тот сержант, одна идет в ногу, изрядно подпортит вам кровь при написании. Все, что ниже 9 версии, сразу ставит крест на разработке, ибо кривая поддержка css и обработки событий делает трудозатраты непропорциональными выхлопу. У меня два основных браузера были IE10 и 11 и это уже вносило достаточно разнообразия в рутинный процесс программирования. Обязательная строка в заголовке, если вы не хотите скатиться во встроенный в 1С IE6:

<meta http-equiv="X-UA-Compatible" content="IE=edge">

Первые кавычки расскажут браузеру, что нужно использовать HTML5 по возможности, вторые заставят поддерживать самую новую версию браузера, установленную на компьютере.

Совет второй: Для общения с 1с используйте javascript глобальные переменные и JSON нотацию. Любая функция js нашего документа возвращает в 1с неинициализированный COMobject, который мы не можем обработать. Возможно есть способы обойти эту ситуацию, мне они неизвестны. Поэтому любой вызов функций HTML документа лучше делать в два приема: 1.Вызов функции, которая что-то запишет в переменную документа в виде строки json, затем обращение к этой строке.

Совет третий: По максимуму используйте события click и dblclick в 1с для получения данных, остальные же события инициируйте и обрабатывайте скриптами документа. И я объясню почему: Единственный вариант вызвать событие в 1с умер вместе с IE9. Это метод fireEvent. Он пробивал броню parentWindow и мог вызвать некое событие в 1С. Метод js document.dispatchEvent стартует событие на странице, но за пределы документа событие не выходит и в 1с его обработчик не срабатывает. Плюс большая часть событий, которые являются свойствами поля HTMLдокумента, давно отменены в стандартах HTML и не поддерживаются тем же IE11.

Совет четвертый: забудьте про новомодные стрелочные функции, хитрые врапперы и замыкания. Чем дубовее код, тем лучше. Нет, врапперы и замыкания существуют, но я даже боюсь предположить какой стандарт js поддерживают IE  в различных изводах. Постоянно будете натыкаться на то, что те или иные приемы не работают. Например для меня открытием стало, что this в IE10 всегда обозначает глобальный контекст, даже если его запихнуть во враппер, а объявление переменной let работает вообще непонятно как.

Совет пятый, вытекающий из предыдущих: Никогда не забывайте про сайты типа mdn или w3Schools, на которых вы можете узнать какие методы поддерживаются той или иной версией браузера.

Итак, сама разработка html для работы с 1с.

Первое что я начал делать: css стили, чтобы переложить отрисовку элементов на браузер.

Сначала нам нужно опеределить стиль для контейнера, который будет содержать информацию о задаче. Поскольку тег div будет использоваться много где, то для контейнера определим отдельный класс.

Небольшое отступление: Все, что я сейчас делаю со стилями — это мой личный вкус, настройка вида списка и его элементов может быть какой угодно. Если результат вам не понравился, смело экспериментируйте.

//чтобы не расписывать, какие селекторы что обозначают,
//отправлю к справочнику по css https://www.w3schools.com/cssref/css_selectors.asp
.container {
-ms-user-select: none;
border: 2px solid transparent;
background-color: hsl(0, 0%, 95%);
border-radius: 10px;
padding: 10px 15px 10px;
margin: 10px 0;
cursor: pointer;
min-width:120px;
}

Определим определим чередование цветов для контейнера. Обратите внимание, я использую функцию hsl чтобы не заморачиваться с вычислением цветов. Берем один цвет, и просто меняем его яркость.

.container:nth-child(even) {
background-color: hsl(0, 0%, 90%);
}

Выделим рамкой элемент, на который указывает курсор

.container:hover {
border-color: hsl(0,0%,60%);
}

Для задач у нас выставлены приоритеты, которые определяются атрибутом контейнера priority. Будем выделять их цветом и подкрашивать контейнер разными оттенками в зависимости от чередования

.container[priority='1'] {
background-color: hsl(120, 92%, 90%);
}
.container[priority='1']:nth-child(even) {
background-color: hsl(120, 92%, 85%);
}

.container[priority='2'] {
background-color: hsl(203, 92%, 90%);
}
.container[priority='2']:nth-child(even) {
background-color: hsl(203, 92%, 85%);}

Устанавливаем стиль для заголовка контейнера.

.tooltip{
margin: 1px;
line-height: 0.9em;
color: hsl(0, 0%, 15%);}

Стиль для тега time. Поскольку он будет использоваться только для одной опции, определяем стиль для всего тега

time {
float: right;
color: #aaa;
}

Теперь добавим стиль таблицы, в которой будем выводить информацию о задаче.

table {
table-layout:fixed;
white-space: pre-wrap;
width:100%;
min-width:90px;
text-align: left;
margin: 10px 0px;
border-collapse: separate;
border-spacing: 5px;
background: transparent;
color: #656665;
border: transparent;
}

Определим стиль ячеек и заголовков таблицы.

table th {
font-size: 12px;
padding: 1px;
margin: 1px;
}

table td {
font-size: 12px;
background: #fff;
padding: 5px;
}

И добавим описание стиля содержимого ячеек.  В ячейки таблицы я вкладываю контейнеры div, потому что для ячеек <td> есть ограничения по настройке стиля.

table div{
position: relative;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre-wrap;
}

Теперь нужно добавить всплывающую подсказку, на случай если содержимое не влезает в ячейку (в самой ячейке текст будет забиваться точками). Обратите внимание на z-index. Он нужен, чтобы всплывающая подсказка оказалась поверх всех элементов контейнера.

 table td span {
display: none;
position: absolute;
z-index:2;
background-color: cornsilk;
color: inherit;
margin: 10px 0;
text-align: center;
border-radius: 6px;
padding: 8px 0;
}
td:hover span {
display: block;
}
table div{
position: relative;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre-wrap;
}

Добавим описание контекстного меню. Само меню не должно быть привязано к элементу и должно быть поверх всех элементов, поэтому установим position:fixed и z-index:99;

.contmenu {
position:fixed;
background-color: hsl(240, 20%, 92%);
list-style-type: none;
margin: 0px;
float:right;
padding: 5px 5px 5px 0px;
border-radius: 6px;
z-index:99;
font-size:medium;
white-space: pre-wrap;
}

Выбранные пункты меню нужно выделять:

.contmenu>li {
background-color: hsl(240, 20%, 95%);
margin: 0px 0px 1px 15px;
padding: 10px 30px 10px 10px;
}

.contmenu>li:hover{background-color: hsl(240, 20%, 80%); color:white}

Получившийся результат выглядит так:

Чтобы контекстное меню на странице работало корректно, в 1с в свойствах формы списка укажем свойство контекстное меню: отсутствует.

На этом со стилями все. Почти. Мы не учли такую штуку, как размер окна, когда при уменьшении ширины наша прекрасная картинка начинает превращаться в  нечто неудобочитаемом. Добавим еще немного стиля.

@media screen and (max-width:400px){
td { display:flex; text-decoration:underline; width:100%}
th { display:none;}
td:hover span {left:20px;}
.contmenu
{display:block;
font-size:small;
overflow-x: hidden;
text-overflow: ellipsis}
time {display:none}
}

Этим стилем мы показываем, что при размере окна меньше 400 пикселей нам надо свернуть информацию в блок, спрятать заголовки и немножко урезать осетра в шрифтах, чтобы контекстное меню можно было прочитать:

Вот теперь стили полностью готовы, можно переходить к скриптам.

Первым делом определим переменные, которые будут отвечать у нас за общение с 1С

var arr=''; //это будет переменная, в которую 1С будет закачивать строку json с данными
var retval=''; //переменная, в которую мы пишем результат выбора на странице
var cur='';//при обновлении экран иногда(не понял в каких случаях)уезжает к первому элементу
//здесь мы будем хранить элемент на который надо спозиционироваться при обновлении страницы.
var filter=new Object; //Отборы в списке. Будем на них строить фильтрацию в документе.

Эти переменные будут доступны в 1С через свойство полеHTMLдокумента.документ.parentWindow.

Создаем несколько функций, которые будут основой для работы со страницей.

1.Удаление всех контейнеров. Я решил не заморачиваться с отрисовкой каждого отдельного контейнера при обновлении, потому что в таком случае нужно будет хранить где-то позиции элементов, или искать их по неким атрибутам, чтобы добавлять последовательно данные. Работы много, а конечный пользователь разницы не увидит. Поэтому, при каждом обновлении списка задач мы просто убиваем все элементы списка и добавляем заново по новым данным из 1С.

function deleteAllTasks() {
var x=document.querySelectorAll('div[id]');
if (x.lenght!=0){
for (i = 0; i < x.length; i++) {document.body.removeChild(x[i]);}
}
}

2. Присваивание элемента выбора переменной, из которой мы получаем данные в 1С.

function addValue(event) {
retval = event.currentTarget.id;
event.currentTarget.focus();
//фокус тут для манипуляций с курсором ожидание/нажатие.
//в статье я этой темы не коснулся как маловажной, поэтому строку просто игнорьте %)
}

Заметьте, я здесь и далее использую свойство currentTarget вместо target. Это сделано для того, чтобы получать элемент (контейнер) к которому обработчик прикреплен, а не элемент, вызвавший событие (например ячейка таблицы).

3.Функция очистки значений. Добавлена чтобы при частично заполненной странице при нажатии на пустое пространство не вызывалась последняя нажатая задача и для того, чтобы запоминать последнюю выбранную задачу (для запоминания последней позиции при автообновлении). У функции есть параметр, в зависимости от которого поведение меняется.

function clearVal(event,mean) {
if (mean) {(event.stopPropagation) ? event.stopPropagation() : event.cancelBubble = true;
cur=event.currentTarget.id}
else{
retval=''};
}

4. Функция обработки одного нажатия. Нужна для работы с курсором и для контекстного меню

function oneClick(event) {
event.currentTarget.blur();
closeMenu(); //удаляем меню при нажатии
}

Раз уж мы коснулись контестного меню, то следующие функции будут для работы с ним. Я решил что хранить контекстное меню в элементе и прятать/открывать его неудобно. Будем меню создавать по клику и удалять после нажатия.  На быстродействие это не влияет в заметных масштабах, поэтому считаю такой вариант оптимальным в плане сложности создания.

5. Функция включения отборов из контекстного меню. В ней мы работаем с глобальной переменной filter.

function enableFilter(event) {
var action=event.currentTarget.getAttribute('action');
var curFilter=event.currentTarget.getAttribute('name');
switch (action) {
case 'add':
filter[curFilter]=event.currentTarget.getAttribute('value');
break;
case 'deleteAll':
filter=new Object;
break;
default:
delete filter[curFilter]
break;
}
}

6. Удаление контекстного меню. Все просто. Нашли по id, удалили.

function closeMenu(){
var x=document.querySelectorAll('.contmenu');
if (x.length!=0){
for (i = 0; i < x.length; i++) {x[i].parentNode.removeChild(x[i]);}
}
}

7. Создание контекстного меню.

function contextMenu(event) {
closeMenu();
var menu=document.createElement('ul');
menu.className='contmenu'; //присвоим ранее определенный в css класс.
menu.style.left=event.clientX+'px'; //настроим позицию относительно пользовательского экрана
menu.style.top=event.clientY+'px';//меню будет появляться в точке, где была нажата мышка.
var type=event.currentTarget.getAttribute('name'); //здесь я добавил только один пункт. Остальные отборы целиком на вашей фантазии
var hasFilter=0; //переменная, проверяющаяя, нужно ли добавить пункт "снять все отборы"
if (type!=''){
var action='add';//у нас есть три типа действия, которы потом обрабатываются в функции enableFilter
var actionName='Отбор по типу задачи: ';
if (filter.hasOwnProperty('type')){hasFilter++; if (filter['type']==type){action='delete';actionName='Отключить отбор по типу задачи: '}}
var li=document.createElement('li');
li.innerHTML=actionName+type;
li.setAttribute('id','contmenu');
li.setAttribute('name','type');
li.setAttribute('value',type);
li.setAttribute('action',action);
li.addEventListener('click',enableFilter,true); //добавим обработчик нажатия.
menu.appendChild(li);}
if (hasFilter!=0) //если есть отборы, добавим пункт "отключить все отборы"
{var li=document.createElement('li');
li.innerHTML='Отключить все отборы';
li.setAttribute('id','contmenu');
li.setAttribute('name','');
li.setAttribute('value','');
li.setAttribute('action','deleteAll');
li.addEventListener('click',enableFilter,true);
menu.appendChild(li);}

event.currentTarget.appendChild(menu);
event.currentTarget.blur();//снимем фокус с контейнера.
}

Теперь перейдем к функциям непосредственного создания списка.

8. Создание списка задач. id объекта в данном случае — строковое представление UID задачи, полученное из 1С

function createPage() {
var tableObject=JSON.parse(arr); //обработаем полученную из 1С строку
for (var k in tableObject) {
var obj=tableObject[k];
if (document.getElementById(obj.taskId)==undefined){ //если элемента с таким id нет (это аппендикс от попыток обновлять странцу через отдельные элементы))
var div=document.createElement('div');
var d=new Date(obj.taskDate);
if (filter.hasOwnProperty('type')) {if (obj.taskName!=filter.type){continue;}} //проверяем на фильтр. Если отбор установлен, пропускаем все что не соответствует.
div.setAttribute('id',obj.taskId); //устанавливаем атрибуты по которым элемент можно найти и
div.setAttribute('name',obj.taskName);//отфильтровать (используются в вызове контекстного меню)
div.setAttribute('dateTime',d);
div.setAttribute('priority',parseInt(obj.taskPriority)); //для расцвечивания и возможного отбора по важности
div.setAttribute('tabindex',-1);// табиндекс не позволяет переходить на элемент и фокусироваться на нем. фокус только скриптом.
div.className='container';
div.addEventListener('dblclick',addValue,true); //присваивание ид возвращаемому значению.
div.addEventListener('mouseover',function(e){return clearVal(e,true)},true); //вот тут делаем два враппера очистки значения,
div.addEventListener('mouseout',function(e){return clearVal(e,false)},true);//когда мышь над элементом и уходит с него
div.addEventListener('click',oneClick,true);
div.addEventListener('contextmenu',return contextMenu,true); //вызов контекстного меню
var t=document.createElement('time');
t.innerHTML=d.toLocaleString();//нарисуем дату задчи.
div.appendChild(t);
var p=document.createElement('p');
p.className='tooltip';
p.innerHTML=obj.taskName;
div.appendChild(p);
var info=obj.info; //для задач которым нечего сказать миру таблицу делать не будем.
if (info.lenght!=0){
createTable(div,info)};
document.body.appendChild(div);}
}
if (cur!=''){ //а вот тут нам пригодилась переменная на которую позиционируемся.
//Вышло не очень удачно, потому что по факту позиционирование идет на последний элемент над которым пробежала мышка
//Выход есть, но в этой стаье я его не рассматриваю, потому что у меня в списке нет позиционирования по одному клику.
var elem= document.getElementById(cur);
if (elem!=undefined)
{elem.scrollIntoView();}
else {cur=''}}
}

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

function createTable(container,source) {
var table=document.createElement('table');
var trH=document.createElement('tr');
var trD=document.createElement('tr');
if (source.length==1){trH.style.display='none'}// таблицу из одной колонки делаем без заголовка
for (var n in source){
var pair=source[n];//получили сложный объект который представляет нам одну колонку таблицы
var keys=Object.keys(pair);//поскольку мы не знаем названий колонок выводимых в задаче, получим их из объекта
var th=document.createElement('th');
var td=document.createElement('td');
var div=document.createElement('div');
th.setAttribute('width',pair[keys[0]]['width']);//в паре колонка-значение в 1С мы добавляем еще
td.setAttribute('width',pair[keys[0]]['width']);//различные реквизиты, помогающие нам оформить таблицу. В данном случае - ширину ячеек
div.innerHTML=keys[0];//заголовок
th.appendChild(div);
var div=document.createElement('div');
var span=document.createElement('span');
div.innerHTML=pair[keys[0]]['value'];//установим значение
span.innerHTML=pair[keys[0]]['value'];
td.appendChild(div);
td.appendChild(span);
trH.appendChild(th);
trD.appendChild(td);
};
table.appendChild(trH);
table.appendChild(trD);
container.appendChild(table);
}

Все ключевые функции написаны. Оформляем в html шаблон, и используем.

В 1С нам нужна процедура выдачи данных для странички (фоновое задание как вариант), обработчик события "ondblclick",  через который мы узнаем УИД задачи которую нужно открыть (глобальные переменные, retval, помните?) и обработчик события "onclick" для обработки событий контекстного меню. Когда мы создавали меню на страничке, мы присвоили ему id contmenu. В обработчике события "onclick" поставим условие:

Если pEvtObj.srcElement.id="contmenu" Тогда
....
КонецЕсли;

Обновление списка можно повесить например на скрытый справочникСписок и процедуру "приОбновленииОтображения". Для этого достаточно на обычную форму добавить скрытый элемент и указать ему период автообновления. Вся форма будет обновляться, независимо от того, виден элемент или нет.

На этом все. Буду рад, если статья поможет кому-то не набить тех же шишек, что и я.

27 Comments

  1. Blagin

    Не хватает картинок (анимации) результата.

    Reply
  2. ntemny

    На выходных добавлю.

    Reply
  3. Gilev.Vyacheslav

    https://hitech.vesti.ru/article/1166293/

    https://wonderland.v8.1c.ru/blog/perevod-klientskikh-prilozheniy-dlya-windows-na-ispolzovanie-webkit-optimizatsiya-otobrazheniya-html/

    а не проще как раз не ориентироваться на старые IE и программно блокировать их использование?

    Reply
  4. ntemny

    Не проще.Реализовано в версии 8.3.14.1565 Не все используют последнюю версию платформы.

    Reply
  5. sikuda

    (4) «Делегаты! Если у меня когда-нибудь будут дети, я повешу им на стену портрет прокуратора Иудеи Понтия Пилата, чтобы дети росли чистоплотными. Прокуратор Понтий Пилат стоит и умывает руки — вот какой это будет портрет.»

    Венедикт Ерофеев. Москва-Петушки

    https://support.microsoft.com/ru-ru/help/17454/lifecycle-faq-internet-explorer

    http://sikuda.ru/?p=10

    Reply
  6. ArchLord42

    (3) не у всех стоит 14 платформа

    Но в целом можно и 11 ИЕ включить для 1С, надо только в реестр заглянуть.

    Reply
  7. ntemny

    (6) Если на машине не установлен 11ИЕ, то и включать нечего. Я понимаю, что ситуация странная, но бывает и вот так, что обновлять нельзя, приходится работать с тем что есть.

    Reply
  8. acsent

    на чистом жс и html?

    даже без jquery, не говоря о реактах?

    Reply
  9. Gilev.Vyacheslav

    (4) (6) понятно что пока 14й релиз не у всех, но судя по новому лицензированию на 8.3.13-8.3.14 фирма 1С пересадит всех насильно в самое ближайшее время, так как теперь «новое правило отъема денег у населения» )))

    но не надо на меня набрасываться, я просто спросил )

    Reply
  10. ntemny

    (8)ага. страничка 10кб живет только в 1с, квери-300кб. смысл? да и фиг его знает, как он в старых браузерах себя поведет.

    Reply
  11. ntemny

    (9)А подробности есть? Как они могут заставить? Не в порядке набрасывания, не понимаю о чем речь просто %)

    Reply
  12. acsent

    (10) Так его фишка как раз и была чтоб кроссбраузерность соблюсти

    Reply
  13. Балабас

    примерно так:

    «Платформа: 1С:Предприятие 8.3 (8.3.9.2170)

    Ошибки:

    ———————————————————————————

    21.02.2019 17:39:42

    Для работы с информационной базой необходима версия платформы не меньше, чем 8.3.13.

    Текущая версия 8.3.9.2170.»

    Reply
  14. Rustig

    а какая изначально задача стояла? подробнее можете написать?

    упомянули СКД, динамический список и другое…

    Reply
  15. ntemny

    (12)ну я то больше на объем текста в шаблоне ориентировался

    Reply
  16. ntemny

    (13) самописным это не грозит %)

    Reply
  17. ntemny

    (14)Я ж вроде описал в начале статьи. Задачи две: 1. сделать подобие динамического списка на обычной форме, 2. выводить произвольное количество информации о задаче в интерактивный список

    Reply
  18. Rustig

    (16) для постановки задачи можно использовать несколько раз «зачем?» — зачем вам динамический список на обычной форме? —

    … я реализовал подобие динамического списка через ТаблицуЗначений на форме — во многих моих публикациях это используется, до сих пор применяю этот метод при решении задач…

    но зачем вам это делать?,- непонятно пока

    какую произвольную информацию о задаче вам надо выводить?

    какую задачу нельзя решить в 1с стандартными способами ? так, что приходится скрещивать ужа и ежа? в чем сложность задачи?

    Reply
  19. ntemny

    (18)Тема публикации «программирование», а не «постановка задачи», поэтому я бы не хотел углубляться в вопросы целесообразности тех или иных решений.

    Reply
  20. Rustig

    (19) не хочу делать выводы раньше времени, но вы не даете пищу ля размышления.

    в чем заключается здравый смысл именно в таком в вашем решении?

    может быть такое, что вы усложнили себе задачу?

    без картинок конечно не представить глубину описанной проблемы и целесообразность ее решения.

    тема джейсон интересна мне, стараюсь вникать в публикации на ИС, поэтому ничего личного, просто бизнес.

    Reply
  21. Gilev.Vyacheslav

    (11) что ж такие ленивые

    держите https://1c.ru/news/info.jsp?id=25491

    Reply
  22. Gilev.Vyacheslav

    (17) налоги вы в самописной считаете? или просто не в 1С?

    Reply
  23. ntemny

    (22)Для налогов отдельный сервер. По сообщению ниже. Это не новость, Б.Нуралиев деление корп и проф еще на erp-форуме анонсировал.Какое это имеет отношение к насильному пересаживанию всех на последние версии платформы?

    Reply
  24. Gilev.Vyacheslav

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

    то что вас пока не коснулось это вам везет, что делать тем у кого erp? )))

    Reply
  25. ntemny

    (24)Ну вы же понимаете, что erp это не аргумент в споре%) Я знаю людей, которые бух отчетность до сих пор в семерке сдают. Мне «везет» в том, что я как раз не могу поставить последний релиз по определенным причинам и приходится выкручиваться. Вот опыт такого выкручивания и переношу в статью, чтобы у людей со схожими проблемами проблем было меньше. Аналогично мне пришлось в свое время писать парсер json, хотя в релизах 8.3 он уже наличествовал тогда. А так конечно, о вебките я мечтал со времен, когда он для 1с линуксовой появился.

    Reply
  26. Gilev.Vyacheslav

    (25) проблема в том что вы думаете что я с вами спорю )

    Reply
  27. ntemny

    (26)Тонко, по-заграничному….

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *