PageRenderTime 56ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/webmapper/static/webmapper/core.js

https://bitbucket.org/svevang/django-webmapper
JavaScript | 415 lines | 265 code | 102 blank | 48 comment | 29 complexity | 474e082d338fbe75d36e9779bdd3524f MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /* webmapper.js */
  2. window.webmapper = (function(webmapper){
  3. if(typeof window.console === "undefined"){
  4. (function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();)b[a]=b[a]||c;})(window.console=window.console||{})
  5. }
  6. webmapper.errorHandler = {error:function(){
  7. // TODO: an error reporting system, display messages to the User
  8. }
  9. }
  10. webmapper.ApiParams = {
  11. // construct and store url paramters on a per Model/Collection basis
  12. // helper function to build query param list
  13. serializeQueryParams: function(pairList){
  14. var str = []
  15. _(pairList).each(function(pair){
  16. str.push(encodeURIComponent(pair[0]) + "=" + encodeURIComponent(pair[1]))
  17. })
  18. return str.join("&")
  19. },
  20. // models and collections can build up api call params
  21. // by setting and deleting specific key/value pairs
  22. setApiParam:function(param, value){
  23. if(!this._apiParams)
  24. this._apiParams = []
  25. this._apiParams.push([param, value])
  26. },
  27. url: function(apiParameters){
  28. var baseUrl = this.urlRoot + (this.id ? this.id + "/" : "")
  29. var params = []
  30. if(apiParameters){
  31. // if we are passed any api parameters, use those
  32. params = apiParameters
  33. }
  34. else{
  35. // otherwise if we have set any parameters on this model/collection
  36. // accumulate those and append them to the url
  37. if(this._apiParams)
  38. _(this._apiParams).each(function(pair){params.push(pair)})
  39. if(this.collection && this.collection._apiParams)
  40. _(this.collection._apiParams).each(function(pair){params.push(pair)})
  41. }
  42. if(params.length > 0)
  43. baseUrl = baseUrl + '?' + this.serializeQueryParams(params)
  44. return baseUrl
  45. },
  46. }
  47. webmapper.BaseModel = Backbone.Model.extend(_.extend(webmapper.ApiParams,{
  48. isNew: function(){
  49. return ( typeof this.id == 'undefined' || this.id == "new")
  50. }
  51. }))
  52. webmapper.BaseCollection = Backbone.Collection.extend(webmapper.ApiParams)
  53. webmapper.Map = webmapper.BaseModel.extend({
  54. urlRoot: webmapperConstant['api_maps_url'],
  55. bindEvents:function(){
  56. var map = this
  57. },
  58. initialize: function(opts){
  59. this.loading = $.Deferred()
  60. this.templateStore = $('<div></div>')
  61. var defaultOptions = {
  62. fillContainer : false,
  63. applicationViews : [],
  64. scrollZoom : false,
  65. tools : [
  66. new webmapper.tools.MarkerTool(),
  67. new webmapper.tools.KmlTool()
  68. ]
  69. }
  70. this.options = _.extend(defaultOptions, opts)
  71. if(this.options.domId)
  72. this.baseEl = $('#'+this.options.domId)
  73. else
  74. this.baseEl = this.options.baseEl || null
  75. this.tools = this.options.tools
  76. this.applicationViews = this.options.applicationViews
  77. // FIXME, need to override the construtor, pull args out and then
  78. // pass the rest to initialize, or maybe whitelist model attributes
  79. this.unset('baseEl',{silent:true})
  80. this.unset('tools',{silent:true})
  81. this.unset('applicationViews',{silent:true})
  82. if(this.isNew() && this.id != "new" )
  83. this.mapLoadedPromise = this.save()
  84. else
  85. this.mapLoadedPromise = this.fetch()
  86. _.bindAll(this)
  87. this.bindTools()
  88. this.bindApplicationViews()
  89. var map = this
  90. // bootstrap the application
  91. $.when(map.mapLoadedPromise).then(function(){
  92. map.mapPane = new webmapper.MapPane({id: map.get('map_pane_id') || 'new'})
  93. map.mapPane.map = map
  94. var mapViewArgs = {model:map}
  95. if(map.baseEl){
  96. mapViewArgs.el = map.baseEl
  97. }
  98. map.view = new webmapper.MapView(mapViewArgs)
  99. $.when(map.mapPane.fetch()).then(function(){
  100. if(map.baseEl){
  101. map.view.render()
  102. // create map widget views render and append to map DOM
  103. map.mapPane.initializeMapPaneView()
  104. }
  105. // create geo attrs
  106. map.kmlLayers = new webmapper.KmlLayerCollection([], {map:map})
  107. map.points = new webmapper.PointCollection([], {map:map})
  108. map.polygons = new webmapper.PolygonCollection([], {map:map})
  109. if(map.isNew()){
  110. map.loading.resolve()
  111. map.trigger('loaded')
  112. return
  113. }
  114. map.trigger('prepare-sync', map)
  115. $.when(map.kmlLayers.fetch(),
  116. map.points.fetch(),
  117. map.polygons.fetch())
  118. .then(function(){
  119. // tools and application views can bind to various events
  120. map.trigger('init-ready', map)
  121. map.trigger('attach-kml', map.kmlLayers)
  122. map.trigger('attach-points', map.points)
  123. map.trigger('attach-polygons', map.polygons)
  124. map.trigger('init-complete', map)
  125. map.loading.resolve()
  126. map.trigger('loaded')
  127. })
  128. })
  129. })
  130. return this
  131. },
  132. // Save the Map and MapPane
  133. create:function(){
  134. var created = $.Deferred()
  135. var map = this
  136. if(this.isNew()){
  137. this.save().done(function(){
  138. map.mapPane.id = map.get('map_pane_id')
  139. map.mapPane.fetch().done(function(){
  140. created.resolve()
  141. }).fail(function(){
  142. created.reject("MapPane fetch failed")
  143. })
  144. }).fail(function(){
  145. created.reject('Map create failed')
  146. })
  147. return created
  148. }
  149. else{
  150. throw('cannot create an existing map')
  151. }
  152. },
  153. bindTools:function(){
  154. var map = this
  155. _(this.tools).each(function(tool){
  156. tool.bindMap(map)
  157. })
  158. },
  159. bindApplicationViews:function(){
  160. var map = this
  161. _(this.applicationViews).each(function(view){
  162. view.bindMap(map)
  163. })
  164. },
  165. getTemplate:function(templateDomId){
  166. return webmapperConstant.templates[templateDomId]
  167. },
  168. getCurrentPane: function(){
  169. return this.mapPane
  170. },
  171. })
  172. webmapper.MapView = Backbone.View.extend({
  173. tagName: 'div',
  174. className: 'webmapper-map',
  175. render: function(){
  176. // build the map dom
  177. this.$el.css('position','relative')
  178. return this
  179. }
  180. })
  181. // MapPanes
  182. // --------
  183. webmapper.MapPane = webmapper.BaseModel.extend({
  184. initializeMapPaneView: function(args){
  185. // Initialize attributes specific to the map.
  186. var opts = args || {}
  187. opts.model = this
  188. // Create the map widget view.
  189. if(this.get('map_type') == "google")
  190. this.view = new webmapper.google.MapPaneView(opts)
  191. else if(this.get('map_type') == "openlayers")
  192. this.view = new webmapper.openlayers.MapPaneView(opts)
  193. else
  194. throw('unknown map type ' + this.get('map_type'))
  195. // Append the mapPane's rendered el to map DOM fragment.
  196. $(this.map.view.el).prepend(this.view.render().el)
  197. // trigger reflow on the map widget
  198. this.view.reflowSize()
  199. this.map.on('attach-kml', this.view.attachKML, this)
  200. this.map.on('attach-points', this.view.attachPoints, this)
  201. this.map.on('attach-polygons', this.view.attachPolygons, this)
  202. this.trigger('map-pane-initialized')
  203. },
  204. changeMapWidget: function(new_map_type){
  205. var mapPane = this
  206. // Supress map_type change event until the various attributes settle.
  207. mapPane.set({map_type:new_map_type},{silent:true})
  208. // We can save and reuse el.
  209. var el = mapPane.view.el
  210. mapPane.view.removeMapWidget()
  211. mapPane.initializeMapPaneView({el:el})
  212. // Impress the server state upon the widget.
  213. mapPane.view.syncMapWidget()
  214. // Re-setup tool delegations.
  215. mapPane.map.bindTools()
  216. mapPane.map.bindApplicationViews()
  217. // Our new widget is lacking the canonical layer: reattach the geo-models.
  218. this.map.trigger('attach-kml', this.map.kmlLayers)
  219. this.map.trigger('attach-points', this.map.points)
  220. this.map.trigger('attach-polygons', this.map.polygons)
  221. // Alert listeners that the map_type has changed.
  222. mapPane.trigger('change:map_type')
  223. },
  224. urlRoot: webmapperConstant['api_map_panes_url'],
  225. url:function(){
  226. return this.urlRoot + (this.id ? this.id + "/" : "")
  227. }
  228. })
  229. // MapPane Views
  230. // -------------
  231. webmapper.AbstractMapPaneView = Backbone.View.extend({
  232. // provides custom events:
  233. // * tiles-loaded
  234. // This class defines the interface to the concrete Google/Openlayers
  235. // implementations. Webmapper map-types implement this interface.
  236. tagName: "div",
  237. className: "webmapper-mappane",
  238. initialize: function(opts){
  239. // if we are in an editing state,
  240. // set up the appropriate css class
  241. if(this.model.map.editing)
  242. $(this.el).addClass('editing')
  243. },
  244. // build up the attributes that handle events
  245. // pass in `translateFunc` to convert the event to an intermediate
  246. // "webmapper" representation
  247. _eventAttrConstructor:function(eventName, translateFunc){
  248. var mapPaneView = this
  249. return function(){
  250. var eventArgs = arguments
  251. mapPaneView.trigger(eventName, translateFunc.apply(mapPaneView, eventArgs))
  252. }
  253. },
  254. render: function(){
  255. // calls to this should be idempotent across implementations
  256. this.removeMapWidget()
  257. var opts = this.model.toJSON()
  258. this.renderMapWidget()
  259. this.bindMapWidgetEvents()
  260. return this
  261. },
  262. reflowSize: function(){
  263. // allow the map to "reflow" in the container div
  264. if(this.model.map.options.fillContainer){
  265. this.$el.width(this.$el.parent().width())
  266. this.$el.height(this.$el.parent().height())
  267. }
  268. else{
  269. this.$el.width(this.model.get('width'))
  270. this.$el.height(this.model.get('height'))
  271. }
  272. this.trigger('reflow')
  273. },
  274. remove: function(){
  275. this.removeMapWidget()
  276. return Backbone.View.prototype.remove.call(this)
  277. },
  278. // Map Panes must implement a bare minimum interface
  279. paneDiv: function(){
  280. throw('not implemented')
  281. },
  282. renderMapWidget:function(){
  283. throw('not implemented')
  284. },
  285. bindMapWidgetEvents:function(){
  286. throw('not implemented')
  287. },
  288. syncMapWidget:function(){
  289. throw('not implemented')
  290. },
  291. removeMapWidget:function(){
  292. throw('not implemented')
  293. },
  294. getBounds:function(){
  295. throw('not implemented')
  296. },
  297. attachPoints:function(){
  298. throw('not implemented')
  299. },
  300. attachKML:function(){
  301. throw('not implemented')
  302. },
  303. attachPolygons:function(){
  304. throw('not implemented')
  305. },
  306. // Internal APIs must provide a mechanism to propagate widget attrs back
  307. // to the MapPane model. Something like:
  308. // * propagateCenter
  309. // * propagateZoom
  310. // * propagateMapTypeId
  311. })
  312. return webmapper
  313. })(window.webmapper || {});