Added ForeignLookup control.

This commit is contained in:
Greg Edwards 2015-05-08 18:30:48 -04:00
parent b150bdf566
commit 855bd9eb88
9 changed files with 626 additions and 1 deletions

View File

@ -0,0 +1,18 @@
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ForeignLookup.ascx.cs" Inherits="PkmnFoundations.Web.controls.ForeignLookup" %>
<%@ Register TagPrefix="pf" Namespace="PkmnFoundations.Web" Assembly="PkmnFoundations.Web" %>
<pf:RequireCss Key="form" CssUrl="~/css/form.css" After="main" runat="server" />
<pf:RequireScript Key="jquery" ScriptUrl="~/scripts/jquery-1.11.1.min.js" runat="server" />
<pf:RequireScript Key="form" ScriptUrl="~/scripts/form.js" runat="server" />
<div id="main" class="pfLookup" tabindex="0" runat="server">
<div class="pfLookupOuter">
<asp:TextBox ID="txtInput" class="input" autocomplete="off" runat="server" />
</div>
<div id="results" class="results" style="visibility: hidden;" runat="server">
<div style="text-align: center; padding: 8px 0;">
<pf:RetinaImage ID="imgLoading" ImageUrl="~/images/working.gif" AlternateSizes="2" Width="32" Height="32" style="margin: 0 auto;" runat="server" />
</div>
</div>
<input type="hidden" ID="hdSelectedValue" runat="server" />
</div>

View File

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace PkmnFoundations.Web.controls
{
public partial class ForeignLookup : System.Web.UI.UserControl
{
protected void Page_Init(object sender, EventArgs e)
{
}
protected void Page_Load(object sender, EventArgs e)
{
if (SourceUrl != null)
{
String url = ResolveUrl(SourceUrl);
//url = url + (url.Contains('?') ? "&lang=" : "?lang=") + ((BasePage)Page).Language;
String _params = "\'" + this.ClientID + "', '"
+ txtInput.ClientID + "', '"
+ results.ClientID + "', '"
+ MaxRows.ToString() + "', '"
+ url + "\'";
main.Attributes["onfocus"] = "pfHandleLookupKeypress(" + _params + ")";
main.Attributes["onblur"] = "pfHideLookupResults('" + results.ClientID + "')";
txtInput.Attributes["onfocus"] = "pfHandleLookupKeypress(" + _params + ")";
txtInput.Attributes["onkeypress"] = "return pfHandleLookupKeypress2(" + _params + ", event)";
txtInput.Attributes["onkeyup"] = "return pfHandleLookupKeypress3(" + _params + ")";
txtInput.Attributes["onchange"] = "pfHandleLookupKeypress(" + _params + ")";
txtInput.Attributes["onblur"] = "setTimeout(function(){pfHideLookupResults('" + results.ClientID + "')},100)";
hdSelectedValue.Attributes["onchange"] = OnClientValueChanged;
}
}
public String Value
{
get
{
return hdSelectedValue.Value;
}
set
{
hdSelectedValue.Value = value;
}
}
public String Text
{
get
{
return txtInput.Text;
}
set
{
txtInput.Text = value;
}
}
public String SourceUrl { get; set; }
public int MaxRows { get; set; }
public String OnClientValueChanged { get; set; }
public String HiddenClientID
{
get
{
return hdSelectedValue.ClientID;
}
}
public String TextClientID
{
get
{
return txtInput.ClientID;
}
}
protected override void LoadViewState(object savedState)
{
ForeignLookupViewState viewstate = (ForeignLookupViewState)savedState;
base.LoadViewState(viewstate.UserControlViewState);
SourceUrl = viewstate.SourceUrl;
MaxRows = viewstate.MaxRows;
OnClientValueChanged = viewstate.OnClientValueChanged;
}
protected override object SaveViewState()
{
ForeignLookupViewState viewstate = new ForeignLookupViewState();
viewstate.UserControlViewState = base.SaveViewState();
viewstate.SourceUrl = SourceUrl;
viewstate.MaxRows = MaxRows;
viewstate.OnClientValueChanged = OnClientValueChanged;
return viewstate;
}
[Serializable()]
private struct ForeignLookupViewState
{
public object UserControlViewState;
public String SourceUrl;
public int MaxRows;
public String OnClientValueChanged;
}
}
}

View File

@ -0,0 +1,60 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PkmnFoundations.Web.controls {
public partial class ForeignLookup {
/// <summary>
/// main control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.HtmlControls.HtmlGenericControl main;
/// <summary>
/// txtInput control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox txtInput;
/// <summary>
/// results control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.HtmlControls.HtmlGenericControl results;
/// <summary>
/// imgLoading control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::PkmnFoundations.Web.RetinaImage imgLoading;
/// <summary>
/// hdSelectedValue control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.HtmlControls.HtmlInputHidden hdSelectedValue;
}
}

View File

@ -225,4 +225,36 @@ input[type=submit].large, button.large
{
bottom: 0;
border-bottom-right-radius: 2px;
}
}
.pfLookup
{
position: relative;
}
.pfLookup input
{
}
.pfLookup .results
{
position: absolute;
z-index: 1;
left: 0;
right: 0;
overflow: auto;
}
.pfLookup .results .result
{
padding: 0
}
.pfLookup .results .result.default, .iaLookup .results:hover .result:hover, .pfLookup .results:hover .result.default:hover
{
}
.iaLookup .results:hover .result.default
{
}

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MySql.Data" version="6.9.5" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net40" />
</packages>

View File

@ -11,3 +11,138 @@ $(document).ready(function ()
$("input").change(labelTextBox_Change);
$("input").each(labelTextBox_Change);
})
// foreign lookup
function pfHandleLookupKeypress(id_outer, id_value, id_results, count, url)
{
if (pfShowLookupResults(id_value, id_results))
{
pfHandleLookup(id_outer, id_value, id_results, count, url);
}
}
// keyup
function pfHandleLookupKeypress3(id_outer, id_value, id_results, count, url)
{
var ctrl_text = document.getElementById(id_value);
if (ctrl_text.prevValue && ctrl_text.prevValue == ctrl_text.value)
{
ctrl_text.prevValue = ctrl_text.value;
return;
}
ctrl_text.prevValue = ctrl_text.value;
var ctrl_value = document.getElementById(id_outer + "_hdSelectedValue");
ctrl_value.value = "";
if (ctrl_value.onchange) ctrl_value.onchange();
pfHandleLookupKeypress(id_outer, id_value, id_results, count, url);
}
// keypress
function pfHandleLookupKeypress2(id_outer, id_value, id_results, count, url, event)
{
var ctrl_value = document.getElementById(id_outer + "_hdSelectedValue");
var ctrl_text = document.getElementById(id_value);
if (event && pfLookupResultsVisible(id_results) && (event.keyCode == 13 || event.keyCode == 9))
{
var ctrl_result1 = document.getElementById(id_outer + "_result1");
if (ctrl_result1)
{
ctrl_value.value = ctrl_result1.getAttribute("data-value");
if (ctrl_value.onchange) ctrl_value.onchange();
ctrl_text.value = ctrl_result1.getAttribute("data-text");
}
pfHideLookupResults(id_results);
ctrl_text.prevValue = ctrl_text.value;
return false;
}
else if (event && (event.keyCode == 27))
{
pfHideLookupResults(id_results);
ctrl_text.prevValue = ctrl_text.value;
return true;
}
else if (event)
{
pfHandleLookupKeypress(id_outer, id_value, id_results, count, url);
return true;
}
else
{
return true;
}
}
function pfHandleLookup(id_outer, id_value, id_results, count, url)
{
var ctrl_value = document.getElementById(id_value);
if (ctrl_value.value.length > 1)
{
var ctrl_results = document.getElementById(id_results);
if (ctrl_results.pfLookupBlocked == undefined) ctrl_results.pfLookupBlocked = false;
if (ctrl_results.pfLastLookup == undefined) ctrl_results.pfLastLookup = "";
var search = encodeURIComponent(ctrl_value.value);
if (ctrl_results.pfLastLookup == search) return;
if (ctrl_results.pfLookupBlocked) return; // todo: timeout this to deal with slow server performance
ctrl_results.pfLastLookup = search;
var request = new ajaxObject(url, function (response, status) { pfReceiveLookupResults(response, status, id_outer, id_value, id_results, count, url); });
request.update("n=" + count + "&q=" + search + "&c=" + id_outer, "POST");
ctrl_results.pfLookupBlocked = true;
}
}
function pfShowLookupResults(id_value, id_results)
{
if (document.getElementById(id_value).value.length > 1)
{
document.getElementById(id_results).style.visibility = "visible";
return true;
}
else
{
document.getElementById(id_results).style.visibility = "hidden";
return false;
}
}
function pfReceiveLookupResults(response, status, id_outer, id_value, id_results, count, url)
{
var ctrl_results = document.getElementById(id_results);
if (ctrl_results.pfLastLookup == undefined) ctrl_results.pfLastLookup = "";
ctrl_results.pfLookupBlocked = false;
if (status == 200)
{
ctrl_results.innerHTML = response;
}
else
{
ctrl_results.innerHTML = "server error";
}
var search = encodeURIComponent(document.getElementById(id_value).value);
if (ctrl_results.pfLastLookup != search) pfHandleLookup(id_outer, id_value, id_results, count, url);
}
function pfHideLookupResults(id)
{
document.getElementById(id).style.visibility = "hidden";
}
function pfSelectLookupResult(id_main, id_value, value, id_text, text)
{
document.getElementById(id_value).value = value;
document.getElementById(id_text).value = text;
document.getElementById(id_main).blur();
var ctrl_value = document.getElementById(id_value);
if (ctrl_value.onchange) ctrl_value.onchange();
}
function pfLookupResultsVisible(id)
{
return document.getElementById(id).style.visibility == "visible";
}

View File

@ -5,6 +5,7 @@ using System.Web;
using System.IO;
using MySql.Data.MySqlClient;
using System.Configuration;
using System.Text;
namespace PkmnFoundations.Web
{
@ -15,6 +16,52 @@ namespace PkmnFoundations.Web
return HttpUtility.HtmlEncode(s);
}
public static String JsEncode(String s)
{
StringBuilder result = new StringBuilder();
foreach (char c in s.ToCharArray())
{
if (c == '\r')
{
result.Append("\\r");
}
else if (c == '\n')
{
result.Append("\\n");
}
else if (c == '\t')
{
result.Append("\\t");
}
else if (Convert.ToUInt16(c) < 32)
{
}
else if (NeedsJsEscape(c))
{
result.Append('\\');
result.Append(c);
}
else result.Append(c);
}
return result.ToString();
}
private static bool NeedsJsEscape(char c)
{
if (Convert.ToUInt16(c) < 32) return true;
switch (c)
{
case '\"':
case '\'':
case '\\':
return true;
default:
return false; // utf8 de OK
}
}
private static byte[] m_pad = null;
/// <summary>

View File

@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Text;
using Newtonsoft.Json;
using PkmnFoundations.Data;
namespace PkmnFoundations.Web
{
/// <summary>
/// Summary description for ForeignLookupSource
/// </summary>
public class ForeignLookupSource : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
public ForeignLookupSource()
{
//
// TODO: Add constructor logic here
//
}
public void ProcessRequest(HttpContext context)
{
if (String.IsNullOrEmpty(context.Request.Form["n"])
|| String.IsNullOrEmpty(context.Request.Form["q"])
|| String.IsNullOrEmpty(context.Request.Form["c"]))
{
ServerError(context);
return;
}
int rows = 0;
if (!Int32.TryParse(context.Request.Form["n"], out rows))
{
ServerError(context);
return;
}
if (rows < 0)
{
ServerError(context);
return;
}
String control_id = context.Request.Form["c"];
if (control_id.Contains('<') || control_id.Contains('>') || control_id.Contains('\"')
|| control_id.Contains('\'') || control_id.Contains('\\'))
{
ServerError(context);
return;
}
bool french = (context.Request.QueryString["lang"] ?? "EN").ToUpperInvariant() == "FR";
String format = context.Request.Form["f"] ?? "h";
if (!new[]{"h", "j"}.Contains(format))
{
ServerError(context);
return;
}
DataTable data;
try
{
data = GetData(context.Request.Form["q"], rows, french);
}
catch (Exception ex)
{
// todo: log error
ServerError(context);
return;
}
if (data == null || data.Rows.Count == 0)
{
EmptyResult(context, format);
return;
}
if (!data.Columns.Contains("Text") || !data.Columns.Contains("Value"))
{
ServerError(context);
return;
}
switch (format)
{
case "h":
WriteHtml(context, data, control_id);
break;
case "j":
WriteJson(context, data);
break;
default:
// unreachable
break;
}
}
public bool IsReusable
{
get
{
return false;
}
}
private void ServerError(HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("Bad request");
context.Response.StatusCode = 400;
}
private void EmptyResult(HttpContext context, String format)
{
switch (format)
{
case "h":
context.Response.ContentType = "text/plain";
context.Response.Write("No results");
break;
case "j":
context.Response.ContentType = "text/javascript";
context.Response.Write("[]");
break;
default:
// unreachable
break;
}
}
private void WriteHtml(HttpContext context, DataTable data, String control_id)
{
context.Response.ContentType = "text/plain";
StringBuilder builder = new StringBuilder();
int index = 0;
foreach (DataRow row in data.Rows)
{
String text = Common.HtmlEncode(row["Text"].ToString());
// this breaks if value contains html control chars but is fine since all values will be int
String value = Common.HtmlEncode(row["Value"].ToString());
builder.Remove(0, builder.Length);
builder.Append("<div class=\"result\" ");
if (index == 0)
{
builder.Append("id=\"");
builder.Append(control_id);
builder.Append("_result1\" ");
}
builder.Append("data-value=\"");
builder.Append(Common.HtmlEncode(value));
builder.Append("\" data-text=\"");
builder.Append(Common.HtmlEncode(text));
builder.Append("\" onclick=\"iaSelectResult('");
builder.Append(control_id);
builder.Append("_main', '"); // slight hack to avoid passing all client ids in here
builder.Append(control_id);
builder.Append("_hdSelectedValue', '");
builder.Append(Common.HtmlEncode(Common.JsEncode(value)));
builder.Append("', '");
builder.Append(control_id);
builder.Append("_txtInput', '");
builder.Append(Common.HtmlEncode(Common.JsEncode(text)));
builder.Append("')\">");
builder.Append(Common.HtmlEncode(text));
builder.Append("</div>\n");
context.Response.Write(builder.ToString());
index++;
}
}
private void WriteJson(HttpContext context, DataTable data)
{
context.Response.ContentType = "text/javascript";
ForeignLookupResult[] results = new ForeignLookupResult[data.Rows.Count];
for (int x = 0; x < results.Length; x++)
{
DataRow row = data.Rows[x];
results[x] = new ForeignLookupResult
{
t = DatabaseExtender.Cast<String>(row["Text"]) ?? "",
v = DatabaseExtender.Cast<int ?>(row["Value"]) ?? 0 };
}
context.Response.Write(JsonConvert.SerializeObject(results));
}
protected virtual DataTable GetData(String query, int rows, bool french)
{
return null;
}
}
internal class ForeignLookupResult
{
public String t;
public int v;
}
}

View File

@ -44,6 +44,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\MySql.Data.6.9.5\lib\net40\MySql.Data.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
@ -66,6 +70,7 @@
<Content Include="admin\AddDressup.aspx" />
<Content Include="admin\AddMusical.aspx" />
<Content Include="battlevideo\Default.aspx" />
<Content Include="controls\ForeignLookup.ascx" />
<Content Include="controls\LabelTextBox.ascx" />
<Content Include="controls\PokemonPicker.ascx" />
<Content Include="css\form.css" />
@ -3609,6 +3614,14 @@
<Compile Include="battlevideo\Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
<Compile Include="controls\ForeignLookup.ascx.cs">
<DependentUpon>ForeignLookup.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="controls\ForeignLookup.ascx.designer.cs">
<DependentUpon>ForeignLookup.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="controls\LabelTextBox.ascx.cs">
<DependentUpon>LabelTextBox.ascx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
@ -3679,6 +3692,7 @@
<Compile Include="src\AppStateHelper.cs" />
<Compile Include="src\Common.cs" />
<Compile Include="src\DependencyNode.cs" />
<Compile Include="src\ForeignLookupSource.cs" />
<Compile Include="src\HeaderColour.cs" />
<Compile Include="src\OnceTemplate.cs" />
<Compile Include="src\RequireCss.cs" />