-
Notifications
You must be signed in to change notification settings - Fork 0
/
twitter_filter.js
405 lines (370 loc) · 14.7 KB
/
twitter_filter.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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
// TODOS:
// Retrieve trucks from server
// Get more than one page of tweets from Twitter API
// Logging
var log = function(message) {
console.log(''+ message);
};
// This widget takes a tweet and formats it to HTML.
var TweetFormatter = {};
TweetFormatter.construct = function(options) {
var _dt_format = options.datetime_format, // this shoudl be a string with hh:mm etc
// Param: JSON for a single tweet. Returns: HTML string
render_markup = function(tweet) {
var _sn = tweet.user.screen_name,
// Turns hashtags, @names and links into links; populates HTML
_linkified_text = $.linkify(tweet.text),
_tweet_timestamp = new Date(tweet.created_at);
return '<div class="tweet">' +
'<a href="http://twitter.com/' + _sn + '" rel="nofollow">' +
'<img src="' + tweet.user.profile_image_url + '" /></a>' +
'<strong><a href="http://twitter.com/' + _sn + '" rel="nofollow">' + _sn + '</a></strong>' +
' <span class="text">' + _linkified_text + '</span>' +
'<span class="time">' +
'<a href="http://twitter.com/' + _sn +'/statuses/' + tweet.id_str + '" rel="nofollow">' +
_tweet_timestamp.toString(_dt_format) + '</a>' +
'</span></div>';
};
return {format_text: render_markup};
};
// This widget filters tweets.
var TweetParser = {};
TweetParser.construct = function() {
var process_tweets = function(fulljson, keywords, successHandler){
// Specific keyword_cluster has been chosen, and there are keywords, so filter the tweets.
filtered_json = _filter_tweets(fulljson, keywords); // which will filter tweets
return successHandler(filtered_json);
},
// For the filtering. Thanks Stack Overflow!
// http://stackoverflow.com/questions/4241431/how-to-search-one-string-for-all-of-the-words-contained-in-a-second-string
_commonwords = function(string, wordlist) {
string= string.toLowerCase().split(/\s+/);
wordlist= wordlist.toLowerCase().split(/\s+/);
return wordlist.every(function(itm){
return string.indexOf(itm)!= -1;
});
},
// This is where the magic happens
_filter_tweets = function(tweets, keywords) {
var filtered_tweets = [],
tweet_added = false; // Don't want tweets appended multiple times for matching multiple keywords
for (var t=0;t < tweets.length; t++){
tweet_text = tweets[t].text;
tweet_name = tweets[t].user.screen_name;
for (var k=0;k<keywords.length;k++){
keyword = keywords[k].toLowerCase(); // We are case-insensitive
if (tweet_added === false){
if ((tweet_text.toLowerCase().search(keyword) > -1) ||
(_commonwords(tweet_text, keyword)) ||
(tweet_name.search(keyword) > -1) ||
(_commonwords(tweet_name, keyword)))
{
filtered_tweets.push(tweets[t]);
tweet_added = true;
continue;
}
}
else { continue; }
}
}
return filtered_tweets;
};
return { filter_tweets : process_tweets };
};
/* Currently, all info is trapped client-side. Maybe send some info to server for caching. */
var FT = {};
FT.construct = function(options) {
var
// URL should return JSON list of tweets. See examples of options in twitter_options below
list_url='https://api.twitter.com/1/statuses/user_timeline.json?callback=?',
//since_id = options.since_id,
/* Specified here: https://dev.twitter.com/docs/api/1/get/lists/statuses */
twitter_options = {
owner_screen_name : 'joebiden',// twitter name, string
per_page: 50, // number of tweets per fetch
include_entities : false
},
current_cluster = options.current_cluster,
container_div = options.container_div,
header_div = options.header_div,
search_div = options.search_div, // for quicksearch
fail_div = options.fail_div,
nav_link_prefix = options.nav_link_prefix,
cached_tweets,
cached_data,
cache_expiry = 1000 * 60, // 1 minute
throbber_on = false,
throbber = $('#throbber'),
_keywords,
// This is a model, should not be here!
/*
data structure looks like this:
{ url_hash_name-for-keyword-cluster : {
display_name: 'name that is shown in header',
keywords: list of keywords to be filtered, as strings
zip_code: this property is currently not used.
}
}
*/
keyword_cluster = {
'no-filter': {
display_name: 'No Filter',
keywords: [],
zip_code: null
},
'santa-monica': {
display_name: 'Santa Monica',
keywords : ['santa monica', 'samo', 'arizona', 'ocean park', 'sawtelle',
'santamonica', 'hulu', 'westla', 'monica'],
zip_code : 90401
},
'hollywood': {
display_name: 'Hollywood',
keywords : ['hollywood', 'cnn', 'arclight', 'cahuenga',
'amoeba', 'highland', 'gower', 'ivar', 'sunset', 'cole'],
zip_code : 90028
},
'pico-robertson': {
display_name: 'Pico/Robertson',
keywords : ['pico robertson', 'robertson'],
zip_code : 90035
},
'downtown-la' : {
display_name: "Downtown LA",
keywords : ['dtla', 'moca', 'artwalk', 'downtown'],
zip_code : 90017
},
'miracle-mile' : {
display_name: 'Miracle Mile',
keywords : ['miracle mile', 'lacma', 'wilshire', 'fairfax', 'miracle'],
zip_code : 90038
},
'pasadena':{
display_name: 'Pasadena',
keywords: ['pasadena'],
zip_code: 90042
},
'ucla':{
display_name: 'UCLA',
keywords: ['ucla', 'westwood'],
zip_code: 90095
},
'silverlake':{
display_name: 'Silverlake',
keywords: ['silverlake', 'echo park', 'rowena'],
zip_code: 90026
},
'tarzana':{
display_name: 'Tarzana',
keywords: ['tarzana', 'valley'],
zip_code: 91356
}
},
// FAIL FAIL FAIL
failHandler = function (message) {
if(throbber !== undefined && throbber_on === true)
{
hide_throbber();
}
log('FAILED: '+ message);
},
// Called by the TweetParser as its successHandler, or by _process_tweets:
// having fetched and/or filtered the tweets, we will cache and display them.
successHandler = function (tweets) {
_set_cache(container_div, current_cluster, tweets);
//and display
display_tweets(tweets);
},
load_throbber = function(){
if (throbber_on === false && throbber !== undefined) {
throbber_on = true;
throbber.show();
}
},
hide_throbber = function(){
if (throbber_on === true && throbber !== undefined) {
throbber_on = false;
throbber.hide();
}
},
// Params:
// element: DOM element that holds the cached data
// address: name for the cached object, a string
// cache_content: data to be cached.
// Also cached in same object as cache_content is timestamp
_set_cache = function(element, address, cache_content) {
element.data(address, {timestamp: new Date(), cache_content: cache_content});
},
// Params: DOM element, name for cached object (a string)
// Returns cached content -- if there's nothing cached, this returns undefined
_get_cache = function(element, address) {
if (element.data(address) !== undefined &&
new Date() - element.data(address).timestamp < cache_expiry)
{
return element.data(address).cache_content;
}
},
// display the keywords for this keyword cluster
show_cluster_info = function(keywords) {
$(header_div).fadeIn();
$(header_div + ' span:first-child').text(keyword_cluster[current_cluster].display_name);
for (var i=0;i<keywords.length;i++){
$('#keyword-list').fadeIn().append(
'<span class="_keyword">'+ keywords[i] + '</span> '
);}
},
clear_cluster_info = function() {
$('#keyword-list').hide();
$('#keyword-list span').remove(); // remove all the keywords we'd appended
$(header_div).hide();
},
// Render each tweet by calling the Formatter.
render_tweet = function(tweet) {
var tweetformatter = TweetFormatter.construct({
datetime_format: 'h:mmtt dddd MMM d'
});
container_div.append(tweetformatter.format_text(tweet));
},
// Show all the tweets, if there are any
display_tweets = function(tweets) {
hide_throbber();
// If no tweets to show, we have a fail div that we can fall back on.
if (tweets.length > 0) {
if (fail_div.is(':visible')){
fail_div.hide();
}
$.each(tweets, function(tweet, value){
render_tweet(value);
});
enable_quicksearch();
}
else{
// Disable the search form (since there are no tweets to search anyway)
disable_quicksearch();
if (fail_div.is(':hidden')){
fail_div.show();
}
failHandler('No tweets found =(');
}
},
/** On-page quick-search to filter content.
Thanks riklomas! https://github.com/riklomas/quicksearch */
enable_quicksearch = function() {
if($(search_div).hasClass("disabled")){
$(search_div).removeClass('disabled');
}
if($(search_div + " input").prop("disabled"))
{$(search_div + " input").prop("disabled", false);}
// Argument for the quicksearch method: container div + class of individual tweet.
var qs = $('input#search').quicksearch('#posts .tweet');
qs.cache();
},
disable_quicksearch = function() {
if ($(search_div).hasClass("disabled") !== true)
{
$(search_div).addClass("disabled");
$(search_div + ' input').prop("disabled", true);
}
},
// After tweets are fetched, we parse them if necessary to filter.
_process_tweets = function(json) {
if (json.length > 0) {
// If no keywords, we can just call sucessHandler ...
if (_keywords.length === 0 || _keywords === undefined) {
log('No keyword filters: Showing all the tweets');
successHandler(json);
}
// ... Otherwise, we shall filter them by those keywords.
else {
var tweetparser = TweetParser.construct();
tweetparser.filter_tweets(json, _keywords, successHandler);
}
}
else {
log('JSON was not fetched =(');
}
},
// If JSON is not already available in cache, let's make a call to Twitter API
_fetch_json = function(){
load_throbber();
cached_data = _get_cache($('body'), 'jsonfetch');
if (cached_data !== undefined ) {
_process_tweets(cached_data);
}
else {
$.getJSON(list_url, twitter_options, function(data, textStatus){
_set_cache($('body'), 'jsonfetch', data); // cache the response
_process_tweets(data);
return false;
// TODO: better error handling
});
}
},
// Add onclick events to all the links for each cluster.
add_link_events = function() {
var nav_links = $('.nav_link'),
link_to_no_filter = $('.raw_feed'); // other links to load raw feed
nav_links.each(function(index, value) {
$(value).click(function(event){
// Grabs value inside <a> tags. Maybe don't do this.
current_cluster = value.id.replace(nav_link_prefix, '');
_reload(current_cluster);
event.preventDefault();
});
});
// Add click events to non-nav links to full feed.
link_to_no_filter.each(function(index, value) {
$(value).click(function(event){
current_cluster = "no-filter";
_reload(current_cluster);
event.preventDefault();
});
});
},
_reload = function(cluster) {
if (container_div.length > 0) {
container_div.empty();}
// Remove all the keywords at the top.
clear_cluster_info();
// In nav, un-highlight all tabs, re-highlight new
$('#nav li').removeClass('ui-selected');
// SETTING THE NEW KEYWORD CLUSTER
current_cluster = cluster;
_init();
},
_init = function() {
if (keyword_cluster[current_cluster] === undefined) {
log("We can't filter this keyword cluster");
current_cluster = 'no-filter';
}
// Get and render the keyword filters for the current keyword cluster
_keywords = keyword_cluster[current_cluster].keywords;
if (_keywords !== undefined && _keywords.length > 0) {
show_cluster_info(_keywords);
}
// If tweets exist in recent cache, use that.
// Might forgo this and let each cluster re-process the JSON,
// since the latter may be available in cache anyway.
cached_tweets = _get_cache(container_div, current_cluster);
if(cached_tweets !== undefined) {
display_tweets(cached_tweets);
}
else {
_fetch_json();
}
// Highlight the currently loaded keyword cluster in the navigation
$('#'+ nav_link_prefix + current_cluster).addClass('ui-selected');
// For history change
window.location.hash = current_cluster;
history.replaceState(current_cluster, 'keywords'); // maybe pushState?
};
_init();
// Bind click events; only wanna do this once, otherwise the clicks pile on.
add_link_events();
// For back arrow functionality.
window.onpopstate = function() {
current_cluster = window.location.hash.replace('#','');
_reload(current_cluster);
return false;
};
};