',
37 | appearance: 'primary',
38 | text: '',
39 | dismissible: false,
40 | linkPlaceHolder: '##',
41 | parseTemplate: _parseTemplate,
42 | autoDestroy: true,
43 | handlers: [{
44 | on: 'close.bs.alert',
45 | fn: 'onClose'
46 | }, {
47 | on: 'closed.bs.alert',
48 | fn: 'onClosed'
49 | }],
50 | close: function() {
51 | $(this.html).alert('close');
52 | },
53 | destroy: function() {
54 | $(this.html).alert('dispose');
55 | this.base(arguments);
56 | },
57 | onClose: function(e) {
58 | this.publish('close-bs-alert', e);
59 | },
60 | onClosed: function(e) {
61 | this.publish('closed-bs-alert', e);
62 | if(this.autoDestroy) {
63 | this.destroy();
64 | }
65 | }
66 | };
67 | };
68 |
69 | export default uiBase.extend(alert);
70 |
--------------------------------------------------------------------------------
/packages/http/src/index.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import { types, optionsRequred, requests } from './constants';
3 | import factory from './factory';
4 | import request from './request';
5 |
6 | const pzHttp = () => {
7 |
8 | return {
9 | latestRequestId: null,
10 | defaultDataType: 'json',
11 | request: function (options) {
12 |
13 | if (pz.isEmpty(options)) {
14 | throw new Error(optionsRequred);
15 | };
16 |
17 | factory.checkMinimalConfiguration(options);
18 |
19 | options.dataType = options.dataType || this.defaultDataType;
20 | let req = new request(options);
21 |
22 | this.latestRequestId = req.id;
23 | factory.configureAndInvokeXHR(req);
24 | requests[req.id] = req;
25 | return req;
26 | },
27 | abort: function (all) {
28 | let abortAll = all || false, requestIds;
29 | let requestToAbort = abortAll ? requests :
30 | requests[this.latestRequestId];
31 |
32 | if (pz.isEmpty(requestToAbort)) {
33 | return;
34 | };
35 |
36 | if (!abortAll) {
37 | requestToAbort.abort();
38 | delete requests[this.latestRequestId];
39 | this.latestRequestId = null;
40 | return;
41 | };
42 |
43 | requestIds = Object.keys(requestToAbort);
44 | pz.forEach(requestIds, function (id) {
45 | let req = requests[id];
46 | req.abort();
47 | delete requests[id];
48 | req = null;
49 | }, this);
50 |
51 | requestToAbort = null;
52 | this.latestRequestId = null;
53 | },
54 | post: function (options) {
55 | options.method = types.post;
56 | return this.request(options);
57 | },
58 | get: function (options) {
59 | options.method = types.get;
60 | return this.request(options);
61 | },
62 | put: function (options) {
63 | options.method = types.put;
64 | return this.request(options);
65 | },
66 | delete: function (options) {
67 | options.method = types.delete;
68 | return this.request(options);
69 | }
70 | };
71 | };
72 |
73 | export default {
74 | init: () => {
75 | pz.http = pzHttp();
76 | }
77 | };
--------------------------------------------------------------------------------
/packages/core/src/statics/binder/binding.js:
--------------------------------------------------------------------------------
1 | import pz from '../../core';
2 | import reservedKeys from './reserved-keys';
3 | import { buildContext, pathToParts } from './util';
4 |
5 | class binding {
6 | _parseAlias(keypath) {
7 | let as = keypath.indexOf(reservedKeys.as) != -1,
8 | parts, result = { keypath: keypath, alias: null };
9 |
10 | if (!as) {
11 | return result;
12 | }
13 |
14 | parts = keypath.split(reservedKeys.as);
15 | result.keypath = parts.shift().trim();
16 | result.alias = parts.pop().trim();
17 | parts = null;
18 | return result;
19 | }
20 | constructor(el, type, keypath, bindingAttr, view) {
21 | let result = this._parseAlias(keypath);
22 |
23 | this.id = pz.guid();
24 | this.el = el;
25 | this.view = view;
26 | this.type = type;
27 | this.keypath = result.keypath.trim();
28 | this.prop = pathToParts(result.keypath).pop();
29 | this.alias = type == 'each' && !pz.isEmpty(result.alias) ?
30 | { name: result.alias, path: (this.prop + '[{0}]') } : null;
31 | this.bindingAttr = bindingAttr;
32 | this.rootVm = view.vm;
33 | this.vm = buildContext(result.keypath, view);
34 | this.binder = pz.binder.binders[this.type];
35 | this.handler = this.binder.handler ? pz.proxy(this.binder.handler, this) : undefined;
36 | view = null;
37 | return this;
38 | }
39 | bind() {
40 |
41 | let observer = this.vm[this.prop];
42 |
43 | if (this.binder.bind) {
44 | this.binder.bind.call(this);
45 | }
46 |
47 | if (this.binder.react && observer && observer.subscribe) {
48 | (function (me, obs) {
49 | obs.subscribe(function () {
50 | me.binder.react.call(me);
51 | }, me.id);
52 | })(this, observer);
53 | }
54 |
55 | if (this.binder.react) {
56 | this.binder.react.call(this);
57 | }
58 | }
59 | unbind() {
60 | let observer = this.vm[this.prop];
61 | if (observer && observer.unsubscribe) {
62 | observer.unsubscribe(this.id);
63 | }
64 | if (pz.isFunction(this.binder.unbind)) {
65 | this.binder.unbind.call(this);
66 | }
67 | }
68 | getValue() {
69 |
70 | let prop, isFn;
71 |
72 | if (this.prop == reservedKeys.current) {
73 | return this.vm;
74 | }
75 |
76 | if (this.prop == reservedKeys.idx) {
77 | return this.view.index;
78 | }
79 |
80 | prop = this.vm[this.prop];
81 | isFn = pz.isFunction(prop);
82 | return isFn && this.type != 'on' ?
83 | this.vm[this.prop].call(this) : this.vm[this.prop];
84 | }
85 | setValue(value) {
86 | return this.vm[this.prop] = value;
87 | }
88 | }
89 |
90 | export default binding;
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/base/ui-base.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import $ from 'jquery';
3 |
4 | let bootstrapUiBase = () => {
5 |
6 | let _const = {
7 | handlerFnNotProvided: 'Handler function was not provided.'
8 | };
9 |
10 | let _initPlugin = (me, type) => {
11 | let plugin = me[type];
12 | if (pz.isEmpty(plugin) || !pz.isObject(plugin)) {
13 | return;
14 | };
15 |
16 | $(me.html)[type](plugin);
17 | };
18 |
19 | return {
20 | type: 'ui-bootstrap-component',
21 | ownerType: 'component',
22 | constructor: function() {
23 |
24 | let me = this;
25 | let sub = this.subscribe('render-complete', () => {
26 |
27 | if (pz.isFunction(me.parseTemplate) && !pz.isEmpty(me.template)) {
28 | me.parseTemplate();
29 | };
30 |
31 | me.prependLabel();
32 | me.initToolTip();
33 | me.initPopOver();
34 | sub.remove();
35 | });
36 |
37 | this.base(arguments);
38 | },
39 |
40 | prependLabel: function(template) {
41 | if (pz.isEmpty(this.labelText)) {
42 | return;
43 | };
44 |
45 | let label = pz.dom.createElement('label');
46 | label.innerText = this.labelText;
47 | this.addCss('col-form-label', label);
48 | pz.dom.insertBefore(this.html, label);
49 | label = null;
50 | },
51 |
52 | initToolTip: function() {
53 | _initPlugin(this, 'tooltip');
54 | },
55 |
56 | initPopOver: function() {
57 | _initPlugin(this, 'popover');
58 | },
59 |
60 | handle: function(handler) { // override handlers binding since we need bootstrap/jquery custom events
61 | let me = this, $html = $(this.html);
62 | let fn = pz.isFunction(handler.fn) ? handler.fn : me[handler.fn];
63 |
64 | if (pz.isEmpty(fn)) {
65 | throw new Error(_const.handlerFnNotProvided);
66 | };
67 |
68 | let hasSelector = !pz.isEmpty(handler.selector);
69 | let args = hasSelector ? [handler.on, handler.selector, pz.proxy(fn, handler.scope || me)] :
70 | [handler.on, pz.proxy(fn, handler.scope || me)];
71 | $html.on.apply($html, args);
72 | },
73 |
74 | destroy: function() {
75 | $(this.html).tooltip('dispose')
76 | .popover('dispose');
77 | $(this.html).off();
78 | this.base(arguments);
79 | }
80 | };
81 | };
82 |
83 | export default pz.component.extend(bootstrapUiBase);
84 |
--------------------------------------------------------------------------------
/packages/http/src/factory.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import response from './response';
3 | import { minConfigNotProfided, requestStates, requestStatus, requests } from './constants';
4 |
5 | class factory {
6 | static createXHR() {
7 | // TODO: Add support for active x object?
8 | // ActiveXObject('MSXML2.XMLHTTP.3.0');
9 | // ActiveXObject('MSXML2.XMLHTTP');
10 | // ActiveXObject('Microsoft.XMLHTTP');
11 | return new XMLHttpRequest();
12 | }
13 | static checkMinimalConfiguration(options) {
14 | let isOK = !pz.isEmpty(options.url) &&
15 | !pz.isEmpty(options.method);
16 |
17 | if (!isOK) {
18 | throw new Error(minConfigNotProfided);
19 | };
20 | }
21 | static configureAndInvokeXHR(request) {
22 | let options = request.options, xhr = request.xhr, callback = options.success,
23 | eCallback = options.fail, aCallback = options.abort,
24 | dataType = options.dataType;
25 |
26 | xhr.onreadystatechange = function () {
27 |
28 | if (this.readyState == requestStates.done
29 | && this.status == requestStatus.abort) {
30 | return;
31 | };
32 |
33 | if (this.readyState == requestStates.done && pz.isFunction(callback)) {
34 | let result = new response(this, dataType);
35 | callback(result);
36 | delete requests[request.id];
37 | };
38 | };
39 |
40 | xhr.onerror = function (e) {
41 | delete requests[request.id];
42 |
43 | if (pz.isFunction(eCallback)) {
44 | eCallback(e.target);
45 | } else {
46 | throw new Error(e.target.statusText);
47 | };
48 | };
49 |
50 | xhr.onabort = function (e) {
51 | if (pz.isFunction(aCallback)) {
52 | aCallback(e);
53 | };
54 | };
55 |
56 | xhr.ontimeout = function (e) {
57 | if (pz.isFunction(aCallback)) {
58 | aCallback(e);
59 | };
60 | };
61 |
62 | request.parseUrlParams();
63 |
64 | xhr.open(options.method, options.url, true,
65 | !pz.isEmpty(options.username) ? options.username : null,
66 | !pz.isEmpty(options.password) ? options.password : null);
67 |
68 | request.setHeaders();
69 | request.setXHROptions();
70 |
71 | if (pz.isObject(options.data)) {
72 | request.setHeaders({
73 | 'Content-Type': 'application/json;charset=UTF-8'
74 | });
75 | options.data = pz.toJSON(options.data, false, true);
76 | };
77 |
78 | xhr.send(options.data || null);
79 | }
80 | }
81 |
82 | export default factory;
--------------------------------------------------------------------------------
/demo/bulma/scripts/components/widgets/demo-grid.component.js:
--------------------------------------------------------------------------------
1 | pz.define('grid-component', function() {
2 |
3 | return {
4 | ownerType: 'base-component',
5 | mixins: ['page-mixin'],
6 | template: '
' +
7 | '
{title}
' +
8 | '
' +
9 | '
' +
12 | '
' +
15 | '
No data available
' +
16 | '
' +
17 | '
',
18 | renderTo: 'root', // default renderTo, this can be overridden when adding this component as a child
19 | viewModel: {
20 | title: 'Uncompleted TODOS',
21 | columns: [],
22 | data: [],
23 | getColumnValue: function() {
24 | var row = this.rootVm.data[this.view.parent.index],
25 | column = this.rootVm.columns[this.view.index];
26 | return row[column.dataIndex()]();
27 | }
28 | },
29 | init: function() {
30 | var me = this;
31 | this.viewModel.columns = this.columns;
32 | this.loadData();
33 |
34 | this.subscribe('todo-added', function(todo) {
35 | var jTodo = pz.binder.toJSON(todo);
36 | me.addTodos([jTodo]);
37 | });
38 |
39 | this.subscribe('todo-updated', function(todo) {
40 | me.loadData();
41 | });
42 |
43 | this.subscribe('todo-deleted', function() {
44 | me.loadData();
45 | });
46 |
47 | this.base(arguments);
48 | },
49 | loadData: function() {
50 | pz.arr.clear(this.viewModel.data);
51 | var uncompletedTodos = this.todoService.getUnCompleted();
52 | this.addTodos(uncompletedTodos);
53 | },
54 | addTodos: function(todos) {
55 | var keys;
56 | if(pz.isEmpty(todos)) {
57 | return;
58 | };
59 | keys = pz.obj.getKeys(todos[0]);
60 | pz.forEach(todos, function(todo) {
61 | var row = {};
62 | pz.forEach(keys, function(key) {
63 | row[key.toLowerCase()] = pz.isFunction(todo[key]) ? todo[key](): todo[key];
64 | }, this);
65 | this.viewModel.data.push(row);
66 |
67 | }, this);
68 | },
69 | require: ['todo-service']
70 | };
71 |
72 | });
--------------------------------------------------------------------------------
/packages/core/src/statics/binder/view.js:
--------------------------------------------------------------------------------
1 | import pz from '../../core';
2 | import textParser from './parser';
3 | import binding from './binding';
4 |
5 | class view {
6 | _parseAttrName(name) {
7 | let startIdx, endIdx;
8 | let inBrackets = ((startIdx = name.indexOf('[')) != -1) &&
9 | ((endIdx = name.indexOf(']')) != -1), attrToBind, parts;
10 |
11 | if (!inBrackets) {
12 | return name.split('-');
13 | }
14 |
15 | attrToBind = name.substring((startIdx + 1), endIdx);
16 | name = name.replace('-[' + attrToBind + ']', '');
17 | parts = name.split('-');
18 | parts.push(attrToBind);
19 | return parts;
20 | }
21 | constructor(el, vm, ctx, index, alias, parent) {
22 | let parentEmpty = pz.isEmpty(parent);
23 |
24 | this.els = pz.isArray(el) || pz.isNodeList(el) ? el : [el];
25 | this.alias = {};
26 | this.vm = vm;
27 | this.ctx = !pz.isEmpty(ctx) ? ctx : null;
28 | this.index = !pz.isEmpty(index) ? index : null;
29 | this.parent = !parentEmpty ? parent : null;
30 | this._bindingRegex = new RegExp('^' + pz.binder.prefix + '-', 'i');
31 |
32 | if (!pz.isEmpty(alias)) {
33 | if (!parentEmpty && parent.alias.hasOwnProperty(alias.name)) {
34 | throw new Error('Alias name must be unique.');
35 | }
36 | this.alias[alias.name] = pz.str.format(alias.path, this.index);
37 | }
38 |
39 | pz.assignTo(this.alias, (!parentEmpty ? parent.alias : {}), false);
40 | this.buildBindings();
41 |
42 | vm = null;
43 | return this;
44 | }
45 | bind() {
46 | pz.forEach(this.bindings, function (binding) {
47 | binding.bind();
48 | });
49 | }
50 | unbind() {
51 | pz.forEach(this.bindings, function (binding) {
52 | binding.unbind();
53 | });
54 | }
55 | buildBindings() {
56 | this.bindings = [];
57 |
58 | let build = (function (me) {
59 | return function (els) {
60 | pz.forEach(els, function (el) {
61 | let isBlock = (el.hasAttribute && el.hasAttribute(pz.binder.prefix + '-each')),
62 | attrs = isBlock ? [el.getAttributeNode(pz.binder.prefix + '-each')] : (el.attributes || []);
63 |
64 | pz.forEach(attrs, function (attr) {
65 | if (me._bindingRegex.test(attr.name)) {
66 | let parts = me._parseAttrName(attr.name);
67 | let bType = parts[1], attrToBind = parts[2];
68 |
69 | if (!pz.isEmpty(pz.binder.binders[bType])) {
70 | let b = new binding(el, bType.toLowerCase(), attr.value, attr.name, me);
71 | if (attrToBind) { b.attrToBind = attrToBind; }
72 |
73 | me.bindings.push(b);
74 | isBlock = isBlock || b.binder.block;
75 | b = null;
76 | }
77 | }
78 | });
79 |
80 | if (!isBlock) {
81 | pz.forEach(el.childNodes, function (childNode) {
82 | build([childNode]);
83 | textParser.parse.call(me, childNode);
84 | });
85 | }
86 | });
87 | };
88 | })(this);
89 |
90 | build(this.els);
91 | this.bindings.sort(function (a, b) {
92 | return a.binder.priority - b.binder.priority;
93 | });
94 | }
95 | }
96 |
97 | export default view;
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-grid.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import uiBase from '../base/ui-base.component';
3 |
4 | const grid = () => {
5 |
6 | let _defaultColSize = 12;
7 |
8 | let _getColumnSizeClass = (size) => {
9 | let _default = 'col-' + _defaultColSize,
10 | lg, md, sm;
11 |
12 | if (pz.isEmpty(size)) {
13 | return _default;
14 | };
15 |
16 | lg = !pz.isEmpty(size.lg) ? 'col-lg-' + size.lg : '';
17 | md = !pz.isEmpty(size.md) ? ' col-md-' + size.md : '';
18 | sm = !pz.isEmpty(size.sm) ? ' col-sm-' + size.sm : '';
19 |
20 | let css = lg + md + sm;
21 | return !pz.isEmpty(css) ?
22 | css : _default;
23 | };
24 |
25 | let _parseTemplate = function() {
26 | let me = this;
27 | this.addCss((this.fluid ? 'container-fluid' : 'container'));
28 |
29 | pz.forEach(this.rows, (row, idx) => {
30 | let rowEl = pz.dom.createElement('div'),
31 | generateRowId = !pz.isEmpty(row.id) || row.generateId;
32 |
33 | if(generateRowId) {
34 | me.addAttr({
35 | name: 'id',
36 | value: row.id || ('row-' + idx)
37 | }, rowEl);
38 | };
39 |
40 | me.addCss('row', rowEl);
41 | if (!pz.isEmpty(row.css)) {
42 | me.addCss(row.css.join(' '), rowEl);
43 | };
44 | pz.dom.append(me.html, rowEl);
45 |
46 | pz.forEach(row.columns, (column, idx) => {
47 | let sizeClass = _getColumnSizeClass(column.size),
48 | columnEl = pz.dom.createElement('div'),
49 | generateColumnId = !pz.isEmpty(column.id) || column.generateId;
50 |
51 | me.addCss(sizeClass, columnEl);
52 | if (!pz.isEmpty(column.css)) {
53 | me.addCss(column.css, columnEl);
54 | };
55 |
56 | if(generateColumnId) {
57 | me.addAttr({
58 | name: 'id',
59 | value: column.id || ('column-' + idx)
60 | }, columnEl);
61 | };
62 |
63 | columnEl.innerHTML = !pz.isEmpty(column.text) ?
64 | column.text : '';
65 |
66 | pz.dom.append(rowEl, columnEl);
67 | });
68 | });
69 | };
70 |
71 | return {
72 | type: 'ui-bootstrap-grid',
73 | ownerType: 'ui-bootstrap-component',
74 | fluid: false,
75 | rows: [{
76 | generateId: false,
77 | css: [],
78 | columns: [{
79 | generateId: false,
80 | text: '',
81 | size: _defaultColSize,
82 | css: []
83 | }]
84 | }],
85 | template: '
',
86 | parseTemplate: _parseTemplate
87 | };
88 | };
89 |
90 | export default uiBase.extend(grid);
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-navbar.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import uiBase from '../base/ui-base.component';
3 |
4 | const navBar = () => {
5 |
6 | let _allowedComponents = [
7 | 'ui-bootstrap-dropdown'
8 | //'ui-bootstrap-input-group'
9 | ];
10 |
11 | let _parseTemplate = function() {
12 | let prefix = this.sticky ? 'sticky' : 'fixed';
13 | let hasMenuItems = !pz.isEmpty(this.menu) && !pz.isEmpty(this.menu.items);
14 |
15 | this.toggler = this.toggler || hasMenuItems;
16 |
17 | if (!pz.isEmpty(this.brand)) {
18 | let isTextType = this.brand.type == 'text';
19 | let brand = pz.dom.createElement('a');
20 |
21 | brand.setAttribute('href', (this.brand.href || '#'));
22 | this.addCss('navbar-brand', brand);
23 |
24 | if (isTextType) {
25 | brand.innerHTML = this.brand.value;
26 | } else {
27 | let brandImg = pz.dom.createElement('img');
28 | brandImg.setAttribute('src', this.brand.imageSrc);
29 | pz.dom.append(brand, brandImg);
30 | };
31 |
32 | pz.dom.append(this.html, brand);
33 | };
34 |
35 | if (this.toggler) {
36 | pz.dom.append(this.html, '
');
37 | };
38 |
39 | if (hasMenuItems) {
40 | pz.dom.append(this.html, '
');
41 |
42 | let collapse = pz.dom.findElement(this.html, 'div#collapse_' + this.id);
43 | let menuPos = this.menu.position || 'left'; // left by default
44 | let hPositionClass = 'm'.concat(menuPos == 'left' ? 'r-' : 'l-').concat('auto');
45 |
46 | pz.dom.append(collapse, '
');
47 |
48 | let ul = pz.dom.findElement(collapse, 'ul.navbar-nav');
49 | pz.forEach(this.menu.items, (menuItem) => {
50 | if (pz.arr.contains(_allowedComponents, menuItem.type)) {
51 | menuItem.renderTo = 'ul.navbar-nav';
52 | this.components.push(menuItem);
53 | } else {
54 | pz.dom.append(ul, '
' + menuItem.text + ' ');
55 | };
56 | }, this);
57 | };
58 |
59 | this.addCss('navbar '.concat(prefix).concat('-')
60 | .concat(this.position).concat(' bg-')
61 | .concat(this.theme).concat(' navbar-expand-lg'));
62 | };
63 |
64 | return {
65 | type: 'ui-bootstrap-navbar',
66 | ownerType: 'ui-bootstrap-component',
67 | position: 'top',
68 | template: '
',
69 | theme: 'light',
70 | components: [],
71 | menu: {},
72 | brand: {
73 | type: 'text',
74 | value: 'My app'
75 | },
76 | sticky: false,
77 | toggler: false,
78 | parseTemplate: _parseTemplate
79 | };
80 | };
81 |
82 | export default uiBase.extend(navBar);
--------------------------------------------------------------------------------
/packages/core/src/statics/binder/parser.js:
--------------------------------------------------------------------------------
1 | import pz from '../../core';
2 | import reservedKeys from './reserved-keys';
3 | import { buildContext, pathRegex, pathToParts } from './util';
4 |
5 | const textParser = {
6 | regex: null,
7 | setRegex: () => {
8 | if (pz.isEmpty(textParser.regex)) {
9 | textParser.regex =
10 | new RegExp(pz.str.format('{0}([^{1}]*){2}',
11 | pz.binder.delimiters[0],
12 | pz.binder.delimiters[1],
13 | pz.binder.delimiters[1]),
14 | 'g');
15 | }
16 | },
17 | parse: function (el) {
18 |
19 | let hasInterpolations,
20 | keypaths, updateContent, elData;
21 |
22 | if (el.nodeType != 3 || el.textContent.length == 0) {
23 | return;
24 | }
25 |
26 | hasInterpolations = (el.textContent.indexOf(pz.binder.delimiters[0]) != -1 &&
27 | el.textContent.indexOf(pz.binder.delimiters[1]) != -1);
28 |
29 | if (!hasInterpolations) {
30 | return;
31 | }
32 |
33 | textParser.setRegex();
34 | keypaths = [];
35 |
36 | updateContent = (function (me, _vm) {
37 | return function (data, parsed) {
38 | data.el.textContent = data.tpl.replace(textParser.regex, function (template, value) {
39 | let isPath, val, vmValue, curr, idx, ctx;
40 |
41 | value = value.replace(/ +?/g, '');
42 | curr = value.indexOf(reservedKeys.current) != -1;
43 | idx = value.indexOf(reservedKeys.idx) != -1;
44 | vmValue = (!curr ? (!idx ? _vm[value] : me.index) : _vm);
45 | isPath = pathRegex.test(value);
46 |
47 | if (isPath && !curr && !idx) {
48 | val = pathToParts(value).pop();
49 | vmValue = ((me.ctx && me.ctx[val]) || me.vm[val]);
50 | if (pz.isEmpty(vmValue)) {
51 | ctx = buildContext(value, me);
52 | vmValue = !pz.isEmpty(ctx) ? ctx[val] : undefined;
53 | }
54 | val = null;
55 | }
56 |
57 | if (!parsed) {
58 | keypaths.push(value);
59 | }
60 |
61 | let result = (!pz.isEmpty(vmValue) ?
62 | (pz.isFunction(vmValue) ? vmValue() : vmValue) : template);
63 | vmValue = null;
64 | return result;
65 | });
66 | };
67 | })(this, this.ctx || this.vm);
68 |
69 | if (!this.elsData) {
70 | this.elsData = [];
71 | }
72 |
73 | elData = {
74 | el: el,
75 | tpl: el.textContent.trim()
76 | };
77 |
78 | this.elsData.push(elData);
79 | updateContent(elData, false);
80 |
81 | (function (me, elsData) {
82 | pz.forEach(keypaths, function (keypath) {
83 | let ctx = buildContext(keypath, me), observer;
84 | let prop = pathToParts(keypath).pop();
85 |
86 | if (pz.isEmpty(ctx) || pz.isEmpty(ctx[prop])) {
87 | return;
88 | }
89 |
90 | observer = ctx[prop];
91 |
92 | if (observer && observer.subscribe) {
93 | observer.subscribe(function () {
94 | pz.forEach(elsData, function (data) {
95 | updateContent(data, true);
96 | });
97 | });
98 | }
99 | ctx = null;
100 | });
101 | })(this, this.elsData);
102 |
103 | keypaths.splice(0, keypaths.length);
104 | this.elsData.splice(0, keypaths.length);
105 | }
106 | };
107 |
108 | export default textParser;
--------------------------------------------------------------------------------
/packages/core/__tests__/components/components.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import pz from '../../src';
4 |
5 | describe('components', () => {
6 | let componentDef, component, componentReconfigured,
7 | childComponentDef;
8 |
9 | beforeAll(() => {
10 | componentDef = pz.define('test-component', {
11 | ownerType: 'component',
12 | renderTo: 'body',
13 | template: '
{message}
',
14 | viewModel: {
15 | message: 'foo'
16 | }
17 | });
18 |
19 | childComponentDef = pz.define('child-component', {
20 | ownerType: 'component',
21 | template: '
{message} ',
22 | viewModel: {
23 | message: 'Child foo!'
24 | }
25 | });
26 |
27 | component = componentDef.create();
28 | componentReconfigured = componentDef.create({
29 | viewModel: {
30 | message: 'bar'
31 | }
32 | });
33 | });
34 |
35 | it('should have $type, create and extend defined', () => {
36 | expect(componentDef.$type).toBeDefined();
37 | expect(componentDef.$type).toBe('test-component');
38 | expect(componentDef.create).toBeDefined();
39 | expect(componentDef.extend).toBeDefined();
40 | });
41 |
42 | it('should have life cycle methods defined', () => {
43 | expect(component.load).toBeDefined();
44 | expect(component.render).toBeDefined();
45 | expect(component.init).toBeDefined();
46 | expect(component.destroy).toBeDefined();
47 | });
48 |
49 | it('should load and initialize the component', () => {
50 | expect(component.isComponentInstance).toBe(true);
51 |
52 | component.load();
53 |
54 | expect(component.initialized).toBe(true);
55 | expect(component.html).toBeDefined();
56 | expect(component.html.innerHTML).toBe('foo');
57 |
58 | component.viewModel.message = 'bar';
59 | expect(component.html.innerHTML).toBe('bar');
60 | });
61 |
62 | it('should add/locate a child component', () => {
63 | expect(component.isComponentInstance).toBe(true);
64 | expect(component.addChild).toBeDefined();
65 | expect(component.traceDown).toBeDefined();
66 |
67 | component.load();
68 | let childComponent = component.addChild(childComponentDef);
69 |
70 | expect(childComponent).toBeDefined();
71 | expect(childComponent.isComponentInstance).toBe(true);
72 | expect(childComponent.initialized).toBe(true);
73 | expect(childComponent.html).toBeDefined();
74 | expect(childComponent.html.innerHTML).toBe('Child foo!');
75 |
76 | expect(childComponent.traceUp).toBeDefined();
77 | expect(childComponent.parentComponent).toBeDefined();
78 | expect(childComponent.parentComponent.type).toBe('test-component');
79 | expect(childComponent.traceUp().id).toBe(component.id);
80 |
81 | expect(component.components.length).toBe(1);
82 | expect(component.components[0].id).toBe(childComponent.id);
83 | expect(component.components[0].type).toBe('child-component');
84 | expect(component.traceDown('child-component').id).toBe(childComponent.id);
85 | });
86 |
87 | it('should re-configure the component', () => {
88 | expect(componentReconfigured.isComponentInstance).toBe(true);
89 | expect(componentReconfigured.type).toBe(component.type);
90 |
91 | componentReconfigured.load();
92 |
93 | expect(componentReconfigured.initialized).toBe(true);
94 | expect(componentReconfigured.html).toBeDefined();
95 | expect(componentReconfigured.html.innerHTML).toBe('bar');
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-input-group.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import uiBase from '../base/ui-base.component';
3 |
4 | const inputGroup = () => {
5 |
6 | let _const = {
7 | addonEmpty: 'Component of type [ui-bootstrap-input-group] requires at least one addon. See addon config docs.',
8 | unsupportedInputType: 'Provided input type is not supported. Please use one of the following: \'ui-bootstrap-input\' or \'ui-bootstrap-select\''
9 | };
10 |
11 | let _allowedComponents = [
12 | 'ui-bootstrap-button',
13 | 'ui-bootstrap-dropdown',
14 | 'ui-bootstrap-input'
15 | ];
16 |
17 | let _getAddonWrapper = (me, addon) => {
18 | let wrapper = pz.dom.findElement(me.html, ('div.input-group-' + addon.position));
19 | return wrapper || (() => {
20 | pz.dom[addon.position](me.html, '
');
21 | return pz.dom.findElement(me.html, ('div.input-group-' + addon.position));
22 | })();
23 | };
24 |
25 | let _addTextWrapper = (wrapper, text, tempCls) => {
26 | pz.dom.append(wrapper, '
' + (text || '') + '
');
27 | };
28 |
29 | return {
30 | type: 'ui-bootstrap-input-group',
31 | ownerType: 'ui-bootstrap-component',
32 | template: '
',
33 | parseTemplate: function() {
34 | let hasSize = !pz.isEmpty(this.size);
35 | this.addCss((hasSize ? ('input-group-' + this.size) : ''));
36 | },
37 | input: {
38 | type: 'ui-bootstrap-input' // or ui-bootstrap-select
39 | },
40 | init: function() {
41 | let addons, wrapper, component;
42 |
43 | if (pz.isEmpty(this.addon)) {
44 | throw new Error(_const.addonEmpty);
45 | };
46 |
47 | if (!pz.arr.contains(['ui-bootstrap-input', 'ui-bootstrap-select'], this.input.type)) {
48 | throw new Error(_const.unsupportedInputType);
49 | };
50 |
51 | addons = pz.isArray(this.addon) ? this.addon : [this.addon];
52 | pz.arr.clear(this.components);
53 | this.components = [];
54 |
55 | pz.forEach(addons, (addon, idx) => {
56 |
57 | if (pz.isEmpty(addon.position)) {
58 | addon.position = 'prepend';
59 | };
60 |
61 | if (addon.position == 'append') {
62 | this.input.renderAfter = 'div.input-group-prepend';
63 | };
64 |
65 | wrapper = _getAddonWrapper(this, addon), component = {};
66 | if (pz.arr.contains(_allowedComponents, addon.renderAs.type)) {
67 | let renderTo = ('div.input-group-' + addon.position);
68 |
69 | if (addon.renderAs.type == 'ui-bootstrap-input') {
70 | _addTextWrapper(wrapper, null, ('component-addon-' + idx));
71 | renderTo = ('div.input-group-text.component-addon-' + idx);
72 | component.simple = true;
73 | };
74 |
75 | if (addon.renderAs.type == 'ui-bootstrap-select') {
76 | component.custom = true;
77 | };
78 |
79 | pz.assignTo(component, addon.renderAs, false);
80 | component.renderTo = renderTo;
81 | this.components.push(component);
82 | } else {
83 | _addTextWrapper(wrapper, addon.renderAs.text);
84 | };
85 | }, this);
86 |
87 | this.base(arguments);
88 | this.addChild(this.input);
89 | }
90 | };
91 | };
92 |
93 | export default uiBase.extend(inputGroup);
--------------------------------------------------------------------------------
/packages/bootstrap-ui/README.md:
--------------------------------------------------------------------------------
1 | # plazarjs/bootstrap-ui
2 |
3 | > A set of predefined components styled with the Bootstrap CSS framework (version v4.1.x).
4 |
5 | ## Usage - es
6 |
7 | ```javascript
8 | $ npm install @plazarjs/bootstrap-ui
9 | ```
10 | ## Register all Components
11 |
12 | Create a folder called `plugins`. Inside of it create a file called `bootstrap.js` and copy the following snippet:
13 |
14 | ```javascript
15 | import 'bootstrap';
16 | import pz from '@plazarjs/core';
17 | import pzBootstrap from '@plazarjs/bootstrap-ui';
18 | pz.plugin(pzBootstrap);
19 | ```
20 | The snippet above will import the module dependencies and register each bootstrap-ui component. Then, import the plugin script in your app entry point:
21 |
22 | ```javascript
23 | import '..my-app-relative-path/plugins/bootstrap';
24 | ```
25 |
26 | ## Import a Specific Component
27 |
28 | ```javascript
29 | import navbar from '@plazarjs/bootstrap-ui/dist/esmodules/components/ui-navbar.component'
30 | ```
31 |
32 | Inheritance is enabled on each component. We could have our custom component created like so:
33 |
34 | ```javascript
35 | export default navbar.extend(/* configs */);
36 | ```
37 |
38 | ## Usage - cdn
39 |
40 | ```html
41 | // include the required scripts before closing the body tag
42 |
43 |
44 |
45 |
46 |
47 |
48 | // initialize the plugin
49 |
52 |
53 | // our app components are listed here
54 | ```
55 |
56 | Next, we could define our custom derived components via `pz.define` and the `extend` API, or simply create them via `pz.create` if no modifications are required. A quick example:
57 |
58 | ```javascript
59 | pz.create({
60 | autoLoad: true,
61 | renderTo: 'body',
62 | type: 'ui-bootstrap-card',
63 | header: {
64 | text: 'Login',
65 | css: ['bg-info', 'text-white']
66 | },
67 | components: [{
68 | type: 'ui-bootstrap-input',
69 | labelText: 'Email:',
70 | placeholder: 'Enter email...'
71 | }, {
72 | type: 'ui-bootstrap-input',
73 | labelText: 'Password:',
74 | placeholder: 'Enter password...',
75 | css: ['mb-2']
76 | }, {
77 | type: 'ui-bootstrap-input',
78 | inputType: 'checkbox',
79 | labelText: 'Remember me'
80 | }],
81 | buttons: [{
82 | text: 'Login',
83 | appearance: 'outline-info',
84 | align: 'right'
85 | }]
86 | // other configs
87 | });
88 | ```
89 | Output:
90 |
91 |
92 |
93 |
94 |
95 |
96 | ## CSS - es
97 |
98 | To import the CSS files you can use your favorite plugin. For example, `rollup.js` or `webpack`.
99 |
100 | ## CSS - cdn
101 |
102 | Place the following `link` tag at the top of your page, within the html `head` tag.
103 |
104 | ```html
105 |
106 | ```
107 |
108 | Detailed documentation can be found
here .
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-carousel.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import $ from 'jquery';
3 | import uiBase from '../base/ui-base.component';
4 |
5 | const carousel = () => {
6 |
7 | let _getNavButton = (id, type) => {
8 | return '
' + type + ' ';
10 | };
11 |
12 | let _parseTemplate = function() {
13 |
14 | this.html.setAttribute('id', 'carousel_' + this.id);
15 | this.html.setAttribute('data-interval', this.interval);
16 | this.html.setAttribute('data-keyboard', this.keyboard);
17 | this.html.setAttribute('data-pause', (this.pauseOnHover ? false : 'hover'));
18 |
19 | let indicators, me = this, prevBtn, nextBtn, item, inner, css, mainCss;
20 |
21 | if (this.indicators) {
22 | pz.dom.append(this.html, '
');
27 | inner = pz.dom.findElement(this.html, 'div.carousel-inner');
28 |
29 | pz.forEach(this.slides, (slide, index) => {
30 |
31 | if (me.indicators) {
32 | pz.dom.append(indicators, '
');
33 | };
34 |
35 | mainCss = 'carousel-item' + ((index == 0 ? ' active' : '') + ' slide_' + index);
36 | css = pz.isEmpty(slide.css) ? mainCss : (mainCss + ' ' + slide.css.join(' ')).trim();
37 | item = pz.dom.parseTemplate('
' + slide.text + '
');
38 |
39 | if (!pz.isEmpty(slide.caption)) {
40 | pz.dom.append(item, '
' + slide.caption + '
');
41 | };
42 |
43 | pz.dom.append(inner, item);
44 | });
45 |
46 | prevBtn = _getNavButton(this.id, 'prev');
47 | nextBtn = _getNavButton(this.id, 'next');
48 |
49 | pz.dom.append(this.html, prevBtn);
50 | pz.dom.append(this.html, nextBtn);
51 |
52 | };
53 |
54 | let _slide = (me, to) => {
55 | $(me.html).carousel(to);
56 | };
57 |
58 | return {
59 | type: 'ui-bootstrap-carousel',
60 | ownerType: 'ui-bootstrap-component',
61 | template: '
',
62 | indicators: true,
63 | interval: 5000,
64 | keyboard: true,
65 | pauseOnHover: false,
66 | handlers: [{
67 | on: 'slide.bs.carousel',
68 | fn: 'onSlide'
69 | }, {
70 | on: 'slid.bs.carousel',
71 | fn: 'onSlid'
72 | }],
73 | slides: [],
74 | parseTemplate: _parseTemplate,
75 | destroy: function() {
76 | $(this.html).carousel('dispose');
77 | this.base(arguments);
78 | },
79 | onSlide: function(e) {
80 | this.publish('slide-bs-carousel', e);
81 | },
82 | onSlid: function(e) {
83 | this.publish('slid-bs-carousel', e);
84 | },
85 | slideNext: function(e) {
86 | _slide(this, 'next');
87 | },
88 | slidePrev: function(e) {
89 | _slide(this, 'prev');
90 | },
91 | slideTo: function(number) {
92 | _slide(this, number);
93 | },
94 | cycle: function(pause) {
95 | _slide(this, (pause ? 'pause' : 'cycle'));
96 | }
97 | };
98 | };
99 |
100 | export default uiBase.extend(carousel);
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-container.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import uiBase from '../base/ui-base.component';
3 |
4 | const container = () => {
5 |
6 | let _defaultColSize = 12;
7 |
8 | let _getColumnSizeClass = (size) => {
9 | let _default = 'col-' + _defaultColSize,
10 | lg, md, sm, css;
11 |
12 | if (pz.isEmpty(size)) {
13 | return _default;
14 | };
15 |
16 | lg = !pz.isEmpty(size.lg) ? 'col-lg-' + size.lg : '';
17 | md = !pz.isEmpty(size.md) ? ' col-md-' + size.md : '';
18 | sm = !pz.isEmpty(size.sm) ? ' col-sm-' + size.sm : '';
19 |
20 | css = lg + md + sm;
21 | return !pz.isEmpty(css) ?
22 | css : _default;
23 | };
24 |
25 | let _parseJumbotron = (me, jumbotron) => {
26 |
27 | let hasBtn, hasLeadText, hasTitle, hasDivider, mainContainer;
28 | if (pz.isEmpty(jumbotron)) {
29 | return;
30 | };
31 |
32 | hasBtn = !pz.isEmpty(jumbotron.buttons);
33 | hasLeadText = !pz.isEmpty(jumbotron.leadText);
34 | hasTitle = !pz.isEmpty(jumbotron.title);
35 | hasDivider = !pz.isEmpty(jumbotron.divider);
36 | mainContainer = me.html;
37 |
38 | if (me.fluid) {
39 | pz.dom.append(me.html, '
');
40 | mainContainer = pz.dom.findElement(me.html, 'div.jumbotron-body');
41 | };
42 |
43 | if (hasTitle) {
44 | let size = jumbotron.title.size || 4;
45 | let text = jumbotron.title.text || 'Welcome';
46 | pz.dom.append(mainContainer, '
' + text + ' ');
47 | };
48 |
49 | if (hasLeadText) {
50 | pz.dom.append(mainContainer, '
' + jumbotron.leadText + '
');
51 | };
52 |
53 | if (hasBtn) {
54 |
55 | if (hasDivider) {
56 | pz.dom.append(mainContainer, '
');
57 | };
58 |
59 | pz.dom.append(mainContainer, '
');
60 | pz.forEach(jumbotron.buttons, (button) => {
61 | let btn = {};
62 | pz.assignTo(btn, button, false);
63 | btn.renderTo = 'p.lead.jumbotron-button';
64 |
65 | if (pz.isEmpty(btn.type)) {
66 | btn.type = 'ui-bootstrap-button';
67 | };
68 |
69 | me.components.push(btn);
70 | });
71 | };
72 | };
73 |
74 | return {
75 | type: 'ui-bootstrap-container',
76 | ownerType: 'ui-bootstrap-component',
77 | template: '
',
78 | renderAs: 'container', // can be row, form-row, container, column, jumbotron
79 | fluid: false,
80 | body: '', // can be html
81 | components: [],
82 | parseTemplate: function() {
83 |
84 | let cls = this.renderAs == 'row' ? 'row' :
85 | (this.renderAs == 'form-row' ? 'form-row' : (this.renderAs == 'column' ? _getColumnSizeClass(this.column) :
86 | (this.renderAs == 'jumbotron' ? (this.fluid ? 'jumbotron jumbotron-fluid' : 'jumbotron') :
87 | (this.fluid ? 'container-fluid' : 'container'))));
88 |
89 | let hasChildren = !pz.isEmpty(this.components);
90 | this.addCss(cls);
91 | this.html.innerHTML = (hasChildren ? '' : (pz.isEmpty(this.body) ? '' : this.body));
92 |
93 | if (this.renderAs == 'jumbotron') {
94 | _parseJumbotron(this, this.jumbotron);
95 | };
96 | }
97 | };
98 | };
99 |
100 | export default uiBase.extend(container);
101 |
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-card.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import uiBase from '../base/ui-base.component';
3 |
4 | const card = () => {
5 |
6 | let _parseTemplate = function() {
7 |
8 | let hasImage = !pz.isEmpty(this.image), pos, method;
9 | let bodyClasses = 'card-body '.concat(this.body ? (!pz.isEmpty(this.body.css) ? this.body.css : []).join(' ') : '').trim();
10 | let headerClasses = 'card-header '.concat(this.header ? (!pz.isEmpty(this.header.css) ? this.header.css : []).join(' ') : '').trim();
11 | let footerClasses = 'card-footer '.concat(this.footer ? (!pz.isEmpty(this.footer.css) ? this.footer.css : []).join(' ') : '').trim();
12 |
13 | pz.dom.append(this.html, (this.header ? ('') : ''));
14 | pz.dom.append(this.html, (this.body ? '
' : ''));
15 |
16 | if (this.block) {
17 | this.html.className = this.html.className.replace('card', 'card-block');
18 | };
19 |
20 | let bodyEl = pz.dom.findElement(this.html, 'div.card-body');
21 |
22 | if (hasImage) {
23 | pos = this.image.position || 'top';
24 | method = pos == 'top' ? 'prepend' : 'append';
25 | pz.dom[method](this.html, '
');
26 |
27 | if (pos == 'overlay') {
28 | bodyEl.className = bodyEl.className.replace('card-body', 'card-img-overlay').trim();
29 | };
30 | };
31 |
32 | pz.dom.append(bodyEl, (pz.isEmpty(this.body.title) ? '' : '
' + this.body.title + ' '));
33 | pz.dom.append(bodyEl, (pz.isEmpty(this.body.text) ? '' : '
' + this.body.text + '
'));
34 |
35 | pz.dom.append(this.html, (this.footer ? ('') : ''));
36 | };
37 |
38 | let _createButtons = (me) => {
39 |
40 | if (pz.isEmpty(me.buttons)) {
41 | return;
42 | };
43 |
44 | me.footer = true;
45 | me.footerCss = pz.arr.merge(me.footerCss || [], ['text-right']);
46 |
47 | let buttons = pz.arr.map((button) => {
48 | return pz.assignTo(button, {
49 | type: button.type || 'ui-bootstrap-button',
50 | renderTo: button.renderTo || ' > div.card-footer'
51 | }, false);
52 | }, me.buttons);
53 |
54 | me.components = pz.arr.merge(me.components || [], buttons);
55 | pz.arr.clear(me.buttons); // clear and delete buttons array since we don't need it anymore
56 | delete me.buttons;
57 | };
58 |
59 | return {
60 | type: 'ui-bootstrap-card',
61 | ownerType: 'ui-bootstrap-component',
62 | template: '
',
63 | block: false,
64 | containerElement: '> div.card-body',
65 | header: {
66 | text: ''
67 | },
68 | body: {
69 | title: '',
70 | text: ''
71 | },
72 | footer: {
73 | text: ''
74 | },
75 | load: function() {
76 | _createButtons(this);
77 | this.base(arguments);
78 | },
79 | buttons:[],
80 | parseTemplate: _parseTemplate,
81 | setHeaderText: function(value) {
82 |
83 | let header;
84 | if (pz.isEmpty(value)) {
85 | return;
86 | };
87 |
88 | header = pz.dom.findElement(this.html, 'div.card-header');
89 | if (pz.isEmpty(header)) {
90 | return;
91 | };
92 |
93 | header.innerHTML = value;
94 | }
95 | };
96 | };
97 |
98 | export default uiBase.extend(card);
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-dropdown.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import $ from 'jquery';
3 | import uiBase from '../base/ui-base.component';
4 |
5 | const dropdown = () => {
6 |
7 | let _parseTemplate = function() {
8 | let hasSize = !pz.isEmpty(this.size), btn, hasPosition =
9 | !pz.isEmpty(this.dropPosition), hasHeader = !pz.isEmpty(this.menuHeaderText),
10 | hasAppearance = !pz.isEmpty(this.appearance);
11 |
12 | if (this.split) {
13 | pz.dom.prepend(this.html, '
' + this.text + ' ');
14 | this.html.className = this.html.className.replace('dropdown', 'btn-group');
15 | };
16 |
17 | if (hasPosition) {
18 | this.html.className = this.html.className.replace('dropdown', 'btn-group');
19 | this.addCss(('drop' + this.dropPosition));
20 | };
21 |
22 | btn = pz.dom.findElement(this.html, (this.inNav ? 'a' : 'button') + '.dropdown-toggle');
23 | this.addCss(((hasAppearance ? ('btn-' + this.appearance) : '') + (hasSize ? (' btn-' + this.size) : '') + (this.split ? ' dropdown-toggle-split' : '')), btn);
24 | btn[this.split ? 'innerHTML' : 'innerText'] = (this.split ? '
Toggle Dropdown ' : this.text);
25 |
26 | let cls = 'dropdown-menu';
27 | pz.dom.append(this.html, '
');
28 | let menuWrapper = pz.dom.findElement(this.html, 'div.dropdown-menu');
29 |
30 | if (hasHeader) {
31 | pz.dom.append(menuWrapper, '');
32 | };
33 |
34 | if (!pz.isEmpty(this.components)) {
35 | return;
36 | };
37 |
38 | pz.forEach(this.menuItems, (item) => {
39 | pz.dom.append(menuWrapper, '
' + item.text + ' ');
40 | if (item.divide) {
41 | pz.dom.append(menuWrapper, '
');
42 | };
43 | });
44 | };
45 |
46 | return {
47 | type: 'ui-bootstrap-dropdown',
48 | ownerType: 'ui-bootstrap-component',
49 | template: '
',
50 | handlers:[{
51 | on: 'show.bs.dropdown',
52 | fn: 'onDropdownShow'
53 | }, {
54 | on: 'shown.bs.dropdown',
55 | fn: 'onDropdownShown'
56 | }, {
57 | on: 'hide.bs.dropdown',
58 | fn: 'onDropdownHide'
59 | }, {
60 | on: 'hidden.bs.dropdown',
61 | fn: 'onDropdownHidden'
62 | }],
63 | load: function() {
64 | let parent = this.traceUp();
65 | let isInNav = !pz.isEmpty(parent) && pz.arr.contains([parent.type, parent.ownerType], 'ui-bootstrap-navbar');
66 | this.inNav = this.inNav || isInNav;
67 | if (this.inNav) {
68 | this.template = '
';
69 | this.appearance = null;
70 | };
71 | this.base(arguments);
72 | },
73 | containerElement: 'div.dropdown-menu',
74 | appearance: 'primary',
75 | text: 'Dropdown',
76 | split: false,
77 | menuItems: [],
78 | parseTemplate: _parseTemplate,
79 | destroy: function() {
80 | $(this.html).dropdown('dispose');
81 | this.base(arguments);
82 | },
83 | toggle: function() {
84 | $(this.html).dropdown('toggle');
85 | },
86 | update: function() {
87 | $(this.html).dropdown('update');
88 | },
89 | onDropdownShow: function() { },
90 | onDropdownShown: function() { },
91 | onDropdownHide: function() { },
92 | onDropdownHidden: function() { }
93 | };
94 | };
95 |
96 | export default uiBase.extend(dropdown);
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## PlazarJS Contributing Guide
2 |
3 | Hello, and Welcome to the PlazarJS contribution guide. I'm very proud of the fact that you are interested in contributing to PlazarJS. This means that we did a good job with the framework. Hopefully, with your help, it will grow and be even better. Please take a minute and read the following guide.
4 |
5 | - [Issue Reporting](#issue-reporting)
6 | - [Pull Request](#pull-request)
7 | - [Branch Naming](#branch-naming)
8 | - [Environment Setup](#environment-setup)
9 | - [Build and Deploy](#build-and-deploy)
10 | - [Project Structure](#project-structure)
11 | - [Financial Contribution](#financial-contribution)
12 |
13 | ## Issue Reporting
14 |
15 | - The issue is considered as valid only if it can be reproduced on the latest master.
16 | - Open an issue on GitHub. The more information you provide, the easier it will be to validate and fix.
17 | - If applicable, create a JSFiddle
18 |
19 | ## Pull Request
20 |
21 | - The `master` branch represents the latest production release. **Please do not push anything directly into this branch**.
22 | - The `develop` branch represents the branch with the latest features.
23 | - All development should be done in dedicated branches.
24 | - Work under the `src` folder. **Please do not push the `dist` folders**. They are used to store the build files which are published when a release happens.
25 | - Checkout a topic branch from the relevant branch and merge it back when finished.
26 |
27 | #### Branch Naming
28 |
29 | - If you are working on a feature:
30 | - Ideally, every new feature should be branched off of the `master` branch. I say ideally because over the years of developing I've learned that the new feature might depend on another feature which hasn't been deployed to production yet, but this is not that common.
31 | - Use prefix `feature` followed by a slash `/` followed by a description. Separate each word with a `-`, e.q. `feature/my-new-feature-description`
32 | - Merge the feature back against the `develop` branch when finished.
33 |
34 | - If you are fixing a bug:
35 | - Checkout a hotfix branch from the relevant branch.
36 | - Use prefix `hotfix` followed by a slash `/` followed by a description. Separate each word with a `-`, e.q. `hotfix/my-hotfix-description`
37 | - Merge the hotfix back to the relevant branch when finished.
38 |
39 | ## Environment Setup
40 |
41 | You need to install [Node.js](http://nodejs.org). Install the recommended version.
42 |
43 | The next step would be to clone the repo and run the following command:
44 |
45 | ```bash
46 | $ npm install
47 | ```
48 |
49 | This will install the required packages found in `package.json` file.
50 |
51 | Since the project is maintained by [lerna](https://github.com/lerna/lerna), you will need to bootstrap it:
52 |
53 | ```bash
54 | $ lerna bootstrap
55 | ```
56 |
57 | #### Build and Deploy
58 |
59 | PlazarJS uses [babel](https://babeljs.io/), [rollup](https://rollupjs.org/guide/en) and [uglifyjs](https://github.com/mishoo/UglifyJS2) as its build tools. Run the following command to deploy the source into `dist` folders:
60 |
61 | The following command will build each package:
62 | ```
63 | $ npm run build
64 | ```
65 | ## Project Structure
66 |
67 | - [Demo](#demo)
68 | - [Packages](#packages)
69 | - [Scripts](#scripts)
70 |
71 | #### Demo
72 |
73 | This folder contains demo applications. Under the folder `bootstrap-ui` there is a demo showing how to use bootstrap components. Under the folder `bulma` there is a demo showing how to use PlazarJS without any ui components pre-defined. The [Bulma CSS Framework](https://bulma.io/) was used only to speed up the styling of the demo application. It is not a requirement.
74 |
75 | #### Packages
76 |
77 | This folder contains `core`, `bootstrap-ui` and `http` which are distributed as separate NPM packages. Each time when you run any of the npm commands defined above, the build scripts will be created within the dist folder for each package. The version is managed by [lerna](https://github.com/lerna/lerna) by using the default `fixed` mode.
78 |
79 | Each package has a `src` folder. This is your working area.
80 |
81 | #### Scripts
82 |
83 | This folder contains the `rollup.js` common config, `rollup-common.config.js` which is used during building process to wrap the content of the output scripts. Each package has a specific `rollup.config.js` extending the common one.
84 |
85 | ## Financial Contribution
86 |
87 | [Become a patron](https://www.patreon.com/mprotic) or [Donate via PayPal](https://www.paypal.me/mprotic).
88 |
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-list-group.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import uiBase from '../base/ui-base.component';
3 |
4 | const listGroup = () => {
5 |
6 | let _const = {
7 | elementAtIdxNotFound: 'Element at index {0} was not found'
8 | };
9 |
10 | let _parseTemplate = function() {
11 | let tabContent;
12 | this.addCss((this.flushed ? 'list-group-flush' : ''));
13 |
14 | if (this.mode == 'tab') {
15 | pz.dom.insertAfter(this.html, '
');
16 | tabContent = this.html.nextSibling;
17 | };
18 |
19 | pz.forEach(this.menuItems, (menuItem, idx) => {
20 | let actionable, link, contentCls, href, jsVoid = 'javascript:void(0)';
21 |
22 | if (this.noHash && this.mode != 'tab' && pz.isEmpty(menuItem.href)) {
23 | menuItem.href = !pz.isEmpty(this.href) ? this.href.replace('#', jsVoid) : jsVoid;
24 | };
25 |
26 | actionable = (this.actionable || this.mode == 'tab');
27 |
28 | if (actionable && pz.isEmpty(menuItem.href)) {
29 | throw new Error('Each menu item must have [href] property configured');
30 | };
31 |
32 | link = pz.dom.createElement((actionable ? 'a' : 'li'));
33 | this.addCss((actionable ? 'list-group-item' : 'list-group-item list-group-item-action'), link);
34 |
35 | if (actionable) {
36 | link.setAttribute('href', (menuItem.href || '#'));
37 | };
38 |
39 | this.addCss((!pz.isEmpty(menuItem.appearance) ? ('list-group-item-' + menuItem.appearance) : ''), link);
40 | link.innerText = menuItem.text || '';
41 |
42 | if (!pz.isEmpty(menuItem.css)) {
43 | this.addCss(menuItem.css, link);
44 | };
45 |
46 | if (this.mode == 'tab') {
47 | link.setAttribute('data-toggle', 'list');
48 | link.setAttribute('role', 'tab');
49 | href = menuItem.href.replace('#', '');
50 | contentCls = idx == 0 ? ('tab-pane active tab-' + href) : ('tab-pane tab-' + href);
51 | pz.dom.append(tabContent, '
');
52 | };
53 |
54 | pz.dom.append(this.html, link);
55 | }, this);
56 | };
57 |
58 | let _setEnabled = (me, idx, value) => {
59 | if (pz.isEmpty(idx)) {
60 | return;
61 | };
62 |
63 | let el = me.html.childNodes[idx];
64 | if (pz.isEmpty(el)) {
65 | let msg = pz.str.format(_const.elementAtIdxNotFound, idx);
66 | throw new Error(msg);
67 | };
68 |
69 | if (value) {
70 | el.className = el.className.replace('disabled', '').trim();
71 | } else {
72 | me.addCss('disabled', el);
73 | };
74 | };
75 |
76 | return {
77 | type: 'ui-bootstrap-list-group',
78 | ownerType: 'ui-bootstrap-component',
79 | menuItems: [],
80 | actionable: false,
81 | flushed: false,
82 | mode: 'list',
83 | init: function() {
84 |
85 | if (this.mode == 'tab') {
86 | this.handlers = pz.arr.merge((this.handlers || []), [{
87 | on: 'show.bs.tab',
88 | fn: 'onTabShow'
89 | }, {
90 | on: 'shown.bs.tab',
91 | fn: 'onTabShown'
92 | }, {
93 | on: 'hide.bs.tab',
94 | fn: 'onTabHide'
95 | }, {
96 | on: 'hidden.bs.tab',
97 | fn: 'onTabHidden'
98 | }]);
99 | };
100 |
101 | this.base(arguments);
102 | },
103 | load: function() {
104 | this.template = (this.actionable || this.mode == 'tab') ? '
' :
105 | '
';
106 | this.base(arguments);
107 | },
108 | template: '
',
109 | parseTemplate: _parseTemplate,
110 | disable: function(idx) {
111 | _setEnabled(this, idx, false);
112 | },
113 | enable: function(idx) {
114 | _setEnabled(this, idx, true);
115 | },
116 | onTabShown: function (e) {
117 | this.publish('shown-bs-tab', e);
118 | },
119 | onTabShow: function(e) {
120 | this.publish('show-bs-tab', e);
121 | },
122 | onTabHide: function(e) {
123 | this.publish('hide-bs-tab', e);
124 | },
125 | onTabHidden: function(e) {
126 | this.publish('hidden-bs-tab', e);
127 | }
128 | };
129 | };
130 |
131 | export default uiBase.extend(listGroup);
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-input.component.js:
--------------------------------------------------------------------------------
1 | import formFieldMixin from '../mixins/form-field.mixin';
2 | import pz from '@plazarjs/core';
3 | import uiBase from '../base/ui-base.component';
4 |
5 | const input = () => {
6 |
7 | let _parseTemplate = function() {
8 |
9 | let clone, tpl, label, hasSize = !pz.isEmpty(this.size),
10 | input = pz.dom.findElement(this.html, 'input') || this.html,
11 | hasGroup = !pz.isEmpty(this.group);
12 |
13 | this.addCss((hasSize ? ('form-control-' + this.size) : ''), input);
14 | input.setAttribute('id', ('input_' + this.id));
15 |
16 | if (pz.arr.contains(['text'], this.inputType)) {
17 |
18 | if (!this.readonly) {
19 | input.setAttribute('placeholder', this.placeholder);
20 | };
21 |
22 | if (this.plaintext) {
23 | input.className = input.className.replace('form-control', 'form-control-plaintext');
24 | };
25 |
26 | if (this.readonly) {
27 | input.setAttribute('readonly', '');
28 | };
29 |
30 | if (!pz.isEmpty(this.helpText)) {
31 | input.setAttribute('aria-describedby', ('help_' + this.id));
32 | pz.dom.insertAfter(input, '
' + this.helpText + ' ');
33 | };
34 |
35 | };
36 |
37 | if (pz.arr.contains(['checkbox', 'radio'], this.inputType)) {
38 |
39 | let isRadio = this.inputType == 'radio';
40 |
41 | if (isRadio && hasGroup) {
42 | input.setAttribute('name', this.group);
43 | };
44 |
45 | if (this.simple) {
46 | input.className = '';
47 | input.removeAttribute('class');
48 | return;
49 | };
50 |
51 | input.className = input.className.replace('form-control', 'form-check-input');
52 | clone = pz.dom.clone(input);
53 | tpl = pz.dom.createElement('div');
54 |
55 | this.addCss('form-check', tpl);
56 | this.addCss((this.inForm ? 'mb-3' : ''), tpl);
57 | this.addCss((this.inline ? 'form-check-inline' : ''), tpl);
58 | pz.dom.append(tpl, clone);
59 |
60 | if (!pz.isEmpty(this.labelText)) {
61 | label = pz.dom.createElement('label');
62 | label.innerText = this.labelText;
63 | this.addCss('form-check-label', label);
64 | label.setAttribute('for', ('input_' + this.id));
65 | pz.dom.append(tpl, label);
66 | label = null;
67 | };
68 |
69 | pz.dom.replaceWith(this.html, tpl);
70 | this.html = tpl;
71 |
72 | //if (this.inForm) {
73 | // pz.dom.remove(input);
74 | // pz.dom.append(this.html, tpl);
75 | //} else {
76 | // pz.dom.replaceWith(this.html, tpl);
77 | // this.html = tpl;
78 | //};
79 |
80 | tpl = null;
81 | clone = null;
82 | };
83 | };
84 |
85 | return {
86 | type: 'ui-bootstrap-input',
87 | ownerType: 'ui-bootstrap-component',
88 | mixins: [formFieldMixin],
89 | inputType: 'text',
90 | inForm: false,
91 | readonly: false,
92 | placeholder: 'Enter text...',
93 | plaintext: false,
94 | inline: false,
95 | load: function() {
96 | let css = this.inputType == 'file' ? 'form-control-file' : 'form-control';
97 | let input = '
';
98 | this.template = ((this.inForm && !pz.arr.contains(['checkbox', 'radio'], this.inputType)) ? '
' + input + '
' : input);
99 | this.base(arguments);
100 | },
101 | parseTemplate: _parseTemplate,
102 | labelText: '',
103 | simple: false,
104 | helpText: '',
105 | handlers: [{
106 | on: 'change',
107 | fn: 'onChange'
108 | }],
109 | onChange: function(e) { },
110 | prependLabel: function() {
111 |
112 | if (pz.isEmpty(this.labelText) || pz.arr.contains(['checkbox', 'radio'], this.inputType)) {
113 | return;
114 | };
115 |
116 | let label = pz.dom.createElement('label'), hasSize = !pz.isEmpty(this.size);
117 | label.innerText = this.labelText;
118 |
119 | this.addCss('col-form-label', label);
120 | this.addCss((hasSize ? ('col-form-label-' + this.size) : ''), label);
121 | label.setAttribute('for', ('input_' + this.id));
122 |
123 | if (this.inForm) {
124 | pz.dom.prepend(this.html, label);
125 | } else {
126 | pz.dom.insertBefore(this.html, label);
127 | };
128 |
129 | label = null;
130 | }
131 | };
132 | };
133 |
134 | export default uiBase.extend(input);
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-nav.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import $ from 'jquery';
3 | import uiBase from '../base/ui-base.component';
4 |
5 | const nav = () => {
6 |
7 | let _const = {
8 | componentsNotAllowedInTabMode: 'Components in menu are not allowed while using tab mode',
9 | hrefIsRequired: 'Each menu item must have [href] property configured'
10 | };
11 |
12 | let _allowedComponents = [
13 | 'ui-bootstrap-dropdown'
14 | ];
15 |
16 | let _parseTemplate = function() {
17 | let hasPosition = !pz.isEmpty(this.position),
18 | html = this.mode == 'tab' ? pz.dom.findElement(this.html, 'div.nav.nav-tabs') : this.html,
19 | tabContent;
20 |
21 | this.addCss(hasPosition ? ('justify-content-' + this.position) : '', html);
22 | this.addCss(this.vertical ? 'flex-column' : '', html);
23 | this.addCss(pz.isObject(this.fill) ? ('nav-' + this.fill.type) : (this.fill ? 'nav-fill' : ''), html);
24 |
25 | if (this.pills) {
26 | html.className = (this.mode == 'tab') ? html.className.replace('nav-tabs', 'nav-pills') :
27 | html.className + ' nav-pills';
28 | };
29 |
30 | if (this.mode == 'tab') {
31 | pz.dom.insertAfter(html, '
');
32 | tabContent = html.nextSibling;
33 | };
34 |
35 | pz.forEach(this.menuItems, (menuItem, idx) => {
36 | let cls, link, href, contentCls,
37 | isComponent = pz.arr.contains(_allowedComponents, menuItem.type);
38 |
39 | if (isComponent && this.mode == 'tab') {
40 | throw new Error(_const.componentsNotAllowedInTabMode);
41 | };
42 |
43 | if (isComponent) {
44 | menuItem.renderTo = 'root';
45 | this.components.push(menuItem);
46 | } else {
47 |
48 | if (pz.isEmpty(menuItem.href)) {
49 | throw new Error(_const.hrefIsRequired);
50 | };
51 |
52 | cls = this.fill ? 'nav-item nav-link' : 'nav-link',
53 | link = pz.dom.createElement('a');
54 |
55 | cls += menuItem.css ? (' ' + menuItem.css.join(' ')) : '';
56 | cls += menuItem.active ? ' active' : '';
57 | this.addCss(cls, link);
58 | link.innerText = menuItem.text;
59 |
60 | if (this.mode == 'tab') {
61 | link.setAttribute('data-toggle', (this.pills ? 'pill' : 'tab'));
62 | link.setAttribute('role', 'tab');
63 | href = (menuItem.href.replace('#', ''));
64 | contentCls = idx == 0 ? ('tab-pane active tab-' + href) : ('tab-pane tab-' + href);
65 | pz.dom.append(tabContent, '
');
66 | };
67 |
68 | link.setAttribute('href', (menuItem.href || '#'));
69 | pz.dom.append(html, link);
70 | };
71 |
72 | }, this);
73 | };
74 |
75 | return {
76 | type:'ui-bootstrap-nav',
77 | ownerType: 'ui-bootstrap-component',
78 | position: 'start',
79 | template: '
',
80 | components: [],
81 | mode: 'nav',
82 | load: function() {
83 | this.template = (this.mode == 'tab') ? '
' :
84 | '
';
85 | this.base(arguments);
86 | },
87 | init: function() {
88 | if (this.mode == 'tab') {
89 | this.handlers = pz.arr.merge((this.handlers || []), [{
90 | on: 'show.bs.tab',
91 | fn: 'onTabShow'
92 | }, {
93 | on: 'shown.bs.tab',
94 | fn: 'onTabShown'
95 | }, {
96 | on: 'hide.bs.tab',
97 | fn: 'onTabHide'
98 | }, {
99 | on: 'hidden.bs.tab',
100 | fn: 'onTabHidden'
101 | }]);
102 | };
103 |
104 | this.base(arguments);
105 | },
106 | menuItems: [],
107 | vertical: false,
108 | fill: false,
109 | pills: false,
110 | parseTemplate: _parseTemplate,
111 | onTabShown: function(e) {
112 | this.publish('shown-bs-tab', e);
113 | },
114 | onTabShow: function(e) {
115 | this.publish('show-bs-tab', e);
116 | },
117 | onTabHide: function(e) {
118 | this.publish('hide-bs-tab', e);
119 | },
120 | onTabHidden: function(e) {
121 | this.publish('hidden-bs-tab', e);
122 | },
123 | destroy: function() {
124 | if (this.mode == 'tab') {
125 | $(this.html).tab('dispose');
126 | };
127 | this.base(arguments);
128 | }
129 | };
130 | };
131 |
132 | export default uiBase.extend(nav);
--------------------------------------------------------------------------------
/demo/bulma/scripts/components/widgets/demo-todo.component.js:
--------------------------------------------------------------------------------
1 | pz.define('todo-component', function() {
2 |
3 | return {
4 | ownerType: 'base-component',
5 | mixins: ['page-mixin'],
6 | template: '
' + // template can also be retrieved from the server via ajaxSetup config
7 | '
Create TODO
' +
8 | '
' +
9 | '
' +
10 | '
Title: ' +
11 | '
' +
12 | ' ' +
13 | '
' +
14 | '
' +
15 | '
' +
16 | '
Description: ' +
17 | '
' +
18 | '' +
19 | '
' +
20 | '
' +
21 | '
' +
22 | '
' +
23 | 'Add ' +
24 | '
' +
25 | '
' +
26 | '
' +
27 | '' +
36 | ' ' +
37 | '
No data available ' +
38 | '
' +
39 | '
',
40 | renderTo: 'section.app-body',
41 | viewModel: {
42 | todos: [],
43 | newTodo: {
44 | text: '',
45 | title: '',
46 | isCompleted: false
47 | }
48 | },
49 | handlers: [{
50 | on: 'click',
51 | selector: 'span.btn-delete',
52 | fn: function(el) { // can be inline fn or component fn (name: String)
53 | var idx = el.getAttribute('data-idx');
54 | pz.arr.removeAt(this.viewModel.todos, idx);
55 | this.todoService.delete(idx);
56 | this.publish('todo-deleted');
57 | }
58 | }],
59 | init: function() {
60 | this.handle({ // example of dynamic event
61 | on: 'click',
62 | selector: 'button.btn-add',
63 | fn: 'addTodo'
64 | });
65 |
66 | var todos = this.todoService.get();
67 | pz.forEach(todos, function(todo) {
68 | this.viewModel.todos.push(pz.obj.clone(todo));
69 | }, this);
70 |
71 | this.base(arguments);
72 |
73 | pz.forEach(this.viewModel.todos, function(todo) { // subscribe to all pre-loaded todos
74 | var me = this;
75 | todo.isCompleted.subscribe(function() {
76 | me.onStatusChange(todo);
77 | });
78 | }, this);
79 | },
80 | addTodo: function() {
81 | var todo = {
82 | id: this.viewModel.todos.length + 1,
83 | text: this.viewModel.newTodo.text(),
84 | title: this.viewModel.newTodo.title(),
85 | isCompleted: false
86 | }, me = this;
87 | this.viewModel.todos.push(todo);
88 | this.viewModel.newTodo.text = '';
89 | this.viewModel.newTodo.title = '';
90 | this.todoService.put(todo);
91 | this.publish('todo-added', todo);
92 | todo.isCompleted.subscribe(function() {
93 | me.onStatusChange(todo);
94 | });
95 | },
96 | onStatusChange: function(todo) {
97 | this.todoService.update(todo);
98 | this.publish('todo-updated', todo);
99 | },
100 | require: ['todo-service']
101 | };
102 |
103 | });
--------------------------------------------------------------------------------
/packages/core/src/asset-types/base.js:
--------------------------------------------------------------------------------
1 | import pz from '../core';
2 |
3 | class base {
4 | init() { }
5 | destroy() {
6 | let idx = pz.application.instances.indexOf(this);
7 | if (idx != -1) {
8 | pz.application.instances.splice(idx, 1);
9 | }
10 | }
11 | applyMixins() {
12 | let me = this;
13 | pz.forEach(this.mixins, function (m) {
14 | let mixin = pz.isMixin(m) ? m : pz.getDefinitionOf(m);
15 | let cleanMixin = pz.assignTo({}, mixin);
16 | delete cleanMixin.ownerType;
17 | delete cleanMixin.type;
18 | pz.assignTo(me, cleanMixin, false);
19 | });
20 | }
21 | setRequiredInstances() {
22 | let isModularEnv = pz.isModularEnv();
23 | let requireDefined = !pz.isEmpty(this.require) &&
24 | pz.isArray(this.require);
25 |
26 | if (isModularEnv || !requireDefined) {
27 | return;
28 | }
29 |
30 | pz.forEach(this.require, function (requiredItemType) {
31 | let instance = pz.getInstanceOf(requiredItemType);
32 | let requiredItem = pz.isEmpty(instance) ?
33 | pz.getDefinitionOf(requiredItemType) : instance;
34 | let camelCaseName = pz.str.camelize(requiredItemType);
35 |
36 | if (!pz.isEmpty(requiredItem) && pz.isEmpty(this[camelCaseName])) {
37 | this[camelCaseName] = !pz.isFunction(requiredItem) ? requiredItem :
38 | pz.create(requiredItemType);
39 | }
40 | }, this);
41 | }
42 | static extend(props) {
43 | // TODO: Inherit statics
44 |
45 | let properties = (pz.toObject(props) || {}), parentClass, returnVal;
46 | if(pz.isEmpty(properties.type)) {
47 | throw new Error('It seems that you are trying to create an object without a type definition. Example invocation: myDefinition.extend({ type: "my-type" // other configs });');
48 | }
49 | parentClass = this;
50 |
51 | returnVal = (function (_parentClass, _properties) {
52 | let _hasCustomConstructor = _properties && _properties.constructor
53 | && _properties.constructor !== {}.constructor;
54 | let propertyNames = Object.keys(_properties);
55 | let propertiesReduced = propertyNames.reduce(function (acc, key) {
56 | let isFunction = pz.isFunction(_properties[key]);
57 | acc[isFunction ? 'fnKeys' : 'attrKeys'].push(key);
58 | return acc;
59 | }, { fnKeys: [], attrKeys: [] });
60 |
61 | let pz_type = function () { // child class
62 | let me = this, result;
63 | pz.forEach(propertiesReduced.attrKeys, function (key) { // apply properties (strings, ints, arrays, objects...etc) to the object instance
64 | if (!me.hasOwnProperty(key) && !pz.isEmpty(_properties[key], true)) {
65 | let isArray = pz.isArray(_properties[key]);
66 | let isObject = pz.isObject(_properties[key]);
67 |
68 | me[key] = (isArray ? pz.deepClone(_properties[key]) : (isObject ? pz.assignTo({}, _properties[key]) : _properties[key]));
69 | }
70 | });
71 |
72 | this.ownerType = this.ownerType || _parentClass.$type || 'base';
73 | this.base = _hasCustomConstructor ? _parentClass : null;
74 | result = _hasCustomConstructor ? _properties.constructor.apply(this, arguments)
75 | : _parentClass.apply(this, arguments);
76 | this.base = null;
77 | return result || me;
78 | };
79 |
80 | pz_type.prototype = Object.create(_parentClass.prototype);
81 | pz_type.prototype.constructor = pz_type;
82 |
83 | pz.forEach(propertiesReduced.fnKeys, function (key) {
84 | pz_type.prototype[key] = key == 'constructor' ? pz_type.prototype.constructor : (function (name, fn, base) { // share the functions between instances via prototype
85 | return function () {
86 | let tmp = this.base;
87 | let addSuperCallWrapper = !pz.isEmpty(base[name]) && pz.isFunction(base[name]);
88 | this.base = addSuperCallWrapper ? base[name] : function () {
89 | throw new Error('Method named: [' + name + '] was not found on type: [' + this.ownerType + ']');
90 | };
91 | let result = fn.apply(this, arguments);
92 | this.base = tmp;
93 | return result;
94 | };
95 | })(key, _properties[key], _parentClass.prototype);
96 | });
97 |
98 | pz_type.extend = _parentClass.extend;
99 | pz_type.create = _parentClass.create;
100 | pz_type.$isPz = true;
101 | pz_type.$type = _properties.type;
102 | return pz_type;
103 |
104 | })(parentClass, properties);
105 |
106 | properties = null;
107 | parentClass = null;
108 |
109 | return returnVal;
110 | }
111 | static create(config) {
112 | let params, instance;
113 |
114 | if(!pz.isEmpty(config)) {
115 | params = pz.assignTo({}, config, false);
116 | delete params.type;
117 | delete config.type;
118 | instance = new this(params);
119 | pz.assignTo(instance, config, false);
120 | } else {
121 | instance = new this();
122 | }
123 |
124 | instance.id = pz.guid();
125 | instance.autoLoad = !pz.isEmpty(this.autoLoad) ? this.autoLoad : instance.autoLoad;
126 | delete this.autoLoad;
127 | instance.setRequiredInstances();
128 |
129 | if (pz.isComponent(instance) || pz.isClass(instance)) {
130 | instance.applyMixins();
131 |
132 | if (instance.autoLoad) {
133 | instance.load();
134 | }
135 | }
136 |
137 | pz.application.instances.push(instance);
138 |
139 | return instance;
140 | }
141 | }
142 |
143 | export default base;
--------------------------------------------------------------------------------
/packages/core/src/statics/binder/observableArray.js:
--------------------------------------------------------------------------------
1 | import pz from '../../core';
2 | import observable from './observable';
3 | //observe and observableArray will throw a circular reference error if they get separated as modules
4 |
5 | let observe = function (value) {
6 |
7 | if (!pz.isObject(value) || value.$observed) {
8 | return false;
9 | }
10 |
11 | let properties = Object.keys(value);
12 |
13 | pz.forEach(properties, function (prop) {
14 |
15 | let propValue = value[prop];
16 | let obsArray = observeArray(value, propValue, prop);
17 |
18 | if (!obsArray && !pz.isInstanceOf(value, observable) && !observe(propValue) &&
19 | !pz.isFunction(propValue)) {
20 | observable.create(value, prop);
21 | }
22 | });
23 |
24 | value.$observed = true;
25 | return true;
26 | };
27 |
28 | let observeArray = function (obj, collection, prop) {
29 |
30 | let isArray = pz.isArray(collection);
31 | let obsArray;
32 |
33 | if (!isArray) {
34 | return obsArray;
35 | }
36 |
37 | obsArray = new observableArray(obj, collection, prop);
38 | pz.forEach(obsArray, function (item) {
39 | observe(item);
40 | });
41 |
42 | return obsArray;
43 | };
44 |
45 | let observableMethods = 'pop push shift unshift splice reverse sort'.split(' '),
46 | normalMethods = 'slice concat join some every forEach map filter reduce reduceRight indexOf lastIndexOf toString toLocaleString'.split(' '),
47 | arrPrototype = Array.prototype;
48 |
49 | class observableArray {
50 | constructor(obj, collection, prop) {
51 | collection = collection || [];
52 |
53 | this.subscriptions = [];
54 | this.prop = prop;
55 |
56 | for (let i = 0; i < collection.length; i++) {
57 | this.push(collection[i]);
58 | }
59 |
60 | let length = this.length;
61 | let me = this;
62 | this.hasData = length > 0;
63 |
64 | Object.defineProperty(this, 'length', {
65 | configurable: false,
66 | enumerable: true,
67 | set: function (newValue) {
68 | let newItem;
69 |
70 | if (newValue > length) { // push or unshift
71 | newItem = this.$action == 'push' ? this[length] : this[0];
72 | observe(newItem);
73 | }
74 |
75 | if (newValue != length) {
76 | length = newValue;
77 | this.hasData = length > 0;
78 | }
79 | },
80 | get: function () {
81 | return length;
82 | }
83 | });
84 |
85 | delete obj[prop];
86 | Object.defineProperty(obj, prop, {
87 | configurable: true,
88 | enumerable: true,
89 | get: function () {
90 | return me;
91 | },
92 | set: function (newValue) {
93 | let shouldNotify = me.length != newValue.length,
94 | newLength = newValue.length,
95 | last = newLength - 1;
96 |
97 | if (shouldNotify) {
98 | me.$pauseNotify = true;
99 | me.removeAll();
100 |
101 | pz.forEach(newValue, (item, index) => {
102 | me.$pauseNotify = (index != last);
103 | me.push(item);
104 | });
105 |
106 | delete me.$pauseNotify;
107 |
108 | if (newLength == 0) { me.removeAll(); } // trigger UI update
109 | }
110 | }
111 | });
112 |
113 | observable.create(this, 'hasData');
114 | return this;
115 | }
116 | }
117 |
118 | observableArray.prototype = [];
119 |
120 | pz.forEach(observableMethods, function (methodName) {
121 |
122 | let method = arrPrototype[methodName];
123 |
124 | observableArray.prototype[methodName] = function () {
125 | this.$action = methodName;
126 | let returnValue = method.apply(this, arguments);
127 |
128 | let subscription = pz.find(function (subscription) {
129 | return subscription.name == methodName;
130 | }, this.subscriptions);
131 |
132 | if (subscription && !this.$pauseNotify) {
133 | let args = arrPrototype.slice.call(arguments);
134 | subscription.callback.apply(this, args);
135 | }
136 |
137 | delete this.$action;
138 | return returnValue;
139 | };
140 | });
141 |
142 | pz.forEach(normalMethods, function (methodName) {
143 | observableArray.prototype[methodName] = arrPrototype[methodName];
144 | });
145 |
146 | observableArray.prototype.subscribe = function (callback, bindingId) {
147 | this.subscriptions.splice(0, this.subscriptions.length);
148 | pz.forEach(observableMethods, function (method) {
149 | let length = this.subscriptions.length;
150 | this.subscriptions.push({
151 | id: bindingId || length++,
152 | name: method,
153 | callback: callback
154 | });
155 | }, this);
156 | callback = null;
157 | };
158 |
159 | observableArray.prototype.unsubscribe = function (bindingId) {
160 |
161 | let bindingSubs = this.subscriptions.filter(function (sub) {
162 | return sub.id == bindingId;
163 | });
164 |
165 | pz.forEach(bindingSubs, function (sub) {
166 | let idx = this.subscriptions.indexOf(sub);
167 | this.subscriptions.splice(idx, 1);
168 | }, this);
169 | };
170 |
171 | observableArray.prototype.getFirst = function () {
172 | return this.getAt(0);
173 | };
174 |
175 | observableArray.prototype.getLast = function () {
176 | return this.getAt(this.length - 1);
177 | };
178 |
179 | observableArray.prototype.getAt = function (index) {
180 |
181 | if (pz.isEmpty(index)) {
182 | return null;
183 | }
184 |
185 | return this[index];
186 | };
187 |
188 | observableArray.prototype.removeAll = function () {
189 | return this.splice(0, this.length);
190 | };
191 |
192 | export {
193 | observe,
194 | observableArray
195 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
31 |
32 |
33 | A versatile framework built to enrich the developer experience in terms of simplicity and speed of application development.
34 |
35 |
36 |
37 | ## Table of Contents
38 |
39 | - :hammer: [Installation](#installation)
40 | - :loudspeaker: [Introduction](#introduction)
41 | - :book: [Getting Started and Documentation](#getting-started-and-documentation)
42 | - :electric_plug: [Bootstrap Integration](#bootstrap-integration)
43 | - :thumbsup: [Contribution](#contribution)
44 | - :signal_strength: [Browser Support](#browser-support)
45 | - :clipboard: [Plans](#plans)
46 |
47 | ## Installation
48 |
49 | Run the following npm command:
50 | ```bash
51 | $ npm install @plazarjs/core
52 | ```
53 | Or, place the script tag on your page:
54 | ```html
55 |
56 | ```
57 | Check out the list of available dependant
packages .
58 |
59 | ## Introduction
60 |
61 | PlazarJS is a un-opinionated framework for JavaScript. It has no dependencies and by leaning on Object-Oriented-Principles (OOP) it can easily be used to create a large Single-Page Application or it can be integrated to a portion of a web page where dynamic workflow is required. It's built to be flexible and designed to help you build the application the way you want it without forcing you to follow a path you don't think is suitable for the application you are developing. The main focus is on good old trio, HTML, CSS and JavaScript.
62 |
63 | ##### TL;DR
64 |
65 | 1. Can define components, mixins or classes by invoking `pz.define`, `pz.component.extend` or `pz.class.extend`. Mixins are not extendable.
66 | 2. Reuse each type later in the app as much as needed. One type, multiple instances.
67 | 3. Extend each type with another by setting the `ownerType`. Note that this config is not required when using the `extend` approach. The `ownerType` is automatically recognized.
68 | 4. Override parent method implementation.
69 | 5. Each method overridden in a child type can call its parent by invoking `this.base(arguments)`.
70 |
71 | ##### Core Features
72 |
73 | 1. Modularity
74 | 2. Components
75 | 3. Mixins
76 | 4. Classes
77 | 5. Inheritance
78 | 6. Bindings and Templating (inline, pre-rendered, async templates)
79 |
80 | ## Getting Started and Documentation
81 |
82 | A quick example:
83 |
84 | ```javascript
85 | // define the component
86 | import pz from '@plazarjs/core';
87 |
88 | const helloWorld = {
89 | ownerType: 'component',
90 | template: '
Hello from {fw}
',
91 | renderTo: 'body',
92 | autoLoad: true,
93 | viewModel: {
94 | fw: 'plazarjs'
95 | }
96 | };
97 |
98 | export default pz.define('hello-world', helloWorld);
99 |
100 | // create the component where required
101 | import helloWorld from 'my-path/helloWorld';
102 | helloWorld.create();
103 | ```
104 |
105 | The equivalent of the code above written with the extend API looks like this:
106 |
107 | ```javascript
108 | // define the component
109 | import pz from '@plazarjs/core';
110 |
111 | const helloWorld = {
112 | type: 'hello-world',
113 | template: '
Hello from {fw}
',
114 | renderTo: 'body',
115 | autoLoad: true,
116 | viewModel: {
117 | fw: 'plazarjs'
118 | }
119 | };
120 |
121 | export default pz.component.extend(helloWorld);
122 |
123 | // create the component where required
124 | import helloWorld from 'my-path/helloWorld';
125 | helloWorld.create();
126 | ```
127 | Detailed documentation can be found
here .
128 |
129 | Live demo can be found
here .
130 |
131 | ## Bootstrap Integration
132 |
133 | Checkout the module integration
here .
134 |
135 | ## Contribution
136 |
137 | Please read the
Contributing Guide before making a pull request.
138 |
139 | ## Browser Support
140 |
141 | PlazarJS supports all ECMAScript 5 compliant browsers. Check out the
compatibility table .
142 |
143 | #### IE Browser Support
144 |
145 | Every implementation/change is done in a way to ignore IE version 9 and lower.
146 |
147 | ## Plans
148 |
149 | Some of the next major releases will contain:
150 |
151 | 1. TypeScript support.
152 | 2. Core plazar-ui (set of controls out of the box). This will eliminate the need for any external CSS framework integration. Of course, you would still be able to integrate any of them if you choose so.
153 |
154 | ## License
155 |
156 |
MIT
157 |
--------------------------------------------------------------------------------
/packages/bootstrap-ui/src/components/ui-modal.component.js:
--------------------------------------------------------------------------------
1 | import pz from '@plazarjs/core';
2 | import $ from 'jquery';
3 | import uiBase from '../base/ui-base.component';
4 |
5 | const modal = () => {
6 |
7 | let _primaryButtons = ['Yes', 'Ok'];
8 |
9 | let _parseTemplate = function() {
10 | let headerMarkup = '',
12 | bodyMarkup = '
' + (this.body ? (pz.isEmpty(this.body.text) ? '' : this.body.text) : '') + '
',
13 | footerMarkup = '',
14 | modalContent = pz.dom.findElement(this.html, 'div.modal-content'),
15 | header = pz.dom.findElement(this.html, 'div.modal-header > h5.modal-title'),
16 | body = pz.dom.findElement(this.html, 'div.modal-body'),
17 | footer = pz.dom.findElement(this.html, 'div.modal-footer'),
18 | modal, hasSize = !pz.isEmpty(this.size);
19 |
20 | let addOrUpdate = (el, markup, value) => {
21 | if (pz.isEmpty(el)) {
22 | pz.dom.append(modalContent, markup);
23 | } else if (pz.isObject(value)) {
24 | el.innerHTML += value.text;
25 | };
26 | };
27 |
28 | addOrUpdate(header, (this.header ? headerMarkup : ''), this.header);
29 | addOrUpdate(body, (this.body ? bodyMarkup : ''), this.body);
30 | addOrUpdate(footer, (this.footer ? footerMarkup : ''), this.footer);
31 |
32 | if (this.centered) {
33 | modal = pz.dom.findElement(this.html, 'div.modal-dialog');
34 | this.addCss('modal-dialog-centered', modal);
35 | };
36 |
37 | if (hasSize) {
38 | modal = pz.dom.findElement(this.html, 'div.modal-dialog');
39 | this.addCss(('modal-' + this.size), modal);
40 | };
41 |
42 | this.html.setAttribute('data-keyboard', this.keyboard);
43 | this.html.setAttribute('data-focus', this.autoFocus);
44 | this.html.setAttribute('data-backdrop', this.backdrop);
45 | this.addCss((this.fade ? 'fade' : ''));
46 | };
47 |
48 | let _hasComponentsForSpecificRender = (me, cssClass) => {
49 | return pz.arr.contains(me.components, (item) => {
50 | return (pz.isEmpty(item.renderTo) && me.containerElement == ('div.' + cssClass)) || item.renderTo.indexOf(cssClass);
51 | });
52 | };
53 |
54 | return {
55 | type: 'ui-bootstrap-modal',
56 | ownerType: 'ui-bootstrap-component',
57 | containerElement: 'div.modal-body',
58 | renderTo: 'body',
59 | autoLoad: true,
60 | buttons: 'Yes_No_Cancel',
61 | centered: false,
62 | autoDestroy: false,
63 | autoHide: true,
64 | autoFocus: true,
65 | keyboard: true,
66 | backdrop: true, // or 'static'
67 | template: '
',
68 | header: {
69 | text: ''
70 | },
71 | body: {
72 | text: ''
73 | },
74 | footer: {
75 | text: ''
76 | },
77 | fade: false,
78 | init: function() {
79 | let buttons = this.buttons.split('_'), me = this,
80 | hasBodyComponents;
81 | this.components = this.components || [];
82 | this.footer = !pz.isEmpty(this.buttons) || this.footer;
83 |
84 | pz.forEach(buttons, (button) => {
85 | this.components.push({
86 | type: 'ui-bootstrap-button',
87 | appearance: (pz.arr.contains(_primaryButtons, button) || buttons.length == 1) ? 'primary' : 'secondary',
88 | renderTo: 'div.modal-footer',
89 | text: button,
90 | onClick: (e) => {
91 | me.onButtonClick.call(this, e, button);
92 | if (me.autoHide) {
93 | me.hide();
94 | };
95 | }
96 | });
97 | }, this);
98 |
99 | hasBodyComponents = !pz.isEmpty(this.components) && _hasComponentsForSpecificRender(this, 'modal-body');
100 |
101 | if (hasBodyComponents) {
102 | this.body = true;
103 | };
104 |
105 | this.handlers = pz.arr.merge((this.handlers || []), [{
106 | on: 'show.bs.modal',
107 | fn: 'onModalShow'
108 | }, {
109 | on: 'shown.bs.modal',
110 | fn: 'onModalShown'
111 | }, {
112 | on: 'hide.bs.modal',
113 | fn: 'onModalHide'
114 | }, {
115 | on: 'hidden.bs.modal',
116 | fn: 'onModalHidden'
117 | }]);
118 |
119 | this.base(arguments);
120 | },
121 | parseTemplate: _parseTemplate,
122 | show: function(config) {
123 | $(this.html).modal('show');
124 | },
125 | hide: function() {
126 | $(this.html).modal('hide');
127 | },
128 | update: function() {
129 | $(this.html).modal('handleUpdate');
130 | },
131 | toggle: function() {
132 | $(this.html).modal('toggle');
133 | },
134 | onButtonClick: function() { },
135 | onModalShown: function(e) {
136 | this.publish('shown-bs-modal', e);
137 | },
138 | onModalShow: function(e) {
139 | this.publish('show-bs-modal', e);
140 | },
141 | onModalHide: function(e) {
142 | this.publish('hide-bs-modal', e);
143 | },
144 | onModalHidden: function(e) {
145 | this.publish('hidden-bs-modal', e);
146 | if (this.autoDestroy) {
147 | $(this.html).modal('dispose');
148 | this.destroy();
149 | };
150 | }
151 | };
152 | };
153 |
154 | export default uiBase.extend(modal);
--------------------------------------------------------------------------------
/packages/core/src/statics/dom.js:
--------------------------------------------------------------------------------
1 | import pz from '../core';
2 |
3 | const dom = () => {
4 |
5 | let _tagNameReg = /<([^\s>]+)(\s|>)+/;
6 |
7 | let _wrapMap = {
8 | option: [1, '
', ' '],
9 | thead: [1, '
'],
10 | col: [2, '
'],
11 | tr: [2, '
'],
12 | td: [3, '
']
13 | };
14 | _wrapMap.optgroup = _wrapMap.option;
15 | _wrapMap.tbody = _wrapMap.tfoot = _wrapMap.colgroup = _wrapMap.caption = _wrapMap.thead;
16 | _wrapMap.th = _wrapMap.td;
17 |
18 | let _doInsert = function (element, newNode, where) {
19 |
20 | if (pz.isEmpty(element) || pz.isEmpty(newNode)) {
21 | return;
22 | }
23 |
24 | element.insertAdjacentHTML(where, (pz.isString(newNode) ?
25 | newNode : newNode.outerHTML));
26 | };
27 |
28 | let _getListener = function (element, event) {
29 | return pz.find(function (lst) {
30 | return lst.el == element && (!pz.isEmpty(event) ? (lst.event == event) : true);
31 | }, _delegate.listeners);
32 | };
33 |
34 | let _delegate = {
35 | data: [],
36 | listeners: [],
37 | remove: function (element, event) {
38 | let i, j, listenerIndexes = this.listeners.reduce(function (acc, lst, idx) {
39 | let isOk = (lst.el == element && (!pz.isEmpty(event) ? (lst.event == event) : true));
40 | if (isOk) { acc.push(idx); }
41 | return acc;
42 | }, []), dataIndexes, listenerIdx, listener;
43 |
44 | i = listenerIndexes.length - 1;
45 | for (; i >= 0; i--) {
46 | listenerIdx = listenerIndexes[i];
47 | listener = this.listeners[listenerIdx];
48 | listener.el.removeEventListener(listener.event, _delegate.fn, listener.capture);
49 | this.listeners.splice(listenerIdx, 1);
50 |
51 | dataIndexes = this.data.reduce(function (acc, dataItem, idx) {
52 | if (dataItem.id == listener.id) { acc.push(idx); }
53 | return acc;
54 | }, []);
55 | j = dataIndexes.length - 1;
56 | for (; j >= 0; j--) {
57 | let idx = dataIndexes[j];
58 | this.data.splice(idx, 1);
59 | }
60 | }
61 | listener = null;
62 | },
63 | fn: function (e) {
64 |
65 | let target = null;
66 | let match = false, i = 0, parentEmpty = true,
67 | triggerEvent, targetMatches, dataItem, selector, fn,
68 | data = _delegate.data.filter(function (item) {
69 | return item.type == e.type;
70 | });
71 |
72 | while (!pz.isEmpty(dataItem = data[i])
73 | && !(match = pz.dom.elementMatches(e.target, dataItem.selector))) {
74 | i++;
75 | }
76 |
77 | selector = (match ? dataItem.selector : undefined);
78 | fn = (match ? dataItem.fn : undefined);
79 | dataItem = null;
80 | targetMatches = match;
81 |
82 | if (targetMatches) {
83 | target = e.target;
84 | }
85 |
86 | if (!targetMatches) {
87 | let parent = pz.dom.findParent(e.target, selector);
88 | parentEmpty = pz.isEmpty(parent);
89 | if (!parentEmpty) {
90 | target = parent;
91 | }
92 | }
93 |
94 | triggerEvent = targetMatches || !parentEmpty;
95 |
96 | if (triggerEvent && pz.isEmpty(target)) {
97 | target = this;
98 | }
99 |
100 | if (triggerEvent) {
101 | fn.call(this, target, e);
102 | }
103 | }
104 | };
105 |
106 | return {
107 | clone: function (el) {
108 | return el.cloneNode(true);
109 | },
110 |
111 | replaceWith: function (node, newNode) {
112 | if (pz.isEmpty(node) || pz.isEmpty(newNode)) {
113 | return;
114 | }
115 |
116 | node.parentNode.replaceChild(newNode, node);
117 | node = null;
118 | },
119 |
120 | append: function (parent, element) {
121 | if (pz.isEmpty(parent) || pz.isEmpty(element)) {
122 | return;
123 | }
124 |
125 | if (pz.isString(element)) {
126 | element = this.parseTemplate(element);
127 | }
128 |
129 | parent.appendChild(element);
130 | },
131 |
132 | prepend: function (parent, element) {
133 | _doInsert(parent, element, 'afterbegin');
134 | },
135 |
136 | insertBefore: function (element, newNode) {
137 | _doInsert(element, newNode, 'beforebegin');
138 | },
139 |
140 | insertAfter: function (element, newNode) {
141 | _doInsert(element, newNode, 'afterend');
142 | },
143 |
144 | findElement: function (rootEl, selector, all) {
145 |
146 | if (pz.isEmpty(selector)) {
147 | return null;
148 | }
149 |
150 | return this.getEl(selector, { rootEl: rootEl, all: (all || false) });
151 | },
152 |
153 | elementMatches: function (element, selector) {
154 | let fn = Element.prototype.matches || Element.prototype.msMatchesSelector || function (s) {
155 | let matches = (this.document || this.ownerDocument).querySelectorAll(s),
156 | i = matches.length;
157 | while (--i >= 0 && matches.item(i) !== this) { }
158 | return i > -1;
159 | };
160 | return fn.call(element, selector);
161 | },
162 |
163 | findParent: function (el, selector, stopSelector) {
164 | let retval = null;
165 | while (!pz.isEmpty(el)) {
166 | if (this.elementMatches(el, selector)) {
167 | retval = el;
168 | break;
169 | } else if (stopSelector && this.elementMatches(el, stopSelector)) {
170 | break;
171 | }
172 | el = el.parentElement;
173 | }
174 | return retval;
175 | },
176 |
177 | on: function (event, element, selector, fn) {
178 | let rootEl, lst, lstEmpty, id, capture;
179 |
180 | if (pz.isEmpty(fn)) {
181 | return;
182 | }
183 |
184 | rootEl = !pz.isEmpty(element) ? element : document;
185 | lst = _getListener(rootEl, event);
186 | lstEmpty = pz.isEmpty(lst);
187 | id = (lstEmpty ? (Date.now() + Math.random()).toString() : lst.id);
188 |
189 | _delegate.data.push({
190 | selector: selector,
191 | fn: fn,
192 | type: event,
193 | id: id
194 | });
195 |
196 | if (!pz.isEmpty(lst)) {
197 | return;
198 | }
199 |
200 | capture = ['blur', 'focus', 'focusout', 'focusin'].indexOf(event) != -1;
201 | rootEl.addEventListener(event, _delegate.fn, capture);
202 | _delegate.listeners.push({
203 | el: rootEl,
204 | event: event,
205 | id: id,
206 | capture: capture
207 | });
208 | },
209 |
210 | getByAttr: function (attrValue, attrName) {
211 | let name = attrName || 'data-template';
212 | let selector = '*['.concat(name).concat('="').concat(attrValue).concat('"]');
213 | return document.querySelector(selector);
214 | },
215 |
216 | getEl: function (selector, options) {
217 | let getAll = options && options.all || false;
218 | let root = options && options.rootEl || document;
219 | let method = getAll ? 'querySelectorAll' : 'querySelector';
220 | let element = root[method](selector);
221 | return pz.isEmpty(element) ? null :
222 | ((element.length == 1 && element.nodeName != 'FORM') ? element[0] : element);
223 | },
224 |
225 | parseTemplate: function (template) {
226 | let tagName, temp, trimmed, i,
227 | regResult, wrapper, wrapperEmpty,
228 | fragment, result;
229 |
230 | if (!pz.isString(template)) {
231 | return null;
232 | }
233 |
234 | trimmed = template.trim();
235 | regResult = _tagNameReg.exec(trimmed);
236 | if (pz.isEmpty(regResult)) {
237 | return null;
238 | }
239 |
240 | fragment = document.createDocumentFragment();
241 | tagName = regResult[1];
242 | wrapper = _wrapMap[tagName];
243 | wrapperEmpty = pz.isEmpty(wrapper);
244 |
245 | temp = fragment.appendChild(this.createElement('div'));
246 | temp.innerHTML = wrapperEmpty ? trimmed : wrapper[1].concat(trimmed).concat(wrapper[2]);
247 |
248 | i = wrapperEmpty ? 0 : wrapper[0];
249 | while (i--) {
250 | temp = temp.lastChild;
251 | }
252 |
253 | result = temp.childNodes.length == 1 ?
254 | temp.lastChild : temp.childNodes;
255 |
256 | fragment = null;
257 | temp = null;
258 | return result;
259 | },
260 |
261 | createElement: function (tagName) {
262 | let el;
263 | try {
264 | el = document.createElement(tagName);
265 | } catch (e) {
266 | throw e;
267 | }
268 | return el;
269 | },
270 |
271 | remove: function (element) {
272 | let parent;
273 |
274 | if (pz.isEmpty(element)) {
275 | return;
276 | }
277 |
278 | parent = element.parentNode;
279 | if (!pz.isEmpty(parent)) {
280 | parent.removeChild(element);
281 | }
282 | element = null;
283 | },
284 |
285 | insertAt: function (parent, newNode, index) {
286 | let referenceNode;
287 | if (pz.isEmpty(parent) || pz.isEmpty(index)) {
288 | return;
289 | }
290 |
291 | if (pz.isEmpty(parent.childNodes)) {
292 | this.prepend(parent, newNode);
293 | return;
294 | }
295 |
296 | referenceNode = parent.childNodes[index];
297 |
298 | if (pz.isEmpty(referenceNode)) {
299 | throw new Error('Node at index: ' + index + ' was not found.');
300 | }
301 |
302 | parent.insertBefore(newNode, referenceNode);
303 | },
304 |
305 | off: function (element, event) {
306 | _delegate.remove(element, event);
307 | },
308 |
309 | indexOf: function (child) {
310 | let i = 0;
311 | while ((child = child.previousSibling) != null)
312 | i++;
313 | return i;
314 | }
315 | };
316 |
317 | };
318 |
319 | export default dom;
--------------------------------------------------------------------------------
/packages/core/src/statics/binder/index.js:
--------------------------------------------------------------------------------
1 | import pz from '../../core';
2 | import { observableArray, observe } from './observableArray';
3 | import view from './view';
4 | import reservedKeys from './reserved-keys';
5 |
6 | const binder = {
7 | prefix: 'data',
8 | delimiters: ['{', '}'],
9 | bind: function (els, viewModel) {
10 |
11 | if (viewModel.$view) {
12 | viewModel.$view.bind();
13 | return;
14 | }
15 |
16 | observe(viewModel);
17 | let v = new view(els, viewModel);
18 | v.bind();
19 | viewModel.$view = v;
20 | v = null;
21 | },
22 | unbind: function (viewModel) {
23 | if (viewModel.$view) {
24 | viewModel.$view.unbind();
25 | }
26 | },
27 | toJSON: function (viewModel) {
28 | let getProperties = function (value) {
29 | return Object.keys(value).filter(function (key) {
30 | return key != reservedKeys.observed && key != reservedKeys.view;
31 | });
32 | }, toJSON = function (value, res) {
33 |
34 | let properties = getProperties(value);
35 |
36 | pz.forEach(properties, function (prop) {
37 |
38 | let isObject = pz.isObject(value[prop]),
39 | isFunction = pz.isFunction(value[prop]),
40 | isObsArray = pz.isInstanceOf(value[prop], observableArray);
41 |
42 | if (isObject) {
43 | res[prop] = toJSON(value[prop], {});
44 | }
45 |
46 | if (isObsArray) {
47 | res[prop] = [];
48 | let dataKeys = Object.keys(value[prop]).filter(function (key) {
49 | return !isNaN(parseInt(key));
50 | });
51 |
52 | pz.forEach(dataKeys, function (key) {
53 | let item = value[prop][key];
54 | let val = (pz.isObject(item) ? toJSON(item, {}) :
55 | (pz.isFunction(item) ? item() : item));
56 | res[prop].push(val);
57 | });
58 | }
59 |
60 | if (isFunction && !pz.isEmpty(value[prop].subscribe)) {
61 | res[prop] = value[prop]();
62 | }
63 | });
64 |
65 | return res;
66 | };
67 |
68 | return toJSON(viewModel, {});
69 | },
70 | binders: {
71 | 'value': {
72 | priority: 3,
73 | bind: function () {
74 |
75 | let isInput = this.el.nodeName == 'INPUT',
76 | isOption = this.el.nodeName == 'OPTION',
77 | isSelect = this.el.nodeName == 'SELECT',
78 | isTextArea = this.el.nodeName == 'TEXTAREA',
79 | event, isText, globalScope;
80 |
81 | if (!isInput && !isOption && !isSelect && !isTextArea) {
82 | throw new Error('Value binding is supported only on INPUT, OPTION or SELECT element');
83 | }
84 |
85 | globalScope = pz.getGlobal();
86 | event = isInput || isTextArea ? (('oninput' in globalScope) ? 'input' : 'keyup') : 'change';
87 | isText = isInput && this.el.type == 'text' || isTextArea;
88 |
89 | if ((isSelect || isText) && pz.isFunction(this.handler)) {
90 | this.el.removeEventListener(event, this.handler, false);
91 | this.el.addEventListener(event, this.handler, false);
92 | this.event = event;
93 | }
94 |
95 | this.el.removeAttribute(this.bindingAttr);
96 | },
97 | react: function () {
98 |
99 | let isInput = this.el.nodeName == 'INPUT',
100 | isSelect = this.el.nodeName == 'SELECT',
101 | isTextArea = this.el.nodeName == 'TEXTAREA',
102 | isText = isInput && this.el.type == 'text';
103 |
104 | if (isText || isSelect || isTextArea) {
105 | this.el.value = this.getValue();
106 | } else {
107 | this.el.setAttribute('value', this.getValue());
108 | }
109 | },
110 | handler: function () {
111 | this.setValue(this.el.value);
112 | },
113 | unbind: function () {
114 | this.el.removeEventListener(this.event, this.handler, false);
115 | }
116 | },
117 | 'each': {
118 | priority: 1,
119 | block: true,
120 | bind: function () {
121 |
122 | if (!this.mark) {
123 | this.mark = document.createComment('each:' + this.id);
124 | this.el.removeAttribute(this.bindingAttr);
125 | this.el.parentNode.insertBefore(this.mark, this.el);
126 | this.el.parentNode.removeChild(this.el);
127 | }
128 |
129 | if (!this.views) {
130 | this.views = [];
131 | }
132 | },
133 | react: function () {
134 |
135 | let value = this.getValue(), template;
136 |
137 | pz.forEach(this.views, function (view) {
138 | view.unbind();
139 | pz.forEach(view.els, function (el) {
140 | el.parentNode.removeChild(el);
141 | el = null;
142 | });
143 | view.els.splice(0, view.els.length);
144 | });
145 |
146 | this.views.splice(0, this.views.length);
147 |
148 | pz.forEach(value, function (item, idx) {
149 | template = this.el.cloneNode(true);
150 | let v = new view(template, this.rootVm, item, idx, this.alias, this.view);
151 | v.bind();
152 | this.mark.parentNode.insertBefore(template, this.mark);
153 | this.views.push(v);
154 | }, this);
155 | },
156 | unbind: function () {
157 | pz.forEach(this.views, function (view) {
158 | view.unbind();
159 | });
160 | }
161 | },
162 | 'text': {
163 | priority: 3,
164 | bind: function () {
165 | this.el.removeAttribute(this.bindingAttr);
166 | },
167 | react: function () {
168 | let hasInnerText = this.el.hasOwnProperty('innerText');
169 | this.el[hasInnerText ? 'innerText' : 'innerHTML'] = this.getValue();
170 | }
171 | },
172 | 'if': {
173 | priority: 2,
174 | bind: function (val) {
175 | let value = val != undefined ? val : this.getValue();
176 | this.el.removeAttribute(this.bindingAttr);
177 |
178 | if (!value && !pz.isEmpty(this.el.parentNode)) {
179 | this.el.parentNode.removeChild(this.el);
180 | }
181 | }
182 | },
183 | 'ifnot': {
184 | priority: 2,
185 | bind: function () {
186 | let value = this.getValue();
187 | pz.binder.binders.if.bind.call(this, !value);
188 | }
189 | },
190 | 'visible': {
191 | priority: 2,
192 | bind: function () {
193 | this.el.removeAttribute(this.bindingAttr);
194 | },
195 | react: function (val) {
196 | let value = val != undefined ? val : this.getValue();
197 |
198 | if (this.initialValue == undefined) {
199 | this.initialValue = this.el.style.display;
200 | }
201 |
202 | this.el.style.display = (value == true ?
203 | (this.initialValue == 'none' ? '' : this.initialValue) : 'none');
204 | }
205 | },
206 | 'hidden': {
207 | bind: function () {
208 | pz.binder.binders.visible.bind.call(this);
209 | },
210 | react: function () {
211 | let value = this.getValue();
212 | pz.binder.binders.visible.react.call(this, !value);
213 | }
214 | },
215 | 'html': {
216 | bind: function () {
217 | this.el.removeAttribute(this.bindingAttr);
218 | },
219 | react: function () {
220 | this.el.innerHTML = this.getValue();
221 | }
222 | },
223 | 'attr': {
224 | priority: 2,
225 | bind: function () {
226 | this.el.removeAttribute(this.bindingAttr);
227 | },
228 | react: function () {
229 | let isClassAttr = (this.attrToBind == 'class');
230 | let value = this.getValue();
231 |
232 | if (isClassAttr && this.el.hasAttribute(this.attrToBind)) {
233 | value = (this.el.getAttribute(this.attrToBind) + ' ' + value).trim();
234 | }
235 |
236 | this.el.setAttribute(this.attrToBind, value.toString());
237 | }
238 | },
239 | 'checked': {
240 | bind: function () {
241 | this.el.removeEventListener('change', this.handler, false);
242 | this.el.addEventListener('change', this.handler, false);
243 | this.event = 'change';
244 | this.el.removeAttribute(this.bindingAttr);
245 | },
246 | react: function () {
247 | let isRadio = this.el.type == 'radio',
248 | value = this.getValue();
249 |
250 | if (isRadio) {
251 | this.el.checked = value == this.el.value;
252 | } else {
253 | this.el.checked = value;
254 | }
255 | },
256 | handler: function () {
257 | let isRadio = this.el.type == 'radio';
258 | this.setValue((isRadio ? this.el.value : this.el.checked));
259 | },
260 | unbind: function () {
261 | this.el.removeEventListener(this.event, this.handler, false);
262 | }
263 | },
264 | 'enabled': {
265 | bind: function () {
266 | this.el.removeAttribute(this.bindingAttr);
267 | },
268 | react: function () {
269 | let value = this.getValue();
270 | this.el.disabled = !value;
271 | }
272 | },
273 | 'options': {
274 | priority: 1,
275 | block: true,
276 | tempAttrs: {
277 | val: null,
278 | text: null
279 | },
280 | bind: function () {
281 |
282 | this.el.removeAttribute(this.bindingAttr);
283 |
284 | if (!this.views) {
285 | this.views = [];
286 | }
287 |
288 | this.binder.tempAttrs.val = this.binder.tempAttrs.val || this.el.getAttribute('data-optionsvalue');
289 | this.binder.tempAttrs.text = this.binder.tempAttrs.text || this.el.getAttribute('data-optionstext');
290 | this.el.removeAttribute('data-optionsvalue');
291 | this.el.removeAttribute('data-optionstext');
292 | },
293 | react: function () {
294 | let value = this.getValue();
295 |
296 | pz.forEach(this.views, function (view) {
297 | view.unbind();
298 | pz.forEach(view.els, function (el) {
299 | el.parentNode.removeChild(el);
300 | el = null;
301 | });
302 | view.els.splice(0, view.els.length);
303 | });
304 |
305 | this.views.splice(0, this.views.length);
306 |
307 | pz.forEach(value, function (item, idx) {
308 | let template = document.createElement('option');
309 | template.setAttribute('data-value', this.binder.tempAttrs.val);
310 | template.setAttribute('data-text', this.binder.tempAttrs.text);
311 |
312 | let v = new view(template, this.rootVm, item, idx, this.alias, this.view);
313 | v.bind();
314 | this.el.appendChild(template);
315 | this.views.push(v);
316 | template = null;
317 | }, this);
318 | },
319 | unbind: function () {
320 | pz.forEach(this.views, function (view) {
321 | view.unbind();
322 | });
323 | }
324 | },
325 | 'on': {
326 | priority: 1,
327 | bind: function () {
328 | this.el.removeAttribute(this.bindingAttr);
329 | this.event = this.attrToBind;
330 | this.capture = ['blur', 'focus', 'focusout', 'focusin'].indexOf(this.event) != -1;
331 | this.el.addEventListener(this.event, this.handler, this.capture);
332 | },
333 | unbind: function () {
334 | this.el.removeEventListener(this.event, this.handler, this.capture);
335 | },
336 | handler: function () {
337 | let value = this.getValue();
338 |
339 | if (!pz.isEmpty(value) && pz.isFunction(value)) {
340 | value.call(this, this.el);
341 | }
342 | }
343 | }
344 | }
345 | };
346 |
347 | export default binder;
--------------------------------------------------------------------------------
/demo/bootstrap-ui/scripts/components/bootstrap-ui-layout.component.js:
--------------------------------------------------------------------------------
1 | pz.define('bootstrap-ui-layout-component', {
2 | ownerType: 'component',
3 | templateSelector: 'main.app-layout',
4 | autoLoad: true,
5 | components: [{
6 | type: 'ui-bootstrap-container',
7 | renderTo: 'root',
8 | renderAs: 'jumbotron',
9 | fluid: true,
10 | jumbotron: {
11 | title: {
12 | text:'Welcome to Bootstrap Demo',
13 | size: 3
14 | },
15 | leadText: 'Easily Create Application Layouts Using PlazarJS Bootstrap-UI',
16 | buttons: [{
17 | text: 'Getting Started',
18 | size: 'lg',
19 | onClick: function() {
20 | alert('Getting Started');
21 | }
22 | }]
23 | }
24 | }, {
25 | type: 'ui-bootstrap-container',
26 | renderTo: 'root',
27 | components:[{
28 | type: 'ui-bootstrap-breadcrumb',
29 | crumbs: [{
30 | text: 'Main Page'
31 | }, {
32 | text: 'Sub Page 1',
33 | asLink: true
34 | }, {
35 | text: 'Sub Page 1',
36 | asLink: true,
37 | isActive: true
38 | }]
39 | }, {
40 | type: 'ui-bootstrap-container',
41 | renderAs: 'row',
42 | components:[{
43 | type: 'ui-bootstrap-container',
44 | renderAs: 'column',
45 | column: {
46 | lg: 4,
47 | md: 6,
48 | sm: 12
49 | },
50 | components: [{
51 | type: 'ui-bootstrap-card',
52 | css: ['mb-2'],
53 | header: {
54 | text: 'Lorem Ipsum',
55 | css: ['bg-dark text-white']
56 | },
57 | footer: {
58 | text: 'Some Footer Text',
59 | css: ['bg-dark text-white']
60 | },
61 | body: {
62 | css: ['bg-info text-white'],
63 | text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' +
64 | 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' +
65 | 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' +
66 | 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum'
67 | }
68 | }]
69 | }, {
70 | type: 'ui-bootstrap-container',
71 | renderAs: 'column',
72 | column: {
73 | lg: 4,
74 | md: 6,
75 | sm: 12
76 | },
77 | components: [{
78 | type: 'ui-bootstrap-card',
79 | css: ['mb-2'],
80 | header: {
81 | text: 'Simple Form (no footer)'
82 | },
83 | footer: false,
84 | components:[{
85 | type: 'ui-bootstrap-form',
86 | components: [{
87 | type: 'ui-bootstrap-input',
88 | labelText: 'Name:',
89 | placeholder: 'Enter name'
90 | }, {
91 | type: 'ui-bootstrap-input',
92 | labelText: 'Surname:',
93 | placeholder: 'Enter surname'
94 | }, {
95 | type: 'ui-bootstrap-input',
96 | inputType: 'checkbox',
97 | labelText: 'Check me',
98 | onChange: function() {
99 | alert('check change');
100 | }
101 | }, {
102 | type: 'ui-bootstrap-dropdown',
103 | appearance: 'outline-secondary',
104 | labelText: 'Select item:',
105 | text: 'Menu Item 3',
106 | menuItems: [{
107 | text: 'Menu Item 3.1'
108 | }, {
109 | text: 'Menu Item 3.2'
110 | }]
111 | }, {
112 | type: 'ui-bootstrap-select',
113 | labelText: 'Pick an Option',
114 | css: ['mb-2'],
115 | dataSource: [{
116 | id: 1,
117 | value: 'Option 1'
118 | },{
119 | id: 2,
120 | value: 'Option 2'
121 | }],
122 | onChange: function() {
123 | alert('select change');
124 | }
125 | }]
126 | }]
127 | }]
128 | }, {
129 | type: 'ui-bootstrap-container',
130 | renderAs: 'column',
131 | column: {
132 | lg: 4,
133 | md: 12,
134 | sm: 12
135 | },
136 | components: [{
137 | type: 'ui-bootstrap-card',
138 | css: ['mb-2'],
139 | headerCss: ['d-flex justify-content-center'],
140 | components: [{
141 | type: 'ui-bootstrap-button-toolbar',
142 | renderTo: 'div.card-header',
143 | groups: [{
144 | buttons: [{
145 | text: 'Button 1',
146 | size: 'sm',
147 | onClick: function() {
148 | alert('Button 1');
149 | }
150 | }, {
151 | text: 'Button 2',
152 | size: 'sm',
153 | appearance: 'warning',
154 | onClick: function() {
155 | alert('Button 2');
156 | }
157 | }, {
158 | type: 'ui-bootstrap-dropdown',
159 | appearance: 'outline-secondary',
160 | size: 'sm',
161 | text: 'Dropdown Button',
162 | menuItems: [{
163 | text: 'Menu Item 1.1'
164 | }, {
165 | text: 'Menu Item 1.2'
166 | }]
167 | }]
168 | }]
169 | }, {
170 | type: 'ui-bootstrap-button',
171 | appearance: 'link',
172 | size: 'sm',
173 | text: 'Click to add alert!',
174 | onClick: function() {
175 | var parent = this.traceUp();
176 | parent.addChild({
177 | type: 'ui-bootstrap-alert',
178 | dismissible: true,
179 | appearance: 'danger',
180 | text: 'This alert was created dynamically! We can dismiss this one'
181 | });
182 | }
183 | }, {
184 | type: 'ui-bootstrap-alert',
185 | appearance: 'success',
186 | text: 'This alert was created upon parent component initialization with dismissible option disabled!'
187 | }, {
188 | type: 'ui-bootstrap-list-group',
189 | renderTo: 'div.card-footer',
190 | actionable: true,
191 | menuItems: [{
192 | text: 'List Item 1',
193 | href: 'javascript:void(0)'
194 | },{
195 | text: 'List Item 2',
196 | href: 'javascript:void(0)'
197 | }]
198 | }, {
199 | type: 'ui-bootstrap-dropdown',
200 | renderTo: 'div.card-footer',
201 | appearance: 'info',
202 | css: ['mt-2'],
203 | split: true,
204 | text: 'Dropdown Button Split',
205 | menuItems: [{
206 | text: 'Menu Item 1.1'
207 | }, {
208 | text: 'Menu Item 1.2'
209 | }]
210 | }]
211 | }]
212 | }]
213 | }, {
214 | type: 'ui-bootstrap-input-group',
215 | css: ['mb-2'],
216 | addon: [{
217 | renderAs: {
218 | type: 'ui-bootstrap-input',
219 | inputType: 'checkbox',
220 | onChange: function() {
221 | alert('change')
222 | }
223 | }
224 | }, {
225 | renderAs: {
226 | type: 'text',
227 | text: '$'
228 | },
229 | position: 'append'
230 | }, {
231 | renderAs: {
232 | type: 'text',
233 | text: '0.00'
234 | },
235 | position: 'append'
236 | }]
237 | }, {
238 | type: 'ui-bootstrap-input-group',
239 | css: ['mb-3'],
240 | addon: [{
241 | renderAs: {
242 | type: 'ui-bootstrap-dropdown',
243 | appearance: 'outline-secondary',
244 | split: true,
245 | text: 'Actions',
246 | menuItems: [{
247 | text: 'Action 1'
248 | }, {
249 | text: 'Action 2'
250 | }],
251 | onChange: function() {
252 | alert('change')
253 | }
254 | }
255 | }]
256 | }]
257 | }]
258 | });
--------------------------------------------------------------------------------