/
myscript.js
231 lines (204 loc) · 7.65 KB
/
myscript.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// FRACTION CLASS
// accepts an improper fraction as a string param ('1/2', '5/2')
// toString class returns a mixed num rounded to nearest param for display.
// i.e
function Fraction(mixedNum) {
var parts = mixedNum.split('/');
this.num = parseInt(parts[0]);
this.denom = parts.length > 1 ? parseInt(parts[1]) : 1;
return this;
}
Fraction.prototype.multiply = function(otherFrac) {
var num = this.num * otherFrac.num;
var denom = this.denom * otherFrac.denom;
return new Fraction(`${num}/${denom}`);
}
Fraction.prototype.toString = function() {
var NEAREST_DENOM = 4; // constant
var nearestNum = 1;
var mixedNum = this.num;
// convert to common base
nearestNum *= this.denom;
mixedNum *= NEAREST_DENOM;
// find out how many parts are in the improper fraction.
var numParts = 0;
while (mixedNum >= nearestNum) {
numParts += 1;
mixedNum -= nearestNum;
}
// if mixedNum is greater than or equal to 1/2 of nearestNum, add one to numParts.
// this rounds to the nearest fraction.
if (mixedNum >= (nearestNum / 2)) {
numParts += 1;
}
// extract whole numbers out of the parts
var whole = 0;
while (numParts >= NEAREST_DENOM) {
whole += 1;
numParts -= NEAREST_DENOM;
}
if (numParts === 0 && whole > 0) {
return `${whole}`;
} else if (numParts === 0 && whole === 0) {
// if the amount is so small that it won't work, just include it as a 1/4
return `1/4`; // smallest fraction
}
// reduce to 1/2 if there are 2 4ths left over.
var frac;
if (numParts === 2) {
frac = '1/2';
} else {
frac = `${numParts}/${NEAREST_DENOM}`;
}
// if there was a whole number in the improper fraction, return that, else just the frac
return whole ? whole + " " + frac : frac;
}
// [2-4, 4-6, 6-8, 8-10, 10-12]
var SCALING_FACTORS = {
0: [new Fraction('1/1'), new Fraction('2/1'), new Fraction('3/1'), new Fraction('4/1'), new Fraction('5/1')],
1: [new Fraction('1/2'), new Fraction('2/2'), new Fraction('3/2'), new Fraction('4/2'), new Fraction('5/2')],
2: [new Fraction('1/3'), new Fraction('2/3'), new Fraction('3/3'), new Fraction('4/3'), new Fraction('5/3')],
3: [new Fraction('1/4'), new Fraction('2/4'), new Fraction('3/4'), new Fraction('4/4'), new Fraction('5/4')],
4: [new Fraction('1/5'), new Fraction('2/5'), new Fraction('3/5'), new Fraction('4/5'), new Fraction('5/5')],
};
var SERVING_ARRAYS = {
range: ['2-4', '4-6', '6-8', '8-10', '10-12'],
noRange: ['2', '4', '6', '8', '10']
};
var SERVES = 'SERVES';
var cache = {
cachedAmounts: [],
servings: [],
};
// sometimes has the word 'serves' in it
// sometimes it is a range of values
// accepts a string that contains the serving in it
// returns the full serving without the serves text.
function parseServing(serveText) {
var upperText = serveText.toUpperCase();
if (upperText.indexOf(SERVES) > -1) {
upperText = upperText.slice(upperText.indexOf(SERVES) + serves.length + 1)
}
return upperText;
}
function isRange(parsedServing) {
return parsedServing.indexOf('-') > -1;
}
// function for whether or not the serving is a ranged serving or not.
// 1 -- noRange
// 12 -- noRange
// 1-2 -- range
function getRangeKey(parsedServing) {
return isRange(parsedServing) ? 'range' : 'noRange';
}
// intake something like 6 or 6-8
// returns the low number in the range
function getDefaultServing(parsedServing) {
return parsedServing.split('-')[0];
}
// intake something like 6
// return something like 2
function getDefaultIndex(defaultServing) {
return (parseInt(defaultServing) / 2) - 1;
}
// takes a serving and creates an array that creates scalings from 2 to 10.
// input 4-6 -- [{'displayText': '2-4', 'scaling': 1, 'isDefaultServing': false}, ... ]
function enumerateServings(parsedServing) {
var rangeKey = getRangeKey(parsedServing);
var defaultServing = getDefaultServing(parsedServing);
var defaultIndex = getDefaultIndex(defaultServing);
var servings = SERVING_ARRAYS[rangeKey]
var scaling = SCALING_FACTORS[defaultIndex];
var scaledServings = servings.map((item, index) => {
return {
isDefaultServing: index === defaultIndex,
scaling: scaling[index],
displayText: item,
};
});
return scaledServings;
}
// TODO: reevaluate if storing the entire json blob in HTML is necessary.
function createDropdown(scaledServings) {
var select = document.createElement('select');
select.setAttribute('id', 'serving-dropdown');
for (var i = 0; i < scaledServings.length; i++) {
var option = document.createElement('option');
option.setAttribute('value', i)
option.innerHTML = scaledServings[i].displayText;
select.appendChild(option);
if (scaledServings[i].isDefaultServing) {
select.value = i;
}
}
select.onchange = onServingChange;
return select;
}
// array of arrays.
// each item in the array either represents a fraction
// a whole number.
// or has two entries which represent the top and bottom portions of the ingredient range.
function getAmountsFromDomNodes(amounts) {
var amountValues = [];
for (var i = 0; i < amounts.length; i++) {
var amount = amounts[i].innerHTML;
if (isRange(amount)) {
// this is when it is a range of numbers
parts = amount.split('-');
amount = [new Fraction(parts[0]), new Fraction(parts[1])];
} else {
// this is when it is just a single number
// or when it is a fraction
amount = [new Fraction(amount)];
}
amountValues.push(amount);
}
return amountValues;
}
// accepts an array of DOMnodes that contain amounts of ingredients.
// scales it by the chosen scaling factor.
// returns the scaled version of the cached values for the current scale.
function scaleAmounts(amounts, scale) {
var scaledArrayOfAmounts = [];
for (var i = 0; i < amounts.length; i++) {
var amountArray = amounts[i];
amountArray = amountArray.map((item) => {
return item.multiply(scale);
});
scaledArrayOfAmounts.push(amountArray);
}
return scaledArrayOfAmounts;
}
// used when serving changes to update the dom.
function onServingChange(e) {
var index = parseInt(e.target.value);
var currentScaling = cache.servings[index].scaling;
var afterScaling = scaleAmounts(cache.cachedAmounts, currentScaling);
var amountsFromDom = document.getElementsByClassName("wprm-recipe-ingredient-amount");
for(var i = 0; i < amountsFromDom.length; i++) {
var domNode = amountsFromDom[i];
var amount = afterScaling[i].length === 1 ? afterScaling[i][0].toString() : `${afterScaling[i][0].toString()}-${afterScaling[i][1].toString()}`;
domNode.innerHTML = amount;
}
}
// execution
var serving = document.getElementsByClassName("wprm-recipe-details-unit wprm-recipe-servings-unit")[0];
if (serving) { // not all pages have serving information
var serve = serving.innerHTML;
// parse serving out of dom and construct the appropriate array of options.
var parsedServing = parseServing(serve);
var enumeratedServings = enumerateServings(parsedServing);
cache.servings = enumeratedServings;
// create the dropdown from the serving options. attachn onchange handler.
var select = createDropdown(enumeratedServings);
serving.replaceChild(select, serving.childNodes[0]); // first argument, node to replace. second is node to be replaced.
var descriptionText = document.createElement('div');
descriptionText.innerHTML = 'Scale your recipe with the dropdown';
descriptionText.setAttribute('style', 'font-weight: bold;');
serving.appendChild(descriptionText);
// scrape ingredients from dom, cache for reference later.
var amountsFromDom = document.getElementsByClassName("wprm-recipe-ingredient-amount");
cache.cachedAmounts = getAmountsFromDomNodes(amountsFromDom);
} else {
console.log('no serving was detected');
}