voltron 0.2.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|