Вопрос

Cделан запрос в базу данных из клиентского модуля для получения системной настройки: 

this.Terrasoft.SysSettings.querySysSettingsItem("PsMaximumProgramsCount", function(value) {
	countSettings = value;
}, this);

Получены данные из БД отфильтровав их и сравнив с полученным значением из системной настройки ранее, и если значение выпадающего списка periodicity  равно "Ежедневно" и количество записей в базе больше чем системной настройке - срабатывает валидация на поле и всплывающее уведомление при попытке сохранения:

message - сообщение для поля валидации;

periodicity  - выпадающий список (словарь) в котором есть значение "Ежедневно";

countSettings  - ранее полученное значение системной настройки;

concertProgramsCount - количество записей в таблице;

var periodicity = "";
if (this.get("PsPeriodicity")) {
	periodicity = this.get("PsPeriodicity").displayValue;
}
var esq = Ext.create("Terrasoft.EntitySchemaQuery", {
	rootSchemaName: "PsConcertPrograms"
});
esq.addColumn("PsPeriodicity.Name", "PsPeriodicityName");
var esq1Filter = esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL,
	"PsPeriodicity.Name", "Ежедневно");
esq.filters.add("esq1Filter", esq1Filter);
esq.getEntityCollection(function(result) {
	var message = "";
	if (result.success) {
		var concertProgramsCount = result.collection.collection.length;
		if (periodicity === "Ежедневно" && concertProgramsCount <= countSettings ) {
			message = this.get("Resources.Strings.PsFewFreeConcertHalls").replace("NNN", countSettings);
		}
	}
	return message;
}, this);

Вывод валидации для поля и при сохранении страницы:

concertHallsValidator: function(message) {
	var invalidMessage = message;
	return {
		fullInvalidMessage: invalidMessage,
		invalidMessage: invalidMessage
	};
}

Проблема в том что по отдельности все работает, но когда вместе - вступает в дело асинхронность и все идет не по очереди, а если использую через callback тогда застреваю на замыкании.

Спасибо всем кто окажет помощь в решении проблемы.

У меня такой же вопрос

7 комментариев
Лучший ответ

Prime Source,

нет. стоп. я думал вы прогоняете esq в onEntityInitialized, а потом используете this.$PeriodValidationMessage для проверки. тогда такие варианты:

1) на изменение поля PsPeriodicity запускаете concertHallsValidator. Изменяете this.addColumnValidator("CreatedOn", this.MessageValidator);
2) Используете asyncValidate (вроде так называется метод)

И уберите return. Ну не используется он в асинхронных функциях)

немного не понимаю смысла return из async функции, поэтому вариант такой:

в attributes добавляете 

"PeriodValidationMessage": {
	dataValueType: 1,
	value: ""
}

esq:

var periodicity = "";
if (this.get("PsPeriodicity")) {
	periodicity = this.get("PsPeriodicity").displayValue;
}
var esq = Ext.create("Terrasoft.EntitySchemaQuery", { rootSchemaName: "PsConcertPrograms" });
esq.addColumn("PsPeriodicity.Name", "PsPeriodicityName");
esq.filters.addItem(esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "PsPeriodicity.Name", "Ежедневно"));
esq.getEntityCollection(function(result) {
	if (result.success) {
		var concertProgramsCount = result.collection.collection.length;
		//считывание сист. настройки
		Terrasoft.SysSettings.querySysSettingsItem("PsMaximumProgramsCount", function(countSettings) {
			//сравнение
			if (periodicity === "Ежедневно" && concertProgramsCount <= countSettings ) {
				this.$PeriodValidationMessage = this.get("Resources.Strings.PsFewFreeConcertHalls").replace("NNN", countSettings);
			}
 
		}, this);
	}
}, this);

валидация

concertHallsValidator: function(message) {
	var invalidMessage = this.$PeriodValidationMessage;
	return {
		fullInvalidMessage: invalidMessage,
		invalidMessage: invalidMessage
	};
}

 

Добрый день,

Каждый борется с асинхронностью своими средствами :)

Как вариант, можно вложить одно в другое: выполнять запросы по очереди, по мере получения ответа из БД. Например перенести вашу логику с esq запросом в колбэк системной настройки.

Тёскин Дмитрий Валерьевич,

 

Пробовал, тогда return возвращает поздно ответ

concertHallsValidator:  function(){
	var periodicity = "";
	if (this.get("PsPeriodicity")) {
		periodicity = this.get("PsPeriodicity").displayValue;
	}
	this.Terrasoft.SysSettings.querySysSettingsItem("PsMaximumProgramsCount", function(countSettings) {
		var esq = Ext.create("Terrasoft.EntitySchemaQuery", {
			rootSchemaName: "PsConcertPrograms"
		});
		esq.addColumn("PsPeriodicity.Name", "PsPeriodicityName");
		var esq1Filter = esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL,
			"PsPeriodicity.Name", "Ежедневно");
		esq.filters.add("esq1Filter", esq1Filter);
		esq.getEntityCollection(function(result) {
			var invalidMessage = "";
			if (result.success) {
				var concertProgramsCount = result.collection.collection.length;
				if (periodicity === "Ежедневно" && concertProgramsCount > countSettings ) {
					invalidMessage = this.get("Resources.Strings.PsFewFreeConcertHalls").replace("NNN", countSettings);
				}
			}
			return invalidMessage;
		}, this);
	}, this);
},
setValidationConfig: function() {
	this.callParent(arguments);
	this.addColumnValidator("PsPeriodicity", this.concertHallsValidator);
}

 

Варфоломеев Данила,

Асинхронность не дает

methods: {
	concertHallsValidator:  function(callback){
		var periodicity = "";
		if (this.get("PsPeriodicity")) {
			periodicity = this.get("PsPeriodicity").displayValue;
		}
		var esq = Ext.create("Terrasoft.EntitySchemaQuery", { rootSchemaName: "PsConcertPrograms" });
		esq.addColumn("PsPeriodicity.Name", "PsPeriodicityName");
		esq.filters.addItem(esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, "PsPeriodicity.Name", "Ежедневно"));
		esq.getEntityCollection(function(result) {
			if (result.success) {
				var concertProgramsCount = result.collection.collection.length;
				//считывание сист. настройки
				Terrasoft.SysSettings.querySysSettingsItem("PsMaximumProgramsCount", function(countSettings) {
					//сравнение
					if (periodicity === "Ежедневно" && concertProgramsCount <= countSettings ) {
						this.$PeriodValidationMessage = this.get("Resources.Strings.PsFewFreeConcertHalls").replace("NNN", countSettings);
					}
					return callback.call(this);
				}, this);
			}
		}, this);
	},
	MessageValidator: function() {
		var invalidMessage = this.$PeriodValidationMessage;
		return {
			fullInvalidMessage: invalidMessage,
			invalidMessage: invalidMessage
		};
	},
	setValidationConfig: function() {
		// Вызывает инициализацию валидаторов родительской модели представления.
		this.callParent(arguments);
		this.addColumnValidator("CreatedOn", this.concertHallsValidator(this.MessageValidator));
	}
},
			}

 

Prime Source,

нет. стоп. я думал вы прогоняете esq в onEntityInitialized, а потом используете this.$PeriodValidationMessage для проверки. тогда такие варианты:

1) на изменение поля PsPeriodicity запускаете concertHallsValidator. Изменяете this.addColumnValidator("CreatedOn", this.MessageValidator);
2) Используете asyncValidate (вроде так называется метод)

И уберите return. Ну не используется он в асинхронных функциях)

Варфоломеев Данила,

 

Спасибо большее, помогло отлично asyncValidate. Раньше не знал что этот метод. Вод такой код заработал отлично:

asyncValidate: function(callback, scope) {
	this.callParent([function(response) {
		if (!this.validateResponse(response)) {
			return;
		}
		Terrasoft.chain(
			function(next) {
				this.validateConcertHalls(function(response) {
					if (this.validateResponse(response)) {
						next();
					}
				}, this);
			},
			function(next) {
				callback.call(scope, response);
				next();
			}, this);
	}, this]);
},
validateConcertHalls: function(callback, scope) {
	Terrasoft.SysSettings.querySysSettingsItem("PsMaximumProgramsCount", function(countSettings) {
		var periodicity = "";
		var result = {success: true};
		if (this.get("PsPeriodicity")) {
			periodicity = this.get("PsPeriodicity").displayValue;
		}
		var esq = Ext.create("Terrasoft.EntitySchemaQuery", { rootSchemaName: "PsConcertPrograms" });
		esq.addColumn("PsPeriodicity.Name", "PsPeriodicityName");
		esq.filters.addItem(esq.createColumnFilterWithParameter(Terrasoft.ComparisonType.EQUAL, 
			"PsPeriodicity.Name", "Ежедневно"));
		esq.getEntityCollection(function(response) {
			if (response.success && periodicity === "Ежедневно" && (!this.isAddMode() && 
				response.collection.getCount() > countSettings) || (this.isAddMode() && 
				response.collection.getCount() >= countSettings)) {
					result.message = this.get("Resources.Strings.PsFewFreeConcertHalls").replace("NNN", countSettings);
					result.success = false;
			}
			callback.call(scope || this, result);
		}, this);
	}, this);
}

 

Prime Source,

Если ваша функция должна возвращать какие-то значение, то из коллбэка это сделать не получится. В этом случае у вас уже должны быть где-то готовые значения для того, что бы функция валидации могла синхронно выполниться и вернуть значения. Например значение системной настройки можно получить при инициализации страницы и сохранить в атрибуте страницы, т.к. системная настройка скорее всего не будет часто меняться. Значение из esq запроса можно так же получить раньше и сохранить в атрибут, а потом уже при валидации оперировать существующими значениями. Ну или сделать так, как посоветовал Варфоломеев Данила.

Войдите или зарегистрируйтесь, чтобы комментировать
Публикация

В статье Асинхронные запросы в bpm’online 5.X был приведен пример отправки Get/Post запросов со страницы с использование языка JavaScript.
В этой статье хотелось бы рассказать про подводные камни, которые встретились при реализации и как их можно обойти.
Предположим, имеется некий абонент интернет провайдера, у которого есть такие поля, как баланс, MAC-адрес, IP-адрес, состояние, список последних сессий абонента, получение доступных для смены тарифов, получение текущего тарифа абонента. При открытии карточки необходимо получать обновленную информацию от WCF сервиса, отображать её на странице и сохранять в базе данных.
При наличии сложной логики на странице, а именно:

  • применение нескольких асинхронных запросов;
  • обновление значений в контроллах на странице;
  • обновление значений столбцов в DataSource.ActiveRow;
  • обновление записей в базе с помощью класса Update (для того, чтобы при получении обновленной информации от сервиса, например, баланса, сразу записывать его значение в базу, и при последующем открытии карточки, даже если пользователь закрыл страницу не сохранив запись нажав "Отмена", пользователь увидит последние обновленные сведения, полученные от биллинговой системы, до того момента, пока от сервиса не придет новая, обновленная информация о балансе, в противном случае, при отсутствии доступа к данному сервису (пропало интернет соединение), будет отображаться последняя загруженная информация о балансе).

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

Данная ошибка возникает при переходе на деталь "Активности", и другие детали, в которые загружается информация, связанная с данной записью. Причем ошибка возникает только во время выполнения асинхронных запросов к сервису, если быть точнее, при получении информации от сервиса, выполняется серверная логика, во время которой и возникает ошибка.
Чтобы избежать данной ошибки, необходимо запретить пользователю переходить на детали, которые вызывают ошибку. Сделать это можно следующим образом.
В БП страницы на событие PageLoadComplete необходимо добавить элемент "Задание сценарий", который добавляет на страницу следующий JavaScript код (данный скрипт должен выполняться перед добавлением скрипта создания самого запроса, т.е. объекта XMLHttpRequest, как было описано в статье):

// объявление глобальных переменных для объектов-запросов
requestUpdateBalance = new XMLHttpRequest();
requestUpdateMacAndSerialNumber = new XMLHttpRequest();
requestUpdateAbonentState = new XMLHttpRequest();
requestGetSessionDetalisation = new XMLHttpRequest();
requestGetAvaliableTariffs = new XMLHttpRequest();
requestGetCurrentTariff = new XMLHttpRequest();

// Функция, позволяющая скрыть/отобразить все детали, следующие за деталью
// "Активности", включая саму эту деталь
function changeVisibleOfTabs(isVisible) {
  for (var i = 0; i PageContainer_DataTabPanel.items.items.length; i++) {
    if (PageContainer_DataTabPanel.items.items[i].caption == 'Активности') {
      for (var j = i; j PageContainer_DataTabPanel.items.items.length; j++) {
        PageContainer_DataTabPanel.items.items[j].tabHeader.setVisible(isVisible);
      }
      break;
    }
  }
}

// скрываем детали перед выполнением запросов
changeVisibleOfTabs(false);

// создаем таймер, который каждые 500мс проверяет, завершились ли все запросы,
// после того, как все запросы завершены, создаем таймер на 2000мс,
// чтобы отработала вся логика на странице, после чего отображаем скрытые детали
var timerCheckIsDone = setInterval(function() {
  if ((requestUpdateBalance != undefined && requestUpdateBalance != null && requestUpdateBalance.readyState == 4) &&
    (requestUpdateMacAndSerialNumber != undefined && requestUpdateMacAndSerialNumber != null && requestUpdateMacAndSerialNumber.readyState == 4) &&
    (requestUpdateAbonentState != undefined && requestUpdateAbonentState != null && requestUpdateAbonentState.readyState == 4) &&
    (requestGetSessionDetalisation != undefined && requestGetSessionDetalisation != null && requestGetSessionDetalisation.readyState == 4) &&
    (requestGetAvaliableTariffs != undefined && requestGetAvaliableTariffs != null && requestGetAvaliableTariffs.readyState == 4) &&
    (requestGetCurrentTariff != undefined && requestGetCurrentTariff != null && requestGetCurrentTariff.readyState == 4)) {
    clearInterval(timerCheckIsDone);
  var timerChangeVisibleOfTabs = setTimeout(function() {
      changeVisibleOfTabs(true); // отображаем детали
    }, 2000);
  }
}, 500);

Важно! После выполнения данного скрипта, необходимо создать объекты запросов, как было показано в статье. Причем объекты запросов должны ссылаться на глобальные переменные, которые мы объявляли в самом начале, т.е. необходимо писать вместо
var request = new XMLHttpRequest();

вот так:
requestUpdateBalance = new XMLHttpRequest();

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

Поделиться

0 комментариев
Войдите или зарегистрируйтесь, чтобы комментировать
Публикация

Есть ситуации, когда нужно, чтобы при открытии карточки редактирования, в поля карточки подгружалась информация, которую нужно выводить в актуальном (обновленном) состоянии. К примеру, когда клиент звонит в тех поддержку, сотрудник техподдержки открывает карточку клиента в bpm'online и должен видеть текущий баланс данного клиента.
Предположим, есть WCF сервис, работающий на некотором хостинге, созданный для интеграции с биллинговой системой, с методом получения баланса абонента GetBalance, ответ от которого приходит в виде строки в формате JSON, который мы можем обработать и вывести в нужном формате на страницу редактирования.
Если выполнять запрос из бизнес процесса карточки редактирования (на PageLoadComplete, к примеру), то при выполнении запроса возникает зависание карточки редактирования, и она не отвиснет пока либо не придет ответ, либо пока запрос не будет завершен по таймауту (если сервер долго не отвечает).
Чтобы выполнить запрос асинхронно, нужно добавить на страницу редактирования JavaScript код, который будет обращаться к сервису, получать ответ от него и выводить результат в контролы страницы. Чтобы отправлять запрос сразу после загрузки страницы, нужно добавить элемент бизнес процесса «Задание-сценарий» на событие «PageLoadComplete», либо если нужно отправлять запрос по нажатию на кнопку, то на событие нажатия на неё.

StringBuilder sb = new StringBuilder();
sb.Append("var request = new XMLHttpRequest();");
sb.Append("var url = 'http://someserveradress/SomeWCFService.svc/GetBalance';");
sb.Append(
"request.open('GET',url,true);" +
"request.onreadystatechange = function() " +
"{  if (request.readyState == 4 && request.status == 200) {" +
Page.BalanceEdit.ClientID + ".setValue(request.responseText); } }; ");
sb.Append("request.send();");
Page.AddScript(sb.ToString());

При выполнении данного кода Java Script кода на странице, мы получим ошибку в консоли браузера:
XMLHttpRequest cannot load http://someserveradress/SomeWCFService.svc/GetBalance. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
Ошибка говорит о том, что запрещено обращаться к другому домену (попытка выполнить кросс-доменный запрос). Данное ограничение введено из соображений безопасности, но его можно обойти используя технологию CORS.
Подробную информацию об этой технологии можно посмотреть тут:
1) https://ru.wikipedia.org/wiki/Cross-origin_resource_sharing
2) http://enable-cors.org/
3) http://habrahabr.ru/post/120917/
Для того, чтобы разрешить кросс-доменные запросу, нужно включить поддержку на сервере, к которому приходит запрос, в моём случае это WCF служба. Для включения поддержки CORS у WCF сервиса, нужно выполнить действия, описанные тут: http://enable-cors.org/server_wcf.html.
Информацию по включению поддержки CORS для других платформ/серверов можно найти тут: http://enable-cors.org/server.html.
После этого, запрос должен выполняться асинхронно, не вызывая зависание карточки, благодаря чему можно просматривать/редактировать карточку в момент выполнения запроса.

Поделиться

0 комментариев
Войдите или зарегистрируйтесь, чтобы комментировать