В данной статье будет рассмотрено создание модуля системы mapSurfer.
1) Первый этап. Создание нового проекта при помощи Play Framework 2.1.0
Полную документацию по работе с фреймворком можно найти на официальном сайте http://www.playframework.com/documentation/2.1.0/Home
Для написания модуля необходимо использовать jdk версии 1.6, так как основной проект написан именно на этой версии java, и с другими версиями могут возникнуть ошибки.
В первую очередь нужно установить Play Framework 2.1.0, полное описание того, как это делается есть на официальном сайте http://www.playframework.com/documentation/2.1.0/Installing .
Далее необходимо создать новый проект http://www.playframework.com/documentation/2.1.0/NewApplication , указав при создании template «gradoservice/mapsurfergs-module», введя его название как показано на картинке:
Для редактирования проекта удобно использовать IntelliJ IDEA. Для работы с IntelliJ IDEA необходимо выполнить команду play idea, находясь в папке с проектом. Более подробно о разработке проекта в различных IDE: http://www.playframework.com/documentation/2.1.0/IDE .
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 запросов в следующем формате:
тип запросапуть относительно корня модуля, по которому будут обрабатываться запросы. Итоговый путь относительно всего сайта будет выглядеть следующим образом: /modules/myModule/myRouteфункция, обрабатывающая запросы по данному пути.
GET POST |
/myRoute | controllers.geoportal.modules.module_name. MyController.myFunction |
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.. В этой директории находятся 3 папки:
javascripts — в этой папке должны находиться все файлы с 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.Module.MyModule.Controller.MyController = GP.Controller.extend({
initialize: function() {
GP.Controller.prototype.initialize.call(this,{});
/* действия контроллера */
}
});
Создание контроллера:
GP.controllers.MyController = new GP.Module.Controller.MyController();
В качестве конструктора контроллера используется функция initialize, в которой необходимо вызвать конструктор родителя, т.е. контроллер должен выглядеть следующим образом:
initialize: function() {
GP.Controller.prototype.initialize.call(this,{});
/* действия контроллера */
}
Хранилище данных Store, Model .
Для хранения данных удобно использовать Store.
В первую очередь нужно создать модель, в которой необходимо описать все составляющие элементов стора.
Пример модели стора с различными типами данных:
GP.Module.MyModule.Model.MyModel = GP.Model.extend({
fields: [
/* описание полей объектов, которые будут храниться в Store */
{
name: "id", //имя поля
type: "integer", //тип значений поля
dvalue: 0, //значение по умолчанию
notNull: true, //может ли поле быть undefined, если false, то да
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.
GP.Module.MyModule.Store.DistrictsStore = GP.Store.extend({
model: GP.Module.MyModule.Model.MyModel
});
События 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.Module.MyModule.Widget.MyWidget= GP.Widget.extend({
_createWidget: function() {
/* действия виджета*/
}
});
Создание виджета:
GP.widgets.myWidget= new GP.Module.Widget.MyWidget();
Событие создания виджета, выполняемая функция принимает на вход переменную, содержащую создаваемый виджет.
GP.on("widgetCreate", myFunction, context);
Событие удаления виджета, выполняемая функция принимает на вход переменную, содержащую удаляемый виджет.
GP.on("widgetDestroy", myFunction, context);
Авторизация
Событие выполнения авторизации можно отследить следующим образом:
GP.widgets.authentication.on(
"authenticationSuccess",
function(user){}
);
Диалоговое окно (GP.Widget.DialogBox)
Пример работы с DialogBox
if(this._myDialogBox == undefined){ //предупреждения открытия одного окна несколько раз
this._myDialogBox = new GP.Widget.DialogBox( //создание DialogBox
{
dialogBoxId:"myDialogBox", //id DOM элемента диалового окна
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(); //закрыть окно (удалить содержимое и сделать невидимым)
}
Добавление кнопок в верхнюю панель
Виджет, управляющий кнопкой в верхней панели, должен наследоваться от GP.Widget.DirectButton.
GP.Module.Widget.myDirectButtonWidget = GP.Widget.DirectButton.extend{
}
Создание кнопки
После создания экземпляра класса GP.Widget.MyDirectButtonWidget будет выполняться функция _createWidget, в которой необходимо выполнить вызов функции _createWidget родителя:
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);
/* деактивация действия кнопки */
}
Также обязательно описать следующие опции:
mainOptions:{
buttonId: "", //id кнопки
title: "", //заголовок кнопки
image: "", //картинка кнопки
bindClick: true //if false, то функция _activateControl
//не будет автоматически выполняться при клике на кнопку.
},
Пример кнопки:
GP.Module.Widget.myDirectButtonWidget = GP.Widget.DirectButton.extend{
mainOptions:{
buttonId: "", //id кнопки
title: "", //заголовок кнопки
image: "", //картинка кнопки
bindClick: true //if false, то функция _activateControl
//не будет автоматически выполняться при клике на кнопку.
},
_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);
/* деактивация действия кнопки */
}
}
Правая панель
Добавление вкладки в правую панель:
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", //сюда можно передать название template,
//который должен лежать в assets/templates и иметь расширешие tl.
dustObject:myDustObject //параметр используется в построении template, более подробно
// в описании плагина, который дает такую возможность http://akdubya.github.io/dustjs/
});
GP.widgets.leftPanel.on(
"leftPanel:destroy", //при срабатывании события закрытия левой панели
this._widgetDestroy,//выполнить функцию this._widgetDestroy
this //контекст выполнения функции
);
}
else
GP.widgets.leftPanel.reload( //если ранее левую панель занимал этот же виджет,
//тогда перерисовать панель, используя параметры
"myTemplate",
dustObject
);
Создание левой панели
После создания экземпляра класса GP.Module.MyModule.Widget.MyLeftPanelWidget будет выполняться функция _createContent, которую мы в своем классе GP.Module.MyModule.Widget.MyLeftPanelWidget переопределяем для отрисовки нужной нам информации.
В классах, отвечающих за отрисовку левой панели можно определить функцию _onResize которая будет автоматически выполняться при изменении размеров левой панели.
функция _close — при закрытии левой панели,
reload — функция выполняется при открытии левой панели, если до этого левую панель занимал виджет — экземпляр этого же класса. При определении функции reload после выполнения всех действий необходимо выполнить следующие действия:
reload: function(template,dustObject){
/****/
GP.fire("leftPanel:reload",{leftPanel:this});
this.options.template = template;
dustRender(template,dustObject,this._dustRender,this);
}
Для простого отображения левой панели можно не создавать свой класс — виджет, а просто создать экземпляр класса GP.Widget.leftPanel и передать конструктору template и dustObject:
GP.widgets.leftPanel = new
GP.Widget.leftPanel({template:"myTemplate",dustObject:dustObject},"#wrap");
GP.widgets.leftPanel.on("leftPanel:destroy",this._widgetDestroy,this);
События левой панели
Событие происходит при отображении левой панели, выполняемая функция принимает на вход 2 аргумента: переменную, содержащую левую панель и ширину панели.
GP.on(
"leftPanel:show",
function(leftPanel,width){},
context
);
Событие происходит при закрытии левой панели, выполняемая функция принимает на вход переменную, содержащую левую панель
GP.widgets.leftPanel.on(
"leftPanel:destroy",
myFunction,
context
);
Событие происходит при замене левой панели на другую, выполняемая функция принимает на вход переменную, содержащую левую панель
GP.on(
"leftPanel:reload",
myFunction,
context
);
Карта
Примеры работы с картой:
Создание слоя из геометрии
var object = jQuery.parseJSON(geom); //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); //переходим к
// выбранному участку на карте, устанавливая карте bBox
Создание wms слоя
mapLayer = new M.TileLayer.WMS(
requestUrl, //url запроса
{ //wms параметры слоя
layers: typeName, //typeName слоя
styles: style, //style слоя
format: 'image/png', //формат отдачи данных, по умолчанию image/jpeg
transparent: true //прозрачность
}
);
mapLayer.wmsParams.cql_filter = filterCQL.filterString(); //добавления CQL фильтров слою.
Создание wfs слоя
mapLayer = new M.WFS(
requestUrl, //Url запроса
typeName, //typeName слоя
styleUrl, //путь к .sld файлу стиля
undefined, //формат отдачи данных, по умолчанию application/json
wfsOptions //wfs опции запроса
);
Удаление слоя с карты
Для того, чтобы убрать слой с карты необходимо выполнить следующую функцию: GP.mainMap.map.removeLayer(layer), где layer – слой, который нужно убрать.
Основные события карты:
Cобытие «click» сработает при клике на карту, в переменную data придет объект, содержащий в себе:
latlng: объект, содержащий координаты (долгота и широта) точки.
layerPoint: координаты клика относительно экрана, начало координат в нижней левой точке
originalEvent: событие клик, отдаваемое браузером
target: контейнер, в котором находится карта
type: строковая переменная, содержащая "click"
GP.mainMap.map.on("click", function (data) {
this.hideMap(this._curtainDragging.offset().left);
}, this);
Событие «dblclick» сработает при двойном клике на карту, в переменную data придет объект, содержащий в себе:
latlng: объект, содержащий координаты (долгота и широта) точки.
layerPoint: координаты клика относительно экрана, начало координат в нижней левой точке
originalEvent: событие клик, отдаваемое браузером
target: контейнер, в котором находится карта
type: строковая переменная, содержащая ""dblclick""
GP.mainMap.map.on("dblclick", function () {
this.hideMap(this._curtainDragging.offset().left);
}, this);
Событие срабатывает после приближения/удаления карты
GP.mainMap.map.on("zoomend", function () {
this.hideMap(this._curtainDragging.offset().left);
}, this);
Событие «move» срабатывает во время и после заканчивания передвижения карты
GP.mainMap.map.on("move", function () {
this.hideMap(this._curtainDragging.offset().left);
}, this);
Событие «viewreset» срабатывает после перезагрузки карты, выполняемая функция принимает на вход один параметр, содержащий в себе boolean переменную hard (если true — перезагрузка со смещением карты)
GP.mainMap.map.on("viewreset", myFunction, context);
Событие «popupopen» срабатывает при открывании всплывающего окошка на карте, выполняемая функция принимает на вход один параметр — popup
GP.mainMap.map.on("popupopen", myFunction, context);
Событие «popupclose» срабатывает при закрывании всплывающего окошка на карте, выполняемая функция принимает на вход один параметр — popup
GP.mainMap.map.on("popupclose", myFunction, context);
Событие «changeCenter» срабатывает после изменения центра карты
GP.mainMap.map.on("changeCenter", function () {
this.hideMap(this._curtainDragging.offset().left);
}, this);