diff --git a/NzbDrone.Web/Controllers/SystemController.cs b/NzbDrone.Web/Controllers/SystemController.cs
index 3c079803b..ae6b99a9d 100644
--- a/NzbDrone.Web/Controllers/SystemController.cs
+++ b/NzbDrone.Web/Controllers/SystemController.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Mvc;
+using System.Web.Script.Serialization;
using NzbDrone.Common;
using NzbDrone.Core.Helpers;
using NzbDrone.Core.Jobs;
@@ -43,8 +44,6 @@ namespace NzbDrone.Web.Controllers
});
var jobs = _jobProvider.All();
-
-
return View(jobs);
}
@@ -55,30 +54,36 @@ namespace NzbDrone.Web.Controllers
public ActionResult Config()
{
- return View(_configProvider.All());
+ var config = _configProvider.All();
+ var serialized = new JavaScriptSerializer().Serialize(config);
+
+ return View((object)serialized);
}
- [GridAction]
- public ActionResult _SelectAjaxEditing()
+ public JsonResult SelectConfigAjax()
{
- return View(new GridModel(_configProvider.All()));
+ var config = _configProvider.All();
+
+ return Json(new
+ {
+ iTotalRecords = config.Count,
+ iTotalDisplayRecords = config.Count,
+ aaData = config
+ }, JsonRequestBehavior.AllowGet);
}
- [AcceptVerbs(HttpVerbs.Post)]
- [GridAction]
- public ActionResult _SaveAjaxEditing(string key, string value)
+ [HttpPost]
+ public string SaveConfigAjax(string id, string value)
{
- _configProvider.SetValue(key, value);
- return View(new GridModel(_configProvider.All()));
+ _configProvider.SetValue(id, value);
+ return value;
}
- [AcceptVerbs(HttpVerbs.Post)]
- [GridAction]
- public ActionResult _InsertAjaxEditing(string key, string value)
+ [HttpPost]
+ public string InsertConfigAjax(string key, string value)
{
-
_configProvider.SetValue(key, value);
- return View(new GridModel(_configProvider.All()));
+ return key;
}
//PostDownloadView
diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj
index d61762e7e..4d3a79049 100644
--- a/NzbDrone.Web/NzbDrone.Web.csproj
+++ b/NzbDrone.Web/NzbDrone.Web.csproj
@@ -391,9 +391,11 @@
+
-
+
+
diff --git a/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.dataTables.editable.js b/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.dataTables.editable.js
new file mode 100644
index 000000000..bd0c287f8
--- /dev/null
+++ b/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.dataTables.editable.js
@@ -0,0 +1,1296 @@
+/*
+* File: jquery.dataTables.editable.js
+* Version: 2.0.8
+* Author: Jovan Popovic
+*
+* Copyright 2010-2011 Jovan Popovic, all rights reserved.
+*
+* This source file is free software, under either the GPL v2 license or a
+* BSD style license, as supplied with this software.
+*
+* This source file is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+* or FITNESS FOR A PARTICULAR PURPOSE.
+*
+* Parameters:
+* @sUpdateURL String URL of the server-side page used for updating cell. Default value is "UpdateData".
+* @sAddURL String URL of the server-side page used for adding new row. Default value is "AddData".
+* @sDeleteURL String URL of the server-side page used to delete row by id. Default value is "DeleteData".
+* @fnShowError Function function(message, action){...} used to show error message. Action value can be "update", "add" or "delete".
+* @sAddNewRowFormId String Id of the form for adding new row. Default id is "formAddNewRow".
+* @oAddNewRowFormOptions Object Options that will be set to the "Add new row" dialog
+* @sAddNewRowButtonId String Id of the button for adding new row. Default id is "btnAddNewRow".
+* @oAddNewRowButtonOptions Object Options that will be set to the "Add new" button
+* @sAddNewRowOkButtonId String Id of the OK button placed in add new row dialog. Default value is "btnAddNewRowOk".
+* @oAddNewRowOkButtonOptions Object Options that will be set to the Ok button in the "Add new row" form
+* @sAddNewRowCancelButtonId String Id of the Cancel button placed in add new row dialog. Default value is "btnAddNewRowCancel".
+* @oAddNewRowCancelButtonOptions Object Options that will be set to the Cancel button in the "Add new row" form
+* @sDeleteRowButtonId String Id of the button for adding new row. Default id is "btnDeleteRow".
+* @oDeleteRowButtonOptions Object Options that will be set to the Delete button
+* @sSelectedRowClass String Class that will be associated to the selected row. Default class is "row_selected".
+* @sReadOnlyCellClass String Class of the cells that should not be editable. Default value is "read_only".
+* @sAddDeleteToolbarSelector String Selector used to identify place where add and delete buttons should be placed. Default value is ".add_delete_toolbar".
+* @fnStartProcessingMode Function function(){...} called when AJAX call is started. Use this function to add "Please wait..." message when some button is pressed.
+* @fnEndProcessingMode Function function(){...} called when AJAX call is ended. Use this function to close "Please wait..." message.
+* @aoColumns Array Array of the JEditable settings that will be applied on the columns
+* @sAddHttpMethod String Method used for the Add AJAX request (default is 'POST')
+* @sAddDataType String Data type expected from the server when adding a row; allowed values are the same as those accepted by JQuery's "datatype" parameter, e.g. 'text' and 'json'. The default is 'text'.
+* @sDeleteHttpMethod String Method used for the Delete AJAX request (default is 'POST')
+* @sDeleteDataType String Data type expected from the server when deleting a row; allowed values are the same as those accepted by JQuery's "datatype" parameter, e.g. 'text' and 'json'. The default is 'text'.
+* @fnOnDeleting Function function(tr, id, fnDeleteRow){...} Function called before row is deleted.
+tr isJQuery object encapsulating row that will be deleted
+id is an id of the record that will be deleted.
+fnDeleteRow(id) callback function that should be called to delete row with id
+returns true if plugin should continue with deleting row, false will abort delete.
+* @fnOnDeleted Function function(status){...} Function called after delete action. Status can be "success" or "failure"
+* @fnOnAdding Function function(){...} Function called before row is added.
+returns true if plugin should continue with adding row, false will abort add.
+* @fnOnNewRowPosted Function function(data) Function that can override default function that is called when server-side sAddURL returns result
+You can use this function to add different behaviour when server-side page returns result
+* @fnOnAdded Function function(status){...} Function called after add action. Status can be "success" or "failure"
+* @fnOnEditing Function function(input){...} Function called before cell is updated.
+input JQuery object wrapping the input element used for editing value in the cell.
+returns true if plugin should continue with sending AJAX request, false will abort update.
+* @fnOnEdited Function function(status){...} Function called after edit action. Status can be "success" or "failure"
+* @sEditorHeight String Default height of the cell editors
+* @sEditorWidth String Default width of the cell editors
+* @oDeleteParameters Object Additonal objects added to the DELETE Ajax request
+* @oUpdateParameters Object Additonal objects added to the UPDATE Ajax request
+* @sIDToken String Token in the add new row dialog that will be replaced with a returned id of the record that is created
+* @sSuccessResponse String Text returned from the server if record is successfully deleted or edited. Default "ok"
+*/
+(function ($) {
+
+ $.fn.makeEditable = function (options) {
+
+ var iDisplayStart = 0;
+
+ function fnGetCellID(cell) {
+ ///
+ ///Utility function used to determine id of the cell
+ ///By default it is assumed that id is placed as an id attribute of
that that surround the cell (
tag). E.g.:
+ ///
+ ///
...
...
...
...
+ ///
+ ///
+ ///TD cell refference
+
+ return properties.fnGetRowID($(cell.parentNode));
+ }
+
+ function _fnSetRowIDInAttribute(row, id, overwrite) {
+ ///
+ ///Utility function used to set id of the row. Usually when a new record is created, added to the table,
+ ///and when id of the record is retrieved from the server-side.
+ ///It is assumed that id is placed as an id attribute of
that that surround the cell (
tag). E.g.:
+ ///
+ ///
...
...
...
...
+ ///
+ ///This function is used when a datatable is configured in the server side processing mode or ajax source mode
+ ///
+ ///TR row where record is placed
+
+ if (overwrite) {
+ row.attr("id", id);
+ } else {
+ if (row.attr("id") == null || row.attr("id") == "")
+ row.attr("id", id);
+ }
+ }
+
+ function _fnGetRowIDFromAttribute(row) {
+ ///
+ ///Utility function used to get id of the row.
+ ///It is assumed that id is placed as an id attribute of
that that surround the cell (
tag). E.g.:
+ ///
+ ///
...
...
...
...
+ ///
+ ///This function is used when a datatable is configured in the standard client side mode
+ ///
+ ///TR row where record is placed
+ ///Id of the row - by default id attribute placed in the TR tag
+
+ return row.attr("id");
+ }
+
+ function _fnSetRowIDInFirstCell(row, id) {
+ ///
+ ///Utility function used to set id of the row. Usually when a new record is created, added to the table,
+ ///and when id of the record is retrieved from the server-side).
+ ///It is assumed that id is placed as a value of the first <TD> cell in the <TR>. As example:
+ ///
+ ///
17
...
...
...
+ ///
+ ///This function is used when a datatable is configured in the server side processing mode or ajax source mode
+ ///
+ ///TR row where record is placed
+
+ $("td:first", row).html(id);
+ }
+
+
+ function _fnGetRowIDFromFirstCell(row) {
+ ///
+ ///Utility function used to get id of the row.
+ ///It is assumed that id is placed as a value of the first <TD> cell in the <TR>. As example:
+ ///
+ ///
17
...
...
...
+ ///
+ ///This function is used when a datatable is configured in the server side processing mode or ajax source mode
+ ///
+ ///TR row where record is placed
+ ///Id of the row - by default id attribute placed in the TR tag
+
+ return $("td:first", row).html();
+
+ }
+
+ //Reference to the DataTable object
+ var oTable;
+ //Refences to the buttons used for manipulating table data
+ var oAddNewRowButton, oDeleteRowButton, oConfirmRowAddingButton, oCancelRowAddingButton;
+ //Reference to the form used for adding new data
+ var oAddNewRowForm;
+
+ //Plugin options
+ var properties;
+
+ function _fnShowError(errorText, action) {
+ ///
+ ///Shows an error message (Default function)
+ ///
+ ///text that should be shown
+ /// action that was executed when error occured e.g. "update", "delete", or "add"
+
+ alert(errorText);
+ }
+
+ function _fnStartProcessingMode() {
+ ///
+ ///Function that starts "Processing" mode i.e. shows "Processing..." dialog while some action is executing(Default function)
+ ///
+
+ if (oTable.fnSettings().oFeatures.bProcessing) {
+ $(".dataTables_processing").css('visibility', 'visible');
+ }
+ }
+
+ function _fnEndProcessingMode() {
+ ///
+ ///Function that ends the "Processing" mode and returns the table in the normal state(Default function)
+ ///
+
+ if (oTable.fnSettings().oFeatures.bProcessing) {
+ $(".dataTables_processing").css('visibility', 'hidden');
+ }
+ }
+
+ var sOldValue, sNewCellValue, sNewCellDislayValue;
+
+ function fnApplyEditable(aoNodes) {
+ ///
+ ///Function that applies editable plugin to the array of table rows
+ ///
+ ///Aray of table rows <TR> that should be initialized with editable plugin
+
+ if (properties.bDisableEditing)
+ return;
+ var oDefaultEditableSettings = {
+ event: 'dblclick',
+
+ "onsubmit": function (settings, original) {
+ sOldValue = original.revert;
+ sNewCellValue = null;
+ sNewCellDisplayValue = null;
+ if(settings.type == "text" || settings.type == "select" || settings.type == "textarea" )
+ {
+ var input = $("input,select,textarea", this);
+ sNewCellValue = $("input,select,textarea", $(this)).val();
+ if (input.length == 1) {
+ var oEditElement = input[0];
+ if (oEditElement.nodeName.toLowerCase() == "select" || oEditElement.tagName.toLowerCase() == "select")
+ sNewCellDisplayValue = $("option:selected", oEditElement).text(); //For select list use selected text instead of value for displaying in table
+ else
+ sNewCellDisplayValue = sNewCellValue;
+ }
+
+ if (!properties.fnOnEditing(input))
+ return false;
+ var x = settings;
+ if (settings.cssclass != null) {
+ input.addClass(settings.cssclass);
+ if (!input.valid() || 0 == input.valid())
+ return false;
+ else
+ return true;
+ }
+ }
+
+ iDisplayStart = fnGetDisplayStart();
+ properties.fnStartProcessingMode();
+ },
+ "submitdata": function (value, settings) {
+ //iDisplayStart = fnGetDisplayStart();
+ //properties.fnStartProcessingMode();
+ var id = fnGetCellID(this);
+ var rowId = oTable.fnGetPosition(this)[0];
+ var columnPosition = oTable.fnGetPosition(this)[1];
+ var columnId = oTable.fnGetPosition(this)[2];
+ var sColumnName = oTable.fnSettings().aoColumns[columnId].sName;
+ if (sColumnName == null || sColumnName == "")
+ sColumnName = oTable.fnSettings().aoColumns[columnId].sTitle;
+ var updateData = null;
+ if (properties.aoColumns == null || properties.aoColumns[columnId] == null) {
+ updateData = $.extend({},
+ properties.oUpdateParameters,
+ {
+ "id": id,
+ "rowId": rowId,
+ "columnPosition": columnPosition,
+ "columnId": columnId,
+ "columnName": sColumnName
+ });
+ }
+ else {
+ updateData = $.extend({},
+ properties.oUpdateParameters,
+ properties.aoColumns[columnId].oUpdateParameters,
+ {
+ "id": id,
+ "rowId": rowId,
+ "columnPosition": columnPosition,
+ "columnId": columnId,
+ "columnName": sColumnName
+ });
+ }
+ return updateData;
+ },
+ "callback": function (sValue, settings) {
+ properties.fnEndProcessingMode();
+ var status = "";
+ var aPos = oTable.fnGetPosition(this);
+
+ if (properties.sSuccessResponse == "IGNORE" ||
+ ( properties.aoColumns != null
+ && properties.aoColumns[aPos[2]] != null
+ && properties.aoColumns[aPos[2]].sSuccessResponse == "IGNORE") ||
+ (sNewCellValue == sValue) ||
+ properties.sSuccessResponse == sValue) {
+ if(sNewCellDisplayValue == null)
+ {
+ //sNewCellDisplayValue = sValue;
+ oTable.fnUpdate(sValue, aPos[0], aPos[2]);
+ }else{
+ oTable.fnUpdate(sNewCellDisplayValue, aPos[0], aPos[2]);
+ }
+ $("td.last-updated-cell", oTable).removeClass("last-updated-cell");
+ $(this).addClass("last-updated-cell");
+ status = "success";
+ } else {
+ oTable.fnUpdate(sOldValue, aPos[0], aPos[2]);
+ properties.fnShowError(sValue, "update");
+ status = "failure";
+ }
+
+ properties.fnOnEdited(status, sOldValue, sNewCellDisplayValue, aPos[0], aPos[1], aPos[2]);
+ if (settings.fnOnCellUpdated != null) {
+ settings.fnOnCellUpdated(status, sValue, aPos[0], aPos[2], settings);
+ }
+
+ fnSetDisplayStart();
+ },
+ "onerror": function () {
+ properties.fnEndProcessingMode();
+ properties.fnShowError("Cell cannot be updated", "update");
+ properties.fnOnEdited("failure");
+ },
+ "height": properties.sEditorHeight,
+ "width": properties.sEditorWidth
+ };
+
+ var cells = null;
+
+ if (properties.aoColumns != null) {
+
+ for (var iDTindex = 0, iDTEindex = 0; iDTindex < oSettings.aoColumns.length; iDTindex++) {
+ if (oSettings.aoColumns[iDTindex].bVisible) {//if DataTables column is visible
+ if (properties.aoColumns[iDTEindex] == null) {
+ //If editor for the column is not defined go to the next column
+ iDTEindex++;
+ continue;
+ }
+ //Get all cells in the iDTEindex column (nth child is 1-indexed array)
+ cells = $("td:nth-child(" + (iDTEindex + 1) + ")", aoNodes);
+
+ var oColumnSettings = oDefaultEditableSettings;
+ oColumnSettings = $.extend({}, oDefaultEditableSettings, properties.oEditableSettings, properties.aoColumns[iDTEindex]);
+ iDTEindex++;
+ var sUpdateURL = properties.sUpdateURL;
+ try {
+ if (oColumnSettings.sUpdateURL != null)
+ sUpdateURL = oColumnSettings.sUpdateURL;
+ } catch (ex) {
+ }
+ //cells.editable(sUpdateURL, oColumnSettings);
+ cells.each(function () {
+ if (!$(this).hasClass(properties.sReadOnlyCellClass)) {
+ $(this).editable(sUpdateURL, oColumnSettings);
+ }
+ });
+ }
+
+ } //end for
+ } else {
+ cells = $('td:not(.' + properties.sReadOnlyCellClass + ')', aoNodes);
+ cells.editable(properties.sUpdateURL, $.extend({}, oDefaultEditableSettings, properties.oEditableSettings));
+ }
+ }
+
+ function fnOnRowAdding(event) {
+ ///
+ ///Event handler called when a user click on the submit button in the "Add new row" form.
+ ///
+ ///Event that caused the action
+
+ if (properties.fnOnAdding()) {
+ if (oAddNewRowForm.valid()) {
+ iDisplayStart = fnGetDisplayStart();
+ properties.fnStartProcessingMode();
+
+ if (properties.bUseFormsPlugin) {
+ //Still in beta(development)
+ $(oAddNewRowForm).ajaxSubmit({
+ dataType: 'xml',
+ success: function (response, statusString, xhr) {
+ if (xhr.responseText.toLowerCase().indexOf("error") != -1) {
+ properties.fnEndProcessingMode();
+ properties.fnShowError(xhr.responseText.replace("Error",""), "add");
+ properties.fnOnAdded("failure");
+ } else {
+ fnOnRowAdded(xhr.responseText);
+ }
+
+ },
+ error: function (response) {
+ properties.fnEndProcessingMode();
+ properties.fnShowError(response.responseText, "add");
+ properties.fnOnAdded("failure");
+ }
+ }
+ );
+
+ } else {
+
+ var params = oAddNewRowForm.serialize();
+ $.ajax({ 'url': properties.sAddURL,
+ 'data': params,
+ 'type': properties.sAddHttpMethod,
+ 'dataType': properties.sAddDataType,
+ success: fnOnRowAdded,
+ error: function (response) {
+ properties.fnEndProcessingMode();
+ properties.fnShowError(response.responseText, "add");
+ properties.fnOnAdded("failure");
+ }
+ });
+ }
+ }
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ function _fnOnNewRowPosted(data) {
+ ///Callback function called BEFORE a new record is posted to the server
+ ///TODO: Check this
+
+ return true;
+ }
+
+ function fnAddRowFromForm(oForm) {
+ ///
+ ///Adding a row in the table from the action form
+ ///
+ ///Form that contains data to be copied into the row
+
+ var oSettings = oTable.fnSettings();
+ var iColumnCount = oSettings.aoColumns.length;
+ var values = new Array();
+
+ $("input:text[rel],input:radio[rel][checked],input:hidden[rel],select[rel],textarea[rel],span.datafield[rel],input:checkbox[rel]", oForm).each(function () {
+ var rel = $(this).attr("rel");
+ var sCellValue = "";
+ if (rel >= iColumnCount)
+ properties.fnShowError("In the add form is placed input element with the name '" + $(this).attr("name") + "' with the 'rel' attribute that must be less than a column count - " + iColumnCount, "add");
+ else {
+ if (this.nodeName.toLowerCase() == "select" || this.tagName.toLowerCase() == "select") {
+ //sCellValue = $("option:selected", this).text();
+ sCellValue = $.map(
+ $.makeArray($("option:selected", this)),
+ function (n, i) {
+ return $(n).text();
+ }).join(",");
+ }
+ else if (this.nodeName.toLowerCase() == "span" || this.tagName.toLowerCase() == "span")
+ sCellValue = $(this).html();
+ else {
+ if (this.type == "checkbox") {
+ if (this.checked)
+ sCellValue = (this.value != "on") ? this.value : "true";
+ else
+ sCellValue = (this.value != "on") ? "" : "false";
+ } else
+ sCellValue = this.value;
+ }
+ sCellValue = sCellValue.replace(properties.sIDToken, data);//@BUG What is data?????
+ values[rel] = sCellValue;
+ }
+ });
+
+ //Add values from the form into the table
+ var rtn = oTable.fnAddData(values);
+ var oTRAdded = oTable.fnGetNodes(rtn);
+ //Apply editable plugin on the cells of the table
+ fnApplyEditable(oTRAdded);
+
+ }
+
+ function fnOnRowAdded(data) {
+ ///
+ ///Function that is called when a new row is added, and Ajax response is returned from server
+ ///
+ ///Id of the new row that is returned from the server
+
+ properties.fnEndProcessingMode();
+
+ if (properties.fnOnNewRowPosted(data)) {
+
+ var oSettings = oTable.fnSettings();
+ if (!oSettings.oFeatures.bServerSide) {
+ var iColumnCount = oSettings.aoColumns.length;
+ var values = new Array();
+ var rowData = new Object();
+
+ $("input:text[rel],input:radio[rel][checked],input:hidden[rel],select[rel],textarea[rel],span.datafield[rel],input:checkbox[rel]", oAddNewRowForm).each(function () {
+ var rel = $(this).attr("rel");
+ var sCellValue = "";
+ if (rel >= iColumnCount)
+ properties.fnShowError("In the add form is placed input element with the name '" + $(this).attr("name") + "' with the 'rel' attribute that must be less than a column count - " + iColumnCount, "add");
+ else {
+ if (this.nodeName.toLowerCase() == "select" || this.tagName.toLowerCase() == "select") {
+ //sCellValue = $("option:selected", this).text();
+ sCellValue = $.map(
+ $.makeArray($("option:selected", this)),
+ function (n, i) {
+ return $(n).text();
+ }).join(",");
+ }
+ else if (this.nodeName.toLowerCase() == "span" || this.tagName.toLowerCase() == "span")
+ sCellValue = $(this).html();
+ else {
+ if (this.type == "checkbox") {
+ if (this.checked)
+ sCellValue = (this.value != "on") ? this.value : "true";
+ else
+ sCellValue = (this.value != "on") ? "" : "false";
+ } else
+ sCellValue = this.value;
+ }
+
+ sCellValue = sCellValue.replace(properties.sIDToken, data);
+ if (oSettings.aoColumns != null
+ && oSettings.aoColumns[rel] != null
+ && isNaN(parseInt(oSettings.aoColumns[0].mDataProp))) {
+ rowData[oSettings.aoColumns[rel].mDataProp] = sCellValue;
+ } else {
+ values[rel] = sCellValue;
+ }
+ }
+ });
+
+ var rtn;
+ //Add values from the form into the table
+ if (oSettings.aoColumns != null && isNaN(parseInt(oSettings.aoColumns[0].mDataProp))) {
+ rtn = oTable.fnAddData(rowData);
+ }
+ else {
+ rtn = oTable.fnAddData(values);
+ }
+
+ var oTRAdded = oTable.fnGetNodes(rtn);
+ //add id returned by server page as an TR id attribute
+ properties.fnSetRowID($(oTRAdded), data, true);
+ //Apply editable plugin on the cells of the table
+ fnApplyEditable(oTRAdded);
+
+ $("tr.last-added-row", oTable).removeClass("last-added-row");
+ $(oTRAdded).addClass("last-added-row");
+ } else {
+ oTable.fnDraw(false);
+ }
+ //Close the dialog
+ oAddNewRowForm.dialog('close');
+ $(oAddNewRowForm)[0].reset();
+ $(".error", $(oAddNewRowForm)).html("");
+
+ fnSetDisplayStart();
+ properties.fnOnAdded("success");
+ }
+ }
+
+ function fnOnCancelRowAdding(event) {
+ ///
+ ///Event handler function that is executed when a user press cancel button in the add new row form
+ ///
+ ///DOM event that caused an error
+
+ //Clear the validation messages and reset form
+ $(oAddNewRowForm).validate().resetForm(); // Clears the validation errors
+ $(oAddNewRowForm)[0].reset();
+
+ $(".error", $(oAddNewRowForm)).html("");
+ $(".error", $(oAddNewRowForm)).hide(); // Hides the error element
+
+ //Close the dialog
+ oAddNewRowForm.dialog('close');
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+
+ function fnDisableDeleteButton() {
+ ///
+ ///Function that disables delete button
+ ///
+
+ if (properties.oDeleteRowButtonOptions != null) {
+ //oDeleteRowButton.disable();
+ oDeleteRowButton.button("option", "disabled", true);
+ } else {
+ oDeleteRowButton.attr("disabled", "true");
+ }
+ }
+
+ function fnEnableDeleteButton() {
+ ///
+ ///Function that enables delete button
+ ///
+
+ if (properties.oDeleteRowButtonOptions != null) {
+ //oDeleteRowButton.enable();
+ oDeleteRowButton.button("option", "disabled", false);
+ } else {
+ oDeleteRowButton.removeAttr("disabled");
+ }
+ }
+
+
+
+ function fnDeleteRow(id, sDeleteURL) {
+ ///
+ ///Function that deletes a row with an id, using the sDeleteURL server page
+ ///
+ ///Id of the row that will be deleted. Id value is placed in the attribute of the TR tag that will be deleted
+ ///Server URL where delete request will be posted
+
+ var sURL = sDeleteURL;
+ if (sDeleteURL == null)
+ sURL = properties.sDeleteURL;
+ properties.fnStartProcessingMode();
+ var data = $.extend(properties.oDeleteParameters, { "id": id });
+ $.ajax({ 'url': sURL,
+ 'type': properties.sDeleteHttpMethod,
+ 'data': data,
+ "success": fnOnRowDeleted,
+ "dataType": properties.sDeleteDataType,
+ "error": function (response) {
+ properties.fnEndProcessingMode();
+ properties.fnShowError(response.responseText, "delete");
+ properties.fnOnDeleted("failure");
+
+ }
+ });
+ }
+
+ function _fnOnRowDelete(event) {
+ ///
+ ///Event handler for the delete button
+ ///
+ ///DOM event
+
+ iDisplayStart = fnGetDisplayStart();
+ if ($('tr.' + properties.sSelectedRowClass + ' td', oTable).length == 0) {
+ fnDisableDeleteButton();
+ return;
+ }
+ var id = fnGetCellID($('tr.' + properties.sSelectedRowClass + ' td', oTable)[0]);
+ if (properties.fnOnDeleting($('tr.' + properties.sSelectedRowClass, oTable), id, fnDeleteRow)) {
+ fnDeleteRow(id);
+ }
+ }
+
+ function fnOnRowDeleted(response) {
+ ///
+ ///Called after the record is deleted on the server (in the ajax success callback)
+ ///
+ ///Response text eturned from the server-side page
+
+ properties.fnEndProcessingMode();
+ var oTRSelected = $('tr.' + properties.sSelectedRowClass, oTable)[0];
+ if (response == properties.sSuccessResponse || response == "") {
+ oTable.fnDeleteRow(oTRSelected);
+ fnDisableDeleteButton();
+ fnSetDisplayStart();
+ properties.fnOnDeleted("success");
+ }
+ else {
+ properties.fnShowError(response, "delete");
+ properties.fnOnDeleted("failure");
+ }
+ }
+
+ function _fnOnDeleting(tr, id, fnDeleteRow) {
+ ///
+ ///The default function that is called before row is deleted
+ ///Returning false will abort delete
+ ///Function can be overriden via plugin properties in order to create custom delete functionality
+ ///in that case call fnDeleteRow with parameter id, and return false to prevent double delete action
+ ///
+ ///JQuery wrapper around the TR tag that will be deleted
+ ///Id of the record that wil be deleted
+ ///Function that will be called to delete a row. Default - fnDeleteRow(id)
+
+ return confirm("Are you sure that you want to delete this record?"); ;
+ }
+
+ /* Function called after delete action
+ * @param result string
+ * "success" if row is actually deleted
+ * "failure" if delete failed
+ * @return void
+ */
+ function _fnOnDeleted(result) { }
+
+ function _fnOnEditing(input) { return true; }
+ function _fnOnEdited(result, sOldValue, sNewValue, iRowIndex, iColumnIndex, iRealColumnIndex) {
+
+ }
+
+ function fnOnAdding() { return true; }
+ function _fnOnAdded(result) { }
+
+ var oSettings;
+ function fnGetDisplayStart() {
+ return oSettings._iDisplayStart;
+ }
+
+ function fnSetDisplayStart() {
+ ///
+ ///Set the pagination position(do nothing in the server-side mode)
+ ///
+
+ if (oSettings.oFeatures.bServerSide === false) {
+ oSettings._iDisplayStart = iDisplayStart;
+ oSettings.oApi._fnCalculateEnd(oSettings);
+ //draw the 'current' page
+ oSettings.oApi._fnDraw(oSettings);
+ }
+ }
+
+ function _fnOnBeforeAction(sAction) {
+ return true;
+ }
+
+ function _fnOnActionCompleted(sStatus) {
+
+ }
+
+ function fnGetActionSettings(sAction) {
+ ///Returns settings object for the action
+ ///The name of the action
+
+ if (properties.aoTableAction)
+ properties.fnShowError("Configuration error - aoTableAction setting are not set", sAction);
+ var i = 0;
+
+ for (i = 0; i < properties.aoTableActions.length; i++) {
+ if (properties.aoTableActions[i].sAction == sAction)
+ return properties.aoTableActions[i];
+ }
+
+ properties.fnShowError("Cannot find action configuration settings", sAction);
+ }
+
+
+ function fnUpdateRow(oActionForm) {
+ ///Updates table row using form fields
+ ///Form used to enter data
+
+ var sAction = $(oActionForm).attr("id");
+ sAction = sAction.replace("form", "");
+ var sActionURL = $(oActionForm).attr("action");
+ if (properties.fnOnBeforeAction(sAction)) {
+ if ($(oActionForm).valid()) {
+ iDisplayStart = fnGetDisplayStart();
+ properties.fnStartProcessingMode();
+ if (properties.bUseFormsPlugin) {
+
+ //Still in beta(development)
+ var oAjaxSubmitOptions = {
+ success: function (response, statusString, xhr) {
+ properties.fnEndProcessingMode();
+ if (response.toLowerCase().indexOf("error") != -1 || statusString != "success") {
+ properties.fnShowError(response, sAction);
+ properties.fnOnActionCompleted("failure");
+ } else {
+ fnUpdateRowOnSuccess(oActionForm);
+ properties.fnOnActionCompleted("success");
+ }
+
+ },
+ error: function (response) {
+ properties.fnEndProcessingMode();
+ properties.fnShowError(response.responseText, sAction);
+ properties.fnOnActionCompleted("failure");
+ }
+ };
+ var oActionSettings = fnGetActionSettings(sAction);
+ oAjaxSubmitOptions = $.extend({}, properties.oAjaxSubmitOptions, oAjaxSubmitOptions);
+ $(oActionForm).ajaxSubmit(oAjaxSubmitOptions);
+
+ } else {
+ var params = $(oActionForm).serialize();
+ $.ajax({ 'url': sActionURL,
+ 'data': params,
+ 'type': properties.sAddHttpMethod,
+ 'dataType': properties.sAddDataType,
+ success: function (response) {
+ properties.fnEndProcessingMode();
+ fnUpdateRowOnSuccess(oActionForm);
+ properties.fnOnActionCompleted("success");
+ },
+ error: function (response) {
+ properties.fnEndProcessingMode();
+ properties.fnShowError(response.responseText, sAction);
+ properties.fnOnActionCompleted("failure");
+ }
+ });
+ }
+ }
+ }
+ }
+
+ function fnUpdateRowOnSuccess(oActionForm) {
+ ///Updates table row using form fields after the ajax success callback is executed
+ ///Form used to enter data
+
+ var iRowID = jQuery.data(oActionForm, 'ROWID');
+ //var iDataRowID = jQuery.data(oActionForm, 'DATAROWID');
+ var oSettings = oTable.fnSettings();
+ var iColumnCount = oSettings.aoColumns.length;
+ var values = new Array();
+
+ var sAction = $(oActionForm).attr("id");
+ sAction = sAction.replace("form", "");
+
+ //$("input.ROWID").val(iRowID);
+ //$("input.DATAROWID").val(iDataRowID);
+
+ $("input:text[rel],input:radio[rel][checked],input:hidden[rel],select[rel],textarea[rel],span.datafield[rel],input:checkbox[rel]", oActionForm).each(function () {
+ var rel = $(this).attr("rel");
+ var sCellValue = "";
+ if (rel >= iColumnCount)
+ properties.fnShowError("In the add form is placed input element with the name '" + $(this).attr("name") + "' with the 'rel' attribute that must be less than a column count - " + iColumnCount, "add");
+ else {
+ if (this.nodeName.toLowerCase() == "select" || this.tagName.toLowerCase() == "select") {
+ //sCellValue = $("option:selected", this).text();
+ sCellValue = $.map(
+ $.makeArray($("option:selected", this)),
+ function (n, i) {
+ return $(n).text();
+ }).join(",");
+ }
+ else if (this.nodeName.toLowerCase() == "span" || this.tagName.toLowerCase() == "span")
+ sCellValue = $(this).html();
+ else {
+ if (this.type == "checkbox") {
+ if (this.checked)
+ sCellValue = (this.value != "on") ? this.value : "true";
+ else
+ sCellValue = (this.value != "on") ? "" : "false";
+ } else
+ sCellValue = this.value;
+ }
+
+ //sCellValue = sCellValue.replace(properties.sIDToken, data);
+ //values[rel] = sCellValue;
+ oTable.fnUpdate(sCellValue, iRowID, rel);
+ }
+ });
+ $(oActionForm).dialog('close');
+
+
+ }
+
+ oTable = this;
+
+ var defaults = {
+
+ sUpdateURL: "UpdateData",
+ sAddURL: "AddData",
+ sDeleteURL: "DeleteData",
+ sAddNewRowFormId: "formAddNewRow",
+ oAddNewRowFormOptions: { autoOpen: false, modal: true },
+ sAddNewRowButtonId: "btnAddNewRow",
+ oAddNewRowButtonOptions: null,
+ sAddNewRowOkButtonId: "btnAddNewRowOk",
+ sAddNewRowCancelButtonId: "btnAddNewRowCancel",
+ oAddNewRowOkButtonOptions: { label: "Ok" },
+ oAddNewRowCancelButtonOptions: { label: "Cancel" },
+ sDeleteRowButtonId: "btnDeleteRow",
+ oDeleteRowButtonOptions: null,
+ sSelectedRowClass: "row_selected",
+ sReadOnlyCellClass: "read_only",
+ sAddDeleteToolbarSelector: ".add_delete_toolbar",
+ fnShowError: _fnShowError,
+ fnStartProcessingMode: _fnStartProcessingMode,
+ fnEndProcessingMode: _fnEndProcessingMode,
+ aoColumns: null,
+ fnOnDeleting: _fnOnDeleting,
+ fnOnDeleted: _fnOnDeleted,
+ fnOnAdding: fnOnAdding,
+ fnOnNewRowPosted: _fnOnNewRowPosted,
+ fnOnAdded: _fnOnAdded,
+ fnOnEditing: _fnOnEditing,
+ fnOnEdited: _fnOnEdited,
+ sAddHttpMethod: 'POST',
+ sAddDataType: "text",
+ sDeleteHttpMethod: 'POST',
+ sDeleteDataType: "text",
+ fnGetRowID: _fnGetRowIDFromAttribute,
+ fnSetRowID: _fnSetRowIDInAttribute,
+ sEditorHeight: "100%",
+ sEditorWidth: "100%",
+ bDisableEditing: false,
+ oDeleteParameters: {},
+ oUpdateParameters: {},
+ sIDToken: "DATAROWID",
+ aoTableActions: null,
+ fnOnBeforeAction: _fnOnBeforeAction,
+ bUseFormsPlugin: false,
+ fnOnActionCompleted: _fnOnActionCompleted,
+ sSuccessResponse: "ok"
+
+
+ };
+
+ properties = $.extend(defaults, options);
+ oSettings = oTable.fnSettings();
+
+ return this.each(function () {
+
+ if (oTable.fnSettings().sAjaxSource != null) {
+ oTable.fnSettings().aoDrawCallback.push({
+ "fn": function () {
+ //Apply jEditable plugin on the table cells
+ fnApplyEditable(oTable.fnGetNodes());
+ $(oTable.fnGetNodes()).each(function () {
+ var position = oTable.fnGetPosition(this);
+ var id = oTable.fnGetData(position)[0];
+ properties.fnSetRowID($(this), id);
+ }
+ );
+ },
+ "sName": "fnApplyEditable"
+ });
+
+ } else {
+ //Apply jEditable plugin on the table cells
+ fnApplyEditable(oTable.fnGetNodes());
+ }
+
+ //Setup form to open in dialog
+ oAddNewRowForm = $("#" + properties.sAddNewRowFormId);
+ if (oAddNewRowForm.length != 0) {
+
+ ///Check does the add new form has all nessecary fields
+ var oSettings = oTable.fnSettings();
+ var iColumnCount = oSettings.aoColumns.length;
+ for (i = 0; i < iColumnCount; i++) {
+ if ($("[rel=" + i + "]", oAddNewRowForm).length == 0)
+ properties.fnShowError("In the form that is used for adding new records cannot be found an input element with rel=" + i + " that will be bound to the value in the column " + i + ". See http://code.google.com/p/jquery-datatables-editable/wiki/AddingNewRecords#Add_new_record_form for more details", "init");
+ }
+
+
+ if (properties.oAddNewRowFormOptions != null) {
+ properties.oAddNewRowFormOptions.autoOpen = false;
+ } else {
+ properties.oAddNewRowFormOptions = { autoOpen: false };
+ }
+ oAddNewRowForm.dialog(properties.oAddNewRowFormOptions);
+
+ //Add button click handler on the "Add new row" button
+ oAddNewRowButton = $("#" + properties.sAddNewRowButtonId);
+ if (oAddNewRowButton.length != 0) {
+ oAddNewRowButton.click(function () {
+ oAddNewRowForm.dialog('open');
+ });
+ } else {
+ if ($(properties.sAddDeleteToolbarSelector).length == 0) {
+ throw "Cannot find a button with an id '" + properties.sAddNewRowButtonId + "', or placeholder with an id '" + properties.sAddDeleteToolbarSelector + "' that should be used for adding new row although form for adding new record is specified";
+ } else {
+ oAddNewRowButton = null; //It will be auto-generated later
+ }
+ }
+
+ //Prevent Submit handler
+ if (oAddNewRowForm[0].nodeName.toLowerCase() == "form") {
+ oAddNewRowForm.unbind('submit');
+ oAddNewRowForm.submit(function (event) {
+ fnOnRowAdding(event);
+ return false;
+ });
+ } else {
+ $("form", oAddNewRowForm[0]).unbind('submit');
+ $("form", oAddNewRowForm[0]).submit(function (event) {
+ fnOnRowAdding(event);
+ return false;
+ });
+ }
+
+ // array to add default buttons to
+ var aAddNewRowFormButtons = [];
+
+ oConfirmRowAddingButton = $("#" + properties.sAddNewRowOkButtonId, oAddNewRowForm);
+ if (oConfirmRowAddingButton.length == 0) {
+ //If someone forgotten to set the button text
+ if (properties.oAddNewRowOkButtonOptions.text == null
+ || properties.oAddNewRowOkButtonOptions.text == "") {
+ properties.oAddNewRowOkButtonOptions.text = "Ok";
+ }
+ properties.oAddNewRowOkButtonOptions.click = fnOnRowAdding;
+ properties.oAddNewRowOkButtonOptions.id = properties.sAddNewRowOkButtonId;
+ // push the add button onto the array
+ aAddNewRowFormButtons.push(properties.oAddNewRowOkButtonOptions);
+ } else {
+ oConfirmRowAddingButton.click(fnOnRowAdding);
+ }
+
+ oCancelRowAddingButton = $("#" + properties.sAddNewRowCancelButtonId);
+ if (oCancelRowAddingButton.length == 0) {
+ //If someone forgotten to the button text
+ if (properties.oAddNewRowCancelButtonOptions.text == null
+ || properties.oAddNewRowCancelButtonOptions.text == "") {
+ properties.oAddNewRowCancelButtonOptions.text = "Cancel";
+ }
+ properties.oAddNewRowCancelButtonOptions.click = fnOnCancelRowAdding;
+ properties.oAddNewRowCancelButtonOptions.id = properties.sAddNewRowCancelButtonId;
+ // push the cancel button onto the array
+ aAddNewRowFormButtons.push(properties.oAddNewRowCancelButtonOptions);
+ } else {
+ oCancelRowAddingButton.click(fnOnCancelRowAdding);
+ }
+ // if the array contains elements, add them to the dialog
+ if (aAddNewRowFormButtons.length > 0) {
+ oAddNewRowForm.dialog('option', 'buttons', aAddNewRowFormButtons);
+ }
+ //Issue: It cannot find it with this call:
+ //oConfirmRowAddingButton = $("#" + properties.sAddNewRowOkButtonId, oAddNewRowForm);
+ //oCancelRowAddingButton = $("#" + properties.sAddNewRowCancelButtonId, oAddNewRowForm);
+ oConfirmRowAddingButton = $("#" + properties.sAddNewRowOkButtonId);
+ oCancelRowAddingButton = $("#" + properties.sAddNewRowCancelButtonId);
+ } else {
+ oAddNewRowForm = null;
+ }
+
+ //Set the click handler on the "Delete selected row" button
+ oDeleteRowButton = $('#' + properties.sDeleteRowButtonId);
+ if (oDeleteRowButton.length != 0)
+ oDeleteRowButton.click(_fnOnRowDelete);
+ else {
+ oDeleteRowButton = null;
+ }
+
+ //If an add and delete buttons does not exists but Add-delete toolbar is specificed
+ //Autogenerate these buttons
+ oAddDeleteToolbar = $(properties.sAddDeleteToolbarSelector);
+ if (oAddDeleteToolbar.length != 0) {
+ if (oAddNewRowButton == null && properties.sAddNewRowButtonId != ""
+ && oAddNewRowForm != null) {
+ oAddDeleteToolbar.append("");
+ oAddNewRowButton = $("#" + properties.sAddNewRowButtonId);
+ oAddNewRowButton.click(function () { oAddNewRowForm.dialog('open'); });
+ }
+ if (oDeleteRowButton == null && properties.sDeleteRowButtonId != "") {
+ oAddDeleteToolbar.append("");
+ oDeleteRowButton = $("#" + properties.sDeleteRowButtonId);
+ oDeleteRowButton.click(_fnOnRowDelete);
+ }
+ }
+
+ //If delete button exists disable it until some row is selected
+ if (oDeleteRowButton != null) {
+ if (properties.oDeleteRowButtonOptions != null) {
+ oDeleteRowButton.button(properties.oDeleteRowButtonOptions);
+ }
+ fnDisableDeleteButton();
+ }
+
+ //If add button exists convert it to the JQuery-ui button
+ if (oAddNewRowButton != null) {
+ if (properties.oAddNewRowButtonOptions != null) {
+ oAddNewRowButton.button(properties.oAddNewRowButtonOptions);
+ }
+ }
+
+
+ //If form ok button exists convert it to the JQuery-ui button
+ if (oConfirmRowAddingButton != null) {
+ if (properties.oAddNewRowOkButtonOptions != null) {
+ oConfirmRowAddingButton.button(properties.oAddNewRowOkButtonOptions);
+ }
+ }
+
+ //If form cancel button exists convert it to the JQuery-ui button
+ if (oCancelRowAddingButton != null) {
+ if (properties.oAddNewRowCancelButtonOptions != null) {
+ oCancelRowAddingButton.button(properties.oAddNewRowCancelButtonOptions);
+ }
+ }
+
+ //Add handler to the inline delete buttons
+ $(".table-action-deletelink", oTable).live("click", function (e) {
+
+ e.preventDefault();
+ e.stopPropagation();
+ var sURL = $(this).attr("href");
+
+ if (sURL == null || sURL == "")
+ sURL = properties.sDeleteURL;
+
+ iDisplayStart = fnGetDisplayStart();
+ var oTD = ($(this).parents('td'))[0];
+ var oTR = ($(this).parents('tr'))[0];
+
+ $(oTR).addClass(properties.sSelectedRowClass);
+
+ var id = fnGetCellID(oTD);
+ if (properties.fnOnDeleting(oTD, id, fnDeleteRow)) {
+ fnDeleteRow(id, sURL);
+ }
+
+
+ }
+ );
+
+ //Add handler to the inline delete buttons
+ $(".table-action-editlink", oTable).live("click", function (e) {
+
+ e.preventDefault();
+ e.stopPropagation();
+ var sURL = $(this).attr("href");
+
+ if (sURL == null || sURL == "")
+ sURL = properties.sDeleteURL;
+
+ iDisplayStart = fnGetDisplayStart();
+ var oTD = ($(this).parents('td'))[0];
+ var oTR = ($(this).parents('tr'))[0];
+
+ $(oTR).addClass(properties.sSelectedRowClass);
+
+ var id = fnGetCellID(oTD);
+ if (properties.fnOnDeleting(oTD, id, fnDeleteRow)) {
+ fnDeleteRow(id, sURL);
+ }
+
+
+ }
+ );
+
+ //Set selected class on row that is clicked
+ //Enable delete button if row is selected, disable delete button if selected class is removed
+ $("tbody", oTable).click(function (event) {
+ if ($(event.target.parentNode).hasClass(properties.sSelectedRowClass)) {
+ $(event.target.parentNode).removeClass(properties.sSelectedRowClass);
+ if (oDeleteRowButton != null) {
+ fnDisableDeleteButton();
+ }
+ } else {
+ $(oTable.fnSettings().aoData).each(function () {
+ $(this.nTr).removeClass(properties.sSelectedRowClass);
+ });
+ $(event.target.parentNode).addClass(properties.sSelectedRowClass);
+ if (oDeleteRowButton != null) {
+ fnEnableDeleteButton();
+ }
+ }
+ });
+
+
+ if (properties.aoTableActions != null) {
+ for (var i = 0; i < properties.aoTableActions.length; i++) {
+ var oTableAction = $.extend({ sType: "edit" }, properties.aoTableActions[i]);
+ var sAction = oTableAction.sAction;
+ var sActionFormId = oTableAction.sActionFormId;
+
+ var oActionForm = $("#form" + sAction);
+ if (oActionForm.length != 0) {
+ var oFormOptions = { autoOpen: false, modal: true };
+ oFormOptions = $.extend({}, oTableAction.oFormOptions, oFormOptions);
+ oActionForm.dialog(oFormOptions);
+ oActionForm.data("action-options", oTableAction);
+
+ var oActionFormLink = $(".table-action-" + sAction);
+ if (oActionFormLink.length != 0) {
+
+ oActionFormLink.live("click", function () {
+
+
+ var sClass = this.className;
+ var classList = sClass.split(/\s+/);
+ var sActionFormId = "";
+ var sAction = "";
+ for (i = 0; i < classList.length; i++) {
+ if (classList[i].indexOf("table-action-") > -1) {
+ sAction = classList[i].replace("table-action-", "");
+ sActionFormId = "#form" + sAction;
+ }
+ }
+ if (sActionFormId == "") {
+ properties.fnShowError("Cannot find a form with an id " + sActionFormId + " that should be associated to the action - " + sAction, sAction)
+ }
+
+ var oTableAction = $(sActionFormId).data("action-options");
+
+ if (oTableAction.sType == "edit") {
+
+ var oTD = ($(this).parents('td'))[0];
+ var oTR = ($(this).parents('tr'))[0];
+
+ $(oTR).addClass(properties.sSelectedRowClass);
+
+ var iRowID = oTable.fnGetPosition(oTR);
+
+ var id = fnGetCellID(oTD);
+
+ $(sActionFormId).validate().resetForm();
+ jQuery.data($(sActionFormId)[0], 'DATARECORDID', id);
+ $("input.DATARECORDID", $(sActionFormId)).val(id);
+ jQuery.data($(sActionFormId)[0], 'ROWID', iRowID);
+ $("input.ROWID", $(sActionFormId)).val(iRowID);
+
+
+ var oSettings = oTable.fnSettings();
+ var iColumnCount = oSettings.aoColumns.length;
+
+
+ $("input:text[rel],input:radio[rel][checked],input:hidden[rel],select[rel],textarea[rel],input:checkbox[rel]",
+ $(sActionFormId)).each(function () {
+ var rel = $(this).attr("rel");
+
+
+ if (rel >= iColumnCount)
+ properties.fnShowError("In the action form is placed input element with the name '" + $(this).attr("name") + "' with the 'rel' attribute that must be less than a column count - " + iColumnCount, "add");
+ else {
+ var sCellValue = oTable.fnGetData(oTR)[rel];
+ if (this.nodeName.toLowerCase() == "select" || this.tagName.toLowerCase() == "select") {
+ //sCellValue = $("option:selected", this).text();
+ /*sCellValue = $.map(
+ $.makeArray($("option:selected", this)),
+ function (n, i) {
+ return $(n).text();
+ }).join(",");
+ */
+ //$(this).val(sCellValue);
+ $(this).attr("value", sCellValue);
+
+ }
+ else if (this.nodeName.toLowerCase() == "span" || this.tagName.toLowerCase() == "span")
+ $(this).html(sCellValue);
+ else {
+ if (this.type == "checkbox") {
+ if (sCellValue == "true") {
+ $(this).attr("checked", true);
+ }
+ } else
+ {
+ if(this.type == "radio"){
+ if(this.value == sCellValue){
+ this.checked = true;
+ }
+ }else{
+ this.value = sCellValue;
+ }
+ }
+ }
+
+ //sCellValue = sCellValue.replace(properties.sIDToken, data);
+ //values[rel] = sCellValue;
+ //oTable.fnUpdate(sCellValue, iRowID, rel);
+ }
+ });
+
+
+ }
+ $(sActionFormId).dialog('open');
+ });
+ }
+
+ oActionForm.submit(function (event) {
+
+ var oTableAction = $(this).data("action-options");
+
+ if (oTableAction.sType == "edit") {
+ ///Start function fnUpdateRow
+ fnUpdateRow(this);
+ ///end function fnUpdateRow
+ } else {
+ fnAddRowFromForm(this);
+ }
+ return false;
+ });
+
+
+ var aActionFormButtons = new Array();
+
+ //var oActionSubmitButton = $("#form" + sAction + "Ok", oActionForm);
+ //aActionFormButtons.push(oActionSubmitButton);
+ var oActionFormCancel = $("#form" + sAction + "Cancel", oActionForm);
+ if (oActionFormCancel.length != 0) {
+ aActionFormButtons.push(oActionFormCancel);
+ oActionFormCancel.click(function () {
+
+ var oActionForm = $(this).parents("form")[0];
+ //Clear the validation messages and reset form
+ $(oActionForm).validate().resetForm(); // Clears the validation errors
+ $(oActionForm)[0].reset();
+
+ $(".error", $(oActionForm)).html("");
+ $(".error", $(oActionForm)).hide(); // Hides the error element
+ $(oActionForm).dialog('close');
+ });
+ }
+
+ //Convert all action form buttons to the JQuery UI buttons
+ $("button", oActionForm).button();
+ /*
+ if (aActionFormButtons.length > 0) {
+ oActionForm.dialog('option', 'buttons', aActionFormButtons);
+ }
+ */
+
+
+
+ }
+
+
+
+
+ } // end for (var i = 0; i < properties.aoTableActions.length; i++)
+ } //end if (properties.aoTableActions != null)
+
+
+ });
+ };
+})(jQuery);
diff --git a/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.jeditable.js b/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.jeditable.js
new file mode 100644
index 000000000..98cd915dd
--- /dev/null
+++ b/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.jeditable.js
@@ -0,0 +1,507 @@
+/*
+ * Jeditable - jQuery in place edit plugin
+ *
+ * Copyright (c) 2006-2008 Mika Tuupola, Dylan Verheul
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Project home:
+ * http://www.appelsiini.net/projects/jeditable
+ *
+ * Based on editable by Dylan Verheul :
+ * http://www.dyve.net/jquery/?editable
+ *
+ */
+
+/**
+ * Version 1.6.2
+ *
+ * ** means there is basic unit tests for this parameter.
+ *
+ * @name Jeditable
+ * @type jQuery
+ * @param String target (POST) URL or function to send edited content to **
+ * @param Hash options additional options
+ * @param String options[method] method to use to send edited content (POST or PUT) **
+ * @param Function options[callback] Function to run after submitting edited content **
+ * @param String options[name] POST parameter name of edited content
+ * @param String options[id] POST parameter name of edited div id
+ * @param Hash options[submitdata] Extra parameters to send when submitting edited content.
+ * @param String options[type] text, textarea or select (or any 3rd party input type) **
+ * @param Integer options[rows] number of rows if using textarea **
+ * @param Integer options[cols] number of columns if using textarea **
+ * @param Mixed options[height] 'auto', 'none' or height in pixels **
+ * @param Mixed options[width] 'auto', 'none' or width in pixels **
+ * @param String options[loadurl] URL to fetch input content before editing **
+ * @param String options[loadtype] Request type for load url. Should be GET or POST.
+ * @param String options[loadtext] Text to display while loading external content.
+ * @param Mixed options[loaddata] Extra parameters to pass when fetching content before editing.
+ * @param Mixed options[data] Or content given as paramameter. String or function.**
+ * @param String options[indicator] indicator html to show when saving
+ * @param String options[tooltip] optional tooltip text via title attribute **
+ * @param String options[event] jQuery event such as 'click' of 'dblclick' **
+ * @param String options[submit] submit button value, empty means no button **
+ * @param String options[cancel] cancel button value, empty means no button **
+ * @param String options[cssclass] CSS class to apply to input form. 'inherit' to copy from parent. **
+ * @param String options[style] Style to apply to input form 'inherit' to copy from parent. **
+ * @param String options[select] true or false, when true text is highlighted ??
+ * @param String options[placeholder] Placeholder text or html to insert when element is empty. **
+ * @param String options[onblur] 'cancel', 'submit', 'ignore' or function ??
+ *
+ * @param Function options[onsubmit] function(settings, original) { ... } called before submit
+ * @param Function options[onreset] function(settings, original) { ... } called before reset
+ * @param Function options[onerror] function(settings, original, xhr) { ... } called on error
+ *
+ * @param Hash options[ajaxoptions] jQuery Ajax options. See docs.jquery.com.
+ *
+ */
+
+(function($) {
+
+ $.fn.editable = function(target, options) {
+
+ var settings = {
+ target : target,
+ name : 'value',
+ id : 'id',
+ type : 'text',
+ width : 'auto',
+ height : 'auto',
+ event : 'click',
+ onblur : 'cancel',
+ loadtype : 'GET',
+ loadtext : 'Loading...',
+ placeholder: 'Click to edit',
+ loaddata : {},
+ submitdata : {},
+ ajaxoptions: {}
+ };
+
+ if(options) {
+ $.extend(settings, options);
+ }
+
+ /* setup some functions */
+ var plugin = $.editable.types[settings.type].plugin || function() { };
+ var submit = $.editable.types[settings.type].submit || function() { };
+ var buttons = $.editable.types[settings.type].buttons
+ || $.editable.types['defaults'].buttons;
+ var content = $.editable.types[settings.type].content
+ || $.editable.types['defaults'].content;
+ var element = $.editable.types[settings.type].element
+ || $.editable.types['defaults'].element;
+ var reset = $.editable.types[settings.type].reset
+ || $.editable.types['defaults'].reset;
+ var callback = settings.callback || function() { };
+ var onsubmit = settings.onsubmit || function() { };
+ var onreset = settings.onreset || function() { };
+ var onerror = settings.onerror || reset;
+
+ /* add custom event if it does not exist */
+ if (!$.isFunction($(this)[settings.event])) {
+ $.fn[settings.event] = function(fn){
+ return fn ? this.bind(settings.event, fn) : this.trigger(settings.event);
+ }
+ }
+
+ /* show tooltip */
+ $(this).attr('title', settings.tooltip);
+
+ settings.autowidth = 'auto' == settings.width;
+ settings.autoheight = 'auto' == settings.height;
+
+ return this.each(function() {
+
+ /* save this to self because this changes when scope changes */
+ var self = this;
+
+ /* inlined block elements lose their width and height after first edit */
+ /* save them for later use as workaround */
+ var savedwidth = $(self).width();
+ var savedheight = $(self).height();
+
+ /* if element is empty add something clickable (if requested) */
+ if (!$.trim($(this).html())) {
+ $(this).html(settings.placeholder);
+ }
+
+ $(this)[settings.event](function(e) {
+
+ /* prevent throwing an exeption if edit field is clicked again */
+ if (self.editing) {
+ return;
+ }
+
+ /* remove tooltip */
+ $(self).removeAttr('title');
+
+ /* figure out how wide and tall we are, saved width and height */
+ /* are workaround for http://dev.jquery.com/ticket/2190 */
+ if (0 == $(self).width()) {
+ //$(self).css('visibility', 'hidden');
+ settings.width = savedwidth;
+ settings.height = savedheight;
+ } else {
+ if (settings.width != 'none') {
+ settings.width =
+ //settings.autowidth ? savedwidth : settings.width
+ settings.autowidth ? $(self).width() : settings.width;
+ }
+ if (settings.height != 'none') {
+ settings.height =
+ //settings.autoheight ? savedheight : settings.height;
+ settings.autoheight ? $(self).height() : settings.height;
+ }
+ }
+ //$(this).css('visibility', '');
+
+ /* remove placeholder text, replace is here because of IE */
+ if ($(this).html().toLowerCase().replace(/;/, '') ==
+ settings.placeholder.toLowerCase().replace(/;/, '')) {
+ $(this).html('');
+ }
+
+ self.editing = true;
+ self.revert = $(self).html();
+ $(self).html('');
+
+ /* create the form object */
+ var form = $('');
+
+ /* apply css or style or both */
+ if (settings.cssclass) {
+ if ('inherit' == settings.cssclass) {
+ form.attr('class', $(self).attr('class'));
+ } else {
+ form.attr('class', settings.cssclass);
+ }
+ }
+
+ if (settings.style) {
+ if ('inherit' == settings.style) {
+ form.attr('style', $(self).attr('style'));
+ /* IE needs the second line or display wont be inherited */
+ form.css('display', $(self).css('display'));
+ } else {
+ form.attr('style', settings.style);
+ }
+ }
+
+ /* add main input element to form and store it in input */
+ var input = element.apply(form, [settings, self]);
+
+ /* set input content via POST, GET, given data or existing value */
+ var input_content;
+
+ if (settings.loadurl) {
+ var t = setTimeout(function() {
+ input.disabled = true;
+ content.apply(form, [settings.loadtext, settings, self]);
+ }, 100);
+
+ var loaddata = {};
+ loaddata[settings.id] = self.id;
+ if ($.isFunction(settings.loaddata)) {
+ $.extend(loaddata, settings.loaddata.apply(self, [self.revert, settings]));
+ } else {
+ $.extend(loaddata, settings.loaddata);
+ }
+ $.ajax({
+ type : settings.loadtype,
+ url : settings.loadurl,
+ data : loaddata,
+ async : false,
+ success: function(result) {
+ window.clearTimeout(t);
+ input_content = result;
+ input.disabled = false;
+ }
+ });
+ } else if (settings.data) {
+ input_content = settings.data;
+ if ($.isFunction(settings.data)) {
+ input_content = settings.data.apply(self, [self.revert, settings]);
+ }
+ } else {
+ input_content = self.revert;
+ }
+ content.apply(form, [input_content, settings, self]);
+
+ input.attr('name', settings.name);
+
+ /* add buttons to the form */
+ buttons.apply(form, [settings, self]);
+
+ /* add created form to self */
+ $(self).append(form);
+
+ /* attach 3rd party plugin if requested */
+ plugin.apply(form, [settings, self]);
+
+ /* focus to first visible form element */
+ $(':input:visible:enabled:first', form).focus();
+
+ /* highlight input contents when requested */
+ if (settings.select) {
+ input.select();
+ }
+
+ /* discard changes if pressing esc */
+ input.keydown(function(e) {
+ if (e.keyCode == 27) {
+ e.preventDefault();
+ //self.reset();
+ reset.apply(form, [settings, self]);
+ }
+ });
+
+ /* discard, submit or nothing with changes when clicking outside */
+ /* do nothing is usable when navigating with tab */
+ var t;
+ if ('cancel' == settings.onblur) {
+ input.blur(function(e) {
+ /* prevent canceling if submit was clicked */
+ t = setTimeout(function() {
+ reset.apply(form, [settings, self]);
+ }, 500);
+ });
+ } else if ('submit' == settings.onblur) {
+ input.blur(function(e) {
+ /* prevent double submit if submit was clicked */
+ t = setTimeout(function() {
+ form.submit();
+ }, 200);
+ });
+ } else if ($.isFunction(settings.onblur)) {
+ input.blur(function(e) {
+ settings.onblur.apply(self, [input.val(), settings]);
+ });
+ } else {
+ input.blur(function(e) {
+ /* TODO: maybe something here */
+ });
+ }
+
+ form.submit(function(e) {
+
+ if (t) {
+ clearTimeout(t);
+ }
+
+ /* do no submit */
+ e.preventDefault();
+
+ /* call before submit hook. */
+ /* if it returns false abort submitting */
+ if (false !== onsubmit.apply(form, [settings, self])) {
+ /* custom inputs call before submit hook. */
+ /* if it returns false abort submitting */
+ if (false !== submit.apply(form, [settings, self])) {
+
+ /* check if given target is function */
+ if ($.isFunction(settings.target)) {
+ var str = settings.target.apply(self, [input.val(), settings]);
+ $(self).html(str);
+ self.editing = false;
+ callback.apply(self, [self.innerHTML, settings]);
+ /* TODO: this is not dry */
+ if (!$.trim($(self).html())) {
+ $(self).html(settings.placeholder);
+ }
+ } else {
+ /* add edited content and id of edited element to POST */
+ var submitdata = {};
+ submitdata[settings.name] = input.val();
+ submitdata[settings.id] = self.id;
+ /* add extra data to be POST:ed */
+ if ($.isFunction(settings.submitdata)) {
+ $.extend(submitdata, settings.submitdata.apply(self, [self.revert, settings]));
+ } else {
+ $.extend(submitdata, settings.submitdata);
+ }
+
+ /* quick and dirty PUT support */
+ if ('PUT' == settings.method) {
+ submitdata['_method'] = 'put';
+ }
+
+ /* show the saving indicator */
+ $(self).html(settings.indicator);
+
+ /* defaults for ajaxoptions */
+ var ajaxoptions = {
+ type : 'POST',
+ data : submitdata,
+ url : settings.target,
+ success : function(result, status) {
+ $(self).html(result);
+ self.editing = false;
+ callback.apply(self, [self.innerHTML, settings]);
+ if (!$.trim($(self).html())) {
+ $(self).html(settings.placeholder);
+ }
+ },
+ error : function(xhr, status, error) {
+ onerror.apply(form, [settings, self, xhr]);
+ }
+ }
+
+ /* override with what is given in settings.ajaxoptions */
+ $.extend(ajaxoptions, settings.ajaxoptions);
+ $.ajax(ajaxoptions);
+
+ }
+ }
+ }
+
+ /* show tooltip again */
+ $(self).attr('title', settings.tooltip);
+
+ return false;
+ });
+ });
+
+ /* privileged methods */
+ this.reset = function(form) {
+ /* prevent calling reset twice when blurring */
+ if (this.editing) {
+ /* before reset hook, if it returns false abort reseting */
+ if (false !== onreset.apply(form, [settings, self])) {
+ $(self).html(self.revert);
+ self.editing = false;
+ if (!$.trim($(self).html())) {
+ $(self).html(settings.placeholder);
+ }
+ /* show tooltip again */
+ $(self).attr('title', settings.tooltip);
+ }
+ }
+ }
+ });
+
+ };
+
+
+ $.editable = {
+ types: {
+ defaults: {
+ element : function(settings, original) {
+ var input = $('');
+ $(this).append(input);
+ return(input);
+ },
+ content : function(string, settings, original) {
+ $(':input:first', this).val(string);
+ },
+ reset : function(settings, original) {
+ original.reset(this);
+ },
+ buttons : function(settings, original) {
+ var form = this;
+ if (settings.submit) {
+ /* if given html string use that */
+ if (settings.submit.match(/>$/)) {
+ var submit = $(settings.submit).click(function() {
+ if (submit.attr("type") != "submit") {
+ form.submit();
+ }
+ });
+ /* otherwise use button with given string as text */
+ } else {
+ var submit = $('');
+ submit.html(settings.submit);
+ }
+ $(this).append(submit);
+ }
+ if (settings.cancel) {
+ /* if given html string use that */
+ if (settings.cancel.match(/>$/)) {
+ var cancel = $(settings.cancel);
+ /* otherwise use button with given string as text */
+ } else {
+ var cancel = $('');
+ cancel.html(settings.cancel);
+ }
+ $(this).append(cancel);
+
+ $(cancel).click(function(event) {
+ //original.reset();
+ if ($.isFunction($.editable.types[settings.type].reset)) {
+ var reset = $.editable.types[settings.type].reset;
+ } else {
+ var reset = $.editable.types['defaults'].reset;
+ }
+ reset.apply(form, [settings, original]);
+ return false;
+ });
+ }
+ }
+ },
+ text: {
+ element : function(settings, original) {
+ var input = $('');
+ if (settings.width != 'none') { input.width(settings.width); }
+ if (settings.height != 'none') { input.height(settings.height); }
+ /* https://bugzilla.mozilla.org/show_bug.cgi?id=236791 */
+ //input[0].setAttribute('autocomplete','off');
+ input.attr('autocomplete','off');
+ $(this).append(input);
+ return(input);
+ }
+ },
+ textarea: {
+ element : function(settings, original) {
+ var textarea = $('');
+ if (settings.rows) {
+ textarea.attr('rows', settings.rows);
+ } else {
+ textarea.height(settings.height);
+ }
+ if (settings.cols) {
+ textarea.attr('cols', settings.cols);
+ } else {
+ textarea.width(settings.width);
+ }
+ $(this).append(textarea);
+ return(textarea);
+ }
+ },
+ select: {
+ element : function(settings, original) {
+ var select = $('');
+ $(this).append(select);
+ return(select);
+ },
+ content : function(string, settings, original) {
+ if (String == string.constructor) {
+ eval ('var json = ' + string);
+ for (var key in json) {
+ if (!json.hasOwnProperty(key)) {
+ continue;
+ }
+ if ('selected' == key) {
+ continue;
+ }
+ var option = $('').val(key).append(json[key]);
+ $('select', this).append(option);
+ }
+ }
+ /* Loop option again to set selected. IE needed this... */
+ $('select', this).children().each(function() {
+ if ($(this).val() == json['selected'] ||
+ $(this).text() == original.revert) {
+ $(this).attr('selected', 'selected');
+ };
+ });
+ }
+ }
+ },
+
+ /* Add new input type */
+ addInputType: function(name, input) {
+ $.editable.types[name] = input;
+ }
+ };
+
+})(jQuery);
diff --git a/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.validate.js b/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.validate.js
new file mode 100644
index 000000000..b2d48c060
--- /dev/null
+++ b/NzbDrone.Web/Scripts/DataTables-1.9.0/media/js/jquery.validate.js
@@ -0,0 +1,1150 @@
+/*
+* Note: While Microsoft is not the author of this file, Microsoft is
+* offering you a license subject to the terms of the Microsoft Software
+* License Terms for Microsoft ASP.NET Model View Controller 3.
+* Microsoft reserves all other rights. The notices below are provided
+* for informational purposes only and are not the license terms under
+* which Microsoft distributed this file.
+*
+* jQuery validation plug-in 1.7
+*
+* http://bassistance.de/jquery-plugins/jquery-plugin-validation/
+* http://docs.jquery.com/Plugins/Validation
+*
+* Copyright (c) 2006 - 2008 Jörn Zaefferer
+*
+* $Id: jquery.validate.js 6403 2009-06-17 14:27:16Z joern.zaefferer $
+*
+*/
+
+(function($) {
+
+$.extend($.fn, {
+ // http://docs.jquery.com/Plugins/Validation/validate
+ validate: function( options ) {
+
+ // if nothing is selected, return nothing; can't chain anyway
+ if (!this.length) {
+ options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" );
+ return;
+ }
+
+ // check if a validator for this form was already created
+ var validator = $.data(this[0], 'validator');
+ if ( validator ) {
+ return validator;
+ }
+
+ validator = new $.validator( options, this[0] );
+ $.data(this[0], 'validator', validator);
+
+ if ( validator.settings.onsubmit ) {
+
+ // allow suppresing validation by adding a cancel class to the submit button
+ this.find("input, button").filter(".cancel").click(function() {
+ validator.cancelSubmit = true;
+ });
+
+ // when a submitHandler is used, capture the submitting button
+ if (validator.settings.submitHandler) {
+ this.find("input, button").filter(":submit").click(function() {
+ validator.submitButton = this;
+ });
+ }
+
+ // validate the form on submit
+ this.submit( function( event ) {
+ if ( validator.settings.debug )
+ // prevent form submit to be able to see console output
+ event.preventDefault();
+
+ function handle() {
+ if ( validator.settings.submitHandler ) {
+ if (validator.submitButton) {
+ // insert a hidden input as a replacement for the missing submit button
+ var hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm);
+ }
+ validator.settings.submitHandler.call( validator, validator.currentForm );
+ if (validator.submitButton) {
+ // and clean up afterwards; thanks to no-block-scope, hidden can be referenced
+ hidden.remove();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ // prevent submit for invalid forms or custom submit handlers
+ if ( validator.cancelSubmit ) {
+ validator.cancelSubmit = false;
+ return handle();
+ }
+ if ( validator.form() ) {
+ if ( validator.pendingRequest ) {
+ validator.formSubmitted = true;
+ return false;
+ }
+ return handle();
+ } else {
+ validator.focusInvalid();
+ return false;
+ }
+ });
+ }
+
+ return validator;
+ },
+ // http://docs.jquery.com/Plugins/Validation/valid
+ valid: function() {
+ if ( $(this[0]).is('form')) {
+ return this.validate().form();
+ } else {
+ var valid = true;
+ var validator = $(this[0].form).validate();
+ this.each(function() {
+ valid &= validator.element(this);
+ });
+ return valid;
+ }
+ },
+ // attributes: space seperated list of attributes to retrieve and remove
+ removeAttrs: function(attributes) {
+ var result = {},
+ $element = this;
+ $.each(attributes.split(/\s/), function(index, value) {
+ result[value] = $element.attr(value);
+ $element.removeAttr(value);
+ });
+ return result;
+ },
+ // http://docs.jquery.com/Plugins/Validation/rules
+ rules: function(command, argument) {
+ var element = this[0];
+
+ if (command) {
+ var settings = $.data(element.form, 'validator').settings;
+ var staticRules = settings.rules;
+ var existingRules = $.validator.staticRules(element);
+ switch(command) {
+ case "add":
+ $.extend(existingRules, $.validator.normalizeRule(argument));
+ staticRules[element.name] = existingRules;
+ if (argument.messages)
+ settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages );
+ break;
+ case "remove":
+ if (!argument) {
+ delete staticRules[element.name];
+ return existingRules;
+ }
+ var filtered = {};
+ $.each(argument.split(/\s/), function(index, method) {
+ filtered[method] = existingRules[method];
+ delete existingRules[method];
+ });
+ return filtered;
+ }
+ }
+
+ var data = $.validator.normalizeRules(
+ $.extend(
+ {},
+ $.validator.metadataRules(element),
+ $.validator.classRules(element),
+ $.validator.attributeRules(element),
+ $.validator.staticRules(element)
+ ), element);
+
+ // make sure required is at front
+ if (data.required) {
+ var param = data.required;
+ delete data.required;
+ data = $.extend({required: param}, data);
+ }
+
+ return data;
+ }
+});
+
+// Custom selectors
+$.extend($.expr[":"], {
+ // http://docs.jquery.com/Plugins/Validation/blank
+ blank: function(a) {return !$.trim("" + a.value);},
+ // http://docs.jquery.com/Plugins/Validation/filled
+ filled: function(a) {return !!$.trim("" + a.value);},
+ // http://docs.jquery.com/Plugins/Validation/unchecked
+ unchecked: function(a) {return !a.checked;}
+});
+
+// constructor for validator
+$.validator = function( options, form ) {
+ this.settings = $.extend( true, {}, $.validator.defaults, options );
+ this.currentForm = form;
+ this.init();
+};
+
+$.validator.format = function(source, params) {
+ if ( arguments.length == 1 )
+ return function() {
+ var args = $.makeArray(arguments);
+ args.unshift(source);
+ return $.validator.format.apply( this, args );
+ };
+ if ( arguments.length > 2 && params.constructor != Array ) {
+ params = $.makeArray(arguments).slice(1);
+ }
+ if ( params.constructor != Array ) {
+ params = [ params ];
+ }
+ $.each(params, function(i, n) {
+ source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
+ });
+ return source;
+};
+
+$.extend($.validator, {
+
+ defaults: {
+ messages: {},
+ groups: {},
+ rules: {},
+ errorClass: "error",
+ validClass: "valid",
+ errorElement: "label",
+ focusInvalid: true,
+ errorContainer: $( [] ),
+ errorLabelContainer: $( [] ),
+ onsubmit: true,
+ ignore: [],
+ ignoreTitle: false,
+ onfocusin: function(element) {
+ this.lastActive = element;
+
+ // hide error label and remove error class on focus if enabled
+ if ( this.settings.focusCleanup && !this.blockFocusCleanup ) {
+ this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass );
+ this.errorsFor(element).hide();
+ }
+ },
+ onfocusout: function(element) {
+ if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) {
+ this.element(element);
+ }
+ },
+ onkeyup: function(element) {
+ if ( element.name in this.submitted || element == this.lastElement ) {
+ this.element(element);
+ }
+ },
+ onclick: function(element) {
+ // click on selects, radiobuttons and checkboxes
+ if ( element.name in this.submitted )
+ this.element(element);
+ // or option elements, check parent select in that case
+ else if (element.parentNode.name in this.submitted)
+ this.element(element.parentNode);
+ },
+ highlight: function( element, errorClass, validClass ) {
+ $(element).addClass(errorClass).removeClass(validClass);
+ },
+ unhighlight: function( element, errorClass, validClass ) {
+ $(element).removeClass(errorClass).addClass(validClass);
+ }
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults
+ setDefaults: function(settings) {
+ $.extend( $.validator.defaults, settings );
+ },
+
+ messages: {
+ required: "This field is required.",
+ remote: "Please fix this field.",
+ email: "Please enter a valid email address.",
+ url: "Please enter a valid URL.",
+ date: "Please enter a valid date.",
+ dateISO: "Please enter a valid date (ISO).",
+ number: "Please enter a valid number.",
+ digits: "Please enter only digits.",
+ creditcard: "Please enter a valid credit card number.",
+ equalTo: "Please enter the same value again.",
+ accept: "Please enter a value with a valid extension.",
+ maxlength: $.validator.format("Please enter no more than {0} characters."),
+ minlength: $.validator.format("Please enter at least {0} characters."),
+ rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
+ range: $.validator.format("Please enter a value between {0} and {1}."),
+ max: $.validator.format("Please enter a value less than or equal to {0}."),
+ min: $.validator.format("Please enter a value greater than or equal to {0}.")
+ },
+
+ autoCreateRanges: false,
+
+ prototype: {
+
+ init: function() {
+ this.labelContainer = $(this.settings.errorLabelContainer);
+ this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
+ this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer );
+ this.submitted = {};
+ this.valueCache = {};
+ this.pendingRequest = 0;
+ this.pending = {};
+ this.invalid = {};
+ this.reset();
+
+ var groups = (this.groups = {});
+ $.each(this.settings.groups, function(key, value) {
+ $.each(value.split(/\s/), function(index, name) {
+ groups[name] = key;
+ });
+ });
+ var rules = this.settings.rules;
+ $.each(rules, function(key, value) {
+ rules[key] = $.validator.normalizeRule(value);
+ });
+
+ function delegate(event) {
+ var validator = $.data(this[0].form, "validator"),
+ eventType = "on" + event.type.replace(/^validate/, "");
+ validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] );
+ }
+ $(this.currentForm)
+ .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate)
+ .validateDelegate(":radio, :checkbox, select, option", "click", delegate);
+
+ if (this.settings.invalidHandler)
+ $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/form
+ form: function() {
+ this.checkForm();
+ $.extend(this.submitted, this.errorMap);
+ this.invalid = $.extend({}, this.errorMap);
+ if (!this.valid())
+ $(this.currentForm).triggerHandler("invalid-form", [this]);
+ this.showErrors();
+ return this.valid();
+ },
+
+ checkForm: function() {
+ this.prepareForm();
+ for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) {
+ this.check( elements[i] );
+ }
+ return this.valid();
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/element
+ element: function( element ) {
+ element = this.clean( element );
+ this.lastElement = element;
+ this.prepareElement( element );
+ this.currentElements = $(element);
+ var result = this.check( element );
+ if ( result ) {
+ delete this.invalid[element.name];
+ } else {
+ this.invalid[element.name] = true;
+ }
+ if ( !this.numberOfInvalids() ) {
+ // Hide error containers on last error
+ this.toHide = this.toHide.add( this.containers );
+ }
+ this.showErrors();
+ return result;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/showErrors
+ showErrors: function(errors) {
+ if(errors) {
+ // add items to error list and map
+ $.extend( this.errorMap, errors );
+ this.errorList = [];
+ for ( var name in errors ) {
+ this.errorList.push({
+ message: errors[name],
+ element: this.findByName(name)[0]
+ });
+ }
+ // remove items from success list
+ this.successList = $.grep( this.successList, function(element) {
+ return !(element.name in errors);
+ });
+ }
+ this.settings.showErrors
+ ? this.settings.showErrors.call( this, this.errorMap, this.errorList )
+ : this.defaultShowErrors();
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/resetForm
+ resetForm: function() {
+ if ( $.fn.resetForm )
+ $( this.currentForm ).resetForm();
+ this.submitted = {};
+ this.prepareForm();
+ this.hideErrors();
+ this.elements().removeClass( this.settings.errorClass );
+ },
+
+ numberOfInvalids: function() {
+ return this.objectLength(this.invalid);
+ },
+
+ objectLength: function( obj ) {
+ var count = 0;
+ for ( var i in obj )
+ count++;
+ return count;
+ },
+
+ hideErrors: function() {
+ this.addWrapper( this.toHide ).hide();
+ },
+
+ valid: function() {
+ return this.size() == 0;
+ },
+
+ size: function() {
+ return this.errorList.length;
+ },
+
+ focusInvalid: function() {
+ if( this.settings.focusInvalid ) {
+ try {
+ $(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
+ .filter(":visible")
+ .focus()
+ // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
+ .trigger("focusin");
+ } catch(e) {
+ // ignore IE throwing errors when focusing hidden elements
+ }
+ }
+ },
+
+ findLastActive: function() {
+ var lastActive = this.lastActive;
+ return lastActive && $.grep(this.errorList, function(n) {
+ return n.element.name == lastActive.name;
+ }).length == 1 && lastActive;
+ },
+
+ elements: function() {
+ var validator = this,
+ rulesCache = {};
+
+ // select all valid inputs inside the form (no submit or reset buttons)
+ // workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved
+ return $([]).add(this.currentForm.elements)
+ .filter(":input")
+ .not(":submit, :reset, :image, [disabled]")
+ .not( this.settings.ignore )
+ .filter(function() {
+ !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this);
+
+ // select only the first element for each name, and only those with rules specified
+ if ( this.name in rulesCache || !validator.objectLength($(this).rules()) )
+ return false;
+
+ rulesCache[this.name] = true;
+ return true;
+ });
+ },
+
+ clean: function( selector ) {
+ return $( selector )[0];
+ },
+
+ errors: function() {
+ return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext );
+ },
+
+ reset: function() {
+ this.successList = [];
+ this.errorList = [];
+ this.errorMap = {};
+ this.toShow = $([]);
+ this.toHide = $([]);
+ this.currentElements = $([]);
+ },
+
+ prepareForm: function() {
+ this.reset();
+ this.toHide = this.errors().add( this.containers );
+ },
+
+ prepareElement: function( element ) {
+ this.reset();
+ this.toHide = this.errorsFor(element);
+ },
+
+ check: function( element ) {
+ element = this.clean( element );
+
+ // if radio/checkbox, validate first element in group instead
+ if (this.checkable(element)) {
+ element = this.findByName( element.name )[0];
+ }
+
+ var rules = $(element).rules();
+ var dependencyMismatch = false;
+ for( method in rules ) {
+ var rule = { method: method, parameters: rules[method] };
+ try {
+ var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters );
+
+ // if a method indicates that the field is optional and therefore valid,
+ // don't mark it as valid when there are no other rules
+ if ( result == "dependency-mismatch" ) {
+ dependencyMismatch = true;
+ continue;
+ }
+ dependencyMismatch = false;
+
+ if ( result == "pending" ) {
+ this.toHide = this.toHide.not( this.errorsFor(element) );
+ return;
+ }
+
+ if( !result ) {
+ this.formatAndAdd( element, rule );
+ return false;
+ }
+ } catch(e) {
+ this.settings.debug && window.console && console.log("exception occured when checking element " + element.id
+ + ", check the '" + rule.method + "' method", e);
+ throw e;
+ }
+ }
+ if (dependencyMismatch)
+ return;
+ if ( this.objectLength(rules) )
+ this.successList.push(element);
+ return true;
+ },
+
+ // return the custom message for the given element and validation method
+ // specified in the element's "messages" metadata
+ customMetaMessage: function(element, method) {
+ if (!$.metadata)
+ return;
+
+ var meta = this.settings.meta
+ ? $(element).metadata()[this.settings.meta]
+ : $(element).metadata();
+
+ return meta && meta.messages && meta.messages[method];
+ },
+
+ // return the custom message for the given element name and validation method
+ customMessage: function( name, method ) {
+ var m = this.settings.messages[name];
+ return m && (m.constructor == String
+ ? m
+ : m[method]);
+ },
+
+ // return the first defined argument, allowing empty strings
+ findDefined: function() {
+ for(var i = 0; i < arguments.length; i++) {
+ if (arguments[i] !== undefined)
+ return arguments[i];
+ }
+ return undefined;
+ },
+
+ defaultMessage: function( element, method) {
+ return this.findDefined(
+ this.customMessage( element.name, method ),
+ this.customMetaMessage( element, method ),
+ // title is never undefined, so handle empty string as undefined
+ !this.settings.ignoreTitle && element.title || undefined,
+ $.validator.messages[method],
+ "Warning: No message defined for " + element.name + ""
+ );
+ },
+
+ formatAndAdd: function( element, rule ) {
+ var message = this.defaultMessage( element, rule.method ),
+ theregex = /\$?\{(\d+)\}/g;
+ if ( typeof message == "function" ) {
+ message = message.call(this, rule.parameters, element);
+ } else if (theregex.test(message)) {
+ message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters);
+ }
+ this.errorList.push({
+ message: message,
+ element: element
+ });
+
+ this.errorMap[element.name] = message;
+ this.submitted[element.name] = message;
+ },
+
+ addWrapper: function(toToggle) {
+ if ( this.settings.wrapper )
+ toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) );
+ return toToggle;
+ },
+
+ defaultShowErrors: function() {
+ for ( var i = 0; this.errorList[i]; i++ ) {
+ var error = this.errorList[i];
+ this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass );
+ this.showLabel( error.element, error.message );
+ }
+ if( this.errorList.length ) {
+ this.toShow = this.toShow.add( this.containers );
+ }
+ if (this.settings.success) {
+ for ( var i = 0; this.successList[i]; i++ ) {
+ this.showLabel( this.successList[i] );
+ }
+ }
+ if (this.settings.unhighlight) {
+ for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) {
+ this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass );
+ }
+ }
+ this.toHide = this.toHide.not( this.toShow );
+ this.hideErrors();
+ this.addWrapper( this.toShow ).show();
+ },
+
+ validElements: function() {
+ return this.currentElements.not(this.invalidElements());
+ },
+
+ invalidElements: function() {
+ return $(this.errorList).map(function() {
+ return this.element;
+ });
+ },
+
+ showLabel: function(element, message) {
+ var label = this.errorsFor( element );
+ if ( label.length ) {
+ // refresh error/success class
+ label.removeClass().addClass( this.settings.errorClass );
+
+ // check if we have a generated label, replace the message then
+ label.attr("generated") && label.html(message);
+ } else {
+ // create label
+ label = $("<" + this.settings.errorElement + "/>")
+ .attr({"for": this.idOrName(element), generated: true})
+ .addClass(this.settings.errorClass)
+ .html(message || "");
+ if ( this.settings.wrapper ) {
+ // make sure the element is visible, even in IE
+ // actually showing the wrapped element is handled elsewhere
+ label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
+ }
+ if ( !this.labelContainer.append(label).length )
+ this.settings.errorPlacement
+ ? this.settings.errorPlacement(label, $(element) )
+ : label.insertAfter(element);
+ }
+ if ( !message && this.settings.success ) {
+ label.text("");
+ typeof this.settings.success == "string"
+ ? label.addClass( this.settings.success )
+ : this.settings.success( label );
+ }
+ this.toShow = this.toShow.add(label);
+ },
+
+ errorsFor: function(element) {
+ var name = this.idOrName(element);
+ return this.errors().filter(function() {
+ return $(this).attr('for') == name;
+ });
+ },
+
+ idOrName: function(element) {
+ return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name);
+ },
+
+ checkable: function( element ) {
+ return /radio|checkbox/i.test(element.type);
+ },
+
+ findByName: function( name ) {
+ // select by name and filter by form for performance over form.find("[name=...]")
+ var form = this.currentForm;
+ return $(document.getElementsByName(name)).map(function(index, element) {
+ return element.form == form && element.name == name && element || null;
+ });
+ },
+
+ getLength: function(value, element) {
+ switch( element.nodeName.toLowerCase() ) {
+ case 'select':
+ return $("option:selected", element).length;
+ case 'input':
+ if( this.checkable( element) )
+ return this.findByName(element.name).filter(':checked').length;
+ }
+ return value.length;
+ },
+
+ depend: function(param, element) {
+ return this.dependTypes[typeof param]
+ ? this.dependTypes[typeof param](param, element)
+ : true;
+ },
+
+ dependTypes: {
+ "boolean": function(param, element) {
+ return param;
+ },
+ "string": function(param, element) {
+ return !!$(param, element.form).length;
+ },
+ "function": function(param, element) {
+ return param(element);
+ }
+ },
+
+ optional: function(element) {
+ return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch";
+ },
+
+ startRequest: function(element) {
+ if (!this.pending[element.name]) {
+ this.pendingRequest++;
+ this.pending[element.name] = true;
+ }
+ },
+
+ stopRequest: function(element, valid) {
+ this.pendingRequest--;
+ // sometimes synchronization fails, make sure pendingRequest is never < 0
+ if (this.pendingRequest < 0)
+ this.pendingRequest = 0;
+ delete this.pending[element.name];
+ if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) {
+ $(this.currentForm).submit();
+ this.formSubmitted = false;
+ } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) {
+ $(this.currentForm).triggerHandler("invalid-form", [this]);
+ this.formSubmitted = false;
+ }
+ },
+
+ previousValue: function(element) {
+ return $.data(element, "previousValue") || $.data(element, "previousValue", {
+ old: null,
+ valid: true,
+ message: this.defaultMessage( element, "remote" )
+ });
+ }
+
+ },
+
+ classRuleSettings: {
+ required: {required: true},
+ email: {email: true},
+ url: {url: true},
+ date: {date: true},
+ dateISO: {dateISO: true},
+ dateDE: {dateDE: true},
+ number: {number: true},
+ numberDE: {numberDE: true},
+ digits: {digits: true},
+ creditcard: {creditcard: true}
+ },
+
+ addClassRules: function(className, rules) {
+ className.constructor == String ?
+ this.classRuleSettings[className] = rules :
+ $.extend(this.classRuleSettings, className);
+ },
+
+ classRules: function(element) {
+ var rules = {};
+ var classes = $(element).attr('class');
+ classes && $.each(classes.split(' '), function() {
+ if (this in $.validator.classRuleSettings) {
+ $.extend(rules, $.validator.classRuleSettings[this]);
+ }
+ });
+ return rules;
+ },
+
+ attributeRules: function(element) {
+ var rules = {};
+ var $element = $(element);
+
+ for (method in $.validator.methods) {
+ var value = $element.attr(method);
+ if (value) {
+ rules[method] = value;
+ }
+ }
+
+ // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs
+ if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) {
+ delete rules.maxlength;
+ }
+
+ return rules;
+ },
+
+ metadataRules: function(element) {
+ if (!$.metadata) return {};
+
+ var meta = $.data(element.form, 'validator').settings.meta;
+ return meta ?
+ $(element).metadata()[meta] :
+ $(element).metadata();
+ },
+
+ staticRules: function(element) {
+ var rules = {};
+ var validator = $.data(element.form, 'validator');
+ if (validator.settings.rules) {
+ rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {};
+ }
+ return rules;
+ },
+
+ normalizeRules: function(rules, element) {
+ // handle dependency check
+ $.each(rules, function(prop, val) {
+ // ignore rule when param is explicitly false, eg. required:false
+ if (val === false) {
+ delete rules[prop];
+ return;
+ }
+ if (val.param || val.depends) {
+ var keepRule = true;
+ switch (typeof val.depends) {
+ case "string":
+ keepRule = !!$(val.depends, element.form).length;
+ break;
+ case "function":
+ keepRule = val.depends.call(element, element);
+ break;
+ }
+ if (keepRule) {
+ rules[prop] = val.param !== undefined ? val.param : true;
+ } else {
+ delete rules[prop];
+ }
+ }
+ });
+
+ // evaluate parameters
+ $.each(rules, function(rule, parameter) {
+ rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter;
+ });
+
+ // clean number parameters
+ $.each(['minlength', 'maxlength', 'min', 'max'], function() {
+ if (rules[this]) {
+ rules[this] = Number(rules[this]);
+ }
+ });
+ $.each(['rangelength', 'range'], function() {
+ if (rules[this]) {
+ rules[this] = [Number(rules[this][0]), Number(rules[this][1])];
+ }
+ });
+
+ if ($.validator.autoCreateRanges) {
+ // auto-create ranges
+ if (rules.min && rules.max) {
+ rules.range = [rules.min, rules.max];
+ delete rules.min;
+ delete rules.max;
+ }
+ if (rules.minlength && rules.maxlength) {
+ rules.rangelength = [rules.minlength, rules.maxlength];
+ delete rules.minlength;
+ delete rules.maxlength;
+ }
+ }
+
+ // To support custom messages in metadata ignore rule methods titled "messages"
+ if (rules.messages) {
+ delete rules.messages;
+ }
+
+ return rules;
+ },
+
+ // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true}
+ normalizeRule: function(data) {
+ if( typeof data == "string" ) {
+ var transformed = {};
+ $.each(data.split(/\s/), function() {
+ transformed[this] = true;
+ });
+ data = transformed;
+ }
+ return data;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Validator/addMethod
+ addMethod: function(name, method, message) {
+ $.validator.methods[name] = method;
+ $.validator.messages[name] = message != undefined ? message : $.validator.messages[name];
+ if (method.length < 3) {
+ $.validator.addClassRules(name, $.validator.normalizeRule(name));
+ }
+ },
+
+ methods: {
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/required
+ required: function(value, element, param) {
+ // check if dependency is met
+ if ( !this.depend(param, element) )
+ return "dependency-mismatch";
+ switch( element.nodeName.toLowerCase() ) {
+ case 'select':
+ // could be an array for select-multiple or a string, both are fine this way
+ var val = $(element).val();
+ return val && val.length > 0;
+ case 'input':
+ if ( this.checkable(element) )
+ return this.getLength(value, element) > 0;
+ default:
+ return $.trim(value).length > 0;
+ }
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/remote
+ remote: function(value, element, param) {
+ if ( this.optional(element) )
+ return "dependency-mismatch";
+
+ var previous = this.previousValue(element);
+ if (!this.settings.messages[element.name] )
+ this.settings.messages[element.name] = {};
+ previous.originalMessage = this.settings.messages[element.name].remote;
+ this.settings.messages[element.name].remote = previous.message;
+
+ param = typeof param == "string" && {url:param} || param;
+
+ if ( previous.old !== value ) {
+ previous.old = value;
+ var validator = this;
+ this.startRequest(element);
+ var data = {};
+ data[element.name] = value;
+ $.ajax($.extend(true, {
+ url: param,
+ mode: "abort",
+ port: "validate" + element.name,
+ dataType: "json",
+ data: data,
+ success: function(response) {
+ validator.settings.messages[element.name].remote = previous.originalMessage;
+ var valid = response === true;
+ if ( valid ) {
+ var submitted = validator.formSubmitted;
+ validator.prepareElement(element);
+ validator.formSubmitted = submitted;
+ validator.successList.push(element);
+ validator.showErrors();
+ } else {
+ var errors = {};
+ var message = (previous.message = response || validator.defaultMessage( element, "remote" ));
+ errors[element.name] = $.isFunction(message) ? message(value) : message;
+ validator.showErrors(errors);
+ }
+ previous.valid = valid;
+ validator.stopRequest(element, valid);
+ }
+ }, param));
+ return "pending";
+ } else if( this.pending[element.name] ) {
+ return "pending";
+ }
+ return previous.valid;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/minlength
+ minlength: function(value, element, param) {
+ return this.optional(element) || this.getLength($.trim(value), element) >= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/maxlength
+ maxlength: function(value, element, param) {
+ return this.optional(element) || this.getLength($.trim(value), element) <= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/rangelength
+ rangelength: function(value, element, param) {
+ var length = this.getLength($.trim(value), element);
+ return this.optional(element) || ( length >= param[0] && length <= param[1] );
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/min
+ min: function( value, element, param ) {
+ return this.optional(element) || value >= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/max
+ max: function( value, element, param ) {
+ return this.optional(element) || value <= param;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/range
+ range: function( value, element, param ) {
+ return this.optional(element) || ( value >= param[0] && value <= param[1] );
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/email
+ email: function(value, element) {
+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/
+ return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/url
+ url: function(value, element) {
+ // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/
+ return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/date
+ date: function(value, element) {
+ return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/dateISO
+ dateISO: function(value, element) {
+ return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/number
+ number: function(value, element) {
+ return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/digits
+ digits: function(value, element) {
+ return this.optional(element) || /^\d+$/.test(value);
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/creditcard
+ // based on http://en.wikipedia.org/wiki/Luhn
+ creditcard: function(value, element) {
+ if ( this.optional(element) )
+ return "dependency-mismatch";
+ // accept only digits and dashes
+ if (/[^0-9-]+/.test(value))
+ return false;
+ var nCheck = 0,
+ nDigit = 0,
+ bEven = false;
+
+ value = value.replace(/\D/g, "");
+
+ for (var n = value.length - 1; n >= 0; n--) {
+ var cDigit = value.charAt(n);
+ var nDigit = parseInt(cDigit, 10);
+ if (bEven) {
+ if ((nDigit *= 2) > 9)
+ nDigit -= 9;
+ }
+ nCheck += nDigit;
+ bEven = !bEven;
+ }
+
+ return (nCheck % 10) == 0;
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/accept
+ accept: function(value, element, param) {
+ param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif";
+ return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i"));
+ },
+
+ // http://docs.jquery.com/Plugins/Validation/Methods/equalTo
+ equalTo: function(value, element, param) {
+ // bind to the blur event of the target in order to revalidate whenever the target field is updated
+ // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead
+ var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() {
+ $(element).valid();
+ });
+ return value == target.val();
+ }
+
+ }
+
+});
+
+// deprecated, use $.validator.format instead
+$.format = $.validator.format;
+
+})(jQuery);
+
+// ajax mode: abort
+// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]});
+// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort()
+;(function($) {
+ var ajax = $.ajax;
+ var pendingRequests = {};
+ $.ajax = function(settings) {
+ // create settings for compatibility with ajaxSetup
+ settings = $.extend(settings, $.extend({}, $.ajaxSettings, settings));
+ var port = settings.port;
+ if (settings.mode == "abort") {
+ if ( pendingRequests[port] ) {
+ pendingRequests[port].abort();
+ }
+ return (pendingRequests[port] = ajax.apply(this, arguments));
+ }
+ return ajax.apply(this, arguments);
+ };
+})(jQuery);
+
+// provides cross-browser focusin and focusout events
+// IE has native support, in other browsers, use event caputuring (neither bubbles)
+
+// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
+// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target
+;(function($) {
+ // only implement if not provided by jQuery core (since 1.4)
+ // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs
+ if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) {
+ $.each({
+ focus: 'focusin',
+ blur: 'focusout'
+ }, function( original, fix ){
+ $.event.special[fix] = {
+ setup:function() {
+ this.addEventListener( original, handler, true );
+ },
+ teardown:function() {
+ this.removeEventListener( original, handler, true );
+ },
+ handler: function(e) {
+ arguments[0] = $.event.fix(e);
+ arguments[0].type = fix;
+ return $.event.handle.apply(this, arguments);
+ }
+ };
+ function handler(e) {
+ e = $.event.fix(e);
+ e.type = fix;
+ return $.event.handle.call(this, e);
+ }
+ });
+ };
+ $.extend($.fn, {
+ validateDelegate: function(delegate, type, handler) {
+ return this.bind(type, function(event) {
+ var target = $(event.target);
+ if (target.is(delegate)) {
+ return handler.apply(target, arguments);
+ }
+ });
+ }
+ });
+})(jQuery);
diff --git a/NzbDrone.Web/Scripts/NzbDrone/grid.js b/NzbDrone.Web/Scripts/NzbDrone/grid.js
index 3e2920a5e..2dffa4a7c 100644
--- a/NzbDrone.Web/Scripts/NzbDrone/grid.js
+++ b/NzbDrone.Web/Scripts/NzbDrone/grid.js
@@ -10,6 +10,8 @@ $('.seriesTable a, .dataTable a').live('click', function (event) {
});
$('.seriesTable .data-row td:not(:last-child)').live('click', function () {
+ if ($(this).closest('table').hasClass('no-details'))
+ return;
$(this).parent('tr').next('.detail-row').toggle();
});
@@ -22,6 +24,9 @@ function grid_onError(e) {
var oTable;
$('.dataTable td:not(:last-child)').live('click', function () {
+ if ($(this).closest('table').hasClass('no-details'))
+ return;
+
var nTr = this.parentNode;
if ($(nTr).hasClass('details-opened')) {
diff --git a/NzbDrone.Web/Views/History/Index.cshtml b/NzbDrone.Web/Views/History/Index.cshtml
index b9b8d7fef..ee05c6824 100644
--- a/NzbDrone.Web/Views/History/Index.cshtml
+++ b/NzbDrone.Web/Views/History/Index.cshtml
@@ -37,23 +37,24 @@
-
+ } //Details
+ ],
+ "aaSorting": [[5, 'desc']]
+ });
+ });
+
+ function deleteHistory(row, historyId) {
+ //Delete from DB
+ $.ajax({
+ type: "POST",
+ url: deleteHistoryRowUrl,
+ data: { historyId: historyId },
+ success: function() {
+ oTable.fnDeleteRow(oTable.fnGetPosition(row));
+ }
+ });
+
+ }
+
+ function redownloadHistory(row, historyId, episodeId) {
+ $.ajax({
+ type: "POST",
+ url: redownloadUrl,
+ data: { historyId: historyId, episodeId: episodeId },
+ success: function() {
+ oTable.fnDeleteRow(oTable.fnGetPosition(row));
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml b/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml
index f28573c1c..33a41fe93 100644
--- a/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml
+++ b/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml
@@ -36,7 +36,10 @@
@Html.IncludeScript("NzbDrone/Notification.js")
@Html.IncludeScript("NzbDrone/AutoBind.js")
@Html.IncludeScript("NzbDrone/grid.js")
- @Html.IncludeScript("DataTables-1.9.0/media/js/jquery.dataTables.min.js")
+ @Html.IncludeScript("DataTables-1.9.0/media/js/jquery.dataTables.js")
+ @Html.IncludeScript("DataTables-1.9.0/media/js/jquery.dataTables.editable.js")
+ @Html.IncludeScript("DataTables-1.9.0/media/js/jquery.jeditable.js")
+ @Html.IncludeScript("DataTables-1.9.0/media/js/jquery.validate.js")
@Html.IncludeScript("jquery.dataTables.4button.pagination.js")
@RenderSection("Scripts", required: false)