# Javascript Code Examples
# Chrome Extension
I built a simple Chrome Extension (local only) as a weekend development project.
I then wrote a blog post (opens new window) outlining my coding experience.
# Tech Stack
- Bootstrap 4 (opens new window)
- Vue JS (opens new window)
- Webpack (opens new window)
- native ES6 Javascript (opens new window)
# Code
- Github Repo » Full Tech Stack (opens new window)
- uses the
tech stack
above
- uses the
- Github Repo » No Dependencies Branch (opens new window)
- uses
native javascript
only - primary JavaScript file for reference (opens new window)
- uses
# Snackbar Component
import $ from 'jquery';
import Snackbar from 'node-snackbar';
import tailwindConfig from '@tailwindConfig';
let globalConfig = () => ({
text: null,
textColor: tailwindConfig.theme.colors.white,
pos: 'bottom-right',
customClass: 'c-toast',
width: 'auto',
showAction: false,
actionText: 'Dismiss',
actionTextColor: tailwindConfig.theme.colors.red['500'],
backgroundColor: tailwindConfig.theme.colors.gray['800'],
duration: 5000,
});
export let mixin = ({ node = null, config = {} } = {}) => {
let state = {
node,
$node: $(node),
config: Object.assign(globalConfig(), config),
};
let module = {
onClick(event) {
let $target = $(event.currentTarget);
let data = $target.data();
let settings = Object.assign({}, state.config, data);
Snackbar.show(settings);
},
init() {
state.$node.on('click', this.onClick.bind(this));
},
};
return module;
};
export let snackbarStatic = (config = {}) => {
let settings = Object.assign({}, globalConfig(), config);
return Snackbar.show(settings);
};
export default {
init({ selector, context, config = {} } = {}) {
return window
.jQuery(selector, context)
.once('Snackbar')
.map((index, node) => mixin({ node, config }).init());
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File Input Component
import { jsClasses } from '@/helpers/settings';
const FileInput = (($) => {
class FileInput {
constructor(node) {
this.$node = $(node);
this.$input = this.$node.find('[data-js="input"]').first();
this.$text = this.$node.find('[data-js="text"]').first();
this.maxTextLength = 25;
this.activeClass = jsClasses.active;
}
getSanitizedFilenameFromPath(path) {
const sanitizedPath = path.replace(/^.*\\/, '');
return sanitizedPath.length > this.maxTextLength
? sanitizedPath.substring(0, this.maxTextLength - 3) + '...'
: sanitizedPath;
}
onChange(event) {
const value = $(event.currentTarget).val();
const text = this.getSanitizedFilenameFromPath(value);
this.$text.text(text);
this.$node.toggleClass(this.activeClass, true);
}
setEventBindings() {
this.$input.on('change', this.onChange.bind(this));
}
init() {
this.setEventBindings();
}
}
return {
init({ selector, context }) {
const $selector = $(selector);
return $(selector, context)
.once('FileInput')
.map((index, node) => {
const module = new FileInput(node);
module.init();
return module;
});
},
};
})(window.jQuery);
export default Object.create(FileInput);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# Search Results Component
import { jsClasses } from '@/helpers/settings';
import debounce from 'lodash.debounce';
let SearchResults = (({ jQuery: $ }) => {
class SearchResults {
constructor(node) {
this.$node = $(node);
this.$input = this.$node.find('[data-js="input"]');
this.$results = this.$node.find('[data-js="results"]');
this.$resultGroup = this.$node.find('[data-js="result-group"]');
this.$result = this.$node.find('[data-js="result"]');
this.isBusy = false;
this.xhr = null;
}
setSearchInputValue(value) {
this.$input.val(value);
return this;
}
toggleResultsVisibility() {
this.$results.toggleClass(jsClasses.notActive, false);
}
getResults() {
let query = this.$input.val();
this.xhr = $.getJSON('/search_results/get_results', {
query,
})
.done((markup) => {
if (!markup) {
markup = '<p>No Results Found</p>';
}
this.$results.html(markup);
})
.always(() => {
this.$node.toggleClass(jsClasses.loading, false);
});
}
onInput() {
if (!this.$input.val()) return;
if (this.xhr) {
this.xhr.abort();
}
this.$node.toggleClass(jsClasses.loading, true);
this.$results.html('');
this.getResults();
}
setSubscriptions() {
PubSub.subscribe('search-callout-input', (msg, data) => {
this.setSearchInputValue(data.value);
this.$input.trigger('input');
this.getResults();
});
PubSub.subscribe('search-overlay-transition-end', (msg, data) => {
if (!data.value) return;
this.$input.focus();
});
}
setEventBindings() {
this.$input.one('input', this.toggleResultsVisibility.bind(this));
this.$input.on('input', debounce(this.onInput.bind(this), 500));
}
init() {
this.setEventBindings();
this.setSubscriptions();
}
}
return {
init({ selector, context }) {
const $selector = $(selector);
return $(selector, context)
.once('SearchResults')
.map((index, node) => {
const module = new SearchResults(node);
module.init();
return module;
});
},
};
})(window);
export default Object.create(SearchResults);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# Search Overlay Component
import PubSub from 'pubsub-js';
import '@/libraries/jquery/jquery.scrollLock';
import bodyLockState from '@/modules/body-lock-state';
import { jsClasses } from '@/helpers/settings';
import { white } from 'ansi-colors';
const SearchOverlay = (({ jQuery: $ }, document) => {
class SearchOverlay {
constructor(node) {
this.$node = $(node);
this.$toggleBtn = $('[data-js="search-overlay-toggle"]');
this.$navMobile = $('[data-js="nav-mobile"]');
this.$container = $('[data-js="search-overlay-content"]');
this.keycodeLookup = {
s: {
keycode: 83,
fn: this.onSKeyPressed.bind(this),
},
esc: {
keycode: 27,
fn: this.onEscKeyPressed.bind(this),
},
};
}
toggleWindowLock(shouldToggle) {
bodyLockState.set('search', shouldToggle);
return this;
}
toggle(bool) {
let isActive = this.$node.hasClass(jsClasses.active);
let shouldToggle = typeof bool === 'boolean' ? bool : !isActive;
this.$node.one('transitionend', () => {
PubSub.publish('search-overlay-transition-end', {
value: shouldToggle,
});
});
this.$node.toggleClass(jsClasses.active, shouldToggle);
this.toggleWindowLock(shouldToggle);
}
onNodeClick(event) {
if (
event.target != this.$container.get(0) &&
this.$container.find(event.target).length == 0
) {
this.toggle(false);
}
}
onSKeyPressed(event) {
this.toggle(true);
}
onEscKeyPressed(event) {
let isActive = this.$node.hasClass(jsClasses.active);
if (!isActive) {
return;
}
this.toggle(false);
}
onDocumentKeyPress(event) {
let keycode = event.keyCode ? event.keyCode : event.which;
let whitelistedKey = Object.keys(this.keycodeLookup).filter(
(key) => this.keycodeLookup[key].keycode === keycode
);
if (!whitelistedKey || !whitelistedKey.length) {
return;
}
let $target = $(event.target);
let blacklist = ['input', 'textarea'];
if (blacklist.some((nodeType) => $target.is(nodeType))) {
return;
}
this.keycodeLookup[whitelistedKey].fn(event);
}
setEventBindings() {
this.$toggleBtn.on('click', this.toggle.bind(this));
this.$node.on('click', this.onNodeClick.bind(this));
$(document).on('keydown', this.onDocumentKeyPress.bind(this));
}
setSubscriptions() {
PubSub.subscribe('toggle-overlay', (msg, data) => {
this.toggle(data.value);
});
}
init() {
this.setEventBindings();
this.setSubscriptions();
}
}
return {
init({ selector, context }) {
return $(selector, context)
.once('SearchOverlay')
.map((index, node) => {
const module = new SearchOverlay(node);
module.init();
return module;
});
},
};
})(window, document);
export default Object.create(SearchOverlay);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124