├── modules
└── cache.node
├── web
├── root
│ ├── css
│ │ ├── elements.css
│ │ ├── base.css
│ │ └── gv.css
│ ├── js
│ │ ├── basic.js
│ │ ├── server.js
│ │ ├── hid.js
│ │ └── gv.js
│ └── index.html
├── engine.js
└── server.js
├── admin.js
├── settings.js
├── README.md
└── database.js
/modules/cache.node:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/intersystems-community/GlobalsDB-NodeJS-Admin/master/modules/cache.node
--------------------------------------------------------------------------------
/web/root/css/elements.css:
--------------------------------------------------------------------------------
1 | circle {
2 | stroke-width: 1px;
3 | stroke: rgba(0, 0, 0, 0.50);
4 | fill: #c2eaea;
5 | }
--------------------------------------------------------------------------------
/web/engine.js:
--------------------------------------------------------------------------------
1 | module.exports = new function() {
2 |
3 | var db = require("./../database.js");
4 |
5 | this.about = function() {
6 | return db.getInfo();
7 | };
8 |
9 | this.getGlobal = function() {
10 |
11 | };
12 |
13 | };
--------------------------------------------------------------------------------
/web/root/css/base.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | padding: 0;
3 | margin: 0;
4 | height: 100%;
5 | background: #eeedc8;
6 | }
7 |
8 | .fullSized {
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | .inactive {
14 | color: rgba(128,128,128,0.5);
15 | }
--------------------------------------------------------------------------------
/admin.js:
--------------------------------------------------------------------------------
1 | var core = new function() {
2 |
3 | var webServer = require("./web/server.js"),
4 | fs = require("fs"),
5 | db = require("./database.js");
6 |
7 | if (!db.connect()) {
8 | console.log("Failed to initialize database.");
9 | return;
10 | }
11 |
12 | webServer.start();
13 | console.log("Application initialized successfully.");
14 |
15 | };
--------------------------------------------------------------------------------
/settings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module represents different setting for application.
3 | */
4 | module.exports = new function() {
5 |
6 | /**
7 | * Because application represents only conceptual part of administrative tools, do not use it for real projects.
8 | * Check database.js file. Settings are hardcoded.
9 | *
10 | * Requirements:
11 | * - GlobalsDB with NodeJS adapter v1 (version is important)
12 | * - NodeJS
13 | * - Settings changes
14 | *
15 | * IMPORTANT!
16 | * Also check db.connect method!
17 | * Comment line with fillTestData evaluation if you have any data in your database.
18 | */
19 |
20 | this.db = {
21 | globalsDBInstallationPath: process.env.GLOBALS_HOME,
22 | globalsDBDatabaseDirectory: "/mgr"
23 |
24 | };
25 | this.app = {
26 |
27 | };
28 | this.SERVER_PORT = 80;
29 | this.SERVER_DOMAIN = "127.0.0.1";
30 |
31 | };
32 |
33 |
--------------------------------------------------------------------------------
/web/root/js/basic.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Some prototypes to simplify work with objects.
3 | */
4 |
5 | /**
6 | * Foreach operator for object's properties. Usage:
7 | * someObject.foreach(function(property){ someActions(this[property]) })
8 | *
9 | * @param callback {function}
10 | */
11 | Object.prototype.foreach = function(callback) {
12 | for (var property in this) {
13 | if (!this.hasOwnProperty(property)) continue;
14 | callback.call(this, property)
15 | }
16 | };
17 |
18 | /**
19 | * Applies property to object. Returns new generated property name.
20 | *
21 | * @param value {*}
22 | * @returns {string}
23 | */
24 | Object.prototype.append = function(value) {
25 | var index = 0;
26 | while (this.hasOwnProperty(index + "")) index++;
27 | this[index + ""] = value;
28 | return index + "";
29 | };
30 |
31 | /**
32 | * Merges target object with current object.
33 | * All properties of current object will be rewritten by given object's properties.
34 | *
35 | * @param object {object}
36 | */
37 | Object.prototype.merge = function(object) {
38 |
39 | var combine = function(target,object) {
40 | for (var property in object) {
41 | if (!object.hasOwnProperty(property)) continue;
42 | target[property] = object[property]; // note about properties-objects
43 | }
44 | };
45 |
46 | combine(this,object);
47 |
48 | };
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | GlobalsDB-NodeJS-Admin concept
2 | ======================
3 |
4 | ##### Note: this is a demo application. Code (engine) will be rewritten soon.
5 |
6 | #### Update: GlobalsDB Admin released!
7 |
8 | Easy visual interface for administrating GlobalsDB. Represents database in hierarchical tree-based view - one you haven't seen before.
9 |
10 | Note that modules/cache.node may be corrupted because of git file encoding. If NodeJS will throw error about cache.node module, download the latest one from [official site](http://www.globalsdb.org/downloads) and replace your current one.
11 |
12 | ### Installation
13 |
14 | Copy files to any directory and start from admin.js file. Customize url/port settings in settings.js. Note that application is in-development and represents only main concept, so it doesn't have security at all. Use at your own risk!
15 |
16 | ### Troubleshooting
17 |
18 | Make sure that:
19 | * NodeJS and GlobalsDB are installed and connected with NodeJS using v1 adapter (important)
20 | * You replaced basic settings in settings.js with yours
21 | * cache.node isn't corrupted
22 |
23 | If console throws error during startup about loading cache.node module, try to replace module file with latest module file in your GlobalsDB/bin directory. This file called like cacheXXX.node, copy and rename this file to project's /modules directory.
24 |
--------------------------------------------------------------------------------
/web/root/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Admin
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | USER
21 |
22 |
27 |
28 |
29 | GlobalsDB administrative tool concept.
30 | Currently in development. Use at your own risk!
31 | Check settings.js file before usage.
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/web/root/js/server.js:
--------------------------------------------------------------------------------
1 | var server = new function() {
2 |
3 | var getXmlHttp = function() {
4 | var xmlhttp;
5 | try {
6 | xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
7 | } catch (e) {
8 | try {
9 | xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
10 | } catch (E) {
11 | xmlhttp = false;
12 | }
13 | }
14 | if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
15 | xmlhttp = new XMLHttpRequest();
16 | }
17 | return xmlhttp;
18 | };
19 |
20 | var makeRequest = function(url, caching, data, method, handler) {
21 |
22 | if (typeof caching === "undefined") caching = false;
23 | method = method.toLowerCase();
24 | var req = getXmlHttp();
25 | caching = (method == "get")?(((url.indexOf("?") === -1)?"?":"&") + "cache=" +
26 | ((caching)?1:new Date().getTime())):"";
27 |
28 | req.onreadystatechange = function() {
29 |
30 | if (req.readyState == 4) { //req.statusText
31 |
32 | if(req.status == 200) {
33 | //console.log(req.responseText);
34 | handler.call(null,req.responseText,1);
35 | } else {
36 | handler.call(null,null,0);
37 | console.log("Ajax GET error: ",req.statusText,req.responseText);
38 | }
39 |
40 | }
41 |
42 | };
43 |
44 | req.open(((method === "get")?"get":"post"), url + caching, true);
45 | req.send(((method === "get")?null:data));
46 |
47 | };
48 |
49 | /**
50 | * Gets data from server and handles it with handler.
51 | *
52 | * @param url
53 | * @param handler
54 | * @param data
55 | * [ @param caching ]
56 | */
57 | this.get = function(url, handler, data, caching) {
58 |
59 | makeRequest(url, caching, data, "get", handler);
60 |
61 | };
62 |
63 | /**
64 | * Gets data from server and handles it with handler.
65 | *
66 | * @param url
67 | * @param handler
68 | * @param data
69 | */
70 | this.post = function(url, handler, data) {
71 |
72 | makeRequest(url, false, data, "post", handler);
73 |
74 | };
75 |
76 | this.initialize = function() {
77 |
78 | // nothing to do
79 |
80 | };
81 |
82 | };
83 |
--------------------------------------------------------------------------------
/web/root/css/gv.css:
--------------------------------------------------------------------------------
1 | #gv-nodesContainer {
2 | position: relative;
3 | width: 10000px;
4 | height: 10000px;
5 | -moz-user-select: -moz-none;
6 | -khtml-user-select: none;
7 | -webkit-user-select: none;
8 | -ms-user-select: none;
9 | user-select: none;
10 | -webkit-backface-visibility: hidden;
11 | }
12 |
13 | #gv-container {
14 | position: fixed;
15 | overflow: hidden;
16 |
17 | }
18 |
19 | #gv-interface-namespace {
20 | position: fixed;
21 | left: 0;
22 | top: 0;
23 | padding: 8px 12px 10px 8px;
24 | border-radius: 0 0 50px/20px 0;
25 | background: rgba(0,0,0,0.5);
26 | color: white;
27 | cursor: pointer;
28 | font-size: 1.5em;
29 | /*-webkit-transition: all 0.3s ease;
30 | -moz-transition: all 0.3s ease;
31 | -o-transition: all 0.3s ease;
32 | transition: all 0.3s ease;*/
33 | }
34 |
35 | #gv-interface-namespace:hover {
36 | color: #eeedc8;
37 | }
38 |
39 | .gv-namespacePart:after {
40 | content: " > ";
41 | }
42 |
43 | .gv-node {
44 | position: absolute;
45 | padding: 5px;
46 | background: rgba(213, 125, 238, 0.83);
47 | border: 1px solid rgb(0, 0, 0);
48 | border-radius: 50%;
49 | vertical-align: middle;
50 | text-align: center;
51 | font-size: 2em;
52 | cursor: pointer;
53 | text-decoration: none;
54 | }
55 |
56 | .gv-node:hover {
57 | opacity: 1 !important;
58 | box-shadow: 0 0 30px yellow;
59 | }
60 |
61 | .gv-nodeType-0 {
62 | background: rgba(238, 86, 0, 0.90);
63 | border: 1px solid rgb(0, 0, 0);
64 | }
65 |
66 | .gv-nodeType-1 {
67 | background: rgba(178, 174, 238, 0.82);
68 | border: 1px solid rgb(0, 0, 0);
69 | }
70 |
71 |
72 | .gv-nodeType-2 {
73 | background: rgba(188, 236, 238, 0.84);
74 | border: 1px solid rgb(0, 0, 0);
75 | }
76 |
77 | .gv-link {
78 | position: absolute;
79 | text-align: center;
80 | left: 0;
81 | top: 0;
82 | width: 0;
83 | height: 5px;
84 | overflow: visible;
85 | background: rgba(188, 236, 238, 0.84);
86 | border-top: 1px solid rgb(0, 0, 0);
87 | border-bottom: 1px solid rgb(0, 0, 0);
88 | }
89 |
90 | .gv-linkText {
91 | position: relative;
92 | top: -0.6em;
93 | padding: 0.1em;
94 | background: rgba(188, 236, 238, 1);
95 | border: 1px solid black;
96 | border-radius: 2px;
97 | text-overflow: clip;
98 | font-weight: 900;
99 | /*max-width: 100%;
100 | overflow: hidden;*/
101 | display: inline-block;
102 | }
103 |
104 | #gv-footer {
105 | position: fixed;
106 | left: 0;
107 | bottom: 0;
108 | width: 100%;
109 | padding: 5px;
110 | }
--------------------------------------------------------------------------------
/database.js:
--------------------------------------------------------------------------------
1 | module.exports = new function() {
2 |
3 | var dbModule = require("./modules/cache.node"),
4 | settings = require("./settings.js"),
5 | fs = require("fs"), // @debug
6 | db = new dbModule.Cache(),
7 | OPENED = false;
8 |
9 | this.getInfo = function() {
10 | return db.about();
11 | };
12 |
13 | this.fillTestData = function() {
14 | if (!OPENED) return;
15 | db.set("root", "2 ways");
16 | db.set("root", "people", 0, "name", "Jacksssssssssssssssssssssssssssssssssssssssssssssssssss");
17 | db.set("root", "people", 0, "age", 25);
18 | db.set("root", "people", 0, "gender", 0);
19 | db.set("root", "people", 1, "name", "Henry");
20 | db.set("root", "people", 1, "age", 12);
21 | db.set("root", "people", 1, "gender", 0);
22 | db.set("root", "people", 2, "name", "Goose");
23 | db.set("root", "people", 2, "age", 2);
24 | db.set("root", "people", 2, "gender", 1);
25 | db.set("root", "people", 2, "Goose");
26 | db.set("root", "people", 3, "name", "Olga");
27 | db.set("root", "people", 3, "age", 12);
28 | db.set("root", "people", 3, "gender", 1);
29 | db.set("root", "loot", 0, "baggage");
30 | db.set("root", "loot", 1, "message");
31 | db.set("root", "loot", 2, "plant");
32 | db.set("root", "loot", 3, "box");
33 | db.set("root", "loot", 3, "box", "9 items");
34 | db.set("root", "loot", 3, "box", "9 items");
35 | db.set("root", "loot", 3, "box", "item1", "knife");
36 | db.set("root", "loot", 3, "box", "item2", "clock");
37 | db.set("root", "loot", 3, "box", "item3", "pen");
38 | db.set("root", "loot", 3, "box", "item4", "letter");
39 | db.set("root", "loot", 3, "box", "item5", "egg");
40 | db.set("root", "loot", 3, "box", "item6", "key");
41 | db.set("root", "loot", 3, "box", "item7", "Guf");
42 | db.set("root", "loot", 3, "box", "item8", "paper");
43 | db.set("root", "loot", 3, "box", "item9", "wood");
44 | console.log("Test data assigned.");
45 | };
46 |
47 | /*
48 |
49 | global_directory() // to list globals in namespace
50 | increment(g,l,o,b,*n) // to increment global value simultaneously
51 | kill() / set() // to kill/set node
52 | lock()/unlock() // to protect
53 | merge({to: {}, from: {}})
54 | next() previous() // subscripts
55 | next_node(node), previous_node() // defined: 0 if no next
56 | order(), next()
57 | retrieve() // 'array', 'list', 'object'
58 |
59 | */
60 |
61 | this.getData = function(globalArray, callBack) { // sync!
62 | //console.log(globalArray);
63 | var global = globalArray[0];
64 | if (globalArray == undefined) {callBack.call(this); return}
65 | db.get({
66 | global: global,
67 | subscripts: globalArray.slice(1)
68 | }, callBack);
69 | };
70 |
71 | this.getLevel = function(globalArray, callBack) {
72 | if (globalArray == false) {
73 | db.global_directory({}, callBack);
74 | return;
75 | }
76 | db.retrieve({
77 | global: globalArray[0],
78 | subscripts: globalArray.slice(1)
79 | }, "list", callBack);
80 | };
81 |
82 | this.connect = function() {
83 | var d = process.cwd(); // remember current directory
84 | var result = db.open({
85 | path: settings.db.globalsDBInstallationPath + settings.db.globalsDBDatabaseDirectory,
86 | username: '_SYSTEM',
87 | password: 'SYS',
88 | namespace: 'USER'})["ok"];
89 |
90 | if (result) OPENED = true;
91 |
92 | this.fillTestData(); // WARNING! COMMENT THIS LINE IF YOU HAVE ANY DATA IN YOUR DATABASE!
93 |
94 | process.chdir(d); // restore directory because of GlobalsDB changes it
95 | return result;
96 | };
97 |
98 | this.disconnect = function() {
99 | db.close(function(error, result) {
100 | if (error) {
101 | console.log(result);
102 | } else {
103 | OPENED = false;
104 | console.log("Disconnected from database.");
105 | }
106 | })
107 | };
108 |
109 | };
--------------------------------------------------------------------------------
/web/server.js:
--------------------------------------------------------------------------------
1 | module.exports = new function() {
2 |
3 | var http = require("http"),
4 | fileSystem = require("fs"),
5 | settings = require("../settings.js"),
6 | queryString = require('querystring'),
7 | engine = require("./engine.js"),
8 | db = require("../database.js"),
9 | server = null;
10 |
11 | var FILE_TYPES = {
12 | "html": "text/html",
13 | "css": "text/css",
14 | "js": "text/javascript",
15 | "jpg": "image/jpeg",
16 | "png": "image/png",
17 | "gif": "image/gif"
18 | };
19 |
20 | var getGlobal = function(request, responce, callback) {
21 |
22 | var generateBred = function() {
23 |
24 | var genName = function() {
25 | var s = "", l = Math.random()*10;
26 | for (var i = 0; i < l; i++) {
27 | s += String.fromCharCode(Math.floor(Math.random()*26 + 65))
28 | }
29 | return s;
30 | };
31 |
32 | var obj = {
33 | status: "ok",
34 | children: {
35 |
36 | }
37 | };
38 | var l = Math.random()*6;
39 | for (var i = 0; i < l; i++) {
40 | obj.children[i+""] = {
41 | n: genName(),
42 | t: Math.floor(Math.random()*4)
43 | }
44 | }
45 |
46 | return obj;
47 | };
48 |
49 | var body = "";
50 | request.on('data', function (data) {
51 | body += data;
52 | });
53 | request.on('end',function(){
54 | var bred = generateBred();
55 | var nodeArray = body.split(",");
56 | nodeArray.splice(0,1);
57 | var ro = {
58 | status: "ok",
59 | count: 0,
60 | children: {
61 |
62 | }
63 | };
64 |
65 | db.getLevel(nodeArray, function(result, data) {
66 | for (var i = 0; i < data.length; i++) {
67 | (function(i){
68 | db.getData(nodeArray.concat(data[i]), function(result, data2) {
69 | var t = (data2.data)?2:1;
70 | ro.children[i] = {
71 | n: data[i],
72 | t: t,
73 | v: data2.data || ""
74 | };
75 | if (ro.count++ === data.length - 1) {
76 | callback.call(this, JSON.stringify(ro));
77 | }
78 | });
79 | })(i);
80 | }
81 | });
82 | //db.getLevel(["root","people","0", "name"], console.log);
83 |
84 | });
85 | };
86 |
87 | var setGlobal = function(request, responce, callback) {
88 |
89 | var body = "";
90 | request.on('data', function (data) {
91 | body += data;
92 | });
93 | request.on('end',function(){
94 | callback.call(this, JSON.stringify({status: "ok", result: 0}));
95 | });
96 |
97 | };
98 |
99 | /**
100 | * Returns contents of file located in root directory or empty string if file not found.
101 | *
102 | * @param request
103 | * @param response
104 | * @param callback
105 | */
106 | var getWebFile = function(request, response, callback) {
107 |
108 | var args = request.url.indexOf("?");
109 | var fileWebPath = request.url.substr(0,(args===-1)?request.url.length:args);
110 | if (fileWebPath.charAt(fileWebPath.length - 1) === "/") fileWebPath += "index.html";
111 | var fileWebName = "web/root" + fileWebPath,
112 | data;
113 |
114 | if (fileWebPath === "/data") {
115 | getGlobal(request, response, function(data) {
116 | callback.call(this, {
117 | status: 200,
118 | contentType: "application/json",
119 | body: data
120 | });
121 | })
122 | } else if (fileWebPath === "/set") {
123 | setGlobal(request, response, function(data) {
124 | callback.call(this, {
125 | status: 200,
126 | contentType: "application/json",
127 | body: data
128 | });
129 | })
130 | } else if (fileSystem.existsSync(fileWebName)) {
131 |
132 | var fileContent = fileSystem.readFileSync(fileWebName);
133 | data = {
134 | status: 200,
135 | contentType: FILE_TYPES[fileWebPath.substr(fileWebPath.lastIndexOf(".") + 1)] || "text/plain",
136 | body: fileContent
137 | };
138 | callback.call(this, data);
139 |
140 | } else {
141 |
142 | data = {
143 | status: 404,
144 | contentType: "text/html",
145 | body: "File " + fileWebPath + " not found on server!" // @improve: make constant
146 | };
147 | callback.call(this, data);
148 |
149 | }
150 |
151 | };
152 |
153 | /**
154 | * Client request handler.
155 | *
156 | * @param request
157 | * @param response
158 | */
159 | var request = function(request, response) {
160 |
161 | getWebFile(request, response, function(data) {
162 | response.writeHead(data.status, {
163 | "Content-Type": data.contentType
164 | });
165 | response.end(data.body);
166 | });
167 |
168 | };
169 |
170 | /**
171 | * Causes web server to listen requests.
172 | */
173 | this.start = function() {
174 | server = http.createServer(request);
175 | server.listen(settings.SERVER_PORT, settings.SERVER_DOMAIN);
176 | console.log("Web Server started to listen on " + settings.SERVER_DOMAIN + ":" + settings.SERVER_PORT);
177 | return true;
178 | };
179 |
180 | /**
181 | * Causes web server to stop listening requests.
182 | */
183 | this.stop = function() {
184 | if (!server) console.log("Server not up!");
185 | server.stop();
186 | server = null;
187 | };
188 |
189 | };
190 |
--------------------------------------------------------------------------------
/web/root/js/hid.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Human input device wrapper object.
3 | *
4 | * Version: 0.4
5 | *
6 | * Required:
7 | * basic.js
8 | */
9 | var hid = new function() {
10 |
11 | var specVar = "!hid", // special variable name that will be handled by html-objects
12 | pointer = { // pointer object
13 | stack: {
14 |
15 | },
16 | STATES: {
17 | NONE: 0,
18 | PRESS: 1,
19 | MOVE: 2,
20 | RELEASE: 3
21 | }
22 | };
23 |
24 | // updates pointer
25 | var updatePointer = function(id, pointerObject) {
26 | if (!pointer.stack.hasOwnProperty(id)) return;
27 | pointer.stack[id].merge(pointerObject);
28 | };
29 |
30 | // @debug
31 | this.getInner = function() { return pointer };
32 |
33 | // removes pointer
34 | var removePointer = function(id) {
35 | if (!pointer.stack.hasOwnProperty(id)) return;
36 | delete pointer.stack[id];
37 | };
38 |
39 | var blockEvent = function(e) {
40 | e.returnValue = false;
41 | e.cancelBubble = true;
42 | if (e.preventDefault) {
43 | e.preventDefault();
44 | e.stopPropagation();
45 | }
46 | };
47 |
48 | // call binders for event for event target and it's parents
49 | var callBinders = function(eventName, currentPointer) {
50 |
51 | var forObject = function(object) {
52 | if (object.hasOwnProperty(specVar) && object[specVar].valid) {
53 | object[specVar].binds[eventName].foreach(function(prop){
54 | this[prop].call(object, currentPointer);
55 | blockEvent(currentPointer.originalEvent);
56 | })
57 | }
58 | return object.parentNode;
59 | };
60 |
61 | var object = currentPointer.target;
62 | while (object) { // down recursively calling handler for every parent of target
63 | object = forObject(object);
64 | }
65 | return 1;
66 |
67 | };
68 |
69 | var handlers = {
70 |
71 | pointerStart: function(event, id, x, y, cX, cY, target) {
72 |
73 | var currentPointer = {
74 | id: id,
75 | x: x,
76 | y: y,
77 | originX: x,
78 | originY: y,
79 | pageX: x,
80 | pageY: y,
81 | clientX: cX,
82 | clientY: cY,
83 | originalEvent: event,
84 | state: pointer.STATES.PRESS,
85 | target: target
86 | };
87 | pointer.principal = currentPointer;
88 | pointer.stack[id] = currentPointer;
89 | //console.log(currentPointer.originalEvent);
90 | callBinders("pointerStart", currentPointer);
91 |
92 | },
93 |
94 | pointerMove: function(event, id, x, y, cX, cY) {
95 |
96 | if (!pointer.stack[id]) return;
97 | var currentPointer = {
98 | id: id,
99 | x: x,
100 | y: y,
101 | pageX: x,
102 | pageY: y,
103 | clientX: cX,
104 | clientY: cY,
105 | originalEvent: event,
106 | state: pointer.STATES.MOVE
107 | };
108 |
109 | updatePointer(id, currentPointer);
110 | callBinders("pointerMove", pointer.stack[id]);
111 |
112 | },
113 |
114 | pointerEnd: function(event, id, x, y, cX, cY) {
115 |
116 | if (!pointer.stack[id]) return;
117 | pointer.stack[id].merge({
118 | x: x,
119 | y: y,
120 | pageX: x,
121 | pageY: y,
122 | clientX: cX,
123 | clientY: cY,
124 | //originalEvent: event,
125 | state: pointer.STATES.RELEASE
126 | });
127 | if (!pointer.stack.hasOwnProperty(id)) return;
128 | if (pointer.stack[id].originX === x && pointer.stack[id].originY === y) {
129 | callBinders("pointerPress", pointer.stack[id]);
130 | }
131 | callBinders("pointerEnd", pointer.stack[id]);
132 | removePointer(id);
133 | },
134 |
135 | pointerPress: function() { // todo: re-organize calls
136 |
137 | }
138 |
139 | };
140 |
141 | /**
142 | * Binds cross-application pointer(s) event.
143 | *
144 | * @param eventName
145 | * @param target
146 | * @param handler
147 | */
148 | this.bind = function(eventName, target, handler) {
149 | if (handlers.hasOwnProperty(eventName)) {
150 | if (!target) {
151 | console.log("Target not specified for event " + eventName);
152 | return;
153 | }
154 | if (!target.hasOwnProperty(specVar) || !target[specVar].valid) {
155 | target[specVar] = {
156 | binds: {
157 | pointerStart: {},
158 | pointerMove: {},
159 | pointerEnd: {},
160 | pointerPress: {},
161 | keyDown: {},
162 | keyUp: {},
163 | keyPress: {},
164 | hoverStart: {},
165 | hoverEnd: {}
166 | },
167 | valid: true
168 | }
169 | }
170 | target[specVar].binds[eventName].append(handler);
171 | } else console.log("No such event \"" + eventName + "\" for hid.bind")
172 | };
173 |
174 | /**
175 | * Cross-browser binding of browser events. For cross-browser application events use hid.bind method.
176 | *
177 | * @param event
178 | * @param element
179 | * @param handler
180 | */
181 | this.bindBrowserEvent = function (event, element, handler) {
182 | if (element.addEventListener) {
183 | element.addEventListener(event,handler,false);
184 | } else if (element.attachEvent) {
185 | element.attachEvent("on"+event, handler);
186 | } else {
187 | element[event] = handler;
188 | }
189 | };
190 |
191 | this.initialize = function() {
192 |
193 | var fixEvent = function(e) {
194 | return e || window.event;
195 | };
196 |
197 | // todo unselectable="on" as attribute for opera
198 |
199 | this.bindBrowserEvent('touchstart', document, function(e){
200 | e = fixEvent(e);
201 | var target = e.target || e.srcElement;
202 | var touch = e.touches[e.touches.length - 1];
203 | handlers.pointerStart(e, touch.identifier, touch.pageX,
204 | touch.pageY, touch.clientX, touch.clientY, target);
205 | });
206 |
207 | this.bindBrowserEvent('touchmove', document, function(e){
208 | e = fixEvent(e);
209 | e.changedTouches.foreach(function(el) {
210 | handlers.pointerMove(e, this[el].identifier, this[el].pageX, this[el].pageY, this[el].clientX, this[el].clientY);
211 | });
212 | });
213 |
214 | this.bindBrowserEvent('touchend', document, function(e){
215 | e = fixEvent(e);
216 | e.changedTouches.foreach(function(el) {
217 | handlers.pointerEnd(e, this[el].identifier, this[el].pageX, this[el].pageY, this[el].clientX, this[el].clientY);
218 | });
219 | });
220 |
221 | this.bindBrowserEvent('mousedown', document, function(e){
222 | e = fixEvent(e);
223 | var target = e.target || e.toElement;
224 | handlers.pointerStart(e, 1, e.pageX, e.pageY, e.clientX, e.clientY, target);
225 | });
226 |
227 | this.bindBrowserEvent('mouseup', document, function(e){
228 | e = fixEvent(e);
229 | handlers.pointerEnd(e, 1, e.pageX, e.pageY, e.clientX, e.clientY)
230 | });
231 |
232 | this.bindBrowserEvent('mousemove', document, function(e){
233 | e = fixEvent(e);
234 | handlers.pointerMove(e, 1, e.pageX, e.pageY, e.clientX, e.clientY)
235 | });
236 |
237 | };
238 |
239 | };
--------------------------------------------------------------------------------
/web/root/js/gv.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Global viewer object - engine for application.
3 | *
4 | * Required:
5 | * hid.js
6 | * basic.js
7 | */
8 | var gv = new function() {
9 |
10 | var LAST_NODE_ID = 0,
11 | LAST_LAYER = 0;
12 |
13 | this.world = {
14 |
15 | };
16 |
17 | var dom = new function() {
18 |
19 | this.element = function(id) {
20 | return document.getElementById(id);
21 | };
22 |
23 | this.getWorld = function() {
24 | return this.element("gv-nodesContainer");
25 | };
26 |
27 | this.getContainer = function() {
28 | return this.element("gv-container");
29 | };
30 |
31 | this.setMultipath = function(pathArray) {
32 | var f = document.getElementById("gv-footer");
33 | if (!f) return;
34 | var gets = function(pathArray) {
35 | var s = "";
36 | for (var i = 0; i < pathArray.length; i++) {
37 | s += "" + pathArray[i] + "";
38 | }
39 | return s;
40 | };
41 | f.innerHTML = gets(pathArray);
42 | };
43 |
44 | this.attachLink = function(name) {
45 | var link = document.createElement("div");
46 | var linkText = document.createElement("div");
47 | linkText.className = "gv-linkText";
48 | linkText.innerHTML = name;
49 | link.className = "gv-link";
50 | link.appendChild(linkText);
51 | link.style.zIndex = LAST_LAYER;
52 | this.getWorld().appendChild(link);
53 | return link;
54 | };
55 |
56 | this.detachLink = function(domLink) {
57 | domLink.parentNode.removeChild(domLink);
58 | }
59 |
60 | };
61 |
62 | var handlers = {
63 | nodeMove: function(pointer) {
64 | this.style.left = pointer.x - this.clientWidth/2 + "px";
65 | this.style.top = pointer.y - this.clientHeight/2 + "px";
66 | }
67 | };
68 |
69 | var addNode = function(id, name) {
70 |
71 |
72 |
73 | };
74 |
75 | var Node = function(name, value, type) {
76 |
77 | var MAX_LEN = 15;
78 |
79 | this.originalID = value + "\\" + name;
80 | this.name = (value.length < MAX_LEN)?value:value.substr(0, MAX_LEN)+"...";
81 | if (value === "") {this.name = "null"}
82 | this.nodeName = name;
83 | this.parent = {};
84 | this.child = {};
85 | this.type = (type === undefined)?1:type;
86 | this.x = 0;
87 | this.level = 0;
88 | this.y = 0;
89 | this.active = 0;
90 | this.r = 0;
91 | this.domElement = null;
92 | this.id = LAST_NODE_ID++;
93 | gv.world.append(this);
94 |
95 | };
96 |
97 | Node.prototype = {
98 |
99 | domElement: null,
100 | name: "",
101 | type: 1,
102 | originalID: "",
103 | nodeName: "",
104 | id: -1,
105 | level: 0,
106 | active: 0,
107 | x: 0,
108 | y: 0,
109 | r: 0,
110 | physics: { // proto
111 | distance: 0,
112 | angle: 0,
113 | timer: 0
114 | },
115 | parent: {},
116 | child: {},
117 | check: function(node) {
118 | return node instanceof Node;
119 | },
120 | addChild: function(node) {
121 | if (!this.check(node)) return;
122 | var link = dom.attachLink(node.nodeName);
123 | this.parent.append({
124 | node: this,
125 | linkElement: link
126 | });
127 | node.child.append({
128 | node: node,
129 | linkElement: link
130 | });
131 | },
132 | physicsStep: function(obj) {
133 | obj.x += obj.physics.distance*Math.cos(Math.PI/2-obj.physics.angle)/2;
134 | obj.y += obj.physics.distance*Math.sin(Math.PI/2-obj.physics.angle)/2;
135 | obj.physics.distance /= 2;
136 | obj.update();
137 | if (obj.physics.distance < 1) {
138 | clearInterval(obj.physics.timer);
139 | obj.physics.timer = 0;
140 | }
141 | },
142 | hasChilds: function() {
143 | for (var a in this.child) {
144 | if (!this.child.hasOwnProperty(a)) continue;
145 | return 1;
146 | }
147 | return 0;
148 | },
149 | // todo check for existence
150 | addParent: function(node) {
151 | if (!this.check(node)) return;
152 | this.level = node.level + 1;
153 | var link = dom.attachLink(this.nodeName);
154 | this.parent.append({
155 | node: node,
156 | linkElement: link
157 | });
158 | node.child.append({
159 | node: this,
160 | linkElement: link
161 | });
162 | },
163 | hasChild: function() {
164 | var h = false;
165 | for (var c in this.child) {
166 | if (!this.child.hasOwnProperty(c)) continue;
167 | h = true;
168 | }
169 | return h;
170 | },
171 | clearChildren: function() {
172 | // todo
173 | var recDel = function(node, a) {
174 | console.log("Clearing children ", node);
175 | if (node && node.hasOwnProperty("child")) {
176 | var c = node.child;
177 | for (var cc in c) {
178 | if (!c.hasOwnProperty(cc)) continue;
179 | //c[cc].linkElement.parentNode.removeChild(c[cc].linkElement);
180 | dom.detachLink(c[cc].linkElement);
181 | recDel(c[cc].node, 1);
182 | }
183 | // delete itself
184 | if (a) node.domElement.parentNode.removeChild(node.domElement);
185 | node.child = {};
186 | // todo
187 | // delete node;
188 | }
189 | };
190 | recDel(this, 0);
191 | },
192 | gextHTMLRoot: function() {
193 |
194 | },
195 | slide: function(a, distance) { // make as light as possible
196 | //if (!a || !b) return;
197 | var b = this, dx = a.x - b.x, dy = a.y - b.y, l = Math.sqrt(dx*dx + dy*dy) - (distance) - 10;
198 | if (l > a.r + b.r) return; // not colliding
199 | var f = (a.r + b.r - l)/5, d = Math.PI/2 - Math.atan2(dx, dy),
200 | fx = f*Math.cos(d), fy = f*Math.sin(d);
201 | //a.x += fx; b.x -= fx;
202 | //a.y += fy; b.y -= fy;
203 | a.x += fx*4; a.y += fy*4;
204 | if (Math.abs(fx) + Math.abs(fy) > 0.001) setTimeout(function(){a.slide(b, distance)}, 25);
205 | a.updateView();
206 | },
207 | updateView: function() {
208 | if (!this.domElement) return;
209 | this.domElement.style.left = this.x - this.r + "px";
210 | this.domElement.style.top = this.y - this.r + "px";
211 | var i = this;
212 | i.domElement.style.zIndex = LAST_LAYER;
213 | var updateLink = function(property) {
214 | var l = this[property].linkElement;
215 | var o = this[property].node;
216 | var or = o.r;
217 | var ir = i.r;
218 | var dx = o.x - i.x;
219 | var dy = o.y - i.y;
220 | var len = Math.sqrt(dx*dx + dy*dy) - ir - or + 1;
221 | var dir = Math.PI/2 - Math.atan2(o.x - i.x, o.y - i.y);
222 | if (dir < 0) dir += Math.PI*2;
223 | if (dir > Math.PI*2) dir -= Math.PI*2;
224 | if (dir > Math.PI*2) dir -= Math.PI*2;
225 | if (dir > Math.PI/2 && dir < Math.PI*1.5) {
226 | dir -= Math.PI;
227 | var t = or;
228 | or = ir;
229 | ir = t;
230 | }
231 | l.style["transform"] = l.style["-ms-transform"] = l.style["-o-transform"] = l.style["-moz-transform"] =
232 | l.style["-webkit-transform"] = "rotate("+dir+"rad)";
233 | l.style.width = Math.max(len, 0) + "px";
234 | l.style.opacity = Math.min(i.domElement.style.opacity, o.domElement.style.opacity) || 1;
235 | l.style.left = (o.x - or*Math.cos(dir) + i.x + ir*Math.cos(dir))/2 - len/2 + "px";
236 | l.style.top = (o.y - or*Math.sin(dir) + i.y + ir*Math.sin(dir))/2 - l.clientHeight/2 - 1 + "px";
237 | l.style.zIndex = LAST_LAYER;
238 | };
239 | LAST_LAYER++;
240 | this.child.foreach(updateLink);
241 | this.parent.foreach(updateLink);
242 | },
243 | /**
244 | * Updates node position.
245 | */
246 | update: function() {
247 | var me = this;
248 | this.parent.foreach(function(you){
249 | me.slide(this[you].node, this[you].linkElement.childNodes[0].clientWidth)
250 | });
251 | this.child.foreach(function(you){
252 | me.slide(this[you].node, this[you].linkElement.childNodes[0].clientWidth)
253 | });
254 | this.updateView();
255 | },
256 | _repeatClickHandler: function() {
257 | // todo
258 | },
259 | _startMoveHandler: function(pointer) {
260 | var i = this,
261 | level = this._parentNode.level;
262 | this._parentNode.wasActive = !!(this._parentNode.active == 1);
263 | gv.world.foreach(function(p){
264 | this[p].active = false;
265 | var oo = this[p].domElement.style.opacity;
266 | if (this[p].level === level || this[p].level === level + 1) {
267 | this[p].domElement.style.opacity = 1;
268 | } else {
269 | this[p].domElement.style.opacity = 1/((level - this[p].level + 1)*(level - this[p].level + 1));
270 | }
271 | if (oo !== this[p].domElement.style.opacity) {
272 | this[p].updateView();
273 | }
274 | });
275 | setTimeout(function(){i._parentNode.active = true}, 1);
276 | dom.setMultipath(this._parentNode.getTreePath());
277 | pointer.origX = pointer.x - i._parentNode.x;
278 | pointer.origY = pointer.y - i._parentNode.y;
279 | },
280 | _moveHandler: function(pointer) {
281 | this._parentNode.x = pointer.x - (pointer.origX || 0);
282 | this._parentNode.y = pointer.y - (pointer.origY || 0);
283 | this._parentNode.update();
284 | },
285 | _clickHandler: function() {
286 | var i = this;
287 | if (i._parentNode.hasChild() && i._parentNode.wasActive) {
288 | this._parentNode.clearChildren();
289 | } else {
290 | if (i._parentNode.active && i._parentNode.hasChilds()) {
291 | i._parentNode._repeatClickHandler();
292 | }
293 | server.post("data",function(data){
294 | try {eval("data = " + data)} catch(e) {}
295 | i._parentNode.demo_expand(data);
296 | }, this._parentNode.getTreePath().toString());
297 | }
298 | },
299 | getTreePath: function() {
300 |
301 | var path = [];
302 |
303 | var fall = function(node) {
304 | path.push(node.nodeName);
305 | if (node.parent["0"]) {
306 | fall(node.parent["0"].node);
307 | }
308 | };
309 |
310 | fall(this);
311 |
312 | return path.reverse();
313 |
314 | },
315 | attachToScene: function() {
316 | var node = document.createElement("div");
317 | var MAX_LEN = 15;
318 | node.className = "gv-node gv-nodeType-"+this.type;
319 | node._parentNode = this;
320 | node.id = "node" + this.id;
321 | var span = document.createElement("div");
322 | span.innerHTML = this.name;//(this.name.length < MAX_LEN)?this.name:this.name.substr(0, MAX_LEN)+"...";
323 | dom.getWorld().appendChild(node);
324 | node.appendChild(span);
325 | var w = span.clientWidth + 10;
326 | var sh = span.clientHeight;
327 | node.style.width = node.style.height = w + "px";
328 | span.style.marginTop = w/2 - sh/2 + "px";
329 | node.style.zIndex = LAST_LAYER;
330 | this.r = (node.clientWidth + 2)/2;
331 | hid.bind("pointerMove", node, this._moveHandler);
332 | hid.bind("pointerPress", node, this._clickHandler);
333 | hid.bind("pointerStart", node, this._startMoveHandler);
334 | this.domElement = node;
335 | this.update();
336 | },
337 | detachFromScene: function() {
338 | // todo
339 | },
340 | demo_expand: function(testObj) {
341 | if (!testObj || !testObj.status || !testObj.children) return;
342 | var i = this;
343 | var n = 0, c = 0;
344 | testObj.children.foreach(function(){n++});
345 | testObj.children.foreach(function(p){
346 | var it = this[p];
347 | var ok = true;
348 | i.child.foreach(function(x){if (this[x].node.originalID === it.v + "\\" + it.n) ok = false});
349 | if (!ok) return;
350 | var node = gv.createNode(it.n, it.v, it.t);
351 | var d = c*Math.PI*2/n;
352 | node.x = i.x + Math.cos(d)*50; node.y = i.y + Math.sin(d)*50;
353 | node.addParent(i);
354 | node.attachToScene();
355 | c++;
356 | })
357 | }
358 |
359 | };
360 |
361 | this.createNode = function(label, value, type) {
362 | return new Node(label, value, type);
363 | };
364 |
365 | this._worldScrollHandler = function(pointer) {
366 | //pointer.originX = pointer.x +=
367 | };
368 |
369 | this.initialize = function() {
370 |
371 | if (hid) hid.initialize();
372 | if (server) server.initialize();
373 |
374 | var node = this.createNode("USER", "user", 0);
375 | node.x = 3300;
376 | node.y = 3300;
377 | node.attachToScene();
378 |
379 | var world = dom.getContainer();
380 | world.scrollLeft = 3000;
381 | world.scrollTop = 3000;
382 |
383 | hid.bind("pointerMove", world, this._worldScrollHandler);
384 |
385 | };
386 |
387 | };
388 |
--------------------------------------------------------------------------------