UNPKG

componentjs-mvc

Version:

Abstract Classes for Model-View-Controller Component Roles

358 lines (286 loc) 16.1 kB
ComponentJS MVC =============== Model-View-Controller Component Roles. <p/> <img src="https://nodei.co/npm/componentjs-mvc.png?downloads=true&stars=true" alt=""/> <p/> <img src="https://david-dm.org/rse/componentjs-mvc.png" alt=""/> About ----- [ComponentJS](https://componentjs.com/) is a generic hierarchical component orchestration library. It has no direct notion of any component roles, although it was designed with the roles of Ralf S. Engelschall's Model-View-Controller/Component-Tree (MVC/CT) pattern in mind. ComponentJS MVC is an opinionated companion library to [ComponentJS](https://componentjs.com/), providing ECMAScript 6 abstract classes for implementing the classes of ComponentJS backing objects by using the distinct Model, View or Controller roles of the MVC/CT pattern. The abstract classes map the major and essential parts of the ComponentJS API onto local methods which then provide the ComponentJS functionality in a way which is slightly adjusted for convenience reasons and their distinct usage under MVC/CT. ComponentJS MVC is intended to simplify the programming of HTML5 Single-Page-Apps (SPA) in an Object-Oriented Programming (OOP) manner. Installation ------------ ```shell # with the help of the NPM package manager (primary approach) $ npm install componentjs-mvc # with the help of the YARN package manager (alternative approach) $ yarn add componentjs-mvc # with the help of the Bower package manager (obsolete approach) $ bower install componentjs-mvc ``` Usage ----- ### Main Procedure: ```js import $ from "jquery" import cs from "componentjs" import "componentjs/component.plugin.vue.js" import mvc from "componentjs-mvc" import SV from "./service" class App { static main () { /* fire up ComponentJS */ cs.bootstrap() cs("/").property("sv", new SV()) /* fire up ComponentJS MVC */ mvc.jQuery = $ mvc.ComponentJS = cs mvc.Plugin() mvc.latch("mask:vue-options", ({ id, options }) => { /* example: provide id to Vue-I18Next Vue plugin */ options.i18nextNamespace = id }) mvc.latch("mask:vue-result", ({ id, mask }) => { /* example: integrate Perfect-Scroll-Bar jQuery plugin */ $(".perfect-scroll-bar", mask.$el).perfectScrollbar({}) }) } } ``` ### Dialog Component: ```js import mvc from "componentjs-mvc" class View extends mvc.View { ... } class Model extends mvc.Model { ... } class Controller extends mvc.Controller { ... } ``` Application Programming Interface (API) --------------------------------------- ### Globals: - `import mvc from "componentjs-mvc"`:<br/> Load the ComponentJS-MVC library. - `mvc.ComponentJS = ...`:<br/> Configure ComponentJS-MVC to use a particular instance of the mandatory ComponentJS framework. By default ComponentJS-MVC uses the global symbol `ComponentJS`. - `mvc.jQuery = ...`:<br/> Configure ComponentJS-MVC to use a particular instance of the optional jQuery library. By default ComponentJS-MVC uses the global symbol `jQuery`. jQuery is necessary only if the `mvc.View::$()` method should be used. jQuery and jQuery-Markup are necessary only if the `mvc.View::mask()` method should be used with neither `template` nor `render` options. - `mvc.Plugin()`:<br/> Hook into ComponentJS as an `mvc` plugin to automatically mark dynamically created ComponentJS-MVC-based components with the proper marker for the ComponentJS debugger without having to override the `create()` method and this way cause a `super.create()` burden on the users of the ComponentJS-MVC classes. Call this once in your main procedure if you want proper colors in the ComponentJS debugger for ComponentJS-MVC-based components. ### Hooks: - `mvc.latch("mask:vue-options", (({ id, options }) => { ... })`:<br/> Hook into the `mvc.View::mask()` method just before `mask = ComponentJS::vue(options)` is called internally. Use this to set particular options for VueJS. For instance, use `mvc.latch("mask:vue-options", ({ id, options }) => { options.i18nextNamespace = id })` to set the I18N namespace when using VueJS and the Vue-I18Next plugin together. - `mvc.latch("mask:vue-result", (({ id, result }) => { ... })`:<br/> Hook into the `mvc.View::mask()` method just after `mask = ComponentJS::vue(options)` is called internally. Use this to post-process the VueJS instance. For instance, use `mvc.latch("mask:vue-result", (({ id, result }) => { $(".perfect-scroll-bar", mask.$el).perfectScrollbar({ ... }) })` to apply the jQuery Perfect-Scrollbar plugin. - `mvc.latch("establish:post-create", ({ id, comp, obj }) => { ... })`:<br/> Hook into the `mvc.Component::establish()` method after each component was created. Use this to post-adjust created components. The `comp` is the created component, `obj` the attached backing object derived from the arguments to `mvc.Component::establish()`. - `mvc.latch("establish:pre-destroy", ({ id, comp, obj }) => { ... })`:<br/> Hook into the `mvc.Component::demolish()` method before each component will be destroyed. Use this to pre-process components. The `comp` is the created component, `obj` the attached backing object derived from the arguments to `mvc.Component::establish()`. ### Classes: - `class Component extends mvc.Component { ... }`:<br/> Define an application Component class based on ComponentJS-MVC's Component class (usually not directly used in regular applications, but exposed for completeness reasons). - `class View extends mvc.View { ... }`:<br/> Define an application View class based on ComponentJS-MVC's View class. The `mvc.View` class inherits from the `mvc.Component` class. - `class Model extends mvc.Model { ... }`:<br/> Define an application Model class based on ComponentJS-MVC's Model class. The `mvc.Model` class inherits from the `mvc.Component` class. - `class Controller extends mvc.Controller`:<br/> Define an application Controller class based on ComponentJS-MVC's Controller class. The `mvc.Controller` class inherits from the `mvc.Component` class. ### Methods (Component/View/Model/Controller Classes): - `mvc.Component::cs(...): ComponentJS`:<br/> Use the configured ComponentJS by just passing-through execution to ComponentJS. Use this in case you want to directly access the ComponentJS API from within a ComponentJS-MVC component. - `mvc.Component::establish(anchor?: Component, tree: String, classes: Object|Object[], autoincrease?: Boolean = true, autodecrease?: Boolean = false): mvc.Component`:<br/> This is a convenience wrapper around `ComponentJS::create()`. It internally basically calls `mvc.ComponentJS(this[, anchor]).create(tree, classes.map((Clz) => typeof Clz === "function" ? new Clz() : Clz))` and remembers the created components by their id for forthcoming use via `mvc.Component::my()`. It also calls `ComponentJS::state_auto_increase(autoincrease)` and `ComponentJS::state_auto_decrease(autodecrease)` on all created components. - `mvc.Component::demolish(id?: String): mvc.Component`:<br/> This is a convenience wrapper around `ComponentJS::destroy()`. It either destroys just the specified component (by id) or all components previously created with `mvc.Component::establish()`. - `mvc.Component::my(id: String): ComponentJS`:<br/> This fetches (by id) a particular component previously created with `mvc.Component::establish()`. - `mvc.Component::exists(id: String): ComponentJS`:<br/> This checks (by id) whether a particular component previously was created with `mvc.Component::establish()` and was still not destroyed with `mvc.Component::demolish()`. - `mvc.Component::state(...args: any[]): ComponentJS`:<br/> This is a convenience wrapper around `ComponentJS::state()`. The `args` positional arguments are just passed-through. The return value is the value of `ComponentJS::state()`. - `mvc.Component::guard(...args: any[]): ComponentJS`:<br/> This is a convenience wrapper around `ComponentJS::guard()`. The `args` positional arguments are just passed-through. The return value is the value of `ComponentJS::guard()`. - `mvc.Component::await(...args: any[]): ComponentJS`:<br/> This is a convenience wrapper around `ComponentJS::await()`. The `args` positional arguments are just passed-through. The return value is the value of `ComponentJS::await()`. - `mvc.Component::spool(func: Function, options?: Object): mvc.Component`:<br/> This is a convenience wrapper around `ComponentJS::spool()`. It internally basically calls `mvc.ComponentJS(this).spool({ name: mvc.ComponentJS(this).state(), ctx: this, func: func, ...options })`. The twist of this wrapper is that it automatically uses the component state based spool. - `mvc.Component::observe(name: String|String[], func: Function, options?: Object): mvc.Component`:<br/> This is a convenience wrapper around `ComponentJS::observe()`. It internally basically calls `mvc.ComponentJS(this[, "model"]) .observe({ name, func, spool: mvc.ComponentJS(this).state(), noevent: true, ...options })`. The twists of this wrapper are: it performs automatical spooling, uses no event argument in the `func` callback and automatically steps down to a `model` child if `this` is a `mvc.Controller`. Additionally, instead of a single `name`, it allows you to pass an array of names. - `mvc.Component::value(...args: any[]): any`:<br/> This is a convenience wrapper around `ComponentJS::value()`. It internally basically calls `mvc.ComponentJS(this[, "model"]) .value(...args)`. The main twist of this wrapper is: it automatically steps down to a `model` child if `this` is a `mvc.Controller`. - `mvc.Component::touch(...args: any[]): mvc.Component`:<br/> This is a convenience wrapper around `ComponentJS::touch()`. It internally basically calls `mvc.ComponentJS(this[, "model"]) .touch(...args)`. The main twist of this wrapper is: it automatically steps down to a `model` child if `this` is a `mvc.Controller`. - `mvc.Component::subscribe(name: String|String[], func: Function, options?: Object): mvc.Component`:<br/> This is a convenience wrapper around `ComponentJS::subscribe()`. It internally basically calls `mvc.ComponentJS(this).subscribe({ name, func, spool: mvc.ComponentJS(this).state(), noevent: true, capturing: false, spreading: false, bubbling: true, ...options })`. The twists of this wrapper are: it performs automatical spooling, uses no event argument in the `func` callback, disables capturing and spreading, and enables bubbling. Additionally, instead of a single `name`, it allows you to pass an array of names. - `mvc.Component::publish(name: String, args: any[], options?: Object): any`:<br/> This is a convenience wrapper around `ComponentJS::publish()`. It internally basically calls `mvc.ComponentJS(this).publish({ name, args, directresult: true, capturing: true, spreading: false, bubbling: true, ...options })`. The twists of this wrapper are: enables direct results, enables capturing and bubbling and disables spreading. - `mvc.Component::register(name: String|String[], func: Function, options?: Object): mvc.Component`:<br/> This is a convenience wrapper around `ComponentJS::register()`. It internally basically calls `mvc.ComponentJS(this).register({ name, func, spool: mvc.ComponentJS(this).state(), capturing: false, spreading: false, bubbling: true, ...options })`. The twists of this wrapper are: it performs automatical spooling, disables capturing and spreading, and enables bubbling. Additionally, instead of a single `name`, it allows you to pass an array of names. - `mvc.Component::call(name: String, args: any[], options?: Object): any`:<br/> This is a convenience wrapper around `ComponentJS::call()`. It internally basically calls `mvc.ComponentJS(this).call({ name, args, capturing: true, spreading: false, bubbling: true, ...options })`. The twists of this wrapper are: it enables capturing, disabled spreading and enables bubbling. ### Methods (View Class): - `mvc.View::$(selector: String, baseElement?: DOMElement): jQuery`:<br/> Use the configured jQuery by just passing-through execution to jQuery. Use this in case you want to directly manipulate the DOM via jQuery from within a View component. Use with caution, as jQuery and VueJS can conflict. - `mvc.View::mask(id: String, options?: any): VueJS`:<br/> Create a UI Mask with the help of VueJS. The `id` has to be unique within the UI, although it is not directly used by ComponentJS-MVC itself. It is intended to be passed through to the `mask:vue-options` hook and used there accordingly. The `options` are just passed-through to the `ComponentJS::vue()` method. Hence, this method requires the ComponentJS `vue` plugin to be loaded first. - `mvc.View::socket(ctx: Object, plug: Function, unplug: Function): Number`:<br/> `mvc.View::socket(options: { ctx: Object, plug?: Function, unplug?: Function, ... }): Number`:<br/> This is a convenience wrapper around `ComponentJS::socket()`. The `ctx`, `plug` and `unplug` positional arguments are just converted to the `options` form of method calling. If the `options` argument has no `spool` field, it is automatically created with the value of `mvc.ComponentJS(this).state()`. The return value is the value of `ComponentJS::socket()`. - `mvc.View::link(target: Object, socket: String): Number`:<br/> `mvc.View::link(options: { target: Object, socket: String, ... }): Number`:<br/> This is a convenience wrapper around `ComponentJS::link()`. The `target` and `socket` positional arguments are just converted to the `options` form of method calling. If the `options` argument has no `spool` field, it is automatically created with the value of `mvc.ComponentJS(this).state()`. The return value is the value of `ComponentJS::link()`. - `mvc.View::plug(object: Object): Number`:<br/> `mvc.View::plug(options: { object: Object, ... }): Number`:<br/> This is a convenience wrapper around `ComponentJS::plug()`. The `object` positional argument is just converted to the `options` form of method calling. If the `options` argument has no `spool` field, it is automatically created with the value of `mvc.ComponentJS(this).state()`. The return value is the value of `ComponentJS::plug()`. ### Methods (Model Class): - `mvc.Model::model(spec: Object): Void`:<br/> This just passes-through `spec` to the `ComponentJS::model()` method. ### Methods (Controller Class): - `mvc.Controller::sv(): Object`:<br/> This is a short-hand for `mvc.ComponentJS(this).property("sv")` and simplifys the fetching of the underlying Service API, which beforehand should be placed into a property on the root component via `mvc.ComponentJS("/").property("sv", value)`. License ------- Copyright (c) 2016-2018 Ralf S. Engelschall (http://engelschall.com/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.