Фреймы

Введение

Приложение apx не основывается на клиенском роутинге, как многие. Нет, как таковой, роутинг присутствует, но не является основным и обязательным механизмом взимодействия с различными частями приложения.

Вместо клиентского роутинга используются "фреймы".

Представим себе такое стандартное расположение основных элементов страницы:

header
menu
main

В областях "header" и "menu" - управляющие элементы. В области "main" - контент, который меняется, в зависимости от того, что выбрано в "header" или "main", например щелчек по какой-то кнопке.

Фрейм как раз и предназначен для того, что бы отображать информацию в "main".

А теперь представим себе диалог. Такую панель, которая содержит что-то, имеет кнопки "ок" , "отмена". При показе может быть модальным. И т.д.

Фрейм предназначен еще и для того, что бы отображать информацию в диалогах.

Фрейм - это vue-компонент

Фрейм - это vue-компонент, который должен быть унаследован от apx.JcFrame.

Пример простейшего фрейма:

<template>
    <div>
        Страница
    </div>
</template>

<script>
import {apx} from './vendor'

export default {
    extends: apx.JcFrame,
}
</script>

Декораторы фрейма

Фрейм должен отрисовывать себя сам полностью. Ему предоставляется некоторая область экрана (в примере выше это "main") и он ее полностью отрисовывает, включая все оформление, например заголовки.

Если фрейм используется как диалог, то он должен отрисовать диалог полностью, включая все кнопки диалога, его заголовок и т.д.

Естественно повторять оформление от фрейма к фрейму мало кому хочется. Поэтому для фрейма используются декораторы.

Декоратор фрейма - это vue-компонент, который используется в качестве корневого компонента в шаблоне фрейма.

Пример фрейма с декоратором:

<template>
    <decor-page>
        <template #toolbar>
            <jc-action icon="mail"/>
            <jc-action icon="calendar"/>
        </template>
        Страница
    </decor-page>
</template>

<script>
import {apx} from './vendor'

export default {
    extends: apx.JcFrame,
    created() {
        this.title = 'Простая страница'
        this.title2 = 'Есть мелкий заголовок'
        this.icon = 'frame'
    },
}
</script>

Компонент decor-page является декоратором фрейма.

По умолчанию для фрейма зарегистрированы следующие декораторы:

  • decor-page - для фреймов-страниц в "main". Компонент-декоратор: apxUi.JcDecorFramePage
  • decor-dialog - для фреймов-диалогов. Компонент-декоратор: apxUi.JcDecorFrameDialog

Собственные декораторы

Никто не мешает Вам разработать собственные декотораторы и использовать их. В качестве стартовых примеров можно взять существующие декортаторы.

Правила их написания просты:

  • нужно унаследовать компонент от apx.JcDecorFrame
  • у вас есть ссылка на оформляемый фрейм в свойстве own. Его свойства можно использовать при оформлении. Например - заголовок фрейма.

Показ фрейма

Функция apx.showFrame показывает фрейм как страницу в "main".

apx.showFrame({
    frame: FRAME,
    props: {PROPS},
})

Параметры:

  • frame - какой фрейм нужно показать. Может быть компонентом vue, import()-модуля, путем в роутинге
  • props - свойства компонента-фрейма.

Функция apx.showFrame асинхронная. Она возвращает Promise, который ресолвится экземпляром фрейма после показа.

frameInit

Фрейм может иметь опцию-функцию frameInit. Функция может быть асинхронной. Эта функция вызывается перед тем, как фрейм будет показан. В качестве параметра в функцию передается экземпляр класса apx.FrameWrapper.

Цель этой функции - загрузить данные в свойство FrameWrapper.frameData, которые затем можно использовать как данные в работе компонента-фрейма.

В момент вызова функции frameInit компонент-фрейм еще не существует. Поэтому this внутри тела функции не определен.

Пример:

frontend/src/frames/FrameInit.vue
<template>
    <decor-page>
        <div class="q-gutter-md">
            <div>users: {{ users }}</div>
            <div>dummyList: {{ frameData.dummyList }}</div>
            <div>post: {{ frameData.post }}</div>
        </div>
    </decor-page>
</template>

<script>
import {apx} from '../vendor'

export default {
    extends: apx.JcFrame,
    // свойства фрейма
    props: {
        // количество генерируемых строк
        cnt: {}
    },
    // метод будет вызван перед монтированием и показом
    // fw это экземпляр apx.FrameWrapper
    // функция может быть асинхронной
    async frameInit(fw) {
        // данные, которые необходимо заполнить
        let frameData = fw.frameData

        // свойства, которые были переданы фрейму при вызове showFrame
        let props = fw.props

        // берем значение свойств
        let cnt = apx.jcBase.toInt(props.cnt, 5)

        // проверяем их
        if (cnt > 10) {
            throw new Error('Слишком много строк заказано')
        }

        // используем свойства для формирования данных
        frameData.dummyList = []
        for (let i = 1; i <= cnt; i++) {
            frameData.dummyList.push({id: i, text: 'text-' + i})
        }

        // можно выполнять запросы
        let users = await apx.jcBase.ajax.request({
            method: 'get',
            url: 'https://jsonplaceholder.typicode.com/users'
        })
        frameData.users = users.data

        // даже несколько запросов
        let post = await apx.jcBase.ajax.request({
            method: 'get',
            url: 'https://jsonplaceholder.typicode.com/posts/1'
        })
        frameData.post = post.data

        // в консоле печатается frameWrapper, можно посмотреть что загрузилось
    },
    created() {
        this.title = 'FrameInit demo'

        // frameData доступно как свойство компонента
        let frameData = this.frameData

        // можно использовать
        for (let user of frameData.users) {
            this.users.push(user.username)
        }

        // frameData можно напрямую использовать в шаблоне
    },
    data() {
        return {
            users: [],
        }
    },
}
</script>

frameInit не наследуется

frameInit не наследуется при наследовании компонентов! В каждом фрейме должен быть собственный frameInit.

Пример:

frontend/src/frames/FrameInit-dialog.vue
import FrameInit from './FrameInit'

export default {
    // наследуем существующий фрейм
    extends: FrameInit,

    // frameInit не наследуется!
    async frameInit(fw) {
        // вызываем унаследованный
        await FrameInit.frameInit(fw)
    }
}