Native JS

Native JS позволяет выполнять код JavaScript внутри скрипта Ninox и обмениваться с ним функциями и переменными. Благодаря этому Native JS может читать переменные Ninox и вызывать функции, объявленные в той же формуле; функции глобального кода вызываются через ninext.callNinoxFunction (см. ниже «Вызов функции из глобального кода»).

Native JS включён по умолчанию; он отключается в Настройках Ninext, раздел «Интеграция JavaScript» (Нативный JavaScript).

image.png

Ограничения

Изменение переменной Ninox в NativeJS не затрагивает саму переменную Ninox.

JavaScript выполняется только локально. Добавление JavaScript в код, выполняемый на сервере, не даёт никакого эффекта. Точно так же не даёт эффекта вызов глобальной функции с JavaScript из кода, выполняемого на сервере. Начиная с версии 3.2.0 вкладка Ошибки сообщает о блоке, помещённом непосредственно в триггер таблицы (после создания, после изменения) или в do as server.

Здесь показано, какие коды выполняются локально, а какие на сервере:

image.png

Синтаксис

Выполнение

Чтобы указать Ninox, что вы переходите на JavaScript, заключите код в #{ ... }#:

Ninox
#{ alert("hello world") }#;

Помните: Ninox — предварительно компилируемый язык, которому нужно заранее знать тип каждой переменной и функции. То же относится и к Native JS: вы должны указать тип, возвращаемый кодом JavaScript. По умолчанию Native JS возвращает текст, но нужный тип можно задать в самом начале кода записью :[type]:

Аннотация пишется вплотную к #{, без пробела: #{:number …, а не #{ :number …. Точно так же две аннотации следуют друг за другом без пробела (:text:callback). С пробелом аннотация не распознаётся: блок получает тип «текст», а аннотация остаётся в коде JavaScript, где вызывает синтаксическую ошибку.

Возвращаемое значение

Ninox
#{:text return "hello world"}#;

Возможные типы: text, number, date, timestamp (дата и время), time, timeinterval, appointment, boolean, html, color, icon, email, phone, location, file, user, json и any. Полный список, с choice, link, style и styled, предлагает Консоль (см. ниже).

Внимание

Дата со временем аннотируется как :timestamp, тогда как параметр функции того же типа объявляется как datetime. Аннотации :datetime не существует: блок получил бы тип «текст». Начиная с версии 3.2.0 вкладка «Ошибки» сообщает о неизвестной аннотации.

Даты, время и длительности передаются в миллисекундах: переменная Ninox такого типа попадает в JavaScript в виде числа, а блок :date или :array(date) должен возвращать миллисекунды (их даёт getTime() даты JavaScript).

Типы массивов

Чтобы вернуть массив значений, укажите тип его элементов с помощью :array(type) (начиная с версии 3.2.0):

Ninox
#{:array(number) return [1, 2, 3] }#;

Допустимы все простые типы: :array(text), :array(date), :array(any)…

До версии 3.1.2 включительно можно вернуть только массив типа any. Такая запись по-прежнему допустима как эквивалент :array(any):

Ninox
#{:any(array) return [1, 2, 3] }#;

Запись :number(array), которую раньше приводила эта страница, не возвращает массив. Начиная с версии 3.2.0 вкладка «Ошибки» сообщает о ней.

Типы записей

Чтобы вернуть записи, укажите nid или rid, а за ним имя таблицы в скобках:

Если вы используете nid, нужно вернуть массив текстовых значений с полным идентификатором записи, то есть идентификатором таблицы + номером записи:

Ninox
#{:nid(customer) return ['A1','A2','A3'] }#

Если вы используете rid, следует вернуть номер записи:

Ninox
#{:rid(customer) return [1,2,3] }#

Для составных имён таблиц не добавляйте ', как в Ninox, а просто пишите полное имя таблицы:

Ninox
#{:rid(my long name table) return [1,2,3] }#

При таком синтаксисе Native JS возвращает переменные, которые ведут себя так же, как в коде Ninox. Значит, можно писать так, будто это select:

Ninox
(#{:rid(customer) return [1,2,3] }# order by 'Last Name').'First Name';

image.png

Помощь при вводе в Консоли

Начиная с версии 3.2.0 Консоль помогает писать блок:

  • в аннотации: когда вы вводите : сразу после #{, она предлагает возвращаемые типы, array(type), nid(Table) и rid(Table); когда вводите ( после nid или rid — список таблиц, а после array — список типов; второе : предлагает модификаторы callback и async;
  • в JavaScript: автодополнение предлагает переменные и функции Ninox, которые получает блок, в их JavaScript-виде, а после точки — члены объекта; для записи — имена её полей (см. «Автодополнение JavaScript в нативных блоках» на странице Консоль).

Эта помощь есть только в Консоли.

Что проверяет Ninext

Начиная с версии 3.2.0 (бета). Вкладка Ошибки проверяет блоки #{ … }# во всей базе, а Консоль — в коде, который вы в ней пишете.

Сообщается о следующем:

  • синтаксическая ошибка JavaScript;
  • неизвестная аннотация (например, :datetime) или таблица, не найденная в :nid(…) или :rid(…): блок получил бы тип «текст»;
  • типизированный блок без единого return или с путями, на которых он не достигается: он вернул бы пустое значение;
  • блок :callback или :async, который никогда не вызывает callback: формула так и не получила бы значения;
  • блок, помещённый непосредственно в триггер таблицы (после создания, после изменения) или в do as server, где он не выполняется.

Изменение записи

Переменные типа «запись» передаются в формате JSON, где каждому полю записи соответствует пара ключ-значение.

Ninox
var r := first(select Customer);
#{:text return r['First Name'] }#;

Здесь содержимое переменной r в JavaScript выглядит так:

Ninox
{
 "Created on": 1648910817206,
 "Id": "A1",
 "Last modified": 1714817525840,
 "Modified by": "7zBqhAKXxfQAPmH7Y",
 "First Name": "Sofia",
 "Last Name": "Young",
 "Company": "Kollwitz GmbH",
 "Postal Code": "12345",
 "City": "New Jersey",
 "Address": "11,South Commerce Street",
 "Email": "sofi@kollwitz-gmbh.com",
 "Customer No": "K0001",
 "Title": "2",
 "Invoices": [
  1,
  16,
  17
 ],
 "Photo": "shutterstock_307218608.jpg",
 "Category": "1",
 "Position": "Marketing head",
 "Checked": true,
 "ID": "A1",
 "id": "A1"
}

Кроме того, у объекта есть ключ _node: запись в том виде, в каком её хранит Ninox, с полями, обозначенными внутренними идентификаторами.

Важно

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

Изменение записи в JavaScript не обновляет её в Ninox.

Асинхронное выполнение

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

В первом случае значение «I don’t know» возвращается немедленно, и функция alert показывает результат раньше, чем вопрос будет задан пользователю.

Ninox
var r:= #{:text
   setTimeout( () => {
      if (confirm("Are you ok ?"))
        return "of course";
      else
        return "Never !";
   }, 2000);
  return "I don't know";
}#;
alert( "Your answer is : "+r );

image.png

Если перейти в режим callback (или равнозначный ему async), функция alert в Ninox выполнится только после того, как код JavaScript вызовет функцию обратного вызова, то есть после того, как вопрос будет задан пользователю. Тип всегда идёт первым: :text:callback, но никогда не :callback отдельно — это было бы принято за тип.

Ninox
var r:= #{:text:callback
   setTimeout( () => {
      if (confirm("Are you ok ?"))
         callback( "of course" );
      else
        callback( "Never !" );
   }, 2000);
   return "I don't know";
}#;
alert( "Your answer is : "+r );

Запись экрана 2025-03-03 в 11.46.26.gif

Вызов функции

Из JavaScript можно вызывать функции Ninox: блок получает функции, объявленные выше в той же формуле. Функций глобального кода он не видит (см. ниже «Вызов функции из глобального кода»).

Ninox
function display( message : text ) do
    alert( message );
end;
#{ display('hello world') }#;

image.png

Чтобы получить возвращаемое значение функции, снова понадобится режим callback (см. выше: Асинхронное выполнение):

Ninox
function add( a : number, b : number ) do
    a + b;
end;

#{:number:callback
   add( 1, 2, (returnValue) => {
     callback( returnValue )
   })
}#;

image.png

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

Ninox
function add( a : number, b : number ) do
    a + b;
end;
#{:number:callback
   add( 1, 2, callback )
}#;

image.png

Вызов функции из глобального кода

Функция глобального кода не входит в область видимости блока: вызов по имени приводит к ошибке. Вызывайте её через ninext.callNinoxFunction с null во втором аргументе. Если в глобальном коде есть такая функция:

Ninox
function hello(message : text) do
    alert(message)
end;

формула вызывает её так:

Ninox
#{ ninext.callNinoxFunction('hello', null, 'hello world') }#;

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

Ninox
#{:number:callback
   ninext.callNinoxFunction('add', null, 1, 2, (error, result) => callback(result))
}#;

Всё о callNinoxFunction: Вспомогательные функции HTML.

Работа с записями и функциями

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

Ninox
function setCustomerChecked( id : text, checked : boolean ) do
   var n := number(replacex(id, "\D",""));
   var rec := record( Customer, n );
   rec.Checked := checked;
end;
var c := select Customer;
var isChecked := not first(c).Checked;
#{
   c.forEach( v => { setCustomerChecked( v, isChecked ) } );
}#;
c.checked;

Ошибки выполнения

Если в коде происходит ошибка выполнения, возвращается текст сообщения об ошибке:

image.png

В режиме callback это же сообщение становится результатом блока. Пример выше — синтаксическая ошибка: начиная с версии 3.2.0 вкладка «Ошибки» сообщает о ней ещё до выполнения (см. «Что проверяет Ninext»).

При запуске: onNinextLoaded

При запуске Ninox триггер «After Open» выполняется до того, как загрузится Ninext: Native JS там ещё не работает, и помещённый туда блок JavaScript молча завершается ошибкой.

Если определить функцию onNinextLoaded(event) в триггере «After Open», Ninext вызовет её, когда действительно загрузится, — у каждого пользователя, при каждом открытии базы. event.version содержит версию Ninext:

Ninox
function onNinextLoaded(event : any) do
   alert("Ninext " + event.version + " is ready");
   "… your JavaScript / NativeJS code here";
end;

Так у вас появляется надёжная точка входа, чтобы выполнять JavaScript при запуске или адаптировать базу к версии Ninext.

Полезно знать

Ninox уже выполнил триггер «After Open» при открытии базы; чтобы вызвать onNinextLoaded, Ninext заново вычисляет его код. Начиная с версии 3.2.0, он заново вычисляет только объявления: функции, а также переменные (var, let), если какая-либо функция читает одну из них. Остальные инструкции, таким образом, выполняются только один раз. Второй раз вычисляется лишь начальное значение переменной, которую читает функция: не делайте его зависимым от изменения данных, например от создания записи. В редком случае, когда эти объявления не компилируются сами по себе, Ninext заново вычисляет весь триггер, как в предыдущих версиях.

До версии 3.1.2 включительно Ninext заново вычисляет весь код триггера: инструкции, находящиеся вне каких-либо функций, выполняются второй раз. Учитывайте это для тех, что должны выполняться только один раз: создание записи, сообщение, открытие таблицы.