Последние версии Ext JS, особенно Modern Toolkit снизили порог вхождения во фреймворк (примеры Kitchen Sink), упростили создание нужного интерфейса (привет Sencha Architect) и добились минимального размера веб-приложений (Sencha Cmd).
Пожалуй, Хабр нужно разбавить примером реализации «ситуационного центра», где в реальном времени можно наблюдать камеры и события с них (все данные фейковые).
Начнем. Создадим Spring Boot проект с 2 контроллерами, которые будут отдавать список камер, существующие события, а также возможность подписки на новые события (через WebSocket).
Далее добавим необходимые модели, хранилища, вьюшки и внешние зависимости:
Также условимся, что события будут исходить от компонента CamerasGrid, т.к. именно этот компонент отвечает за добавление/удаление камер с карты.
В итоге получается довольно занятный интерфейс. Замечу, что при правильном проектировании архитектуры приложения (даже такого маленького), время на создание оного совсем невелико, до пары часов.
Код примера доступен на github.com.
Пожалуй, Хабр нужно разбавить примером реализации «ситуационного центра», где в реальном времени можно наблюдать камеры и события с них (все данные фейковые).
Начнем. Создадим Spring Boot проект с 2 контроллерами, которые будут отдавать список камер, существующие события, а также возможность подписки на новые события (через WebSocket).
Далее добавим необходимые модели, хранилища, вьюшки и внешние зависимости:
Более детально вьюшка карты, взаимодействующая с React компонентом карты.
Ext.define('Cameras.view.override.Map', {
override: 'Cameras.view.Map',
config: {
cameras: {},
react: null
},
initialize: function() {
let that = this;
let e = React.createElement;
this.setReact(ReactDOM.render(e(createReactClass({
getInitialState: function() {
return { items: [], center: [55.751574, 37.573856]};
},
render: function() {
let placemarks = [];
for(let i=0; i < this.state.items.length; i++) {
let location = this.state.items[i].get("location");
placemarks.push(e(window.ReactYandexMaps.Placemark, {
geometry: [location.latitude, location.longitude],
options: {
preset: 'islands#blueCircleDotIconWithCaption',
iconCaptionMaxWidth: '50'
}
}));
}
let map = e(window.ReactYandexMaps.Map, {
state: { center: this.state.center, zoom: 10 },
width: '100%',
height: '100%'
}, placemarks);
return e(window.ReactYandexMaps.YMaps, null, map);
}
})), this.mapContainer.dom));
},
getElementConfig: function() {
return {
reference: 'element',
className: 'x-container',
children: [{
reference: 'bodyElement',
style: 'width: 100%; height: 100%',
className: 'x-inner',
children: [{
style: 'width: 100%; height: 100%',
reference: 'mapContainer',
className: Ext.baseCSSPrefix + 'map-container'
}]
}]
};
},
addCamera: function(cameraModel) {
if(!this.containsCamera(cameraModel)) {
this.getCameras()[cameraModel.get("id")] = cameraModel;
this.getReact().setState({
items: Object.values(this.getCameras())
});
this.fitCamera(cameraModel);
}
},
removeCamera: function(cameraModel) {
if(this.containsCamera(cameraModel)) {
delete this.getCameras()[cameraModel.get("id")];
this.getReact().setState({
items: Object.values(this.getCameras())
});
}
},
fitCamera: function(cameraModel) {
if(this.containsCamera(cameraModel)) {
let location = this.getCameras()[cameraModel.get("id")].get("location");
this.getReact().setState({
center: [location.latitude, location.longitude]
});
}
},
privates: {
containsCamera: function(cameraModel) {
cameraId = "" + cameraModel.get("id");
return Object.keys(this.getCameras()).includes(cameraId);
}
}
});
Также условимся, что события будут исходить от компонента CamerasGrid, т.к. именно этот компонент отвечает за добавление/удаление камер с карты.
Контроллер компонента CamerasGrid, добавляющий компоненту генерацию событий
Ext.define('Cameras.view.CamerasGridViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.camerasgrid',
init: function() {
let socket = new WebSocket("ws://localhost:8080/events/sub");
socket.onopen = function(e) {
console.log('onopen');
};
socket.onmessage = this.onMessage.bind(this);
},
onMessage: function(event) {
let data = Ext.decode(event.data);
let gridData = this.getView().getStore().getData();
for(let i=0; i < gridData.length; i++) {
let checked = gridData.getAt(i).get("checked");
if(checked !== undefined && checked) {
if(gridData.getAt(i).get("id") == data.camera.id) {
this.fireViewEvent("cameraRecognition", data);
}
}
}
}
});
В итоге получается довольно занятный интерфейс. Замечу, что при правильном проектировании архитектуры приложения (даже такого маленького), время на создание оного совсем невелико, до пары часов.
Код примера доступен на github.com.