Dynamic Tabs - Вкладки на лету (electron+vue+tabs+view)

Добрый день Владимир @nolimits4web!

Предисловие:
Была задача сделать учетную систему на Electron. Задача стоит, чтобы каждая ссылка(новая сущность) открывалась в новой вкладке. На днях, в Telegram канале @shastox посоветовал, для этого использовать view as tabs.

Нужная структура - view as tabs. Где каждая вкладка полностью независима от других, т.е. в каждой вкладке свой View > значит свой router.
Вот как организовать эту структуру: https://framework7.io/docs/view.html#multiple-views-layout
1. Пробегаемся по текущим views, если нет нужного, то добавляем в DOM сам View, затем создаем его, а затем добавляем ссылку на него в Tabs и открываем нужный Tab.
2. В этом случае нужно просто переключить вкладку.
3. Уничтожаем View, удаляем его из Dom, удаляем ссылку на него в Tab. Если это View текущее, то предвариательно другое View делаем текущим.
4. Меняем позицию в DOM для View (только если используется Swipe-переключатель) и меняем позицию Tabs в DOM.

Детали:

  • Каждая уникальная ссылка должна открываться в новой вкладке.
    – возможно передать название вкладки 'title' через параметры, или через данные страницы.
  • Если ссылка уже есть в наличии, то переходим(активируем) на данную вкладку.
  • Возможность закрытия любой вкладки, кроме главной(первой).
  • Не критичные:
    – возможность сортировки местоположением вкладок.
    – открывать вкладки по соседству, не в самом конце.
    – ограничить максимальное количество открытых вкладок.
    – возможность обновления данных на нажатие вкладки (event на click название вкладки).

Реализация
Вроде все как есть реализовал во Vue. Ссылка на github - https://github.com/almazk/framework7-tabs-via-view.git

  1. Создал tabs.mixin.js и подключил это в основной app.js.
    Детали:
    activeTab: { index: 0, id: 'home'} - здесь указывается информация про активную вкладку, индекс и идентификатор.
    tabs: [{id: 'home', title: 'Home', icon: 'f7:house_fill', main: true, url: '/'}] - массив объектов, которая хранит информацию про вкладки, уникальный идентификатор, название, иконка, статус главности, ссылка на страницу.
    IDs - computed, выдает в виде массива уникальные идентификаторы вкладок
    – Далее методы для работы со вкладками:
    existsByID - проверка на наличие вкладки по Идентификатору,
    add - добавление вкладки,
    getIndexByID - получение индекса через Идентификатор,
    setActiveByID - выставляем активную вкладку через Идентификатор,
    isActive - проверка вкладки на активность через Идентификатор

  2. Во app.vue убрал все лишнее, в итоге:

  • панелька для правой стороны, для проверки глобально доступной,
  • appbar для красоты и для кнопок навигации, когда более одного становится активным,
  • f7-views:
    – f7-toolbar
    — f7-link итерация по tabs
    – f7-view итерация по tabs
    – В computed добавил Tabs, который возвращает $root.tabs то есть, который в tabs.mixin.js,
    – В методы добавил Tab, который возвращает аналогично $root.Tab.
  1. Отдельно вынос логику проверки вкладок в tabs.routes.js
Tab().existsByID(TabID)                      //?Вкладка существует
      .then(() => {                            //Да, существует
         Tab().isActive(TabID)                   //?Проверка вкладки на активность
            .then(($Index) => {                    //Активна
               Component_show();                     //показываем содержимое
            })
            .catch(() => {                        //Не активна
               Tab().setActiveByID(TabID)           //Активизируем вкладку
                  .then(() => {                       //Удачно
                     Component_show();                  //и показываем содержимое вкладки
                  })
                  .catch(() => {                      //Не удачно
                     reject();                          //ничего не делаем
                  });
            });
      })
      .catch(() => {                           //Нет, вкладка не существует :(
         let tabData = {
            id: TabID,
            title: Title,
            icon,
            url,
            place: 'last'  //todo not implemented yet
         };
         Tab().add(tabData)                            //Добавляем вкладку
            .then(($Result) => {                         //Удачно добавили
               // console.warn('Tab_add SUCCESS:', $Result)
            })
            .catch(($Error) => {                         //Не удачное добавление
               // console.error('Tab_add ERROR:', $Error)
               reject();
            });
      })
  1. В главной вкладке показывает страница home.vue. Внутри есть список ссылок на Каталог, на конкретный продукт, на страницу настроек и не существую страницу. Ссылки создаются через итерацию по массиву данных.

  2. В routes.js подключил tabs.routes.js, и настроил для текущих страниц async, в формате:

   {
      path: '/catalog',
      async: TabRouting,    //основной метод в 'tabs.routes.js'
      options: {
         context: {
            component: CatalogPage //vue component
         }
      }
   }

Проблемы:

Проблема #1

  • Все вкладки вроде работают, открываются закрываются.
  • Но если последовать текущий последовательности:
    – Home -> Catalog
    – Catalog -> Product 1 (открывать первый продукт)
    – Product 1 -> Catalog (возвращаемся на страницу каталога)
    – Catalog -> Product 2 (открываем второй)
    – Product 2 -> Catalog (возвращаемся заново)
    – Catalog -> Product 3 (открываем последний продукт)
    – Product 3 -> Catalog (возвращаемся заново)
    – Catalog -> Product 1 (открываем заново продукт №1)
    – Product 1 -> Catalog (возвращаемся заново)
    – Catalog -> Product 2 (открываем заново продукт №2)
    – Product 2 -> Catalog (Возникает ошибка №1, содержимое от Продукта 1)

Из за того, что так и не смог полностью разобраться во view. Это критично, как решит этот вопрос?

  • Как передать view во ссылке и поймать его во TabRouting?
    p.s. для удобства тестирования сделал свою приблуду, на главной странице нужно нажать START TESTs, делает выше указанные движения за вас.

Проблема #2
На главной странице есть две модальные окна. Одну хочу сделать глобальным, вторую только для данной вкладки.

  • Что для этого надо сделать?

Проблема #3
Есть проблема с панелями. На главное странице и в странице каталога есть кнопки для открытия вкладок. Все ок, но когда делаешь свайп на странице каталога, там есть маленький глюк, открывается содержимое главной панели, потом перерисовывается на содержимое вкладки.

  • Это баг или я что то не правильно делаю?

Тут что-то с роутером, так сказать трудно.

Они и так «глобальные», ко view не привязаны.

Может из за того что панель тоже свайпом открывается?

Посмотрел, ты конечно выбрал сложный путь. Проблема 1 случается из-за того что у тебя не срабатывает reject() в каком то из условий. И вообще почему они у тебя асинхронные? Проверка на существование вкладки это же статическая операция, и async роуты тут не подходят. Нужно использовать beforeEnter, добавь параметр в f7params:

view: {
   routesBeforeEnter(to, from, resolve, reject) {
      const router = this;
      // тут уже логика
   }
}

Например если у router.history.length > 0 (уже есть страница), то это однозначно reject() и загрузка нового таба.
Если router.history.length === 0 то это новый пустой таб и соотвественно resolve()

2 панели со свайпом не будут работать правильно, отключай/включай свайп на нужной panel.disable/enableSwipe() https://framework7.io/docs/panel.html#panel-methods-properties

У меня начал получаться, немного по другому. Через какое то время сюда выложу. Хотел все вынести в отдельный plugin, чтобы можно было использовать и в других проектах.
Как это подцепить вот этот event

view: {
   routesBeforeEnter(to, from, resolve, reject) {
      ...
   }
}

в https://framework7.io/docs/plugins-api.html ?

Перепробовал все возможные комбинации. Исходники тоже посмотрел, но так и не разобрался.

Framework7.use({
  name: 'my-plugin',
  params: {
    view: {
      routesBeforeEnter(to, from, resolve, reject) {
        console.log('...');
      }
    }
  }
})