Рассмотрим поэтапное создание модуля системы mapSurfer GS.
1. Создание проекта
Необходимо создать новый проект при помощи Play Framework 2.3.3. Для получения подробной информации о работе с фреймворком, перейдите на официальный сайт Play Framework .
Для написания модуля необходимо использовать JDK версии 1.7 (с другими версиями могут возникнуть ошибки несовместимости).
Чтобы создать новый проект, перейдите по указанной ссылке. Для редактирования проекта лучше воспользоваться средой разработки IntelliJ IDEA. Чтобы приступить к работе с этой средой разработки, выполните команду play idea, находясь при этом в папке с проектом. Подробнее о разработке проекта в различных IDE можно узнать на сайте Play Framework.
2. Подключение базы данных
Для подключения к базе данных (далее БД) добавьте ниже представленную информацию в файл application.conf:
db.default.driver=org.postgresql.Driver | Драйвер для использования БД postgreSQL |
db.default.url=»jdbc:postgresql://’IP адрес БД’:’порт БД’/my_DB» | Адрес БД |
db.default.user=user_name | Имя пользователя для подключения к БД |
db.default.password=user_password | Пароль пользователя для подключения к БД |
3. Пути
Внутри файла geoportal.modules.module_name.routes пропишите соответствия для HTTP запросов в следующем формате:
GET POST |
/myRoute | controllers.geoportal.modules.module_name.MyController.myFunction |
Тип запроса | Путь относительно корня модуля, по которому будут обрабатываться запросы. Итоговый путь относительно всего сайта будет выглядеть следующим образом: /modules/myModule/myRoute | Функция, обрабатывающая запросы по данному пути |
4. Создание серверной части модуля
Все классы-модели необходимо поместить в package «models.geoportal.modules.’название модуля’». Это необходимо для предотвращения конфликтов с классами основного проекта и других модулей.
Для того чтобы связать класс с таблицей, в БД перед определением класса необходимо добавить следующие аннотации:
@Entity @Table(schema = «shema», name = «table_name») |
После этого каждое поле класса будет сопоставлено по названию со столбцом в таблице «table_name». Для полей так же определены свои аннотации. Например:
@Id | Ставится перед полем primary key |
@OneToMany, @OneToOne и др. | Задают связи между разными таблицами |
Подробнее об аннотациях и их видах можно узнать на официальном сайте Ebean ORM.
Ebean позволяет удобно выбирать из БД необходимую информацию посредством finder. Для этого необходимо создать в классе статическую переменную:
public static Finder<Long,MyClass> find = new Finder(Long.class, MyClass.class); |
После этого пользователю становится доступным удобный поиск необходимых объектов класса из БД.
Пример:
List myClass = MyClass.find.where().like(«name», name).findList(); |
Контроллеры
Контроллеры обеспечивают оперирования моделями и ответами на HTTP запросы. Все контроллеры должны находиться в package «controllers.geoportal.modules.’название модуля’».
Пример отдачи пользователю Json объекта:
/****/ ObjectNode result = Json.newObject(); /****/ result.put(«key», Json.toJson(value)); return crossdomain(ok(result). as(«Content-Type: application/json; charset=UTF-8»)); |
5. Создание front-end части модуля
Вся front-end часть модуля должна находиться в отдельной папке с тем же названием, что и у модуля. Папка находится в директории public/modules. В этой директории находятся следующие папки:
- javascript – в этой папке находятся все файлы с java-script
- images – в этой папке находятся все картинки
- stylesheets – в этой папке находятся все стили
В этой же директории находится файл MyModule.json, в котором необходимо записать все подключаемые js и css файлы:
[{
"path": "stylesheets/MyStyles.css",
"type": "css"
},
{
"path": "javascripts/controllers/MyController.js",
"type": "script"
}]
Определение
Для построения структуры модуля необходимо определить этот модуль в системе:
GP.Module.MyModule = {}; |
Если пользователю необходимо воспользоваться контроллерами, виджетами, сторами и т.д., то они так же должны быть переопределены в системе:
GP.Module.MyModule.Controller = {}; GP.Module.MyModule.Widget = {}; GP.Module.MyModule.Store = {}; |
Наследование
В нашей системе существует наследование классов JavaScript, которое осуществляется по следующей схеме. Предположим, нам необходимо создать класс MyController, который должен наследоваться от класса GP.Controller. Таким образом, получим следующее:
GP.Module.MyModule.Controller.MyController = GP.Controller.extend({ /* тело класса */ }); |
M.Util.bind
Для вызова функции в определенном контексте необходимо выполнить следующие действия:
M.Util.bind(
this._saveToStore,
this)
События
В ядре JavaScript реализована система событий: для любого экземпляра класса, наследуемого от GP.Widget, GP.Controller, GP.Store, можно создать событие, которое возможно «поймать» в любом другом месте.
Пример создания события внутри функции виджета:
this.fire(
"event",
{variable:value}
);
// событие
// передаваемые переменные
Пример отслеживания события:
GP.widgets.myWidget.on(
"event",
function,
context
);
// событие
// функция, которая будет выполняться при срабатывании события
// контекст, в котором будет выполняться функция, например this
Контроллеры
Выполнение модуля должно начинаться с создания экземпляра класса, наследуемого от GP.Controller. Например, GP.Module.MyModule.Controller.MyController.
Процесс написания контроллера:
|
Создание контроллера:
GP.controllers.MyController = new GP.Module.Controller.MyController(); |
В качестве конструктора контроллера используется функция initialize, в которой необходимо вызвать конструктор родителя. Таким образом, контроллер должен выглядеть следующим образом:
|
Хранилище данных Store, Model
Для хранения данных удобнее всего использовать Store. В первую очередь необходимо создать модель, в которой будут описаны все составляющие элементов стора.
Пример модели Store с различными типами данных:
GP.Module.MyModule.Model.MyModel = GP.Model.extend({
fields: [
{
name: "id",
type: "integer",
dvalue: 0,
notNull: true,
rusName: "Id"
},
{name: "title", type: "string", dvalue: "", notNull:true, rusName: "заголовок"},
{name: "subTitle", type: "string", dvalue: "", notNull:true, rusName: "подзаголовок"},
{name: "geom", type: "object", dvalue: "", notNull:true, rusName: "геометрия"},
{name: "districtFields", type: "object", dvalue: [], notNull:true, rusName: "fields"},
{name: "bbox", type: "object",dvalue: "", notNull:true,rusName:"bBox"}
]});
// описание полей объектов, которые будут храниться в Store
// имя поля
// тип значений поля
// значение по умолчанию
// может ли поле быть undefined, если false, то да
// русское название
Затем необходимо описать Store:
|
Среди событий Store можно отметить событие ready, которое срабатывает, когда Store готов к использованию.
GP.stores.module.myModule.myStore.on(
"ready",
function () {
this.hideMap(this._curtainDragging.offset().left);
},
this
);
// название события
// функция, выполняемая при срабатывании события
// контекст выполнения функции
Виджеты
В виджетах, как правило, происходит непосредственно отрисовка и управление содержимого в HTML.
Конструктор GP.Widget принимает два элемента: первый записывается в options, и к нему можно обращаться во время написания класса. Второй элемент может являться объектом jQuery или текстовой строчкой(String), хранящей в себе id HTML элемента. Он необходим для того, чтобы записать jQuery объект, указывающий на HTML элемент, во внутреннюю переменную _mainElement.
При создании виджета, наследуемого от GP.Widget, выполняется функция _createWidget, которая должна быть обязательно определена. В виджетах, которые наследуются от других классов, например, GP.Widget.LeftPanel (оперирующие левой панелью) функция-конструктор может быть другой.
Написание виджета:
|
Создание виджета:
GP.widgets.myWidget= new GP.Module.Widget.MyWidget(); |
Событие создания виджета заключается в том, что выполняемая функция принимает на вход переменную, содержащую создаваемый виджет.
GP.on(«widgetCreate», myFunction, context); |
Событие удаления виджета заключается в том, что выполняемая функция принимает на вход переменную, содержащую удаляемый виджет.
GP.on(«widgetDestroy», myFunction, context); |
Авторизация
Событие выполнения авторизации можно отследить следующим образом:
|
Диалоговое окно (GP.Widget.DialogBox)
Пример работы с DialogBox:
if(this._myDialogBox == undefined){
this._myDialogBox = new GP.Widget.DialogBox(
{
dialogBoxId:"myDialogBox",
width:110,
height:200px,
top:87,
left:106
},
"#wrap"
);
this._myDialogBox.on(
"closeDialog",
this._disableMyDialogBox,
this
);
this._myDialogBox.setTitle("заголовок");
this._myDialogBox.setContainer
('<div>внутренность DialogBox</div>');
this._myDialogBox.show();
this._myDialogBox.left(0);
this._myDialogBox.top(0);
this._myDialogBox.clear();
this._myDialogBox.close();
}
// предупреждения открытия одного окна несколько раз
// создание DialogBox
// id DOM элемента диалогового окна
// ширина окна
// высота окна
// расположение сверху
// расположение слева
// элемент, в который помещается диалоговое окно
// событие, срабатывающее при закрытии окна
// функция, которая будет выполняться
// контекст, в котором будет выполняться функция
// установить заголовок окна
// добавить содержимое в диалоговое окно
// показать диалоговое окно
// установить расположение окна слева
// установить расположение окна сверху
// удалить содержимое окна
// закрыть окно (удалить содержимое и сделать невидимым)
Добавление кнопок в верхнюю панель
Виджет, управляющий кнопкой в верхней панели, должен наследоваться от GP.Widget.DirectButton.
|
Создание кнопки
После создания экземпляра класса GP.Widget.MyDirectButtonWidget будет выполняться функция _createWidget, в которой необходимо выполнить вызов функции _createWidget родителя:
GP.Widget.DirectButton.prototype._createWidget.call(this); |
Далее необходимо создать функции, отвечающие за активацию и деактивацию кнопки, в которых нужно обязательно вызвать функцию родителя:
|
|
Также обязательно необходимо описать следующие опции:
mainOptions:{
buttonId: "",
title: "",
image: "",
bindClick: true
}
// id кнопки
// заголовок кнопки
// картинка кнопки
// если false, то функция _activateControl не будет
// автоматически выполняться при клике на кнопку
Пример кнопки:
GP.Module.Widget.myDirectButtonWidget = GP.Widget.DirectButton.extend{
mainOptions:{
buttonId: "",
title: "",
image: "",
bindClick: true
},
_createWidget: function(){
GP.Widget.DirectButton.prototype._createWidget.call(this);
},
_activateControl: function(){
GP.Widget.DirectButton.prototype._activateControl.call(this);
/*активация действия кнопки */
},
_disableControl: function(){
GP.Widget.DirectButton.prototype._disableControl.call(this);
/* деактивация действия кнопки */
}
// id кнопки
// заголовок кнопки
// картинка кнопки
// если false, то функция _activateControl не будет
// автоматически выполняться при клике на кнопку
// активация
// деактивация
Правая панель
Добавление вкладки в правую панель:
GP.widgets.tabs.addTab({
name : "MyTab",
image : GP.imagesPath + "/icons/tab-icon.png",
imageWhite : GP.imagesPath + "/icons/tab-icon-white.png",
divId : "my-tab-block"
});
// надпись, выводимая при наведении
// иконка, отображаемая, когда вкладка неактивна
// иконка, отображаемая, когда вкладка активна
// id, присваиваемый элементу вкладки
После этого можно редактировать содержимое вкладки, изменяя соответствующий DOM элемент.
События правой панели
Следующее событие срабатывает при развертывании правой панели:
GP.widgets.tabs.on(«tab:rightPanelShow», myFunction, context); |
Следующее событие срабатывает при скрывании правой панели:
GP.widgets.tabs.on(«tab:rightPanelHide», myFunction, context); |
Следующее событие срабатывает при изменении размера правой панели:
GP.widgets.tabs.on(«tab:resize»); |
Левая панель
Виджет, управляющий левой панелью, должен наследоваться от GP.Widget.LeftPanel.
GP.Module.Widget.myLeftPanel = GP.Widget.LeftPanel.extend{} |
Пример создания левой панели:
if(GP.widgets.leftPanel == undefined || !(GP.widgets.leftPanel instanceof GP.Widget.MyLeftPanel)){
GP.widgets.leftPanel = new GP.Widget.MyLeftPanel({
template:"myTemplate",
dustObject:myDustObject
});
GP.widgets.leftPanel.on(
"leftPanel:destroy",
this._widgetDestroy,
this
);
}
else
GP.widgets.leftPanel.reload(
"myTemplate",
dustObject
);
// сюда можно передать название template,
// который должен лежать в assets/templates и иметь расширение tl
// параметр используется в построении template, более подробно
// в описании плагина, который дает такую возможность http://akdubya.github.io/dustjs/
// при срабатывании события закрытия левой панели
// выполнить функцию this._widgetDestroy
// контекст выполнения функции
// если ранее левую панель занимал этот же виджет,
// тогда перерисовать панель, используя параметры
Создание левой панели
После создания экземпляра класса GP.Module.MyModule.Widget.MyLeftPanelWidget будет выполняться функция _createContent, которую мы в своем классе GP.Module.MyModule.Widget.MyLeftPanelWidget переопределяем для отображения нужной нам информации.
В классах, отвечающих за отрисовку левой панели можно определить следующие функции:
- onResize – выполняется автоматически при изменении размеров левой панели
- close — выполняется автоматически при закрытии левой панели
- reload — выполняется при открытии левой панели, если до этого левую панель занимал виджет — экземпляр этого же класса
При определении функции reload после выполнения всех действий необходимо выполнить следующее:
|
Для простого отображения левой панели можно не создавать свой класс — виджет, а просто создать экземпляр класса GP.Widget.leftPanel и передать конструктору template и dustObject:
|
События левой панели
Событие происходит при отображении левой панели. Выполняемая функция принимает на вход 2 аргумента: переменную, содержащую левую панель и ширину панели:
|
Событие происходит при закрытии левой панели. Выполняемая функция принимает на вход переменную, содержащую левую панель:
|
Событие происходит при замене левой панели на другую. Выполняемая функция принимает на вход переменную, содержащую левую панель:
|
Карта
Создание слоя из геометрии:
var object = jQuery.parseJSON(geom);
this._layer = new M.GeoJSON(object);
GP.mainMap.map.addLayer(this._layer);
GP.mainMap.setBBox(bbox.minX,bbox.minY,bbox.maxX,bbox.maxY,bbox.projection);
// geom - геометрия слоя в строковой переменной
// создаем слой из геометрии
// добавляем слой на карту
// переходим к выбранному участку на карте, устанавливая карте bBox
Создание wms слоя:
mapLayer = new M.TileLayer.WMS(
requestUrl,
{
layers: typeName,
styles: style,
format: 'image/png',
transparent: true
}
);
mapLayer.wmsParams.cql_filter = filterCQL.filterString();
// url запроса
// wms параметры слоя
// typeName слоя
// style слоя
// формат отдачи данных, по умолчанию image/jpeg
// прозрачность
// добавление CQL фильтров слою
Создание wfs слоя:
mapLayer = new M.WFS(
requestUrl,
typeName,
styleUrl,
undefined,
wfsOptions
);
// url запроса
// typeName слоя
// путь к .sld файлу стиля
// формат отдачи данных, по умолчанию application/json
// wfs опции запроса
Удаление слоя с карты
Для того, чтобы убрать слой с карты необходимо выполнить следующую функцию: GP.mainMap.map.removeLayer(layer), где layer – слой, который необходимо удалить.
Основные события карты
Событие «click» срабатывает при клике на карту. В переменную data при этом придет один из следующих объектов:
|
Событие «dblclick» срабатывает при двойном клике на карту. В переменную data при том придет один из следующих объектов:
|
|
Следующее событие срабатывает при приближении/отдалении карты:
|
Событие «move» срабатывает в процессе и после окончания передвижения карты:
|
Событие «viewreset» срабатывает после перезагрузки карты. Выполняемая функция принимает на вход один параметр, содержащий в себе boolean переменную hard (если true — перезагрузка со смещением карты)
|
Событие «popupopen» срабатывает при открывании всплывающего окошка на карте, выполняемая функция принимает на вход один параметр — popup:
GP.mainMap.map.on(«popupopen», myFunction, context); |
Событие «popupclose» срабатывает при закрывании всплывающего окошка на карте, выполняемая функция принимает на вход один параметр — popup:
GP.mainMap.map.on(«popupopen», myFunction, context); |
Событие «changeCenter» срабатывает после изменения центра карты:
|