Add client-side validate to category add/edit and add subscription forms.
diff --git a/app/src/main/resources/ApplicationResources.properties b/app/src/main/resources/ApplicationResources.properties
index df4cae1..c887a55 100644
--- a/app/src/main/resources/ApplicationResources.properties
+++ b/app/src/main/resources/ApplicationResources.properties
@@ -180,6 +180,7 @@
 categoriesForm.move.confirm=Move selected categories?
 categoriesForm.remove=Remove
 categoriesForm.imageUrl=Image URL
+categoryForm.badURL=Image URL is invalid
 
 # --------------------------------------------------------- CategoryDeleteOK.jsp
 
@@ -1173,6 +1174,8 @@
 planetGroupSubs.error.fetchingFeed=Error fetching subscription: {0}
 planetSubscription.error.deleting=Error deleting object
 
+planetGroupSubs.badFeedURL=Invalid URL
+
 planetGroupSubs.delete.confirm=Are you sure you want to delete this subscription?
 
 # ------------------------------------------------------------ Planet Roller: groups
diff --git a/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSidebar.jsp b/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSidebar.jsp
index 8d2a29f..970ad33 100644
--- a/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSidebar.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSidebar.jsp
@@ -21,6 +21,9 @@
 <%-- ================================================================== --%>
 <%-- add new custom planet group --%>
 
+<h3><s:text name="mainPage.actions"/></h3>
+<hr size="1" noshade="noshade"/>
+
 <s:url var="createNewUrl" action="planetGroupSubs" namespace="/roller-ui/admin">
     <s:param name="createNew">true</s:param>
 </s:url>
diff --git a/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubs.jsp b/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubs.jsp
index c840705..3822cb7 100644
--- a/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubs.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubs.jsp
@@ -161,8 +161,4 @@
         }
     }
 
-    function validateProperties() {
-
-    }
-
 </script>
\ No newline at end of file
diff --git a/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubsSidebar.jsp b/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubsSidebar.jsp
index f2fee0f..a46e1bb 100644
--- a/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubsSidebar.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/admin/PlanetGroupSubsSidebar.jsp
@@ -23,28 +23,50 @@
 
 <s:if test="!createNew">
 
+    <h3><s:text name="mainPage.actions"/></h3>
+    <hr size="1" noshade="noshade"/>
+
     <s:text name="planetGroupSubs.addFeed"/>
 
-    <s:form action="planetGroupSubs!saveSubscription" theme="bootstrap" cssClass="form-horizontal">
+    <s:form action="planetGroupSubs!saveSubscription"
+            theme="bootstrap" cssClass="form-horizontal" style="margin-top:1em">
         <s:hidden name="salt"/>
         <s:hidden name="group.handle"/>
-        <s:textfield name="subUrl" size="40" maxlength="255" label="%{getText('planetSubscription.feedUrl')}"/>
+
+        <s:textfield name="subUrl" size="40" maxlength="255" label="%{getText('planetSubscription.feedUrl')}"
+            onchange="validateUrl()" onkeyup="validateUrl()" />
+
+        <p id="feedback-area" style="clear:right; width:100%"></p>
+
         <s:submit value="%{getText('generic.save')}" cssClass="btn btn-default" />
+
     </s:form>
 
+    <script>
+
+        function validateUrl() {
+            var feedbackArea = $("#feedback-area");
+            var url = $("#planetGroupSubs_subUrl").val();
+            var saveButton = $('#planetGroupSubs_0');
+
+            if (url && url.trim() !== '') {
+                if (!isValidUrl(url)) {
+                    saveButton.attr("disabled", true);
+                    feedbackArea.html('<s:text name="planetGroupSubs.badFeedURL" />');
+                    feedbackArea.css("color", "red");
+                    return;
+                }
+            }
+
+            feedbackArea.html('');
+            saveButton.attr("disabled", false);
+        }
+
+        $( document ).ready(function() {
+            var saveButton = $('#planetGroupSubs_0');
+            saveButton.attr("disabled", true);
+        });
+    </script>
+
 </s:if>
 
-<%-- ================================================================== --%>
-
-<script>
-
-    function isValidUrl(url) {
-        return /^(http|https|ftp):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i.test(url);
-    }
-
-    function validateUrl() {
-
-    }
-
-</script>
-
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/Bookmarks.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/Bookmarks.jsp
index 0fe20f2..3b11853 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/Bookmarks.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/Bookmarks.jsp
@@ -790,9 +790,6 @@
         }
     }
 
-    function isValidUrl(url) {
-        return /^(http|https|ftp):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i.test(url);
-    }
 
     function saveBookmark() {
 
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/Categories.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/Categories.jsp
index 39ccd7b..bf4761b 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/Categories.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/Categories.jsp
@@ -119,9 +119,13 @@
                     <%-- action needed here because we are using AJAX to post this form --%>
                     <s:hidden name="action:categoryEdit!save" value="save"/>
 
-                    <s:textfield name="bean.name" label="%{getText('generic.name')}" maxlength="255"/>
+                    <s:textfield name="bean.name" label="%{getText('generic.name')}" maxlength="255"
+                                 onchange="validateCategory()" onkeyup="validateCategory()" />
+
                     <s:textfield name="bean.description" label="%{getText('generic.description')}"/>
-                    <s:textfield name="bean.image" label="%{getText('categoryForm.image')}"/>
+
+                    <s:textfield name="bean.image" label="%{getText('categoryForm.image')}"
+                                 onchange="validateCategory()" onkeyup="validateCategory()" />
                 </s:form>
             </div>
 
@@ -153,6 +157,34 @@
         $('#categoryEditForm_bean_image').val(image);
 
         $('#category-edit-modal').modal({show: true});
+
+    }
+
+    function validateCategory() {
+
+        var saveCategoryButton = $('#categoryEditForm:first');
+
+        var categoryName = $("#categoryEditForm_bean_name").val();
+        var imageURL = $("#categoryEditForm_bean_image").val();
+
+        if (!categoryName || categoryName.trim() === '') {
+            saveCategoryButton.attr("disabled", true);
+            feedbackAreaEdit.html('<s:text name="categoryForm.requiredFields" />');
+            feedbackAreaEdit.css("color", "red");
+            return;
+        }
+
+        if (imageURL && imageURL.trim() !== '') {
+            if (!isValidUrl(imageURL)) {
+                saveCategoryButton.attr("disabled", true);
+                feedbackAreaEdit.html('<s:text name="categoryForm.badURL" />');
+                feedbackAreaEdit.css("color", "red");
+                return;
+            }
+        }
+
+        feedbackAreaEdit.html('');
+        saveCategoryButton.attr("disabled", false);
     }
 
     function submitEditedCategory() {
diff --git a/app/src/main/webapp/WEB-INF/jsps/editor/CategoriesSidebar.jsp b/app/src/main/webapp/WEB-INF/jsps/editor/CategoriesSidebar.jsp
index 7f2d449..ca4b34a 100644
--- a/app/src/main/webapp/WEB-INF/jsps/editor/CategoriesSidebar.jsp
+++ b/app/src/main/webapp/WEB-INF/jsps/editor/CategoriesSidebar.jsp
@@ -46,6 +46,8 @@
         $('#categoryEditForm_bean_description').val("");
         $('#categoryEditForm_bean_image').val("");
 
+        validateCategory();
+
         $('#category-edit-modal').modal({show: true});
     }
 
diff --git a/app/src/main/webapp/theme/scripts/roller.js b/app/src/main/webapp/theme/scripts/roller.js
index d7eb78a..bc1a1e7 100644
--- a/app/src/main/webapp/theme/scripts/roller.js
+++ b/app/src/main/webapp/theme/scripts/roller.js
@@ -25,19 +25,19 @@
 
 /* This function is used to get cookies */
 function getCookie(name) {
-	var prefix = name + "=" 
-	var start = document.cookie.indexOf(prefix) 
+	var prefix = name + "=";
+	var start = document.cookie.indexOf(prefix);
 
-	if (start==-1) {
+	if (start===-1) {
 		return null;
 	}
 	
-	var end = document.cookie.indexOf(";", start+prefix.length) 
-	if (end==-1) {
+	var end = document.cookie.indexOf(";", start+prefix.length);
+	if (end===-1) {
 		end=document.cookie.length;
 	}
 
-	var value=document.cookie.substring(start+prefix.length, end) 
+	var value=document.cookie.substring(start+prefix.length, end);
 	return unescape(value);
 }
 
@@ -70,7 +70,7 @@
 function toggle(targetId) {
     if (document.getElementById) {
         target = document.getElementById(targetId);
-    	if (target.style.display == "none") {
+    	if (target.style.display === "none") {
     		target.style.display = "";            
     	} else {
     		target.style.display = "none";
@@ -83,7 +83,7 @@
     var expanded;
     if (document.getElementById) {
         target = document.getElementById(targetId);
-    	if (target.style.display == "none") {
+    	if (target.style.display === "none") {
     		target.style.display = "";    
             expanded = true;        
     	} else {
@@ -102,7 +102,7 @@
 function togglePlusMinus(targetId) {
     if (document.getElementById) {
         target = document.getElementById(targetId);
-    	if (target.innerHTML == "+") {
+    	if (target.innerHTML === "+") {
     		target.innerHTML = "-";
     	} else {
     		target.innerHTML = "+";
@@ -116,7 +116,7 @@
     if (folderCookie != null) { // we have user's last setting
         folder = document.getElementById(folderId);
         plusMinus = document.getElementById("i"+folderId);
-        if (folderCookie == "true") { // show
+        if (folderCookie === "true") { // show
             folder.style.display = "";
             plusMinus.innerHTML = "-";
         } else { // hide
@@ -127,11 +127,11 @@
 }
 
 function toggleNextRow(e) {
-    if (e.type == "checkbox") {
+    if (e.type === "checkbox") {
         var checked = e.checked;
-    } else if (e.type == "radio") {
+    } else if (e.type === "radio") {
         var v = e.value;
-        var checked = (v == "1" || v == "y" || v == "true") ? true : false;
+        var checked = (v === "1" || v === "y" || v === "true");
     }
     // var nextRow = e.parentNode.parentNode.nextSibling;
     // the above doesn't work on Mozilla since it treats white space as nodes
@@ -139,7 +139,7 @@
     var tableBody = thisRow.parentNode;
     var nextRow = tableBody.getElementsByTagName("tr")[thisRow.rowIndex+1];
     
-    if (checked == true) {
+    if (checked === true) {
         nextRow.style.display = "";
     } else {
         nextRow.style.display = "none";
@@ -151,7 +151,7 @@
     if (document.getElementById) {
         target = document.getElementById(targetId);
         toggle = document.getElementById(toggleId);
-    	if (target.style.display == "none") {
+    	if (target.style.display === "none") {
     		target.style.display = "";  
             expanded = true;  
                   
@@ -171,7 +171,7 @@
 function isblank(s) {
    for (var i=0; i<s.length; s++) {
       var c = s.charAt(i);
-      if ((c!=' ') && (c!='\n') && (c!='')) return false;
+      if ((c!==' ') && (c!=='\n') && (c!=='')) return false;
    }
     return true;
 }
@@ -183,8 +183,8 @@
 function toggleFunctionAll(toggle) {
 	var inputs = document.getElementsByTagName('input');
 	for(var i = 0; i < inputs.length ; i++) {
-		if(inputs[i].name != "control" && inputs[i].type == 'checkbox' && inputs[i].disabled == false ) {
-			if (inputs[i].checked == true){
+		if(inputs[i].name !== "control" && inputs[i].type === 'checkbox' && inputs[i].disabled === false ) {
+			if (inputs[i].checked === true){
 				inputs[i].checked = !inputs[i].checked;
 			} else{
 				inputs[i].checked = toggle;
@@ -192,11 +192,16 @@
 		}
 	}
 }
+
 function toggleFunction(toggle,name) {;
 	var inputs = document.getElementsByName(name);
 	for(var i = 0; i < inputs.length ; i++) {
-		if(inputs[i].type == 'checkbox' && inputs[i].disabled == false) {
+		if(inputs[i].type === 'checkbox' && inputs[i].disabled === false) {
            inputs[i].checked = toggle;
 		}
 	}
-};
\ No newline at end of file
+}
+
+function isValidUrl(url) {
+    return /^(http|https|ftp):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i.test(url);
+}