-
Notifications
You must be signed in to change notification settings - Fork 0
/
Morbid.js
207 lines (173 loc) · 5.61 KB
/
Morbid.js
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
(function() {
var rootEl = document.createElement('div');
var lutesSortedArray = [];
var jqEvents = ['blur'
, 'focus'
, 'focusin'
, 'focusout'
, 'load'
, 'resize'
, 'scroll'
, 'unload'
, 'click'
, 'dblclick'
, 'mousedown'
, 'mouseup'
, 'mousemove'
, 'mouseover'
, 'mouseout'
, 'mouseenter'
, 'mouseleave'
, 'change'
, 'select'
, 'submit'
, 'keydown'
, 'keypress'
, 'keyup'
, 'error'
];
function isNonJqueryDomManipulationFunction(jQuerySet, propertyName) {
if (typeof jQuerySet[propertyName] !== 'function') return true;
if (propertyName == "val") return true;
return false;
}
M = function(selectorOrJQueryObject) {
var foundSet = selectorOrJQueryObject.context ? selectorOrJQueryObject :
jQuery(selectorOrJQueryObject, rootEl);
//if selectorOrJQueryObject is jqueryObject, we should wrap it
function run(methodName) {
var meaningfulArguments = Array.prototype.slice.call(arguments); //arguments passed from caller
meaningfulArguments.shift();
//iterate over all elements of foundSet. If method is present for them, invoke it and report
var report = [];
_.each(foundSet, elementReference => {
var thisObject = getThis(elementReference);
var method = thisObject[methodName];
if (method) {
var returnValue = method.apply(M(elementReference)
, meaningfulArguments);
report.push({
elementReference
, returnValue
});
}
});
return report;
}
var soleWrapper = new Proxy(foundSet, {
get: (fs, name) => {
if (fs[name]) {
if (!isNonJqueryDomManipulationFunction(fs, name)) {
return function(a) {
//return M( (fs[name]).apply(fs, arguments); )
return M(fs[name].apply(fs, arguments));
}
} else {
return fs[name];
}
return fs[name];
}
if (name == "W") return multipleWrapper;
//we have a name of a method. we have to iterate over collection
return function() {
var a = Array.prototype.slice.call(arguments);
a.splice(0, 0, name);
var report = run.apply(foundSet, a);
if (_.isEmpty(report)) {
return undefined;
}
return report[0].returnValue;
};
}
});
var multipleWrapper = new Proxy(foundSet, {
get: (fs, name) => {
//we have a name of a method. we have to iterate over collection
return run.bind(foundSet, name);
}
});
return soleWrapper;
}
function extractSoleFromReport(report) {
return report[0].returnValue;
}
//adds new css rule. No way to delete rule yet. Maybe there should not be.
M.bulk = function(o) {
_.forIn(o, (ruleObject, selector) => addLute(selector, ruleObject));
};
M.wipe = function() {
jQuery(rootEl).off();
rootEl = document.createElement('div');
lutesSortedArray = [];
}
M.rein = function(elementReference) {
rootEl = elementReference;
}
//check that there is no pair of rules in ruleObject, that match the same event.
//in this case it will be impossible to decide order of execution
function validateRule(selector, ruleObject) {
var properties = _.keys(ruleObject);
var eventProperties = _.filter(properties, isValidEventSelector);
if (_.isEmpty(eventProperties)) return;
var eventsByOneTwoLevel = _.map(eventProperties, (key) => key.split(' '));
var events = _.flatten(eventsByOneTwoLevel);
var eventIndexToCount = _.countBy(events, eventName => _.indexOf(jqEvents
, eventName));
var eventIndexToCountWhereTwiceOrMore = _.pickBy(eventIndexToCount, quantity =>
quantity > 1);
var multipleEventIndexes = _.keys(eventIndexToCountWhereTwiceOrMore);
var multipleEventNames = _.map(multipleEventIndexes, index => jqEvents[index]);
if (multipleEventNames && multipleEventNames.length) {
throw new Error(multipleEventNames.join(' and ') +
'repeat more than once in passed rules object. This has no sense and behaviour is unpredictable. Remove one.'
)
}
}
function addLute(selector, ruleObject) {
validateRule(selector, ruleObject);
lutesSortedArray.push({
selector
, ruleObject
, number: lutesSortedArray.length
});
lutesSortedArray.sort((i1, i2) => {
var specificityComparison = SPECIFICITY.compare(i1.selector, i2.selector);
if (specificityComparison != 0) return specificityComparison;
return i1.number - i2.number;
});
var delegatedMethods = _.pickBy(ruleObject, (__, key) =>
isValidEventSelector(key));
_.mapKeys(delegatedMethods, (handler, eventName) => {
jQuery(rootEl).on(eventName, selector, listener);
});
};
function getThis(domReference) {
const applicableLutes = _.filter(lutesSortedArray, lute => jQuery(
domReference).is(lute.selector));
//need to get topmost lutes with same ids.
const ruleObjects = _.map(applicableLutes, lute => lute.ruleObject)
var thisObject = _.partial(_.extend, {}).apply(window, ruleObjects);
//thisObject now should have actual methods with maximum specificity;
//TODO ADD SOME MAP UNIQUE TO DOM ELEMENT
return thisObject;
}
function isValidEventSelector(sel) {
if (sel.indexOf(' ') != -1) {
return true;
}
return jqEvents.includes(sel);
}
function listener(event) {
//here call our most specific method with proper binding
var type = event.type;
var to = getThis(event.currentTarget);
if (to[event.type]) {
return to[event.type].call(M(event.currentTarget), event);
}
//if we got there, user has specified event name like 'click onkeyup', and we have to find a selector with substring.
var oneRandomMatchingEventListener = _.chain(to).pickBy((fn, sel) => {
return (sel.indexOf(event.type) !== -1);
}).toPairs().first().last().value();
return oneRandomMatchingEventListener.call(M(event.currentTarget), event);
}
})();