yet-another-js-utils 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gruntfile.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Grunt configuration file.
3
+ *
4
+ * @param {*} grunt - Grunt.
5
+ */
6
+ module.exports = function(grunt) {
7
+ // Project configuration.
8
+ grunt.initConfig({
9
+ pkg: grunt.file.readJSON('package.json'),
10
+ // Replaces watch:devBuild.tasks for modules
11
+ concurrent: {
12
+ testAndBuildDev: {
13
+ main: ['exec:mocha', 'esbuild:dev'],
14
+ }
15
+ },
16
+ watch: {
17
+ devBuild: {
18
+ files: ['app.js', 'js/*.js'],
19
+ tasks: ['esbuild:dev', 'exec:mocha'],
20
+ options: {
21
+ livereload: true
22
+ },
23
+ },
24
+ cssConcatenation: {
25
+ files: ['css/*.css', '!css/main.css'],
26
+ tasks: ['concat:css'],
27
+ options: {
28
+ livereload: true
29
+ },
30
+ },
31
+ },
32
+ exec: {
33
+ mocha: {
34
+ command: 'npx mocha tests/main.test.js --parallel --slow 0',
35
+ },
36
+ },
37
+ esbuild: {
38
+ options: {
39
+ buildFunction: require('esbuild').build
40
+ },
41
+ prod: {
42
+ entryPoints: ['app.js'],
43
+ outfile: './dist/client.min.js',
44
+ bundle: true,
45
+ minify: true
46
+ },
47
+ dev: {
48
+ entryPoints: ['app.js'],
49
+ outfile: './out.js',
50
+ bundle: true,
51
+ sourcemap: true,
52
+ }
53
+ },
54
+ concat: {
55
+ css: {
56
+ options: {
57
+ separator: '\n',
58
+ },
59
+ src: [
60
+ 'css/0-noscript.css',
61
+ 'css/1-root.css',
62
+ 'css/2-contents.css'
63
+ ],
64
+ dest: 'css/main.css'
65
+ }
66
+ },
67
+ cssmin: {
68
+ options: {},
69
+ target: {
70
+ files: {
71
+ 'css/main.min.css': ['css/main.css']
72
+ }
73
+ }
74
+ }
75
+ });
76
+
77
+ // Load the plugin that provides the tasks.
78
+ grunt.loadNpmTasks('grunt-exec');
79
+ grunt.loadNpmTasks('grunt-esbuild');
80
+ grunt.loadNpmTasks('grunt-concurrent');
81
+ grunt.loadNpmTasks('grunt-contrib-concat');
82
+ grunt.loadNpmTasks('grunt-contrib-cssmin');
83
+ grunt.loadNpmTasks('grunt-contrib-watch');
84
+
85
+ // Default task(s).
86
+ grunt.registerTask('test', ['exec:mocha']);
87
+ grunt.registerTask('dist', ['esbuild', 'cssmin']);
88
+ };
package/js-utils.js ADDED
@@ -0,0 +1,299 @@
1
+ module.exports = {
2
+ // # Nil
3
+
4
+ // I like the NOT operator
5
+ // I like strict type checking
6
+ // Hence a strictly typed equivalent:
7
+ // - 3-letters name n
8
+ // - single argument
9
+
10
+ /**
11
+ * Nil
12
+ * Neither null nor undefined
13
+ * @param { boolean } exp
14
+ * @returns { boolean }
15
+ */
16
+ nil(exp) { // values implicitly considered functions
17
+ if (
18
+ exp === null
19
+ || typeof exp === 'undefined'
20
+ ) {
21
+ return true;
22
+ } else {
23
+ return false;
24
+ }
25
+ },
26
+
27
+ // # Hack
28
+
29
+ // .slice() uses a closed-open interval
30
+ // the mathematical convention goes closed-closed
31
+
32
+ /**
33
+ * Hack
34
+ * @param { number } start
35
+ * @param { number } end
36
+ * @returns
37
+ */
38
+ hack(array, start, end) {
39
+ return array.slice(start, end + 1);
40
+ },
41
+
42
+ // # Prune/Pick
43
+
44
+ // Javascript inherited spreadsheet-like .filter()
45
+ // It felt confusing there
46
+ // It feels confusing here too
47
+
48
+ /**
49
+ * Pick
50
+ * @param {*} array
51
+ * @param {*} pruningFunction
52
+ * @returns
53
+ */
54
+ pick(array, pruningFunction) {
55
+ return array.filter(pruningFunction);
56
+ },
57
+
58
+ /**
59
+ * Prune
60
+ * The inverse of filter()
61
+ * @param {*} array
62
+ * @param {*} filteringFunction
63
+ * @returns
64
+ */
65
+ prune(array, filteringFunction) {
66
+ return array.filter(function (x) {
67
+ return !filteringFunction(x);
68
+ });
69
+ },
70
+
71
+ // # Check
72
+
73
+ // .every() just sounds weird
74
+ /**
75
+ * Check
76
+ * @param {*} array
77
+ * @param {*} checkingFunction
78
+ * @returns
79
+ */
80
+ check(array, checkingFunction) {
81
+ return array.every(checkingFunction);
82
+ },
83
+
84
+ /**
85
+ * Escape
86
+ * @param { string } string
87
+ * @returns
88
+ */
89
+ escape(string) {
90
+ return string
91
+ .replace(/&/g, '&')
92
+ .replace(/</g, '&lt;')
93
+ .replace(/>/g, '&gt;')
94
+ .replace(/"/g, '&quot;')
95
+ .replace(/'/g, '&#039;');
96
+ },
97
+
98
+ /**
99
+ * DOM-rendering template tag
100
+ * @param {Array} strings
101
+ * @returns {object} a DocumentFragment
102
+ */
103
+ html(strings) {
104
+ let output = strings[0], // assumes empty string start?
105
+ max = Math.max(strings.length, arguments.length - 1);
106
+ for (let i = 1; i < max; i++) {
107
+ output += arguments[i];
108
+ output += strings[i];
109
+ }
110
+ let fragment = document.createDocumentFragment();
111
+ fragment.innerHTML = output;
112
+ return fragment;
113
+ },
114
+
115
+ /**
116
+ * Basic contents updater
117
+ * Assuming:
118
+ * - secondNode already holds states updates
119
+ * - rendering applied event-listeners already
120
+ * @param {*} firstNode
121
+ * @param {*} secondNode
122
+ */
123
+ replaceChildren(firstNode, secondNode) {
124
+ let firstNodeChildren = firstNode.childNodes,
125
+ secondNodeChildren = secondNode.childNodes;
126
+ for (let i = 0, c = firstNodeChildren.length; i < c; i++) {
127
+ if (
128
+ secondNodeChildren[i]
129
+ && firstNodeChildren[i].outerHTML !== secondNodeChildren[i].outerHTML
130
+ ) {
131
+ firstNodeChildren[i].parentNode.replaceChild(firstNodeChildren[i], secondNodeChildren[i]);
132
+ }
133
+ }
134
+ if (firstNodeChildren.length < secondNodeChildren.length) {
135
+ for (let i = firstNodeChildren.length, c = secondNodeChildren.length; i < c; i++) {
136
+ firstNodeChildren[i].parentNode.appendChild(secondNodeChildren[i])
137
+ }
138
+ }
139
+ if (firstNodeChildren.length > secondNodeChildren.length) {
140
+ for (let i = firstNodeChildren.length - 1; i > secondNodeChildren.length - 1; i--) {
141
+ firstNodeChildren[i].remove();
142
+ }
143
+ }
144
+ },
145
+
146
+ // # DATA-STATE
147
+
148
+ // Alternative CSS state management
149
+ // - using data-attributes instead of classes
150
+ // - easier to match model and vue
151
+ // - dataset over classlist allows specialization
152
+ // - classes work as booleans (".walked")
153
+ // - you need to notice abscence
154
+ // - data-state work as a string type you can dedicate to store state only, and more than one state
155
+
156
+ /**
157
+ * Add
158
+ * @param { object } element
159
+ * @param { string } state
160
+ */
161
+ addDataState(element, state) {
162
+ let stateArray = [];
163
+ if (
164
+ element.dataset.state
165
+ && element.dataset.state.indexOf(' ') > -1
166
+ ) {
167
+ stateArray = element.dataset.state.split(' ');
168
+ } else {
169
+ stateArray = [element.dataset.state];
170
+ }
171
+ if (stateArray.indexOf(state) === -1) {
172
+ stateArray.push(state);
173
+ element.dataset.state = stateArray.join(' ');
174
+ }
175
+ },
176
+
177
+ /**
178
+ * Remove
179
+ * @param { object } element
180
+ * @param { string } state
181
+ */
182
+ removeDataState(element, state) {
183
+ let stateArray = [];
184
+ if (
185
+ element.dataset.state
186
+ && element.dataset.state.indexOf(' ') > -1
187
+ ) {
188
+ stateArray = element.dataset.state.split(' ');
189
+ } else {
190
+ stateArray = [element.dataset.state];
191
+ }
192
+ element.dataset.state =
193
+ stateArray
194
+ .filter(element => (element !== state))
195
+ .join(' ');
196
+ },
197
+
198
+ /**
199
+ * Replace
200
+ * @param { object } element
201
+ * @param { string } stateToRemove
202
+ * @param { string } stateToAdd
203
+ */
204
+ replaceDataState(element, stateToRemove, stateToAdd) {
205
+ this.removeDataState(element, stateToRemove);
206
+ this.addDataState(element, stateToAdd);
207
+ },
208
+
209
+ /**
210
+ * Toggle
211
+ * @param { object } element
212
+ * @param { string } state
213
+ */
214
+ toggleDataState(element, state) {
215
+ let stateArray = element.dataset.state.split(' ');
216
+ if (stateArray.indexOf(state) == -1) {
217
+ this.addDataState(element, state);
218
+ } else {
219
+ this.removeDataState(element, state);
220
+ }
221
+ },
222
+
223
+ //# AJAX Functions
224
+
225
+ /**
226
+ * AJAX GET
227
+ * @param {string} url The target url
228
+ * @param {function} callback
229
+ * @param {boolean} isJson Response contains json
230
+ * @param {object} callbackContext
231
+ */
232
+ ajaxGet(url, callback, isJson, callbackContext) {
233
+ var req = new XMLHttpRequest();
234
+ req.open("GET", url);
235
+ req.addEventListener("load", function () {
236
+ if (req.status >= 200 && req.status < 400) {
237
+ if (isJson) {
238
+ var json = {};
239
+ try {
240
+ json = JSON.parse(req.responseText);
241
+ } catch (error) {
242
+ console.error("Get request returned invalid JSON.")
243
+ }
244
+ callbackContext === undefined ? callback(json) : callback.apply(callbackContext, [json]);
245
+ } else {
246
+ callbackContext === undefined ? callback(req.responseText) : callback.apply(callbackContext, [req.responseText]);
247
+ }
248
+ } else {
249
+ console.error(req.status + " " + req.statusText + " " + url);
250
+ }
251
+ });
252
+ req.addEventListener("error", function () {
253
+ console.error("Network error trying to access: " + url);
254
+ });
255
+ req.send(null);
256
+ },
257
+
258
+ /**
259
+ * AJAX POST
260
+ * @param {string} url
261
+ * @param {string} data
262
+ * @param {function} successCallback
263
+ * @param {function} failureCallback
264
+ * @param {boolean} isJson
265
+ * @param {object} successCallbackContext
266
+ * @param {object} failureCallbackContext
267
+ */
268
+ ajaxPost(url, data, successCallback, failureCallback, isJson, successCallbackContext, failureCallbackContext) {
269
+ var req = new XMLHttpRequest();
270
+ req.open("POST", url);
271
+ req.addEventListener("load", function () {
272
+ if (req.status >= 200 && req.status < 400) {
273
+ successCallbackContext === undefined ? successCallback(req) : successCallback.apply(successCallbackContext, [req]);
274
+ } else {
275
+ failureCallbackContext === undefined ? failureCallback(req) : failureCallback.apply(failureCallbackContext, [req]);
276
+ console.error(req.status + " " + req.statusText + " " + url);
277
+ }
278
+ });
279
+ req.addEventListener("error", function () {
280
+ console.error("Network error trying to access: " + url);
281
+ });
282
+ if (isJson) {
283
+ req.setRequestHeader("Content-Type", "application/json");
284
+ data = JSON.stringify(data);
285
+ }
286
+ req.send(data);
287
+ },
288
+
289
+ /**
290
+ * Pipe
291
+ * Function piping with initial input
292
+ * @param {*} functions
293
+ * @param {*} input
294
+ * @returns {*}
295
+ */
296
+ pipe(input, ...functions) {
297
+ return functions.reduce((res, fun) => fun(res), input);
298
+ }
299
+ };
package/jsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "include": ["app.js"],
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "checkJs": true,
7
+ "allowJs": true,
8
+ "declaration": true,
9
+ "emitDeclarationOnly": true,
10
+ "outDir": "@types",
11
+ "declarationMap": true,
12
+ "strict": true,
13
+ "exactOptionalPropertyTypes": true
14
+ },
15
+ }
package/main.js ADDED
@@ -0,0 +1,3 @@
1
+ (function () {
2
+ window.Damon = require('./js-utils.js');
3
+ })();
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "yet-another-js-utils",
3
+ "description": "Basic javascript utils",
4
+ "authors": "Adrian Turcev",
5
+ "version": "0.0.1",
6
+ "license": "MPL-2.0",
7
+ "homepage": "https://github.com/adrianturcev/js-utils",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/adrianturcev/JS-UTILS.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/adrianturcev/js-utils/issues"
14
+ },
15
+ "keywords": ["javascript", "utils"],
16
+ "main": ".js",
17
+ "scripts": {
18
+ "watch": "npx grunt watch",
19
+ "test": "npx grunt test",
20
+ "build": "npx grunt dist"
21
+ },
22
+ "devDependencies": {
23
+ "@eslint/js": "^9.27.0",
24
+ "esbuild": "^0.25.4",
25
+ "eslint": "^9.27.0",
26
+ "eslint-plugin-jsdoc": "^50.6.17",
27
+ "globals": "^16.1.0",
28
+ "grunt": "^1.6.1",
29
+ "grunt-concurrent": "^3.0.0",
30
+ "grunt-contrib-watch": "^1.1.0",
31
+ "grunt-esbuild": "^1.0.1",
32
+ "grunt-exec": "^3.0.0",
33
+ "husky": "^9.1.7",
34
+ "mocha": "^11.4.0"
35
+ }
36
+ }
package/tests/index.md ADDED
@@ -0,0 +1 @@
1
+ This file intentionaly left blank.
File without changes