voltron 0.2.10
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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/voltron-core.js +282 -0
- data/app/assets/javascripts/voltron-dispatch.js +159 -0
- data/app/assets/javascripts/voltron-ext.js +122 -0
- data/app/assets/javascripts/voltron-observer.js +156 -0
- data/app/assets/javascripts/voltron.js +3 -0
- data/app/helpers/voltron_helper.rb +11 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/generators/voltron/install_generator.rb +37 -0
- data/lib/generators/voltron/js/install_generator.rb +48 -0
- data/lib/voltron/asset.rb +24 -0
- data/lib/voltron/config/js.rb +28 -0
- data/lib/voltron/config.rb +40 -0
- data/lib/voltron/engine.rb +13 -0
- data/lib/voltron/version.rb +3 -0
- data/lib/voltron.rb +38 -0
- data/voltron.gemspec +30 -0
- metadata +182 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
//= require voltron-ext
|
2
|
+
//= require voltron-core
|
3
|
+
|
4
|
+
Voltron.addModule('Dispatch', function(){
|
5
|
+
var _events = {}
|
6
|
+
_globals = {};
|
7
|
+
|
8
|
+
return {
|
9
|
+
addEventWatcher: function(event){
|
10
|
+
var args = Array.prototype.slice.call(arguments, 1).flatten().compact();
|
11
|
+
_events[event] = args;
|
12
|
+
$.each(args, function(index, evt){
|
13
|
+
if(['element', 'event', 'data'].includes(evt.toLowerCase())){
|
14
|
+
Voltron.debug('error', 'Provided event watcher argument %o is a reserved observer param and will be overridden when the event is dispatched. Consider changing the name of the argument in your call to addEventWatcher for %o', evt, event);
|
15
|
+
}
|
16
|
+
});
|
17
|
+
Voltron.debug('info', 'Added event watcher for %o', event);
|
18
|
+
return this.listen();
|
19
|
+
},
|
20
|
+
|
21
|
+
addGlobalEvent: function(event, selector){
|
22
|
+
var options = this.getDispatchOptions(event, { data: {} }, {});
|
23
|
+
event = Object.keys(options).first();
|
24
|
+
var module = options[event].module;
|
25
|
+
var alias = options[event].alias;
|
26
|
+
if(!$.isArray(_globals[event])) _globals[event] = [];
|
27
|
+
_globals[event].push({ selector: selector, alias: alias, module: module });
|
28
|
+
return this.listen();
|
29
|
+
},
|
30
|
+
|
31
|
+
listen: function(){
|
32
|
+
$(document).off('.voltron').off('.voltron_global');
|
33
|
+
$(document).on(this.getEvents(), '[data-dispatch]', this.trigger);
|
34
|
+
|
35
|
+
var globals = this.getGlobalEvents();
|
36
|
+
for(var i=0; i<globals.length; i++){
|
37
|
+
for(var j=0; j<globals[i].data.length; j++){
|
38
|
+
$(document).on(globals[i].event, [globals[i].data[j].selector].flatten().join(', '), this.getGlobalCallback(globals[i].data[j]))
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
return this;
|
43
|
+
},
|
44
|
+
|
45
|
+
getEvents: function(){
|
46
|
+
return $.map(_events, function(val,key){
|
47
|
+
return key + '.voltron';
|
48
|
+
}).join(' ');
|
49
|
+
},
|
50
|
+
|
51
|
+
getGlobalEvents: function(){
|
52
|
+
return $.map(_globals, function(val,key){
|
53
|
+
return { event: key + '.voltron_global', data: val };
|
54
|
+
});
|
55
|
+
},
|
56
|
+
|
57
|
+
getGlobalCallback: function(data){
|
58
|
+
return function(event){
|
59
|
+
// In case the element in question has a data-dispatch attribute already, don't overwrite it
|
60
|
+
// We'll restore the existing data after we trigger the event
|
61
|
+
var oldDispatch = $(this).data('dispatch');
|
62
|
+
$(this).data('dispatch', [[data.module, event.type].compact().join(':'), data.alias].compact().join('/') );
|
63
|
+
Voltron.getModule('Dispatch').trigger.call(this, event);
|
64
|
+
$(this).data('dispatch', oldDispatch);
|
65
|
+
};
|
66
|
+
},
|
67
|
+
|
68
|
+
getSelectors: function(event){
|
69
|
+
var pattern = new RegExp(event);
|
70
|
+
return $.map(_globals, function(val,key){
|
71
|
+
if(pattern.test(key)){
|
72
|
+
var selectors = val.map(function(v){ return v.selector; });
|
73
|
+
return selectors.flatten();
|
74
|
+
}
|
75
|
+
}).flatten().compact();
|
76
|
+
},
|
77
|
+
|
78
|
+
getHash: function(keys, vals){
|
79
|
+
return keys.length === vals.length ? keys.reduce(function(obj, key, index){
|
80
|
+
obj[key] = vals[index];
|
81
|
+
return obj;
|
82
|
+
}, {}) : {};
|
83
|
+
},
|
84
|
+
|
85
|
+
getArgumentHash: function(event, args){
|
86
|
+
if(_events[event]){
|
87
|
+
return this.getHash(_events[event], args);
|
88
|
+
}
|
89
|
+
return {};
|
90
|
+
},
|
91
|
+
|
92
|
+
trigger: function(event){
|
93
|
+
if($(this).data('dispatch')){
|
94
|
+
var args = Voltron('Dispatch/getArgumentHash', event.type, Array.prototype.slice.call(arguments, 1));
|
95
|
+
var params = $.extend(args, { element: this, event: event, data: $(this).data() });
|
96
|
+
|
97
|
+
var dispatches = params.data.dispatch.split(/\s+/);
|
98
|
+
var events = {};
|
99
|
+
|
100
|
+
for(var i=0; i<dispatches.length; i++){
|
101
|
+
events = $.extend(events, Voltron('Dispatch/getDispatchOptions', dispatches[i], params, this));
|
102
|
+
}
|
103
|
+
|
104
|
+
if(events[event.type]){
|
105
|
+
var alias = events[event.type]['alias'];
|
106
|
+
var moduleName = events[event.type]['module'];
|
107
|
+
|
108
|
+
if(Voltron.hasModule(moduleName)){
|
109
|
+
var module = Voltron.getModule(moduleName);
|
110
|
+
var aliasMethod = Voltron('Dispatch/getDispatchMethod', event.type, alias);
|
111
|
+
|
112
|
+
if(module.canReceiveEvents()){
|
113
|
+
if($.isFunction(module[aliasMethod])){
|
114
|
+
module[aliasMethod](params);
|
115
|
+
Voltron.debug('info', 'Dispatching callback function %o in the %o module with observer object: %o', aliasMethod, module.name(), params);
|
116
|
+
}else if(!$.isFunction(module[aliasMethod])){
|
117
|
+
Voltron.debug('warn', 'Callback function %o was not found in the %o module. Try defining it, or remove %o from your element\'s data-dispatch attribute if the event does not need to be observed', aliasMethod, module.name(), event.type);
|
118
|
+
return;
|
119
|
+
}else{
|
120
|
+
Voltron.debug('log', 'Attempted to dispatch %o in %o module with observer object: %o', aliasMethod, module.name(), params);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
}else if(Voltron._modules[moduleName.toLowerCase()]){
|
124
|
+
Voltron.debug('log', 'Tried to dispatch the %o event, but the module %o does not exist. Triggered on element: %o', event.type, moduleName, this);
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
},
|
129
|
+
|
130
|
+
getDispatchOptions: function(dispatch, params, element){
|
131
|
+
var defaultModule = params.data.module || Voltron.getConfig('controller');
|
132
|
+
var defaultAlias = element.id || element.tagName;
|
133
|
+
var options = [];
|
134
|
+
|
135
|
+
if((matches = dispatch.match(/^([a-z_\-]+):([a-z\_\-:]+)\/([a-z\_\-]+)/i)) !== null){
|
136
|
+
// Match format: "module:action/alias"
|
137
|
+
options[matches[2]] = { alias: matches[3], module: matches[1] };
|
138
|
+
}else if((matches = dispatch.match(/^([a-z\_\-:]+)\/([a-z\_\-]+)/i)) !== null){
|
139
|
+
// Match format: "action/alias", using default module
|
140
|
+
options[matches[1]] = { alias: matches[2], module: defaultModule };
|
141
|
+
}else if((matches = dispatch.match(/^([a-z_\-]+):([a-z\_\-:]+)/i)) !== null){
|
142
|
+
// Match format: "module:action", using default alias
|
143
|
+
options[matches[2]] = { alias: defaultAlias, module: matches[1] };
|
144
|
+
}else{
|
145
|
+
// Match everthing else as if no alias or module was defined, use defaults for both
|
146
|
+
options[dispatch.toLowerCase()] = { alias: defaultAlias, module: defaultModule };
|
147
|
+
}
|
148
|
+
return options;
|
149
|
+
},
|
150
|
+
|
151
|
+
getDispatchMethod: function(event, alias){
|
152
|
+
return ['on', event, alias].compact().join('_')
|
153
|
+
.replace(/[^a-z0-9\_\-:]+/ig, '')
|
154
|
+
.replace(/([a-z0-9])([A-Z])/g, function(match){ return [match[0], match[1]].join('_') })
|
155
|
+
.toLowerCase()
|
156
|
+
.replace(/_([a-z0-9])|\-([a-z0-9])|:([a-z0-9])/ig, function(match){ return match[1].toUpperCase(); });
|
157
|
+
}
|
158
|
+
};
|
159
|
+
}, true);
|
@@ -0,0 +1,122 @@
|
|
1
|
+
// String
|
2
|
+
|
3
|
+
String.prototype.trim = function(what){
|
4
|
+
var out = $.trim(this);
|
5
|
+
if(what){
|
6
|
+
var re = new RegExp('^' + what.toString() + '|' + what.toString() + '$');
|
7
|
+
out = out.replace(re, '');
|
8
|
+
}
|
9
|
+
return $.trim(out);
|
10
|
+
};
|
11
|
+
|
12
|
+
String.prototype.blank = function(){
|
13
|
+
return this.trim() == '';
|
14
|
+
};
|
15
|
+
|
16
|
+
String.prototype.startsWith = function(what){
|
17
|
+
var re = new RegExp('^' + what.toString());
|
18
|
+
return re.test(this.trim());
|
19
|
+
};
|
20
|
+
|
21
|
+
String.prototype.endsWidth = function(what){
|
22
|
+
var re = new RegExp(what.toString() + '$');
|
23
|
+
return re.test(this.trim());
|
24
|
+
};
|
25
|
+
|
26
|
+
String.prototype.contains = function(what){
|
27
|
+
var re = new RegExp(what.toString(), 'i');
|
28
|
+
return re.test(this);
|
29
|
+
};
|
30
|
+
|
31
|
+
// Array
|
32
|
+
|
33
|
+
Array.prototype.compact = function(){
|
34
|
+
for(var i=0; i<this.length; i++){
|
35
|
+
if(!this[i] || (typeof this[i].blank == 'function' && this[i].blank())){
|
36
|
+
this.splice(i, 1);
|
37
|
+
i--;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
return this;
|
41
|
+
};
|
42
|
+
|
43
|
+
Array.prototype.includes = function(what){
|
44
|
+
if(typeof what == 'object'){
|
45
|
+
var re = new RegExp(what);
|
46
|
+
for(var i=0; i<this.length; i++){
|
47
|
+
if(re.test(this[i].toString())){
|
48
|
+
return true;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}else{
|
52
|
+
for(var i=0; i<this.length; i++){
|
53
|
+
if(this[i] == what){
|
54
|
+
return true;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
return false;
|
59
|
+
};
|
60
|
+
|
61
|
+
Array.prototype.flatten = function(){
|
62
|
+
var b = Array.prototype.concat.apply([], this);
|
63
|
+
if(b.length != this.length){
|
64
|
+
b = b.flatten();
|
65
|
+
};
|
66
|
+
|
67
|
+
return b;
|
68
|
+
};
|
69
|
+
|
70
|
+
Array.prototype.blank = function(){
|
71
|
+
return this.compact().length == 0;
|
72
|
+
};
|
73
|
+
|
74
|
+
Array.prototype.uniq = function(){
|
75
|
+
var u = {}, a = [];
|
76
|
+
for(var i=0, l=this.length; i<l; ++i){
|
77
|
+
if(u.hasOwnProperty(this[i])){
|
78
|
+
continue;
|
79
|
+
}
|
80
|
+
a.push(this[i]);
|
81
|
+
u[this[i]] = 1;
|
82
|
+
}
|
83
|
+
return a;
|
84
|
+
};
|
85
|
+
|
86
|
+
Array.prototype.first = function(){
|
87
|
+
return this[0];
|
88
|
+
};
|
89
|
+
|
90
|
+
Array.prototype.last = function(){
|
91
|
+
return this[this.length-1];
|
92
|
+
};
|
93
|
+
|
94
|
+
Array.prototype.any = function(callback){
|
95
|
+
for(var i=0; i<this.length; i++){
|
96
|
+
if(callback(this[i])){
|
97
|
+
return true;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
return false;
|
101
|
+
};
|
102
|
+
|
103
|
+
Array.prototype.all = function(callback){
|
104
|
+
for(var i=0; i<this.length; i++){
|
105
|
+
if(!callback(this[i])){
|
106
|
+
return false;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
return true;
|
110
|
+
};
|
111
|
+
|
112
|
+
// Boolean
|
113
|
+
|
114
|
+
Boolean.prototype.blank = function(){
|
115
|
+
return this === false;
|
116
|
+
};
|
117
|
+
|
118
|
+
// Number
|
119
|
+
|
120
|
+
Number.prototype.blank = function(){
|
121
|
+
return this <= 0;
|
122
|
+
};
|
@@ -0,0 +1,156 @@
|
|
1
|
+
//= require voltron
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Voltron::Observer
|
5
|
+
*
|
6
|
+
* Adds ability to monitor DOM events that can be observed by means of the +Voltron.on()+ method
|
7
|
+
*
|
8
|
+
* List of events that can now be observed:
|
9
|
+
*
|
10
|
+
* +append+ When an element is append to the DOM
|
11
|
+
* +remove+ When an element is removed from the DOM
|
12
|
+
* +conceal+ When an element on the DOM is hidden (defined as the result of jQuery's `:hidden` selector)
|
13
|
+
* +reveal+ When an element on the DOM is displayed (defined as the result of jQuery's `:visible` selector)
|
14
|
+
*
|
15
|
+
* Example usage, from within any method defined in a Voltron module:
|
16
|
+
*
|
17
|
+
* Voltron.on('append:div', function(o){
|
18
|
+
* // Do things with the appended element, context is +Voltron+
|
19
|
+
* });
|
20
|
+
*
|
21
|
+
* OR
|
22
|
+
*
|
23
|
+
* this.on('append:div', function(o){
|
24
|
+
* // Do things with the appended element, context is the module in which this observer was defined
|
25
|
+
* });
|
26
|
+
*
|
27
|
+
*/
|
28
|
+
Voltron.addModule('Observer', '*', function(){
|
29
|
+
'use strict';
|
30
|
+
|
31
|
+
var _observer = null;
|
32
|
+
|
33
|
+
var _defaults = {
|
34
|
+
subtree: true,
|
35
|
+
childList: true,
|
36
|
+
characterData: false,
|
37
|
+
attributes: true,
|
38
|
+
attributeFilter: ['style', 'class']
|
39
|
+
};
|
40
|
+
|
41
|
+
return {
|
42
|
+
initialize: function(options){
|
43
|
+
options = $.extend(_defaults, options);
|
44
|
+
this.getObserver().observe(document.body, options);
|
45
|
+
|
46
|
+
// Trigger append and reveal events on start up for appropriate elements
|
47
|
+
$(this.getElements('append')).trigger('append');
|
48
|
+
$(this.getElements('reveal')).filter(function(){
|
49
|
+
return Voltron('Observer/isVisible', this);
|
50
|
+
}).trigger('reveal');
|
51
|
+
},
|
52
|
+
|
53
|
+
stop: function(){
|
54
|
+
this.getObserver().disconnect();
|
55
|
+
},
|
56
|
+
|
57
|
+
process: function(mutations){
|
58
|
+
// Get a unique array of DOM elements that each has the associated mutation as a part of it's dataset
|
59
|
+
var elements = this.getMutationElements(mutations);
|
60
|
+
|
61
|
+
// Iterate through each element, dispatching the appropriate event for each elements mutation
|
62
|
+
for(var i=0; i<elements.length; i++){
|
63
|
+
var mutation = $(elements[i]).data('_mutation');
|
64
|
+
|
65
|
+
if(!mutation || !mutation.type) continue;
|
66
|
+
|
67
|
+
if(mutation.type == 'childList'){
|
68
|
+
// Flag nodes that have been added, and don't dispatch on any that have
|
69
|
+
// This solves the issue of recursion if an element that dispatches `added` is moved in the DOM
|
70
|
+
// Also dispatch only on elements that are configured to have `added` dispatched,
|
71
|
+
// including the element itself if applicable
|
72
|
+
$(mutation.addedNodes).filter(function(){
|
73
|
+
return !$(this).data('_mutation_appended');
|
74
|
+
}).data('_mutation_appended', true)
|
75
|
+
.find(this.getElements('append', 'reveal'))
|
76
|
+
.addBack(this.getElements('append', 'reveal'))
|
77
|
+
.trigger('append').filter(function(){
|
78
|
+
return Voltron('Observer/isVisible', this);
|
79
|
+
}).trigger('reveal');
|
80
|
+
|
81
|
+
// Flag nodes that have been removed to avoid unnecessary dispatching
|
82
|
+
// Dispatch the removed event on any child elements configured to do so,
|
83
|
+
// including the element itself if applicable
|
84
|
+
// Event must be dispatched manually since at this point the element no
|
85
|
+
// longer exists in the DOM, and can't be trigger()'ed
|
86
|
+
$(mutation.removedNodes).filter(function(){
|
87
|
+
return !$(this).data('_mutation_removed');
|
88
|
+
}).data('_mutation_removed', true)
|
89
|
+
.find(this.getElements('remove', 'conceal'))
|
90
|
+
.addBack(this.getElements('remove', 'conceal'))
|
91
|
+
.each(function(){
|
92
|
+
Voltron.getModule('Dispatch').trigger.call(this, $.Event('remove', { target: this }));
|
93
|
+
Voltron.getModule('Dispatch').trigger.call(this, $.Event('conceal', { target: this }));
|
94
|
+
});
|
95
|
+
}else if(mutation.type == 'attributes'){
|
96
|
+
var target = $(mutation.target);
|
97
|
+
// If currently animating, break out. We only want to dispatch when the state is truly reached
|
98
|
+
if(target.is(':animated')) break;
|
99
|
+
|
100
|
+
if(!this.isVisible(mutation.target)){
|
101
|
+
target.find(this.getElements('conceal')).trigger('conceal');
|
102
|
+
}else if(this.isVisible(mutation.target)){
|
103
|
+
target.find(this.getElements('reveal')).filter(function(){
|
104
|
+
return Voltron('Observer/isVisible', this);
|
105
|
+
}).trigger('reveal');
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
},
|
110
|
+
|
111
|
+
getObserver: function(){
|
112
|
+
if(_observer === null){
|
113
|
+
_observer = new MutationObserver($.proxy(function(mutations){
|
114
|
+
// Process all of the elements with mutations
|
115
|
+
this.process(mutations);
|
116
|
+
}, this));
|
117
|
+
}
|
118
|
+
return _observer;
|
119
|
+
},
|
120
|
+
|
121
|
+
getMutationElements: function(mutations){
|
122
|
+
if($.isFunction($.uniqueSort)){
|
123
|
+
// >= jQuery 3
|
124
|
+
return $.uniqueSort($.map(mutations, function(mut){
|
125
|
+
return $(mut.target).data('_mutation', mut).get(0);
|
126
|
+
}));
|
127
|
+
}else{
|
128
|
+
// < jQuery 3
|
129
|
+
return $.unique($.map(mutations, function(mut){
|
130
|
+
return $(mut.target).data('_mutation', mut).get(0);
|
131
|
+
}));
|
132
|
+
}
|
133
|
+
},
|
134
|
+
|
135
|
+
getElements: function(){
|
136
|
+
return $.map(Array.prototype.slice.call(arguments, 0), function(dispatch){
|
137
|
+
return ['[data-dispatch*="' + dispatch + '"]', Voltron('Dispatch/getSelectors', dispatch)];
|
138
|
+
}).flatten().join(', ');
|
139
|
+
},
|
140
|
+
|
141
|
+
isVisible: function(element){
|
142
|
+
var i = 0,
|
143
|
+
visible = $(element).is(':visible');
|
144
|
+
|
145
|
+
while(element.tagName !== 'BODY'){
|
146
|
+
if($(element).is(':hidden') || $(element).width() == 0 || $(element).height() == 0) return false;
|
147
|
+
// Limit to only traversing up 200 DOM elements before we hit the body tag
|
148
|
+
// If we go that far and still don't reach the body tag, something's up
|
149
|
+
// or the html is beyond crappy.
|
150
|
+
if(i++ >= 200) break;
|
151
|
+
element = $(element).parent().get(0);
|
152
|
+
}
|
153
|
+
return true && visible;
|
154
|
+
}
|
155
|
+
};
|
156
|
+
}, true);
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module VoltronHelper
|
2
|
+
|
3
|
+
def voltron_include_tag
|
4
|
+
javascript_tag "Voltron.initialize(#{voltron_config_json});", type: 'text/javascript'
|
5
|
+
end
|
6
|
+
|
7
|
+
def voltron_config_json
|
8
|
+
Voltron.config.to_h.merge({ controller: controller_name, action: action_name }).to_json.html_safe
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|