Как стать автором
Обновить

ExtJS 7 и Spring Boot 2. Как построить SPA, взаимодействующее с вашим API и внешними ReactJS плагинами?

Время на прочтение3 мин
Количество просмотров5.9K
Последние версии Ext JS, особенно Modern Toolkit снизили порог вхождения во фреймворк (примеры Kitchen Sink), упростили создание нужного интерфейса (привет Sencha Architect) и добились минимального размера веб-приложений (Sencha Cmd).

Пожалуй, Хабр нужно разбавить примером реализации «ситуационного центра», где в реальном времени можно наблюдать камеры и события с них (все данные фейковые).



Начнем. Создадим 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.
Теги:
Хабы:
Всего голосов 4: ↑3 и ↓1+2
Комментарии1

Публикации