diff --git a/.c9revisions/css/styles.css.c9save b/.c9revisions/css/styles.css.c9save new file mode 100644 index 0000000..c561469 --- /dev/null +++ b/.c9revisions/css/styles.css.c9save @@ -0,0 +1,2 @@ +{"ts":1354219251604,"silentsave":true,"restoring":false,"patch":[[]],"length":0} +{"contributors":[],"silentsave":false,"ts":1354219251940,"patch":[[{"diffs":[[1,"[data-role=\"content\"] h1,\r\n[data-role=\"content\"] h2,\r\n[data-role=\"content\"] h3,\r\n[data-role=\"content\"] h4 {\r\n margin-top: 0;\r\n}\r\n\r\ndt {\r\n\tfont-weight: bold;\r\n\tbackground-color: #e6e6e6;\r\n}\r\n\r\ndd {\r\n\tmin-height: 15px;\r\n\tmargin-left: 20px;\r\n}\r\n\r\naddress {\r\n\tfont-style: normal;\r\n}\r\n\r\n[data-role=\"footer\"] {\r\n\tpadding: 5px;\r\n}\r\n\r\n.ui-collapsible-content {\r\n\tpadding: 0px 5px;\r\n}\r\n"]],"start1":0,"start2":0,"length1":0,"length2":380}]],"length":380,"saved":false} diff --git a/.c9revisions/index.html.c9save b/.c9revisions/index.html.c9save new file mode 100644 index 0000000..9ee1faa --- /dev/null +++ b/.c9revisions/index.html.c9save @@ -0,0 +1,2 @@ +{"ts":1354219139108,"silentsave":true,"restoring":false,"patch":[[]],"length":0} +{"contributors":[],"silentsave":false,"ts":1354219139823,"patch":[[{"diffs":[[1,"\r\n\r\n\r\n L&I Property History\r\n\t\r\n\t\r\n\t\r\n\t\r\n\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\r\n\r\n\r\n\t
\r\n\t\t
\r\n\t\t\t

L&I Property History

\r\n\t\t\t
\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t
\r\n\t\t
\r\n\t
\r\n\r\n\t
\r\n\t\t
\r\n\t\t
\r\n\t\t\tSearch\r\n\t\t
\r\n\t
\r\n\t\r\n\t
\r\n\t\t
\r\n\t\t
\r\n\t\t\tSearch Results\r\n\t\t
\r\n\t
\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\t\r\n\r\n"]],"start1":0,"start2":0,"length1":0,"length2":11827}]],"length":11827,"saved":false} diff --git a/.c9revisions/js/jquery.mobile.router.min.js.c9save b/.c9revisions/js/jquery.mobile.router.min.js.c9save new file mode 100644 index 0000000..f922eae --- /dev/null +++ b/.c9revisions/js/jquery.mobile.router.min.js.c9save @@ -0,0 +1,2 @@ +{"ts":1354219178507,"silentsave":true,"restoring":false,"patch":[[]],"length":0} +{"contributors":[],"silentsave":false,"ts":1354219179414,"patch":[[{"diffs":[[1,"/*!\r\n * jQueryMobile-router v0.93\r\n * http://github.com/azicchetti/jquerymobile-router\r\n *\r\n * Copyright 2011 (c) Andrea Zicchetti\r\n * Dual licensed under the MIT or GPL Version 2 licenses.\r\n * http://github.com/azicchetti/jquerymobile-router/blob/master/MIT-LICENSE.txt\r\n * http://github.com/azicchetti/jquerymobile-router/blob/master/GPL-LICENSE.txt\r\n */\r\n(function(a,b){if(typeof define===\"function\"&&define.amd){define([\"jquery\"],b)}else{b(jQuery)}}(this,function(a){a(document).bind(\"mobileinit\",function(){var e=a.extend({fixFirstPageDataUrl:false,firstPageDataUrl:\"index.html\",ajaxApp:false,firstMatchOnly:false},a.mobile.jqmRouter||{});var c=true;function b(h){if(c){console.log(h)}}var f=null,g=null,d=false;a(document).bind(\"pagebeforechange\",function(l,k){var i=(typeof k.toPage===\"string\")?k.toPage:k.toPage.jqmData(\"url\")||\"\";if(k.options.hasOwnProperty(\"_jqmrouter_handled\")){return}k.options._jqmrouter_handled=true;if(k.options.data&&(k.options.type+\"\").toLowerCase()==\"get\"){i+=\"?\"+k.options.data}var h=a.mobile.path.parseUrl(i);f=g;g=h;if(h.hash.indexOf(\"?\")!==-1||(h.hash.length>0&&f!==null&&f.hash.indexOf(h.hash+\"?\")!==-1)){var j=h.hash.replace(/\\?.*$/,\"\");k.options.dataUrl=h.href;if(a.mobile.activePage&&j.replace(/^#/,\"\")==a.mobile.activePage.jqmData(\"url\")){k.options.allowSamePageTransition=true&&!d;a.mobile.changePage(a(j),k.options)}else{a.mobile.changePage(a(j),k.options)}l.preventDefault();a.mobile.urlHistory.ignoreNextHashChange=true}d=false;if(window.location.hash.indexOf(\"&ui-state=dialog\")!=-1){d=true}});if(e.fixFirstPageDataUrl){a(document).ready(function(){if(!window.location.pathname.match(\"/$\")){return}var i=a(\":jqmData(role='page')\").first();var j=i.jqmData(\"url\"),h=window.location.pathname+e.firstPageDataUrl+window.location.search+window.location.hash;if(j!=h){i.attr(\"data-url\",h).jqmData(\"url\",h)}})}a.mobile.Router=function(l,j,k){this.routes={pagebeforecreate:null,pagecreate:null,pagebeforeshow:null,pageshow:null,pagebeforehide:null,pagehide:null,pageinit:null,pageremove:null,pagebeforechange:null,pagebeforeload:null,pageload:null,popupbeforeposition:null,popupafteropen:null,popupafterclose:null};this.evtLookup={bC:\"pagebeforechange\",bl:\"pagebeforeload\",l:\"pageload\",bc:\"pagebeforecreate\",c:\"pagecreate\",bs:\"pagebeforeshow\",s:\"pageshow\",bh:\"pagebeforehide\",h:\"pagehide\",i:\"pageinit\",rm:\"pageremove\",pbp:\"popupbeforeposition\",pao:\"popupafteropen\",pac:\"popupafterclose\"};this.routesRex={};this.conf=a.extend({},e,k||{});this.defaultHandlerEvents={};if(this.conf.defaultHandlerEvents){var h=this.conf.defaultHandlerEvents.split(\",\");for(var m=0;m0){this._eventData={events:i.join(\" \"),selectors:\":jqmData(role='page'),:jqmData(role='dialog')\",handler:l};a(document).delegate(this._eventData.selectors,this._eventData.events,this._eventData.handler)}if(n.length>0){this._docEventData={events:n.join(\" \"),handler:l};a(document).bind(this._docEventData.events,this._docEventData.handler)}},_processRoutes:function(m,q,n){var o=this,p,i,l,h=0;if(m.type in {pagebeforehide:true,pagehide:true,pageremove:true}){p=f}else{p=g}do{if(!p){if(n){l=a(n);p=l.jqmData(\"url\");if(p){if(l.attr(\"id\")==p){p=\"#\"+p}p=a.mobile.path.parseUrl(p)}}}else{if(!this.documentEvts[m.type]&&n&&!a(n).jqmData(\"url\")){return}}if(!p){return}i=(!this.conf.ajaxApp?p.hash:p.pathname+p.search+p.hash);if(i.length==0){p=\"\"}h++}while(i.length==0&&h<=1);var k=false;a.each(this.routes[m.type],function(r,t){var s,v;if((s=i.match(o.routesRex[r]))){if(typeof(t)==\"function\"){v=t}else{if(typeof(o.userHandlers[t])==\"function\"){v=o.userHandlers[t]}}if(v){try{v.apply(o.userHandlers,[m.type,s,q,n,m]);k=true}catch(u){b(u)}}}if(k&&o.conf.firstMatchOnly){return false}});if(!k&&this.conf.defaultHandler&&this.defaultHandlerEvents[m.type]){if(typeof(this.conf.defaultHandler)==\"function\"){try{this.conf.defaultHandler.apply(o.userHandlers,[m.type,q,n,m])}catch(j){b(j)}}}},_detachEvents:function(){if(this._eventData){a(document).undelegate(this._eventData.selectors,this._eventData.events,this._eventData.handler)}if(this._docEventData){a(document).unbind(this._docEventData.events,this._docEventData.handler)}},destroy:function(){this._detachEvents();this.routes=this.routesRex=null},getParams:function(h){if(!h){return null}var k={},i;var j=h.slice(h.indexOf(\"?\")+1).split(\"&\");a.each(j,function(m,l){i=l.split(\"=\");i[0]=decodeURIComponent(i[0]);if(k[i[0]]){if(!(k[i[0]] instanceof Array)){k[i[0]]=[k[i[0]]]}k[i[0]].push(decodeURIComponent(i[1]))}else{k[i[0]]=decodeURIComponent(i[1])}});if(a.isEmptyObject(k)){return null}return k}})});return{}}));"]],"start1":0,"start2":0,"length1":0,"length2":5678}]],"length":5678,"saved":false} diff --git a/.c9revisions/js/phillyapi.js.c9save b/.c9revisions/js/phillyapi.js.c9save new file mode 100644 index 0000000..d56c1f8 --- /dev/null +++ b/.c9revisions/js/phillyapi.js.c9save @@ -0,0 +1,2 @@ +{"ts":1354219191586,"silentsave":true,"restoring":false,"patch":[[]],"length":0} +{"contributors":[],"silentsave":false,"ts":1354219193149,"patch":[[{"diffs":[[1,"var phillyapi = phillyapi || {};\r\nphillyapi = {\r\n options: {\r\n\t\tphillyapi: {\r\n\t\t\tbase: \"http://services.phila.gov/PhillyApi/Data/\"\r\n\t\t\t,summary: \"HelperService.svc/GetLocationHistory?$format=json&AddressKey=\"\r\n\t\t\t,permit: \"Service.svc/permits('%id%')?$format=json\"\r\n\t\t\t,license: \"Service.svc/licenses('%id%')?$format=json\"\r\n\t\t\t,_case: \"Service.svc/violationdetails?$filter=case_number%20eq%20'%id%'&$expand=cases&$format=json\"\r\n\t\t\t,zoningboardappeal: \"Service.svc/zoningboardappeals?$filter=appeal_number%20eq%20'%id%'&$format=json\"\r\n\t\t\t,zbahearingdecisions: \"Service.svc/zbahearingdecisions?$filter=appeal_id%20eq%20%id%&$format=json\"\r\n\t\t\t,zbacourtdetails: \"Service.svc/zbacourtdetails?$filter=appeal_id%20eq%20%id%&$format=json\"\r\n\t\t\t,buildingboardappeal: \"Service.svc/buildingboardappeals?$filter=appeal_number%20eq%20'%id%'&$format=json\"\r\n\t\t\t,bbshearingdecisions: \"Service.svc/bbshearingdecisions?$filter=appeal_id%20eq%20%id%&$format=json\"\r\n\t\t\t,bbscourtdetails: \"Service.svc/bbscourtdetails?$filter=appeal_id%20eq%20%id%&$format=json\"\r\n\t\t\t,lireviewboardappeal: \"Service.svc/lireviewboardappeals?$filter=appeal_number%20eq%20'%id%'&$format=json\"\r\n\t\t\t,lirbhearingdecisions: \"Service.svc/lirbhearingdecisions?$filter=appeal_id%20eq%20%id%&$format=json\"\r\n\t\t\t,lirbcourtdetails: \"Service.svc/lirbcourtdetails?$filter=appeal_id%20eq%20%id%&$format=json\"\r\n\t\t\t,timeout: 20000\r\n\t\t}\r\n\t\t,ulrs311: {\r\n\t\t\tbase: \"http://services.phila.gov/ULRS311/Data/\"\r\n\t\t\t,addressKey: \"LIAddressKey/\"\r\n\t\t\t,timeout: 20000\r\n\t\t}\r\n\t}\r\n\r\n\t,getAddressKey: function(input, successCallback, errorCallback) {\r\n\t\tvar url = phillyapi.options.ulrs311.base + phillyapi.options.ulrs311.addressKey + encodeURIComponent(input);\r\n\t\t$.ajax({\r\n\t\t\turl: url\r\n\t\t\t,dataType: \"jsonp\"\r\n\t\t\t,contentType: \"application/json; charset=utf-8\"\r\n\t\t\t,async: false\r\n\t\t\t,cache: true\r\n\t\t\t,crossDomain: true\r\n\t\t\t,timeout: phillyapi.options.ulrs311.timeout\r\n\t\t\t,type: \"GET\"\r\n\t\t\t,xhrFields: { withCredentials: false }\r\n\t\t\t,error: errorCallback\r\n\t\t\t,success: function(data) {\r\n\t\t\t\tif(\"TopicID\" in data) {\r\n\t\t\t\t\tvar addressKey = data.TopicID;\r\n \t\t\t\t// Clean up the address. Default format is something like \" 01234 MARKET ST\" - needs to be \"1234 MARKET ST\" - Anyone have a better regex for it?\r\n\t\t\t\t\tvar address = data.AddressRef ? $.trim(data.AddressRef.replace(/ +(?= )/g,\"\")).replace(/^0+/, \"\") : null;\r\n\t\t\t\t\tsuccessCallback(addressKey, address);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\t,getSummary: function(addressKey, successCallback, errorCallback, sorted) {\r\n\t\tvar url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi.summary + addressKey;\r\n\t\tphillyapi.fetch(url, function(data) {\r\n\t\t\tsuccessCallback(sorted ? phillyapi.sortSummary(data) : data);\r\n\t\t}, errorCallback);\r\n\t}\r\n\t\r\n\t,getPermit: function(id, successCallback, errorCallback) {\r\n\t\tvar url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi.permit.replace(\"%id%\", id);\r\n\t\tphillyapi.fetch(url, successCallback, errorCallback);\r\n\t}\r\n\t\r\n\t,getLicense: function(id, successCallback, errorCallback) {\r\n\t\tvar url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi.license.replace(\"%id%\", id);\r\n\t\tphillyapi.fetch(url, successCallback, errorCallback);\r\n\t}\r\n\t\r\n\t,getCase: function(id, successCallback, errorCallback, sorted) {\r\n\t\tvar url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi._case.replace(\"%id%\", id);\r\n\t\tphillyapi.fetch(url, function(data) {\r\n\t\t\tsuccessCallback(sorted ? phillyapi.sortCase(data) : data);\r\n\t\t}, errorCallback);\r\n\t}\r\n\t\r\n\t/*\r\n\t\tDue to a known issue in the API, we have to do 3 calls here instead of 1. Also, since the Summary gives us the appeal\r\n\t\tnumber rather than appeal id, we have to wait for the first call to finish in order to get the appeal id for the second two\r\n\t*/\r\n\t,getAppeal: function(type, id, successCallback, errorCallback) {\r\n\t\tvar sortedData = {}, urlKeys = [];\r\n\t\tswitch(type) {\r\n\t\t\tcase \"zoningboardappeals\":\r\n\t\t\t\turlKeys = [\"zoningboardappeal\", \"zbahearingdecisions\", \"zbacourtdetails\"];\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"buildingboardappeals\":\r\n\t\t\t\turlKeys = [\"buildingboardappeal\", \"bbshearingdecisions\", \"bbscourtdetails\"];\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"lireviewboardappeals\":\r\n\t\t\t\turlKeys = [\"lireviewboardappeal\", \"lirbhearingdecisions\", \"lirbcourtdetails\"];\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t\t\r\n\t\t// Appeal Details\r\n\t\tvar url1 = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi[urlKeys[0]].replace(\"%id%\", id);\r\n\t\tphillyapi.fetch(url1, function(data) {\r\n\t\t\tsortedData.d = data.d.results[0];\r\n\t\t\tvar requestsPending = 0;\r\n\t\t\t\r\n\t\t\t// Hearing Decisions\r\n\t\t\trequestsPending++;\r\n\t\t\tvar url2 = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi[urlKeys[1]].replace(\"%id%\", data.d.results[0].appeal_id);\r\n\t\t\tphillyapi.fetch(url2, function(data) {\r\n\t\t\t\tsortedData.d.hearingdecisions = data.d.results;\r\n\t\t\t\trequestsPending--;\r\n\t\t\t\tif(requestsPending < 1) successCallback(sortedData);\r\n\t\t\t}, errorCallback);\r\n\t\t\t// Court History\r\n\t\t\trequestsPending++;\r\n\t\t\tvar url3 = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi[urlKeys[2]].replace(\"%id%\", data.d.results[0].appeal_id);\r\n\t\t\tphillyapi.fetch(url3, function(data) {\r\n\t\t\t\tsortedData.d.courtdetails = data.d.results;\r\n\t\t\t\trequestsPending--;\r\n\t\t\t\tif(requestsPending < 1) successCallback(sortedData);\r\n\t\t\t}, errorCallback);\r\n\t\t}, errorCallback);\r\n\t}\r\n\r\n\t,fetch: function(url, successCallback, errorCallback) {\r\n\t\t$.ajax({\r\n\t\t\turl: url\r\n\t\t\t,dataType: \"jsonp\"\r\n\t\t\t,cache: true\r\n\t\t\t,timeout: phillyapi.options.phillyapi.timeout\r\n\t\t\t,jsonp: \"$callback\"\r\n\t\t\t,success: successCallback\r\n\t\t\t,error: errorCallback\r\n\t\t});\r\n\t}\r\n\t\r\n\t// Sort summary data into categories\r\n\t,sortSummary: function(data) {\r\n\t\tvar sortedData = {};\r\n\t\tfor(var i = 0; i < data.length; i++) {\r\n\t\t\tswitch(data[i].category) {\r\n\t\t\t\tcase \"Violation\":\r\n\t\t\t\t\tif(sortedData.cases === undefined) {\r\n\t\t\t\t\t\tsortedData.cases = {};\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif(sortedData.cases[data[i].id] === undefined) {\r\n\t\t\t\t\t\tsortedData.cases[data[i].id] = {\r\n\t\t\t\t\t\t\t\"case_number\": data[i].id\r\n\t\t\t\t\t\t\t,\"violations\": []\r\n\t\t\t\t\t\t\t,\"entity\": data[i].entity\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tsortedData.cases[data[i].id].violations.push(data[i].type);\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"Permit\":\r\n\t\t\t\t\tif(sortedData.permits === undefined) {\r\n\t\t\t\t\t\tsortedData.permits = [];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tsortedData.permits.push({\r\n\t\t\t\t\t\t\"permit_number\": data[i].id\r\n\t\t\t\t\t\t,\"permit_type_name\": data[i].type\r\n\t\t\t\t\t\t,\"entity\": data[i].entity\r\n\t\t\t\t\t});\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"Business\":\r\n\t\t\t\t\tif(sortedData.licenses === undefined) {\r\n\t\t\t\t\t\tsortedData.licenses = [];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tsortedData.licenses.push({\r\n\t\t\t\t\t\t\"license_number\": data[i].id\r\n\t\t\t\t\t\t,\"license_type_name\": data[i].type\r\n\t\t\t\t\t\t,\"entity\": data[i].entity\r\n\t\t\t\t\t});\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"Appeal\":\r\n\t\t\t\t\tif(sortedData.appeals === undefined) {\r\n\t\t\t\t\t\tsortedData.appeals = [];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tsortedData.appeals.push({\r\n\t\t\t\t\t\t\"appeal_number\": data[i].id\r\n\t\t\t\t\t\t,\"appeal_type_name\": data[i].type\r\n\t\t\t\t\t\t,\"entity\": data[i].entity\r\n\t\t\t\t\t});\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tif(sortedData.other === undefined) {\r\n\t\t\t\t\t\tsortedData.other = [];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tsortedData.other.push({\r\n\t\t\t\t\t\t\"number\": data[i].id\r\n\t\t\t\t\t\t,\"type_name\": data[i].type\r\n\t\t\t\t\t});\r\n\t\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif(sortedData.cases !== undefined) sortedData.cases = phillyapi.objToArray(sortedData.cases);\r\n\t\treturn sortedData;\r\n\t}\r\n\t\r\n\t/*\r\n\t\tThis is a work-around for a known issue in the API. Ideally we'd do a call to the case and expand the violationdetails,\r\n\t\tbut since that gives an error, we do a call to the violation details of the case and expand the case.\r\n\t\tThat gives the case details multiple times, but at least it's only one ajax call. This function shuffles the data\r\n\t\taround to be the case with a violationdetails array.\r\n\t*/\r\n\t,sortCase: function(data) {\r\n\t\tvar sortedData = {};\r\n\t\tsortedData.d = data.d.results[0].cases;\r\n\t\tfor(var i = 0; i < data.d.results.length; i++) {\r\n\t\t\tif(data.d.results[i].cases !== undefined) delete data.d.results[i].cases;\r\n\t\t}\r\n\t\tsortedData.d.violationdetails = data.d.results;\r\n\t\treturn sortedData;\r\n\t}\r\n\t\r\n\t// Stock function\r\n\t,objToArray: function(obj) {\r\n\t\tvar arr = []\r\n\t\tfor(var key in obj) {\r\n\t\t\tarr.push(obj[key]);\r\n\t\t}\r\n\t\treturn arr;\r\n\t}\r\n};"]],"start1":0,"start2":0,"length1":0,"length2":8149}]],"length":8149,"saved":false} diff --git a/.c9revisions/js/script.js.c9save b/.c9revisions/js/script.js.c9save new file mode 100644 index 0000000..14fcde8 --- /dev/null +++ b/.c9revisions/js/script.js.c9save @@ -0,0 +1,2 @@ +{"ts":1354219214887,"silentsave":true,"restoring":false,"patch":[[]],"length":0} +{"contributors":[],"silentsave":false,"ts":1354219215426,"patch":[[{"diffs":[[1,"var DEBUG = false;\r\nvar cache = {summary: null, details: null};\r\n\r\nvar controller = {\r\n search: function() {\r\n\t\t$(\"#search form input\").eq(0).focus();\r\n\t}\r\n\t,summary: function(eventType, matchObj, ui, page, evt) {\r\n\t\tvar input = decodeURIComponent(matchObj[1].replace(/\\+/g, \"%20\")).replace(/^\\s+|\\s+$/g, \"\");\r\n\t\tif(cache.summary != matchObj[0]) {\r\n\t\t\t$(\"[data-role=\\\"content\\\"]\", page).empty();\r\n\t\t\tsetLoading(true);\r\n\t\t\tphillyapi.getAddressKey(input, function(addressKey, address) {\r\n\t\t\t\tif(addressKey) {\r\n\t\t\t\t\tphillyapi.getSummary(addressKey, function(data) {\r\n\t\t\t\t\t\tif(DEBUG) console.log(data);\r\n\t\t\t\t\t\tif(_.isEmpty(data)) {\r\n\t\t\t\t\t\t\tcontroller.error(\"No history found for this address\", page);\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t$(\"[data-role=\\\"content\\\"]\", page).html(_.template($(\"#template-summary\").html(), {address: address, data: data}));\r\n\t\t\t\t\t\t\t$(\"[data-role=\\\"listview\\\"]\", page).listview();\r\n\t\t\t\t\t\t\tcache.summary = matchObj[0];\r\n\t\t\t\t\t\t\tsetLoading(false);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}, function(xhr, status, error) {\r\n\t\t\t\t\t\tcontroller.error(\"There was an issue fetching the summary data from the server\", page, xhr);\r\n\t\t\t\t\t}, true);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tcontroller.error(\"The address entered is not a valid L&I address\", page);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\t,permit: function(eventType, matchObj, ui, page, evt) {\r\n\t\tif(cache.details != matchObj[0]) {\r\n\t\t\t$(\"[data-role=\\\"content\\\"]\", page).empty();\r\n\t\t\tsetLoading(true);\r\n\t\t\tphillyapi.getPermit(matchObj[1], function(data) {\r\n\t\t\t\tif(DEBUG) console.log(data);\r\n\t\t\t\t$(\"[data-role=\\\"content\\\"]\", page).html(_.template($(\"#template-details-permit\").html(), {data: data.d}));\r\n\t\t\t\tcache.details = matchObj[0];\r\n\t\t\t\tsetLoading(false);\r\n\t\t\t}, function(xhr, status, error) {\r\n\t\t\t\tcontroller.error(\"There was an issue fetching the permit data from the server\", page, xhr);\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\t,license: function(eventType, matchObj, ui, page, evt) {\r\n\t\tif(cache.details != matchObj[0]) {\r\n\t\t\t$(\"[data-role=\\\"content\\\"]\", page).empty();\r\n\t\t\tsetLoading(true);\r\n\t\t\tphillyapi.getLicense(matchObj[1], function(data) {\r\n\t\t\t\tif(DEBUG) console.log(data);\r\n\t\t\t\t$(\"[data-role=\\\"content\\\"]\", page).html(_.template($(\"#template-details-license\").html(), {data: data.d}));\r\n\t\t\t\tcache.details = matchObj[0];\r\n\t\t\t\tsetLoading(false);\r\n\t\t\t}, function(xhr, status, error) {\r\n\t\t\t\tcontroller.error(\"There was an issue fetching the license data from the server\", page, xhr);\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\t,_case: function(eventType, matchObj, ui, page, evt) {\r\n\t\tif(cache.details != matchObj[0]) {\r\n\t\t\t$(\"[data-role=\\\"content\\\"]\", page).empty();\r\n\t\t\tsetLoading(true);\r\n\t\t\tphillyapi.getCase(matchObj[1], function(data) {\r\n\t\t\t\tif(DEBUG) console.log(data);\r\n\t\t\t\t$(\"[data-role=\\\"content\\\"]\", page).html(_.template($(\"#template-details-case\").html(), {data: data.d}));\r\n\t\t\t\t$(\"[data-role=\\\"collapsible-set\\\"]\", page).collapsibleset();\r\n\t\t\t\tcache.details = matchObj[0];\r\n\t\t\t\tsetLoading(false);\r\n\t\t\t}, function(xhr, status, error) {\r\n\t\t\t\tcontroller.error(\"There was an issue fetching the case data from the server\", page, xhr);\r\n\t\t\t}, true);\r\n\t\t}\r\n\t}\r\n\t,appeal: function(eventType, matchObj, ui, page, evt) {\r\n\t\tif(cache.details != matchObj[0]) {\r\n\t\t\t$(\"[data-role=\\\"content\\\"]\", page).empty();\r\n\t\t\tsetLoading(true);\r\n\t\t\tphillyapi.getAppeal(matchObj[1] + \"appeals\", matchObj[2], function(data) {\r\n\t\t\t\tif(DEBUG) console.log(data);\r\n\t\t\t\t$(\"[data-role=\\\"content\\\"]\", page).html(_.template($(\"#template-details-appeal\").html(), {data: data.d}));\r\n\t\t\t\t$(\"[data-role=\\\"collapsible-set\\\"]\", page).collapsibleset();\r\n\t\t\t\tcache.details = matchObj[0];\r\n\t\t\t\tsetLoading(false);\r\n\t\t\t}, function(xhr, status, error) {\r\n\t\t\t\tcontroller.error(\"There was an issue fetching the appeal data from the server\", page, xhr);\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\t,error: function(errorMsg, page, xhr) {\r\n\t\t$(\"[data-role=\\\"content\\\"]\", page).html(_.template($(\"#template-details-error\").html(), {errorMsg: errorMsg, xhr: xhr}));\r\n\t\tsetLoading(false);\r\n\t}\r\n};\r\nnew $.mobile.Router({\r\n\t\"#search\": { handler: \"search\", events: \"s\" }\r\n\t,\"#summary\\\\?address=(.*)\": { handler: \"summary\", events: \"bs\" }\r\n\t,\"#details\\\\?entity=permits&eid=(\\\\d*)\": { handler: \"permit\", events: \"bs\" }\r\n\t,\"#details\\\\?entity=licenses&eid=(\\\\d*)\": { handler: \"license\", events: \"bs\" }\r\n\t,\"#details\\\\?entity=violationdetails&eid=(\\\\d*)\": { handler: \"_case\", events: \"bs\" }\r\n\t,\"#details\\\\?entity=(.*)appeals&eid=(\\\\d*)\": { handler: \"appeal\", events: \"bs\" }\r\n}, controller);\r\n\r\n$(document).ready(function() {\r\n // Ensure user has input an address before pressing search\r\n\t$(\"#search form\").submit(function(e) {\r\n\t\tvar inputNode = $(\"input[name=\\\"address\\\"]\", $(this));\r\n\t\tif( ! $.trim(inputNode.val())) {\r\n\t\t\tinputNode.focus();\r\n\t\t\treturn false;\r\n\t\t}\r\n\t});\r\n});\r\n\r\n// Necessary because v1.1.0 of jQuery Mobile doesn't seem to let you show the loading message during pagebeforeshow\r\nfunction setLoading(on) {\r\n\tif(on) $(\"body\").addClass(\"ui-loading\");\r\n\telse $(\"body\").removeClass(\"ui-loading\");\r\n}\r\n\r\nfunction display_date(input, show_time){\r\n\tvar str;\r\n\tif(input) {\r\n\t\tvar UNIX_timestamp = input.replace(/\\D/g, \"\") / 1000;\r\n\t\tvar a = new Date(UNIX_timestamp*1000);\r\n\t\tvar months = [\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"];\r\n\t\tvar year = a.getFullYear();\r\n\t\tvar month = a.getMonth()+1;\r\n\t\tvar date = a.getDate();\r\n\t\tstr = month+\"/\"+date+\"/\"+year;\r\n\t\tif(show_time) {\r\n\t\t\tvar hour = a.getHours();\r\n\t\t\tvar min = (\"0\" + a.getMinutes()).slice(-2);\r\n\t\t\tvar sec = (\"0\" + a.getSeconds()).slice(-2);\r\n\t\t\tstr += hour+\":\"+min+\":\"+sec;\r\n\t\t}\r\n\t}\r\n\treturn str;\r\n}"]],"start1":0,"start2":0,"length1":0,"length2":5499}]],"length":5499,"saved":false} diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..062d24e --- /dev/null +++ b/css/styles.css @@ -0,0 +1,28 @@ +[data-role="content"] h1, +[data-role="content"] h2, +[data-role="content"] h3, +[data-role="content"] h4 { + margin-top: 0; +} + +dt { + font-weight: bold; + background-color: #e6e6e6; +} + +dd { + min-height: 15px; + margin-left: 20px; +} + +address { + font-style: normal; +} + +[data-role="footer"] { + padding: 5px; +} + +.ui-collapsible-content { + padding: 0px 5px; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..77687d4 --- /dev/null +++ b/index.html @@ -0,0 +1,307 @@ + + + + L&I Property History + + + + + + + + + + + + + + + + + +
+
+
+ Search +
+
+ +
+
+
+ Search Results +
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/jquery.mobile.router.min.js b/js/jquery.mobile.router.min.js new file mode 100644 index 0000000..1af09c0 --- /dev/null +++ b/js/jquery.mobile.router.min.js @@ -0,0 +1,10 @@ +/*! + * jQueryMobile-router v0.93 + * http://github.com/azicchetti/jquerymobile-router + * + * Copyright 2011 (c) Andrea Zicchetti + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://github.com/azicchetti/jquerymobile-router/blob/master/MIT-LICENSE.txt + * http://github.com/azicchetti/jquerymobile-router/blob/master/GPL-LICENSE.txt + */ +(function(a,b){if(typeof define==="function"&&define.amd){define(["jquery"],b)}else{b(jQuery)}}(this,function(a){a(document).bind("mobileinit",function(){var e=a.extend({fixFirstPageDataUrl:false,firstPageDataUrl:"index.html",ajaxApp:false,firstMatchOnly:false},a.mobile.jqmRouter||{});var c=true;function b(h){if(c){console.log(h)}}var f=null,g=null,d=false;a(document).bind("pagebeforechange",function(l,k){var i=(typeof k.toPage==="string")?k.toPage:k.toPage.jqmData("url")||"";if(k.options.hasOwnProperty("_jqmrouter_handled")){return}k.options._jqmrouter_handled=true;if(k.options.data&&(k.options.type+"").toLowerCase()=="get"){i+="?"+k.options.data}var h=a.mobile.path.parseUrl(i);f=g;g=h;if(h.hash.indexOf("?")!==-1||(h.hash.length>0&&f!==null&&f.hash.indexOf(h.hash+"?")!==-1)){var j=h.hash.replace(/\?.*$/,"");k.options.dataUrl=h.href;if(a.mobile.activePage&&j.replace(/^#/,"")==a.mobile.activePage.jqmData("url")){k.options.allowSamePageTransition=true&&!d;a.mobile.changePage(a(j),k.options)}else{a.mobile.changePage(a(j),k.options)}l.preventDefault();a.mobile.urlHistory.ignoreNextHashChange=true}d=false;if(window.location.hash.indexOf("&ui-state=dialog")!=-1){d=true}});if(e.fixFirstPageDataUrl){a(document).ready(function(){if(!window.location.pathname.match("/$")){return}var i=a(":jqmData(role='page')").first();var j=i.jqmData("url"),h=window.location.pathname+e.firstPageDataUrl+window.location.search+window.location.hash;if(j!=h){i.attr("data-url",h).jqmData("url",h)}})}a.mobile.Router=function(l,j,k){this.routes={pagebeforecreate:null,pagecreate:null,pagebeforeshow:null,pageshow:null,pagebeforehide:null,pagehide:null,pageinit:null,pageremove:null,pagebeforechange:null,pagebeforeload:null,pageload:null,popupbeforeposition:null,popupafteropen:null,popupafterclose:null};this.evtLookup={bC:"pagebeforechange",bl:"pagebeforeload",l:"pageload",bc:"pagebeforecreate",c:"pagecreate",bs:"pagebeforeshow",s:"pageshow",bh:"pagebeforehide",h:"pagehide",i:"pageinit",rm:"pageremove",pbp:"popupbeforeposition",pao:"popupafteropen",pac:"popupafterclose"};this.routesRex={};this.conf=a.extend({},e,k||{});this.defaultHandlerEvents={};if(this.conf.defaultHandlerEvents){var h=this.conf.defaultHandlerEvents.split(",");for(var m=0;m0){this._eventData={events:i.join(" "),selectors:":jqmData(role='page'),:jqmData(role='dialog')",handler:l};a(document).delegate(this._eventData.selectors,this._eventData.events,this._eventData.handler)}if(n.length>0){this._docEventData={events:n.join(" "),handler:l};a(document).bind(this._docEventData.events,this._docEventData.handler)}},_processRoutes:function(m,q,n){var o=this,p,i,l,h=0;if(m.type in {pagebeforehide:true,pagehide:true,pageremove:true}){p=f}else{p=g}do{if(!p){if(n){l=a(n);p=l.jqmData("url");if(p){if(l.attr("id")==p){p="#"+p}p=a.mobile.path.parseUrl(p)}}}else{if(!this.documentEvts[m.type]&&n&&!a(n).jqmData("url")){return}}if(!p){return}i=(!this.conf.ajaxApp?p.hash:p.pathname+p.search+p.hash);if(i.length==0){p=""}h++}while(i.length==0&&h<=1);var k=false;a.each(this.routes[m.type],function(r,t){var s,v;if((s=i.match(o.routesRex[r]))){if(typeof(t)=="function"){v=t}else{if(typeof(o.userHandlers[t])=="function"){v=o.userHandlers[t]}}if(v){try{v.apply(o.userHandlers,[m.type,s,q,n,m]);k=true}catch(u){b(u)}}}if(k&&o.conf.firstMatchOnly){return false}});if(!k&&this.conf.defaultHandler&&this.defaultHandlerEvents[m.type]){if(typeof(this.conf.defaultHandler)=="function"){try{this.conf.defaultHandler.apply(o.userHandlers,[m.type,q,n,m])}catch(j){b(j)}}}},_detachEvents:function(){if(this._eventData){a(document).undelegate(this._eventData.selectors,this._eventData.events,this._eventData.handler)}if(this._docEventData){a(document).unbind(this._docEventData.events,this._docEventData.handler)}},destroy:function(){this._detachEvents();this.routes=this.routesRex=null},getParams:function(h){if(!h){return null}var k={},i;var j=h.slice(h.indexOf("?")+1).split("&");a.each(j,function(m,l){i=l.split("=");i[0]=decodeURIComponent(i[0]);if(k[i[0]]){if(!(k[i[0]] instanceof Array)){k[i[0]]=[k[i[0]]]}k[i[0]].push(decodeURIComponent(i[1]))}else{k[i[0]]=decodeURIComponent(i[1])}});if(a.isEmptyObject(k)){return null}return k}})});return{}})); \ No newline at end of file diff --git a/js/phillyapi.js b/js/phillyapi.js new file mode 100644 index 0000000..2c8fca2 --- /dev/null +++ b/js/phillyapi.js @@ -0,0 +1,218 @@ +var phillyapi = phillyapi || {}; +phillyapi = { + options: { + phillyapi: { + base: "http://services.phila.gov/PhillyApi/Data/" + ,summary: "HelperService.svc/GetLocationHistory?$format=json&AddressKey=" + ,permit: "Service.svc/permits('%id%')?$format=json" + ,license: "Service.svc/licenses('%id%')?$format=json" + ,_case: "Service.svc/violationdetails?$filter=case_number%20eq%20'%id%'&$expand=cases&$format=json" + ,zoningboardappeal: "Service.svc/zoningboardappeals?$filter=appeal_number%20eq%20'%id%'&$format=json" + ,zbahearingdecisions: "Service.svc/zbahearingdecisions?$filter=appeal_id%20eq%20%id%&$format=json" + ,zbacourtdetails: "Service.svc/zbacourtdetails?$filter=appeal_id%20eq%20%id%&$format=json" + ,buildingboardappeal: "Service.svc/buildingboardappeals?$filter=appeal_number%20eq%20'%id%'&$format=json" + ,bbshearingdecisions: "Service.svc/bbshearingdecisions?$filter=appeal_id%20eq%20%id%&$format=json" + ,bbscourtdetails: "Service.svc/bbscourtdetails?$filter=appeal_id%20eq%20%id%&$format=json" + ,lireviewboardappeal: "Service.svc/lireviewboardappeals?$filter=appeal_number%20eq%20'%id%'&$format=json" + ,lirbhearingdecisions: "Service.svc/lirbhearingdecisions?$filter=appeal_id%20eq%20%id%&$format=json" + ,lirbcourtdetails: "Service.svc/lirbcourtdetails?$filter=appeal_id%20eq%20%id%&$format=json" + ,timeout: 20000 + } + ,ulrs311: { + base: "http://services.phila.gov/ULRS311/Data/" + ,addressKey: "LIAddressKey/" + ,timeout: 20000 + } + } + + ,getAddressKey: function(input, successCallback, errorCallback) { + var url = phillyapi.options.ulrs311.base + phillyapi.options.ulrs311.addressKey + encodeURIComponent(input); + $.ajax({ + url: url + ,dataType: "jsonp" + ,contentType: "application/json; charset=utf-8" + ,async: false + ,cache: true + ,crossDomain: true + ,timeout: phillyapi.options.ulrs311.timeout + ,type: "GET" + ,xhrFields: { withCredentials: false } + ,error: errorCallback + ,success: function(data) { + if("TopicID" in data) { + var addressKey = data.TopicID; + // Clean up the address. Default format is something like " 01234 MARKET ST" - needs to be "1234 MARKET ST" - Anyone have a better regex for it? + var address = data.AddressRef ? $.trim(data.AddressRef.replace(/ +(?= )/g,"")).replace(/^0+/, "") : null; + successCallback(addressKey, address); + } + } + }); + } + + ,getSummary: function(addressKey, successCallback, errorCallback, sorted) { + var url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi.summary + addressKey; + phillyapi.fetch(url, function(data) { + successCallback(sorted ? phillyapi.sortSummary(data) : data); + }, errorCallback); + } + + ,getPermit: function(id, successCallback, errorCallback) { + var url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi.permit.replace("%id%", id); + phillyapi.fetch(url, successCallback, errorCallback); + } + + ,getLicense: function(id, successCallback, errorCallback) { + var url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi.license.replace("%id%", id); + phillyapi.fetch(url, successCallback, errorCallback); + } + + ,getCase: function(id, successCallback, errorCallback, sorted) { + var url = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi._case.replace("%id%", id); + phillyapi.fetch(url, function(data) { + successCallback(sorted ? phillyapi.sortCase(data) : data); + }, errorCallback); + } + + /* + Due to a known issue in the API, we have to do 3 calls here instead of 1. Also, since the Summary gives us the appeal + number rather than appeal id, we have to wait for the first call to finish in order to get the appeal id for the second two + */ + ,getAppeal: function(type, id, successCallback, errorCallback) { + var sortedData = {}, urlKeys = []; + switch(type) { + case "zoningboardappeals": + urlKeys = ["zoningboardappeal", "zbahearingdecisions", "zbacourtdetails"]; + break; + case "buildingboardappeals": + urlKeys = ["buildingboardappeal", "bbshearingdecisions", "bbscourtdetails"]; + break; + case "lireviewboardappeals": + urlKeys = ["lireviewboardappeal", "lirbhearingdecisions", "lirbcourtdetails"]; + break; + } + + // Appeal Details + var url1 = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi[urlKeys[0]].replace("%id%", id); + phillyapi.fetch(url1, function(data) { + sortedData.d = data.d.results[0]; + var requestsPending = 0; + + // Hearing Decisions + requestsPending++; + var url2 = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi[urlKeys[1]].replace("%id%", data.d.results[0].appeal_id); + phillyapi.fetch(url2, function(data) { + sortedData.d.hearingdecisions = data.d.results; + requestsPending--; + if(requestsPending < 1) successCallback(sortedData); + }, errorCallback); + // Court History + requestsPending++; + var url3 = phillyapi.options.phillyapi.base + phillyapi.options.phillyapi[urlKeys[2]].replace("%id%", data.d.results[0].appeal_id); + phillyapi.fetch(url3, function(data) { + sortedData.d.courtdetails = data.d.results; + requestsPending--; + if(requestsPending < 1) successCallback(sortedData); + }, errorCallback); + }, errorCallback); + } + + ,fetch: function(url, successCallback, errorCallback) { + $.ajax({ + url: url + ,dataType: "jsonp" + ,cache: true + ,timeout: phillyapi.options.phillyapi.timeout + ,jsonp: "$callback" + ,success: successCallback + ,error: errorCallback + }); + } + + // Sort summary data into categories + ,sortSummary: function(data) { + var sortedData = {}; + for(var i = 0; i < data.length; i++) { + switch(data[i].category) { + case "Violation": + if(sortedData.cases === undefined) { + sortedData.cases = {}; + } + if(sortedData.cases[data[i].id] === undefined) { + sortedData.cases[data[i].id] = { + "case_number": data[i].id + ,"violations": [] + ,"entity": data[i].entity + } + } + sortedData.cases[data[i].id].violations.push(data[i].type); + break; + case "Permit": + if(sortedData.permits === undefined) { + sortedData.permits = []; + } + sortedData.permits.push({ + "permit_number": data[i].id + ,"permit_type_name": data[i].type + ,"entity": data[i].entity + }); + break; + case "Business": + if(sortedData.licenses === undefined) { + sortedData.licenses = []; + } + sortedData.licenses.push({ + "license_number": data[i].id + ,"license_type_name": data[i].type + ,"entity": data[i].entity + }); + break; + case "Appeal": + if(sortedData.appeals === undefined) { + sortedData.appeals = []; + } + sortedData.appeals.push({ + "appeal_number": data[i].id + ,"appeal_type_name": data[i].type + ,"entity": data[i].entity + }); + break; + default: + if(sortedData.other === undefined) { + sortedData.other = []; + } + sortedData.other.push({ + "number": data[i].id + ,"type_name": data[i].type + }); + break; + } + } + if(sortedData.cases !== undefined) sortedData.cases = phillyapi.objToArray(sortedData.cases); + return sortedData; + } + + /* + This is a work-around for a known issue in the API. Ideally we'd do a call to the case and expand the violationdetails, + but since that gives an error, we do a call to the violation details of the case and expand the case. + That gives the case details multiple times, but at least it's only one ajax call. This function shuffles the data + around to be the case with a violationdetails array. + */ + ,sortCase: function(data) { + var sortedData = {}; + sortedData.d = data.d.results[0].cases; + for(var i = 0; i < data.d.results.length; i++) { + if(data.d.results[i].cases !== undefined) delete data.d.results[i].cases; + } + sortedData.d.violationdetails = data.d.results; + return sortedData; + } + + // Stock function + ,objToArray: function(obj) { + var arr = [] + for(var key in obj) { + arr.push(obj[key]); + } + return arr; + } +}; \ No newline at end of file diff --git a/js/script.js b/js/script.js new file mode 100644 index 0000000..529382a --- /dev/null +++ b/js/script.js @@ -0,0 +1,141 @@ +var DEBUG = false; +var cache = {summary: null, details: null}; + +var controller = { + search: function() { + $("#search form input").eq(0).focus(); + } + ,summary: function(eventType, matchObj, ui, page, evt) { + var input = decodeURIComponent(matchObj[1].replace(/\+/g, "%20")).replace(/^\s+|\s+$/g, ""); + if(cache.summary != matchObj[0]) { + $("[data-role=\"content\"]", page).empty(); + setLoading(true); + phillyapi.getAddressKey(input, function(addressKey, address) { + if(addressKey) { + phillyapi.getSummary(addressKey, function(data) { + if(DEBUG) console.log(data); + if(_.isEmpty(data)) { + controller.error("No history found for this address", page); + } else { + $("[data-role=\"content\"]", page).html(_.template($("#template-summary").html(), {address: address, data: data})); + $("[data-role=\"listview\"]", page).listview(); + cache.summary = matchObj[0]; + setLoading(false); + } + }, function(xhr, status, error) { + controller.error("There was an issue fetching the summary data from the server", page, xhr); + }, true); + } else { + controller.error("The address entered is not a valid L&I address", page); + } + }); + } + } + ,permit: function(eventType, matchObj, ui, page, evt) { + if(cache.details != matchObj[0]) { + $("[data-role=\"content\"]", page).empty(); + setLoading(true); + phillyapi.getPermit(matchObj[1], function(data) { + if(DEBUG) console.log(data); + $("[data-role=\"content\"]", page).html(_.template($("#template-details-permit").html(), {data: data.d})); + cache.details = matchObj[0]; + setLoading(false); + }, function(xhr, status, error) { + controller.error("There was an issue fetching the permit data from the server", page, xhr); + }); + } + } + ,license: function(eventType, matchObj, ui, page, evt) { + if(cache.details != matchObj[0]) { + $("[data-role=\"content\"]", page).empty(); + setLoading(true); + phillyapi.getLicense(matchObj[1], function(data) { + if(DEBUG) console.log(data); + $("[data-role=\"content\"]", page).html(_.template($("#template-details-license").html(), {data: data.d})); + cache.details = matchObj[0]; + setLoading(false); + }, function(xhr, status, error) { + controller.error("There was an issue fetching the license data from the server", page, xhr); + }); + } + } + ,_case: function(eventType, matchObj, ui, page, evt) { + if(cache.details != matchObj[0]) { + $("[data-role=\"content\"]", page).empty(); + setLoading(true); + phillyapi.getCase(matchObj[1], function(data) { + if(DEBUG) console.log(data); + $("[data-role=\"content\"]", page).html(_.template($("#template-details-case").html(), {data: data.d})); + $("[data-role=\"collapsible-set\"]", page).collapsibleset(); + cache.details = matchObj[0]; + setLoading(false); + }, function(xhr, status, error) { + controller.error("There was an issue fetching the case data from the server", page, xhr); + }, true); + } + } + ,appeal: function(eventType, matchObj, ui, page, evt) { + if(cache.details != matchObj[0]) { + $("[data-role=\"content\"]", page).empty(); + setLoading(true); + phillyapi.getAppeal(matchObj[1] + "appeals", matchObj[2], function(data) { + if(DEBUG) console.log(data); + $("[data-role=\"content\"]", page).html(_.template($("#template-details-appeal").html(), {data: data.d})); + $("[data-role=\"collapsible-set\"]", page).collapsibleset(); + cache.details = matchObj[0]; + setLoading(false); + }, function(xhr, status, error) { + controller.error("There was an issue fetching the appeal data from the server", page, xhr); + }); + } + } + ,error: function(errorMsg, page, xhr) { + $("[data-role=\"content\"]", page).html(_.template($("#template-details-error").html(), {errorMsg: errorMsg, xhr: xhr})); + setLoading(false); + } +}; +new $.mobile.Router({ + "#search": { handler: "search", events: "s" } + ,"#summary\\?address=(.*)": { handler: "summary", events: "bs" } + ,"#details\\?entity=permits&eid=(\\d*)": { handler: "permit", events: "bs" } + ,"#details\\?entity=licenses&eid=(\\d*)": { handler: "license", events: "bs" } + ,"#details\\?entity=violationdetails&eid=(\\d*)": { handler: "_case", events: "bs" } + ,"#details\\?entity=(.*)appeals&eid=(\\d*)": { handler: "appeal", events: "bs" } +}, controller); + +$(document).ready(function() { + // Ensure user has input an address before pressing search + $("#search form").submit(function(e) { + var inputNode = $("input[name=\"address\"]", $(this)); + if( ! $.trim(inputNode.val())) { + inputNode.focus(); + return false; + } + }); +}); + +// Necessary because v1.1.0 of jQuery Mobile doesn't seem to let you show the loading message during pagebeforeshow +function setLoading(on) { + if(on) $("body").addClass("ui-loading"); + else $("body").removeClass("ui-loading"); +} + +function display_date(input, show_time){ + var str; + if(input) { + var UNIX_timestamp = input.replace(/\D/g, "") / 1000; + var a = new Date(UNIX_timestamp*1000); + var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; + var year = a.getFullYear(); + var month = a.getMonth()+1; + var date = a.getDate(); + str = month+"/"+date+"/"+year; + if(show_time) { + var hour = a.getHours(); + var min = ("0" + a.getMinutes()).slice(-2); + var sec = ("0" + a.getSeconds()).slice(-2); + str += hour+":"+min+":"+sec; + } + } + return str; +} \ No newline at end of file