Ikariam City Select Reorder-er

By Overkill Last update 21 hours ago — Installed 8,512 times.

There are 5 previous versions of this script.

// ==UserScript==
// @name           Ikariam City Select Reorder-er
// @author	overkill
// @namespace      overkill_gm
// @version	0.11
// @description    Lets you reorder your cities in the drop down
// @include        http://*.ikariam.*/*
// @exclude        http://board.ikariam.*
// ==/UserScript==

/*
Homepage: http://userscripts.org/scripts/show/27630

*/
function debug(aMsg) { setTimeout(function() { throw new Error("[debug] " + aMsg); }, 0);}

function getListOfCities(){
	var city;
	var cities = $x("./option",$('citySelect'));
  if (cities.length == 1) { return false; }
	for (var i = 0; i < cities.length; ++i) {
		idToCity[cities[i].value] = trim(cities[i].innerHTML);
		if (cities[i].selected) {
			currentCityId = parseInt(cities[i].value,10);
			currentCityName = trim(cities[i].innerHTML);
		}
		city = cities[i].value.toString();
		currentCityList.push(city);
	}
  return true;
}
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].replace(/\D+/g,'');
		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);
	}
}

// this doesn't quite work the way i want to, but it works, so i'm leaving it alone
function syncListOfCities(){
	//debug("syncListOfCities :  old " + myCityList);
	var n = myCityList.length;
	var n2 = currentCityList.length;
	//debug("syncing " + n + " to " + n2);
	var id, saved_id;
	if ((n > 0) && (n == n2)) {
		for (var x = 0; x < n2; ++x){
			id       = currentCityList[x].replace(/\D+/,'');
		}
	} else {
		//debug("syncListOfCities :  replaced " + myCityList);
		myCityList = currentCityList;
		return false;
	}
	//debug("syncListOfCities :  new " + myCityList);
	return true;
}


function saveListFromOption(){
	//debug("saving list");
  var li = $x("./li",$('city_reorder'));
	myCityList = [];
  for (var i = 0; i < li.length; i++) {
		myCityList.push(li[i].id.replace(/^reorder/,''));
	}
  //debug('saving ' + uneval(myCityList));
	GM_setValue('cities_'+server,uneval(myCityList));
}

function moveUp(row){
	var clicked = this.parentNode;
	var cloned  = clicked.cloneNode(true);
	var ul = this.parentNode.parentNode;
	var li = ul.getElementsByTagName('li');
	for (var i = 1; i < li.length; ++i){
		if (li[i] == clicked) {
			cloned = ul.insertBefore(cloned,li[i-1]);
			ul.removeChild(clicked);
			spanUp   = cloned.childNodes[1];
			spanDown = cloned.childNodes[2];
			onClick(spanUp,   moveUp,   false);
			onClick(spanDown, moveDown, false);
			saveListFromOption();
		}
	}
	return true;
}

function moveDown(row){
	var clicked = this.parentNode;
	var cloned  = clicked.cloneNode(true);
	var ul = this.parentNode.parentNode;
	var li = ul.getElementsByTagName('li');
	for (var i = 0; i < li.length-1; ++i){
		if (li[i] == clicked) {
			cloned = ul.insertBefore(cloned,li[i+1].nextSibling);
			ul.removeChild(clicked);
			spanUp   = cloned.childNodes[1];
			spanDown = cloned.childNodes[2];
			onClick(spanUp,   moveUp,   false);
			onClick(spanDown, moveDown, false);
			saveListFromOption();
		}
	}
	return true;
}
function stripHTML(s){
  return s.replace(/<[^>]*>/g, "");
}
// 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();
}

function getCoordOfCity(){
	// check if breadcrumbs correspond to currentCityName
	var city = $x(".//*[@class='city']",$('breadcrumbs'));
	if (city.length == 1) {
		if (city[0].firstChild.nodeValue == currentCityName) { 
			var x = $('breadcrumbs');
			var match = x.innerHTML.match(/\[(.+)\]/);	// can't narrow this down any further since it's so inconsistent
			//debug(match[1]);
			if (match) { cityCoords[currentCityId] = match[1]; }
		}
	}
}

//rearrange Balances page
function reorderBalances(){
	var cities = $x(".//tr",$('balance'));
	var n = cities.length;
	var 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);
			rows[cityName] = cities[i].cloneNode(true);
			p.removeChild(cities[i]);
		}
		for (i = 0; i < n-3; ++i) {
			id = myCityList[i].replace(/\D+/g,'');
			rows[idToCity[id]].className = (i % 2)?  "alt" : "";
			p.insertBefore(rows[idToCity[id]],resultRow);
		}
	}
}

// show options
function showOptions(){
	// from score linker
	//debug("showing options");
  var mybox = node("div", "", { textAlign: "center" });
	var opts  = '<h3>Reorder</h3>(will automatically save, no need to hit the "Save settings!" button)<br /><ul id="city_reorder" style=\"width:300px; margin-left:auto; margin-right:auto\">';
	var id;
	//debug("myCityList.length " + myCityList.length);
	for (var i = 0; i < myCityList.length ; ++i) {
		id       = myCityList[i].match(/\d+/);
		opts += '<li id="reorder'+ myCityList[i] +'"><span style="padding-left: 10px; padding-right:15px; width:200px">' + idToCity[id] + '</span><span id="up'+id+'">&#9650;</span><span id="down'+id+'" cl>&#9660;</span></li>'+"\n";
	}
	opts += '</ul>';
	mybox.innerHTML = opts;
	
  var pwd = $('options_changePass');
  pwd.appendChild(mybox);
	
  //var up = $x('//li[@type="checkbox" and contains(@id,"Score")]');
	var ul = $('city_reorder');
  var up = ul.getElementsByTagName("span");
	//debug("up " + up.length);
  for (i = 0; i < up.length; i++) {
		if (up[i].id.search(/^up/) != -1) { onClick(up[i], moveUp, false); }
		else if (up[i].id.search(/^down/) != -1) { onClick(up[i], moveDown, false); }
		//up.onclick = "moveUp('this');";
	}
}

/**************************************** HELPER FUNCTIONS *********************************************/

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 duration(seconds){
	var x = [Math.floor(seconds / 86400) ,	Math.floor(seconds/3600) % 24 ,	Math.floor(seconds/60) % 60 ,	Math.round(seconds % 60) ];
	var y = ['d'                         , 'h'                            , 'm'                         , 's'];
	var r = [];
	for (var i = 0; i < x.length; ++i){ if (x[i] > 0) { r.push(x[i].toString() + y[i]); } }
	return r.join(' ');
}

function distance(r1,r2){
	if ((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){
			var x1 = r1[0];
			var x2 = r2[0];
			var y1 = r1[1];
			var y2 = r2[1];
			return Math.round(Math.sqrt(Math.pow(x1-x2,2) + Math.pow(y1-y2,2))*100)/100;
		}
	}
	return false;
}

function writeTransportBoxHTML(){
	var ikNewElement = document.createElement('div');
	ikNewElement.id = 'transportBox';
	$('container2').appendChild(ikNewElement);
	//$('mainview').insertBefore(ikNewElement,$('mainview').firstChild);

	var list        = $('citySelect');
	list            = list.previousSibling.childNodes[1];
	var optionElems = list.getElementsByTagName('li');
	var n_cities    = optionElems.length;
	var output      = '';
	var id, coords, d, selected, dOutput;
	var curCoords = cityCoords[currentCityId];

	for (var i = 0; i < n_cities; ++i) {
		id       = myCityList[i].replace(/\D+/g,'');
		selected = (id == currentCityId) ? ' selected ' : '';
		if ((d = distance(curCoords,cityCoords[id])) !== false) { dOutput = ' (' + duration(60*20*(d+1)) + ')'; }
		else { dOutput = ''; }
		output += '<option value="'+id+'" ' + selected + '>' + idToCity[id] + dOutput + '</option>';
	}
	$("transportBox").innerHTML = "<form method=\"GET\" action=\"index.php\"><select name=\"destinationCityId\" onchange=\"this.parentNode.submit()\">"+output+"</select><input type=\"hidden\" name=\"view\" value=\"transport\" /></form>";
}

// global variabless
var currentCityName;
var currentCityId;
var currentCityList = []; // pre-re-ordrered list of cities
var idToCity = {};
var server = document.location.toString().split('/')[2];
var myCityList = eval(GM_getValue('cities_'+server,'[]'));    // load saved order
var cityCoords = eval(GM_getValue('coords_'+server,'({})'));  // load saved coordinates

if (getListOfCities()) {
  getCoordOfCity();
  syncListOfCities();
  writeCitySelectHTML();
  // save list
  GM_setValue('cities_'+server,myCityList.toSource());
  GM_setValue('coords_'+server,cityCoords.toSource());

  // special pages
  switch(document.body.id) {
  	case 'options'  : showOptions(); break;
  	case 'finances' : reorderBalances(); break;
  }


  // transporter code, based on  darkyndy's Ikariam: Ikariam Transporter
  GM_addStyle(
  "" + <><![CDATA[
  #cityNav .dropbutton img { position:relative; top:2px; background:none; padding-right:4px; }
  #cityNav .optionList img { position:relative; top:2px; background:none; padding-right:4px; }
  #transportBox {
  position:absolute; left:24px; top:155px;
  margin: 0px;
  padding: 0px;
  }
  #transportBox select {
    font-size:8pt;
    background-color:#fff7e2;
    border: 0px none;
    margin: 0px;
    padding: 0px;
    width:44px;
  }
  #transportBox select:focus { width:auto; }
]]></>);

  writeTransportBoxHTML();

}