mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-13 07:42:55 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
44
packages/noodl-editor/src/shared/ReactView.ts
Normal file
44
packages/noodl-editor/src/shared/ReactView.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import View from './view';
|
||||
|
||||
export interface ReactViewDefaultProps {
|
||||
owner?: TSFixme;
|
||||
}
|
||||
|
||||
export abstract class ReactView<TProps extends ReactViewDefaultProps> extends View {
|
||||
private props: TProps;
|
||||
|
||||
public el: any;
|
||||
|
||||
constructor(props: TProps) {
|
||||
super();
|
||||
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
public set owner(owner: TSFixme) {
|
||||
this.props.owner = owner;
|
||||
this.render();
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (!this.el) {
|
||||
this.el = $(document.createElement('div'));
|
||||
this.el.css({
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
});
|
||||
}
|
||||
|
||||
ReactDOM.render(React.createElement(this.renderReact.bind(this), this.props), this.el[0]);
|
||||
|
||||
return this.el;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.el && ReactDOM.unmountComponentAtNode(this.el[0]);
|
||||
}
|
||||
|
||||
protected abstract renderReact(props: TProps): JSX.Element;
|
||||
}
|
||||
17
packages/noodl-editor/src/shared/config/config-dev.js
Normal file
17
packages/noodl-editor/src/shared/config/config-dev.js
Normal file
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
type: 'dev',
|
||||
Tracker: {
|
||||
trackExceptions: false
|
||||
},
|
||||
PreviewServer: {
|
||||
port: 8574
|
||||
},
|
||||
devMode: true,
|
||||
|
||||
apiEndpoint: 'https://apidev.noodlcloud.com',
|
||||
domainEndpoint: 'http://domains.noodlcloud.com',
|
||||
aiEndpoint: 'https://ftii7qa6g2a3k3hlwi6etoo3z40qbbtw.lambda-url.us-east-1.on.aws'
|
||||
|
||||
// Test config during dev
|
||||
// userConfig: require('./userconfig-dev')
|
||||
};
|
||||
13
packages/noodl-editor/src/shared/config/config-dist.js
Normal file
13
packages/noodl-editor/src/shared/config/config-dist.js
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
type: 'dist',
|
||||
Tracker: {
|
||||
trackExceptions: true
|
||||
},
|
||||
PreviewServer: {
|
||||
port: 8574
|
||||
},
|
||||
|
||||
apiEndpoint: 'https://api.noodlcloud.com',
|
||||
domainEndpoint: 'http://domains.noodlcloud.com',
|
||||
aiEndpoint: 'https://p2qsqhrh6xd6relfoaye4tf6nm0bibpm.lambda-url.us-east-1.on.aws'
|
||||
};
|
||||
10
packages/noodl-editor/src/shared/config/config-test.js
Normal file
10
packages/noodl-editor/src/shared/config/config-test.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
type: 'test',
|
||||
Tracker: {
|
||||
trackExceptions: false
|
||||
},
|
||||
PreviewServer: {
|
||||
port: 8574
|
||||
},
|
||||
apiEndpoint: 'https://apidev.noodlcloud.com'
|
||||
};
|
||||
17
packages/noodl-editor/src/shared/config/config.js
Normal file
17
packages/noodl-editor/src/shared/config/config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const configDev = require('./config-dev');
|
||||
const configDist = require('./config-dist');
|
||||
|
||||
function getProcess() {
|
||||
try {
|
||||
const remote = require('@electron/remote');
|
||||
return remote ? remote.process : process;
|
||||
} catch (exc) {
|
||||
// Error: "@electron/remote" cannot be required in the browser process. Instead require("@electron/remote/main").
|
||||
return process;
|
||||
}
|
||||
}
|
||||
|
||||
const _process = getProcess();
|
||||
|
||||
if (!_process.env.devMode) _process.env.devMode = (_process.argv || []).indexOf('--dev') !== -1 ? 'yes' : 'no';
|
||||
module.exports = _process.env.devMode === 'yes' ? configDev : configDist;
|
||||
15
packages/noodl-editor/src/shared/model.d.ts
vendored
Normal file
15
packages/noodl-editor/src/shared/model.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export default class Model {
|
||||
events: { [key: string]: string };
|
||||
|
||||
listeners: TSFixme[];
|
||||
static _listenersEnabled: boolean;
|
||||
|
||||
set(args: TSFixme): void;
|
||||
|
||||
on(event: string | string[], listener: (...args: any) => void, group?: number | string | object): Model;
|
||||
once(event: string | string[], listener: (...args: any) => void): Model;
|
||||
|
||||
notifyListeners(event: string, ...args: any[]): Model;
|
||||
off(group: number | string | object): Model;
|
||||
removeAllListeners(): void;
|
||||
}
|
||||
100
packages/noodl-editor/src/shared/model.js
Normal file
100
packages/noodl-editor/src/shared/model.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const { EventDispatcher } = require('./utils/EventDispatcher');
|
||||
|
||||
var Model = function () {
|
||||
this.listeners = [];
|
||||
this.listenersOnce = [];
|
||||
};
|
||||
|
||||
Model._listenersEnabled = true;
|
||||
|
||||
Model.prototype.on = function (event, listener, group) {
|
||||
this.listeners.push({
|
||||
event: event,
|
||||
listener: listener,
|
||||
group: group
|
||||
});
|
||||
|
||||
if (this.listeners.length > 10000) {
|
||||
console.log('Warning: we have more that 10000 listeners on this model, is this sane?');
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Model.prototype.once = function (event, listener) {
|
||||
this.listenersOnce.push({
|
||||
event: event,
|
||||
listener: listener
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Model.prototype.set = function (args) {
|
||||
for (var i in args) this[i] = args[i];
|
||||
this.notifyListeners('change', {
|
||||
model: this
|
||||
});
|
||||
};
|
||||
|
||||
function shouldNotify(l, event) {
|
||||
if (l.event.constructor == Array && l.event.indexOf(event) !== -1) {
|
||||
return true;
|
||||
} else if (l.event === event) {
|
||||
return true;
|
||||
} else if (l.event.indexOf !== undefined) {
|
||||
// Support for dot notation in subscription
|
||||
var index = l.event.indexOf(event);
|
||||
if (index > 0 && l.event[index - 1] === '.') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Model.prototype.notifyListeners = function (event, args) {
|
||||
if (Model._listenersEnabled === false) return;
|
||||
|
||||
for (let index = 0; index < this.listeners.length; index++) {
|
||||
const listener = this.listeners[index];
|
||||
if (shouldNotify(listener, event)) {
|
||||
listener.listener(args);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.listenersOnce.length > 0) {
|
||||
this.listenersOnce = this.listenersOnce.filter((listener) => {
|
||||
if (shouldNotify(listener, event)) {
|
||||
listener.listener(args);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Dispatch global event
|
||||
EventDispatcher.instance.notifyListeners('Model.' + event, {
|
||||
model: this,
|
||||
args: args
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Model.prototype.off = function (group) {
|
||||
for (var i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i].group === group) {
|
||||
this.listeners.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Model.prototype.removeAllListeners = function () {
|
||||
this.listeners = [];
|
||||
this.listenersOnce = [];
|
||||
};
|
||||
|
||||
module.exports = Model;
|
||||
45
packages/noodl-editor/src/shared/utils/EventDispatcher.ts
Normal file
45
packages/noodl-editor/src/shared/utils/EventDispatcher.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export class EventDispatcher {
|
||||
listeners: any[];
|
||||
|
||||
constructor() {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
on(event: string | string[], listener, group: unknown) {
|
||||
this.listeners.push({ event: event, listener: listener, group: group });
|
||||
}
|
||||
|
||||
notifyListeners(event: string, args?) {
|
||||
const testForComps = event.split('.');
|
||||
|
||||
function notify(event, listener, eventName) {
|
||||
const comps = event.split('.');
|
||||
if (comps[0] === testForComps[0] && (comps[1] === '*' || comps[1] === testForComps[1])) listener(args, eventName);
|
||||
}
|
||||
|
||||
for (const i in this.listeners) {
|
||||
if (this.listeners[i].event instanceof Array) {
|
||||
for (const j in this.listeners[i].event) notify(this.listeners[i].event[j], this.listeners[i].listener, event);
|
||||
} else {
|
||||
notify(this.listeners[i].event, this.listeners[i].listener, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit(event: string, args?) {
|
||||
this.notifyListeners(event, args);
|
||||
}
|
||||
|
||||
off(group: unknown) {
|
||||
if (group === undefined) return;
|
||||
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i].group === group) {
|
||||
this.listeners.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static instance = new EventDispatcher();
|
||||
}
|
||||
68
packages/noodl-editor/src/shared/utils/jsonstorage.js
Normal file
68
packages/noodl-editor/src/shared/utils/jsonstorage.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const path = require('path');
|
||||
const mkdir = require('mkdirp-sync');
|
||||
const fs = require('fs');
|
||||
const rimraf = require('rimraf');
|
||||
const electron = require('electron');
|
||||
const app = electron.app || require('@electron/remote').app;
|
||||
|
||||
function fileNameForKey(key) {
|
||||
const keyFileName = path.basename(key, '.json') + '.json';
|
||||
|
||||
// Prevent ENOENT and other similar errors when using
|
||||
// reserved characters in Windows filenames.
|
||||
// See: https://en.wikipedia.org/wiki/Filename#Reserved%5Fcharacters%5Fand%5Fwords
|
||||
const escapedFileName = encodeURIComponent(keyFileName);
|
||||
|
||||
const userDataPath = app.getPath('userData');
|
||||
return path.join(userDataPath, escapedFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depracted This implementation is not cross-platform use this instead:
|
||||
* ```ts
|
||||
* import { JSONStorage } from '@noodl/platform';
|
||||
* ```
|
||||
*
|
||||
* Can't remove this file because it's called from main thread,
|
||||
* where the platform code is not setup.
|
||||
*/
|
||||
module.exports = {
|
||||
get: function (key, callback) {
|
||||
var filename = fileNameForKey(key);
|
||||
fs.readFile(filename, { encoding: 'utf8' }, function (error, object) {
|
||||
if (!error) {
|
||||
var objectJSON = {};
|
||||
try {
|
||||
objectJSON = JSON.parse(object);
|
||||
} catch (error) {
|
||||
return callback(); //new Error('Invalid data'));
|
||||
}
|
||||
return callback(objectJSON);
|
||||
}
|
||||
|
||||
if (error.code === 'ENOENT') {
|
||||
return callback(JSON.stringify({}));
|
||||
}
|
||||
|
||||
return callback();
|
||||
});
|
||||
|
||||
/*storage.get(path, function(error, data) {
|
||||
if (error) { callback(); return }
|
||||
callback(data);
|
||||
});*/
|
||||
},
|
||||
set: function (key, data) {
|
||||
var filename = fileNameForKey(key);
|
||||
const json = JSON.stringify(data);
|
||||
|
||||
mkdir(path.dirname(filename));
|
||||
fs.writeFileSync(filename, json);
|
||||
},
|
||||
remove: function (key, callback) {
|
||||
var filename = fileNameForKey(key);
|
||||
rimraf(filename, callback);
|
||||
|
||||
// storage.remove(path,callback);
|
||||
}
|
||||
};
|
||||
161
packages/noodl-editor/src/shared/utils/projectmodules.js
Normal file
161
packages/noodl-editor/src/shared/utils/projectmodules.js
Normal file
@@ -0,0 +1,161 @@
|
||||
const fs = require('fs');
|
||||
|
||||
class ProjectModules {
|
||||
constructor() {}
|
||||
scanProjectModules(projectDirectory, callback) {
|
||||
if (projectDirectory === undefined) {
|
||||
callback(); // No project directory, no modules
|
||||
return;
|
||||
}
|
||||
|
||||
var modulesPath = projectDirectory + '/noodl_modules';
|
||||
fs.readdir(modulesPath, function (err, files) {
|
||||
if (err) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
//we only care about directories
|
||||
const directories = files.filter((f) => {
|
||||
var stats = fs.lstatSync(modulesPath + '/' + f);
|
||||
return stats.isDirectory() || stats.isSymbolicLink();
|
||||
});
|
||||
|
||||
if (directories.length === 0) {
|
||||
callback();
|
||||
} else {
|
||||
// For each subfolder in noodl_modules
|
||||
var modules = [];
|
||||
var modulesLeft = 0;
|
||||
|
||||
function completeModule(path, manifest) {
|
||||
if (path && manifest) {
|
||||
// Module manifest is fetched, resolve local paths
|
||||
var m = {
|
||||
dependencies: [],
|
||||
browser: manifest.browser,
|
||||
runtimes: manifest.runtimes || ['browser'] //default to browser
|
||||
};
|
||||
|
||||
if (manifest.main) {
|
||||
m.index = path + '/' + manifest.main;
|
||||
}
|
||||
|
||||
if (manifest.dependencies) {
|
||||
for (var j = 0; j < manifest.dependencies.length; j++) {
|
||||
var d = manifest.dependencies[j];
|
||||
if (!d.startsWith['http']) d = path + '/' + d;
|
||||
|
||||
m.dependencies.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
modules.push(m);
|
||||
}
|
||||
|
||||
modulesLeft--;
|
||||
if (modulesLeft === 0) {
|
||||
//sort modules so the order is deterministics
|
||||
//helps the editor understand when node libraries change, or are the same
|
||||
const modulesWithIndexFile = modules.filter((m) => m.index);
|
||||
const modulesWithoutIndexFile = modules.filter((m) => !m.index);
|
||||
|
||||
modulesWithIndexFile.sort((a, b) => a.index.localeCompare(b.index));
|
||||
|
||||
callback(modulesWithIndexFile.concat(modulesWithoutIndexFile));
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < directories.length; i++) {
|
||||
const dir = directories[i];
|
||||
modulesLeft++;
|
||||
// Read manifest
|
||||
fs.readFile(
|
||||
modulesPath + '/' + dir + '/manifest.json',
|
||||
'utf8',
|
||||
(function () {
|
||||
const _dir = dir;
|
||||
return function (err, data) {
|
||||
if (err) {
|
||||
completeModule(); // Module load failed
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
completeModule('noodl_modules/' + _dir, JSON.parse(data));
|
||||
} catch (e) {
|
||||
// JSON not valid
|
||||
completeModule(); // Module load failed
|
||||
}
|
||||
};
|
||||
})()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
injectIntoHtml(projectDirectory, template, pathPrefix, callback) {
|
||||
this.scanProjectModules(projectDirectory, function (modules) {
|
||||
var dependencies = '';
|
||||
var modulesMain = '';
|
||||
if (modules) {
|
||||
const browserModules = modules.filter((m) => m.runtimes.indexOf('browser') !== -1);
|
||||
for (var i = 0; i < browserModules.length; i++) {
|
||||
var m = browserModules[i];
|
||||
if (m.index) {
|
||||
modulesMain += '<script type="text/javascript" src="' + pathPrefix + m.index + '"></script>\n';
|
||||
}
|
||||
|
||||
// Module javascript dependencies
|
||||
if (m.dependencies) {
|
||||
for (var j = 0; j < m.dependencies.length; j++) {
|
||||
var d = m.dependencies[j];
|
||||
var dTag = '<script type="text/javascript" src="' + pathPrefix + d + '"></script>\n';
|
||||
if (dependencies.indexOf(dTag) === -1) dependencies += dTag;
|
||||
}
|
||||
}
|
||||
|
||||
// Browser modules
|
||||
if (m.browser) {
|
||||
if (m.browser.head) {
|
||||
var head = m.browser.head;
|
||||
for (var j = 0; j < head.length; j++) {
|
||||
dependencies += head[j] + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (m.browser.styles) {
|
||||
var styles = m.browser.styles;
|
||||
for (var j = 0; j < styles.length; j++) {
|
||||
dependencies += '<style>' + styles[j] + '</style>' + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (m.browser.stylesheets) {
|
||||
var sheets = m.browser.stylesheets;
|
||||
for (var j = 0; j < sheets.length; j++) {
|
||||
if (typeof sheets[j] === 'string') {
|
||||
let path = sheets[j];
|
||||
if (!path.startsWith('http')) {
|
||||
path = pathPrefix + path;
|
||||
}
|
||||
|
||||
dependencies += '<link href="' + path + '" rel="stylesheet">';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var injected = template.replace('<%modules_dependencies%>', dependencies);
|
||||
injected = injected.replace('<%modules_main%>', modulesMain);
|
||||
|
||||
callback(injected);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ProjectModules.instance = new ProjectModules();
|
||||
|
||||
module.exports = ProjectModules;
|
||||
20
packages/noodl-editor/src/shared/view.d.ts
vendored
Normal file
20
packages/noodl-editor/src/shared/view.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export default class View {
|
||||
el: JQuery<HTMLElement>;
|
||||
|
||||
render(): TSFixme;
|
||||
|
||||
static $(el, selector): JQuery<HTMLElement>;
|
||||
|
||||
static showTooltip(args): void;
|
||||
static hideTooltip(args): void;
|
||||
|
||||
$(selector): any;
|
||||
|
||||
on(event: string, listener: (...args: TSFixme) => void, group?: any): View;
|
||||
off(group): View;
|
||||
notifyListeners(event, args?): void;
|
||||
|
||||
cloneTemplate(tmpl): TSFixme;
|
||||
|
||||
bindView(el, obj?): TSFixme;
|
||||
}
|
||||
278
packages/noodl-editor/src/shared/view.js
Normal file
278
packages/noodl-editor/src/shared/view.js
Normal file
@@ -0,0 +1,278 @@
|
||||
const watch = function (obj, prop, handler) {
|
||||
var value = obj[prop],
|
||||
oldsetter = obj.__lookupSetter__(prop),
|
||||
oldgetter = obj.__lookupGetter__(prop),
|
||||
getter = function () {
|
||||
return value;
|
||||
},
|
||||
setter = function (val) {
|
||||
value = handler.call(obj, prop, value, val);
|
||||
|
||||
if (oldsetter) return oldsetter(value);
|
||||
return value;
|
||||
};
|
||||
if (oldsetter || delete obj[prop]) {
|
||||
// can't watch constants
|
||||
if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) {
|
||||
// legacy
|
||||
Object.prototype.__defineGetter__.call(obj, prop, getter);
|
||||
Object.prototype.__defineSetter__.call(obj, prop, setter);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const unwatch = function (obj, prop) {
|
||||
var val = obj[prop];
|
||||
delete obj[prop]; // remove accessors
|
||||
obj[prop] = val;
|
||||
};
|
||||
|
||||
var View = function () {
|
||||
this.templates = {};
|
||||
this.listeners = [];
|
||||
};
|
||||
|
||||
View.$ = function (el, selector) {
|
||||
return el.filter(selector).add(el.find(selector));
|
||||
};
|
||||
|
||||
View.showTooltip = function (args) {
|
||||
// Will be overridden externally
|
||||
};
|
||||
|
||||
View.hideTooltip = function (el) {
|
||||
// Will be overridden externally
|
||||
};
|
||||
|
||||
View.prototype.$ = function (selector) {
|
||||
const other = this.el.find(selector);
|
||||
return this.el.filter(selector).add(other);
|
||||
};
|
||||
|
||||
View.prototype.on = function (event, listener, group) {
|
||||
this.listeners.push({
|
||||
event: event,
|
||||
listener: listener,
|
||||
group
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
View.prototype.off = function (group) {
|
||||
for (var i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i].group === group) {
|
||||
this.listeners.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
View.prototype.notifyListeners = function (event, args) {
|
||||
for (var i in this.listeners) {
|
||||
if (this.listeners[i].event === event) this.listeners[i].listener(args);
|
||||
}
|
||||
};
|
||||
|
||||
View.prototype.cloneTemplate = function (tmpl) {
|
||||
return this.templates[tmpl].clone();
|
||||
};
|
||||
|
||||
View.prototype.bindView = function (el, obj) {
|
||||
var _this = this;
|
||||
|
||||
// Collect templates
|
||||
el.find('[data-template]').each(function () {
|
||||
var _el = $(this);
|
||||
_el.remove();
|
||||
_this.templates[_el.attr('data-template')] = _el;
|
||||
});
|
||||
|
||||
var resolvePathOnObject = function (path, obj) {
|
||||
var cmp = path.split('.');
|
||||
for (var i = 0; i < cmp.length - 1; i++) {
|
||||
obj = obj[cmp[i]];
|
||||
}
|
||||
return {
|
||||
prop: cmp[cmp.length - 1],
|
||||
obj: obj
|
||||
};
|
||||
};
|
||||
|
||||
var getObjectFromPath = function (path, obj) {
|
||||
var resolved = resolvePathOnObject(path, obj);
|
||||
return resolved.obj[resolved.prop];
|
||||
};
|
||||
|
||||
function resolveStaticOrFromPath(attribute, obj) {
|
||||
if (attribute.startsWith('path:')) {
|
||||
const path = attribute.split('path:')[1];
|
||||
const resolved = resolvePathOnObject(path, obj);
|
||||
return resolved.obj[resolved.prop];
|
||||
}
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
// Change events
|
||||
View.$(el, '[data-change]').each(function () {
|
||||
$(this).on('change', function (evt) {
|
||||
_this[$(this).attr('data-change')].apply(_this, [obj, $(this), evt]);
|
||||
});
|
||||
});
|
||||
|
||||
// On click events
|
||||
View.$(el, '[data-click]').each(function () {
|
||||
$(this).on('click', function (evt) {
|
||||
_this[$(this).attr('data-click')].apply(_this, [obj, $(this), evt]);
|
||||
});
|
||||
});
|
||||
|
||||
// On tooltip
|
||||
var tooltipTimeout;
|
||||
View.$(el, '[data-tooltip]').each(function () {
|
||||
$(this)
|
||||
.on('mouseenter', function (evt) {
|
||||
var el = $(this);
|
||||
|
||||
let tooltip = resolveStaticOrFromPath(el.attr('data-tooltip'), obj);
|
||||
if (!tooltip) return;
|
||||
|
||||
let content, extendedContent;
|
||||
|
||||
if (typeof tooltip === 'object') {
|
||||
content = tooltip.standard;
|
||||
extendedContent = tooltip.extended;
|
||||
} else {
|
||||
content = tooltip;
|
||||
}
|
||||
|
||||
const position = el.attr('data-tooltip-position');
|
||||
|
||||
tooltipTimeout = setTimeout(() => {
|
||||
const dimensions = View.showTooltip({
|
||||
attachTo: el,
|
||||
content,
|
||||
position
|
||||
});
|
||||
|
||||
if (extendedContent) {
|
||||
tooltipTimeout = setTimeout(() => {
|
||||
const offset = {
|
||||
x: (300 - dimensions.contentWidth) / 2 //a hack to try to keep the arrow in the same position. Assumes the expanded popup is 300px wide.
|
||||
};
|
||||
|
||||
View.showTooltip({
|
||||
attachTo: el,
|
||||
content: extendedContent,
|
||||
position,
|
||||
offset
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
}, 500);
|
||||
})
|
||||
.on('mouseleave mousedown', function (evt) {
|
||||
View.hideTooltip($(this));
|
||||
clearTimeout(tooltipTimeout);
|
||||
});
|
||||
});
|
||||
|
||||
// Data text
|
||||
View.$(el, '[data-text]').each(function () {
|
||||
var _el = $(this);
|
||||
var resolved = resolvePathOnObject($(this).attr('data-text'), obj);
|
||||
_el.text(resolved.obj[resolved.prop]);
|
||||
var bind = _el.attr('data-bind');
|
||||
if (bind === 'watch') {
|
||||
watch(resolved.obj, resolved.prop, function (prop, oldVal, newVal) {
|
||||
_el.text(newVal);
|
||||
return newVal;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Data html
|
||||
View.$(el, '[data-html]').each(function () {
|
||||
var _el = $(this);
|
||||
var resolved = resolvePathOnObject($(this).attr('data-html'), obj);
|
||||
_el.html(resolved.obj[resolved.prop]);
|
||||
var bind = _el.attr('data-bind');
|
||||
if (bind === 'watch') {
|
||||
watch(resolved.obj, resolved.prop, function (prop, oldVal, newVal) {
|
||||
_el.html(newVal);
|
||||
return newVal;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Identifier (for easier input targeting)
|
||||
View.$(el, '[data-identifier]').each(function () {
|
||||
$(this).attr('data-identifier', obj?.name);
|
||||
});
|
||||
|
||||
// Data val
|
||||
View.$(el, '[data-val]').each(function () {
|
||||
$(this).val(getObjectFromPath($(this).attr('data-val'), obj));
|
||||
});
|
||||
|
||||
// Data src
|
||||
View.$(el, '[data-src]').each(function () {
|
||||
$(this).attr('src', getObjectFromPath($(this).attr('data-src'), obj));
|
||||
});
|
||||
|
||||
// Data checked
|
||||
View.$(el, '[data-checked]').each(function () {
|
||||
$(this).prop('checked', getObjectFromPath($(this).attr('data-checked'), obj));
|
||||
});
|
||||
|
||||
// Data class
|
||||
View.$(el, '[data-class]').each(function () {
|
||||
var _el = $(this);
|
||||
var decl = $(this).attr('data-class');
|
||||
var conds = decl.split(',');
|
||||
for (var i in conds) {
|
||||
var terms = conds[i].split(':');
|
||||
var cls = terms[1];
|
||||
var path = terms[0];
|
||||
var invert = false;
|
||||
if (path[0] === '!') {
|
||||
invert = true;
|
||||
path = path.substring(1);
|
||||
}
|
||||
var resolved = resolvePathOnObject(path, obj);
|
||||
var check = invert ? !resolved.obj[resolved.prop] : resolved.obj[resolved.prop];
|
||||
if (check) _el.addClass(cls);
|
||||
else _el.removeClass(cls);
|
||||
|
||||
// bind with a watch, if the property changes
|
||||
// update the class
|
||||
watch(
|
||||
resolved.obj,
|
||||
resolved.prop,
|
||||
(function () {
|
||||
var _cls = cls;
|
||||
var _invert = invert;
|
||||
return function (prop, oldVal, newVal) {
|
||||
if (oldVal !== newVal) {
|
||||
var check = _invert ? !newVal : newVal;
|
||||
if (check) _el.addClass(_cls);
|
||||
else _el.removeClass(_cls);
|
||||
}
|
||||
return newVal;
|
||||
};
|
||||
})()
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent buttons from focusing
|
||||
View.$(el, 'button').on('focus', function () {
|
||||
$(this).blur();
|
||||
});
|
||||
|
||||
return el;
|
||||
};
|
||||
|
||||
module.exports = View;
|
||||
Reference in New Issue
Block a user