Ikariam City Select Reorder-er

By Overkill Last update Sep 14, 2009 — Installed 39,373 times. Daily Installs: 66, 77, 79, 64, 80, 72, 70, 111, 79, 90, 75, 88, 68, 86, 59, 87, 62, 65, 47, 55, 75, 49, 53, 56, 47, 71, 68, 63, 57, 59, 58, 67

There are 14 previous versions of this script.

var scriptMetadata = parseMetadata(<><![CDATA[
// ==UserScript==
// @name           Ikariam City Select Reorder-er
// @author         overkill
// @namespace      overkill_gm
// @version        3.7
// @description    Lets you reorder your cities in the drop down
// @homepage       http://userscripts.org/scripts/show/27630
// @include        http://s*.ikariam.*/index.php*// ==/UserScript==
]]></>.toString());
function parseMetadata(a){var b=a.split(/[\r\n]+/).filter(/\/\/ @/);var c={include:[],exclude:[]};for each(var d in b){[d,name,value]=d.match(/\/\/ @(\S+)\s*(.*)/);if(c[name]instanceof Array)c[name].push(value);else c[name]=value}return c}
function debug() { var msg = []; for (var i = 0, n = arguments.length; i<n; ++i) msg.push(arguments[i]); setTimeout(function() { throw new Error("[debug] " + msg.join(' ')); }, 0);}
function stripHTML(s){ return s.replace(/<[^>]*>/g, ""); }
function onClick(node, fn, capture, e) { node.addEventListener((e||"") + "click", fn, !!capture); }
function node(type, className, styles, content) {
  var n = document.createElement(type||"div");
  if (className) n.className = className;
  if (styles) for (var prop in styles) n.style[prop] = styles[prop];
  if (content) n.innerHTML = "string" == typeof content ? content : content.toXMLString();
  return n;
}
function $(id) { return document.getElementById(id); }
function $x( xpath, root ) {
  var doc = root ? root.evaluate ? root : root.ownerDocument : document, next;
  var got = doc.evaluate( xpath, root||doc, null, 0, null ), result = [];
  switch (got.resultType) {
    case got.STRING_TYPE:
      return got.stringValue;
    case got.NUMBER_TYPE:
      return got.numberValue;
    case got.BOOLEAN_TYPE:
      return got.booleanValue;
    default:
      while (next = got.iterateNext())
        result.push( next );
      return result;
  }
}
function $X( xpath, root ) { var got = $x( xpath, root ); return got instanceof Array ? got[0] : got; }
function trim(str) {
	str = str.replace(/^\s\s*/, '');
	var ws = /\s/;
	var i = str.length;
	while (ws.test(str.charAt(--i)));
	return str.slice(0, i + 1);
}
function in_array(needle, haystack) { for (var key in haystack) if (haystack[key] == needle) return true; return false; }
function durationHMS(seconds,depth){
  var temp = unsafeWindow.LocalizationStrings['timeunits']['short'], ret = [], prefix = '';
  if (seconds == 0) { return '0'; }
  else if (seconds < 0) { seconds = -seconds; prefix = '-'; }
	var x = [ Math.floor(seconds / 86400) , Math.floor(seconds/3600) % 24 ,	Math.floor(seconds/60) % 60 , Math.ceil(seconds % 60) ];
	var y = [ temp.day                    , temp.hour                     , temp.minute,                  temp.second  ];
	for (var i = 0; i < x.length; ++i){ if (x[i] != 0) { ret.push(x[i].toString() + y[i]); } }
  if (depth && depth<ret.length) return prefix + ret.slice(0,depth).join(' ');
  else return prefix + ret.join(' ');
}
function distance(r1,r2){
	if (r1 && r2 && (typeof r1 == "string") && (typeof r2 == "string")) {	// uhm, has to be a better way of doing this
		r1=r1.split(':');
		r2=r2.split(':');
		if (r1.length && r2.length)	return Math.round(Math.sqrt(Math.pow(r1[0]-r2[0],2) + Math.pow(r1[1]-r2[1],2))*100)/100;
	}
	return false;
}
function generateSelfCache(){
  var cache = {};
  for each (var town in $x('./option',$('citySelect'))){
    if (town.innerHTML.charAt(0) == '[') {  //todo: move this if statement outside the loop
      // COORDS in town navigation
      cache[town.value] = {
        'name'     : town.innerHTML.replace('&nbsp;',' ').replace(/\[[0-9:\s]+\]\s+/,''),
        'position' : town.innerHTML.match(/\[([0-9:\s]+)\]/,'')[1].replace(/00/g,'100'),
        'tradegood': town.title.substring(12)
      };
    } else {
      // TRADE GOOD in town navigation
      var coords = town.title;
      coords     = coords.substr(1,coords.length-2);
      cache[town.value] = {
        'name':town.innerHTML,
        'position':coords,
        'tradegood':unsafeWindow.LocalizationStrings['resources'][town.className.charAt(town.className.indexOf('tradegood')+9)]
      };
    }
  }
  return cache;
}
function writeCitySelectHTML(){
	var optionParent = $('citySelect');
	var optionElems  = $x('./option',optionParent);
	var liParent     = optionParent.previousSibling.childNodes[1];
	var liElems      = liParent.getElementsByTagName('li');
	var oldOptions   = {};
	var oldLis       = {};
	var n_cities     = optionElems.length;
	var id, newLi;
	for (var i = n_cities-1; i >= 0; --i) {
		oldOptions[optionElems[i].value] = optionElems[i].cloneNode(true);
		newLi = liElems[i].cloneNode(true);
		newLi.className = newLi.className.replace(/( first)|( last)|( active)/g,'');
		oldLis[optionElems[i].value]  = newLi;
		optionParent.removeChild(optionElems[i]);
		liParent.removeChild(liElems[i]);
	}
	for (i = 0; i < n_cities; ++i) {
		id = myCityList[i];
		optionParent.appendChild(oldOptions[id]);
		newLi = oldLis[id];
		if (id == currentCityId) { newLi.className += ' active'; }
		if (i == 0) { newLi.className += ' first'; }
		if (i == n_cities-1) { newLi.className += ' last'; }
		onClick(newLi, selectCity, false);
		liParent.appendChild(newLi);
	}
}
// called when a new city is chosen. rewrites the citySelect form and submits it
function selectCity(event){
	var optionParent = $('citySelect');
	var optionElems  = $x('./option',optionParent);
	var liParent     = optionParent.previousSibling.childNodes[1];
	var liElems      = liParent.getElementsByTagName('li');
	var n_cities     = optionElems.length;
	
	var x = 0;
	while ((x < n_cities) && (this != liElems[x])) { ++x; }
	if (x >= n_cities) {
		alert("An error has occured. Please Disable this script and contact the author.");
		return;
	}
	//debug(this.parentNode.parentNode.parentNode.parentNode.parentNode);
	for (var i = n_cities-1; i >= 0 ; --i) {
		optionElems[i].selected = i == x;
	}
	
	//debug(active.className);
	var active   = optionParent.previousSibling.childNodes[0];
	var newClass = liElems[x].className.match(/tradegood./);
	if (newClass) { active.className = "avatarCities "+newClass[0]+" dropbutton"; }
	active.innerHTML = liElems[x].innerHTML;
	// don't bother changing title
	
	var form = this.parentNode.parentNode.parentNode.parentNode.parentNode;	// errr... this is silly =P
	form.submit();
}

///////////////////////////////////////// START OPTIONS
function moveUp(){
	var clicked = this.parentNode;
  clicked.parentNode.insertBefore(clicked,clicked.previousSibling);
  saveListFromOption();
}
function moveDown(){
	var clicked = this.parentNode;
  clicked.parentNode.insertBefore(clicked,clicked.nextSibling.nextSibling);
	saveListFromOption();
}
// will add content to the OPTIONS page in place-th place (optional) and encapsulate it in a div. returns the DIV element of the options
function addOptionsToPage(content,place){
  place = place || 5; // place must be > 0 and a number
  var parentElem = $('options_changePass').parentNode;  // an arbitrary element with an id was chosen
  return parentElem.insertBefore(node("div", "", { textAlign: "center" },content),$X('./div['+place+']',parentElem));
}

function showOptions(){
  var element, id;
  var opts  = '<h3>Reorder Towns v'+scriptMetadata['version']+' (by <a href="http://userscripts.org/users/53907">Overkill</a>)</h3><i>(will automatically save, no need to hit the "Save settings!" button)</i><br /><ul id="city_reorder" style="width:300px; margin:3px auto;">';
	for (var i = 0; i < myCityList.length ; ++i) {
		id    = myCityList[i];
		opts += '<li id="reorder'+ myCityList[i] +'"><span>' + selfCache[id].name + '</span><span class="ok_up">&#9650;</span><span class="ok_down">&#9660;</span></li>';
	}

	opts += '</ul>Put Transport Helper<select>';
  opts += '<option' + ((transporterContainer == '')                ? ' selected' : '') + ' value="">disabled</option>';
  opts += '<option' + ((transporterContainer == 'globalResources') ? ' selected' : '') + ' value="globalResources">top</option>';
  opts += '<option' + ((transporterContainer == 'mainview')        ? ' selected' : '') + ' value="mainview">bottom</option>';
  opts += '<option' + ((transporterContainer == 'Overkill Bar')    ? ' selected' : '') + '>Overkill Bar</option>';
	opts += '</select>';
  var newDiv = addOptionsToPage(opts,3);

  for each (element in $x('.//span[@class="ok_up"]'  ,newDiv)){ onClick(element,moveUp); }
  for each (element in $x('.//span[@class="ok_down"]',newDiv)){ onClick(element,moveDown); }
  //onClick($X('.//select',newDiv),saveListFromOption);
  $X('.//select',newDiv).addEventListener("change", saveListFromOption, false);
  GM_addStyle('#city_reorder span:first-child { display:inline-block; width: 200px; }');
  GM_addStyle('#city_reorder span + span { cursor: pointer; }');
}
function saveListFromOption(){
	//debug("saving list");
	myCityList = [];
  var li = $x("./li",$('city_reorder'));
  for (var i = 0; i < li.length; i++)
		myCityList.push(parseInt(li[i].id.replace(/^reorder/,''),10));

  //debug('saving ' + uneval(myCityList));
  //debug('saving use_bar ' + $X('..//select',$('city_reorder')).value);
	GM_setValue('cities_'+server,uneval(myCityList));
  GM_setValue('use_bar',$X('..//select',$('city_reorder')).value);
}
///////////////////////////////////////// END OPTIONS


//rearrange Balances page
function reorderBalances(){
	var cities = $x(".//tr",$('balance'));
	var n = cities.length;
	var row, rows = {};
	var cityName, id, p;
	if ((n > 3) && (n == myCityList.length+3)) {
		var resultRow = cities[n-2];
		p = cities[0].parentNode;
		for (var i = n-3; i > 0; --i) {
			cityName = trim(cities[i].getElementsByTagName('td')[0].innerHTML);
			if (!rows[cityName]) rows[cityName] = p.removeChild(cities[i]); // if multiple cities have the same name, ignore after the 1st city
		}
		for (i = 0; i < myCityList.length; ++i) {
      if (row = rows[selfCache[myCityList[i]].name]){
        row.className = (i % 2)?  "alt" : "";
        p.insertBefore(row,resultRow);
      }
    }
	}
}

function writeTransportBoxHTML(){
  var output  = '', id, d, selected, dOutput;

	for (var i = 0,n = myCityList.length; i < n; ++i) {
		id       = myCityList[i];
		selected = (id == currentCityId) ? ' selected ' : '';
		dOutput  = ((d = distance(selfCache[currentCityId].position,selfCache[id].position)) !== false) ? ' (' + durationHMS(Math.max(1200*d,600)) + ')' : '';
		output += '<option value="'+id+'"' + selected + '>' + selfCache[id].name + dOutput + '</option>';
	}
  var html = "<form method='get' action='index.php' style='float:right;'><select id='overkillTransport' name='destinationCityId' onchange='this.parentNode.submit()'>"+output+"</select><input type=\"hidden\" name=\"view\" value=\"transport\" /></form>";
  switch (transporterContainer) {
    case 'mainview' :
      GM_addStyle("#overkillTransport { background:wheat; font-size:8pt; border: 0 none; margin:0; padding:0; height:15px; }");
      GM_addStyle("#overkillTransport option { padding: 0; }");
      $(transporterContainer).appendChild(node('div','',{position:'absolute'},html));
    break;
    case 'globalResources' :
      GM_addStyle("#overkillTransport { background:wheat; font-size:8pt; border: 0 none; margin:0; padding:0; height:15px; }");
      GM_addStyle("#overkillTransport option { padding: 0; }");
      $(transporterContainer).appendChild(node('div','',{position:'absolute',top:'-11px'},html));
    break;
    case 'Overkill Bar' :
      GM_addStyle("#overkillTransport { font-size:8pt; border: 0 none; margin: 0 0 0 4px; padding:0; height:15px; }");
      GM_addStyle("#overkillTransport option { padding: 0; }");
      var barTab = overkillBar(html+'Transport');
      overkillBar_add(barTab,'<a href="'+scriptMetadata['homepage']+'">Version: '+scriptMetadata['version']+'</a>');
      overkillBar_add(barTab,'<a href="/index.php?view=options#city_reorder">Change Options</a>');
    break;
  }
}
if ($('servertime') && unsafeWindow.IKARIAM){
  // global variabless
  var server = document.domain;
  var currentCityId   = parseInt($X('//select[@id="citySelect"]/option[@selected]').value,10);
  var selfCache       = generateSelfCache();
  var myCityList      = eval(GM_getValue('cities_'+server,[]));    // load saved order
  var transporterContainer = GM_getValue('use_bar','globalResources');

  // reconcile saved list of cities with current list of cities
  var change = false;
  for (var i = myCityList.length-1; i>=0; --i)
      if (!selfCache[myCityList[i]]) { myCityList.splice(i,1); change = true; }
  for (var id in selfCache) {
    id = parseInt(id,10);
    if (!in_array(id,myCityList)) { myCityList.push(id); change = true; }
  }
  if (change) GM_setValue('cities_'+server,uneval(myCityList));


  writeCitySelectHTML();
  if (document.body.id == 'finances') reorderBalances();
  else if (document.body.id == 'options') showOptions();
  if (transporterContainer) writeTransportBoxHTML();
}






/*
OVERKILL BAR -- add everything below this to script to enable the overkill bar. Add a new tab and initialize the bar with

  var tab = overkillBar('tab title');

*/
function overkillBar(title){
  if (!$('overkillBar')){
    var div = node('div','','','<ul></ul>');
    div.id = "overkillBar";
    document.body.appendChild(node('div','',{"height":"3em;"},'&nbsp;'));
    document.body.appendChild(div);
    var ul = $X('./ul',div);
    ul.appendChild(node('li','','','<h2 style="font-weight:bold;">OVERKILL</h2><ul><li><a href="http://userscripts.org/users/53907/scripts">Homepage</a></li></ul>'));
    //test
    //styleBar();
    //production
    GM_addStyle('#overkillBar ul{margin:0;padding:0;border:0;list-style-type:none;display:block}#overkillBar ul li{margin:0;padding:0;border:0;display:block;float:left;position:relative;z-index:5}#overkillBar ul li:hover{z-index:10000}#overkillBar ul li li{float:none}#overkillBar ul ul{visibility:hidden;position:absolute;z-index:10;left:0;bottom:0}#overkillBar ul li:hover>ul{visibility:visible;bottom:100%}#overkillBar ul li li:hover>ul{bottom:0;left:100%}#overkillBar ul:after,#overkillBar ul ul:after{content:".";height:0;display:block;visibility:hidden;overflow:hidden;clear:both}#overkillBar ul ul{ background:none;padding:30px 30px 10px 30px;margin:0 0 -10px -30px}#overkillBar ul ul ul{padding:30px 30px 30px 10px;margin:0 0 -30px -10px}#overkillBar{ position:fixed; bottom:0px; width:100%; z-Index:10}#overkillBar h2{ font-size:14px}#overkillBar ul{color:#eee;background:#234}#overkillBar ul ul li{color:#eee;background:#234; font-size:smaller}#overkillBar ul ul{width:15em}#overkillBar ul a{text-decoration:none;color:#eee;padding:.4em 1em;display:block;position:relative}#overkillBar ul a:hover,#overkillBar ul li:hover>a{color:#fc3}#overkillBar ul li{ padding:3px; white-space:nowrap}#overkillBar ul li{border:1px solid #ccc}#overkillBar ul>li+li{border-left:0}#overkillBar ul ul>li+li{border-left:1px solid}#overkillBar ul ul>li+li{border-top:0}#overkillBar ul li li:hover>ul{bottom:5px;left:90%}#overkillBar em{ font-style:italic}');
  }
  var ul = $X('./ul',$('overkillBar'));
  var tab = ul.appendChild(node('li','','','<h2>'+title+'</h2>'));
  return tab.appendChild(node('ul','','',''));
}
function overkillBar_add(barTab,content){
  return barTab.appendChild(node('li','','',"string" == typeof content ? content : content.toXMLString()));
}
function overkillBarOptions(barTab){
  var gameServer = document.domain.replace(/ikariam\./,'');
  this.save = function(){
    var copy = {};
    for (thing in this)
      if ((typeof this[thing] !== 'function') && (typeof this[thing] !== 'object'))
        copy[thing] = this[thing];
      //debug("save : "+uneval(copy));
    GM_setValue('_okbarOptions_'+gameServer,uneval(copy));
  }
  this.setTab = function(barTab){
    this.barTab = barTab;
  }
  this.addInput = function(label,defaultValue){
    var options = this;
    var labelName = stripHTML(label);
    //debug(label + ' ' + labelName);
    if (!options[labelName]) { options[labelName] = defaultValue || ''; }
    var ctrl = options.barTab.appendChild(node('li','',{cursor:'pointer'},label+': '+(options[labelName]?options[labelName]:'not set')));
    onClick(ctrl,function(){
      var newValue = prompt('New '+labelName,options[labelName]);
      if (newValue !== null) options[labelName] = newValue;
      this.innerHTML = label+': '+(options[labelName]?options[labelName]:'not set');
      options.save();
    });
  }
  this.addCB = function(label,defaultValue){
    var options = this;
    var labelName = trim(stripHTML(label));
    //debug(label + ' ' + labelName);
    if (typeof options[labelName] != 'boolean') { options[labelName] = !!defaultValue; }
    var ctrl = options.barTab.appendChild(node('li','',{cursor:'pointer'},label+' '+(options[labelName]?'&#10003':'')));
    onClick(ctrl,function(){
      options[labelName] = !options[labelName];
      this.innerHTML = label+' '+(options[labelName]?'&#10003':'');
      options.save();
    });
  }
  this.barTab = barTab;
  var options = eval(GM_getValue('_okbarOptions_'+gameServer),{});
  for (thing in options) this[thing] = options[thing];
}