Перейти к содержанию

Примеры JS и CSS

ℹ️ Обратите внимание: добавленные ранее 2.256 версии вставки не будут корректно работать при переходе из старого МТФ/НТФ в новый в SPA.

Подробнее о правилах миграции JS-вставок со старой карточки задачи в SPA

Примеры JS-вставок

Изменение значения одного ДП в зависимости от другого ДП

Изменение значения одного ДП в зависимости от другого ДП обычно настраивается с помощью смартов. Но если такой пакет действий привязан к смарт-событию "После смены ДП", то он будет выполняться только в момент сохранения карточки задачи. Чтобы при изменении одного ДП пользователь мог сразу увидеть обновленное значение второго ДП, без необходимости лишний раз сохранять карточку задачи, можно настроить изменение с помощью приведенной ниже js-вставки.

Новый МТФ

//После выбора даты и времени в ДП 74, установить в ДП 102 = ДП 74 + 1 час
window.addEventListener('NewTaskLoadFinished', function(event) {

let ep74 = new ExtParam(74);        //ДП "Дата с"

// Создаем функцию с обработкой изменения ДП
function f_change() {
  let ep102 = new ExtParam(102);               //ДП "Дата по"
  if (ep74?.val()) {                                //если дата не введена (null в "Дата с"), то выход
  let date1 = new Date(ep74.val())
  let date2 = date1.setHours(date1.getHours() + 1)        //плюс 1 час к значению "дата с"
   ep102.val(date2);
   }
  else {return
  };
}

//Привязываем функцию к событию изменения значения ДП с ID=74
ep74.change( function () {
   f_change();
});

//Вызываем привязанную функцию
f_change();

})

Старый МТФ

К дате, которая записана в ДП ID=123, надо прибавить 1 час и записать в ДП ID=456.

(window).addEventListener('MTFMainLoadFinished', function() {

 //Получаем объект ДП с ID=123
 var ep123 = new ExtParam(123);

 // Создаем функцию с обработкой изменения ДП
 function f_change() {               
   var ep456 = new ExtParam(456);
   var text1 = ep123.val();
  var date1 = new Date(text1.replace(/(\d+).(\d+).(\d+)/, '$3/$2/$1'));
  date1.setHours(date1.getHours() + 1);               // прибавляем 1 час

   var newFormatedDate = date1.format("dd.MM.yyyy H:mm").toString();
  ep456.val(newFormatedDate);
  ep456.save();
 }

 //Привязываем функцию к событию изменения значения ДП с ID=123
 ep123.change( function () {
    f_change();
 });             

 //Вызываем только что привязанную функцию
 f_change();

});

Встaвка произвольного HTML в карточку задачи

Новый МТФ

window.addEventListener('MTFMainLoadFinished', function onMTFInit(e) {
       const ctx = e.detail;
       const taskInitId = ctx.taskId;

       window.addEventListener('MTFMainDestroyed', function onMTFDestroy(e) {
           const ctx = e.detail;
           const taskDestroyId = ctx.taskId;
       }, { once: true });

      // встaвка произвольного html
       ctx.insertHtmlBefore('mainparams', `<div class="test">test insertHtmlBefore mainparams</div>`);
       const div = document.createElement('div');
       div.textContent = 'test insertHtmlAfter mainparams'
       ctx.insertHtmlAfter('mainparams', div);
       ctx.insertHtmlBefore('extparams', `<div class="test">test insertHtmlBefore </div>`);
       ctx.insertHtmlAfter('extparams', `<div class="test">test insertHtmlAfter extparams</div>`);
       ctx.insertHtmlBefore('comments', `<div class="test">test insertHtmlBefore comments</div>`);
       ctx.insertHtmlBefore('block:2490', `<div class="test">test insertHtmlBefore block:2490</div>`);
       ctx.insertHtmlAfter('block:2490', `<div class="test">test insertHtmlAfter block:2490</div>`);
   });

Отображение встaвки произвольного HTML в пользовательском режиме:

Отображение встaвки произвольного HTML в пользовательском режиме

Отображение\скрытие поля в зависимости от условий

ℹ️ При использовании метода ep.hide() в разметке страницы останется пустое пространство, хотя сам ДП отображаться не будет. Если вам необходимо скрыть ДП, не оставляя пустого места, используйте css-селектор со свойством display: none.

Новый НТФ

Пример JS-вставки скрывающей ДП в зависимости от значения другого ДП

  ...
   {
  var ep30601, ep72670;
  function check() {
      const ep30601Val = ep30601?.val();
      console.log(ep30601Val)
      if (ep30601Val?.value == "test") {
          ep72670.show()
       }
      else {
          ep72670.hide()
       }
   };
 // Срабатывает на открытии и обновлении МТФ
  function onLoad() {
      console.log('js')
      ep30601 = new ExtParam(30601);
      ep72670 = new ExtParam(72670);

      check();

      ep30601.change(() => check());

   }
 //
  function onDestroy() {
      window.removeEventListener('MTFMainLoadFinished', onLoad);
      // Чтобы change не вызывался после нового открытия,
       // т.к. глобально в памяти ep30601 еще есть
      ep30601?.destroy();
      ep72670?.destroy();
   }

  window.addEventListener('MTFMainLoadFinished', onLoad);
  window.addEventListener('MTFMainDestroyed', onDestroy);

}
  ...

Скрыть или отобразить значение ДП в зависимости от ДП Выпадающий список

window.addEventListener('NewTaskLoadFinished', function() {
 var ep2725 = new ExtParam(2725);
 var ep2478 = new ExtParam(2478);
function toggleEp(epToHide = [], epToShow = []) {
   epToHide.forEach(id => {
     const ep = new ExtParam(id);
     ep.hide()
 })

   epToShow.forEach(id => {
     const ep = new ExtParam(id);
     ep.show()
 })
 }
 function emp_visible(){
       if (ep2725.val() && ep2725.val().text == 'Групповая') {
     toggleEp([2478], []);
   }
   else {
     toggleEp([], [2478]);
   }
   };
 emp_visible();
 ep2725.change(emp_visible);
})

Скрыть или отобразить значение ДП в зависимости от ДП Lookup

window.addEventListener('NewTaskLoadFinished', function() {
 let ep1248 = new ExtParam(1248);
 let ep1249 = new ExtParam(1249);
 let ep1861 = new ExtParam(1861);

function toggleEp(epToHide = [], epToShow = []) {
   epToHide.forEach(id => {
     const ep = new ExtParam(id);
     ep.hide()
 })

   epToShow.forEach(id => {
     const ep = new ExtParam(id);
     ep.show()
 })
 }
 function ep1248_change() {
         if (ep1248.val().taskId == '234443') {
         toggleEp([], [1249, 1861]);
         }
       else {
         console.log('hide all');
         toggleEp([1249, 1861], []);
       }
 };

   ep1248.change( function () {
   ep1248_change();
 });

  //Вызываем только что привязанную функцию
 ep1248_change();
})

Старый МТФ

Если значение ДП 123 равно "открыть", то ДП 456 отображается, иначе (по умолчанию) — скрывается.

(window).addEventListener('MTFMainLoadFinished', function()
{
var ep123 = new ExtParam(123);

function f_change() { 
  var ep456 = new ExtParam(456);
  switch(ep123.val()) {
      case 'открыть':
        ep456.show();         
        break;
      default:
        ep456.hide();
        break;
   };


ep123.change( function () {
  f_change();
 });             

//Вызываем только что привязанную функцию
f_change();
});

Сообщение пользователю во всплывающем окне (alert)

Старый МТФ

При изменении ДП 123 типа "выпадающий список" отображается сообщение с ID выбранного пользователя.

(window).addEventListener('MTFMainLoadFinished', function()
{
  var dropdown = new ExtParam(123);
  dropdown.change(function () {
     alert('Выбран пользователь с ID ' + dropdown.val());
  });
}

Изменение цвета текста колонки ДП таблица

Новый МТФ

Изменение цвета текста колонки

На примере таблицы 22581 Ежемесячных начислений красим колонку 10551

var table22581;

function onLoad() {
 table22581 = window.firstForma.ep.table(22581);

function changeRowColors() {
   console.log("begin");
  let tableRows = document.querySelectorAll(
    '[vh-ext-param-id="22581"] .ag-row'
   );
   console.log(tableRows.length);
  for (let i = 0; i < tableRows.length; i++) {
    let statusCell = tableRows[i].querySelector(
      '[col-id="c10551"] > div > span'
     );
    if (statusCell?.innerText == "Стандарт") {
       statusCell.style.color = "#00A86B";
       statusCell.style.fontWeight = 600;
     }
    if (statusCell?.innerText == "Нарушение") {
       statusCell.style.color = "#e63333";
       statusCell.style.fontWeight = 600;
     }
    if (statusCell?.innerText == "Фикс") {
       statusCell.style.color = "#2c65d8";
       statusCell.style.fontWeight = 600;
     }
    if (statusCell?.innerText == "Бонус") {
       statusCell.style.color = "#ddba00";
       statusCell.style.fontWeight = 600;
     }
   }
 }
 table22581.onTableLoaded(() => changeRowColors());
 table22581.onTableRowChanged(() => changeRowColors());
 table22581.onTableRowAdded(() => changeRowColors());
 table22581.onTableSaved(() => changeRowColors());
}

function onDestroy() {
 window.removeEventListener("MTFMainLoadFinished", onLoad);
 table22581?.destroy();
}
window.addEventListener("MTFMainDestroyed", onDestroy);
window.addEventListener("MTFMainLoadFinished", onLoad);

Изменение цвета отображения ДП в зависимости от значения

Новый МТФ

let ep91800,
   ep91800content,
   ep91800label

function onLoad() {
   ep91800 = new ExtParam(91800);
   ep91800content = document.querySelector('[vh-ext-param-id="91800"] > div');
   ep91800label = document.querySelector('[ep-wrapper-id="91800"] > div');

const reactionColor = () => {
      if (ep91800?.val() > 0) {
           ep91800content.style.border = '2px solid green';
           ep91800label.style.color = 'green'
           }

      else {
           ep91800content.style.border = '2px solid red';
           ep91800label.style.color = 'red'
           }
       }
   reactionColor()
   ep91800.change(() => reactionColor());
}

function onDestroy() {
   window.removeEventListener('MTFMainLoadFinished', onLoad);
   ep91800?.destroy();
   ep91800?.change(null);
   }

window.addEventListener('MTFMainDestroyed', onDestroy);
window.addEventListener('MTFMainLoadFinished', onLoad);

Старый МТФ

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

Пример 1

ДП "% предоплаты по договору" необходимо отображать зеленым цветом если его значение менее 20, желтым цветом если значение от 20 до 40, красным если свыше 40. Команда .replace(',','.').replace(/\s+/g,' ') приводит значение ДП к виду, который можно будет сконвертировать в float для сравнения с другими числами.

function paintYellow ( a )
  {
  a.css({"background-color" : "yellow", "font-weight" : "bolder"});
  a.blur(function () 
     {
    $(this).css({"background-color" : "yellow", "font-weight" : "bolder"});
             });
  a.hover(function () 
     {
    $(this).css({"background-color" : "yellow", "font-weight" : "bolder"});
     });
  }

function paintGreen ( a )
  {
  a.css({"background-color" : "green", "font-weight" : "bolder"});
  a.blur(function () 
     {
    $(this).css({"background-color" : "green", "font-weight" : "bolder"});
     });
  a.hover(function () 
     {
    $(this).css({"background-color" : "green", "font-weight" : "bolder"});
     });
  }

function paintRed ( a )
  {
  a.css({"background-color" : "red", "font-weight" : "bolder"});
  a.blur(function () 
     {
    $(this).css({"background-color" : "red", "font-weight" : "bolder"});
     });
  a.hover(function () 
     {
    $(this).css({"background-color" : "red", "font-weight" : "bolder"});
     });
  }

function paintWhite ( a )
  {
  a.css({"background-color" : "white", "font-weight" : "bolder"});
  a.blur(function () 
     {
    $(this).css({"background-color" : "white", "font-weight" : "bolder"});
     });
  a.hover(function () 
     {
    $(this).css({"background-color" : "white", "font-weight" : "bolder"});
     });
  }

$(document).ready(function () 
{
var Prepayment = new ExtParam (1234);
Prepayment.change ( function ()
 {
    if ( parseFloat( Prepayment.val().replace(',','.').replace(/\s+/g,' ')) == 0 ) 
  { paintWhite ( Prepayment ); } 
    if ( parseFloat( Prepayment.val().replace(',','.').replace(/\s+/g,' ')) > 0 && parseFloat( Prepayment.val().replace(',','.').replace(/\s+/g,' ')) <= 20 ) 
  { paintGreen ( Prepayment ); } 
    if ( parseFloat( Prepayment.val().replace(',','.').replace(/\s+/g,' ')) > 20 && parseFloat( Prepayment.val().replace(',','.').replace(/\s+/g,' ')) <= 40 ) 
  { paintYellow ( Prepayment ); } 
    if ( parseFloat( Prepayment.val().replace(',','.').replace(/\s+/g,' ')) > 40 ) 
  { paintRed ( Prepayment ); } 
 };
});

ℹ️ Если ДП доступен только для чтения, то "подкраска" по приведенному выше способу не будет производится, так как в данном случае "подкрашивается" ввод ДП. Чтобы "подкрасить" ДП, доступный только для чтения, необходимо аналогичным образом "подкрасить" ячейку таблицы, в которой расположен данный ДП.

Пример 2

Изменение цвета ДП + запрет на ввод нового значения в зависимости от значения другого ДП.

(window).addEventListener('MTFMainLoadFinished', function() // для карточки задачи
(window).addEventListener('NewTaskLoadFinished', function() // для карточки новой задачи
 {
         var paramControl = document.querySelector('vh-ext-param-control-wrapper[ep-wrapper-id="ХХХ"]'); // вместо ХХХ указывается ID ДП
         var ep301 = new ExtParam(301);   // переменная (ДП с ID=301)
         ep301.change(function() {         // вызов функции на изменение этой переменной
                 switch (ep301.val()) {
                         case 'Рубли':
                                 paramControl.style.pointerEvents = "none";           // контрол ДП с указанным ID становится неактивным
                                 paramControl.style.backgroundColor = "gainsboro";   // цвет фона для ДП с указанным ID
                                 break;
                         default:
                                 paramControl.style.pointerEvents = "auto";
                                 paramControl.style.backgroundColor = "white";
                                 break;
                 }
         });
         ep301.change();

 });

Видимость ДП в зависимости от значения другого ДП

Новый МТФ

function onLoad (event) {
let ep21 = new ExtParam(21, event.detail.cardGuid);
// Функция скрытия ДП без сохранения пустого пространства
function toggleElement(id, action) {
  const card = document.querySelector(`[data-card-guid="${event.detail.cardGuid}"]`);
  if (card) {
    const element = card.querySelector(`vh-ext-param-control-wrapper[ep-wrapper-id="${id}"]`);
        if (element) {
             element.style.display = action === "show" ? "" : "none";
         }
   }
}
// Показываем ДП 2181 если ДП 21 содержит значения задач 20706, 20662. Иначе скрываем
function epch () {
if ([20706, 20662].includes(ep21.val()?.taskId))
 {
       toggleElement(2181, 'show');
 }
else
 {
   toggleElement(2181, 'hide');
 }
}
epch()
ep21.change(() => epch());
}

function onDestroy() {
   window.removeEventListener('MTFMainLoadFinished', onLoad);
}
window.addEventListener('MTFMainDestroyed', onDestroy);
window.addEventListener('MTFMainLoadFinished', onLoad);

Старый МТФ

В зависимости от значения ДП с ID=1234 (в данном случае, это ДП типа "Checkbox") скрыть\показать ДП с ID=5678 (в данном случае это ДП типа "Таблица").

$(window).on('MTFMainLoadFinished', function(){
 //Получаем объект ДП с ID=1234
 var ep1 = new ExtParam(1234);

 // Создаем функцию с обработкой изменения ДП
 function f_change() {
   var ep2 = new ExtParam(5678);
   switch(ep1.val()) {
       case 'да':
         ep2.show();
         break;
       case 'нет':
         ep2.hide();
         break;
       default:
         ep2.show();
       break;
    }
 }

 //Привязываем функцию к событию Смены значения ДП с ID=1234
 ep1.change( function () {
   f_change();
 });

 //Вызываем только что привязанную функцию, чтобы при загрузке ДП с ID=5678 был отображен в соответствии с требованиями условий.
 f_change();
});

Описанный выше пример отслеживает только "ручное" изменение ДП. Если надо отслеживать также автоматические изменения (в том числе сделанные смартами), то можно использовать следующую конструкцию:

...
 var show = true;
 ...
 ep1.change(function(fromAutomatization) {
    ...
   if(show && fromAutomatization) {
         ep2.show();
         show = false;
    }
   else {
         ep2.hide();
         show = true;
     }
 });
 ...

Здесь используется событие change(), которое возникает при автоматическом обновлении ДП (в результате работы смарта или других изменений на сервере). В функции, которая подписывается на событие change(), используется параметр fromAutomatization, который равен true при автоматическом обновлении и null при изменении ДП пользователем (однако в силу технических ограничений этот параметр не доступен для ДП типа Lookup или "Выпадающий список").

Видимость ДП в зависимости от значений нескольких ДП

Новый МТФ

Отображение ДП с ID 3143, 222 в зависимости от их значений.

function onLoad (event) {
let ep3143 = new ExtParam(3143, event.detail.cardGuid);
let ep222 = new ExtParam(222, event.detail.cardGuid);

  function toggleElement(id, action) {
  const card = document.querySelector(`[data-card-guid="${event.detail.cardGuid}"]`);
  if (card) {
    const element = card.querySelector(`vh-ext-param-control-wrapper[ep-wrapper-id="${id}"]`);
        if (element) {
             element.style.display = action === "show" ? "" : "none";
         }
   }
}
  function epch () {
if (ep3143.val()?.taskId == 36032 || ep222.val()?.taskId == 36056)
 {
   toggleElement(3254, 'show');
 }
else
 {
   toggleElement(3254, 'hide');
 }
}
epch()
ep3143.change(() => epch());
}

function onDestroy() {
   window.removeEventListener('MTFMainLoadFinished', onLoad);
}
window.addEventListener('MTFMainDestroyed', onDestroy);
window.addEventListener('MTFMainLoadFinished', onLoad);

Старый МТФ

Отображение ДП с ID 237, 239, 249, 250 в зависимости от их значений.

$(window).on('NewTaskLoadFinished', function() //оставить эту строчку для карточки новой задачи
$(window).on('MTFMainLoadFinished', function() //оставить эту строчку для карточки задачи
{
var ep250 = new ExtParam(250);
var ep249 = new ExtParam(249);
var ep239 = new ExtParam(239);
var ep237 = new ExtParam(237);
ep250.change(function() {
  if (!!window.frames['ep250iframe'].$find('rgTable').get_masterTableView().get_dataItems()[0]) //проверяет, есть ли строки в таблице, если есть то возвращает true
    ep250.show();
  else
    ep250.hide();

  });
ep249.change(function() {
  if (!!window.frames['ep249iframe'].$find('rgTable').get_masterTableView().get_dataItems()[0]) //проверяет, есть ли строки в таблице, если есть то возвращает true
    ep249.show();
  else
    ep249.hide();

  });
ep239.change(function() {
  if (!!window.frames['ep239iframe'].$find('rgTable').get_masterTableView().get_dataItems()[0]) //проверяет, есть ли строки в таблице, если есть то возвращает true
    ep239.show();
  else
    ep239.hide();

  });
ep237.change(function() {
  if (!!window.frames['ep237iframe'].$find('rgTable').get_masterTableView().get_dataItems()[0])
    ep237.show();
  else
    ep237.hide();

  });
ep249.change(); //запускает функцию на действие "загрузка мтф"
ep239.change();
ep237.change();
ep250.change();
}
  function onDestroy() {
      window.removeEventListener('MTFMainLoadFinished', onLoad);
      ep249?.destroy();
      ep239?.destroy();
      ep237?.destroy();
      ep250?.destroy();
   }

  window.addEventListener('MTFMainLoadFinished', onLoad);
  window.addEventListener('MTFMainDestroyed', onDestroy);

Работа со связанными ДП

Старый МТФ

Если на карточке расположены два связанных ДП, доступных для редактирования, то при вызове обработчика change() для родительского ДП значение подчиненного ДП сбрасывается. Обойти это можно, если вместо обработчика change() вызывать собственную функцию.

Пример

Пусть на карточке задачи есть обычный ДП с ID = 11 и два связанных ДП — родительский с ID = 23 и подчиненный с ID = 34.

(window).addEventListener('MTFMainLoadFinished', function()
{
  var ep11 = new ExtParam(11);   // обычный ДП
  var ep23 = new ExtParam(23);   // родительский ДП
  var ep34 = new ExtParam(34);   // подчиненный ДП

  // Функция для родительского ДП
  function ep23СhangeFunction() {
     ...
  }
  ep23.change( function() {
    ep23СhangeFunction();
  });

  // Функция для подчиненного ДП
  function ep34СhangeFunction() {
     ...
  }
  ep34.change (function() {
    ep34СhangeFunction();
  });


  // для обычного ДП вызывается стандартный обработчик 
  ep11.change();

  // для связанных ДП вызываются собственные функции
  ep23СhangeFunction();
  ep34СhangeFunction();
});

Скрытие основных полей и кнопок карточки задачи

Старый МТФ

Cкрытие кнопок Делегировать, Установить срок, Маршрут, Вложить

#btnAdvancedTaskInfo,#btnChangeDueDate,#btnDelegateTask,#ctl00_formInner_NewTaskUploadsButton_btnUploads{display:none}

Скрытие кнопок От, Подписать, Уведомить, Вложить

$(window).on('NewTaskLoadFinished', function() {
var hideElems = [
'ctl00_formInner_newTaskSubscribeButton_btnSubscriber',
'ctl00_formInner_NewTaskUploadsButton_btnUploads',
'ctl00_formInner_newTaskFromButton_btnFrom',
'ctl00_formInner_newTaskNotifyButton_btnNotify'
];
$.each(hideElems, function(k,el){
$('#' + el).parent().parent().hide();
});
cmDependEp(depConf);
});

Скрытие текста задачи

#ctl00_formInner_newTaskText_TaskBlock{display:none}
#ctl00_formInner_dvFormTemplateContainer > table > tbody > tr:nth-child(10) > td.paramname.grayed{visibility: hidden}

Скрытие блока комментариев

#commentsAddDivContainer{display:none}
#ctl00_formInner_commentsWrapper1{display:none}

Транслитерация текста, введенного в ДП

Старый МТФ

Функция транслитерации (написания русских слов латинскими буквами) может применяться для автоматической генерации логина или e-mail сотрудника на основании его ФИО. Она полезна при автоматизации бизнес-процессов кадровой службы, связанных с приемом на работу новых сотрудников. Функция транслитерации реализуется с помощью следующей JS-вставки:

function translite ( string )
  {
  var dictionary = 
     { 
     "а": "a", 
     "б": "b", 
     "в": "v", 
     "г": "g", 
     "д": "d", 
     "е": "e", 
     "ё": "jo", 
     "ж": "zh", 
     "з": "z", 
     "и": "i", 
     "й": "y", 
     "к": "k", 
     "л": "l", 
     "м": "m", 
     "н": "n", 
     "о": "o", 
     "п": "p", 
     "р": "r", 
     "с": "s", 
     "т": "t", 
     "у": "u", 
     "ф": "f", 
     "х": "kh", 
     "ц": "ts", 
     "ч": "ch", 
     "ш": "sh", 
     "щ": "shh", 
     "ъ": "'", 
     "ы": "y", 
     "ь": "'", 
     "э": "e", 
     "ю": "ju", 
     "я": "ja",
     "А": "A", 
     "Б": "B", 
     "В": "V", 
     "Г": "G", 
     "Д": "D", 
     "Е": "E", 
     "Ё": "Jo", 
     "Ж": "Zh", 
     "З": "Z", 
     "И": "I", 
     "Й": "Y", 
     "К": "K", 
     "Л": "L", 
     "М": "M", 
     "Н": "N", 
     "О": "O", 
     "П": "P", 
     "Р": "R", 
     "С": "S", 
     "Т": "T", 
     "У": "U", 
     "Ф": "F", 
     "Х": "Kh", 
     "Ц": "Ts", 
     "Ч": "Ch", 
     "Ш": "Sh", 
     "Щ": "Shh", 
     "Ъ": "'", 
     "Ы": "Y", 
     "Ь": "'", 
     "Э": "E", 
     "Ю": "Ju", 
     "Я": "Ja",
     };
  return string.replace( /[\s\S]/g, function ( x )
     {
      if( dictionary.hasOwnProperty( x ) ) { return dictionary[ x ]; }
      else { return x; }
     });
  };

(window).addEventListener('MTFMainLoadFinished', function() {

 var RusAcceptant = new ExtParam(872); // ДП имя подписанта контракта на русском
 var EngAcceptant = new ExtParam (874); // ДП имя подписанта контракта на английском

 // На событие смены ДП с русским именем вешаем функцию транслитерации, которая записывает имя на транслите в ДП с английским именем

 RusAcceptant.change(function () { 
         EngAcceptant.val (translite (RusAcceptant.val()));
 });

});

Настройка скрывающегося блока ДП в карточке задачи

Старый МТФ

Рассмотрим возможность настройки скрывающего блока ДП на примере из двух ДП, один из которых будет скрывать по нажатию кнопки (ссылки).

Шаблон дизайна

В Дизайне категории необходимо войти в режим отображения HTML (см. правое меню на предыдущем скриншоте) и найти первую строчку блока ДП:

<div class="extparamsBlock">

В данном примере в блоке ДП есть 4 пары тегов <tr> ... </tr>, по две пары для каждого из двух ДП: одна пара на название ДП и другая на сам ДП. Надо разделить блок <div> на два таких же блока и прописать у каждого некоторые свойства. Между этими двумя блоками нужно вставить блок со ссылкой, при нажатии на которую будет вызываться js, скрывающий нижний блок ДП:

<div style="padding: 4px 0px 0px 10px;">
  <a style="text-decoration: underline;" onclick="javascript:ShowPanel(2); return false;" href="#" tabindex="0">
  <b>Дополнительные параметры</b>
  </a>
</div>

Результат должен быть такой:

<div class="extparamsBlock" style="" id="1">
  <table style="width:100%;table-layout:fixed;" class="" border="0">
    <tbody>
        <tr class="">
          <td valign="top" style="width: 32.743362831858406%; " class="">
              <label id="labelExtParam1158" for="ExtParam1158" class="LabelExtParameter1158" style="">
                 test1
              </label>
          </td>
          <td valign="top" style="width: 35.004916420845625%; " class="">
              &nbsp;
          </td>
          <td class="" style="width: 31.465093411996065%; ">
              &nbsp;
          </td>
        </tr>
        <tr class="">
          <td valign="top" style="width:33%;" class="">
              <input style="width: 205px; " class="ExtParameter" type="text" id="ExtParam1158" value="test1"/>
          </td>
          <td valign="top" style="width:33%;" class="">
              &nbsp;
          </td>
          <td class="">
              &nbsp;
          </td>
        </tr>
    </tbody>
  </table>
</div>
<div style="padding: 4px 0px 0px 10px;">
  <a style="text-decoration: underline;" onclick="javascript:CrmShowPanel(2); return false;" href="#" tabindex="0">
  <b>Дополнительные параметры</b>
  </a>
</div>
<div class="extparamsBlock" style="display: none; " id="2">
  <table style="width:100%;table-layout:fixed;" class="" border="0">
    <tbody>
        <tr>
          <td class="">
              <label id="labelExtParam1190" for="ExtParam1190" class="LabelExtParameter1190" style="">
                 **Сумма
              </label>
          </td>
          <td class="">
              &nbsp;
          </td>
          <td class="">
              &nbsp;
          </td>
        </tr>
        <tr>
          <td class="">
              <input style="width: 205px; " class="ExtParameter" type="text" id="ExtParam1190" value="**Сумма"/>
          </td>
          <td class="">
              &nbsp;
          </td>
          <td class="">
              &nbsp;
          </td>
        </tr>
        <tr>
          <td class="">
              &nbsp;
          </td>
          <td class="">
              &nbsp;
          </td>
          <td class="">
              &nbsp;
          </td>
        </tr>
    </tbody>
  </table>
</div>

На что стоит обратить внимание: -в первом блоке div добавлены свойства style="" и id="1"; -во втором блоке добавлены свойство style="display: none; " , которое скрывает этот блок по умолчанию, и id="2". После редактирования html-кода нажмите кнопку Сохранить. Теперь надо добавить js, который вызывается по нажатию на ссылку, добавленную выше отдельным блоком. В Дизайнере надо нажать кнопку Настроить вставки (js и css) (выделена на скриншоте выше) и добавить созданную js вставку:

function ShowPanel(panelid) {
  var pnl = document.getElementById(panelid);
  pnl.style.display = (pnl.style.display == "" ? "none" : "");
  if (panelid == 2)
    TaskUsedAsEpUpdateHelper.ReloadAndResizeGrid(102)
}

Заполнение контактных лиц при выборе компании

Старый МТФ

При выборе компании (ДП Lookup с ID=456) заполяется список контактных лиц (ДП Multilookup с ID=123, связанный с ДП 456).

var ep123 = new ExtParam(123), 
    ep456 = new ExtParam(456);

ep456.change(function () {
  ep123.getAvailableValues(function (d) {
  MultiTasksSelectExtParam.rebindGrid(123, d.map(function (d) { return d.value }));
  })
})

Персонализированная подсказка в категории

Старый МТФ

В карточке задачи можно выводить подсказки по работе с задачей. Подсказки настраиваются на вкладке "Примечания" для определенных статусов. Если вы хотите сделать текст подсказки персонализированным, можно выводить в нем имя пользователя. Подсказка для статуса "Новая" (в режиме HTML) Подсказка для статуса "Новая" в режиме HTML В данном примере имя пользователя хранится в скрытом ДП "Выбор пользователя" с ID=123.

(window).addEventListener('MTFMainLoadFinished', function() {
var ep123 = new ExtParam(123),
  username = ep123.textVal();
if(StateID = 1){$('#hintText > p:nth-child(1) > span > strong').text('Уважаемый(ая) ' + username.substring(0, username.length  1) + ',')}
});

Проверка настройки повторений при создании повторяющихся задач

Старый МТФ

В одной и той же категории часть задач может носить разовый характер, а часть — регулярно повторяющийся. Для повторяющихся задач включается флажок "Регулярно" (в данном примере этот ДП имеет ID=111). Важно, чтобы для повторяющихся задач было настроено повторение. Чтобы пользователи при создании таких задач не забывали настраивать повторения, добавляется проверка при нажатии кнопки "Поставить задачу": если на карточке создания новой задачи включен флажок "Регулярно", а настройки повторений не заданы, пользователю выдается предупреждение, задача не создается, и окно создания задачи остается открытым. Чтобы автоматизировать такую проверку, к карточке создания новой задачи можно добавить JS-вставку:

$(window).on('load', function() {
  // Создаем функцию вызывающую старый код кнопки постановки задачи с контекстом
  function evalInContext( context, js) {
    return function() {
        return eval(js);
     }.call(context);
  }

  var ep111 = new ExtParam(111);   // Значение ДП "Регулярно"    
  var submitBtnClickFunc = $('#btnPostTask').attr('onclick');   // Запоминаем настройки события при клике на кнопку постановки задачи
  $('#btnPostTask').attr('onclick', '');   // Очищаем событие при клике на кнопку
  $('#btnPostTask').click(function() {   // Проверяем условия при клике
    var tr = typeof getNtfModelTaskTimeSettings();
    // Проверяем условие:
    // если окно настройки повторений не открывалось вообще, то typeof getNtfModelTaskTimeSettings() будет 'undefined'
    // если окно настройки повторений открывалось, но расписание не установлено (не сохранено), 
    // то typeof getNtfModelTaskTimeSettings() будет 'object', в этом случае надо проверить его свойство .taskRecurrence — оно будет 'null'
    if (ep111.val() == 'да' && (tr == 'undefined' || (tr == 'object' && getNtfModelTaskTimeSettings().taskRecurrence == null))) {
        // Если выполняется условие отмены, выводим предупреждение
        dialogs.alert("Внимание!", "Создание поручений данного вида без расписания повторения запрещено!"); 
        return false;
     } 
    else {
        // Если условие не выполняется, запускаем старый код кнопки
        PostTaskClick(); 
     }
  });
});

Чтобы иконка настройки параметров повторения на панели инструментов была доступна только при включенном флажке "Регулярно", можно добавить такую JS-вставку (на панели инструментов нужная иконка -- 6-ая по порядку, с учетом разделителя):

$(window).on('NewTaskLoadFinished', function() 
{
  var ep111 = new ExtParam(111); 
  ep111.change(function () { 
    switch(ep111.val()) {
        case 'нет':   
          $('#NewTaskToolbar > ul > li:nth-child(6)').css("pointer-events","none")
          break;
        case 'да':
          $('#NewTaskToolbar > ul > li:nth-child(6)').css("pointer-events","auto")
          break;
        default:
          $('#NewTaskToolbar > ul > li:nth-child(6)').css("pointer-events","none")
          break;
     }
  });
  ep111.change(); 
});

Вызов функции swal(SweetAlert) с последующей обработкой

Новый МТФ

function showAlert5() {
 swal({
 title: 'Окончательно?',
 showCancelButton: true,
 confirmButtonColor: '#7ec9e1',
 confirmButtonText: 'Да',
 cancelButtonText: 'Нет',
 closeOnConfirm: false,
 closeOnCancel: false
}, function(isConfirm){

  if (isConfirm){

    swal.close();
    document.querySelector('#stepBtnUnderTaskText1718').click()

   } else {
   swal.close();
    document.querySelector('#stepBtnUnderTaskText1902').click()

   }
});
};

window.addEventListener('MTFMainLoadFinished', function() {
 document.querySelector('#stepBtnUnderTaskText1705').style.display = 'none';
 document.querySelector('#stepBtnUnderTaskText1718').style.display = 'none';
 document.querySelector('#stepBtnUnderTaskText1902').style.display = 'none';
})

Замена стандартного окна предупреждения (alert) на SweetAlert

Старый МТФ

Вместо стандартного окна предупреждения JS alert() можно использоватьфункцию swal() из Bootstrap Sweet-Alert. Начиная с версии 2.182 функция swal() не входит в поставку "Первой Формы", ее нужно предварительно загрузить:

var scr = document.createElement('script');     
scr.src = 'https://unpkg.com/sweetalert/dist/sweetalert.min.js';
var doc = document.getElementById ('aspnetForm');
doc.appendChild(scr);

ℹ️ Поскольку для использования функции swal() нужно загружать дополнительный скрипт из внешнего ресурса, функция не будет работать без доступа в интернет.

Окно с сообщением:

По клику на кнопку с ID=11 выполняется автоматизация, на время выполнения отображается окно.

$(window).on('MTFMainLoadFinished', function(){
  var scr = document.createElement('script');     
  scr.src = 'https://unpkg.com/sweetalert/dist/sweetalert.min.js';
  var doc = document.getElementById ('aspnetForm');
  doc.appendChild(scr);

  $('#taskFormCustomButtonId11').click(function(){ 
      swal({
          imageUrl:'/img/orig.gif',
           title:'Происходит запись на носитель.\n Пожалуйста, подождите.',
          showConfirmButton:false
       })
  });
});

Окно с сообщением и кнопкой подтверждения:

Кнопка подтверждения заменяет кнопку перехода по маршруту. Стандартная кнопка перехода скрывается.

$(window).on('MTFMainLoadFinished', function() {
  var scr = document.createElement('script');     
  scr.src = 'https://unpkg.com/sweetalert/dist/sweetalert.min.js';
  var doc = document.getElementById ('aspnetForm');
  doc.appendChild(scr);

// кнопки перехода по маршруту скрываются
$('#stepBtnUnderTaskText1234').hide();

if ($('#fieldState')[0].innerText=='ЭП записана на носитель'){
  swal({
      html:true,
       title:"",
       text: "<p style=\"font-size:16px;\"><span class=\"inline-comment-marker\">Ваша Электронная Подпись успешно записана на Вашу персональную карту.</span></p><p style=\"font-size:16px;\">Для активации Вашей Электронной Подписи нажмите \"Подтвердить получение ЭП\".</p>",
       type:"success",
      showConfirmButton:true,
      confirmButtonText: 'Подтвердить получение ЭП'
      },

       function(isConfirm) {
         $('#stepBtnUnderTaskText1234').click()
    })
  };
});

Окно с сообщением, полем для ввода и двумя кнопками:

Окно вызывается по клику на кнопку перехода по маршруту. Описание неверных данных из поля ввода записывается в ДП с ID=999.

$(window).on('MTFMainLoadFinished', function(){
  var scr = document.createElement('script');     
  scr.src = 'https://unpkg.com/sweetalert/dist/sweetalert.min.js';
  var doc = document.getElementById ('aspnetForm');
  doc.appendChild(scr);

  var
  a = document.getElementById('stepBtnUnderTaskText1234'),
  resultCss = "",
  ep999 = new ExtParam(999);

  // по кнопке перехода 'Проверка данных' не выполняется переход по маршруту
  if ($('#fieldState')[0].innerText == 'Проверка данных'{
      a.removeAttribute("onclick")};

  function addStyle(data) { 
      head = document.head || document.getElementsByTagName('head')[0]
      style = document.createElement('style');
      style.type = 'text/css';
      if (style.styleSheet) {
        style.styleSheet.cssText = data;
      } 
      else {
        style.appendChild(document.createTextNode(data));
      }
      head.appendChild(style);
    } 

  $('#stepBtnUnderTaskText1234').click(function(){
      resultCss+= 'input {width: 120% !important;}'
      addStyle(resultCss);
      swal({
         title: "Комментарий к неверным данным",
         text: "В поле ниже укажите, какие данные неверны",
         type:"input",
        showCancelButton: true,
        confirmButtonText:'Отправить',
        cancelButtonText:'Отмена',
        closeOnConfirm: false,
        inputPlaceholder: "Какие данные неверны?"
      },

      function (inputValue) {
        if (inputValue === false) return false;
        if (inputValue === "") {
          swal.showInputError("Обязательно заполните причину!");
          return false;
        }
        swal({
           title:"Сообщение о неверных данных отправлено!", 
           text:"Вы указали: " + inputValue, 
           type:"success"},
          function (){
              var func=function(event){ChangeStep(1234, '', '', false);}; 
              if(func(event)){
                WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions('', '', false, '', '', false, false));
                __doPostBack('','')
              }
           });

        ep999.val(inputValue);

      })
  });
});

Повышение приоритета задачи (вызов метода Valhalla)

Старый МТФ

В портальном блоке по клику на кнопку задаче присваивается повышенный приоритет с помощью вызова метода веб-сервиса Valhalla.

var xhr = new XMLHttpRequest();

// подписка на событие загрузки
xhr.addEventListener('load', transferComplete);
// подписка на событие ошибки
xhr.addEventListener('error', transferFailed);

// api url
var url = appFullPath + '/app/v1.0/api/task/priority/' + event.data.taskId;

// параметры запроса
xhr.open('POST', url);
xhr.setRequestHeader('Content-type', 'application/json');
// отправка запроса
xhr.send('{ "priority": "High" }');

function transferComplete(e) {
  // обновление блока
   event.block.reload();
}

function transferFailed(e) {
  // отображение ошибки в консоли браузера
  console.error(e);
  // обновление блока
   event.block.reload();
}

где вместо appFullPath надо подставить путь к приложению "Первая Форма".

Вывод названия категории на панели инструментов

Старый МТФ

Название категории отображается на панели инструментов карточки задачи, рядом с номером задачи. Текст JS-вставки без jQuery

(window).addEventListener('MTFMainLoadFinished', function() {

 function drawBar ( name ) {
         var bar = document.createElement('span');
         bar.innerHTML = ' (' + name + ')';
         var a = document.getElementsByClassName('mtftb-tasknum');
         a[0].appendChild( bar );
 }

 function getSubcatName () {
         var purl = '/app/v1.0/api/task/short/info/' + taskId + '?v=1.0';
         var xhr = new XMLHttpRequest();
         xhr.open( "GET", purl, false );
         xhr.onreadystatechange = function() {
                 if (xhr.status == 200) { 
                         var responseJSON = JSON.parse(xhr.responseText);
                         purl = '/app/v1.0/api/subcategories/' + responseJSON.data[0].subcatId + '/short?v=1.0';
                         var xhr1 = new XMLHttpRequest();
                         xhr1.open( "GET", purl, false );
                         xhr1.onreadystatechange = function() {
                                 if (xhr1.status == 200) { 
                                         var responseJSON1 = JSON.parse(xhr1.responseText);
                                         drawBar ( responseJSON1.name );
                                 } 
                         }           
                         xhr1.send();                   
                 }           
         }
         xhr.send();   
 }   

 getSubcatName ();

});

Текст JS-вставки с jQuery

$(window).on('MTFMainLoadFinished', function() {

 function drawBar ( name ) {
         var bar = document.createElement('span');
         bar.innerHTML = ' (' + name + ')';
         var a = document.getElementsByClassName('mtftb-tasknum');
         a[0].appendChild( bar );
 }

 function getSubcatName () {
         var purl = '/app/v1.0/api/task/short/info/' + taskId + '?v=1.0';
         $.ajax({
                 method: "GET",
                 url: purl,
         })
         .done( function( hh ) {
                 purl = '/app/v1.0/api/subcategories/' + hh.data[0].subcatId + '/short?v=1.0';
                 $.ajax({
                         method: "GET",
                         url: purl,
                 })
                 .done( function( hh ) {
                         drawBar ( hh.name )
                 });           
         });           
 }

 getSubcatName ();

});

Открытие задачи из виджета в модальном окне

Новый фронт

JS-вставка добавляет ссылку, которая открывает NTF в диалоговом окне, а после закрытия окна перезагружает текущую страницу для обновления данных.

<a
   class="button-add-link"
   onclick="OpenedModal = radopen('/spa/noframe/newtask/XXX?onlyCloseAfterCreate=true'); OpenedModal.__zone_symbol__value.add_close(()=>{
  window.location.reload();
         })"
>
  <span class="button-add">добавить объект</span>
</a>

XXX - ID категории.

Старый МТФ

Данная JS-вставка используется для портала с виджетом SmartHtml с ID=123. В виджете выводится список задач с иконками для открытия задачи в модальном окне: Верстка для иконки использует определенный в блоке CSS-класс projects-btn:

<a href="/MainTaskForm.aspx?TaskID={{taskID}}" target="_blank" class="projects-btn"><i class="fa fa-external-link" title="Открыть в новом окне"></i></a>

Для портальной страницы используется JS-вставка, которая проверяет все элементы данного класса и по клику на элемент открывает ссылку (на карточку задачи) в модальном окне:

(window.firstForma.portal.block(123)).onLoaded(function () {
   document.querySelectorAll('.projects-btn').forEach(el => el.addEventListener('click', function(event) {
       event.preventDefault();
      radopen(el.href)
   }));
});

ℹ️ Портал должен быть открыт не как внешняя ссылка, а в среде "Первой Формы" -- например, можно из интерфейса администратора добавить ссылку на портал в Избранное и открывать портал по этой ссылке. В этом случае будет отображаться верхнее меню "Первой Формы" и будут подгружены необходимые компоненты.

Обновление виджета при закрытии модального окна

Часто задача открывается в модальном окне для того, чтобы изменить отдельные параметры этой задачи. В зависимости от этих параметров может потребоваться обновление исходного списка задач в виджете. Например, в виджете отображается список задач со сроком выполнения "сегодня". По клику на задачу открывается ее карточка, и пользователь меняет срок задачи. После этого задача должна исчезнуть из исходного списка. В этом случае вместо стандартной процедуры radopen можно вызывать собственную процедуру, в которой настроен обработчик кнопки закрытия модального окна (эта кнопка имеет класс rwCloseButton). Обработчик выполняет перезагрузку виджета:

function radwin(link) { 
  radopen(link);
  fr = document.getElementsByTagName('iframe')[0];
  fr.onload = function() {
      document.getElementsByClassName('rwCloseButton')[0].addEventListener("click", function(){
         window.location.reload(); // обновляем весь портал
      })
  } 
}
...
// открытие ссылки в модальном окне
radwin(el.href);

Другие действия

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

<a href="/NewTask.aspx?SubcatID={{subcatID}}" target="_blank" class="projects-btn"><i class="fa fa-external-link" title="Открыть в новом окне"></i></a>

Пример работы с radwindow и iframe

Старый МТФ

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

(window).addEventListener('MTFMainLoadFinished', function() {
  var srok = document.getElementById('lblSrok').textContent.trim();
   document.querySelector('#btnChangeDueDate').addEventListener('click', function() {
      var frame = document.querySelector("iframe")
      function UpdateRadWindow() {
        var RadWindow1 = GetRadWindowManager().GetWindows();
        var fContent = frame.contentDocument || frame.contentWindow.document;
        var reasonElem = fContent.getElementById('txReason');
        var reasonTxt = reasonElem.value;
        var reasonTxtLen = reasonTxt.length;
        if (srok == "(срок не установлен)") {
          reasonElem.value = 'Установка срока';
        };
        RadWindow1[0].get_contentFrame().contentWindow.$("#ctl00_formInner_btnOk").click(function() {
            if(reasonElem.value.length == 0 || reasonElem.value == 'Укажите причину смены срока') {;
                   alert('Пожалуйста, укажите причину смены срока');
                                              }
               });
         }
        frame.addEventListener("load", function() {
          UpdateRadWindow();
       });
   });
});

Пример AJAX-запросов, Promise и SweetAlert

Старый МТФ

В портальном блоке Smart Html слева выводится список незавершеных задач из категории, а справа — список завершенных. По клику на задачу она переводится в статус "Завершено" и переходит из левого списка в правый. После этого страница перезагружается, чтобы отобразить обновленные данные. Если переход невозможен, выдается окно с предупреждением.

Полезные ссылки

Работа с блоком Smart Html описана здесь. Для обращения к данным, отбираемым с помощью смарт-выражений, используется шаблонизатор mustache. Описание шаблонизатора mustache на английском Описание шаблонизатора mustache на русском

Решение

Верстка блока

<div class='block-wrapper'>
  <div class='block-wrapper__tasks'>
      <h3>Задачи <i id="addNewTask" class="add-task fa fa-plus-circle"></i></h3>
      <div>
           {{#smart555}}
                 <div>
                         <span data-state-id='{{stateID}}' data-task-id='{{taskID}}' class='task-item'>{{description}}</span>
                         <i data-task-id='{{taskID}}' class="to-complete fa fa-arrow-right"></i>
                 </div>
          {{/smart555}}
   </div>
  </div>
  <div class='block-wrapper__completed-tasks'>
      <h3>Завершенные задачи</h3>
      <div class="block-wrapper__completed-tasks-list"></div>
  </div>
</div>

JS-вставка

ℹ️ В начале JS-вставки подключаются скрипты polyfill и sweetalert. Их можно подключать непосредственно из HTML-верстки, но считается правилом хорошего тона подключать все скрипты из JS.

//polyfill
var promisePolyfill = document.createElement('script');
promisePolyfill.setAttribute('src','https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.js');
document.head.appendChild(promisePolyfill);

//sweetalert
var sweetAlertScript = document.createElement('script');
sweetAlertScript.setAttribute('src','https://cdn.jsdelivr.net/npm/sweetalert2@8');
document.head.appendChild(sweetAlertScript);

// обновление страницы
function reloadPage() {
 window.location.reload();
}

(window.firstForma.portal.block(1234)).onLoaded(function() {
  // назначаем обработчик для события клика по кнопке создания задачи
   document.getElementById('addNewTask').addEventListener('click', function() {
      var openedWindow = radopen('/NewTask.aspx?&SubCatID=123456&returnjs=window.top.reloadPage();');
   });

  // для каждой задачи назначаем обработчик для события клика по тексту задачи 
  var tasks = document.getElementsByClassName('task-item');
  var completedTasks = [];
  for (var i = 0; i < tasks.length; i++) {
    var stateId = tasks[i].dataset.stateId;
    tasks[i].addEventListener('click', function() {
      var task = event.target;
      var taskId = task.dataset.taskId;
      var url = '/MainTaskForm.aspx?taskId=' + taskId;
      var openedWindow = radopen(url);
     });
    // stateId = 3 соответствует статусу "Завершено", завершенные задачи заносим в отдельный список
    if (+stateId === 3) {
      completedTasks.push({
        taskId: tasks[i].dataset.taskId,
        taskText: tasks[i].innerHTML
       });
     }
   }

  function isTasksArrayIncludedTask(taskArray, taskId) {
    var isTasksArrayIncludedTask = taskArray.find(function (task) {
        return task.taskId === taskId;
     });
    return !!isTasksArrayIncludedTask;
   }

  // готовим Promise
  function httpPostRequest(url, data) {
    var data = data || {};
    return new Promise(function(resolve, reject) {
      var xhr = new XMLHttpRequest();
      xhr.open('POST', url);
      xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');

      xhr.onload = function() {
        if (this.status == 200) {
          resolve(this.response);
         } else {
          reject(this.response);
         }
       };

      xhr.onerror = function() {
        reject(new Error("Network Error"));
       };

      xhr.send(data);
     });
   }
  // функция добавляет завершенные задачи в список в правой половине блока
  // tasks: {taskId: number; taskText: string}[]
  function addCompletedTasks(tasks) {
    var completedTaskWrapper = document.getElementsByClassName('block-wrapper__completed-tasks-list')[0];
    for (var i = 0; i < tasks.length; i++) {
        var completedTask = document.createElement('div');
        completedTask.innerHTML = tasks[i].taskText;
        completedTask.dataset.taskId = tasks[i].taskId;
        completedTask.classList.add('completed-task-item');
        completedTaskWrapper.appendChild(completedTask);
     }
   }
  // функция удаляет завершенные задачи из списка в левой половине блока
  // tasks: {taskId: number; taskText: string}[]
  function removeTasks(tasks) {
    var itemsToRemove = [];
    var taskItems = document.getElementsByClassName('task-item');
    for (var i = 0; i < taskItems.length; i++) {
      var currentTask = taskItems[i];
      if (isTasksArrayIncludedTask(tasks, currentTask.dataset.taskId)) {
          itemsToRemove.push(currentTask);
       }
     }
    for (var i = 0; i < itemsToRemove.length; i++) {
        itemsToRemove[i].parentNode.remove();
     }
   }
  // переносим завершенные задачи из левой части в правую
   addCompletedTasks(completedTasks);
   removeTasks(completedTasks);
  // назначаем обработчик для события клика для каждой кнопки завершения задачи
  var completeButtons = document.getElementsByClassName('to-complete');
  for (var i = 0; i < completeButtons.length; i++) {
      completeButtons[i].addEventListener('click', function(event) {
            var plusBtn = event.target;
            var taskId = plusBtn.dataset.taskId;
            var url = '/app/v1.0/api/task/step/' + taskId;
            var data = JSON.stringify({stepId: 99, comment: "Задача завершена"});
            var completedTask = [{
            taskId: taskId,
            taskText: event.target.parentNode.getElementsByClassName('task-item')[0].innerText
            }];

            httpPostRequest(url, data).then(
            function(response) {
              addCompletedTasks(completedTask);
              removeTasks(completedTask);
            },
            function(error) {
              Swal.fire({
                  type: 'error',
                  title: 'Ошибка',
                  text: JSON.parse(error).message,
                  footer: '<a href="https://1forma.ru/help.html" target="_blank">Помощь</a>'
               });
            }
         );
      });
   }
});

Работа со всплывающим окном radwindow

Radwindow — это всплывающее окно подобного такого вида:

Пример 1

Пример 2

Содержание внутри окна может быть любым. Radwindow используется, например, для открытия задачи поверх текущего окна, а не в отдельной вкладке браузера. Radwindow представляет собой фрейм. Для вызова radwindow достаточно передать в него url. Функция возвращает ссылку на объект — открытое окно.

openedWindow = radopen(url);

У объекта radwindow нет собственного API, но можно настроить некоторые действия для события закрытия этого окна.

Radwindow с другим содержимым

ℹ️ Неактуально для интерфейса SPA, т.к. там окно radwindow имеет другую кнопку закрытия окна

Часто после закрытия radwindow нужно обновить исходное окно. Для этого отслеживается событие нажатия на кнопку закрытия окна . В данном примере создается функция, в которой вызывается radwindow с нужным url и назначается обработчик на событие клика по значку закрытия.

function radwin(link){ 
radopen(link);

var fr = null;
fr = document.getElementsByTagName('iframe')[0];
fr.onload = function() {
   document.getElementsByClassName('rwCloseButton')[0].addEventListener("click", function(){
     ... 
   })
 } 
}

...
radwin(url);

Переход к планировщику (Помощнику по планированию) в карточке создания новой задачи (NTF):

Вид карточки создания новой задачи с кнопкой:

Вид карточки создания новой задачи с кнопкой

Скрипт:

(function(){
 // ДП адресаты Email
 const participantsEpId = 10;
 function addSchedulingAssistantBtn (parentElement) {
 //Делаем кнопку
 // Кнопка для добавления снизу страницы рядом с кнопками "Создать встречу" и "Отмена"
   /*
   const assistantElement = `<button id="planner-button" style="
                 /*
                 background-color: #ffd271;
                 position: absolute;
           right: 5vw;
           background-color: #e4ebf0;
                 "
         class="vh-btn vh-btn--transparent ng-star-inserted"> &#128197; Открыть планировщик </button>`
   */

   //Кнопка для добавления вверху страницы рядом с кнопкой "Повторения"
 const assistantElement = `<div class="system-param-duedate--first ng-star-inserted"><div id="planner-button"
         class="vh-btn vh-btn--icon-left ng-star-inserted"> &#128197; Открыть планировщик </div></div>`

   //Добавление кнопки на страницу
   parentElement.insertAdjacentHTML('beforeend', assistantElement);

 //Вешаем обработчик нажатия на новую кнопку
   document.querySelector("#planner-button").addEventListener('click', function() {
     let qsParts = [];
     let users = [];

   //Пользователи из ДП Участники встречи(10)
     const participantsEp = new ExtParam(participantsEpId).val().users;
     participantsEp.forEach(item => users.push(item.userId));

   //Только уникальные - "onlyUnique"
     function onlyUnique(value, index, self) {
       return self.indexOf(value) === index;
     }

     // Пользователи из ДП списком
     users = users.filter(onlyUnique);
     if (users.length) {
           qsParts.push('users=' + users.join(','))
     }
     // Открываем окно с планировищком
     const queryString = qsParts.length ? '?' + qsParts.join('&') : '';
     window.radopen('/spa/noframe/scheduling-assistant' + queryString);

     // ссылка вида:  /spa/noframe/scheduling-assistant?users=2124,2186

   })
 }
 // Обработчик для NTF
 window.addEventListener('NewTaskLoadFinished', function() {
         //Ищем место куда вставляем кнопку
 //Добавить кнопку наверх рядом с кнопкой "Повторения"
         const parentDivs = document.querySelectorAll('.system-param');

           const parentElement = Array.from(parentDivs).find(node =>
                       node.classList.contains('system-param-duedate')
         );
         addSchedulingAssistantBtn(parentElement);

   // Это, если хотим кнопку снизу страницы рядом с кнопками "Создать встречу" и "Отмена"
   /*const parentDivs = document.querySelectorAll('.create-task-btn');
     parentDivs.forEach(parentDiv => {
         const parentElement = parentDiv.querySelector('.vh-btn-container');
         console.log('parentElement: ',parentElement);
         addSchedulingAssistantBtn(parentElement);
   })*/
 })

})
();

Подставить значение в LookUP из публикации

Новый МТФ

function onLoad (event) {
  let toe = new ExtParam (3036, event.detail.cardGuid);
  let sz = new ExtParam (99, event.detail.cardGuid);
 let dds = new ExtParam (21, event.detail.cardGuid);
  let AOorZRDS = new ExtParam (511, event.detail.cardGuid);
   // Передаем в публикацию значения ДП с формы, получаем ответ и записываем в ДП
  function LoadSZandDDS() {
    if (toe.val()?.taskId && AOorZRDS.val()?.taskId) {
           fetch('/app/v1.2/api/publications/action/SZandDDSfromTypeOfExpenses?TOE=' + toe.val().taskId + '&AOorZRDS=' + AOorZRDS.val().taskId)
               .then(response => response.json())
               .then(
                  function(Load) {
                       sz.val({taskId: Load[0].SZ, taskText: Load[0].SZtask});
                       dds.val({taskId: Load[0].DDS, taskText: Load[0].DDStask});
                   });
                   };
       };
 toe.change(() => LoadSZandDDS());
}
function onDestroy() {
     window.removeEventListener('NewTaskLoadFinished', onLoad);
 }
window.addEventListener('NewTaskDestroyed', onDestroy);
window.addEventListener('NewTaskLoadFinished', onLoad);
}

Примеры CSS-вставок

Скрыть кнопку "Удалить все файлы"

CSS-вставка для удаления кнопки для удаления всех файлов:

i[title="Удалить все файлы"] {
 display: none;
}

Кнопка "Удалить все файлы"

Увеличить текст задачи

CSS-вставка для увеличения текста задачи до 20px:

#txTaskText {font-size: 20px; height:25px; padding-left:4px}

Заблокировать ДП "Выбор пользователя" при создании

CSS-вставка для блокировки ДП "Выбор пользователя" на НТФ:

.disabledparam64 {
   pointer-events: none;
   opacity: 0.4;
}

Изменить фон кнопки

CSS-вставка для смены фона кнопки на красный:

#vh-layout-main-content-area > vh-content-overlay > div > vh-new-task-card > div > div > div > button:nth-child(1) {
 background: red;
}

Скрыть системные поля

CSS-вставка, которая скрывает исполнителя, срок, начало работы, кнопки "Делегировать" и "Установить срок":

#ctl00_formInner_Contenttd > div:nth-child(1) > table > tbody > tr:nth-child(1) > td:nth-child(1) > table > tbody > tr:nth-child(5) {display: none;}
#ctl00_formInner_Contenttd > div:nth-child(1) > table > tbody > tr:nth-child(1) > td:nth-child(1) > table > tbody > tr:nth-child(7) {display: none;}
#ctl00_formInner_Contenttd > div:nth-child(1) > table > tbody > tr:nth-child(1) > td:nth-child(1) > table > tbody > tr:nth-child(8) {display: none;}

#btnDelegateTask,#btnChangeDueDate {display: none;}

Скрыть блок "Используется"

CSS-вставка, которая скрывает блок "Используется":

#ctl00_formInner_Contenttd > div:nth-child(11){display: none}

Скрыть кнопку редактирования текста

CSS-вставка, которая скрывает кнопку "карандаш" для редактирования текста:

#imgTaskTextEditID {display:none}

Установить размер ресурсного планирования

CSS-вставка для установки размера ресурсного планирования:

.mtfTaskResources-inner{max-width: 60%;}

Ограничение ширины ДП

CSS-вставка для ограничения ширины компонентов задачи (ДП БТСФ, Таблица, Выбор нескольких задач из категории (Multilookup), и т.п.):

vh-ext-param-control-wrapper[ep-wrapper-id="ХХХ"] {
 grid-column: span min(1, var(--task-card-params-grid-column)) !important;
}

где: ХХХ — ID ДП. Число 1 в строке grid-column: span min(1, var(--task-card-params-grid-column)) !important; можно заменить на другое и в зависимости от значения и будет меняться занимаемое этим ДП место.