Последние версии Ext JS, особенно Modern Toolkit снизили порог вхождения во фреймворк (примеры Kitchen Sink), упростили создание нужного интерфейса (привет Sencha Architect) и добились минимального размера веб-приложений (Sencha Cmd).
Пожалуй, Хабр нужно разбавить примером реализации «ситуационного центра», где в реальном времени можно наблюдать камеры и события с них (все данные фейковые).
![](https://habrastorage.org/r/w1560/webt/ic/yg/pv/icygpvzc4tbfn9jon93q78z2u-w.png)
Начнем. Создадим Spring Boot проект с 2 контроллерами, которые будут отдавать список камер, существующие события, а также возможность подписки на новые события (через WebSocket).
![](https://habrastorage.org/r/w1560/webt/qz/gq/y5/qzgqy5rj1yiovuxbeiej7xjdmas.png)
Далее добавим необходимые модели, хранилища, вьюшки и внешние зависимости:
![](https://habrastorage.org/r/w1560/webt/dr/aw/xb/drawxbnnfe07zwday70x3xdf1q0.png)
Также условимся, что события будут исходить от компонента CamerasGrid, т.к. именно этот компонент отвечает за добавление/удаление камер с карты.
В итоге получается довольно занятный интерфейс. Замечу, что при правильном проектировании архитектуры приложения (даже такого маленького), время на создание оного совсем невелико, до пары часов.
![](https://habrastorage.org/webt/el/mu/av/elmuav_-ihtklpkeqe-vimfnbkk.gif)
Код примера доступен на github.com.
Пожалуй, Хабр нужно разбавить примером реализации «ситуационного центра», где в реальном времени можно наблюдать камеры и события с них (все данные фейковые).
![](https://habrastorage.org/webt/ic/yg/pv/icygpvzc4tbfn9jon93q78z2u-w.png)
Начнем. Создадим Spring Boot проект с 2 контроллерами, которые будут отдавать список камер, существующие события, а также возможность подписки на новые события (через WebSocket).
![](https://habrastorage.org/webt/qz/gq/y5/qzgqy5rj1yiovuxbeiej7xjdmas.png)
Далее добавим необходимые модели, хранилища, вьюшки и внешние зависимости:
![](https://habrastorage.org/webt/dr/aw/xb/drawxbnnfe07zwday70x3xdf1q0.png)
Более детально вьюшка карты, взаимодействующая с 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);
}
}
}
}
});
В итоге получается довольно занятный интерфейс. Замечу, что при правильном проектировании архитектуры приложения (даже такого маленького), время на создание оного совсем невелико, до пары часов.
![](https://habrastorage.org/webt/el/mu/av/elmuav_-ihtklpkeqe-vimfnbkk.gif)
Код примера доступен на github.com.