HM-30. Добавлен класс Компонент, поправлены api, внедрен тестовый ком… (#5)

This commit is contained in:
Nikolay
2020-07-07 00:35:36 +03:00
committed by GitHub
parent 06d5ed1522
commit 6dabdd42c0
20 changed files with 580 additions and 95 deletions

View File

@ -1,3 +1,7 @@
{ {
"presets": ["@babel/preset-env"] "presets": ["@babel/preset-env"],
"plugins": [
"@babel/plugin-syntax-class-properties",
"@babel/plugin-proposal-class-properties"
]
} }

6
.eslintignore Normal file
View File

@ -0,0 +1,6 @@
/node_modules
/dist
/out
.vscode
.idea
webpack.config.js

View File

@ -1,4 +1,5 @@
module.exports = { {
"parser": "babel-eslint",
"env": { "env": {
"browser": true, "browser": true,
"es2020": true "es2020": true
@ -9,5 +10,8 @@ module.exports = {
"sourceType": "module" "sourceType": "module"
}, },
"rules": { "rules": {
"no-console": "warn",
"semi": "error",
"quotes": ["error", "single"]
} }
}; }

2
.gitignore vendored
View File

@ -3,4 +3,4 @@
/dist /dist
/node_modules /node_modules
.DS_Store **.DS_Store

150
package-lock.json generated
View File

@ -1638,6 +1638,20 @@
} }
} }
}, },
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
"integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/parser": "^7.7.0",
"@babel/traverse": "^7.7.0",
"@babel/types": "^7.7.0",
"eslint-visitor-keys": "^1.0.0",
"resolve": "^1.12.0"
}
},
"babel-loader": { "babel-loader": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz",
@ -2551,6 +2565,15 @@
"integrity": "sha512-ufHZNtMaDEuRBpTbqD93tIQnngmJ+oBknjvr0IbFympSdtFpAUFmNv4mVKbb53qltxFx0nK3iy32S9AqkLzUNA==", "integrity": "sha512-ufHZNtMaDEuRBpTbqD93tIQnngmJ+oBknjvr0IbFympSdtFpAUFmNv4mVKbb53qltxFx0nK3iy32S9AqkLzUNA==",
"dev": true "dev": true
}, },
"catharsis": {
"version": "0.8.11",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz",
"integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
}
},
"caw": { "caw": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz",
@ -6137,6 +6160,51 @@
"esprima": "^4.0.0" "esprima": "^4.0.0"
} }
}, },
"js2xmlparser": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz",
"integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==",
"dev": true,
"requires": {
"xmlcreate": "^2.0.3"
}
},
"jsdoc": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.4.tgz",
"integrity": "sha512-3G9d37VHv7MFdheviDCjUfQoIjdv4TC5zTTf5G9VODLtOnVS6La1eoYBDlbWfsRT3/Xo+j2MIqki2EV12BZfwA==",
"dev": true,
"requires": {
"@babel/parser": "^7.9.4",
"bluebird": "^3.7.2",
"catharsis": "^0.8.11",
"escape-string-regexp": "^2.0.0",
"js2xmlparser": "^4.0.1",
"klaw": "^3.0.0",
"markdown-it": "^10.0.0",
"markdown-it-anchor": "^5.2.7",
"marked": "^0.8.2",
"mkdirp": "^1.0.4",
"requizzle": "^0.2.3",
"strip-json-comments": "^3.1.0",
"taffydb": "2.6.2",
"underscore": "~1.10.2"
},
"dependencies": {
"escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"dev": true
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
}
}
},
"jsesc": { "jsesc": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@ -6211,6 +6279,15 @@
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true "dev": true
}, },
"klaw": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
"integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.9"
}
},
"leven": { "leven": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@ -6236,6 +6313,15 @@
"type-check": "~0.4.0" "type-check": "~0.4.0"
} }
}, },
"linkify-it": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
"dev": true,
"requires": {
"uc.micro": "^1.0.1"
}
},
"load-json-file": { "load-json-file": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
@ -6406,6 +6492,31 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"markdown-it": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz",
"integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"entities": "~2.0.0",
"linkify-it": "^2.0.0",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
}
},
"markdown-it-anchor": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
"integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
"dev": true
},
"marked": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz",
"integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==",
"dev": true
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -6424,6 +6535,12 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"mdurl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
"integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=",
"dev": true
},
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -8031,6 +8148,15 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true "dev": true
}, },
"requizzle": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz",
"integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
}
},
"resolve": { "resolve": {
"version": "1.17.0", "version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
@ -9106,6 +9232,12 @@
"string-width": "^3.0.0" "string-width": "^3.0.0"
} }
}, },
"taffydb": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz",
"integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=",
"dev": true
},
"tapable": { "tapable": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@ -9382,6 +9514,12 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true "dev": true
}, },
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==",
"dev": true
},
"unbzip2-stream": { "unbzip2-stream": {
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
@ -9406,6 +9544,12 @@
} }
} }
}, },
"underscore": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
"integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==",
"dev": true
},
"unicode-canonical-property-names-ecmascript": { "unicode-canonical-property-names-ecmascript": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
@ -10223,6 +10367,12 @@
"async-limiter": "~1.0.0" "async-limiter": "~1.0.0"
} }
}, },
"xmlcreate": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz",
"integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==",
"dev": true
},
"xtend": { "xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -20,7 +20,10 @@
"homepage": "https://github.com/vigdorov/storage-service-ui#readme", "homepage": "https://github.com/vigdorov/storage-service-ui#readme",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.10.4", "@babel/core": "^7.10.4",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-syntax-class-properties": "^7.10.4",
"@babel/preset-env": "^7.10.4", "@babel/preset-env": "^7.10.4",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.6.0", "css-loader": "^3.6.0",
@ -28,6 +31,7 @@
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0", "html-webpack-plugin": "^4.3.0",
"image-webpack-loader": "^6.0.0", "image-webpack-loader": "^6.0.0",
"jsdoc": "^3.6.4",
"mini-css-extract-plugin": "^0.9.0", "mini-css-extract-plugin": "^0.9.0",
"url-loader": "^4.1.0", "url-loader": "^4.1.0",
"webpack": "^4.43.0", "webpack": "^4.43.0",

BIN
src/.DS_Store vendored

Binary file not shown.

View File

@ -1,9 +1,16 @@
import {v4 as uuidv4} from 'uuid';
import StorageServiceApi from './StorageServiceAPI'; import StorageServiceApi from './StorageServiceAPI';
/**
* @typedef Element
* @type {object}
* @property {string} _id - уникальный id элемента
*/
/** /**
* Класс создания api для списков данных * Класс создания api для списков данных
* @class * @class
* @public
*/ */
class StorageListApi { class StorageListApi {
/** /**
@ -16,33 +23,47 @@ class StorageListApi {
/** /**
* @private * @private
*
* @param {Array.<Element>} list - список элементов по которым осуществялется поиск
* @param {string} _id - _id искомого элемента * @param {string} _id - _id искомого элемента
* *
* @returns {number} - Возвращает индекс искомого эллемента по _id
* @returns {Promise<Object, string>}
*/ */
async _findIndex(_id) { _findIndex = (list, _id) => {
const list = await this.request();
return list.findIndex(item => item._id === _id); return list.findIndex(item => item._id === _id);
} }
/** /**
* @public * @private
* @returns {Promise} - возвращает все элементы списка *
* @param {Array<Element>} list - новый список элементов
*
* @returns {Promise<Array<Element>>} - возвращает обновленный список элементов
*/ */
async request() { _updateList = async (list) => {
return await this.api.createOrUpdate(this.key, list);
}
/**
* @public
*
* @returns {Promise<Array<Element>>} - возвращает все элементы списка
*/
request = async () => {
const data = await this.api.find(this.key); const data = await this.api.find(this.key);
return (data && data.value) || []; return (data && data.value) || [];
} }
/** /**
* @public
*
* @param {string} _id - _id искомого элемента списка * @param {string} _id - _id искомого элемента списка
* *
* @returns {Promise} - возвращает элемент списка или генерит ошибку * @returns {Promise<Element>} - возвращает элемент списка или генерит ошибку
*/ */
async find(_id) { find = async (_id) => {
const list = await this.request(); const list = await this.request();
const findIndex = LocalStorageListApi.findIndex(list, _id); const findIndex = this._findIndex(list, _id);
if (findIndex === -1) { if (findIndex === -1) {
throw new Error(`Not Found _id: ${_id}`); throw new Error(`Not Found _id: ${_id}`);
} }
@ -50,51 +71,58 @@ class StorageListApi {
} }
/** /**
* @public
*
* @param {Object} data - элемент списка * @param {Object} data - элемент списка
* *
* @returns {Promise} - Возвращает вновь созданный элемент с уникальным полем _id * @returns {Promise<Element>} - Возвращает вновь созданный элемент с уникальным полем _id
*/ */
async create(data) { create = async (data) => {
const list = await this.request(); const list = await this.request();
const _id = uuidv4(); const _id = uuidv4();
const newData = { const newData = {
...data, ...data,
_id, _id,
}; };
await this.api.createOrUpdate(this.key, list.concat(newData)); await this._updateList(list.concat(newData));
return newData; return newData;
} }
/** /**
* @param {Object} data - элемент списка * @public
* @param {string} data._id - наличие _id обязательно
* *
* @returns {Promise} - Возвращает обновленный элемент списка * @param {Element} data - элемент списка
*
* @returns {Promise<Element>} - Возвращает обновленный элемент списка
*/ */
async update(data) { update = async (data) => {
const list = await this.request(); const list = await this.request();
const findIndex = LocalStorageListApi.findIndex(list, data._id); const findIndex = this._findIndex(list, data._id);
if (findIndex === -1) { if (findIndex === -1) {
throw new Error(`Not Found _id: ${data._id}`); throw new Error(`Not Found _id: ${data._id}`);
} }
list.splice(findIndex, 1, data); list.splice(findIndex, 1, data);
await this.api.createOrUpdate(this.key, list); await this._updateList(list);
return data; return data;
} }
/** /**
* @public
*
* @param {string} _id - _id удаляемого элемента * @param {string} _id - _id удаляемого элемента
* *
* @returns {Promise} - Возвращает _id удаленного элемента или ошибку * @returns {Promise<string>} - Возвращает _id удаленного элемента или ошибку
*/ */
async remove(_id) { remove = async (_id) => {
const list = await this.request(); const list = await this.request();
const findIndex = LocalStorageListApi.findIndex(list, _id); const findIndex = this._findIndex(list, _id);
if (findIndex === -1) { if (findIndex === -1) {
throw new Error(`Not Found _id: ${_id}`); throw new Error(`Not Found _id: ${_id}`);
} }
list.splice(findIndex, 1); list.splice(findIndex, 1);
await this.api.createOrUpdate(this.key, list); await this._updateList(list);
return _id; return _id;
} }
} }
export default StorageListApi;

View File

@ -1,25 +1,64 @@
import axios from 'axios';
import {API_URL, ENDPOINT} from './consts'; import {API_URL, ENDPOINT} from './consts';
/**
* @typedef Store
* @type {object}
* @property {string} key
* @property {unknown} value
*/
/**
* Класс для работы с store-service api
* @class
*/
class StorageServiceApi { class StorageServiceApi {
URL = `${API_URL}${ENDPOINT}`; URL = `${API_URL}${ENDPOINT}`;
async request() { /**
* @public
*
* @returns {Promise<Array<Store>>} - Возвращает список всех пар ключ-значение
*/
request = async () => {
const {data} = await axios.get(this.URL); const {data} = await axios.get(this.URL);
return data; return data;
} }
async find(key) { /**
const {data} = await axios.get(`${this.URL}/${key}`) * @public
*
* @param {string} key - ключ хранилища в api
*
* @returns {Promise<unknown>} - Возвращает значение по указанному ключу
*/
find = async (key) => {
const {data} = await axios.get(`${this.URL}/${key}`);
return data; return data;
} }
async createOrUpdate(key, value) { /**
const {data} = await axios.post(this.URL, {key, value}) * @public
*
* @param {string} key - ключ хранилища в api
* @param {unknown} value - значение, которое будет хранится под указанным ключом
*
* @returns {Promise<unknown>} - возвращает вновь созданный элемент
*/
createOrUpdate = async (key, value) => {
const {data} = await axios.post(this.URL, {key, value});
return data; return data;
} }
async remove(key) { /**
* @public
*
* @param {string} key - ключ хранилища api
*
* @returns {Promise<string>} - возвращает 'ok', если удаление было выполнено
*/
remove = async (key) => {
const {data} = await axios.delete(`${this.URL}/${key}`); const {data} = await axios.delete(`${this.URL}/${key}`);
return data; return data;
} }

View File

@ -1,5 +1,15 @@
/**
* @type {string}
*/
export const API_URL = 'http://vigdorov.ru:4001'; export const API_URL = 'http://vigdorov.ru:4001';
export const ENDPOINT = '/store';
export const API_KEYS = {
/**
* @type {string}
*/
export const ENDPOINT = '/store';
/**
* @type {Object<string, string>}
*/
export const API_KEYS = {
}; };

View File

@ -1,11 +1,26 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title> <title>Document</title>
</head> </head>
<body> <body>
<!-- Шаблон Модального окна -->
<template id="test-modal">
<div class="TestModal">
<div class="TestModal__shadow"></div>
<div class="TestModal__window"></div>
</div>
</template>
<!-- Шаблон кнопки -->
<template id="test-button">
<button type="button" class="btn btn-primary">Жми меня для теста</button>
</template>
</body> </body>
</html> </html>

View File

@ -1,3 +1,14 @@
import './app.css'; import './app.css';
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap'; import 'bootstrap';
// ! TODO: 5-14 строчки удалить, после теста компонента
import TestModal from './components/test-modal';
import TestButton from './components/test-button';
const testModal = new TestModal();
const testButton = new TestButton();
testButton.subscribe('click', () => {
testModal.show();
});

View File

@ -0,0 +1,118 @@
/**
* @typedef Listener
* @type {Object}
* @property {Node} element
* @property {string} eventName
* @property {function} listener
*/
/**
* @function EventListener
* @param {unknown[]} args - аргументы функции
*/
/**
* @typedef Events
* @type {Object<string, EventListener[]>}
*/
/**
* Класс для создания компонентов приложения. Необходим для нследования.
* @class
*/
class Component {
/**
* @private
* @type {Listener[]}
*/
_listeners;
/**
* @private
* @type {Events}
*/
_events;
/**
* @public
* @type {Node} - корневой элемент компонента
*/
mainNode;
/**
* @param {string} mainNodeSelector - селектор, с помощью которого извлекается шаблон компонента
* @param {Node} parentNode - родительский Node, в который следует положить созданный элемент
*/
constructor(mainNodeSelector, parentNode) {
/**
* @type {DocumentFragment}
*/
const content = document.querySelector(mainNodeSelector).content;
if (content.children.length > 1) {
const message = '<template> должен содержать только один элемент children';
alert(message);
throw new Error(message);
}
this.mainNode = content.firstElementChild.cloneNode(true);
parentNode.appendChild(this.mainNode);
this._listeners = [];
this._events = {};
}
/**
* Метод добавления обработчиков события на Node'ы компонента
* @public
*
* @param {Node} element - элемент, на который будет навешен обработчик
* @param {string} eventName - событие, на которое будет реагировать обработчик
* @param {function} listener - обработчик события
*/
addEventListener = (element, eventName, listener) => {
element.addEventListener(eventName, listener);
this._listeners.push({element, eventName, listener});
}
/**
* Метод подписки на события компонента
* @public
*
* @param {string} eventName - событие компонента, на которое будет реагировать обработчик
* @param {EventListener} listener - обработчик события
*/
subscribe = (eventName, listener) => {
const listeners = this._events[eventName] || [];
this._events[eventName] = [
...listeners,
listener,
];
}
/**
* Метод генерирует событие
* @public
*
* @param {string} eventName - событие, которое необходимо сгенерировать
* @param {unknown[]} args - аругемнты, который необходимо передать обработчикам события
*/
next = (eventName, ...args) => {
const listeners = this._events[eventName];
listeners.forEach(listener => {
listener(...args);
});
}
/**
* Метод уничтожения компонента. Удаляет элемент из верстки, снимает обработчики и очищает подписки
* @public
*/
destroy = () => {
this._listeners.forEach(({element, eventName, listener}) => {
element.removeEventListener(eventName, listener);
});
this.mainNode.remove();
this._listeners = [];
this._events = {};
}
}
export default Component;

View File

@ -0,0 +1,3 @@
import Component from './Component';
export default Component;

View File

@ -0,0 +1,15 @@
// ! TODO: Удалить, необходим для примера работы с компонентами
import Component from '../component';
class TestButton extends Component {
constructor() {
super('#test-button', document.body);
this.addEventListener(this.mainNode, 'click', (evt) => {
this.next('click', evt);
});
}
}
export default TestButton;

View File

@ -0,0 +1,3 @@
import TestButton from './TestButton';
export default TestButton;

View File

@ -0,0 +1,31 @@
.TestModal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.TestModal_hide {
display: none;
}
.TestModal__shadow {
position: absolute;
background-color: rgba(0, 0, 0, 0.4);
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 4;
}
.TestModal__window {
width: 300px;
height: 200px;
background-color: white;
z-index: 5;
}

View File

@ -0,0 +1,38 @@
// ! TODO: Удалить, необходим для примера работы с компонентами
import Component from '../component';
import './TestModal.css';
const MAIN = 'TestModal';
const CN = {
SHADOW: `${MAIN}__shadow`,
WINDOW: `${MAIN}__window`,
HIDE_MODAL: `${MAIN}_hide`,
};
class TestModal extends Component {
constructor() {
super('#test-modal', document.body);
this.shadow = this.mainNode.querySelector(`.${CN.SHADOW}`);
this.window = this.mainNode.querySelector(`.${CN.WINDOW}`);
this.addEventListener(this.shadow, 'click', (evt) => {
if (evt.target === this.shadow) {
this.hide();
}
});
this.hide();
}
show = () => {
this.mainNode.classList.remove(CN.HIDE_MODAL);
}
hide = () => {
this.mainNode.classList.add(CN.HIDE_MODAL);
}
}
export default TestModal;

View File

@ -0,0 +1,3 @@
import TestModal from './TestModal';
export default TestModal;

View File

@ -4,57 +4,60 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin'); const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = { module.exports = {
entry: './src/app.js', entry: './src/app.js',
output: { output: {
filename: 'main.js', filename: 'script.js',
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
}, },
devServer: { devServer: {
compress: true, compress: true,
port: 9000, port: 9000,
open: true open: true
}, },
plugins: [ plugins: [
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
filename: 'index.html', filename: 'index.html',
template: './src/app.html' template: './src/app.html'
}), }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: 'style.css', filename: 'style.css',
}), }),
],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
{
loader: 'image-webpack-loader',
options: {
quality: 80
},
},
{
loader: 'url-loader',
options: {
limit: 200000,
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader',
],
},
], ],
}, module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
{
loader: 'image-webpack-loader',
options: {
quality: 80
},
},
{
loader: 'url-loader',
options: {
limit: 200000,
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader',
],
},
],
},
}; };