Index: xfn.dev.js
===================================================================
--- xfn.dev.js	(revision 17572)
+++ xfn.dev.js	(working copy)
@@ -1,16 +1,146 @@
 jQuery(document).ready( function($) {
-	$('#link_rel').attr('readonly', 'readonly');
-	$('#linkxfndiv input').bind('click keyup', function() {
-		var isMe = $('#me').is(':checked'), inputs = '';
-		$('input.valinp').each( function() {
-			if (isMe) {
+	var friendship = new Array("contact", "acquaintance", "friend");
+	var geographical = new Array("co-resident", "neighbor");
+	var family = new Array("child", "kin", "parent", "sibling", "spouse");
+	
+	$('#link_rel').attr('readonly', '');
+	$('#link_rel').bind('keyup', function() {
+		if($('#link_rel').val().substr($('#link_rel').val().length-1, $('#link_rel').val().length) == ' '){
+			return;
+		}
+		
+		var oldWords = $('#link_rel').val().split(" ");
+		var unique = new Array()
+		for(var i = 0; i < oldWords.length; i++) {
+			if($.inArray(oldWords[i], unique) == -1) {
+				unique.push(oldWords[i]);
+			}
+		}	
+		var count = oldWords.length - unique.length;
+		
+		oldWords = unique;
+
+		if(count > 0) {
+			alert("The input field contains duplicates or none, these will be removed.");
+			
+			var output = "";
+			for(var i = 0; i < oldWords.length; i++) {
+				output = output + oldWords[i] + " ";
+			}
+			
+			output = $.trim(output); // trim both sides cuz the user knows we will change sthg
+			
+			$('#link_rel').val( output );
+		}
+	});
+
+	$('#linkxfndiv .form-table input').bind('click', function() {
+		var isMe = $('#me').is(':checked'), inputs = '', //toRemove = new Array(),
+		wasMe = ($('#link_rel').val() == "me");
+		
+		if($('#link_rel').val() == "me") {
+			var oldWords = new Array();
+		}
+		else {
+			var oldWords = $('#link_rel').val().split(" ");
+		}
+
+		if (isMe) {
+			$('input.valinp').each( function() {
 				$(this).attr('disabled', 'disabled').parent().addClass('disabled');
-			} else {
+			});
+		} 
+		else {
+			$('input.valinp').each( function() {
 				$(this).removeAttr('disabled').parent().removeClass('disabled');
-				if ( $(this).is(':checked') && $(this).val() != '')
-					inputs += $(this).val() + ' ';
+			});
+
+			if(wasMe) {
+				// trigger the code below for every field to rebuild input
+				$('input.valinp').each( function() {
+					oldWords = updateField($(this), oldWords) ;
+				});
 			}
-		});
-		$('#link_rel').val( (isMe) ? 'me' : inputs.substr(0,inputs.length - 1) );
+			else {
+				// execute only for clicked field
+				oldWords = updateField($(this), oldWords) ;
+			}
+		}
+		
+		var output = "";
+		for(var i = 0; i < oldWords.length; i++) {
+			output = output + oldWords[i] + " ";
+		}
+		output = $.trim(output);
+
+		$('#link_rel').val( (isMe) ? 'me' : output );
 	});
+	
+	function updateField(field, oldWords) {
+		if ( field.is(':checked') && field.val() != '') {
+			// we know that this value should be added, so check if its there
+			
+			if($.inArray(field.val(), oldWords) == -1) { // its not
+				// determine the group
+				var group = determineGroup(field.val());
+				
+				// check if any alternative there to remove
+				if(group != null) {
+					for(var i = 0; i < group.length; i++) {
+						oldWords = jQuery.grep(oldWords, function(value) {
+							return value != group[i];
+						});
+					}
+				}
+				
+				oldWords.push(field.val());
+			}
+		}
+		else if ( field.is(':checked') && field.val() == '') { // its a none that was selected, so remove any alternatives, it will remove duplicates as well
+			// determine the group
+			var group = determineGroup(field.get(0).name);
+
+			// check if any alternative present to remove
+			if(group != null) {
+				for(var i = 0; i < group.length; i++) {
+					oldWords = jQuery.grep(oldWords, function(value) {
+						return value != group[i];
+					});
+				}
+			}
+		}
+		else if ( !field.is(':checked') && field.get(0).type === "checkbox") {
+			// its a checkbox not selected so check it
+			while($.inArray(field.val(), oldWords) != -1) { // it is, while lus to remove several duplicates
+				oldWords.splice($.inArray(field.val(), oldWords),1); // remove it
+			}
+		}
+
+		return oldWords;
+	}
+	
+	function removeDuplicates(current) {
+		var unique = new Array()
+		for(var i = 0; i < current.length; i++) {
+			if($.inArray(current[i], unique) == -1) {
+				unique.push(current[i]);
+			}
+		}	
+		return unique;
+	}
+	
+	function determineGroup(field) {
+		// determine the group
+		var group = null;
+		if($.inArray(field, friendship) != -1 || field == "friendship") {
+			group = friendship;
+		}
+		else if($.inArray(field, geographical) != -1 || field == "geographical") {
+			group = geographical;
+		}
+		else if($.inArray(field, family) != -1 || field == "family") {
+			group = family;
+		}
+		return group;
+	}
 });
\ No newline at end of file
Index: xfn.js
===================================================================
--- xfn.js	(revision 17572)
+++ xfn.js	(working copy)
@@ -1 +1,291 @@
-jQuery(document).ready(function(a){a("#link_rel").attr("readonly","readonly");a("#linkxfndiv input").bind("click keyup",function(){var c=a("#me").is(":checked"),b="";a("input.valinp").each(function(){if(c){a(this).attr("disabled","disabled").parent().addClass("disabled")}else{a(this).removeAttr("disabled").parent().removeClass("disabled");if(a(this).is(":checked")&&a(this).val()!=""){b+=a(this).val()+" "}}});a("#link_rel").val((c)?"me":b.substr(0,b.length-1))})});
\ No newline at end of file
+jQuery(document).ready( function($) {
+	var friendship = new Array("contact", "acquaintance", "friend");
+	var geographical = new Array("co-resident", "neighbor");
+	var family = new Array("child", "kin", "parent", "sibling", "spouse");
+	
+	$('#link_rel').attr('readonly', '');
+	$('#link_rel').bind('keyup', function() {
+		if($('#link_rel').val().substr($('#link_rel').val().length-1, $('#link_rel').val().length) == ' '){
+			return;
+		}
+		
+		var oldWords = $('#link_rel').val().split(" ");
+		var unique = new Array()
+		for(var i = 0; i < oldWords.length; i++) {
+			if($.inArray(oldWords[i], unique) == -1) {
+				unique.push(oldWords[i]);
+			}
+		}	
+		var count = oldWords.length - unique.length;
+		
+		oldWords = unique;
+
+		if(count > 0) {
+			alert("The input field contains duplicates or none, these will be removed.");
+			
+			var output = "";
+			for(var i = 0; i < oldWords.length; i++) {
+				output = output + oldWords[i] + " ";
+			}
+			
+			output = $.trim(output); // trim both sides cuz the user knows we will change sthg
+			
+			$('#link_rel').val( output );
+		}
+	});
+
+	$('#linkxfndiv .form-table input').bind('click', function() {
+		var isMe = $('#me').is(':checked'), inputs = '', //toRemove = new Array(),
+		wasMe = ($('#link_rel').val() == "me");
+		
+		if($('#link_rel').val() == "me") {
+			var oldWords = new Array();
+		}
+		else {
+			var oldWords = $('#link_rel').val().split(" ");
+		}
+
+		if (isMe) {
+			$('input.valinp').each( function() {
+				$(this).attr('disabled', 'disabled').parent().addClass('disabled');
+			});
+		} 
+		else {
+			$('input.valinp').each( function() {
+				$(this).removeAttr('disabled').parent().removeClass('disabled');
+			});
+
+			if(wasMe) {
+				// trigger the code below for every field to rebuild input
+				$('input.valinp').each( function() {
+					oldWords = updateField($(this), oldWords) ;
+				});
+			}
+			else {
+				// execute only for clicked field
+				oldWords = updateField($(this), oldWords) ;
+			}
+		}
+		
+		var output = "";
+		for(var i = 0; i < oldWords.length; i++) {
+			output = output + oldWords[i] + " ";
+		}
+		output = $.trim(output);
+
+		$('#link_rel').val( (isMe) ? 'me' : output );
+	});
+	
+	function updateField(field, oldWords) {
+		if ( field.is(':checked') && field.val() != '') {
+			// we know that this value should be added, so check if its there
+			
+			if($.inArray(field.val(), oldWords) == -1) { // its not
+				// determine the group
+				var group = determineGroup(field.val());
+				
+				// check if any alternative there to remove
+				if(group != null) {
+					for(var i = 0; i < group.length; i++) {
+						oldWords = jQuery.grep(oldWords, function(value) {
+							return value != group[i];
+						});
+					}
+				}
+				
+				oldWords.push(field.val());
+			}
+		}
+		else if ( field.is(':checked') && field.val() == '') { // its a none that was selected, so remove any alternatives, it will remove duplicates as well
+			// determine the group
+			var group = determineGroup(field.get(0).name);
+
+			// check if any alternative present to remove
+			if(group != null) {
+				for(var i = 0; i < group.length; i++) {
+					oldWords = jQuery.grep(oldWords, function(value) {
+						return value != group[i];
+					});
+				}
+			}
+		}
+		else if ( !field.is(':checked') && field.get(0).type === "checkbox") {
+			// its a checkbox not selected so check it
+			while($.inArray(field.val(), oldWords) != -1) { // it is, while lus to remove several duplicates
+				oldWords.splice($.inArray(field.val(), oldWords),1); // remove it
+			}
+		}
+
+		return oldWords;
+	}
+	
+	function removeDuplicates(current) {
+		var unique = new Array()
+		for(var i = 0; i < current.length; i++) {
+			if($.inArray(current[i], unique) == -1) {
+				unique.push(current[i]);
+			}
+		}	
+		return unique;
+	}
+	
+	function determineGroup(field) {
+		// determine the group
+		var group = null;
+		if($.inArray(field, friendship) != -1 || field == "friendship") {
+			group = friendship;
+		}
+		else if($.inArray(field, geographical) != -1 || field == "geographical") {
+			group = geographical;
+		}
+		else if($.inArray(field, family) != -1 || field == "family") {
+			group = family;
+		}
+		return group;
+	}
+});jQuery(document).ready( function($) {
+	var friendship = new Array("contact", "acquaintance", "friend");
+	var geographical = new Array("co-resident", "neighbor");
+	var family = new Array("child", "kin", "parent", "sibling", "spouse");
+	
+	$('#link_rel').attr('readonly', '');
+	$('#link_rel').bind('keyup', function() {
+		if($('#link_rel').val().substr($('#link_rel').val().length-1, $('#link_rel').val().length) == ' '){
+			return;
+		}
+		
+		var oldWords = $('#link_rel').val().split(" ");
+		var unique = new Array()
+		for(var i = 0; i < oldWords.length; i++) {
+			if($.inArray(oldWords[i], unique) == -1) {
+				unique.push(oldWords[i]);
+			}
+		}	
+		var count = oldWords.length - unique.length;
+		
+		oldWords = unique;
+
+		if(count > 0) {
+			alert("The input field contains duplicates or none, these will be removed.");
+			
+			var output = "";
+			for(var i = 0; i < oldWords.length; i++) {
+				output = output + oldWords[i] + " ";
+			}
+			
+			output = $.trim(output); // trim both sides cuz the user knows we will change sthg
+			
+			$('#link_rel').val( output );
+		}
+	});
+
+	$('#linkxfndiv .form-table input').bind('click', function() {
+		var isMe = $('#me').is(':checked'), inputs = '', //toRemove = new Array(),
+		wasMe = ($('#link_rel').val() == "me");
+		
+		if($('#link_rel').val() == "me") {
+			var oldWords = new Array();
+		}
+		else {
+			var oldWords = $('#link_rel').val().split(" ");
+		}
+
+		if (isMe) {
+			$('input.valinp').each( function() {
+				$(this).attr('disabled', 'disabled').parent().addClass('disabled');
+			});
+		} 
+		else {
+			$('input.valinp').each( function() {
+				$(this).removeAttr('disabled').parent().removeClass('disabled');
+			});
+
+			if(wasMe) {
+				// trigger the code below for every field to rebuild input
+				$('input.valinp').each( function() {
+					oldWords = updateField($(this), oldWords) ;
+				});
+			}
+			else {
+				// execute only for clicked field
+				oldWords = updateField($(this), oldWords) ;
+			}
+		}
+		
+		var output = "";
+		for(var i = 0; i < oldWords.length; i++) {
+			output = output + oldWords[i] + " ";
+		}
+		output = $.trim(output);
+
+		$('#link_rel').val( (isMe) ? 'me' : output );
+	});
+	
+	function updateField(field, oldWords) {
+		if ( field.is(':checked') && field.val() != '') {
+			// we know that this value should be added, so check if its there
+			
+			if($.inArray(field.val(), oldWords) == -1) { // its not
+				// determine the group
+				var group = determineGroup(field.val());
+				
+				// check if any alternative there to remove
+				if(group != null) {
+					for(var i = 0; i < group.length; i++) {
+						oldWords = jQuery.grep(oldWords, function(value) {
+							return value != group[i];
+						});
+					}
+				}
+				
+				oldWords.push(field.val());
+			}
+		}
+		else if ( field.is(':checked') && field.val() == '') { // its a none that was selected, so remove any alternatives, it will remove duplicates as well
+			// determine the group
+			var group = determineGroup(field.get(0).name);
+
+			// check if any alternative present to remove
+			if(group != null) {
+				for(var i = 0; i < group.length; i++) {
+					oldWords = jQuery.grep(oldWords, function(value) {
+						return value != group[i];
+					});
+				}
+			}
+		}
+		else if ( !field.is(':checked') && field.get(0).type === "checkbox") {
+			// its a checkbox not selected so check it
+			while($.inArray(field.val(), oldWords) != -1) { // it is, while lus to remove several duplicates
+				oldWords.splice($.inArray(field.val(), oldWords),1); // remove it
+			}
+		}
+
+		return oldWords;
+	}
+	
+	function removeDuplicates(current) {
+		var unique = new Array()
+		for(var i = 0; i < current.length; i++) {
+			if($.inArray(current[i], unique) == -1) {
+				unique.push(current[i]);
+			}
+		}	
+		return unique;
+	}
+	
+	function determineGroup(field) {
+		// determine the group
+		var group = null;
+		if($.inArray(field, friendship) != -1 || field == "friendship") {
+			group = friendship;
+		}
+		else if($.inArray(field, geographical) != -1 || field == "geographical") {
+			group = geographical;
+		}
+		else if($.inArray(field, family) != -1 || field == "family") {
+			group = family;
+		}
+		return group;
+	}
+});
\ No newline at end of file
