You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

517 lines
15 KiB

  1. /*!
  2. * # Semantic UI 2.0.0 - Visit
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2015 Contributors
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ($, window, document, undefined) {
  12. "use strict";
  13. $.visit = $.fn.visit = function(parameters) {
  14. var
  15. $allModules = $.isFunction(this)
  16. ? $(window)
  17. : $(this),
  18. moduleSelector = $allModules.selector || '',
  19. time = new Date().getTime(),
  20. performance = [],
  21. query = arguments[0],
  22. methodInvoked = (typeof query == 'string'),
  23. queryArguments = [].slice.call(arguments, 1),
  24. returnedValue
  25. ;
  26. $allModules
  27. .each(function() {
  28. var
  29. settings = ( $.isPlainObject(parameters) )
  30. ? $.extend(true, {}, $.fn.visit.settings, parameters)
  31. : $.extend({}, $.fn.visit.settings),
  32. error = settings.error,
  33. namespace = settings.namespace,
  34. eventNamespace = '.' + namespace,
  35. moduleNamespace = namespace + '-module',
  36. $module = $(this),
  37. $displays = $(),
  38. element = this,
  39. instance = $module.data(moduleNamespace),
  40. module
  41. ;
  42. module = {
  43. initialize: function() {
  44. if(settings.count) {
  45. module.store(settings.key.count, settings.count);
  46. }
  47. else if(settings.id) {
  48. module.add.id(settings.id);
  49. }
  50. else if(settings.increment && methodInvoked !== 'increment') {
  51. module.increment();
  52. }
  53. module.add.display($module);
  54. module.instantiate();
  55. },
  56. instantiate: function() {
  57. module.verbose('Storing instance of visit module', module);
  58. instance = module;
  59. $module
  60. .data(moduleNamespace, module)
  61. ;
  62. },
  63. destroy: function() {
  64. module.verbose('Destroying instance');
  65. $module
  66. .removeData(moduleNamespace)
  67. ;
  68. },
  69. increment: function(id) {
  70. var
  71. currentValue = module.get.count(),
  72. newValue = +(currentValue) + 1
  73. ;
  74. if(id) {
  75. module.add.id(id);
  76. }
  77. else {
  78. if(newValue > settings.limit && !settings.surpass) {
  79. newValue = settings.limit;
  80. }
  81. module.debug('Incrementing visits', newValue);
  82. module.store(settings.key.count, newValue);
  83. }
  84. },
  85. decrement: function(id) {
  86. var
  87. currentValue = module.get.count(),
  88. newValue = +(currentValue) - 1
  89. ;
  90. if(id) {
  91. module.remove.id(id);
  92. }
  93. else {
  94. module.debug('Removing visit');
  95. module.store(settings.key.count, newValue);
  96. }
  97. },
  98. get: {
  99. count: function() {
  100. return +(module.retrieve(settings.key.count)) || 0;
  101. },
  102. idCount: function(ids) {
  103. ids = ids || module.get.ids();
  104. return ids.length;
  105. },
  106. ids: function(delimitedIDs) {
  107. var
  108. idArray = []
  109. ;
  110. delimitedIDs = delimitedIDs || module.retrieve(settings.key.ids);
  111. if(typeof delimitedIDs === 'string') {
  112. idArray = delimitedIDs.split(settings.delimiter);
  113. }
  114. module.verbose('Found visited ID list', idArray);
  115. return idArray;
  116. },
  117. storageOptions: function(data) {
  118. var
  119. options = {}
  120. ;
  121. if(settings.expires) {
  122. options.expires = settings.expires;
  123. }
  124. if(settings.domain) {
  125. options.domain = settings.domain;
  126. }
  127. if(settings.path) {
  128. options.path = settings.path;
  129. }
  130. return options;
  131. }
  132. },
  133. has: {
  134. visited: function(id, ids) {
  135. var
  136. visited = false
  137. ;
  138. ids = ids || module.get.ids();
  139. if(id !== undefined && ids) {
  140. $.each(ids, function(index, value){
  141. if(value == id) {
  142. visited = true;
  143. }
  144. });
  145. }
  146. return visited;
  147. }
  148. },
  149. set: {
  150. count: function(value) {
  151. module.store(settings.key.count, value);
  152. },
  153. ids: function(value) {
  154. module.store(settings.key.ids, value);
  155. }
  156. },
  157. reset: function() {
  158. module.store(settings.key.count, 0);
  159. module.store(settings.key.ids, null);
  160. },
  161. add: {
  162. id: function(id) {
  163. var
  164. currentIDs = module.retrieve(settings.key.ids),
  165. newIDs = (currentIDs === undefined || currentIDs === '')
  166. ? id
  167. : currentIDs + settings.delimiter + id
  168. ;
  169. if( module.has.visited(id) ) {
  170. module.debug('Unique content already visited, not adding visit', id, currentIDs);
  171. }
  172. else if(id === undefined) {
  173. module.debug('ID is not defined');
  174. }
  175. else {
  176. module.debug('Adding visit to unique content', id);
  177. module.store(settings.key.ids, newIDs);
  178. }
  179. module.set.count( module.get.idCount() );
  180. },
  181. display: function(selector) {
  182. var
  183. $element = $(selector)
  184. ;
  185. if($element.length > 0 && !$.isWindow($element[0])) {
  186. module.debug('Updating visit count for element', $element);
  187. $displays = ($displays.length > 0)
  188. ? $displays.add($element)
  189. : $element
  190. ;
  191. }
  192. }
  193. },
  194. remove: {
  195. id: function(id) {
  196. var
  197. currentIDs = module.get.ids(),
  198. newIDs = []
  199. ;
  200. if(id !== undefined && currentIDs !== undefined) {
  201. module.debug('Removing visit to unique content', id, currentIDs);
  202. $.each(currentIDs, function(index, value){
  203. if(value !== id) {
  204. newIDs.push(value);
  205. }
  206. });
  207. newIDs = newIDs.join(settings.delimiter);
  208. module.store(settings.key.ids, newIDs );
  209. }
  210. module.set.count( module.get.idCount() );
  211. }
  212. },
  213. check: {
  214. limit: function(value) {
  215. value = value || module.get.count();
  216. if(settings.limit) {
  217. if(value >= settings.limit) {
  218. module.debug('Pages viewed exceeded limit, firing callback', value, settings.limit);
  219. settings.onLimit.call(element, value);
  220. }
  221. module.debug('Limit not reached', value, settings.limit);
  222. settings.onChange.call(element, value);
  223. }
  224. module.update.display(value);
  225. }
  226. },
  227. update: {
  228. display: function(value) {
  229. value = value || module.get.count();
  230. if($displays.length > 0) {
  231. module.debug('Updating displayed view count', $displays);
  232. $displays.html(value);
  233. }
  234. }
  235. },
  236. store: function(key, value) {
  237. var
  238. options = module.get.storageOptions(value)
  239. ;
  240. if(settings.storageMethod == 'localstorage' && window.localStorage !== undefined) {
  241. window.localStorage.setItem(key, value);
  242. module.debug('Value stored using local storage', key, value);
  243. }
  244. else if($.cookie !== undefined) {
  245. $.cookie(key, value, options);
  246. module.debug('Value stored using cookie', key, value, options);
  247. }
  248. else {
  249. module.error(error.noCookieStorage);
  250. return;
  251. }
  252. if(key == settings.key.count) {
  253. module.check.limit(value);
  254. }
  255. },
  256. retrieve: function(key, value) {
  257. var
  258. storedValue
  259. ;
  260. if(settings.storageMethod == 'localstorage' && window.localStorage !== undefined) {
  261. storedValue = window.localStorage.getItem(key);
  262. }
  263. // get by cookie
  264. else if($.cookie !== undefined) {
  265. storedValue = $.cookie(key);
  266. }
  267. else {
  268. module.error(error.noCookieStorage);
  269. }
  270. if(storedValue == 'undefined' || storedValue == 'null' || storedValue === undefined || storedValue === null) {
  271. storedValue = undefined;
  272. }
  273. return storedValue;
  274. },
  275. setting: function(name, value) {
  276. if( $.isPlainObject(name) ) {
  277. $.extend(true, settings, name);
  278. }
  279. else if(value !== undefined) {
  280. settings[name] = value;
  281. }
  282. else {
  283. return settings[name];
  284. }
  285. },
  286. internal: function(name, value) {
  287. module.debug('Changing internal', name, value);
  288. if(value !== undefined) {
  289. if( $.isPlainObject(name) ) {
  290. $.extend(true, module, name);
  291. }
  292. else {
  293. module[name] = value;
  294. }
  295. }
  296. else {
  297. return module[name];
  298. }
  299. },
  300. debug: function() {
  301. if(settings.debug) {
  302. if(settings.performance) {
  303. module.performance.log(arguments);
  304. }
  305. else {
  306. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  307. module.debug.apply(console, arguments);
  308. }
  309. }
  310. },
  311. verbose: function() {
  312. if(settings.verbose && settings.debug) {
  313. if(settings.performance) {
  314. module.performance.log(arguments);
  315. }
  316. else {
  317. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  318. module.verbose.apply(console, arguments);
  319. }
  320. }
  321. },
  322. error: function() {
  323. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  324. module.error.apply(console, arguments);
  325. },
  326. performance: {
  327. log: function(message) {
  328. var
  329. currentTime,
  330. executionTime,
  331. previousTime
  332. ;
  333. if(settings.performance) {
  334. currentTime = new Date().getTime();
  335. previousTime = time || currentTime;
  336. executionTime = currentTime - previousTime;
  337. time = currentTime;
  338. performance.push({
  339. 'Name' : message[0],
  340. 'Arguments' : [].slice.call(message, 1) || '',
  341. 'Element' : element,
  342. 'Execution Time' : executionTime
  343. });
  344. }
  345. clearTimeout(module.performance.timer);
  346. module.performance.timer = setTimeout(module.performance.display, 500);
  347. },
  348. display: function() {
  349. var
  350. title = settings.name + ':',
  351. totalTime = 0
  352. ;
  353. time = false;
  354. clearTimeout(module.performance.timer);
  355. $.each(performance, function(index, data) {
  356. totalTime += data['Execution Time'];
  357. });
  358. title += ' ' + totalTime + 'ms';
  359. if(moduleSelector) {
  360. title += ' \'' + moduleSelector + '\'';
  361. }
  362. if($allModules.length > 1) {
  363. title += ' ' + '(' + $allModules.length + ')';
  364. }
  365. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  366. console.groupCollapsed(title);
  367. if(console.table) {
  368. console.table(performance);
  369. }
  370. else {
  371. $.each(performance, function(index, data) {
  372. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  373. });
  374. }
  375. console.groupEnd();
  376. }
  377. performance = [];
  378. }
  379. },
  380. invoke: function(query, passedArguments, context) {
  381. var
  382. object = instance,
  383. maxDepth,
  384. found,
  385. response
  386. ;
  387. passedArguments = passedArguments || queryArguments;
  388. context = element || context;
  389. if(typeof query == 'string' && object !== undefined) {
  390. query = query.split(/[\. ]/);
  391. maxDepth = query.length - 1;
  392. $.each(query, function(depth, value) {
  393. var camelCaseValue = (depth != maxDepth)
  394. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  395. : query
  396. ;
  397. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  398. object = object[camelCaseValue];
  399. }
  400. else if( object[camelCaseValue] !== undefined ) {
  401. found = object[camelCaseValue];
  402. return false;
  403. }
  404. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  405. object = object[value];
  406. }
  407. else if( object[value] !== undefined ) {
  408. found = object[value];
  409. return false;
  410. }
  411. else {
  412. return false;
  413. }
  414. });
  415. }
  416. if ( $.isFunction( found ) ) {
  417. response = found.apply(context, passedArguments);
  418. }
  419. else if(found !== undefined) {
  420. response = found;
  421. }
  422. if($.isArray(returnedValue)) {
  423. returnedValue.push(response);
  424. }
  425. else if(returnedValue !== undefined) {
  426. returnedValue = [returnedValue, response];
  427. }
  428. else if(response !== undefined) {
  429. returnedValue = response;
  430. }
  431. return found;
  432. }
  433. };
  434. if(methodInvoked) {
  435. if(instance === undefined) {
  436. module.initialize();
  437. }
  438. module.invoke(query);
  439. }
  440. else {
  441. if(instance !== undefined) {
  442. instance.invoke('destroy');
  443. }
  444. module.initialize();
  445. }
  446. })
  447. ;
  448. return (returnedValue !== undefined)
  449. ? returnedValue
  450. : this
  451. ;
  452. };
  453. $.fn.visit.settings = {
  454. name : 'Visit',
  455. debug : false,
  456. verbose : false,
  457. performance : true,
  458. namespace : 'visit',
  459. increment : false,
  460. surpass : false,
  461. count : false,
  462. limit : false,
  463. delimiter : '&',
  464. storageMethod : 'localstorage',
  465. key : {
  466. count : 'visit-count',
  467. ids : 'visit-ids'
  468. },
  469. expires : 30,
  470. domain : false,
  471. path : '/',
  472. onLimit : function() {},
  473. onChange : function() {},
  474. error : {
  475. method : 'The method you called is not defined',
  476. missingPersist : 'Using the persist setting requires the inclusion of PersistJS',
  477. noCookieStorage : 'The default storage cookie requires $.cookie to be included.'
  478. }
  479. };
  480. })( jQuery, window , document );