diff --git a/.gitignore b/.gitignore index 0db216b..36dfce7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ npm-debug.log node_modules +.idea +app.bundle.js + +frontend/dist/mvc.bundle.js diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e7e9d11 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml diff --git a/api/config/database.php b/api/config/database.php index 75b77c4..2be7c91 100644 --- a/api/config/database.php +++ b/api/config/database.php @@ -6,10 +6,10 @@ namespace Config; -class Database +class Database { const HOST = 'localhost'; const NAME = 'market'; - const USERNAME = 'root'; - const PASSWORD = ''; -} \ No newline at end of file + const USERNAME = 'admin'; + const PASSWORD = 'mysql'; +} diff --git a/api/controllers/catalog.php b/api/controllers/catalog.php index 85c6cf8..6e4199f 100644 --- a/api/controllers/catalog.php +++ b/api/controllers/catalog.php @@ -2,6 +2,8 @@ /** * @file : controllers/catalog.php * @author : Leonid Vinikov + * + * @desc: : Catalog Controller */ namespace Controllers; diff --git a/api/index.php b/api/index.php index 322095b..6c5cb66 100644 --- a/api/index.php +++ b/api/index.php @@ -35,7 +35,7 @@ }, E_ALL); -//header('Access-Control-Allow-Origin: http://leonid.viewdns.net:8888'); +header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Headers:X-Request-With, Content-Type'); header('Access-Control-Allow-Credentials: true'); // for cookies diff --git a/market.sql b/database.sql similarity index 100% rename from market.sql rename to database.sql diff --git a/dev/document/index.html b/dev/document/index.html new file mode 100644 index 0000000..0cad806 --- /dev/null +++ b/dev/document/index.html @@ -0,0 +1,26 @@ + + + + + Dev + + + + + + +
+ Root +
+ + + \ No newline at end of file diff --git a/dev/document/index.js b/dev/document/index.js new file mode 100644 index 0000000..9ee0917 --- /dev/null +++ b/dev/document/index.js @@ -0,0 +1,88 @@ +import Component from './modules/component'; + +class Container extends Component { + initialize() { + this.events = { + onRender: () => {}, + } + + super.initialize(); + } + + afterRender() { + super.afterRender(); + + this.events.onRender(); + } + + set( child ) { + this.child = child; + this.child.render(); + } + /** + * Function on() : Declare event callback + * + * @param {'render'} event + * @param {{function()}} callback + */ + on(event, callback) { + switch (event) { + case 'render': { + this.events.onRender = callback; + } break; + + default: { + alert(`${this.constructor.name}::on() -> invalid event type: '${event}'`); + } + } + } +} + +class Page extends Container { + +} + +class PageContainer extends Container { + +} + +class CatalogPage extends Page { + onClick = () => { + alert( 'page home'); + }; +} + + + +class App { + initialize() { + this.elements = { + root: document.getElementById('root'), + }; + + this.app = new Component(this.elements.root, `
App
`); + + this.pageContainer = new PageContainer( this.app, '
Page Container
' ); + + this.pageContainer.on('render', this.onPageContainerRender.bind( this ) ); + + this.homePage = new CatalogPage( this.pageContainer, '
Home Page
') + + this.app.render(); + + this.pageContainer.render(); + + this.pageContainer.set( this.homePage ); + + } + + onPageContainerRender() { + console.log('onPageContainerRender'); + } +} + +const app = new App(); + +app.initialize(); + + diff --git a/dev/mvc/index.html b/dev/mvc/index.html new file mode 100644 index 0000000..06115e9 --- /dev/null +++ b/dev/mvc/index.html @@ -0,0 +1,26 @@ + + + + + Dev + + + + + + +
+ Root +
+ + + diff --git a/dev/mvc/index.js b/dev/mvc/index.js new file mode 100644 index 0000000..e5d5351 --- /dev/null +++ b/dev/mvc/index.js @@ -0,0 +1,76 @@ +import * as Core from '../../frontend/core/index.js'; +import Modules from '../../frontend/modules/index.js'; + +class PageContainer extends Core.Container { + +} + +class Page extends Core.Container { + +} + +class CatalogPage extends Page { + onClick = () => { + alert( 'page home'); + }; +} + + +class Item extends Modules.Component { + template() { + return '' + + '
' + + '

Item

' + + '' + + ' { + alert('Hello from item class view'); + } +} + + +class App { + initialize() { + this.elements = { + root: document.getElementById('root'), + }; + + this.app = new Core.Element(this.elements.root, `
App
`); + + this.pageContainer = new PageContainer( this.app, '
Page Container
' ); + + this.pageContainer.on('render', this.onPageContainerRender.bind( this ) ); + + this.homePage = new CatalogPage( this.pageContainer, '
Home Page
') + + this.app.render(); + + this.pageContainer.set( this.homePage ); + this.pageContainer.render(); + } + + onPageContainerRender() { + console.log( 'onPageContainerRender' ); + + const item = new Item( this.homePage ); + item.render(); + } + +} + +const app = new App(); + +app.initialize(); + + diff --git a/js/api/cart.js b/frontend/api/cart.js similarity index 69% rename from js/api/cart.js rename to frontend/api/cart.js index f250759..dcd8a39 100644 --- a/js/api/cart.js +++ b/frontend/api/cart.js @@ -3,16 +3,16 @@ * @author: Leonid Vinikov */ -import { Http } from './api.js'; +import { Http } from 'API'; -import Modules from '../modules/modules.js'; -import Services from '../services/services.js'; +import Modules from 'MODULES'; +import Services from 'SERVICES'; export default class Cart { /** - * Function constructor() Create Cart Api - * + * Function constructor() Create Cart Api + * * @param {Http} http */ constructor(http) { @@ -20,14 +20,14 @@ export default class Cart { this.logger.setOutputHandler(Services.Terminal.onOutput); this.logger.startWith({ http }); - + this.http = http; } /** * Function get() : Get cart - * - * @param {{function()}} callback + * + * @param {{function()}} callback */ get(callback) { this.logger.startWith({ callback }); @@ -37,10 +37,10 @@ export default class Cart { /** * Function addItem() : Add item to cart - * - * @param {{function()}} callback - * @param {number} id - * @param {number} amount + * + * @param {{function()}} callback + * @param {number} id + * @param {number} amount */ addItem(callback, id, amount = 1) { this.logger.startWith({ callback, id, amount }); @@ -52,9 +52,9 @@ export default class Cart { /** * Function removeItem() : Remove item from cart - * - * @param {{function()}} callback - * @param {number} id + * + * @param {{function()}} callback + * @param {number} id */ removeItem(callback, id) { this.logger.startWith({ callback, id }); diff --git a/js/api/catalog.js b/frontend/api/catalog.js similarity index 78% rename from js/api/catalog.js rename to frontend/api/catalog.js index edb7e34..eb41a89 100644 --- a/js/api/catalog.js +++ b/frontend/api/catalog.js @@ -3,16 +3,16 @@ * @author: Leonid Vinikov */ -import { Http } from './api.js'; +import { Http } from 'API'; -import Modules from '../modules/modules.js'; -import Services from '../services/services.js'; +import Modules from 'MODULES'; +import Services from 'SERVICES'; export default class Catalog { /** - * Function constructor() Create Catalog Api - * + * Function constructor() Create Catalog Api + * * @param {Http} api */ @@ -29,9 +29,9 @@ export default class Catalog { /** * Function get() : Get catalog from the server - * - * @param {{function()}} callback - * @param {number} page + * + * @param {{function()}} callback + * @param {number} page */ get(callback, page = 0) { this.logger.startWith({ callback, page }); @@ -47,9 +47,9 @@ export default class Catalog { /** * Function getById() : Return product with specific id's - * - * @param {{function()}} callback - * @param {number[]} ids + * + * @param {{function()}} callback + * @param {number[]} ids */ getByIds(callback, ids) { this.logger.startWith({ callback, ids }); @@ -66,8 +66,8 @@ export default class Catalog { /** * Function getLocalProductById() : Get product from local catalog - * - * @param {number} id + * + * @param {number} id * @return {{}|null} */ getLocalProductById(id) { @@ -85,4 +85,4 @@ export default class Catalog { return null; } -} \ No newline at end of file +} diff --git a/js/api/http.js b/frontend/api/http.js similarity index 70% rename from js/api/http.js rename to frontend/api/http.js index aeed567..b634194 100644 --- a/js/api/http.js +++ b/frontend/api/http.js @@ -1,19 +1,19 @@ /** * @file: js/api/http.js * @author: Leonid Vinikov - * @description: inseatd of using jquery ajax, i choosed this. + * @description: instead of using jquery ajax, i choose this. */ -import Modules from '../modules/modules.js'; -import Services from '../services/services.js'; +import Modules from 'MODULES'; +import Services from 'SERVICES'; export default class Http { /** * Function constructor() : Create API - * - * @param {string} apiBaseUrl + * + * @param {string} apiBaseUrl */ - constructor(apiBaseUrl = 'http://localhost:8080/api/?cmd=') { + constructor(apiBaseUrl = 'http://localhost') { this.logger = new Modules.Logger('API.' + this.constructor.name, true); this.logger.setOutputHandler(Services.Terminal.onOutput); @@ -24,12 +24,12 @@ export default class Http { /** * Function fetch() : fetch api - * - * @param {string} path - * @param {string} method - * @param {{}} body - * - * @return {any} + * + * @param {string} path + * @param {string} method + * @param {{}} body + * + * @return {*} */ async _fetch(path, method, body = null) { this.logger.startWith({ path, method, body }); @@ -52,10 +52,18 @@ export default class Http { // since i made it async function const response = await fetch(this.apiBaseUrl + path, params); - const data = await response.json(); + let data = undefined; + + try { + data = await response.json(); + } catch ( e ) { + console.error( e ); + + return false; + } this.logger.recv({ path }, data); - + if (data.error) { data.message = this.translateError(data.message); @@ -63,17 +71,17 @@ export default class Http { throw data.message; } } - + return data; } /** * Function translateError() : Used to translate server error. - * - * @todo Function should be exported to api.js - * - * @param {({}|string)} message - * + * + * TODO Function should be exported to api.js + * + * @param {({}|string)} message + * * @return {string} */ translateError(message) { @@ -95,10 +103,10 @@ export default class Http { /** * Function get() : Send get request - * - * @param {string} path - * - * @return {any} + * + * @param {string} path + * + * @return {*} */ get(path) { this.logger.startWith({ path }); @@ -107,16 +115,16 @@ export default class Http { } /** - * Funciton post() : Send post request - * - * @param {string} path - * @param {{}} params - * - * @return {any} + * Function post() : Send post request + * + * @param {string} path + * @param {{}} params + * + * @return {*} */ post(path, params) { this.logger.startWith({ path, params }); return this._fetch(path, 'post', params); } -} \ No newline at end of file +} diff --git a/js/api/api.js b/frontend/api/index.js similarity index 100% rename from js/api/api.js rename to frontend/api/index.js diff --git a/js/api/websocket.js b/frontend/api/websocket.js similarity index 93% rename from js/api/websocket.js rename to frontend/api/websocket.js index f0f4dcb..653c5ec 100644 --- a/js/api/websocket.js +++ b/frontend/api/websocket.js @@ -1,18 +1,17 @@ /** * @file: js/api/websocket.js * @author: Leonid Vinikov - * @description: + * @description: */ -import Modules from '../modules/modules.js'; -import Services from '../services/services.js'; +import Modules from 'MODULES'; +import Services from 'SERVICES'; /** * @typedef {"open" | "message" | "close" | "error"} API_Websocket_Events */ export default class Websocket { - constructor(address, port, autoInit = false) { this.logger = new Modules.Logger('API.' + this.constructor.name, true); this.logger.setOutputHandler(Services.Terminal.onOutput); @@ -20,17 +19,17 @@ export default class Websocket { this.logger.startWith({ address, port, autoInit }); this.binds = []; - + this.webSocket = null; this.connectionString = `ws://${address}:${port}/`; this.logger.debug(`connection string: '${this.connectionString}'`); - if (autoInit) this.initalize(); + if (autoInit) this.initialize(); } - initalize() { + initialize() { this.logger.startEmpty(); this.webSocket = new WebSocket(this.connectionString); @@ -122,4 +121,4 @@ export default class Websocket { this.webSocket.close(); } -} \ No newline at end of file +} diff --git a/js/app.js b/frontend/app.js similarity index 59% rename from js/app.js rename to frontend/app.js index fbe4f50..b136619 100644 --- a/js/app.js +++ b/frontend/app.js @@ -1,211 +1,228 @@ -/** - * @file: js/app.js - * @author: Leonid Vinikov - * @description: Main File - */ - -import API from './api/api.js'; -import Modules from './modules/modules.js'; -import Services from './services/services.js'; -import Components from './components/components.js'; -import Pages from './pages/pages.js'; - -class App { - /** - * Function constructor() : Create App - */ - constructor() { - Services.Terminal.initalize(); - - this.logger = new Modules.Logger(this, true); - this.logger.setOutputHandler(Services.Terminal.onOutput); - - this.logger.startEmpty(); - - const remoteAdress = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + '/api/?cmd=' - - const http = new API.Http(remoteAdress); - - this.apis = { - catalog: new API.Catalog(http), - cart: new API.Cart(http), - } - - this.elements = { - header: { - logo: $('header #logo'), - toggler: $('header #toggler'), - - cart: $('header #toggler .cart'), - amount: $('header #toggler .amount'), - spinner: $('header #toggler .spinner') - }, - - sidebar: { - self: $('#sidebar'), - closeButton: $('#sidebar #close'), - }, - - overlay: $('#overlay'), - - sections: { - main: $("section.main") - } - } - - this.container = new Modules.PageContainer(this.elements.sections.main); - - this.container.ready(this._onContainerReady.bind(this)); - - this.pages = { - catalog: new Pages.Catalog(this.apis.catalog), - checkout: new Pages.Checkout() - } - } - - /** - * Function initialize() : Initialize App - */ - initialize() { - this.logger.startEmpty(); - - const { header, overlay, sidebar } = this.elements; - - overlay.click(() => this.sidebarToggle(false)); - - header.toggler.click(() => this.sidebarToggle(true)); - - header.logo.click(() => this.container.set(this.pages.catalog)); - - sidebar.closeButton.click(() => this.sidebarToggle(false)); - - this.container.set(this.pages.catalog); - } - - /** - * Function _onContainerReady() : Called when container ready. - * - * @param {Modules.Page} pageModule - */ - _onContainerReady(pageModule) { - this.logger.startWith({ pageModule: pageModule.constructor.name }); - - if (pageModule instanceof Pages.Catalog) { - this.pages.catalog.on('productAdd', this._onCatalogProductAdd.bind(this)); - - if (! this.cart) { - this.cart = new Components.Cart(this.apis.cart, this.apis.catalog); - - this.cart.on('get', this._onCartGet.bind(this)); - this.cart.on('received', this._onCartReceived.bind(this)); - this.cart.on('amountChange', this._onCartAmountChange.bind(this)); - this.cart.on('emptyState', this._onCartEmptyState.bind(this)); - this.cart.on('checkout', this._onCartCheckout.bind(this)); - - this.cart.render(this.elements.sidebar.self[0]); - } - } - } - - /** - * Function _onCartGet() : Called on request cart from the server - */ - _onCartGet() { - this.logger.startEmpty(); - - const { spinner } = this.elements.header; - - spinner.show(); - } - - /** - * Function _onCartReceived() : Called after cart received - */ - _onCartReceived() { - this.logger.startEmpty(); - - const { cart, spinner } = this.elements.header; - - cart.show(); - spinner.hide(); - } - - /** - * Function _onCartAmountChange() : Called on cart amount change. - * - * @param {Number} count - */ - _onCartAmountChange(count) { - this.logger.startWith({ count }); - - const { amount } = this.elements.header; - - amount.html(count); - } - - /** - * Function _onCartEmptyState() : Called on cart empty state change (cart have items|cart does have items) - * - * @param {Boolean} state - */ - _onCartEmptyState(state) { - this.logger.startWith({ state }); - - const { amount } = this.elements.header; - - state ? amount.show() : amount.hide(); - } - - /** - * Function onCartCheckout() : Called on cart checkout - */ - _onCartCheckout() { - this.logger.startEmpty(); - - this.sidebarToggle(false); - - this.container.set(this.pages.checkout); - this.pages.checkout.ready(function onCheckoutLoaded() { - this.logger.debug(`i was loaded`); - }.bind(this)); - } - - /** - * Function onCatalogPrudctAdd() : Called on catalog item add - */ - _onCatalogProductAdd(product) { - this.logger.startWith({ product }); - - this.cart.itemAdd(product, () => { - if (Components.Cart.openCartOnUpdate) { - this.sidebarToggle(true); - } - }); - } - - /** - * Function sidebarToggle() : Change the sidebar state - * - * @param {boolean} state - */ - sidebarToggle(state) { - this.logger.startWith({ state }); - - const { sidebar, overlay } = this.elements; - - if (state) { - overlay.fadeIn(); - sidebar.self.addClass('show'); - - this.cart.open(); - } else { - overlay.fadeOut(); - sidebar.self.removeClass('show'); - - this.cart.close(); - } - } - -} - -(new App().initialize()); +/** + * @file: js/app.js + * @author: Leonid Vinikov + * @description: Main File + */ +import "@babel/polyfill" + +import * as Core from 'CORE' +import API from 'API'; +import Modules from 'MODULES'; +import Services from 'SERVICES'; +import Components from 'COMPONENTS'; +import Pages from 'PAGES'; + + +class App { + /** + * Function constructor() : Create App + */ + constructor() { + Services.Terminal.initialize(); + + this.logger = new Modules.Logger(this, true); + this.logger.setOutputHandler(Services.Terminal.onOutput); + + this.logger.startEmpty(); + + const remoteAddress = window.location.href.substring(0, window.location.href.lastIndexOf("/")) + '/../api/?cmd=', + http = new API.Http(remoteAddress); + + this.apis = { + catalog: new API.Catalog(http), + cart: new API.Cart(http), + } + + this.elements = { + header: { + logo: Core.Factory.createElement('header #logo'), + toggle: Core.Factory.createElement( 'header #toggle'), + + cart: Core.Factory.createElement('header #toggle .cart'), + amount: Core.Factory.createElement('header #toggle .amount'), + spinner: Core.Factory.createElement('header #toggle .spinner') + }, + + sidebar: { + self: Core.Factory.createElement('#sidebar'), // Self should not be exist, if you use self, it should be component. + closeButton: Core.Factory.createElement('#sidebar #close'), + }, + + overlay: $('#overlay'), + + sections: { + main: $("section.main")[ 0 ] + } + } + + this.container = new Core.Container(this.elements.sections.main, '
'); + + this.pages = { + catalog: new Pages.Catalog( this.container, '
', { + api: this.apis.catalog, + } ), + checkout: new Pages.Checkout( this.container, '
' + + '

Check OUT.

' + + '
' + ), + } + } + + /** + * Function initialize() : Initialize App + */ + initialize() { + this.logger.startEmpty(); + + const { header, overlay, sidebar } = this.elements; + + this.container.on('render', this._onContainerRender.bind(this)); + + overlay.click(() => this.sidebarToggle(false)); + + header.toggle.click(() => this.sidebarToggle(true)); + + header.logo.click(() => { + // TODO: Handle with view, when container will be view, then you will have extend page-container view, and extend it like this page-container.set( page ) // auto render. + this.container.set(this.pages.catalog) + this.container.render(); + }); + + sidebar.closeButton.click(() => this.sidebarToggle(false)); + + this.container.set(this.pages.catalog); + this.container.render(); + } + + /** + * Function _onContainerReady() : Called when container ready. + * + * @param {Modules.Page} pageModule + */ + _onContainerRender(pageModule) { + this.logger.startWith({ pageModule: pageModule.constructor.name }); + + if (pageModule instanceof Pages.Catalog) { + this.pages.catalog.on('productAdd', this._onCatalogProductAdd.bind(this)); + + if (! this.cart) { + this.cart = new Components.Cart(this.apis.cart, this.apis.catalog); + + this.cart.on('get', this._onCartGet.bind(this)); + this.cart.on('received', this._onCartReceived.bind(this)); + this.cart.on('amountChange', this._onCartAmountChange.bind(this)); + this.cart.on('emptyState', this._onCartEmptyState.bind(this)); + this.cart.on('checkout', this._onCartCheckout.bind(this)); + + // TODO: should use 'this.elements.sidebar.self' instead of 'this.elements.sidebar.self.element' + // TODO: FIX ASAP. + this.cart.render(this.elements.sidebar.self.element); + } + } + } + + /** + * Function _onCartGet() : Called on request cart from the server + */ + _onCartGet() { + this.logger.startEmpty(); + + const { spinner } = this.elements.header; + + spinner.show(); + } + + /** + * Function _onCartReceived() : Called after cart received + */ + _onCartReceived() { + this.logger.startEmpty(); + + const { cart, spinner } = this.elements.header; + + cart.show(); + spinner.hide(); + } + + /** + * Function _onCartAmountChange() : Called on cart amount change. + * + * @param {Number} count + */ + _onCartAmountChange(count) { + this.logger.startWith({ count }); + + const { amount } = this.elements.header; + + amount.html(count); + } + + /** + * Function _onCartEmptyState() : Called on cart empty state change (cart have items|cart does have items) + * + * @param {Boolean} state + */ + _onCartEmptyState(state) { + this.logger.startWith({ state }); + + const { amount } = this.elements.header; + + state ? amount.show() : amount.hide(); + } + + /** + * Function _onCartCheckout() : Called on cart checkout + */ + _onCartCheckout() { + this.logger.startEmpty(); + + this.sidebarToggle(false); + + this.container.set(this.pages.checkout); + + this.pages.checkout.on( 'render', () => { + console.log( 'onCartCheckout this.page.checkout rendered'); + } ); + + this.container.render(); + } + + /** + * Function _onCatalogProductAdd() : Called on catalog item add + */ + _onCatalogProductAdd(product) { + this.logger.startWith({ product }); + + this.cart.itemAdd(product, () => { + if (Components.Cart.openCartOnUpdate) { + this.sidebarToggle(true); + } + }); + } + + /** + * Function sidebarToggle() : Change the sidebar state + * + * @param {boolean} state + */ + sidebarToggle(state) { + this.logger.startWith({ state }); + + const { sidebar, overlay } = this.elements; + + if (state) { + overlay.fadeIn(); + sidebar.self.addClass('show'); + + this.cart.open(); + } else { + overlay.fadeOut(); + sidebar.self.removeClass('show'); + + this.cart.close(); + } + } + +} + +(new App().initialize()); diff --git a/js/components/cart.js b/frontend/components/cart.js similarity index 90% rename from js/components/cart.js rename to frontend/components/cart.js index 7a327c7..61b6308 100644 --- a/js/components/cart.js +++ b/frontend/components/cart.js @@ -4,9 +4,9 @@ * @description: Manages cart */ -import API from '../api/api.js'; -import Modules from '../modules/modules.js'; -import Services from '../services/services.js'; +import API from 'API'; +import Modules from 'MODULES'; +import Services from 'SERVICES'; export default class Cart { static openCartOnUpdate = true; @@ -14,9 +14,9 @@ export default class Cart { /** * Function constructor() : Create Cart - * + * * @param {API.Cart} cart - * @param {API.Catalog} catalog + * @param {API.Catalog} catalog */ constructor(cart, catalog) { this.logger = new Modules.Logger(`Components.${this.constructor.name}`, true); @@ -71,8 +71,8 @@ export default class Cart { /** * Function _onRecv() : Called when cart received - * - * @param {[]} data + * + * @param {[]} data */ _onRecv(data) { this.logger.object(data, 'data'); @@ -138,8 +138,8 @@ export default class Cart { /** * Function _onItemRemove() : Called on item remove - * - * @param {Event} e + * + * @param {Event} e */ _onItemRemove(e) { this.logger.startWith({ e }); @@ -160,8 +160,8 @@ export default class Cart { /** * function _doInsertItem() : Insert new item dom and virtual - * - * @param {{}} item + * + * @param {{}} item */ _doInsertItem(item) { this.logger.startWith({ item }); @@ -177,10 +177,10 @@ export default class Cart { /** * Function _doUpdateItem() : Update Cart Item dom and virtual - * - * @param {{}} item - * @param {string} key - * @param {Element} domItem + * + * @param {{}} item + * @param {string} key + * @param {Element} domItem */ _doUpdateItem(item, key, domItem) { this.logger.startWith({ item, key, domItem }); @@ -204,10 +204,10 @@ export default class Cart { /** * Function _doAddItem() : Adds item to cart - * - * @param {{}} item - * @param {boolean} notifyCartChanged - * @param {boolean} highlight + * + * @param {{}} item + * @param {boolean} notifyCartChanged + * @param {boolean} highlight */ _doAddItem(item, notifyCartChanged = true, highlight = false) { this.logger.startWith({ item, notifyCartChanged, highlight }); @@ -240,9 +240,9 @@ export default class Cart { /** * Function _doRemoveItem() : Remove's item from cart - * - * @param {{}} item - * @param {boolean} notifyCartChanged + * + * @param {{}} item + * @param {boolean} notifyCartChanged */ _doRemoveItem(item, notifyCartChanged = true) { this.logger.startWith({ item, notifyCartChanged }); @@ -269,8 +269,8 @@ export default class Cart { /** * Function _doHighlightItem() : highlight item in cart - * - * @param {Element} domItem + * + * @param {Element} domItem */ _doHighlightItem(domItem) { this.logger.startWith({ domItem }); @@ -288,7 +288,7 @@ export default class Cart { this.events.onGet(); - // clear toggler amount + // clear toggle amount this.events.onAmountChange(0); // clear visual cart @@ -305,8 +305,8 @@ export default class Cart { /** * Function _getItemKeyById() : Get Item key from cart by id. - * - * @param {number} id + * + * @param {number} id */ _getItemKeyById(id) { this.logger.startWith({ id }); @@ -325,9 +325,9 @@ export default class Cart { /** * Function on() : Delcare event callback - * - * @param {'get'|'received'|'amountChange'|'emptyState'|'checkout'} event - * @param {{function()}} callback + * + * @param {'get'|'received'|'amountChange'|'emptyState'|'checkout'} event + * @param {{function()}} callback */ on(event, callback) { this.logger.startWith({ event, callback }); @@ -387,8 +387,8 @@ export default class Cart { /** * Function efficientEmptyState() : Update when cart have items or not. - * - * @param {boolean} state + * + * @param {boolean} state */ efficientEmptyState(state) { this.logger.startWith({ state }); @@ -412,8 +412,8 @@ export default class Cart { /** * Function itemAdd() : Add items or update - * - * @param {{}} product + * + * @param {{}} product * @param {{function()}} onSuccess */ itemAdd(product, onSuccess = null) { @@ -432,7 +432,7 @@ export default class Cart { /** * Function renderItem() : Return html markup for item - * + * * @param {{}} item * @param {Element} parent */ @@ -458,7 +458,7 @@ export default class Cart { /** * Function render() : Add component markup to parent innerHTML - * + * * @param {Element} parent */ render(parent) { diff --git a/img/.gitignore b/frontend/components/cart/item.js similarity index 100% rename from img/.gitignore rename to frontend/components/cart/item.js diff --git a/js/components/catalog.js b/frontend/components/catalog.js similarity index 76% rename from js/components/catalog.js rename to frontend/components/catalog.js index 539d87c..78b47b1 100644 --- a/js/components/catalog.js +++ b/frontend/components/catalog.js @@ -1,284 +1,297 @@ -/** - * @file: js/components/catalog.js - * @author: Leonid Vinikov - * @description: Manages catalog - */ - -import API from '../api/api.js'; -import Modules from '../modules/modules.js'; -import Services from '../services/services.js'; - -export default class Catalog { - static amountMaxValue = 999; - static amountMinValue = 1; - - /** - * Function constructor() : Create Catalog - * - * @param {API.Catalog} catalog - */ - constructor(catalog) { - this.logger = new Modules.Logger(`Components.${this.constructor.name}`, true); - this.logger.setOutputHandler(Services.Terminal.onOutput); - - this.apiCatalog = catalog; - - this.page = 0; - - this.events = { - onInitialRecv: () => { }, - onProductAdd: (product) => { }, - } - } - - /** - * Function initialize() : Initialize catalog - */ - initialize() { - this.logger.startEmpty(); - - this.elements = { - pagination: { - self: $('#pagination'), - prev: $("#pagination .prev"), - next: $("#pagination .next"), - placeHolder: $('#pagination .placeholder') - }, - - catalog: { - self: $('#catalog'), - spinner: $('#catalog .spinner'), - }, - - template: { - product: $('template#product'), - } - }; - - this.elements.pagination.next.click(() => this._onPageChange((this.page + 1))); - this.elements.pagination.prev.click(() => this._onPageChange((this.page - 1))); - - this.elements.catalog.self.on('change', '.product .amount', ((e) => this._onProudctAmountChange(e))); - this.elements.catalog.self.on('click', '.product button', ((e) => this._onProductAdd(e))); - - this._getCatalog(0, this._onInitialRecv.bind(this)); - } - - /** - * Function onInitialRecv() : Called on success of intial getCatalog request - */ - _onInitialRecv() { - this.events.onInitialRecv(); - } - - /** - * Function _onPageChange() : Called on page change - * - * @param {number} page - */ - _onPageChange(page) { - this.logger.startWith({ page }); - - const { catalog, pagination } = this.elements; - - --page; - - catalog.self.children('.product').remove(); - catalog.spinner.show(); - - pagination.self.hide(); - pagination.placeHolder.empty(); - - this._getCatalog(page); - } - - /** - * Function _onProductAdd() : Called on "Add to cart button" - * - * @param {event} e - */ - _onProductAdd(e) { - this.logger.startWith({ e }); - - // maybe there is better way. - const el = $(e.currentTarget); - const domProduct = el.parentsUntil('.product').parent(); - - const id = parseInt(domProduct.attr('data-id')); - const amount = parseInt(domProduct.find('.amount').val()); - - let product = this.apiCatalog.getLocalProductById(id); - - Object.assign(product, { id, amount }); - - // call callback - this.events.onProductAdd(product) - - // put it back to 1. - domProduct.find('.amount').val('1'); - } - - /** - * Function _onProudctAmountChange() : Called on "Product Amount Change" - * - * @param {event} e - */ - _onProudctAmountChange(e) { - this.logger.startWith({ e }); - - // maybe there is better way. - const el = $(e.currentTarget); - - let val = el.val(); - - this.logger.debug(`val: '${val}'`); - - if (val > Catalog.amountMaxValue) { - val = Catalog.amountMaxValue; - } else if (val < Catalog.amountMinValue) { - val = Catalog.amountMinValue; - } - - el.val(val); - } - - /** - * Function _getCatalog() : Get catalog from the server. - * - * @param {number} page - * @param {{function()}} onSuccess - */ - _getCatalog(page, onSuccess = null) { - this.logger.startWith({ page, onSuccess }); - - const { catalog, template } = this.elements; - - this.apiCatalog.get(data => { - // used slow here to fake loading - catalog.spinner.fadeOut('slow', () => { - if (!data.error) { - - this._setPagination(data.pagination); - - data.result.map((product) => { - catalog.self.append(this.renderProduct(product)); - }); - - if (onSuccess) onSuccess(); - } - }); - }, page); - } - - /** - * Function _setPagination() : Set pagination to dom. - * - * @param {{}} paginationResult - */ - _setPagination(paginationResult) { - this.logger.startWith({ paginationResult }); - - const { pagination } = this.elements; - - // pages - for (let i = 0; i < paginationResult.pages; ++i) { - - const anchor = $(`${i + 1}`) - - anchor.click(function (val) { - this._onPageChange(val); - }.bind(this, parseInt(anchor.html()))); - - pagination.placeHolder.append(anchor); - - pagination.self.fadeIn(); - } - - // set page - this.page = paginationResult.current + 1; - - // next - if (paginationResult.current >= (paginationResult.pages - 1)) { - pagination.next.hide(); - } else { - pagination.next.show(); - } - - // prev - if (this.page == 1) { - pagination.prev.hide(); - } else { - pagination.prev.show(); - } - } - - /** - * Function on() : Delcare event callback - * - * @param {'initialRecv'|'productAdd'} event - * @param {{function()}} callback - */ - on(event, callback) { - this.logger.startWith({ event, callback }); - - switch (event) { - case 'initialRecv': { - this.events.onInitialRecv = callback; - } break; - - case 'productAdd': { - this.events.onProductAdd = callback; - } break; - - - default: { - alert(`${this.constructor.name}::on() -> invalid event type: '${event}'`); - } - } - } - - /** - * Function renderProduct() : Return html markup for product - * - * @param {{}} product - */ - renderProduct(product) { - const { id, name, price } = product; - - return (` -
- -

${name}

- - -
- `) - } - - /** - * Function render() : Return html markup for catalog it self - */ - render() { - return (` -
-
-
- - - `); - } -} \ No newline at end of file +/** + * @file: js/components/catalog.js + * @author: Leonid Vinikov + * @description: Manages catalog + */ + +import Services from 'SERVICES'; +import Modules from 'MODULES'; +import Container from 'CORE/container.js'; + +export default class Catalog extends Container { + static amountMaxValue = 999; + static amountMinValue = 1; + + constructor( parent, context, options ) { + super( parent, context, options ); + + this.logger = new Modules.Logger(`Components.${this.constructor.name}`, true); + this.logger.setOutputHandler(Services.Terminal.onOutput); + + this.logger.startWith( { parent, context, options } ); + + this.apiCatalog = options.api; + + this.page = 0; + + this.events = { + onInitialRecv: () => { }, + onProductAdd: (product) => { }, + } + + this.afterRender = () => { + super.afterRender(); + + this.elements = { + pagination: { + self: $('#pagination'), + prev: $("#pagination .prev"), + next: $("#pagination .next"), + placeHolder: $('#pagination .placeholder') + }, + + catalog: { + self: $('#catalog'), + spinner: $('#catalog .spinner'), + }, + + template: { + product: $('template#product'), + } + }; + + this._initialize(); + } + } + + /** + * Function _initialize() : Initialize catalog + */ + _initialize() { + this.logger.startEmpty(); + + this.elements.pagination.next.click(() => this._onPageChange((this.page + 1))); + this.elements.pagination.prev.click(() => this._onPageChange((this.page - 1))); + + this.elements.catalog.self.on('change', '.product .amount', ((e) => this._onProductAmountChange(e))); + this.elements.catalog.self.on('click', '.product button', ((e) => this._onProductAdd(e))); + + this._getCatalog(0, this._onInitialRecv.bind(this)); + } + + /** + * Function onInitialRecv() : Called on success of intial getCatalog request + */ + _onInitialRecv() { + this.events.onInitialRecv(); + } + + /** + * Function _onPageChange() : Called on page change + * + * @param {number} page + */ + _onPageChange(page) { + this.logger.startWith({ page }); + + const { catalog, pagination } = this.elements; + + --page; + + catalog.self.children('.product').remove(); + catalog.spinner.show(); + + pagination.self.hide(); + pagination.placeHolder.empty(); + + this._getCatalog(page); + } + + /** + * Function _onProductAdd() : Called on "Add to cart button" + * + * @param {event} e + */ + _onProductAdd(e) { + this.logger.startWith({ e }); + + // maybe there is better way. + const el = $(e.currentTarget); + const domProduct = el.parentsUntil('.product').parent(); + + const id = parseInt(domProduct.attr('data-id')); + const amount = parseInt(domProduct.find('.amount').val()); + + let product = this.apiCatalog.getLocalProductById(id); + + Object.assign(product, { id, amount }); + + // call callback + this.events.onProductAdd(product) + + // put it back to 1. + domProduct.find('.amount').val('1'); + } + + /** + * Function _onProductAmountChange() : Called on "Product Amount Change" + * + * @param {Event} e + */ + _onProductAmountChange(e) { + this.logger.startWith({ e }); + + // maybe there is better way. + const el = $(e.currentTarget); + + let val = el.val(); + + this.logger.debug(`val: '${val}'`); + + if (val > Catalog.amountMaxValue) { + val = Catalog.amountMaxValue; + } else if (val < Catalog.amountMinValue) { + val = Catalog.amountMinValue; + } + + el.val(val); + } + + /** + * Function _getCatalog() : Get catalog from the server. + * + * @param {number} page + * @param {{function()}} onSuccess + */ + _getCatalog(page, onSuccess = null) { + this.logger.startWith({ page, onSuccess }); + + const { catalog, template } = this.elements; + + this.apiCatalog.get(data => { + //debugger; + + // used slow here to fake loading + catalog.spinner.fadeOut('slow', () => { + + if (!data.error) { + + this._setPagination(data.pagination); + + data.result.map((product) => { + catalog.self.append(this.renderProduct(product)); + }); + + if (onSuccess) onSuccess(); + } + }); + }, page); + } + + /** + * Function _setPagination() : Set pagination to dom. + * + * @param {{}} paginationResult + */ + _setPagination(paginationResult) { + this.logger.startWith({ paginationResult }); + + const { pagination } = this.elements; + + // pages + for (let i = 0; i < paginationResult.pages; ++i) { + + const anchor = $(`${i + 1}`) + + anchor.click(function (val) { + this._onPageChange(val); + }.bind(this, parseInt(anchor.html()))); + + pagination.placeHolder.append(anchor); + + pagination.self.fadeIn(); + } + + // set page + this.page = paginationResult.current + 1; + + // next + if (paginationResult.current >= (paginationResult.pages - 1)) { + pagination.next.hide(); + } else { + pagination.next.show(); + } + + // prev + if (this.page == 1) { + pagination.prev.hide(); + } else { + pagination.prev.show(); + } + } + + /** + * Function on() : Declare event callback + * + * @param {'initialRecv'|'productAdd'} event + * @param {{function()}} callback + */ + on(event, callback) { + this.logger.startWith({ event, callback }); + + switch (event) { + case 'initialRecv': { + this.events.onInitialRecv = callback; + } break; + + case 'productAdd': { + this.events.onProductAdd = callback; + } break; + + + default: { + alert(`${this.constructor.name}::on() -> invalid event type: '${event}'`); + } + } + } + + /** + * Function renderProduct() : Return html markup for product + * + * @param {{}} product + */ + renderProduct(product) { + const { id, name, price } = product; + + return (` +
+ +

${name}

+ + +
+ `) + } + + /** + * Function render_afterRender() : Return html markup for catalog it self + * + * @param {Element} parent + */ + _render(parent) { + // TODO: spinner and pagination should be components. + const markup = (` +
+
+
+ + + `); + + return markup; + } +} diff --git a/js/components/components.js b/frontend/components/index.js similarity index 100% rename from js/components/components.js rename to frontend/components/index.js diff --git a/frontend/core/base.js b/frontend/core/base.js new file mode 100644 index 0000000..cf9d371 --- /dev/null +++ b/frontend/core/base.js @@ -0,0 +1,81 @@ +import Context from './context.js'; + +export class Base { + /** + * @type {HTMLElement} + */ + element; + + /** + * Function constructor() : Create Custom Element. + * + * @param {Node|HTMLElement|Base} parent + * @param {String|HTMLElement|Context} context + * @param {{}} options + */ + constructor(parent, context, options ) { + if ( ! parent ) { + throw Error('parent is required.'); + } + + this.context = context; + this.parent = parent; + + if ( context instanceof HTMLElement ) { + this.element = context; + } else if ( ! (context instanceof Context)) { + context = new Context( this.context ); + } else { + throw Error( 'context is invalid' ); + } + + this.context = context; + + this.beforeInit(); + + this.initialize( options ); + + this.afterInit(); + } + + initialize( options = {} ) {} + + render( preventDefault = false ) { + if ( ! preventDefault ) this.beforeRender(); + + let parent = this.parent; + + if ( parent instanceof Base ) { + parent = this.parent.element; + } + + // If its instance of HTMLElement then we assume it was rendered before. + if ( this.context instanceof HTMLElement && this.context.isConnected ) { + // Re-render. + parent.removeChild( this.context ); + + // Render + parent.appendChild( this.context ); + } else if ( this.context instanceof Context ) { + // Do not remove if its not attached to DOM. + if ( this.element && this.element.isConnected ) { + parent.removeChild( this.element ); + } + + // Render. + this.element = parent.appendChild( this.context.create() ); + } + + if ( ! preventDefault ) this.afterRender(); + + return this.element; + } + + beforeInit() {} + afterInit() {}; + + beforeRender() {} + afterRender() {} +} + +export default Base; diff --git a/frontend/core/container.js b/frontend/core/container.js new file mode 100644 index 0000000..42d930a --- /dev/null +++ b/frontend/core/container.js @@ -0,0 +1,73 @@ +import Base from './base.js'; + +export class Container extends Base { + constructor( parent, context, options ) { + super( parent, context, options ); + + } + + initialize() { + this.events = { + onRender: () => {}, + } + + super.initialize(); + } + + afterRender() { + super.afterRender(); + + if ( this.events && this.events.onRender ) { + this.events.onRender( this.child ); + } + } + + /** + * + * @param {Container} child + */ + set( child ) { + if ( ! ( child instanceof Container ) ) { + throw new Error(); + } + + this.child = child; + + } + + render() { + this.beforeRender(); + super.beforeRender(); + + // Self Re-render. + super.render( true ); + + // Re-render of child. + if ( this.child ) { + this.child.render(); + } + + this.afterRender(); + super.afterRender(); + } + + /** + * Function on() : Declare event callback + * + * @param {'render'} event + * @param {{function()}} callback + */ + on(event, callback) { + switch (event) { + case 'render': { + this.events.onRender = callback; + } break; + + default: { + alert(`${this.constructor.name}::on() -> invalid event type: '${event}'`); + } + } + } +} + +export default Container; diff --git a/frontend/core/context.js b/frontend/core/context.js new file mode 100644 index 0000000..348a07e --- /dev/null +++ b/frontend/core/context.js @@ -0,0 +1,35 @@ +import HTML from '../library/html.js'; + +export class Context +{ + /** + * @type {Node} + */ + node; + + constructor( context ) { + this.context = context; + } + + /** + * + * @returns {Node} + */ + create() { + this.beforeCreate(); + + this.node = HTML.toNode( this.context ); + + this.afterCreate(); + + return this.node; + } + + beforeCreate() { + } + + afterCreate() { + } +} + +export default Context; diff --git a/frontend/core/controller.js b/frontend/core/controller.js new file mode 100644 index 0000000..7c3117f --- /dev/null +++ b/frontend/core/controller.js @@ -0,0 +1,2 @@ +// https://github.com/robCrawford/es6-mvc +// https://gist.github.com/Caballerog/aa69c9465cc866afd1bc53a00f1f08b6 diff --git a/frontend/core/element.js b/frontend/core/element.js new file mode 100644 index 0000000..f31ce24 --- /dev/null +++ b/frontend/core/element.js @@ -0,0 +1,99 @@ +import Container from './container.js'; + +export class Element extends Container { + initialize() { + this.beforeInit(); + + super.initialize(); + + if ( this.context instanceof HTMLElement ) { + this.attachListeners(); + } + + this.afterInit(); + } + + beforeInit() { + } + + afterInit() { + } + + afterRender() { + super.afterRender(); + + this.attachListeners(); + } + + attachListener( method, callback ) { + switch ( method ) { + case 'onClick': { + this.element.addEventListener( 'click', callback ); + } + break; + } + } + + attachListeners( from = this ) { + // Handle all parent properties if startsWith 'on' then attach it listener. + // Allow you extend components with custom callbacks. + Object.getOwnPropertyNames( from ).forEach( ( method ) => { + if ( method.startsWith( 'on' ) ) { + this.attachListener( method, from[ 'onClick' ] ); + } + } ); + + // Attach All `this.context` elements events to `from` component. + let nodes = []; + + if ( this.context.node ) { + nodes = [ this.context.node ]; + } + + if ( nodes.length > 0 && this.context.node.childNodes ) { + nodes = [ nodes, ... this.context.node.childNodes ]; + } + + nodes.forEach( ( node ) => { + // Now u need loop all over on shit :) + for ( let i in node ) { + if ( i.startsWith( 'on' ) && node[ i ] ) { + // here u wanted to eval onclick. + let funcContent = node[ i ].toString(); + + funcContent = funcContent.replace( 'this', 'from' ); + funcContent = funcContent.split( '{' )[ 1 ].replace( '}', '' ); + funcContent = funcContent.replace( '()', '( ... arguments)'); + + node[ i ] = () => eval( funcContent ); + } + } + } ); + } + + click( callback ) { + this.attachListener( 'onClick', callback ); + } + + show() { + this.element.classList.remove( 'hidden') + } + + hide() { + this.element.classList.add( 'hidden') + } + + html( content ) { + this.element.innerHTML = content.toString(); + } + + addClass( className ) { + this.element.classList.add( className ); + } + + removeClass( className ) { + this.element.classList.remove( className ); + } +} + +export default Element; diff --git a/frontend/core/factory.js b/frontend/core/factory.js new file mode 100644 index 0000000..0c7e216 --- /dev/null +++ b/frontend/core/factory.js @@ -0,0 +1,11 @@ +import * as Modules from './index.js' + +export class Factory { + static createElement( selector ) { + selector = document.querySelector( selector ); + + return new Modules.Element( selector.parentElement, selector ); + } +} + +export default Factory; diff --git a/frontend/core/index.js b/frontend/core/index.js new file mode 100644 index 0000000..8c9a9ca --- /dev/null +++ b/frontend/core/index.js @@ -0,0 +1,18 @@ +import {View} from './view.js'; +import { Element } from './element.js'; + +export { Base } from './base.js'; +export { Container } from './container.js'; +export { Context } from './context.js'; +export { Factory } from './factory.js'; + +// TODO model +export { + View, + Element, +} + +export default { + View, + Element +} diff --git a/frontend/core/model.js b/frontend/core/model.js new file mode 100644 index 0000000..e69de29 diff --git a/frontend/core/view.js b/frontend/core/view.js new file mode 100644 index 0000000..1c5d19f --- /dev/null +++ b/frontend/core/view.js @@ -0,0 +1,30 @@ +import Element from './element.js'; + +export class View { + constructor( parent, options = { } ) { + this.element = new Element( + parent, + options.template() || this.template(), + options, + ); + + + this.initialize( options ); + } + + initialize( options ) { + if ( options.template ) { + this.template = options.template; + } + } + + /** + * @return {String} HTML Markup. + */ + template() { alert('no template'); } + + + render() { + return this.element.render(); + } +} diff --git a/css/fonts.css b/frontend/css/fonts.css similarity index 100% rename from css/fonts.css rename to frontend/css/fonts.css diff --git a/css/main.css b/frontend/css/main.css similarity index 100% rename from css/main.css rename to frontend/css/main.css diff --git a/css/mobile.css b/frontend/css/mobile.css similarity index 100% rename from css/mobile.css rename to frontend/css/mobile.css diff --git a/css/normalize.css b/frontend/css/normalize.css similarity index 100% rename from css/normalize.css rename to frontend/css/normalize.css diff --git a/css/style.css b/frontend/css/style.css similarity index 97% rename from css/style.css rename to frontend/css/style.css index df01b93..1ae967b 100644 --- a/css/style.css +++ b/frontend/css/style.css @@ -17,7 +17,7 @@ @keyframes highlight { 0% { - background: #ffff99; + background: #ffff99; } 100% { background: none; @@ -178,30 +178,27 @@ header li h1, h2, h3, h4, h5, h6 { margin: 0px; } -header #toggler { +header #toggle { float: right; } - -header #toggler img { +header #toggle img { width: 35px; height: 35px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; - display: none; } -header #toggler .spinner { +header #toggle .spinner { width: 35px; height: 35px; padding: 0; margin: 0; } -header #toggler .amount { - display: none; +header #toggle .amount { position: relative; bottom: 8px; font-size: 13px; @@ -264,7 +261,6 @@ header #toggler .amount { color: white; font-size: 30px; border: 0px solid black; - display: none; } #sidebar .cart .checkout:hover { diff --git a/css/terminal.css b/frontend/css/terminal.css similarity index 100% rename from css/terminal.css rename to frontend/css/terminal.css diff --git a/img/cart.png b/frontend/img/cart.png similarity index 100% rename from img/cart.png rename to frontend/img/cart.png diff --git a/img/icon.png b/frontend/img/icon.png similarity index 100% rename from img/icon.png rename to frontend/img/icon.png diff --git a/img/product--.jpg b/frontend/img/product--.jpg similarity index 100% rename from img/product--.jpg rename to frontend/img/product--.jpg diff --git a/img/product-1.jpg b/frontend/img/product-1.jpg similarity index 100% rename from img/product-1.jpg rename to frontend/img/product-1.jpg diff --git a/img/product-10.jpg b/frontend/img/product-10.jpg similarity index 100% rename from img/product-10.jpg rename to frontend/img/product-10.jpg diff --git a/img/product-11.jpg b/frontend/img/product-11.jpg similarity index 100% rename from img/product-11.jpg rename to frontend/img/product-11.jpg diff --git a/img/product-2.jpg b/frontend/img/product-2.jpg similarity index 100% rename from img/product-2.jpg rename to frontend/img/product-2.jpg diff --git a/img/product-3.jpg b/frontend/img/product-3.jpg similarity index 100% rename from img/product-3.jpg rename to frontend/img/product-3.jpg diff --git a/img/product-4.jpg b/frontend/img/product-4.jpg similarity index 100% rename from img/product-4.jpg rename to frontend/img/product-4.jpg diff --git a/img/product-5.jpg b/frontend/img/product-5.jpg similarity index 100% rename from img/product-5.jpg rename to frontend/img/product-5.jpg diff --git a/img/product-6.jpg b/frontend/img/product-6.jpg similarity index 100% rename from img/product-6.jpg rename to frontend/img/product-6.jpg diff --git a/img/product-7.jpg b/frontend/img/product-7.jpg similarity index 100% rename from img/product-7.jpg rename to frontend/img/product-7.jpg diff --git a/img/product-8.jpg b/frontend/img/product-8.jpg similarity index 100% rename from img/product-8.jpg rename to frontend/img/product-8.jpg diff --git a/img/product-9.jpg b/frontend/img/product-9.jpg similarity index 100% rename from img/product-9.jpg rename to frontend/img/product-9.jpg diff --git a/babel-index.html b/frontend/index.html similarity index 83% rename from babel-index.html rename to frontend/index.html index 2644676..d185720 100644 --- a/babel-index.html +++ b/frontend/index.html @@ -5,7 +5,7 @@ MyShop - + @@ -26,9 +26,9 @@ -
  • - - +
  • + +
  • @@ -57,7 +57,7 @@

    YOUR CART

    - - + + diff --git a/frontend/library/html.js b/frontend/library/html.js new file mode 100644 index 0000000..27fd796 --- /dev/null +++ b/frontend/library/html.js @@ -0,0 +1,26 @@ +export default class HTML { + /** + * @param {String} HTML representing a single element + * @return {Node} + */ + static toNode(html) { + const template = document.createElement('template'); + + html = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = html; + + return template.content.firstChild; + } + + /** + * @param {String} HTML representing any number of sibling elements + * @return {NodeList} + */ + static toNodes(html) { + const template = document.createElement('template'); + + template.innerHTML = html; + + return template.content.childNodes; + } +} diff --git a/js/library/jquery.js b/frontend/library/jquery.js similarity index 100% rename from js/library/jquery.js rename to frontend/library/jquery.js diff --git a/frontend/modules/component.js b/frontend/modules/component.js new file mode 100644 index 0000000..2af0f0a --- /dev/null +++ b/frontend/modules/component.js @@ -0,0 +1,43 @@ +import Core from 'CORE'; + +export class Component { + /** + * + * @param model + * @param {View} view + * @param controller + * @param options + */ + constructor( parent, model, view, controller, options = {} ) { + // Mode model, view, controller? assuming you create MVC component. + if ( arguments.length === 1 ) { + model, view, controller = this; + + view = new Core.View( parent, { + template: this['template'], + } ); + } else if ( arguments.length < 4 ) { + throw Error('WTF'); + } + + this.model = model; + this.view = view; + this.controller = controller; + this.options = options; + + this.initialize( this.options ); + } + + initialize( options ) { + // Attach listeners of view.element to the controller. + this.view.element.attachListeners = () => { + return Core.Element.prototype.attachListeners.call( this.view.element, this.controller ); + } + } + + render() { + this.view.render(); + } +} + +export default Component; diff --git a/frontend/modules/index.js b/frontend/modules/index.js new file mode 100644 index 0000000..97b33c1 --- /dev/null +++ b/frontend/modules/index.js @@ -0,0 +1,15 @@ +/** + * @file: js/modules/modules.js + * @author: Leonid Vinikov + * @description: Modules Namespace O__o + */ + +import Logger from './logger.js'; +import Page from './page.js'; +import Component from './component.js'; + +export default { + Page, + Logger, + Component, +} diff --git a/js/modules/logger.js b/frontend/modules/logger.js similarity index 100% rename from js/modules/logger.js rename to frontend/modules/logger.js diff --git a/frontend/modules/page.js b/frontend/modules/page.js new file mode 100644 index 0000000..8f82738 --- /dev/null +++ b/frontend/modules/page.js @@ -0,0 +1,20 @@ +/** + * @file: js/modules/page.js + * @author: Leonid Vinikov + * @description: Modules Namespace O__o + */ + +import Logger from './logger.js'; +import Container from 'CORE/container.js'; +import Services from 'SERVICES'; + +export default class Page extends Container { + initialize() { + this.logger = new Logger(`Modules.Page`, true); + this.logger.setOutputHandler(Services.Terminal.onOutput); + + this.logger.startWith(this.constructor.name); + + super.initialize(); + } +} diff --git a/js/pages/catalog.js b/frontend/pages/catalog.js similarity index 55% rename from js/pages/catalog.js rename to frontend/pages/catalog.js index 3f5eece..8493118 100644 --- a/js/pages/catalog.js +++ b/frontend/pages/catalog.js @@ -1,27 +1,24 @@ /** - * @file: js/modules/page.js + * @file: js/pages/catalog.js * @author: Leonid Vinikov - * @description: Modules Namespace O__o + * @description: Catalog page. */ -import Modules from '../modules/modules.js'; -import Components from '../components/components.js'; -import API from '../api/api.js'; +import Modules from 'MODULES'; +import Components from 'COMPONENTS'; +import Base from 'CORE/base'; export default class Catalog extends Modules.Page { - - /** - * Function constructor() Create Catalog Page - * - * @param {API.Catalog} api - */ - constructor(api) { - super(); + + initialize( options ) { + super.initialize(); + + const { api } = options; this.logger.name = `Pages.${this.constructor.name}`; this.logger.startWith({ api }); - this.catalog = new Components.Catalog(api); + this.catalog = new Components.Catalog( this, '', { api } ); } /** @@ -29,7 +26,7 @@ export default class Catalog extends Modules.Page { */ _onReady() { this.logger.startEmpty(); - + //this.catalog.on('initialRecv', this._onCatalogInitialRecv.bind(this)); //this.catalog.on('productAdd', this._onCatalogProductAdd.bind(this)); @@ -39,23 +36,30 @@ export default class Catalog extends Modules.Page { /** * Function _render() : Return the html markup for this page */ - _render() { - return (` + render() { + super.render(); + + const markup = (`
    - ${this.catalog.render()} + ${this.catalog._render()}
    - `); + `), element = new Base( this, markup ); + + + element.render(); + + this.catalog.render(); } /** - * Function on() : Delcare event callback - * - * @param {'initialRecv'|'productAdd'} event - * @param {{function()}} callback + * Function on() : Declare event callback + * + * @param {'initialRecv'|'productAdd'} event + * @param {{function()}} callback */ on(event, callback) { this.logger.startWith({ event, callback }); this.catalog.on(event, callback); } -} \ No newline at end of file +} diff --git a/js/pages/checkout.js b/frontend/pages/checkout.js similarity index 82% rename from js/pages/checkout.js rename to frontend/pages/checkout.js index 884b101..334c1e2 100644 --- a/js/pages/checkout.js +++ b/frontend/pages/checkout.js @@ -1,5 +1,5 @@ -import Modules from '../modules/modules.js'; +import Modules from 'MODULES'; export default class Checkout extends Modules.Page { _render() { @@ -9,4 +9,4 @@ export default class Checkout extends Modules.Page { `); } -} \ No newline at end of file +} diff --git a/js/pages/pages.js b/frontend/pages/index.js similarity index 100% rename from js/pages/pages.js rename to frontend/pages/index.js diff --git a/js/services/services.js b/frontend/services/index.js similarity index 92% rename from js/services/services.js rename to frontend/services/index.js index 13d5143..145955d 100644 --- a/js/services/services.js +++ b/frontend/services/index.js @@ -12,4 +12,4 @@ Services.Terminal = Terminal; export default Services; -export { Terminal }; \ No newline at end of file +export { Terminal }; diff --git a/js/services/terminal.js b/frontend/services/terminal.js similarity index 75% rename from js/services/terminal.js rename to frontend/services/terminal.js index 27b0fc7..bcf0774 100644 --- a/js/services/terminal.js +++ b/frontend/services/terminal.js @@ -4,7 +4,7 @@ * @description: A live console opend by tilda key */ -import Modules from '../modules/modules.js'; +import Modules from 'MODULES'; import JQuery from '../library/jquery.js'; @@ -45,7 +45,7 @@ export default class Terminal { } }; - this._initalize(); + this._initialize(); Terminal.instance = this; } @@ -54,9 +54,9 @@ export default class Terminal { } /** - * Function _initalize() : Initalize Terminal + * Function _initialize() : initialize Terminal */ - _initalize() { + _initialize() { const { body, terminal } = this.elements; const { buttons } = terminal; @@ -90,7 +90,7 @@ export default class Terminal { /** * Function _onKeyDown() : Called when key down on $('body) * - * @param {Event} e + * @param {Event} e */ _onKeyDown(e) { //this.logger.startWith({ e }); @@ -104,7 +104,7 @@ export default class Terminal { /** * Function _onMouseMove() : Called when mouse move on $('body) * - * @param {Event} e + * @param {Event} e */ _onMouseMove(e) { //this.logger.startWith({ e }); @@ -136,7 +136,7 @@ export default class Terminal { /** * Function _onMouseMove() : Called when mouse up on $('body) * - * @param {Event} e + * @param {Event} e */ _onMouseUp(e) { //this.logger.startWith({ e }); @@ -163,7 +163,7 @@ export default class Terminal { /** * Function _onTerminalReiszeMouseDown() : Called when mouse down on $('#terminal button.resize') * - * @param {Event} e + * @param {Event} e */ _onTerminalReiszeMouseDown(e) { this.logger.startWith({ e }); @@ -179,7 +179,7 @@ export default class Terminal { /** * Function _onTerminalReiszeMouseUp() : Called when mouse up on $('#terminal button.resize') * - * @param {Event} e + * @param {Event} e */ _onTerminalReiszeMouseUp(e) { //this.logger.startWith({ e }); @@ -190,7 +190,7 @@ export default class Terminal { /** * Function _onTerminalCloseClick() : Called when mouse up on $('#terminal button.close') * - * @param {Event} e + * @param {Event} e */ _onTerminalCloseClick(e) { this.logger.startWith({ e }); @@ -200,9 +200,9 @@ export default class Terminal { /** * Function _storage() : Get or set local storage - * - * @param {string} type - * @param {any} val + * + * @param {string} type + * @param {any} val */ _stroage(type, val = null) { this.logger.startWith({ type, val }); @@ -247,15 +247,13 @@ export default class Terminal { } } - /** * Function onOutput() : Output handler - * - * @param {string} text - * - * @todo change paramter text to something else. + * + * @param {*} output + * */ -Terminal.onOutput = function (text) { +Terminal.onOutput = function (output) { const _this = Terminal.instance; let plain = false; @@ -269,27 +267,40 @@ Terminal.onOutput = function (text) { console.log.apply(this, arguments); // if jQuery element - if (text instanceof jQuery) { - text = `[jQuery Element]: '${text.getSelector()}'`; - } else if (typeof text == 'object') { + if (output instanceof jQuery) { + output = `[jQuery Element]: '${output.getSelector()}'`; + } else if (typeof output == 'object') { // for events. - if (text.hasOwnProperty('originalEvent')) { - text = `[Event]: type: '${text.type}' selector: '${text.handleObj.selector}'`; - + if (output instanceof Event) { + /** + * @type {Element} + */ + const el = output.path[0]; + let selector = el.nodeName; + + if (el.id) { + selector += '#' + el.id; + } + + if (el.className) { + selector += '.' + [...el.classList].join('.'); + } + + output = `[Event]: type: '${output.type}' element: '${selector}'`; } else { - text = JSON.stringify(text, null, 4); + output = JSON.stringify(output, null, 4); - text = `
    ${text}
    `; + output = `
    ${output}
    `; } } - for (let i = 0; i < text.length; ++i) { + for (let i = 0; i < output.length; ++i) { if (skipFlag) { skipFlag = false; continue; } - if (text[i] === '{' || text[i] === '}') { + if (output[i] === '{' || output[i] === '}') { if (!objectFlag) { formated.push(`{`); } else { @@ -299,37 +310,37 @@ Terminal.onOutput = function (text) { objectFlag = !objectFlag; continue; - } else if (text[i] === '`') { + } else if (output[i] === '`') { if (!tildaFlag) { - formated.push(`${text[i]}`); + formated.push(`${output[i]}`); } else { - formated.push(`${text[i]}`); + formated.push(`${output[i]}`); } tildaFlag = !tildaFlag; continue; - } else if (text[i] === "'") { + } else if (output[i] === "'") { if (!quoteFlag) { - formated.push(`${text[i]}`); + formated.push(`${output[i]}`); } else { - formated.push(`${text[i]}`); + formated.push(`${output[i]}`); } quoteFlag = !quoteFlag; continue; - } else if (text[i] === '"') { + } else if (output[i] === '"') { if (!dbQuotesFlag) { - formated.push(`${text[i]}`); + formated.push(`${output[i]}`); } else { - formated.push(`${text[i]}`); + formated.push(`${output[i]}`); } dbQuotesFlag = !dbQuotesFlag; continue; - } else if (text[i] == '%' && text[i + 1] == 'c') { + } else if (output[i] == '%' && output[i + 1] == 'c') { if (plain) { i++; continue; @@ -350,15 +361,15 @@ Terminal.onOutput = function (text) { } skipFlag = false; - formated.push(text[i]); + formated.push(output[i]); } - text = formated.join(''); + output = formated.join(''); - text = text.replace(new RegExp('null', 'g'), 'null'); + output = output.replace(new RegExp('null', 'g'), 'null'); - terminal.self.append(`

    ${text}

    `); + terminal.self.append(`

    ${output}

    `); terminal.self.stop(); terminal.self.animate({ @@ -367,8 +378,8 @@ Terminal.onOutput = function (text) { } /** - * Funciton initalize() : Create Instance + * Funciton initialize() : Create Instance */ -Terminal.initalize = function () { +Terminal.initialize = function () { new Terminal(); } diff --git a/js/vendor/jquery-3.4.1.min.js b/frontend/vendor/jquery-3.4.1.min.js similarity index 100% rename from js/vendor/jquery-3.4.1.min.js rename to frontend/vendor/jquery-3.4.1.min.js diff --git a/index.html b/index.html deleted file mode 100644 index 8ddf635..0000000 --- a/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - MyShop - - - - - - - - - - - - - - - -
    - -
    - -
    - - -
    -
    - -
    - -
    - - -
    -
    - - -
    -
    - - - - - \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index 0891af1..0000000 --- a/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// this entrypoint for webpack -// this is for babel-index.html (es5) -import "@babel/polyfill"; - -require('./js/app.js'); diff --git a/js/modules/modules.js b/js/modules/modules.js deleted file mode 100644 index 12948d6..0000000 --- a/js/modules/modules.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @file: js/modules/modules.js - * @author: Leonid Vinikov - * @description: Modules Namespace O__o - */ - -import Logger from './logger.js'; -import Page from './page.js'; -import PageContainer from './pageContainer.js'; - -const Modules = {}; - -Modules.Logger = Logger; -Modules.Page = Page; -Modules.PageContainer = PageContainer; - -export default Modules; - -export { Logger, Page, PageContainer }; \ No newline at end of file diff --git a/js/modules/page.js b/js/modules/page.js deleted file mode 100644 index 7b27d38..0000000 --- a/js/modules/page.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @file: js/modules/page.js - * @author: Leonid Vinikov - * @description: Modules Namespace O__o - */ - -import Logger from './logger.js'; -import Services from '../services/services.js'; - -export default class Page { - /** - * @type Logger - */ - logger = null; - - /** - * @type Element - */ - dom = null; - - /** - * Function constructor() : Create page module - */ - constructor() { - // not good. - this.logger = new Logger(`Modules.Page`, true); - this.logger.setOutputHandler(Services.Terminal.onOutput); - - this.logger.startWith(this.constructor.name); - - this.events = { - onReady: (pageModule) => { }, - } - } - - /** - * Function onReady() : Called when this.dom is ready - */ - onReady() { - this.logger.startWith(this.constructor.name); - - this.events.onReady(); - - if (this._onReady) { - this._onReady(); - } - } - - /** - * function ready() : Set ready callback - * - * @param {{function()}} callback - */ - ready(callback) { - this.logger.startWith({ callback }); - - this.events.onReady = callback; - } - - /** - * Function createElement() : Create and render element. - */ - createElement() { - if (this._render) { - this.dom = $(this.render()); - - return this.dom; - } - - return null; - } - - /** - * Function destroyElement() : Destroy the element. - */ - destroyElement() { - if (this.dom) { - this.dom.remove(); - } - } - - /** - * Render the element. - */ - render() { - const element = $(this._render()); - - element.ready(this.onReady.bind(this)) - - return $(` -
    -
    - `).append(element); - } -} \ No newline at end of file diff --git a/js/modules/pageContainer.js b/js/modules/pageContainer.js deleted file mode 100644 index fe70c17..0000000 --- a/js/modules/pageContainer.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * @file: js/modules/pageContainer.js - * @author: Leonid Vinikov - * @description: used to mange pages - * @todo find wise alternative - */ - -import Services from '../services/services.js'; -import Modules from './modules.js'; - -export default class PageContainer { - - /** - * Function constructor() : Create Template Module - * - * @param {$} rootDom - */ - constructor(rootDom) { - this.logger = new Modules.Logger(`Modules.${this.constructor.name}`, true); - this.logger.setOutputHandler(Services.Terminal.onOutput); - - this.logger.startWith({ rootDom }); - - this.events = { - onReady: (pageModule) => { }, - } - - /** @type {Modules.Page} */ - this.pageModule = null; - - this.rootDom = rootDom; - } - - /** - * Function onLoad() : Called on Inner dom loaded - */ - onReady() { - this.logger.startEmpty() - - this.events.onReady(this.pageModule); - } - - /** - * function ready() : Set ready callback - * - * @param {{function()}} callback - */ - ready(callback) { - this.logger.startWith({ callback }); - - this.events.onReady = callback; - } - - /** - * Function set() : Set's pageModule - * - * @param {Modules.Page} pageModule - */ - set(pageModule) { - this.logger.startWith({ pageModule: pageModule.constructor.name }); - - if (this.pageModule) { - this.pageModule.destroyElement(); - } - - this.pageModule = pageModule; - - if (pageModule instanceof Modules.Page) { - this.render(); - } else { - this.logger.throw(`pageModule: is not instance of: 'Modules.Page`, 'pageModule', pageModule); - } - } - - /** - * Function render() : Re-render inner dom. - */ - render() { - this.logger.startEmpty() - - const element = this.pageModule.createElement(); - - element.ready(this.onReady.bind(this)); - - this.rootDom.empty(); - this.rootDom.append(element); - } -} \ No newline at end of file diff --git a/package.json b/package.json index 30b4f47..8315637 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "scripts": { - "webpack": "webpack", - "start": "http-server" + "watch": "webpack --watch --watchContentBase" }, "devDependencies": { "@babel/core": "^7.4.5", @@ -12,6 +11,6 @@ "webpack-cli": "^3.3.4" }, "dependencies": { - "@babel/polyfill": "^7.4.4" + "@babel/polyfill": "^7.7.0" } } diff --git a/readme.md b/readme.md index b392409..a2cc86e 100644 --- a/readme.md +++ b/readme.md @@ -12,9 +12,16 @@ - ES6 - Babel - Webpack + +## Goals + - Remove jQuery. + - Modular + - Well structured + - Apply MVC. # Code -Editor: vscode +Editor: PhpStrom + ## Rules: ### Code Access | Type | Modules | Services | Library @@ -32,28 +39,22 @@ Editor: vscode ![alt text](https://i.ibb.co/kHsq0dq/image.png) ### Live -http://138.201.155.5/leo123/shop-catalog/ +No hosting at the moment # frontend: -### es6 plain js: index.html (no extrernal libraries or freamworks except jQuery) -### babel with webpack: index_babel.html +``` +npm run watch +``` # backend: -### folder: api ## to start the backend ``` mysql mysql> create database market $ mysql -u market < market.sql -$ vim api/config/database.php +$ nano api/config/database.php $ php -S localhost:8080 ```` -## es6 http://localhost:8080/ -## babel http://localhost:8080/babel-index.html +## http://localhost:8080/frontend/ -## build for babel with the command -```` -$ npm install -$ npm run webpack -```` diff --git a/webpack.config.js b/webpack.config.js index 7072bf9..8f7dfa2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,12 +1,16 @@ -var path = require('path'); +const path = require('path'); module.exports = { - entry: { - app: './index.js' + mode: 'development', + devtool: 'inline-source-map', + watch: true, + entry: { + app: './frontend/app.js', + mvc: './dev/mvc/index.js' }, output: { - filename: 'app.bundle.js', - path: path.resolve(__dirname, 'js/dist') + filename: '[name].bundle.js', + path: path.resolve(__dirname, 'frontend/dist') }, module: { rules: [ @@ -22,8 +26,21 @@ module.exports = { } } ] - + }, + resolve: { + alias: { + 'API': path.resolve(__dirname, 'frontend/api'), + 'COMPONENTS': path.resolve(__dirname, 'frontend/components'), + 'CORE': path.resolve(__dirname, 'frontend/core'), + 'LIBRARY': path.resolve(__dirname, 'frontend/library'), + 'MODULES': path.resolve(__dirname, 'frontend/modules'), + 'PAGES': path.resolve(__dirname, 'frontend/pages'), + 'SERVICES': path.resolve(__dirname, 'frontend/services'), + 'DEV-LIBRARY': path.resolve(__dirname, 'dev/library'), + 'DEV-MODULES': path.resolve(__dirname, 'dev/modules'), + } + }, stats: { colors: true },