Skip to content

Commit 5468d84

Browse files
committed
Enhance Who's Using list
Change Facebook wordmark to a svg logo As a test. Support for all header filter and sort options And lots of layout fine tuning to support a nice legacy layout. Move to a testimonial like list Fix calculation of row Filter renamed Paypal to icon Added company Teampage to users Added logo Teampage Added add-your-company bot It works. See ymschaap#10 Added a far away default timestamp Which fixes sort by Newest Removed test user data. Added matching flags. Added (previous) sponsor Behance Re: #1834 (comment) My bad
1 parent 566d28f commit 5468d84

11 files changed

Lines changed: 1736 additions & 743 deletions

File tree

js/users.js

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
/* global window, document */
2+
/*eslint quotes: ["error", "double", { "avoidEscape": true }]*/
3+
4+
"use strict";
5+
6+
class Users {
7+
constructor(element) {
8+
this.defaultSort = "position";
9+
this.totalCounter = 0;
10+
this.filterCounter = 0;
11+
12+
this.displayAttributes = [
13+
"testimonial",
14+
"openSource",
15+
"contributor",
16+
"sponsor",
17+
];
18+
19+
this.displayOptions = [
20+
{
21+
label: "Babel",
22+
},
23+
{
24+
label: "Open Source",
25+
filterBy: "openSource",
26+
},
27+
{
28+
label: "Newest",
29+
sortBy: "added_timestamp",
30+
},
31+
{
32+
label: "Sponsors",
33+
filterBy: "sponsor",
34+
},
35+
{
36+
label: "Contributors",
37+
filterBy: "contributor",
38+
},
39+
{
40+
label: "Testimonials",
41+
filterBy: "testimonial",
42+
},
43+
];
44+
this.displaySelected = this.displayOptions[0];
45+
46+
this.container = element;
47+
this.users = {};
48+
49+
this.render();
50+
}
51+
52+
render() {
53+
const settings = this.container.dataset;
54+
55+
if (!settings || settings.rendered === true) {
56+
return;
57+
}
58+
59+
// check browser compatability
60+
if (!window.addEventListener || !window.JSON) {
61+
return;
62+
}
63+
64+
this.container.setAttribute("data-rendered", true);
65+
66+
// get and prepare our users
67+
if (settings.users) {
68+
try {
69+
this.users = JSON.parse(settings.users);
70+
} catch (e) {
71+
this.renderError("Error in parsing user JSON object");
72+
return;
73+
}
74+
this.users.map((user, i) => {
75+
// prepare users object
76+
if (!user.attributes) {
77+
user.attributes = {};
78+
}
79+
80+
// get a timestamp, add as attribute (to filter on new/old)
81+
user.added_timestamp = new Date("1 Januari 1970").getTime();
82+
if (user.added) {
83+
const time = new Date(user.added);
84+
if (time) {
85+
user.added_timestamp = time.getTime();
86+
}
87+
}
88+
89+
// default sorting
90+
user.position = i;
91+
return user;
92+
});
93+
this.renderList();
94+
} else {
95+
this.renderError("No users found");
96+
return;
97+
}
98+
}
99+
100+
renderList() {
101+
let userList = [];
102+
const list = document.createElement("ul");
103+
const userTemplate = `
104+
<div class="\${className}">
105+
<a href="\${url}" target="_blank">
106+
<img class="logo" src="/img/users/\${logo}">
107+
<div class="name">\${name}</div>
108+
</a>
109+
<div class="attributes">
110+
\${attributes}
111+
</div>
112+
</div>`;
113+
const clean = /\${[a-z]+}/gi;
114+
this.container.innerHTML = ""; // empty block
115+
this.totalCounter = 0;
116+
this.filterCounter = 0;
117+
118+
if (this.users) {
119+
userList = this.users
120+
.filter(user => {
121+
// filter based on displaySelected
122+
this.totalCounter++;
123+
if (this.displaySelected !== null) {
124+
if (!user.attributes) {
125+
return false;
126+
}
127+
if (!this.displaySelected.filterBy) {
128+
return true;
129+
}
130+
const filterThis = Object.entries(user.attributes).find(
131+
attribute => {
132+
if (
133+
this.displaySelected.filterBy === attribute[0] &&
134+
((!this.displaySelected.value && attribute[1]) ||
135+
(this.displaySelected.value &&
136+
this.displaySelected.value.test(attribute[1])))
137+
) {
138+
//no match for filter
139+
return true;
140+
}
141+
return false;
142+
}
143+
);
144+
// no match for filter
145+
if (filterThis === undefined) {
146+
return false;
147+
}
148+
}
149+
this.filterCounter++;
150+
return true;
151+
})
152+
.sort((a, b) => {
153+
// sort by selected filter
154+
if (this.displaySelected !== null) {
155+
if (this.displaySelected.sortBy) {
156+
return (
157+
b[this.displaySelected.sortBy] - a[this.displaySelected.sortBy]
158+
);
159+
}
160+
}
161+
// or default sort
162+
return a[this.defaultSort] - b[this.defaultSort];
163+
})
164+
.map(user => {
165+
// from template to a list item
166+
const prepare = [];
167+
168+
prepare.className = user.logoIcon ? "holder icon" : "holder";
169+
prepare.attributes = this.displayAttributes
170+
.map(displayAttribute => {
171+
if (!user.attributes) {
172+
return;
173+
}
174+
//match displayAttribute with user.attribute
175+
const value = user.attributes[displayAttribute];
176+
if (!value) {
177+
return;
178+
}
179+
180+
if (displayAttribute === "testimonial") {
181+
return (
182+
'<div class="testimonial"><div class="hr"><span>“</span></div><div class="test">' +
183+
value +
184+
"</div></div>"
185+
);
186+
} else {
187+
// flairs flairs
188+
return '<div class="' + displayAttribute + '"></div>';
189+
}
190+
})
191+
.join("");
192+
193+
let template = this.template(userTemplate, prepare); // prepare
194+
template = this.template(template, user); // user
195+
196+
const li = document.createElement("li");
197+
li.innerHTML = template.replace(clean, "");
198+
return li;
199+
});
200+
}
201+
202+
// testimonial rows moved to every 7th position
203+
const testimonialList = [this.renderAdd()];
204+
205+
let currentPosition = 1;
206+
userList.forEach(li => {
207+
const wholeRowPosition = currentPosition % 6 === 0;
208+
const testimonial = /class="testimonial"/i.test(li.innerHTML);
209+
// pop testimonial in our array
210+
if (testimonial) {
211+
testimonialList.push(li);
212+
return; // save for later
213+
}
214+
215+
list.appendChild(li);
216+
currentPosition++;
217+
218+
if (wholeRowPosition && testimonialList.length > 0) {
219+
const currentTestimional = testimonialList.pop(); //shift
220+
currentTestimional.className = "row";
221+
list.appendChild(currentTestimional);
222+
}
223+
});
224+
225+
// if testimonialList not empty
226+
testimonialList.forEach(li => {
227+
if (li.className !== "row") {
228+
// non renderAdd()
229+
li.className = "row";
230+
list.appendChild(li);
231+
}
232+
});
233+
234+
// stats
235+
this.container.appendChild(this.renderStats());
236+
237+
// header
238+
this.container.appendChild(this.renderHeader());
239+
240+
// render
241+
this.container.appendChild(list);
242+
}
243+
244+
renderHeader() {
245+
const div = document.createElement("div");
246+
div.className = "filter";
247+
248+
this.displayOptions.map((option, i) => {
249+
const dot = document.createElement("span");
250+
dot.innerHTML = " &middot; ";
251+
252+
const a = document.createElement("a");
253+
a.className = this.displaySelected === option ? "active" : "";
254+
a.innerHTML = option.label;
255+
a.href = "#";
256+
a.addEventListener("click", event => {
257+
event.preventDefault();
258+
this.displaySelected = option;
259+
this.renderList();
260+
});
261+
i > 0 && div.appendChild(dot);
262+
div.appendChild(a);
263+
});
264+
return div;
265+
}
266+
267+
renderAdd() {
268+
const li = document.createElement("li");
269+
li.className = "row";
270+
271+
const button = document.createElement("button");
272+
button.innerHTML = "Add your company";
273+
button.addEventListener("click", event => {
274+
event.preventDefault();
275+
window.open("https://build.amsterdam/your-company/babel");
276+
});
277+
li.appendChild(button);
278+
return li;
279+
}
280+
281+
renderStats() {
282+
const div = document.createElement("div");
283+
div.className = "stats";
284+
285+
div.innerHTML =
286+
(this.displaySelected && this.displaySelected.filterBy
287+
? "Showing " + this.filterCounter + " out of " + this.totalCounter
288+
: "Showing " + this.totalCounter) + " users.";
289+
return div;
290+
}
291+
292+
renderError(msg) {
293+
const div = document.createElement("div");
294+
div.className = "error";
295+
div.innerHTML = msg;
296+
this.container.appendChild(div);
297+
}
298+
299+
template(template, data) {
300+
const keyTemplate = "\\${key}";
301+
return (Object.keys(data) || []).reduce((template, key) => {
302+
const val =
303+
data[key] !== undefined && data[key] !== null ? data[key] : "";
304+
return template.replace(
305+
new RegExp(keyTemplate.replace("key", key), "gi"),
306+
val
307+
);
308+
}, template);
309+
}
310+
311+
arrayize(obj) {
312+
return Object.keys(obj).map(key => {
313+
const item = obj[key];
314+
item._key = key;
315+
return item;
316+
});
317+
}
318+
}
319+
320+
[...document.querySelectorAll(".users")].forEach(elem => new Users(elem));

webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const config = {
55
entry: {
66
repl: "./js/repl/index.js",
77
minirepl: "./js/minirepl.js",
8+
users: "./js/users.js",
89
},
910
output: {
1011
// Don't bother with hashing/versioning the filename - Netlify does it

0 commit comments

Comments
 (0)