------
# SGModel & SGModelView
*Ссылка на GitHub-страницу: [https://github.com/VediX/model.sg2d.github.io](https://github.com/VediX/model.sg2d.github.io)*
**SGModel** - Быстрая легковесная библиотека-класс для структурирования веб-приложений с помощью биндинг-моделей. Это более быстрый и упрощенный аналог Backbone.js! Библиотека хорошо адаптирована для наследования классов (ES6+).
**SGModelView** - надстройка над SGModel позволяющая связать данные в JavaScript с визуальными элементами HTML-документа, используя MVVM-паттерн. Это очень упрощенный аналог KnockoutJS или VueJS.
*Пример использования: [Перейти на страницу примера](/example/)*
#### Исходники (версия 1.0.4):
* [sg-model.js (29KB)](https://raw.githubusercontent.com/VediX/model.sg2d.github.io/master/src/sg-model.js)
* [sg-model-view.js (17KB)](https://raw.githubusercontent.com/VediX/model.sg2d.github.io/master/src/sg-model-view.js)
## Описание API
* [Основные статические свойства SGModel](#основные-статические-свойства-sgmodel)
* [static typeProperties = {…}](#static-typeproperties--)
* [static defaultsProperties = {…}](#static-defaultsproperties--)
* [static options = {...}](#static-options--)
* [static localStorageKey = ""](#static-localstoragekey--)
* [static storageProperties = []](#static-storageproperties--)
* [Свойства и методы экземпляра SGModel](#свойства-и-методы-экземпляра-sgmodel)
* [constructor(props, options)](#constructorprops-options)
* [uid](#uid)
* [initialized](#initialized)
* [changed = false](#changed--false)
* [destroyed = false](#destroyed--false)
* [defaults()](#defaults)
* [initialize(properties, options)](#initializeproperties-options---override)
* [set(name, value, options = 0, flags = void 0, event = void 0, elem = void 0)](#setname-value-options--0-flags--void-0-event--void-0-elem--void-0)
* [get(name)](#getname)
* [on(name, func, context = void 0, data = void 0, flags = 0)](#onname-func-context--void-0-data--void-0-flags--0)
* [off(name, func)](#offname-func)
* [trigger(name, value = void 0, flags = 0)](#triggername-value--void-0-flags--0)
* [save()](#save)
* [getData(bDeleteEmpties = false)](#getdatabdeleteempties--false)
* [destroy()](#destroy)
* [Собственные сеттеры в наследуемых классах](#собственные-сеттеры-в-наследуемых-классах)
* [static ownSetters = {…}](#static-ownsetters--)
* [Поддержка Singleton паттерна в наследуемых классах](#поддержка-singleton-паттерна-в-наследуемых-классах)
* [static singleInstance = false](#static-singleinstance--false)
* [static getInstance(bIgnoreEmpty=false)](#static-getinstancebignoreemptyfalse)
* [Статические методы get, set, on, off, save](#статические-методы-get-set-on-off-save)
* [static getProperties()](#static-getproperties)
* [Утилиты используемые в SGModel](#утилиты-используемые-в-sgmodel)
* [static defaults(dest, …sources)](#static-defaultsdest-sources)
* [static clone(source)](#static-clonesource)
* [static initObjectByObject(dest, source)](#static-initobjectbyobjectdest-source)
* [static upperFirstLetter(s)](#static-upperfirstletters)
* [static roundTo(value, precision = 0)](#static-roundtovalue-precision--0)
* [MVVM-паттерн в SGModelView](#mvvm-паттерн-в-sgmodelview)
* [Методы экземпляра SGModelView](#методы-экземпляра-sgmodelview)
* [bindHTML(root=void 0)](#bindhtmlrootvoid-0)
* [Атрибуты в HTML-документе](#атрибуты-в-html-документе)
* [sg-property](#sg-property)
* [sg-type, sg-option и sg-dropdown](#sg-type-sg-option-и-sg-dropdown)
* [sg-options](#sg-options)
* [sg-css](#sg-css)
* [sg-format](#sg-format)
* [sg-attributes](#sg-attributes)
* [sg-value](#sg-value)
* [sg-click](#sg-click)
* [Лицензия](#лицензия)
# SGModel
SGModel - Быстрая легковесная библиотека-класс для структурирования веб-приложений с помощью биндинг-моделей. Это более быстрый и упрощенный аналог Backbone.js! Библиотека хорошо адаптирована для наследования классов (ES6).
## Основные статические свойства SGModel
### static typeProperties = {...}
Описание типов свойств. Пример кода:
```js
class PlayerBase extends SGModel {
static typeProperties = Object.assign({
position: SGModel.TYPE_OBJECT_NUMBERS,
rotate: SGModel.TYPE_NUMBER
}, SGModel.typeProperties);
//...
}
class Tank extends PlayerBase {
static typeProperties = Object.assign({
armor: SGModel.TYPE_NUMBER,
ammunition1: SGModel.TYPE_NUMBER,
ammunition2: SGModel.TYPE_NUMBER,
ammunition3: SGModel.TYPE_NUMBER
}, PlayerBase.typeProperties);
//...
}
```
#### Поддерживаемые типы свойств:
- `SGModel.TYPE_ANY` - тип свойства по умолчанию
- `SGModel.TYPE_NUMBER` - при установке `null` или пустой строки (`""`) сохраняется значение `null` (как в СУБД)
- `SGModel.TYPE_STRING`
- `SGModel.TYPE_BOOLEAN`
- `SGModel.TYPE_OBJECT` - при изменении хотя бы одного свойства объекта выполняются колбэки заданные методом `.on()`
- `SGModel.TYPE_ARRAY` - при изменении хотя бы одного элемента массива выполняются колбэки заданные методом `.on()`
- `SGModel.TYPE_ARRAY_NUMBERS` - то же что и `SGModel.TYPE_ARRAY`, но значения приводятся к числовому типу
- `SGModel.TYPE_OBJECT_NUMBERS` - то же что и `SGModel.TYPE_OBJECT`, но значения приводятся к числовому типу
- `SGModel.TYPE_VECTOR` - либо число, например, 1234.5, либо объект, например: {x: 1234.5, y: 1234.5}. Этот тип удобен для работы с графическими движками
(!) При проверке изменения значения везде применяется строгая проверка (===).
(!) При получении `undefined` (или то же что и `void 0`) свойство удаляется (`delete this.properties[propName]`)
### static defaultsProperties = {...}
Один из способов задания перечня свойств и их значений по умолчанию при создании экземпляра. Другой способ - использовать метод `defaults()` экземпляра, см. ниже.
Пример кода:
```js
class PlayerBase extends SGModel {
//...
static defaultProperties = {
position: {x: 0, y: 0},
rotate: 0
}
//...
}
class Tank extends PlayerBase {
//...
defaults() {
return SGModel.defaults({
armor: 1000,
ammunition1: 20,
ammunition2: 20,
ammunition3: 5
}, PlayerBase.defaultProperties);
}
//...
}
```
### static options = {...}
Пользовательские настройки. Если заданы, то в при создании экземпляра эти настройки объединяются с настройками options, которые передаются в конструкторе, в this.options.
### static localStorageKey = ""
Если задано не пустое строковое значение, то данные синхронизируются с локальным хранилищем.
Поддержка хранения данных как одного экземпляра класса (single instance), так и нескольких экземпляров: `localStorageKey+"_"+uid`
### static storageProperties = []
Если задан перечень названий свойств, то при выполнении save() записываются только эти свойства! Также эти свойства возвращаются методом getData()
## Свойства и методы экземпляра SGModel
### constructor(props, options)
* `props` - свойства
* `options` - пользовательские настройки
* `options._this` - свойства и методы передающиеся в контекст this созданного экземпляра
### uid
Уникальный числовой идентификатор экземпляра. Генерируется автоматически при создании экземпляра, если не был передан вручную. Также присутствует в свойствах экземпляра: `this.get('uid')`.
Если при создании экземпляра нужно заполнить его данными из локального хранилища localStorage (синхронизировать), то uid необходимо указать заранее при вызове конструктора (в props). Также в этом случае необходимо задать `localStorageKey`.
### initialized
Promise возвращаемый методом initialize()
### changed = false
Если какое-то свойство было изменено, то устанавливается в true. Сбрасывается вручную (в false).
### destroyed = false
Если true, значит экземпляр прошёл процедуру уничтожения destroy()
### defaults()
Один из способов задания перечня свойств и их значений по умолчанию при создании экземпляра.
Этот вариант предпочтителен, когда нужно обратиться к статическим свойствам и методам класса.
Другой способ - использовать `static defaultsProperties = {...}`, см. выше.
### initialize(properties, options) {} // override
Вызывается сразу после создании экземпляра. Переопределяется в классах потомках. Объекты properties и options, переданные в конструкторе, полностью склонированы (включая вложенные объекты)!
### set(name, value, options = void 0, flags = 0, event = void 0, elem = void 0)
Задать значение свойства.
* `name`
* `val`
* `options`
* `precision` - Точность округления чисел
* `previous_value` - Если задано, то используется в качестве предыдущего значения
* `flags` - допустимые флаги:
* `SGModel.FLAG_OFF_MAY_BE` - если при .set() могут быть .off() то нужно передать этот флаг
* `SGModel.FLAG_PREV_VALUE_CLONE` - передавать предыдущее значение (делается тяжёлый clone)
* `SGModel.FLAG_NO_CALLBACKS` - если задано, то колбэки не выполняются
* `SGModel.FLAG_FORCE_CALLBACKS` - выполнить колбеки даже если нет изменений
* `SGModel.FLAG_IGNORE_OWN_SETTER` - игнорировать собственные сеттеры (выполняется стандартный)
* `event` - событие элемента
* `elem` - DOM-элемент вызвавший событие
Возвращает true если свойство было изменено.
### get(name)
Получить значение свойства
### on(name, func, context = void 0, data = void 0, flags = 0)
Задать колбэк на изменение свойства
* `name` - имя свойства или массив имён свойств
* `func` - колбэк
* `context` - если не задано, то передаётся "this" текущего объекта. Для массива имён можно передать массив контекстов
* `data` - если задано, то в колбэке вместо текущего значения (первый элемент в arguments[]) передаётся это значение (data). Для массива имён можно передать массив данных
* `flags` - допустимые флаги:
* `SGModel.FLAG_IMMEDIATELY` - func выполнится сразу
Пример выполнении колбэка и список параметров:
```js
this.on(
["field1", "field2", "field3", "field4"], // один колбэк для нескольких полей
(newValue, oldValue, name)=>{ // name - имя поля, значение которого изменилось
//...
}
);
```
### off(name, func)
Удалить колбэки из списка подписчиков на изменение свойства. Если задан func, то из списка удаляется конкретный колбэк
* `name` - имя свойства или массив имён свойств
* `func` - колбэк
### trigger(name, value = void 0, flags = 0)
Выполнить колбэки, которые выполняются при изменении значения свойства, либо сгенерировать событие
* `name` - имя свойства или имя события
* `value` - параметр события
* `flags` - допустимые флаги:
* `SGModel.FLAG_OFF_MAY_BE` - если при .set() могут быть .off() то нужно передать этот флаг
### save()
Сохраняет данные (в this.properties) в локальное хранилище localStorage.
Если `storageProperties` не задан, то свойства, начинающиеся с символа "_" не записывается в хранилище.
Если `storageProperties` задан, то в хранилище записываются только те свойства, которые указаны в массиве `storageProperties`.
### getData(bDeleteEmpties = false)
Получить объект с properties и значениями. Используется либо данные `storageProperties`, либое берутся свойства без начального символа "_". Флаг `bDeleteEmpties` определяет - будут ли в возвращаемом объекте пустые свойства (пустая строка, `0`, `null`, `undefined`).
### destroy()
Очищает список колбэков и присваивает `destroyed = true`
## Собственные сеттеры в наследуемых классах
Предпочтительнее `.on()` по скорости работы при большом количестве экземпляров класса.
Также используются, если есть базовый класс и класс потомок, где нужно специфическое поведение при изменении свойств.
### static ownSetters = {...}
Список свойств для которых вначале использовать собственные сеттеры. Пример кода см. ниже.
Пример кода:
```js
class PlayerBase extends SGModel {
static typeProperties = {
state: PlayerBase.TYPE_NUMBER
};
static defaultsProperties = {
state: 0
};
//...
}
class Tank extends PlayerBase {
static typeProperties = Object.assign({
state_index: PlayerBase.TYPE_NUMBER
}, PlayerBase.typeProperties);
defaults() {
return SGModel.defaults({
state_index: 0
}, PlayerBase.defaultProperties);
}
// В Tank указываем собственный сеттер для работы со свойством state
static ownSetters = Object.assign({
state: true
}, PlayerBase.ownSetters);
setState(value, flags = 0, options) {
if (this.set("state", value, flags | SGModel.FLAG_IGNORE_OWN_SETTER, options)) {
this.set("state_index", 0);
}
}
}
```
## Поддержка Singleton паттерна в наследуемых классах
### static singleInstance = false
Если true, то возможен только один действующий экземпляр класса.
Пример кода:
```js
class Application extends SGModel {
static singleInstance = true;
static localStorageKey = "app_options";
static APP_STATE1 = 0;
static APP_STATE2 = 0;
//...
constructor(...args) {
super(...args);
window.MyApp = this;
//...
}
defaults() {
return {
state: Application.APP_STATE1
};
}
initialize(properties, options) {
//...
}
}
new Application();
```
### static getInstance(bIgnoreEmpty=false)
Получить указатель на одиночный экземляр класса. Если `bIgnoreEmpty` равен true, то при пустом экземпляре Singleton ошибка игнорируется и возвращается null.
### Статические методы get, set, on, off, save
Проекции на соответствующие методы singleton-экземпляра
### static getProperties()
Возвращает объект со свойствами singleton-экземпляра
## Утилиты используемые в SGModel
### static defaults(dest, ...sources)
Если какого-то свойства в dest не оказалось, то оно при наличии берётся из объектов sources
### static clone(source)
Полное клонирование объекта с вложенными массивами и объектами (используется рекурсия)
### static initObjectByObject(dest, source)
Заполнить значения объекта/массива dest значениями из объекта/массива source (с рекурсией)
### static upperFirstLetter(s)
Сделать первый символ прописным
### static roundTo(value, precision = 0)
Округление числа до заданной точности
# MVVM-паттерн в SGModelView
**SGModelView** - надстройка над **SGModel** позволяющая связать данные в JavaScript с визуальными элементами HTML-документа, используя MVVM-паттерн. Это очень упрощенный аналог KnockoutJS или VueJS.
## Статические свойства экземпляра SGModelView
### static htmlFile = void 0
При наличии значения в this.constructor.htmlFile асинхронно и только один раз для всех экземпляров загружается html-файл. Содержимое файла сохраняется в свойство this.constructor.htmlFileContent, которое может многократно использоваться в дальнейшем.
Пример кода:
```js
export default class MyView1 extends SGModelView {
static htmlFile = '/cmp/views/my-view1.html';
...
initialize(properties, options) {
...
return super.initialize(properties, options); // Внимание! Нужно вызвать родительский метод
}
}
```
### static htmlFileContent = void 0
Сюда сохраняется содержимое загруженного html-файла вьюхи.
### static htmlContainer = void 0
При заданном значении this.constructor.htmlContainer автоматический биндинг экземпляров выполняется в указанный DOM-элемент.
Пример кода:
```js
static htmlContainer = 'body > div.my-class1 main';
```
Также при создании конкретного экземпляра можно задать свой контейнер (переопределить статический), например:
```js
let entity1 = new MyView1(void 0, void 0, { htmlContainer: '#view1' });
```
### static htmlViewId = void 0
При заданном значении автоматически выполняет биндинг в момент инициализации экзмепляра. Ссылка на корневой DOM-элемент вьюхи сохраняется в свойство экземпляра this.eHtmlContainer.
При заданном свойстве this.constructor.htmlFile сначала будет загружен html-код из файла.
Если значение не указано, но при этом указаны htmlFile и htmlContainer, то html-код вьюхи вставляется как последний вложенный DOM-элемент в контейнер.
## Свойства экземпляра SGModelView
### htmlContainer
Селектор контейнера, в который вставляется html-код вьюхи. По умолчанию равен static htmlContainer. Можно переопределить при создании экземпляра в options.
### eHtmlContainer
Корневой DOM-элемент вьюхи.
## Методы экземпляра SGModelView
### bindHTML(root=void 0)
Связать вручную (если не указываются статические свойства htmlContainerId и htmlViewId) модель данных (экземпляр класса `SGModel->SGModelView`) с HTML-документом (его частью, например, с формой). При изменении значений в HTML-элементах автоматически обновляются данные в экземпляре модели и наоборот.
```js
initialize(properties, options)
...
let promise = this.bindHTML("#my_form");
...
return promise;
}
```
## Атрибуты в HTML-документе
### sg-property
Поддерживаются следующие HTML-элементы ввода данных (и типы):
- `INPUT` (text, range, checkbox, radio)
- `SELECT` и `OPTION` (select-one, select-multiple)
- `BUTTON` (button)
Также `sg-property` можно указать на любом другом теге. В этом случае значение будет выводится через innerHTML.
### sg-type, sg-option и sg-dropdown
Для реализации кастомных выпадающих списков выбора значения, реализованных, например, в Bootstrap, нужно задать атрибут `sg-type="dropdown"`. Пример, html-кода:
```html
```
```js
class MyForm extends SGModelView {
initialize(properties, options)
this.set('myitems', [ { value: 1001, title: "One", hint: "Description for one..." }, { value: 2002, title: "Two", hint: "Description for two..."} ]);
return this.bindHTML("#my_form");
}
}
```
### sg-css
Для динамического формирования списка css-классов элемента можно прописать javascript inline-условие прямо в атрибуте `sg-css` тега. Свойства и методы распознаются автоматически. Пример HTML-кода:
```html
- Трудовой договор
- Самозанятый
- Фриланс + Безопасная сделка
- Договор услуг
- Договор подряда
- ИП
4ч.
```
```js
class MyForm extends SGModelView {
static defaultProperties = {
hours: 8
}
/* or
defaults() {
return {
hours: 8
};
}*/
...
someMethod() {
this.set("hours", 4);
}
}
```
или
```html
4ч.
```
```js
class MyForm extends SGModelView {
...
cssDangerOrSuccess(property) {
let value = this.get(property);
if (value == 0) return ""; else return value < 0 ? "text-success" : "text-danger";
}
}
```
При этом *some-base-class1* и *some-base-class2* не будут затронуты при вычислении списка css-классов!
Важно! Для ускорения работы используется упрощеный вариант парсера - все операторы должны быть разделены хотя бы одним пробелом!
### sg-format
Для форматирования значения можно использовать атрибут `sg-format` в значении которого имя функции обработчика. Пример HTML и Javascript-кода:
```html
1 000 000 руб./год
```
```js
class MyForm extends SGModelView {
...
getNumThinsp(value) {
return (''+value.toLocaleString()).replace(/\s/g, " ");
}
}
```
### sg-attributes
Для задания первоначальных значений атрибутов элемента можно использовать атрибут `sg-attributes`. В текущей версии реализована только инициализация атрибутов. Пример HTML и Javascript-кода:
```html
Short text...
Description...
```
```js
class MyForm extends SGModelView {
...
getSomeSrcA() {
let src = 'https://site.ru/picture.png';
//...
return src;
}
getSomeTitleA() {
return 'Some title';
}
getSomeTitleB(param1) {
return this.get(param1);
}
getSomeTitleC(value1, value2) {
value1 = +value1;
//...
return 'color: #f673c9; font-size: 15pt';
}
}
```
В целях упрощения работы парсера, значения параметров передаются в методы в HTML-шаблоне в одинарных кавычках (в том числе и для чисел) !
### sg-value
Для задания первоначального innerHTML элемента можно использовать атрибут `sg-value`. В текущей версии реализована только инициализация innerHTML. Пример HTML и Javascript-кода:
```html
loading...
loading...
loading...
loading...
```
```js
class MyForm extends SGModelView {
const STAT_PROP_NAME1 = 'value1'; // Можно вывести значение статического свойства
getSomeValue(a, b) {
return 'Some value';
}
}
```
В целях упрощения работы парсера, значения параметров передаются в методы в HTML-шаблоне в одинарных кавычках (в том числе и для чисел) !
### sg-click
Для назначения обработчика onclick можно использовать атрибут `sg-click` в значении которого имя функции обработчика. Пример HTML и Javascript-кода:
```html
```
```js
class MyForm extends SGModelView {
...
sendEmail(event) {
...
}
}
```
# Лицензия
**SGModel и SGModelView распространяются под [MIT License](http://opensource.org/licenses/MIT)**