/** * Copyright (c) 2008-2010 The Open Source Geospatial Foundation * * Published under the BSD license. * See http://svn.geoext.org/core/trunk/geoext/license.txt for the full text * of the license. */ /** * @include GeoExt/data/LayerReader.js */ /** api: (define) * module = GeoExt.data * class = LayerStore * base_link = `Ext.data.Store `_ */ Ext.namespace("GeoExt.data"); /** private: constructor * .. class:: LayerStoreMixin * A store that synchronizes a layers array of an {OpenLayers.Map} with a * layer store holding {} entries. * * This class can not be instantiated directly. Instead, it is meant to * extend ``Ext.data.Store`` or a subclass of it. */ /** private: example * Sample code to extend a store with the LayerStoreMixin. * * .. code-block:: javascript * * var store = new (Ext.extend(Ext.data.Store, new GeoExt.data.LayerStoreMixin))({ * map: myMap, * layers: myLayers * }); * * For convenience, a :class:`GeoExt.data.LayerStore` class is available as a * shortcut to the ``Ext.extend`` sequence in the above code snippet. */ GeoExt.data.LayerStoreMixin = function() { return { /** api: config[map] * ``OpenLayers.Map`` * Map that this store will be in sync with. If not provided, the * store will not be bound to a map. */ /** api: property[map] * ``OpenLayers.Map`` * Map that the store is synchronized with, if any. */ map: null, /** api: config[layers] * ``Array(OpenLayers.Layer)`` * Layers that will be added to the store (and the map, depending on the * value of the ``initDir`` option. */ /** api: config[initDir] * ``Number`` * Bitfields specifying the direction to use for the initial sync between * the map and the store, if set to 0 then no initial sync is done. * Defaults to ``GeoExt.data.LayerStore.MAP_TO_STORE|GeoExt.data.LayerStore.STORE_TO_MAP`` */ /** api: config[fields] * ``Array`` * If provided a custom layer record type with additional fields will be * used. Default fields for every layer record are `layer` * (``OpenLayers.Layer``) `title` (``String``). The value of this option is * either a field definition objects as passed to the * :meth:`GeoExt.data.LayerRecord.create` function or a * :class:`GeoExt.data.LayerRecord` constructor created using * :meth:`GeoExt.data.LayerRecord.create`. */ /** api: config[reader] * ``Ext.data.DataReader`` The reader used to produce * :class:`GeoExt.data.LayerRecord` objects from ``OpenLayers.Layer`` * objects. If not provided, a :class:`GeoExt.data.LayerReader` will be * used. */ reader: null, /** private: method[constructor] */ constructor: function(config) { config = config || {}; config.reader = config.reader || new GeoExt.data.LayerReader({}, config.fields); delete config.fields; // "map" option var map = config.map instanceof GeoExt.MapPanel ? config.map.map : config.map; delete config.map; // "layers" option - is an alias to "data" option if(config.layers) { config.data = config.layers; } delete config.layers; // "initDir" option var options = {initDir: config.initDir}; delete config.initDir; arguments.callee.superclass.constructor.call(this, config); if(map) { this.bind(map, options); } }, /** private: method[bind] * :param map: ``OpenLayers.Map`` The map instance. * :param options: ``Object`` * * Bind this store to a map instance, once bound the store * is synchronized with the map and vice-versa. */ bind: function(map, options) { if(this.map) { // already bound return; } this.map = map; options = options || {}; var initDir = options.initDir; if(options.initDir == undefined) { initDir = GeoExt.data.LayerStore.MAP_TO_STORE | GeoExt.data.LayerStore.STORE_TO_MAP; } // create a snapshot of the map's layers var layers = map.layers.slice(0); if(initDir & GeoExt.data.LayerStore.STORE_TO_MAP) { this.each(function(record) { this.map.addLayer(record.getLayer()); }, this); } if(initDir & GeoExt.data.LayerStore.MAP_TO_STORE) { this.loadData(layers, true); } map.events.on({ "changelayer": this.onChangeLayer, "addlayer": this.onAddLayer, "removelayer": this.onRemoveLayer, scope: this }); this.on({ "load": this.onLoad, "clear": this.onClear, "add": this.onAdd, "remove": this.onRemove, "update": this.onUpdate, scope: this }); this.data.on({ "replace" : this.onReplace, scope: this }); }, /** private: method[unbind] * Unbind this store from the map it is currently bound. */ unbind: function() { if(this.map) { this.map.events.un({ "changelayer": this.onChangeLayer, "addlayer": this.onAddLayer, "removelayer": this.onRemoveLayer, scope: this }); this.un("load", this.onLoad, this); this.un("clear", this.onClear, this); this.un("add", this.onAdd, this); this.un("remove", this.onRemove, this); this.data.un("replace", this.onReplace, this); this.map = null; } }, /** private: method[onChangeLayer] * :param evt: ``Object`` * * Handler for layer changes. When layer order changes, this moves the * appropriate record within the store. */ onChangeLayer: function(evt) { var layer = evt.layer; var recordIndex = this.findBy(function(rec, id) { return rec.getLayer() === layer; }); if(recordIndex > -1) { var record = this.getAt(recordIndex); if(evt.property === "order") { if(!this._adding && !this._removing) { var layerIndex = this.map.getLayerIndex(layer); if(layerIndex !== recordIndex) { this._removing = true; this.remove(record); delete this._removing; this._adding = true; this.insert(layerIndex, [record]); delete this._adding; } } } else if(evt.property === "name") { record.set("title", layer.name); } else { this.fireEvent("update", this, record, Ext.data.Record.EDIT); } } }, /** private: method[onAddLayer] * :param evt: ``Object`` * * Handler for a map's addlayer event */ onAddLayer: function(evt) { if(!this._adding) { var layer = evt.layer; this._adding = true; this.loadData([layer], true); delete this._adding; } }, /** private: method[onRemoveLayer] * :param evt: ``Object`` * * Handler for a map's removelayer event */ onRemoveLayer: function(evt){ //TODO replace the check for undloadDestroy with a listener for the // map's beforedestroy event, doing unbind(). This can be done as soon // as http://trac.openlayers.org/ticket/2136 is fixed. if(this.map.unloadDestroy) { if(!this._removing) { var layer = evt.layer; this._removing = true; this.remove(this.getById(layer.id)); delete this._removing; } } else { this.unbind(); } }, /** private: method[onLoad] * :param store: ``Ext.data.Store`` * :param records: ``Array(Ext.data.Record)`` * :param options: ``Object`` * * Handler for a store's load event */ onLoad: function(store, records, options) { if (!Ext.isArray(records)) { records = [records]; } if (options && !options.add) { this._removing = true; for (var i = this.map.layers.length - 1; i >= 0; i--) { this.map.removeLayer(this.map.layers[i]); } delete this._removing; // layers has already been added to map on "add" event var len = records.length; if (len > 0) { var layers = new Array(len); for (var j = 0; j < len; j++) { layers[j] = records[j].getLayer(); } this._adding = true; this.map.addLayers(layers); delete this._adding; } } }, /** private: method[onClear] * :param store: ``Ext.data.Store`` * * Handler for a store's clear event */ onClear: function(store) { this._removing = true; for (var i = this.map.layers.length - 1; i >= 0; i--) { this.map.removeLayer(this.map.layers[i]); } delete this._removing; }, /** private: method[onAdd] * :param store: ``Ext.data.Store`` * :param records: ``Array(Ext.data.Record)`` * :param index: ``Number`` * * Handler for a store's add event */ onAdd: function(store, records, index) { if(!this._adding) { this._adding = true; var layer; for(var i=records.length-1; i>=0; --i) { layer = records[i].getLayer(); this.map.addLayer(layer); if(index !== this.map.layers.length-1) { this.map.setLayerIndex(layer, index); } } delete this._adding; } }, /** private: method[onRemove] * :param store: ``Ext.data.Store`` * :param record: ``Ext.data.Record`` * :param index: ``Number`` * * Handler for a store's remove event */ onRemove: function(store, record, index){ if(!this._removing) { var layer = record.getLayer(); if (this.map.getLayer(layer.id) != null) { this._removing = true; this.removeMapLayer(record); delete this._removing; } } }, /** private: method[onUpdate] * :param store: ``Ext.data.Store`` * :param record: ``Ext.data.Record`` * :param operation: ``Number`` * * Handler for a store's update event */ onUpdate: function(store, record, operation) { if(operation === Ext.data.Record.EDIT) { if (record.modified && record.modified.title) { var layer = record.getLayer(); var title = record.get("title"); if(title !== layer.name) { layer.setName(title); } } } }, /** private: method[removeMapLayer] * :param record: ``Ext.data.Record`` * * Removes a record's layer from the bound map. */ removeMapLayer: function(record){ this.map.removeLayer(record.getLayer()); }, /** private: method[onReplace] * :param key: ``String`` * :param oldRecord: ``Object`` In this case, a record that has been * replaced. * :param newRecord: ``Object`` In this case, a record that is replacing * oldRecord. * Handler for a store's data collections' replace event */ onReplace: function(key, oldRecord, newRecord){ this.removeMapLayer(oldRecord); }, /** public: method[getByLayer] * :param layer: ``OpenLayers.Layer`` * :return: :class:`GeoExt.data.LayerRecord` or undefined if not found * * Get the record for the specified layer */ getByLayer: function(layer) { var index = this.findBy(function(r) { return r.getLayer() === layer; }); if(index > -1) { return this.getAt(index); } }, /** private: method[destroy] */ destroy: function() { this.unbind(); GeoExt.data.LayerStore.superclass.destroy.call(this); } }; }; /** api: example * Sample to create a new store containing a cache of * :class:`GeoExt.data.LayerRecord` instances derived from map layers. * * .. code-block:: javascript * * var store = new GeoExt.data.LayerStore({ * map: myMap, * layers: myLayers * }); */ /** api: constructor * .. class:: LayerStore * * A store that contains a cache of :class:`GeoExt.data.LayerRecord` * objects. */ GeoExt.data.LayerStore = Ext.extend( Ext.data.Store, new GeoExt.data.LayerStoreMixin ); /** * Constant: GeoExt.data.LayerStore.MAP_TO_STORE * {Integer} Constant used to make the store be automatically updated * when changes occur in the map. */ GeoExt.data.LayerStore.MAP_TO_STORE = 1; /** * Constant: GeoExt.data.LayerStore.STORE_TO_MAP * {Integer} Constant used to make the map be automatically updated * when changes occur in the store. */ GeoExt.data.LayerStore.STORE_TO_MAP = 2;