PageRenderTime 59ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/themes/speed/js/speed.js

https://bitbucket.org/builtbyclick/new-speed-evidence
JavaScript | 497 lines | 379 code | 62 blank | 56 comment | 68 complexity | 8444a7b637db90b4b3c4777bca2a16a8 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, LGPL-3.0, LGPL-2.1, BSD-3-Clause
  1. Backbone.View.prototype.close = function(){
  2. this.remove();
  3. this.unbind();
  4. if (this.onClose){
  5. this.onClose();
  6. }
  7. }
  8. var Report = Backbone.Model.extend({
  9. forView: function () {
  10. var modes = {
  11. 1: 'Web',
  12. 2: 'SMS',
  13. 3: 'Email',
  14. 4: 'Twitter'
  15. };
  16. var context = this.toJSON();
  17. context.id = context.incident.incidentid;
  18. context.link = baseURL + "/reports/view/"+context.incident.incidentid;
  19. context.name = context.incident.incidenttitle;
  20. context.description = $("<div>"+context.incident.incidentdescription+"</div>").text();
  21. context.date = moment(context.incident.incidentdate, "YYYY-MM-DD HH:mm:ss");
  22. context.site = context.incident.sharingsourceid;
  23. context.source = modes[context.incident.incidentmode];
  24. context.category = [];
  25. _.each(context.categories, function (c) { context.category.push(c.category.title); });
  26. context.category = context.category.join(', ');
  27. context.thumbnail = false;
  28. _.each(context.media, function (m) { if (m.type == 1 && ! context.thumbnail) context.thumbnail = m.link_url; });
  29. // Source Icon
  30. context.icon = 'icon-globe';
  31. if (context.incident.incidentmode == 4)
  32. {
  33. context.icon = 'icon-twitter';
  34. }
  35. else if (context.incident.incidentmode == 3)
  36. {
  37. context.icon = 'icon-envelope';
  38. }
  39. else if (context.incident.incidentmode == 2)
  40. {
  41. context.icon = 'icon-mobile-phone';
  42. }
  43. else if (context.incident.incidentmode == 1)
  44. {
  45. context.icon = 'icon-globe';
  46. _.each(context.media, function (m)
  47. {
  48. if (m.type == 2) context.icon = 'icon-facetime-video';
  49. if (m.type == 1 && (context.icon == 'icon-globe' || context.icon == 'icon-file-text')) context.icon = 'icon-camera';
  50. if (m.type == 4 && context.icon == 'icon-globe') context.icon = 'icon-rss';
  51. });
  52. }
  53. if (context.site && context.site !== 'main')
  54. {
  55. context.url = baseURL + '/reports/sharing/view/'+context.id;
  56. }
  57. else
  58. {
  59. context.url = baseURL + '/reports/view/'+context.id;
  60. }
  61. return context;
  62. }
  63. });
  64. var ReportCollection = Backbone.Collection.extend(
  65. {
  66. model : Report,
  67. initialize : function (models, options)
  68. {
  69. },
  70. });
  71. /*
  72. * Report list view
  73. */
  74. var ReportListView = Backbone.View.extend(
  75. {
  76. template : _.template($("#reports-list-template").html()),
  77. emptyItemTemplate : _.template($("#empty-item-template").html()),
  78. initialize : function(options) {
  79. _.bindAll(this, "addReport", "addAll", 'loading', 'saveFilter', 'removeReport');
  80. this.model.bind('add', this.addReport);
  81. this.model.bind('remove', this.removeReport);
  82. this.model.bind('reset', this.addAll);
  83. this.container = $(options.container);
  84. this.liveReports = options.liveReports;
  85. },
  86. events : {
  87. 'click .paused' : function () { this.liveReports.toggle(); },
  88. 'click .filter-button' : function() { $('#middle').toggleClass('filterOpen'); },
  89. 'submit form' : 'saveFilter',
  90. 'click form input#reset' : function (e) {
  91. liveReports.filter({ c:null, m:null, mode:null, q:null, sharing:null });
  92. liveReports.updateFormFromFilter();
  93. //e.preventDefault();
  94. }
  95. },
  96. render : function() {
  97. this.$el.html(this.template({}));
  98. this.reportList = this.$('ul.reports-list');
  99. if (this.model.length > 0)
  100. {
  101. this.addAll();
  102. }
  103. else
  104. {
  105. this.reportList.append(this.emptyItemTemplate());
  106. }
  107. this.container.html(this.el);
  108. return this;
  109. },
  110. addReport : function(report) {
  111. var view = new ReportListLiView(
  112. {
  113. model : report,
  114. id : 'update_' + report.id,
  115. parent : this
  116. });
  117. this.reportList.append(view.render().el);
  118. report.view = view;
  119. // Remove no reports box
  120. this.$('li.no-reports').remove();
  121. },
  122. removeReport : function() {
  123. // If we have an empty collection, show 'no reports' message
  124. if (this.model.length == 0)
  125. {
  126. this.reportList.empty();
  127. this.reportList.append(this.emptyItemTemplate());
  128. }
  129. },
  130. addAll : function() {
  131. this.reportList.empty();
  132. this.model.each(this.addReport);
  133. },
  134. onClose : function() {
  135. this.model.unbind('add', this.addReport);
  136. this.model.unbind('reset', this.addAll);
  137. // Destroy report views
  138. this.model.each(function(model) { model.view.close() });
  139. },
  140. loading : function(show) {
  141. if (show)
  142. {
  143. this.$('.loading').show();
  144. this.$('li.no-reports').hide();
  145. }
  146. else
  147. {
  148. this.$('.loading').hide();
  149. this.$('li.no-reports').hide();
  150. }
  151. },
  152. saveFilter : function(event) {
  153. filter = $('#latest-updates form').toJSON();
  154. // Defaults
  155. filter = _.extend({ c:[], m:[], mode:[], q:null, sharing:null }, filter);
  156. delete filter.form_auth_token;
  157. liveReports.filter(filter);
  158. event.preventDefault();
  159. }
  160. });
  161. var ReportListLiView = Backbone.View.extend({
  162. initialize : function(params) {
  163. _.bindAll(this, "update", "remove", "close", "togglePopup");
  164. this.model.bind('change', this.update);
  165. this.model.bind('remove', this.close);
  166. this.parent = params.parent;
  167. // Add events to close popups when clicking anywhere outside the popup
  168. $(document).on('click', function(e) {
  169. params.parent.$('ul.reports-list li').popover('destroy').removeClass('popped');
  170. });
  171. // Stop clicks on popup closing it.
  172. $('.popover').on('click', function(e) { e.stopPropagation(); });
  173. },
  174. tagName : 'li',
  175. className : 'clearfix',
  176. events : {
  177. 'click' : 'togglePopup'
  178. },
  179. attributes : function () {
  180. var context = this.model.forView();
  181. return {
  182. 'data-placement':'right',
  183. 'title' : context.name,
  184. 'data-content' : _.template($("#report-popover-template").html(), context)
  185. }
  186. },
  187. template : _.template($("#report-item-template").html()),
  188. render : function() {
  189. var context = this.model.forView();
  190. this.$el.html(this.template(context));
  191. return this;
  192. },
  193. remove : function() {
  194. this.$el.popover('destroy');
  195. this.$el.remove();
  196. },
  197. update : function(report) {
  198. this.render();
  199. },
  200. onClose : function() {
  201. this.model.unbind('change', this.update);
  202. this.model.unbind('remove', this.remove);
  203. },
  204. togglePopup: function (e) {
  205. // when clicking on an element which has a popover, hide
  206. // them all except the current one being hovered upon
  207. var $popover = this.$el;
  208. if (! $popover.hasClass('popped'))
  209. {
  210. this.parent.$('ul.reports-list li').not('#' + $popover.attr('id')).popover('destroy').removeClass('popped');
  211. $popover.popover({html : 'true', trigger : 'manual'}).popover('show').addClass('popped');
  212. }
  213. else
  214. {
  215. $popover.popover('destroy').removeClass('popped');
  216. }
  217. e.stopPropagation();
  218. }
  219. });
  220. var liveReports = {
  221. request : null,
  222. pollTimeout: null,
  223. loaded: null,
  224. // Latest report ID we pulled
  225. latestid: 0,
  226. // Update frequence in ms
  227. delay: 20000,
  228. // Current filter
  229. filters: {},
  230. initialize : function(params) {
  231. _.bindAll(this, 'fetch', 'startPolling', 'update', 'stopPolling', 'toggle', 'updateFormFromFilter');
  232. // Deferred object for tracking fetch process
  233. dfd = $.Deferred();
  234. this.request = dfd.promise();
  235. dfd.resolve();
  236. this.loaded = $.Deferred();
  237. try {
  238. var filters = JSON.parse(localStorage.getItem("updateFilters"));
  239. } catch (e) {
  240. filters = {};
  241. }
  242. filters = _.extend({ c:[], m:[], mode:[] }, filters);
  243. this.filters = filters;
  244. // Create collection
  245. this.reports = new ReportCollection();
  246. // Render list view
  247. this.listView = new ReportListView({
  248. model : this.reports,
  249. container : '#latest-updates',
  250. liveReports : this
  251. });
  252. this.listView.render();
  253. this.reports.view = this.listView;
  254. this.updateFormFromFilter();
  255. // Show loading text, hide after first load
  256. this.listView.loading(true);
  257. liveReports.loaded.done(_.bind(function () {
  258. this.listView.loading(false);
  259. }, this));
  260. params = _.extend({}, params);
  261. if (typeof params.delay !== 'undefined')
  262. {
  263. this.delay == params.delay;
  264. }
  265. },
  266. // Fetch data from the server
  267. fetch : function() {
  268. // Clear timeout
  269. this.clearPollingTimeout();
  270. // Check if fetch is already running
  271. if (this.request.state() == 'pending') return this.request;
  272. // need to handle race conditions so
  273. // 1. we're never running multiple syncs at once
  274. // handled in poll or fetch
  275. // 2. we can trigger events when a sync finishes
  276. // this.fetching.done( somefunc )
  277. // 3. we can tell if a sync is running now?
  278. // this.fetching.state == 'pending'
  279. // 4. we can say 'sync now or ignore if already syncing'
  280. // this.startPolling(0) is pretty close
  281. // Build request url
  282. var fetchURL = baseURL + '/api?task=incidents&limit=20';
  283. //var fetchURL = '/json/index';
  284. var params = [];
  285. for (var _key in this.filters) {
  286. if (this.filters[_key] != null)
  287. {
  288. params.push(_key + '=' + this.filters[_key]);
  289. }
  290. }
  291. if (fetchURL.indexOf("?") !== -1) {
  292. var index = fetchURL.indexOf("?");
  293. var args = fetchURL.substr(index+1, fetchURL.length).split("&");
  294. fetchURL = fetchURL.substring(0, index);
  295. for (var i=0; i<args.length; i++) {
  296. params.push(args[i]);
  297. }
  298. }
  299. // Update the fetch URL with parameters
  300. fetchURL += (params.length > 0) ? '?' + params.join('&') : '';
  301. dfd = $.getJSON(fetchURL)
  302. .done(this.update)
  303. .always(_.bind(function () {
  304. this.startPolling();
  305. this.loaded.resolve();
  306. }, this));
  307. this.request = dfd.promise();
  308. // Returning promise object when done
  309. return this.request;
  310. },
  311. // Start timer for polling server
  312. startPolling : function(delay) {
  313. // Only reset polling if we're not polling already OR delay is passed
  314. if (this.pollTimeout == null || typeof delay !== 'undefined')
  315. {
  316. delay = typeof delay !== 'undefined' ? delay : this.delay;
  317. // Clear existing timeout
  318. this.clearPollingTimeout();
  319. this.pollTimeout = _.delay(this.fetch, delay);
  320. // Update pause/resume display
  321. $('#latest-updates .paused .pause').show();
  322. $('#latest-updates .paused .resume').hide();
  323. }
  324. },
  325. // Clear timer for polling server
  326. clearPollingTimeout : function() {
  327. clearTimeout(this.pollTimeout);
  328. this.pollTimeout = null;
  329. },
  330. // Clear timer for polling server
  331. stopPolling : function() {
  332. this.clearPollingTimeout();
  333. // Update pause/resume display
  334. $('#latest-updates .paused .pause').hide();
  335. $('#latest-updates .paused .resume').show();
  336. },
  337. isPolling : function()
  338. {
  339. if (this.pollTimeout == null) return false;
  340. return true;
  341. },
  342. // Set filter
  343. // @todo save filters in local storage?
  344. filter: function(filters) {
  345. if (filters == undefined) {
  346. throw "Missing filters";
  347. }
  348. var hasChanged = false;
  349. // Overwrite the current set of filters with the new values
  350. $.each(filters, _.bind(function(filter, value) {
  351. var currentValue = this.filters[filter];
  352. if ((currentValue == undefined && currentValue == null) ||
  353. currentValue !== value) {
  354. hasChanged = true;
  355. this.filters[filter] = value;
  356. }
  357. }, this));
  358. localStorage.setItem("updateFilters", JSON.stringify(this.filters));
  359. // Have the filters changed
  360. if (hasChanged) {
  361. this.startPolling(0);
  362. }
  363. },
  364. // Update display with new data
  365. update: function (data) {
  366. var context = this;
  367. if (typeof data.payload != 'undefined')
  368. {
  369. if (typeof data.payload.incidents != 'undefined')
  370. {
  371. // Set id field
  372. _.each(data.payload.incidents, function (item, i) { data.payload.incidents[i].id = item.incident.incidentid; });
  373. // Set up collection
  374. this.reports.set(data.payload.incidents);
  375. }
  376. else
  377. {
  378. this.reports.set([]);
  379. }
  380. }
  381. },
  382. // Toggle polling
  383. toggle: function (event) {
  384. if (this.pollTimeout == null) this.startPolling();
  385. else this.stopPolling();
  386. event.preventDefault();
  387. },
  388. // @todo move to view
  389. updateFormFromFilter : function () {
  390. var filters = this.filters
  391. $('#latest-updates form input').attr('checked', false);
  392. if (typeof filters.q !== 'undefined' ) $("#latest-updates form #q").val(filters.q);
  393. //if (typeof filters.q !== 'undefined' ) $("#latest-updates form #q").val(filters.q);
  394. if (typeof filters.m !== 'undefined' ) {
  395. _.each(filters.m, function(v) {
  396. $('#latest-updates form input[name="m[]"][value="'+v+'"]').attr('checked','checked');
  397. });
  398. }
  399. if (typeof filters.mode !== 'undefined' ) {
  400. _.each(filters.mode, function(v) {
  401. $('#latest-updates form input[name="mode[]"][value="'+v+'"]').attr('checked','checked');
  402. });
  403. }
  404. if (typeof filters.c !== 'undefined' ) {
  405. _.each(filters.c, function(v) {
  406. $('#latest-updates form input[name="c[]"][value="'+v+'"]').attr('checked','checked');
  407. });
  408. }
  409. if (typeof filters.sharing !== 'undefined' ) {
  410. _.each(filters.sharing, function(v) {
  411. $('#latest-updates form input[name="sharing[]"][value="'+v+'"]').attr('checked','checked');
  412. });
  413. }
  414. }
  415. };
  416. $(document).ready(function () {
  417. liveReports.initialize();
  418. // Bind events
  419. // @todo only after first load
  420. // @todo move to view or something
  421. liveReports.loaded.done(function () {
  422. var pausedState = false;
  423. $('#latest-updates').hover(function () {
  424. $('#latest-updates .paused').show();
  425. $('#latest-updates .filter-button').show();
  426. pausedState = liveReports.isPolling();
  427. liveReports.stopPolling();
  428. }, function () {
  429. $('#latest-updates .paused').hide();
  430. $('#latest-updates .filter-button').hide();
  431. if (pausedState) liveReports.startPolling();
  432. });
  433. });
  434. liveReports.startPolling(0);
  435. });