// vim: et:ts=4:sw=4:sts=4:ft=javascript /*@ Ox.ListMap ListMap object () -> ListMap object (options) -> ListMap object (options, self) -> ListMap object options Options object height Height in px labels If true, show labels places Array of places, or function that returns places selected Selected places width Width in px self Shared private variable @*/ Ox.ListMap = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ addPlace: null, editPlace: null, height: 256, labels: false, pageLength: 100, places: null, removePlace: null, selected: [], sort: [{key: 'geoname', operator: '+'}], width: 256 }) .options(options || {}) .addClass('OxListMap') .css({ width: self.options.width + 'px', height: self.options.height + 'px' }); self.isAsync = Ox.isFunction(self.options.places); self.columns = [ { addable: false, // fixme: implement id: 'id', title: 'Id', unique: true, visible: false, width: 64 }, { format: function(value) { return $('') .attr({ // fixme: not the right place to do this src: Ox.PATH + 'Ox.Geo/png/icons/16/' + (value || 'NTHH') + '.png' }) .css({ width: '14px', height: '14px', borderRadius: '4px', marginLeft: '-3px', marginTop: 0 }); }, id: 'countryCode', resizable: false, // fixme: implement title: $('').attr({ src: Ox.UI.getImageURL('symbolFlag') // fixme: why does this not work? it does in folderBrowserList }), visible: true, width: 16 }, { id: 'name', operator: '+', removable: false, title: 'Name', visible: true, width: 144 }, { format: function(value) { return value.join('; '); }, id: 'alternativeNames', operator: '+', title: 'Alternative Names', visible: true, width: 144 }, { id: 'geoname', map: function(value) { var names = value.split(', '); if (!Ox.getCountryByGeoname(names[names.length - 1])) { names.push('~'); } return names.reverse().join(', '); }, operator: '+', title: 'Geoname', visible: true, width: 192 }, { format: function(value) { return Ox.toTitleCase(value); }, id: 'type', operator: '+', title: 'Type', visible: true, width: 64 }, { align: 'right', format: toFixed, id: 'south', operator: '+', title: 'South', visible: false, width: 96 }, { align: 'right', id: 'west', operator: '+', title: 'West', visible: false, width: 96 }, { align: 'right', format: toFixed, id: 'north', operator: '+', title: 'North', visible: false, width: 96 }, { align: 'right', format: toFixed, id: 'east', operator: '+', title: 'East', visible: false, width: 96 }, { align: 'right', format: toFixed, id: 'lat', operator: '+', title: 'Latitude', visible: true, width: 96 }, { align: 'right', format: toFixed, id: 'lng', operator: '+', title: 'Longitude', visible: true, width: 96 }, { align: 'right', format: {type: 'area', args: []}, id: 'area', operator: '-', title: 'Area', visible: true, width: 128 }, { id: 'user', operator: '+', title: 'User', visible: false, width: 96 }, { format: function(value) { return value.replace('T', ' ').replace('Z', ''); }, id: 'created', operator: '-', title: 'Date Created', visible: false, width: 128, }, { format: function(value) { return value.replace('T', ' ').replace('Z', ''); }, id: 'modified', operator: '-', title: 'Date Modified', visible: false, width: 128, }, { align: 'right', id: 'matches', operator: '-', title: 'Matches', visible: false, width: 64, } ]; self.$listToolbar = Ox.Bar({ size: 24 }); self.$findElement = Ox.FormElementGroup({ elements: [ self.$findSelect = Ox.Select({ items: [ {id: 'all', title: 'Find: All'}, {id: 'name', title: 'Find: Name'}, {id: 'alternativeNames', title: 'Find: Alternative Names'}, {id: 'geoname', title: 'Find: Geoname'} ], overlap: 'right', type: 'image' }) .bindEvent({ change: function(data) { var key = data.selected[0].id, value = self.$findInput.value(); value && updateList(key, value); } }), self.$findInput = Ox.Input({ clear: true, placeholder: 'Find in List', width: 234 }) .bindEvent({ submit: function(data) { var key = self.$findSelect.value(), value = data.value; updateList(key, value); } }) ] }) .css({float: 'right', margin: '4px'}) .bindEvent({ change: function(data) { } }) .appendTo(self.$listToolbar); self.$list = Ox.TextList({ columns: self.columns, columnsRemovable: true, columnsVisible: true, //items: Ox.clone(self.options.places), items: self.options.places, pageLength: self.options.pageLength, scrollbarVisible: true, sort: self.options.sort }) .bindEvent({ 'delete': removeItem, init: initList, // fixme: do we need 0/shift-0? return already zooms to place key_0: function() { self.$map.panToPlace(); }, key_equal: function() { self.$map.zoom(1); }, key_minus: function() { self.$map.zoom(-1); }, key_shift_0: function() { self.$map.zoomToPlace(); }, load: function() { that.triggerEvent('loadlist'); }, open: openItem, select: selectItem }); self.$listStatusbar = Ox.Bar({ size: 16 }); self.$status = Ox.Element() .css({paddingTop: '2px', margin: 'auto', fontSize: '9px', textAlign: 'center'}) .appendTo(self.$listStatusbar); self.$map = Ox.Map({ clickable: true, editable: true, findPlaceholder: 'Find on Map', height: self.options.height, places: self.options.places, //statusbar: true, showTypes: self.options.showTypes, toolbar: true, width: self.options.width - 514,//self.mapResize[1], zoombar: true }) .bindEvent({ /* addplace: function(data) { that.triggerEvent('addplace', data); }, */ changeplace: function(data) { self.$placeForm.values(data).show(); self.$areaKmInput.options({value: Ox.formatArea(data.area)}); }, changeplaceend: function(data) { //Ox.print('ssP', self.selectedPlace); var isResult = self.selectedPlace[0] == '_'; !isResult && editPlace([ 'lat', 'lng', 'south', 'west', 'north', 'east', 'area' ]); }, geocode: function(data) { that.triggerEvent('geocode', data); }, /* resize: function() { self.$map.resizeMap(); // fixme: don't need event }, */ selectplace: selectPlace }); self.$placeTitlebar = Ox.Bar({ size: 24 }); self.$placeTitle = $('
') .hide() .appendTo(self.$placeTitlebar) self.$placeFlag = $('') .addClass('OxFlag') .attr({ src: Ox.getImageByGeoname('icon', 16, '') }) .css({float: 'left', margin: '4px'}) .appendTo(self.$placeTitle); self.$placeName = Ox.Label({ title: '', width: 208 }) .css({float: 'left', margin: '4px 0 4px 0'}) .appendTo(self.$placeTitle) .bindEvent({ singleclick: function() { self.$map.panToPlace(); }, doubleclick: function() { self.$map.zoomToPlace(); } }); self.$deselectPlaceButton = Ox.Button({ title: 'close', tooltip: 'Done', type: 'image' }) .css({float: 'left', margin: '4px'}) .bindEvent({ click: function() { self.$map.options({selected: null}); } }) .appendTo(self.$placeTitle); self.$nameInput = Ox.Input({ id: 'name', label: 'Name', labelWidth: 64, width: 240 }).bindEvent({ change: function(data) { var isResult = self.selectedPlace[0] == '_'; !isResult && self.$list.value(self.selectedPlace, 'name', data.value); if (!self.isAsync) { Ox.getObjectById( self.options.places, self.selectedPlace ).name = data.value; } else { !isResult && editPlace(['name']); } self.$map.value(self.selectedPlace, 'name', data.value); } }); self.$alternativeNamesInput = Ox.ArrayInput({ id: 'alternativeNames', label: 'Alternative Names', max: 10, //sort: true, values: [], width: 240 }).bindEvent({ change: function(data) { var isResult = self.selectedPlace[0] == '_'; if (!self.isAsync) { } else { !isResult && editPlace(['alternativeNames']) } self.$map.value(self.selectedPlace, 'alternativeNames', data.value); } }); self.$geonameInput = Ox.Input({ id: 'geoname', label: 'Geoname', labelWidth: 64, width: 240 }).bindEvent({ change: function(data) { var geoname = data.value, country = Ox.getCountryByGeoname(geoname), countryCode = country ? country.code : '', isResult = self.selectedPlace[0] == '_'; self.$placeFlag.attr({ src: Ox.getImageByGeoname('icon', 16, geoname) }); self.$placeName.options({title: geoname}); self.$placeForm.values({countryCode: countryCode}); if (!self.isAsync) { if (!isResult) { self.$list.value(self.selectedPlace, 'geoname', geoname); self.$list.value(self.selectedPlace, 'countryCode', countryCode); } } else { !isResult && editPlace(['countryCode', 'geoname']); } self.$map.value(self.selectedPlace, 'countryCode', countryCode); self.$map.value(self.selectedPlace, 'geoname', geoname); } }); // fixme: form should have a change event // fixme: it has one now, but inputs fire on blur self.$placeFormItems = Ox.merge([ self.$nameInput, self.$alternativeNamesInput, self.$geonameInput, Ox.Input({ id: 'countryCode' }).hide(), Ox.Select({ id: 'type', items: [ {id: 'country', title: 'Country'}, {id: 'region', title: 'Region'}, // administative (Kansas) or colloquial (Midwest) {id: 'city', title: 'City'}, {id: 'borough', title: 'Borough'}, {id: 'street', title: 'Street'}, // streets, squares, bridges, tunnels, ... {id: 'building', title: 'Building'}, // airports, stations, stadiums, military installations, ... {id: 'feature', title: 'Feature'} // continents, islands, rivers, lakes, seas, oceans, ... ], label: 'Type', labelWidth: 64, width: 240 }).bindEvent({ change: function(data) { var isResult = self.selectedPlace[0] == '_'; if (!self.isAsync) { } else { !isResult && editPlace(['type']) } self.$map.value(self.selectedPlace, 'type', data.selected[0].id); } }) ], ['Latitude', 'Longitude', 'South', 'West', 'North', 'East'].map(function(v) { var id = ( v == 'Latitude' ? 'lat' : v == 'Longitude' ? 'lng' : v ).toLowerCase(), max = ['Latitude', 'South', 'North'].indexOf(v) > -1 ? Ox.MAX_LATITUDE : 180; return Ox.Input({ decimals: 8, disabled: ['lat', 'lng'].indexOf(id) > -1, id: id, label: v, labelWidth: 80, min: -max, max: max, type: 'float', width: 240 }) .bindEvent({ blur: function(data) { ///* // fixme: if type is set, no change event fires var isResult = self.selectedPlace[0] == '_'; if (!self.isAsync) { } else { !isResult && editPlace([v]) } self.$map.value(self.selectedPlace, id, parseFloat(data.value)); //*/ } }); }), [ self.$areaInput = Ox.Input({ id: 'area', type: 'float' }).hide() ]); self.$placeForm = Ox.Form({ items: self.$placeFormItems, width: 240 }) .css({margin: '8px'}) .hide(); self.$areaKmInput = Ox.Input({ disabled: true, id: 'areaKm', label: 'Area', labelWidth: 80, textAlign: 'right', width: 240 }) .css({margin: '4px 0 4px 0'}) .appendTo(self.$placeForm); self.$placeStatusbar = Ox.Bar({ size: 24 }); self.$newPlaceButton = Ox.Button({ title: 'New Place', width: 96 }) .css({float: 'left', margin: '4px 2px 4px 4px'}) .bindEvent({ click: function() { self.$map.newPlace(); } }) .appendTo(self.$placeStatusbar); self.$placeButton = Ox.Button({ title: 'Add Place', width: 96 }) .css({float: 'right', margin: '4px 4px 4px 2px'}) .bindEvent({ click: function() { if (self.$placeButton.options('title') == 'Add Place') { addPlace(); } else { removePlace(); } } }) .hide() .appendTo(self.$placeStatusbar); /* self.$revertButton = Ox.Button({ title: 'Revert', width: 96 }) .css({float: 'right', margin: '4px 4px 4px 2px'}) .hide() .appendTo(self.$placeStatusbar); */ /* self.mapResize = [ Math.round(self.options.width * 0.25), Math.round(self.options.width * 0.5), Math.round(self.options.width * 0.75) ]; */ /* if (!self.isAsync) { self.placesLength = self.options.places.length; setStatus(); } else { self.options.places({}, function(results) { self.placesLength = results.data.items; setStatus(); }); } */ that.$element.replaceWith( that.$element = Ox.SplitPanel({ elements: [ { collapsible: true, element: Ox.SplitPanel({ elements: [ { element: self.$listToolbar, size: 24 }, { element: self.$list }, { element: self.$listStatusbar, size: 16 } ], orientation: 'vertical' }), resizable: true, resize: [256, 384, 512], size: 256 }, { element: self.$map, }, { collapsible: true, element: Ox.SplitPanel({ elements: [ { element: self.$placeTitlebar, size: 24 }, { element: self.$placeForm }, { element: self.$placeStatusbar, size: 24 } ], orientation: 'vertical' }) .bindEvent({ resize: function(data) { self.$placeName.options({width: data.size - 48}); // fixme: pass width through form self.$placeFormItems.forEach(function($item) { $item.options({width: data.size - 16}); }); } }), resizable: true, resize: [204, 256, 384], size: 256 } ], orientation: 'horizontal' }).$element ); function addPlace() { var place = self.$placeForm.values(), country = Ox.getCountryByGeoname(place.geoname); place.countryCode = country ? country.code : ''; if (!self.isAsync) { place.id = self.selectedPlace.substr(1); // fixme: safe? self.selectedPlace = place.id; self.options.places.push(place); self.$list.options({ items: Ox.clone(self.options.places), selected: [place.id] }); self.$map.addPlace(place); self.$placeButton.options({title: 'Remove Place'}); //setStatus(); } //that.triggerEvent('addplace', {place: place}); if (self.isAsync) { self.$placeButton.options({disabled: true, title: 'Adding Place'}); self.options.addPlace(place, function(result) { if (result.status.code == 200) { place.id = result.data.id; self.selectedPlace = place.id; self.$list.reloadList().options({selected: [place.id]}); self.$map.addPlace(place); self.$placeButton.options({disabled: false, title: 'Remove Place'}); } else { if (result.data.names) { if (result.data.names.indexOf(self.$nameInput.value()) > -1) { self.$nameInput.addClass('OxError'); } self.$alternativeNamesInput.setErrors(result.data.names); } if (result.data.geoname) { self.$geonameInput.addClass('OxError'); } self.$placeButton.options({disabled: false, title: 'Add Place'}); } }); } } function editPlace(keys) { var values = Ox.filter(self.$placeForm.values(), function(values, key) { return keys.indexOf(key) > -1; }); values.id = self.selectedPlace; self.options.editPlace(values, function() { if (keys.indexOf(self.$list.options('sort')[0].key) > -1) { self.$list.reloadList(); } else { Ox.forEach(values, function(value, key) { if (key != 'id') { self.$list.value(values.id, key, value); self.$map.value(values.id, key, value); } }); } }); } function initList(data) { self.$status.html( Ox.formatNumber(data.items) + ' Place' + ( data.items == 1 ? '' : 's' ) ); } function openItem(data) { selectItem(data); self.$map.zoomToPlace(data.ids[0]); } function removeItem(data) { var id = data.ids[0]; // fixme: events or callback functions?? that.triggerEvent('removeplace', {id: id}); self.$map.removePlace(id); } function removePlace() { var index; Ox.print('REMOVE PLACE', self.selectedPlace, index) if (!self.isAsync) { // fixme: doesn't call self.options.removePlace! index = Ox.getPositionById(self.options.places, self.selectedPlace); self.options.places.splice(index, 1); self.$list.options({items: Ox.clone(self.options.places)}); //setStatus(); } // fixme: what is this? both options.removePlace and event removeplace?? if (self.isAsync) { self.options.removePlace({id: self.selectedPlace}, function() { self.$list.options({selected: []}).reloadList(true); }); } self.$map.removePlace(); self.$placeButton.options({title: 'Add Place'}); that.triggerEvent('removeplace', {id: self.selectedPlace}); } function selectItem(data) { var id = data.ids.length ? data.ids[0] : null; self.$map.options({selected: id}); id && self.$map.panToPlace(); } function selectPlace(place) { var isResult = place.id && place.id[0] == '_'; self.$list.options({ selected: place.id && !isResult ? [place.id] : [] }); if (place.id) { //isResult && self.options.places.push(place); self.selectedPlace = place.id; self.$placeFlag.attr({ src: Ox.getImageByGeoname('icon', 16, place.geoname) }); self.$placeName.options({title: place.geoname || ''}); self.$placeTitle.show(); self.$placeForm.values(place).show(); self.$areaKmInput.options({value: Ox.formatArea(place.area)}); self.$placeButton.options({title: isResult ? 'Add Place' : 'Remove Place'}).show(); } else { self.selectedPlace = null; self.$placeTitle.hide(); self.$placeForm.hide(); self.$placeButton.hide(); } } function toFixed(val) { return Ox.isNumber(val) ? val.toFixed(8) : val; // fixme: why can a string be passed ?? } function updateList(key, value) { var query = { conditions: Ox.merge( ['all', 'name'].indexOf(key) > -1 ? [{key: 'name', value: value, operator: '='}] : [], ['all', 'alternativeNames'].indexOf(key) > -1 ? [{key: 'alternativeNames', value: value, operator: '='}] : [], ['all', 'geoname'].indexOf(key) > -1 ? [{key: 'geoname', value: value, operator: '='}] : [] ), operator: key == 'all' ? '|' : '&' }; self.$list.options({ items: function(data, callback) { return pandora.api.findPlaces(Ox.extend(data, { query: query }), callback); } }); } /*@ setOption setOption @*/ self.setOption = function(key, value) { if (key == 'height') { self.$list.size(); self.$map.resizeMap(); } else if (key == 'selected') { self.$list.options({selected: value}); } else if (key == 'width') { self.$map.resizeMap(); } } /*@ focusList focusList @*/ that.focusList = function() { self.$list.gainFocus(); return that; } /*@ reloadList reloadList @*/ that.reloadList = function() { self.$list.reloadList(); return that; } /*@ resizeMap resizeMap @*/ that.resizeMap = function() { Ox.print('Ox.ListMap.resizeMap()') self.$map.resizeMap(); return that; }; return that; };