index.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. // TODO - Add initial page loading and handlers
  2. (function($,History,console){
  3. var State = History.getState(),
  4. Old = {},
  5. Key = null,
  6. flags = [],
  7. templates = [],
  8. flag = window.flag = function(name,value){
  9. if(exists(value)){
  10. flags[name] = value;
  11. }else{
  12. return exists(flags[name])?flags[name]:false;
  13. }
  14. },
  15. settings = {},
  16. exists = function(v){
  17. return typeof v != 'undefined';
  18. },
  19. get = window.get = function(s){
  20. return settings[s];
  21. },
  22. set = window.set = function(s,v){
  23. settings[s] = v;
  24. return v;
  25. },
  26. setKey = window.setKey = function(key){
  27. if(key !== null){
  28. console.log('Key change to '+key);
  29. Key = key;
  30. var d = new Date();
  31. d.setTime(d.getTime()+get('expire'));
  32. $.cookie('key',key,{
  33. expires: d
  34. });
  35. }else{
  36. console.log('Key deleted');
  37. Key = null;
  38. $.removeCookie('key');
  39. }
  40. },
  41. getKey = window.getKey = function(){
  42. return Key;
  43. },
  44. template = window.template = function(name,template){
  45. var d = +new Date,
  46. id = (function(name){
  47. for(var i in templates){
  48. if(templates[i].name == name){
  49. return i;
  50. }
  51. }
  52. return false;
  53. })(name);
  54. if(exists(template)){
  55. if(template === null){
  56. if(id!==false){
  57. templates.splice(id,1);
  58. }
  59. $.localStorage('templates',templates);
  60. console.log('Dropping template for: '+name);
  61. return '';
  62. }else{
  63. var o = {
  64. name: name,
  65. template: template,
  66. date: get('expire')+d
  67. }
  68. if(id===false){
  69. console.log('Storing new template for: '+name);
  70. templates.push(o);
  71. }else{
  72. console.log('Replacing old template for: '+name);
  73. templates[id] = o;
  74. }
  75. $.localStorage('templates',templates);
  76. }
  77. }else if(id!==false){
  78. console.log('Using cached template for: '+name);
  79. var template = templates[id].template;
  80. if(templates[id].date < d){
  81. delete templates[name];
  82. $.localStorage('templates',templates);
  83. }
  84. return template;
  85. }else{
  86. console.log('No cached template stored for: '+name);
  87. return '';
  88. }
  89. },
  90. apiCall = window.apiCall = function(data,callback){
  91. console.log('apiCall('+data.type+'-'+data.id+')');
  92. if(!flag('error')){
  93. loading(true);
  94. data.get = 'api';
  95. data.back = State.data.back;
  96. data.timestamp = +new Date;
  97. if(''!=template(data.type+'-'+data.id)){
  98. data.template = false;
  99. }
  100. $.get(location.href,data,function(d){
  101. if(exists(d['error'])){
  102. error(d);
  103. }else{
  104. d.state.title = d.state.title.capitalize();
  105. if(location.href.substr(location.href.lastIndexOf('/')+1) != d.state.url){
  106. console.log('Forced redirection to '+d.state.url);
  107. History.replaceState(d.state.data,d.state.title,d.state.url);
  108. getNewState();
  109. }
  110. document.title = d.state.title;
  111. }
  112. if(exists(callback)){
  113. console.log('Running apiCall callback');
  114. callback(d);
  115. }
  116. },'json');
  117. }
  118. },
  119. loadState = window.loadState = function(href,callback){
  120. console.log('loadState('+href+')');
  121. if(!flag('error')){
  122. loading(true);
  123. var data = {
  124. get:'state',
  125. timestamp: +new Date,
  126. back: location.href
  127. };
  128. ajax = $.ajax(href,{
  129. data: data,
  130. async: true,
  131. type: 'GET',
  132. success: function(d){
  133. if(exists(d['error'])){
  134. error(d);
  135. }else{
  136. d.state.title = d.state.title.capitalize();
  137. console.log('pushState: '+d.state.title+'['+href+']');
  138. History.pushState(d.state.data,d.state.title,href);
  139. getNewState();
  140. }
  141. if(exists(callback)){
  142. callback(d);
  143. }
  144. if(!exists(d['error'])){
  145. flag('handled',true);
  146. stateChange();
  147. flag('handled',false);
  148. }
  149. },
  150. error: function(x,t,e){
  151. error({
  152. error: '['+t+'] '+e
  153. });
  154. if(exists(callback)){
  155. callback({
  156. error: '['+t+'] '+e
  157. });
  158. }
  159. },
  160. dataType: 'json'
  161. });
  162. }
  163. },
  164. replaceState = window.replaceState = function(href,callback){
  165. console.log('apiState('+href+')');
  166. if(!flag('error')){
  167. loading(true);
  168. var data = {
  169. get:'state',
  170. timestamp: +new Date,
  171. back: State.data.back
  172. };
  173. $.ajax(href,{
  174. data: data,
  175. async: true,
  176. type: 'GET',
  177. success: function(d){
  178. if(exists(d['error'])){
  179. error(d);
  180. }else{
  181. d.state.title = d.state.title.capitalize();
  182. console.log('pushState: '+d.state.title+'['+href+']');
  183. History.replaceState(d.state.data,d.state.title,href);
  184. getNewState();
  185. }
  186. if(exists(callback)){
  187. callback(d);
  188. }
  189. console.log(d.state.title);
  190. if(!exists(d['error'])){
  191. flag('handled',true);
  192. stateChange();
  193. flag('handled',false);
  194. }
  195. },
  196. error: function(x,t,e){
  197. error({
  198. error: '['+t+'] '+e
  199. });
  200. if(exists(callback)){
  201. callback({
  202. error: '['+t+'] '+e
  203. });
  204. }
  205. },
  206. dataType: 'json'
  207. });
  208. }
  209. },
  210. error = function(e,callback){
  211. if(!flag('error')){
  212. flag('error',true);
  213. var msg = '['+State.url+']'+e.error;
  214. console.error(msg.trim()+"\n"+(exists(e.state)?JSON.stringify(e.state):''));
  215. alert(msg.trim(),'Error',callback);
  216. }
  217. },
  218. getNewState = function(){
  219. State = History.getState();
  220. console.log("State change: ["+State.title+"] "+JSON.stringify(State.data));
  221. if(!window.location.origin){
  222. window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
  223. }
  224. },
  225. stateChange = function(){
  226. getNewState();
  227. if(!equal(State.data,Old)){
  228. switch(State.data.type){
  229. case 'page':case 'user':case 'project':
  230. apiCall(State.data,function(d){
  231. if(!exists(d.error)){
  232. if(exists(d.context)){
  233. if(!exists(d.context.key)&&Key!==null){
  234. console.log('Context detected console.logout');
  235. setKey(null);
  236. }
  237. if(exists(d.template)){
  238. template(State.data.type+'-'+State.data.id,d.template);
  239. }else{
  240. d.template = template(State.data.type+'-'+State.data.id);
  241. }
  242. render.topbar(d.topbar.template,d.topbar.context);
  243. render.content(d.template,d.context);
  244. $(window).resize();
  245. loading(false);
  246. }else{
  247. console.error('No context given');
  248. back();
  249. }
  250. }else{
  251. error(d);
  252. back();
  253. }
  254. });
  255. break;
  256. case 'action':break;
  257. default:
  258. error({
  259. url: State.url,
  260. error: "Something went wrong!"
  261. });
  262. }
  263. Old = State.data;
  264. }else{
  265. console.log(State.data,Old);
  266. console.warn('Stopped double load of '+Old.type+'-'+Old.id);
  267. loading(false);
  268. }
  269. },
  270. equal = function(o1,o2){
  271. for(var i in o1){
  272. if(!exists(o2[i])||o2[i]!=o1[i]){
  273. return false;
  274. }
  275. }
  276. for(i in o2){
  277. if(!exists(o1[i])||o2[i]!=o1[i]){
  278. return false;
  279. }
  280. }
  281. return true;
  282. },
  283. render = window.render = {
  284. topbar: function(t,c){
  285. $('#topbar').html(Handlebars.compile(t)(c));
  286. render.links('#topbar');
  287. render.buttons('#topbar');
  288. render.menus('#topbar');
  289. render.time('#topbar');
  290. if(State.url == location.origin+'/page-index'){
  291. $('#topbar').find('.topbar-history').hide();
  292. }
  293. $('#topbar').addClass('overflow-hide');
  294. $(window).resize();
  295. },
  296. content: function(t,c){
  297. $(document).unbind('ready');
  298. $('#content').html(
  299. Handlebars.compile(t)(c)
  300. );
  301. render.links('#content');
  302. render.buttons('#content');
  303. render.accordions('#content');
  304. render.menus('#content');
  305. render.form('#content');
  306. render.time('#content');
  307. $(window).resize();
  308. },
  309. time: function(selector){
  310. $(selector).find('time.timeago').each(function(){
  311. var time = new Date($(this).text()*1000);
  312. $(this).replaceWith(
  313. $('<abbr>').attr({
  314. 'title': time.toISOString(),
  315. 'style': $(this).attr('style')
  316. }).addClass($(this).attr('class')).timeago()
  317. );
  318. });
  319. },
  320. accordions: function(selector){
  321. $(selector).find('.accordion').each(function(){
  322. var icons = {};
  323. if($(this).children('.icons').length == 1){
  324. icons = JSON.parse($(this).children('.icons').text());
  325. }
  326. $(this).children('.icons').remove();
  327. $(this).accordion({
  328. collapsible: true,
  329. icons: icons,
  330. active: false
  331. });
  332. });
  333. },
  334. buttons: function(selector){
  335. $(selector).find('.button').button();
  336. $(selector).find('input[type=submit]').button();
  337. $(selector).find('input[type=button]').button();
  338. $(selector).find('button').button();
  339. render.comment.buttons(selector);
  340. },
  341. comment: {
  342. buttons: function(selector){
  343. $(selector).find('.comment').each(function(){
  344. var context = JSON.parse($(this).text());
  345. $(this).text(context.text);
  346. $(this).button();
  347. $(this).click(function(){
  348. render.comment.dialog(context.id,context.type,context.title);
  349. });
  350. });
  351. },
  352. dialog: function(id,type,title){
  353. $('#comment').find('form').find('input[name=type]').val(type);
  354. $('#comment').find('form').find('input[name=id]').val(id);
  355. $('#comment').find('form').find('textarea[name=message]').val('');
  356. $('#comment').dialog({
  357. close: function(){
  358. $('#comment').find('form').find('input[name=type]').val('');
  359. $('#comment').find('form').find('input[name=id]').val('');
  360. },
  361. resizable: false,
  362. draggable: false,
  363. title: title,
  364. buttons: [
  365. {
  366. text: 'Ok',
  367. class: 'recommend-force',
  368. click: function(){
  369. var context = $(this).find('form').serializeObject();
  370. if(context.message!=''){
  371. console.log(context);
  372. // TODO - Handle sending to server and then refresh page
  373. $(this).dialog('close');
  374. }
  375. }
  376. },
  377. {
  378. text: 'Cancel',
  379. class: 'cancel-force',
  380. click: function(){
  381. $(this).dialog('close');
  382. }
  383. }
  384. ]
  385. });
  386. },
  387. },
  388. menus: function(selector){
  389. $(selector).find('.menu').css({
  390. 'list-style':'none'
  391. }).menu({
  392. icons:{
  393. submenu: "ui-icon-circle-triangle-e"
  394. }
  395. }).removeClass('ui-corner-all').addClass('ui-corner-bottom').parent().click(function(e){
  396. e.stopPropagation();
  397. });
  398. },
  399. form: function(selector){
  400. $(selector).find('#form').width('320px').children();
  401. render.inputs(selector);
  402. },
  403. inputs: function(selector){
  404. $(selector).find('input[type=text],input[type=password]').each(function(){
  405. var input = $(this),
  406. height = input.height()>=17?17:input.height();
  407. input.siblings('.input-clear').remove();
  408. input.off('focus').off('blur').after(
  409. $('<div>').css({
  410. position: 'absolute',
  411. right: $(window).width() - (input.outerWidth() + input.position().left)+2,
  412. top: input.position().top+2,
  413. 'background-image': 'url(img/headers/icons/clear.png)',
  414. 'background-position': 'center',
  415. 'background-size': height+'px '+height+'px',
  416. 'background-repeat': 'no-repeat',
  417. width: input.height(),
  418. height: input.height(),
  419. cursor: 'pointer'
  420. }).hide().addClass('input-clear').mousedown(function(){
  421. input.val('');
  422. })
  423. );
  424. input.focus(function(){
  425. input.next().show();
  426. }).blur(function(e){
  427. input.next().hide();
  428. });
  429. });
  430. $(selector).find('input[type=text],input[type=password],textarea').each(function(){
  431. var input = $(this);
  432. if(input.hasClass('fill-width')){
  433. input.css('width','calc(100% - '+(input.outerWidth()-input.width())+'px)');
  434. }
  435. });
  436. },
  437. dialog: function(selector,title){
  438. $(selector).dialog({
  439. close: function(){
  440. flag('error',false);
  441. var fn = $(this).data('callback');
  442. if(exists(fn)){
  443. fn();
  444. }
  445. loading(false);
  446. },
  447. resizable: false,
  448. draggable: false,
  449. title: title,
  450. buttons: [
  451. {
  452. text: 'Ok',
  453. class: 'recommend-force',
  454. click: function(){
  455. $(this).dialog('close');
  456. }
  457. }
  458. ]
  459. });
  460. },
  461. links: function(selector){
  462. $(selector).find('a').each(function(){
  463. var href = this.href;
  464. if(href.indexOf('#')!=-1&&(href.indexOf(location.origin)!=-1||href.indexOf('#')==0)){
  465. href = href.substr(href.indexOf('#')+1);
  466. $(this).click(function(e){
  467. try{
  468. if(($(this).hasClass('topbar-home') || $(this).hasClass('topbar-back'))&&$(window).width()<767){
  469. $('#topbar').children('div.topbar-right,div.topbar-left').toggle();
  470. $('#topbar').toggleClass('overflow-hide');
  471. $(window).resize();
  472. }else if($(this).hasClass('topbar-history')){
  473. back();
  474. }else{
  475. loadState(href);
  476. }
  477. }catch(error){
  478. console.error(error);
  479. }
  480. e.preventDefault();
  481. return false;
  482. });
  483. }
  484. });
  485. }
  486. },
  487. back = window.back = function(){
  488. if(exists(State.data.back)){
  489. if(!History.back()){
  490. loadState(State.data.back);
  491. }
  492. }else if(State.data.type != 'page' || State.data.id != 'index'){
  493. loadState('page-index');
  494. }else{
  495. location.reload();
  496. }
  497. },
  498. alert = function(text,title,callback){
  499. $('#dialog').text(text).data('callback',callback);
  500. render.dialog('#dialog',title,callback);
  501. },
  502. loading = function(state){
  503. state = exists(state)?state:false;
  504. console.log('loading state '+state);
  505. if(!flag('error') && !state){
  506. $('#loading').hide();
  507. }else if(state){
  508. $('#loading').show();
  509. }
  510. };
  511. if(exists($.cookie('key'))){
  512. setKey($.cookie('key'));
  513. }else{
  514. setKey(null);
  515. }
  516. $(document).ready(function(){
  517. $.ajaxSetup({
  518. async: false,
  519. cache: false,
  520. timeout: 30000 // 30 seconds
  521. });
  522. $(document).ajaxError(function(event, request, settings) {
  523. error({error:'Request timed out'});
  524. });
  525. if(!exists($.support.touch)){
  526. $.support.touch = 'ontouchstart' in window || 'onmsgesturechange' in window;
  527. }
  528. $('#content').niceScroll({
  529. cursorwidth: 10,
  530. nativeparentscrolling: false,
  531. preservenativescrolling: false
  532. });
  533. $('#content,#topbar').click(function(){
  534. $('.menu').hide();
  535. });
  536. document.addEventListener('touchmove',function(e){
  537. e.preventDefault();
  538. });
  539. $(window).resize(function(){
  540. if($(window).width()>767){
  541. $('#topbar div.topbar-right, #topbar div.topbar-left').css({
  542. 'display': ''
  543. });
  544. }
  545. $('#content').height($('body').height()-$('#topbar').height());
  546. $('#content').getNiceScroll().resize();
  547. render.inputs('#content');
  548. render.inputs('#topbar');
  549. });
  550. $.get(location.href,{
  551. get: 'settings',
  552. timestamp: +new Date,
  553. back: false
  554. },function(d){
  555. if(!exists(d.error)){
  556. settings = d.settings;
  557. if(d.version != $.localStorage('version')){
  558. $.localStorage('version',d.version);
  559. $.localStorage('templates',null);
  560. templates = [];
  561. }else{
  562. templates = $.localStorage('templates');
  563. if(templates === null){
  564. templates = [];
  565. }
  566. }
  567. replaceState(location.href);
  568. }else{
  569. error(d.error);
  570. }
  571. },'json');
  572. $(window).on('statechange',function(){
  573. if(!flag('handled')){
  574. console.log('unhandled state change event');
  575. stateChange();
  576. }
  577. });
  578. var getState = History.getState;
  579. History.getState = function(flag){
  580. if(exists(flag)){
  581. return State;
  582. }else{
  583. return getState.call(History);
  584. }
  585. };
  586. });
  587. shortcut.add('f12',function(){
  588. if(!flag('firebug-lite')){
  589. $('head').append(
  590. $('<script>').attr({
  591. 'type': 'text/javascript',
  592. 'src': 'https://getfirebug.com/firebug-lite.js#startOpened',
  593. 'id': 'FirebugLite'
  594. })
  595. );
  596. $('<image>').attr('src','https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png');
  597. flag('firebug-lite',true);
  598. }
  599. });
  600. shortcut.add('Ctrl+f12',function(){
  601. if(!flag('manifesto')){
  602. if(window.applicationCache){
  603. if(window.applicationCache.status==window.applicationCache.UNCACHED){
  604. $('head').append(
  605. $('<script>').attr({
  606. 'type': 'text/javascript',
  607. 'src': 'http://manifesto.ericdelabar.com/manifesto.js?x="+(Math.random())'
  608. })
  609. );
  610. (function wait(){
  611. if($('#cacheStatus').length == 0){
  612. setTimeout(wait,10);
  613. }else{
  614. $('#cacheStatus').niceScroll();
  615. }
  616. })();
  617. }else{
  618. alert("Manifest file is valid.");
  619. }
  620. }else{
  621. alert("This browser does not support HTML5 Offline Application Cache.");
  622. }
  623. flag('manifesto',true);
  624. }
  625. });
  626. shortcut.add('Shift+f12',function(){
  627. templates = [];
  628. $.localStorage('templates',null);
  629. console.log('Templates cleared.');
  630. });
  631. $.fn.serializeObject = function(){
  632. var o = {},
  633. a = this.serializeArray();
  634. $.each(a,function(){
  635. if(o[this.name] !== undefined){
  636. if(!o[this.name].push){
  637. o[this.name] = [o[this.name]];
  638. }
  639. o[this.name].push(this.value || '');
  640. }else{
  641. o[this.name] = this.value || '';
  642. }
  643. });
  644. return o;
  645. };
  646. String.prototype.capitalize = function(lower) {
  647. return (lower?this.toLowerCase():this).replace(/(?:^|\s)\S/g, function(a){
  648. return a.toUpperCase();
  649. });
  650. };
  651. })(jQuery,History,console);