Pull to refresh

Jiant. Модели для фронтенд приложения

Reading time6 min
Views7.6K

Предисловие


Цель данного поста — популяризация веб-фреймворка Jiant и поиск людей, которые хотели бы принять участие в его раскрутке, создание сообщества для дальнейшего развития и распространения фреймворка. Jiant активно использует JQuery и естественно с ним взаимодействует, выполняя свою цель по организации и упрощению веб приложений любой сложности.

Целевая аудитория — полагаю, будет полезно любому веб-разработчику, но особенно тем кто пришел с сервера — например, из джавы.

Ссылка на github: https://github.com/vecnas/jiant
Ссылка на todo-mvc: https://github.com/vecnas/todo-mvc

Введение


Jiant — фреймворк для разработки веб-приложений, предоставляющий набор инструментов, упрощающих и ускоряющих разработку и затем поддержку JavaScript приложений.

Jiant приложение объявляется как переменная JavaScript и содержит несколько секций. Одна из таких секций — models. В этой секции объявляются модели данных приложения:

var app = {
  models: {
    user: {
      //declaration is here
    },
    env: {
      //declaration is here
    }
  }
}

Для каждой модели описываются ее поля в минимальной форме, затем Jiant автоматически реализует модель. Модели бывают двух типов — singleton(единственный объект данных в приложении) и репозиторий (коллекция объектов).

Преимущества перед всеми известными мне на текущий момент фреймворками — минимум кода, удобство автозавершения в IDE, автодокументирование, простота понимания, естественная декомпозиция приложения на простейшие части.

Singleton модель


Объявление включает список полей и несколько функций, добавляемых по умолчанию, таких как update, on, off:

models: {

  loggedUser: {
    // auto-added function, declared for readability
    update: function(obj) {},

    // model fields
    firstName: function(val) {},
    lastName: function(val) {},
    id: function(val) {}
  }

}

Или так:

  app.models.loggedUser = {
    // auto-added function, declared for readability
    update: function(obj) {},

    // model fields
    firstName: function(val) {},
    lastName: function(val) {},
    id: function(val) {}
  }

Полями считаются все пустые функции кроме нескольких предопределенных, таких как update, on. Реализация выполняется Jiant'ом в момент вызова следующей функции, в этот момент все модели должны быть добавлены в файл описания приложения:

jiant.bindUi(app);

Изменение свойств модели

После связывания можно пользоваться моделью, как правило внутри метода jiant.onUiBound:

jiant.onUiBound(app, function($, app) {
  var loggedUser = app.models.loggedUser;
  loggedUser.firstName("Joy");
})

Если есть данные в виде json, можно просто вызвать update (наиболее часто используемый сценарий — получение данных с сервера):

var userData = {
  firstName: "John",
  lastName: "Smith",
  id: 1234
};
app.models.loggedUser.update(userData);

В чем удобство? Теперь можем из любого места доступаться до этих данных, например где-то еще, в не связанном с первым блоком коде:

var loggedUserId = app.models.loggedUser.id();

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

loggedUser.firstName() // getter
loggedUser.firstName("Johann") // setter
loggedUser.firstName(undefined) // setter

Синтетические методы

В рамках модели можем определить методы, предоставляющие более сложные функции чем setter/getter:

loggedUser: {
  //...
  fullName: function() { 
              return this.firstName() + " " + this.lastName()
            }
}

Можно вернуть ссылку на другую модель или просто сообщить залогинен ли кто-нибудь:

models: {
  env: {
    loggedUser: function() {return app.models.loggedUser}
    isLogged: function() {return !!app.models.loggedUser.id()}
  }
}

На синтетических методах не генерируются уведомления об изменении (нет методов on, off, asap).

Подписка на изменения

Каждое свойство модели предоставляет метод .on(callback), для подписки на изменение данного свойства:

jiant.onUiBound(app, function($, app) {
  app.models.loggedUser.id.on(function(loggedUser, id) {
    alert("logged user id changed: " + id);
  }
})

Полный синтаксис callback для метода on:

function(modelObject, newValue, oldValue)

Метод off идет в комплекте. Также для самого модельного объекта доступна подписка на любые изменения его полей через все тот же метод on:

  app.models.loggedUser.on(function(loggedUser) {})

ASAP

Нередки ситуации когда необходимо выполнить какой-то код когда доступны определенные данные, либо немедленно если они уже доступны. В этом случае используется метод asap (as soon as possible), который срабатывает только один раз — либо сразу, либо когда появляется требуемое значение:

app.models.loggedUser.id.asap(function(loggedUser, id) {
  alert("Hello " + loggedUser.fullName());
})

Репозиторий-модель


Основное отличие репозитория в том, что в нем хранится много объектов данной модели, а не одна. Соответственно, работа с репозиторием осуществляется как с коллекцией. Название репозиторий используется так как изначально идея была рождена на базе JpaRepository из Java Spring фреймворка. При этом синтаксис объявления похож на синглтон, но добавляются методы работы с коллекцией:

models: {
  listing: {
    updateAll: function(arr, removeMissing) {},
    add: function(arr) {},
    remove: function(obj) {},
    
    all: function() {},
    findById: function(val) {},
    listByTp: function(val) {},

    id: function(val) {},
    tp: function(val) {},
    price: function(val) {},
    baths: function(val) {},

    ui: function(val) {}
  }
}

Модификация репозитория

Методы add, remove делают в точности то что говорит их название. Единственный не очевидный метод — updateAll — он производит сравнение по полю id массива или объекта который ему передан и текущего содержимого репозитория, после чего добавляет-удаляет-обновляет содержимое. В случае если у объекта нет идентификатора — можно либо объявить синтетический метод id(), либо передать 3й параметр методу updateAll, функцию которая сравнит два объекта и вернет true/false.

Метод remove можно использовать любым способом, допустим если есть объект данной коллекции obj, тогда обе следующие строки делают одно и то же:

app.models.listing.remove(obj);
obj.remove()

Метод update в случае репозитория применяется к отдельному объекту, а не ко всему репозиторию в целом:

  var obj = app.models.listing.findById(365);
  obj.update(newJsonData);
  // или
  obj.price(newPrice);

Получение объектов репозитория

Метод all возвращает все объекты репозитория и доступен всегда. Также можно определить методы поиска findByXXX, listByXXX, которые ищут в репозитории по значению поля (или нескольких):

  findByTp: function(val) {} // ищем по полю tp
  listByTpAndBaths: function(tp, baths) {} // ищем сразу по двум полям

Перечень полей если их больше одного — разделяется словом And. Реализация этих методов осуществляется автоматически. Допустим, понадобилось искать еще по какому-то полю — добавляем пустую функцию и готово.

Различие find и list в том что find возвращает только один объект (первый попавшийся, либо нулл), а list всегда возвращает массив (со списком объектов или пустой).

Функции коллекций

Методы all и listBy всегда возвращают массивы, но массивы не простые, а обогащенные методами базовой модели, такими как геттеры, update, remove. Например:

app.models.listing.listByTp("obsolete").remove() // удаляем все tp=="obsolete" листинги
app.models.listing.all().price("n/a") // ставим всем цену n/a
app.models.listing.all().ui() // массив ui объектов
app.models.listing.all().asMap() // содержимое репозитория в виде json

Не только данные

В модели можно хранить не только данные, а любые объекты, например в примере выше есть поле ui — в нем мы будем хранить визуальное представление листинга на интерфейсе, ссылку на jQuery объект и сможем доступаться к нему в любой момент в нужном месте.

app.models.listing.findById(234).ui().addClass("active")

Примеры


Вот некоторые примеры использования вышеперечисленных функций:

asap — в проекте использовались google maps, и выставлять на них маркеры нужно было когда они проинициализированы. Чтобы не пихать весь код в одно место — добавлено поле gmap в модель env и код расставляющий маркеры начинал работать когда оно выставлялось. Получились два небольших независимых участка кода вместо одного месива.

singleton — постоянно используется для работы с окружением (тот же пример с google maps, аналогии легко добавить) или информации о залогиненном пользователе

repository — любые загружаемые с сервера списки данных, отображаемые на интерфейсе или используемые в программной логике. Например — список штатов, с возможностью поиска по коду и потом отображение на интерфейсе полного имени:

models: {
  state: {
    updateAll: function(arr) {},

    findByShort: function(val) {},

    short: function(val) {},
    full: function(val) {}
  }
}

И затем:

var fullStateName = app.models.state.findByShort(short).full()

Другой пример — уведомления и нотификации. Поступающие уведомления просто скидываются в модель. Отдельно существующий независимый код висит над этой моделью и показывает уведомления пользователю, удаляя те что пользователь убрал.

Еще меньше кода

Объявив

var f = function(val) {}

можно уменьшить количество кода:

models: {
  state: {
    updateAll: function(arr) {},

    findByShort: f,

    short: f,
    full: f
  }
}

Не делайте так, не будет подсказки от IDE что это функция (пока не разработают плагин).

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

Бонусы


Документация за просто так. Объявив модель, сразу получаем документацию к приложению.
Ускорение разработки. Меньше писать.
Значительное упрощение поддержки. Приложение разбито на произвольно мелкие части. IDE автозавершение и поиск «мест использования» работают, что крайне редко для javascript кода.
Главный недостаток — излишняя простота — было такое что поначалу непонятно «как это может работать, тут же просто пустая функция». Риторический вопрос, недостаток ли это.
Tags:
Hubs:
Total votes 6: ↑6 and ↓0+6
Comments24

Articles