I have recently decided to write a generic Table html helper to generate tables for my models and other objects, I have used reflection to make it more generic by taking an IEnumerable argument as the table data and a Dictionary for the .
I want to use reflection or some other method to get the properties [DisplayName()] attribute from the models MetaData so that it does not need to be specified in a dictionary. However all methods I have tried seem to return null, so I have removed them from my code.
public static MvcHtmlString Table(this HtmlHelper htmlHelper, Dictionary<string, string> boundColumns, IEnumerable<object> objectData, string tagId, string className, string controllerName, string idProperty)
{
bool hasAction = !String.IsNullOrEmpty(idProperty);
bool hasData = objectData.Count() > 0;
UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
Type objectDataType = hasData ? objectData.First().GetType() : null;
IEnumerable<PropertyInfo> objectDataProperties = hasData ? from propInfo in objectDataType.GetProperties()
where boundColumns.ContainsKey(propInfo.Name)
select propInfo : null;
// Thead
TagBuilder theadtr = new TagBuilder("tr");
foreach (string col in boundColumns.Values)
theadtr.InnerHtml = String.Format("{0}\n{1}", theadtr.InnerHtml, (new TagBuilder("th") { InnerHtml = col }).ToString());
if (hasAction)
theadtr.InnerHtml = String.Format("{0}\n{1}", theadtr.InnerHtml, new TagBuilder("th") { InnerHtml = "Action" });
TagBuilder thead = new TagBuilder("thead") { InnerHtml = theadtr.ToString() };
// Tfoot
TagBuilder tfoot = new TagBuilder("tfoot");
if (!hasData) // Warn that there was no data to be displayed.
{
TagBuilder tfoottd = new TagBuilder("td") { InnerHtml = "There is currently nothing to display." };
tfoottd.MergeAttribute("colspan", (hasAction ? (boundColumns.Count + 1) : boundColumns.Count).ToString());
tfoottd.MergeAttribute("style", "text-align:center");
tfoot.InnerHtml = (new TagBuilder("tr") { InnerHtml = tfoottd.ToString() }).ToString();
}
else // Display a pager & filter for navigating through large amounts of data.
{
// The button for navigating to the first page.
TagBuilder pagefirst = new TagBuilder("img");
pagefirst.MergeAttribute("id", String.Format("{0}-page-first", tagId));
pagefirst.MergeAttribute("class", "first");
pagefirst.MergeAttribute("alt", "First Page");
pagefirst.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_first.png"));
pagefirst.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The button for navigating to the previous page.
TagBuilder pageprev = new TagBuilder("img");
pageprev.MergeAttribute("id", String.Format("{0}-page-prev", tagId));
pageprev.MergeAttribute("class", "prev");
pageprev.MergeAttribute("alt", "Previous Page");
pageprev.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_previous.png"));
pageprev.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The button for navigating to the next page.
TagBuilder pagenext = new TagBuilder("img");
pagenext.MergeAttribute("id", String.Format("{0}-page-next", tagId));
pagenext.MergeAttribute("class", "next");
pagenext.MergeAttribute("alt", "Next Page");
pagenext.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_next.png"));
pagenext.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The button for navigating to the last page.
TagBuilder pagelast = new TagBuilder("img");
pagelast.MergeAttribute("id", String.Format("{0}-page-last", tagId));
pagelast.MergeAttribute("class", "last");
pagelast.MergeAttribute("alt", "Last Page");
pagelast.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_last.png"));
pagelast.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The display field for the pager status.
TagBuilder pagedisplay = new TagBuilder("input");
pagedisplay.MergeAttribute("id", String.Format("{0}-page-display", tagId));
pagedisplay.MergeAttribute("type", "text");
pagedisplay.MergeAttribute("class", "pagedisplay");
pagedisplay.MergeAttribute("disabled", "disabled");
pagedisplay.MergeAttribute("style", "width:12%;");
// The select for changing page size.
TagBuilder pagesize = new TagBuilder("select");
pagesize.MergeAttribute("id", String.Format("{0}-page-size", tagId));
pagesize.MergeAttribute("class", "pagesize");
pagesize.MergeAttribute("style", "width:12%;");
for (int i = 10; i <= 100; i += 10)
{
TagBuilder option = new TagBuilder("option") { InnerHtml = i.ToString() };
if (i == 10)
option.MergeAttribute("selected", "selected");
option.MergeAttribute("value", i.ToString());
pagesize.InnerHtml = String.Format("{0}\n{1}", pagesize.InnerHtml, option.ToString());
}
// The pager container.
TagBuilder pagediv = new TagBuilder("div") { InnerHtml = (new TagBuilder("form") { InnerHtml = String.Format("{0}\n{1}\n{2}\n{3}\n{4}\n{5}", pagefirst.ToString(), pageprev.ToString(), pagenext.ToString(), pagelast.ToString(), pagedisplay.ToString(), pagesize.ToString()) }).ToString() };
pagediv.MergeAttribute("id", String.Format("{0}-pager", tagId));
pagediv.MergeAttribute("style", "float:left; width:50%;");
// Filter Text Field
TagBuilder filterfield = new TagBuilder("input");
filterfield.MergeAttribute("id", String.Format("{0}-filter-field", tagId));
filterfield.MergeAttribute("type", "text");
filterfield.MergeAttribute("style", "width:30%;");
// The filter container.
TagBuilder filterdiv = new TagBuilder("div") { InnerHtml = (new TagBuilder("form") {InnerHtml = String.Format("Search: {0}", filterfield.ToString())}).ToString() };
filterdiv.MergeAttribute("id", String.Format("{0}-filter", tagId));
filterdiv.MergeAttribute("style", "float:right; width:50%;");
TagBuilder tfoottd = new TagBuilder("td") { InnerHtml = String.Format("{0}\n{1}", pagediv.ToString(), filterdiv.ToString()) };
tfoottd.MergeAttribute("colspan", (hasAction ? (boundColumns.Count + 1) : boundColumns.Count).ToString());
tfoottd.MergeAttribute("style", "text-align:center");
tfoot.InnerHtml = (new TagBuilder("tr") { InnerHtml = tfoottd.ToString() }).ToString();
}
// Tbody
TagBuilder tbody = new TagBuilder("tbody");
foreach (object o in objectData)
{
TagBuilder tbodytr = new TagBuilder("tr");
foreach (PropertyInfo p in objectDataProperties)
{
string val = "N/A";
object pval = p.GetValue(o, null);
if (pval != null)
val = pval.ToString();
tbodytr.InnerHtml = String.Format("{0}\n{1}", tbodytr.InnerHtml, (new TagBuilder("td") { InnerHtml = val }).ToString());
}
if (hasAction)
{
string id = objectDataType.GetProperty(idProperty).GetValue(o, null).ToString();
tbodytr.InnerHtml = String.Format(
"{0}\n{1}",
tbodytr.InnerHtml,
(new TagBuilder("td") { InnerHtml = Table_ActionLinks(htmlHelper, controllerName, id) }).
ToString());
}
tbody.InnerHtml = String.Format("{0}\n{1}", tbody.InnerHtml, tbodytr.ToString());
}
// Table
TagBuilder table = new TagBuilder("table") { InnerHtml = String.Format("{0}\n{1}\n{2}", thead.ToString(), tfoot.ToString(), tbody.ToString()) };
table.MergeAttribute("id", string.IsNullOrEmpty(tagId) ? String.Format("table-{0}", boundColumns.Count.ToString()) : tagId);
table.MergeAttribute("summary", "Generic data list");
if (!String.IsNullOrEmpty(className))
table.MergeAttribute("class", String.Format("{0} {1}", className, "tablesorter"));
else
table.MergeAttribute("class", "tablesorter");
// Enable Sorting/Searching
if (hasData)
{
TagBuilder sortscript = new TagBuilder("script") { InnerHtml = String.Format("$(document).ready(function(){{$(\"#{0}\").tablesorter().tablesorterPager({{container:$(\"#{1}\")}});}});", tagId, String.Format("{0}-pager", tagId)) };
TagBuilder searchscript = new TagBuilder("script") { InnerHtml = String.Format("$(document).ready(function(){{$(\"#{0}\").keyup(function(){{$.uiTableFilter($(\"#{1}\"), this.value);}})}});", String.Format("{0}-filter-field", tagId), tagId) };
sortscript.MergeAttribute("type", "text/javascript");
return new MvcHtmlString(String.Format("{0}\n{1}\n{2}", table.ToString(), sortscript.ToString(), searchscript.ToString()));
}
return new MvcHtmlString(table.ToString());
}
So basically I am looking to use as much reflection as possible to eliminate as many arguments to this method as possible.
Thanks,
Alex.
I'm not sure why your Display attribute retrieval isn't working. Here's what I use. It's from a method which retrieves the Display attribute from enum field values, but it's the same basic pattern that I use to retrieve any attribute from an object:
public static string GetDisplayName<T>( T toCheck )
{
Type enumType = typeof(T);
if( !enumType.IsEnum ) return null;
MemberInfo[] members = enumType.GetMember(toCheck.ToString());
if( ( members == null ) || ( members.Length != 1 ) ) return toCheck.ToString();
foreach( MemberInfo memInfo in members )
{
DisplayAttribute[] attrs = (DisplayAttribute[]) memInfo.GetCustomAttributes(typeof(DisplayAttribute), false);
if( ( attrs != null ) && ( attrs.Length == 1 ) ) return attrs[0].Name;
}
return toCheck.ToString();
}
Related
My project has models with 2 or more words in the name:
EngineConfigurationModel
MyProductModel
CurrentProductModel
CheckNetworkInventoryModel
I've got an extension that can create a breadcrumb:
public static string BuildBreadcrumbNavigation(this HtmlHelper helper)
{
// optional condition: I didn't wanted it to show on home and account controller
if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" ||
helper.ViewContext.RouteData.Values["controller"].ToString() == "Account")
{
return string.Empty;
}
var htmlLink = helper.ActionLink("Home", "Index", "Home").ToHtmlString();
var sb = new StringBuilder("<ol class='breadcrumb'><li>");
sb.Append(htmlLink);
sb.Append("</li>");
sb.Append("<li>");
sb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["controller"].ToString().Titleize(),
"", // "Index",
helper.ViewContext.RouteData.Values["controller"].ToString()));
sb.Append("</li>");
if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index")
{
sb.Append("<li>");
sb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["action"].ToString().Titleize(),
helper.ViewContext.RouteData.Values["action"].ToString(),
helper.ViewContext.RouteData.Values["controller"].ToString()));
sb.Append("</li>");
}
var result = sb.Append("</ol>").ToString().Replace("Index", "");
return result;
}
Source: https://stackoverflow.com/a/26439510/153923
But, I want to split-up the words for project models with 2 or more words in the name.
for EngineConfigurationModel, class name EngineConfiguration would be 'Engine Configuration'
MyProductModel, class name MyProduct would be 'My Product'
CurrentProductModel, class name CurrentProduct would be 'Current Product'
CheckNetworkInventoryModel, class name CheckNetworkInventory would be 'Check Network Inventory'
For model properties with multiple words, I can use a [Display(Name = "some thing")] parameter like this:
[Display(Name = "Some Thing")]
public string SomeThing { get; set; }
I tried putting the Display attribute on the class declaration, but VS2022 says:
Attribute 'Display' is not valid on this declaration type. It is only valid on 'method, property, indexer, field, parameter' declarations.
I made something and I put it into an extension. It has gone through 2 revisions, but it seems to be flawless now.
Adding my work here for others:
public static string SplitTitleWords(this string value)
{
var cList = new List<char>();
if (!string.IsNullOrEmpty(value))
{
cList.Add(value[0]); // just add the first letter, whether caps, no caps, or number
for (var i = 1; i < value.Length; i++)
{
var c = value[i];
if (char.IsUpper(c))
{ // 01234567891234 0123456789012345
// check special cases like class AddPDFResource => Add PDF Resource
var c0 = value[i - 1];
if (char.IsUpper(c0))
{
if (i + 1 < value.Length)
{
var c1 = value[i + 1];
if (!char.IsUpper(c1))
{
cList.Add(' ');
}
}
} else
{
cList.Add(' ');
}
}
cList.Add(c);
}
}
var result = new String(cList.ToArray());
return result;
}
And here is a Breadcrumb extension method that calls it twice:
public static string BuildBreadcrumbNavigation(this HtmlHelper helper)
{
var result = string.Empty;
var controllerName = helper.ViewContext.RouteData.Values["controller"].ToString();
// optional condition: I didn't wanted it to show on home and account controller
if ((controllerName != "Home") && (controllerName != "Account"))
{
var homeLink = helper.ActionLink(
linkText: "Home",
actionName: "Index",
controllerName: "Home").ToHtmlString();
var sb = new StringBuilder($"<ol class='breadcrumb'><li>{homeLink}</li>");
var url = HttpContext.Current.Request.Url.ToString();
var urlParts = url.Split(new char[] { '/' });
if (!urlParts.Contains("Console"))
{
var controllerLink = helper.ActionLink(
linkText: controllerName.SplitTitleWords(),
actionName: "Index",
controllerName: controllerName);
sb.Append($"<li>{controllerLink}</li>");
} else
{
var a = $"Console";
sb.Append($"<li>{a}</li>");
}
var actionName = helper.ViewContext.RouteData.Values["action"].ToString();
sb.Append($"<li class=\"active\">{actionName.SplitTitleWords()}</li>");
result = sb.Append("</ol>").ToString();
}
return result;
}
It has been working great for me.
I need to extend selection of external and Internal Link and provide a image selection.
Please see the snapshot in the below :
Here the above snapshot allows you to add properties for External Link.In the same popup can we add a field Called Image(as shown in screenshot) which will allow user to select images from the media library??
Thanks,
Suhas
If you are using Sitecore 7.2 and want to update internal link then you need to do with Sitecore speak and in case of external link simply update under /shell/Applications/Dialogs/ExternalLink folder.
To render this field create CustomLinkRenderer class and inheri this class by LinkRenderer of Sitecore.Kernel.dll.
Please see below code for CustomLinkRenderer
public class CustomLinkRenderer : LinkRenderer
{
public CustomLinkRenderer(Item item)
: base(item)
{
}
public override RenderFieldResult Render()
{
string str8;
SafeDictionary<string> dictionary = new SafeDictionary<string>();
dictionary.AddRange(this.Parameters);
if (MainUtil.GetBool(dictionary["endlink"], false))
{
return RenderFieldResult.EndLink;
}
Set<string> set = Set<string>.Create(new string[] { "field", "select", "text", "haschildren", "before", "after", "enclosingtag", "fieldname" });
LinkField linkField = this.LinkField;
if (linkField != null)
{
dictionary["title"] = StringUtil.GetString(new string[] { dictionary["title"], linkField.Title });
dictionary["target"] = StringUtil.GetString(new string[] { dictionary["target"], linkField.Target });
dictionary["class"] = StringUtil.GetString(new string[] { dictionary["class"], linkField.Class });
}
string str = string.Empty;
string rawParameters = this.RawParameters;
if (!string.IsNullOrEmpty(rawParameters) && (rawParameters.IndexOfAny(new char[] { '=', '&' }) < 0))
{
str = rawParameters;
}
if (string.IsNullOrEmpty(str))
{
Item targetItem = this.TargetItem;
string str3 = (targetItem != null) ? targetItem.DisplayName : string.Empty;
string str4 = (linkField != null) ? linkField.Text : string.Empty;
str = StringUtil.GetString(new string[] { str, dictionary["text"], str4, str3 });
}
string url = this.GetUrl(linkField);
if (((str8 = this.LinkType) != null) && (str8 == "javascript"))
{
dictionary["href"] = "#";
dictionary["onclick"] = StringUtil.GetString(new string[] { dictionary["onclick"], url });
}
else
{
dictionary["href"] = HttpUtility.HtmlEncode(StringUtil.GetString(new string[] { dictionary["href"], url }));
}
// Add onclick attribute for Google event tracking
dictionary["onclick"] = LinkField.GetAttribute("on_click");
StringBuilder tag = new StringBuilder("<a", 0x2f);
foreach (KeyValuePair<string, string> pair in dictionary)
{
string key = pair.Key;
string str7 = pair.Value;
if (!set.Contains(key.ToLowerInvariant()))
{
FieldRendererBase.AddAttribute(tag, key, str7);
}
}
tag.Append('>');
if (!MainUtil.GetBool(dictionary["haschildren"], false))
{
if (string.IsNullOrEmpty(str))
{
return RenderFieldResult.Empty;
}
tag.Append(str);
}
return new RenderFieldResult { FirstPart = tag.ToString(), LastPart = "</a>" };
}
}
You need to extend this class as per your need to render image.
If you are talking about a whole field and not just an external link inside of a rich text field, you should create a custom field based on the documentation to do so in SDN:
http://sdn.sitecore.net/Articles/API/Creating%20a%20Composite%20Custom%20Field.aspx
You can inherit most functionality from the current link field.
You can actually extend any dialog by editing the xml files in /shell/Applications/Dialogs
In another thread I have shown how to add a maxlength to the title field, that should help you get on your way: Limit number of Characters entered for Link Title Field in General Link
I am trying to replace some text using HtmlAgilityPack in Html string and placing ASP.net user controls but I am getting lower case in output html. Any Idea how to get original case output.
Code :
public static string ConvertPageTitlesToCMSTitle(string htmlstring, string themeSlug)
{
var htmlDoc = new HtmlAgilityPack.HtmlDocument()
{
OptionOutputOriginalCase = true,
OptionWriteEmptyNodes = true
};
htmlDoc.LoadHtml(htmlstring);
var stPageTitleTags = htmlDoc.DocumentNode.SelectNodes("//stpagetitle");
foreach (var stPageTitleTag in stPageTitleTags)
{
var pageTitle = Strings.StripHTML(stPageTitleTag.InnerText);
pageTitle = pageTitle.Trim();
var pageId = CreateUpdateContentPageInDb(pageTitle, themeSlug, null, null);
var widgetControl = string.Format("<widget:PageTitleDisplay runat=\"server\" PageId=\"{0}\" Editable=\"True\" />", pageId);
htmlDoc.DocumentNode.InnerHtml = htmlDoc.DocumentNode.InnerHtml.Replace(stPageTitleTag.OuterHtml, widgetControl);
}
return htmlDoc.DocumentNode.OuterHtml;
}
As a workaround you could create a text node instead of HTML node. See:
foreach (var stPageTitleTag in stPageTitleTags)
{
var pageTitle = Strings.StripHTML(stPageTitleTag.InnerText);
pageTitle = pageTitle.Trim();
var pageId = CreateUpdateContentPageInDb(pageTitle, themeSlug, null, null);
var widgetControl = string.Format("<widget:PageTitleDisplay runat=\"server\" PageId=\"{0}\" Editable=\"True\" />", pageId);
// creating a text node
var widget = htmlDoc.CreateTextNode(widgetControl);
// replacing <sppagetitle> node with the new one
stPageTitleTag.ReplaceChild(widget, stPageTitleTag);
}
This should get the output you want.
I got an ActionResult TabNotes which returns a View for a tab which shows notes from a database in a grid. On the tab is a button for ActionResult CreateNote, which returns a PartialView and after saving the note I redirect back to the ActionResult TabNotes with
return RedirectToAction("TabNotes", new { modelEntity = "Phrase", id = itemId});
However, when it goes to the action result TabNotes using this redirect it does not show the grid. The javascript gives the following error
Uncaught ReferenceError: $ is not defined (anonymous function)
Uncaught ReferenceError: ko is not defined (anonymous function)
This does not happen the first time it goes to ActionResult. Using breakpoints the following part of the ActionResult TabNotes:
[...]
Model.Grid.url = Url.Action("TabNoteData", new { id = Model.meta.entity, itemId = Model.meta.id.Value});
}
return View("TabNotes", Model);
}
gives the same input values in Model for the first time and the second time. Where can this error come from?
Edit: Firebug shows the following errors:
prompt aborted by user
throw Components.Exception...by user", Cr.NS_ERROR_NOT_AVAILABLE); nsPrompter.js (regel 462 <Systeem>
$ is not defined
$(document).ready(function(){$('#tblTN...tes/44?cmd=refresh" id="TNPhrase44"> 44?mod...=Phrase (regel 2)
ko is not defined
var viewModel=ko.mapping.fromJ...],"buttons":[],"PostAction":null}}); 44?mod...=Phrase (regel 12)
Below is the javascript and code
#model test.Web.Framework.Areas.Administration.Models.TabNotesModel
#using (UI.DocumentReadyScript())
{
if (Model.meta.id.HasValue)
{
UI.jQuery("#tbl" + Model.meta.modelname).flexigrid(Model.Grid);
}
}
<form method="post" action="#Url.Action("TabNotes", new { cmd = "refresh" })" id="#Model.meta.modelname">
<div class="ui-state-highlight ui-corner-all highlight" data-bind="visible: meta.message">
<span class="ui-icon ui-icon-info"></span><strong data-bind="text: meta.message">
</strong>
</div>
#using (UI.BeginBlock("Administation.TabNotes", UI.Label("Notes", "Notes").ToString(), test.Web.Framework.Core.enumIcons.pencil, false, false))
{
<table id="#("tbl" + Model.meta.modelname)">
</table>
}
</form>
<script type="text/javascript">
(function() {
var viewModel=ko.mapping.fromJS(#Html.Raw(UI.JavascriptEncode(Model)));
viewModel.getData=function() { return ko.mapping.toJSON( this ); };
viewModel.setData=function(data){
$('#tbl'+this.meta.modelname()).flexigrid( data.Grid);
ko.mapping.updateFromJS(this,data);
};
$('##Model.meta.modelname').koform({viewmodel: viewModel , validate : {errorElement:'p' } } );
$('##Model.meta.modelname').koform('applyBindings');
$('#load-partial').click(function() {
$('#partial').load('#Url.Action("CreateNote", "Entity", new {itemId = #Model.meta.id, modelEntity = "Phrase"})');
});
})();
</script>
<div id="partial"></div>
<button type="button" id="load-partial">Create Note</button>
'
public ActionResult CreateNote(
[ModelBinder(typeof(Models.JsonModelBinder))]
NoteModel Model, string cmd, long? itemId, string modelEntity)
{
if (cmd == "Save")
{
Model.meta.message = "Note saved";
test.Database.User User = UserRepository.GetUser(1);
Entity entity = NotesRepository.GetEntity("Phrase");
NotesRepository.StoreNote(Model.subject, Model.text, User, entity, itemId);
return RedirectToAction("TabNotes", new { modelEntity = "Phrase", id = itemId});
}
Model.meta.modelname = "CreateNote";
Model.meta.JsViewModelType = "EditNoteModel";
Model.meta.PostAction = Url.Action("CreateNote", new { cmd = "Save", itemId = itemId});
return PartialView("CreateNotePartial",Model);
}
'
public ActionResult TabNotes([ModelBinder(typeof(Models.JsonModelBinder))]
TabNotesModel Model, string cmd, string modelEntity, long? id)
{
if (modelEntity != null)
{
Model.meta.entity = modelEntity;
}
Entity entity = NotesRepository.GetEntity(Model.meta.entity);
if (id.HasValue)
{
Model.meta.id = id;
}
if (Model.meta.id.HasValue)
{
Model.meta.modelname = "TN" + Model.meta.entity + Model.meta.id.Value.ToString();
Dictionary<string, object> defaultValues = new Dictionary<string, object>();
defaultValues.Add("Entity", entity.EntityId);
defaultValues.Add("ItemId", Model.meta.id.Value);
Entity noteEntity = NotesRepository.GetEntity("Note");
var grid = UI.GetEntityFlexiGrid(noteEntity, true, true, true, true, defaultValues);
grid.buttons.Clear();
//grid.buttons.Add(new Button { onpress = "CreateNote", action = Url.Action("CreateNote"), name = "CreateNote", postdata = new { meta = Model.meta }});
grid.title = "";
Model.Grid = grid;
Model.Grid.url = Url.Action("TabNoteData", new { id = Model.meta.entity, itemId = Model.meta.id.Value});
}
return View("TabNotes", Model);
}
'
public GridResult TabNoteData(string id, long itemId, FlexigridRequest request, string cmd)
{
GridResult returnValue = null;
var entity = NotesRepository.GetEntity(id);
Entity noteEntity = NotesRepository.GetEntity("Note");
//var Acess = UIRepository.GetEntityAccess(id);
FlexigridConfiguration grid;
Dictionary<string, object> defaultValues = new Dictionary<string, object>();
defaultValues.Add("Entity", entity.EntityId);
defaultValues.Add("ItemId",itemId);
grid = UI.GetEntityFlexiGrid(noteEntity, true, true, true, true, defaultValues);
IQueryable q = NotesRepository.GetNotes(entity.EntityId, itemId);
var sortField = entity.EntityFields.SingleOrDefault(c => c.Name == request.sortname);
if (sortField == null)
{
request.sortname = grid.sortname;
}
IQueryable qdata = null;
if (!string.IsNullOrEmpty(request.sortname) && request.sortname != "undefined")
{
switch (request.sortorder)
{
case enumFlexigridRequestSortOrder.asc:
qdata = q.OrderBy(request.sortname + " ascending");
break;
case enumFlexigridRequestSortOrder.desc:
qdata = q.OrderBy(request.sortname + " descending");
break;
}
}
if (!string.IsNullOrEmpty(request.query) && !string.IsNullOrEmpty(request.qtype))
{
qdata = qdata.Where(request.qtype.SanitizeFieldExpression() + ".Contains(#0)", request.query);
}
if (request.q != null && request.q.Length > 0)
{
for (int i = 0; i < request.q.Length; i++)
{
var type = UIRepository.GetType(id);
var property = type.GetProperty(request.q[i]);
System.ComponentModel.TypeConverter tc = System.ComponentModel.TypeDescriptor.GetConverter(property.PropertyType);
string sv = request.v[i];
if (sv == null || sv == "null")
{
qdata = qdata.Where(request.q[i].SanitizeFieldExpression() + "=#0", (object)null);
}
else
{
object v = tc.ConvertFromString(sv);
qdata = qdata.Where(request.q[i].SanitizeFieldExpression() + "=#0", v);
}
}
}
string settingName = "Grid." + id + ".Rp";
var setting = UIRepository.GetQuery<test.Database.UserSetting>().SingleOrDefault(uc => uc.CreatedById == CurrentUser.UserId && uc.Name == settingName);
if (setting == null)
{
setting = UIRepository.Create<test.Database.UserSetting>();
setting.Name = settingName;
setting.Value = request.rp.ToString();
UIRepository.Add(setting);
}
else
{
if (request.rp.ToString() != setting.Value)
{
setting.Value = request.rp.ToString();
UIRepository.Update(setting);
}
}
int rowId = 0;
var datarows = new List<object>();
foreach (var record in qdata.Skip((request.page - 1) * request.rp).Take(request.rp).GetData())
{
var cellValues = new List<object>();
foreach (var gc in grid.colModel.OrderBy(c => c.di))
{
cellValues.Add(gc.ToString(UI, record));
}
var row = new { id = rowId, cell = cellValues.ToArray() };
datarows.Add(row);
rowId++;
}
returnValue = Grid(request.page, qdata.Count(), datarows.ToList());
return returnValue;
}
That error can only be caused be one of three things:
Your JavaScript file is not being properly loaded into your page
You have a botched version of jQuery. This could happen because someone edited the core file, or a plugin may have overwritten the $ variable.
You have JavaScript running before the page is fully loaded, and as such, before jQuery is fully loaded.
You should check the Firebug net panel to see if the file is actually being loaded properly. If not, it will be highlighted red and will say "404" beside it. If the file is loading properly, that means that the issue is number 2.
Make sure all javascript code is being run inside a code block such as:
$(document).ready(function () {
//your code here
});
This will ensure that your code is being loaded after jQuery has been initialized.
One final thing to check is to make sure that you are not loading any plugins before you load jQuery. Plugins extend the "$" object, so if you load a plugin before loading jQuery core, then you'll get the error you described.
So to avoid that you can use a "bodyguard" function like the following:
( function($) {
//We can now use $ as I implemented the bodyguard!
$(document).ready(function() {
//your code...
});
} ) ( jQuery );
If I have an Action like this:
public ActionResult DoStuff(List<string> stuff)
{
...
ViewData["stuff"] = stuff;
...
return View();
}
I can hit it with the following URL:
http://mymvcapp.com/controller/DoStuff?stuff=hello&stuff=world&stuff=foo&stuff=bar
But in my ViewPage, I have this code:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = ViewData["stuff"] }, null) %>
Unfortunately, MVC is not smart enough to recognize that the action takes an array, and unrolls the list to form the proper url route. instead it just does a .ToString() on the object which just lists the data type in the case of a List.
Is there a way to get Html.ActionLink to generate a proper URL when one of the destination Action's parameters is an array or list?
-- edit --
As Josh pointed out below, ViewData["stuff"] is just an object. I tried to simplify the problem but instead caused an unrelated bug! I'm actually using a dedicated ViewPage<T> so I have a tightly coupled type aware Model. The ActionLink actually looks like:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = ViewData.Model.Stuff }, null) %>
Where ViewData.Model.Stuff is typed as a List
I'm thinking that a custom HtmlHelper would be in order.
public static string ActionLinkWithList( this HtmlHelper helper, string text, string action, string controller, object routeData, object htmlAttributes )
{
var urlHelper = new UrlHelper( helper.ViewContext.RequestContext );
string href = urlHelper.Action( action, controller );
if (routeData != null)
{
RouteValueDictionary rv = new RouteValueDictionary( routeData );
List<string> urlParameters = new List<string>();
foreach (var key in rv.Keys)
{
object value = rv[key];
if (value is IEnumerable && !(value is string))
{
int i = 0;
foreach (object val in (IEnumerable)value)
{
urlParameters.Add( string.Format( "{0}[{2}]={1}", key, val, i ));
++i;
}
}
else if (value != null)
{
urlParameters.Add( string.Format( "{0}={1}", key, value ) );
}
}
string paramString = string.Join( "&", urlParameters.ToArray() ); // ToArray not needed in 4.0
if (!string.IsNullOrEmpty( paramString ))
{
href += "?" + paramString;
}
}
TagBuilder builder = new TagBuilder( "a" );
builder.Attributes.Add("href",href);
builder.MergeAttributes( new RouteValueDictionary( htmlAttributes ) );
builder.SetInnerText( text );
return builder.ToString( TagRenderMode.Normal );
}
you can suffix your routevalues with an array index like so:
RouteValueDictionary rv = new RouteValueDictionary();
rv.Add("test[0]", val1);
rv.Add("test[1]", val2);
this will result in the querystring containing test=val1&test=val2
that might help ?
Combining both methods works nicely.
public static RouteValueDictionary FixListRouteDataValues(RouteValueDictionary routes)
{
var newRv = new RouteValueDictionary();
foreach (var key in routes.Keys)
{
object value = routes[key];
if (value is IEnumerable && !(value is string))
{
int index = 0;
foreach (string val in (IEnumerable)value)
{
newRv.Add(string.Format("{0}[{1}]", key, index), val);
index++;
}
}
else
{
newRv.Add(key, value);
}
}
return newRv;
}
Then use this method in any extension method that requires routeValues with IEnumerable(s) in it.
Sadly, this workaround seams to be needed in MVC3 too.
This will just act as an extension to the UrlHelper and just provide a nice url ready to put anywhere rather than an an entire a tag, also it will preserve most of the other route values for any other specific urls being used... giving you the most friendly specific url you have (minus the IEnumerable values) and then just append the query string values at the end.
public static string ActionWithList(this UrlHelper helper, string action, object routeData)
{
RouteValueDictionary rv = new RouteValueDictionary(routeData);
var newRv = new RouteValueDictionary();
var arrayRv = new RouteValueDictionary();
foreach (var kvp in rv)
{
var nrv = newRv;
var val = kvp.Value;
if (val is IEnumerable && !(val is string))
{
nrv = arrayRv;
}
nrv.Add(kvp.Key, val);
}
string href = helper.Action(action, newRv);
foreach (var kvp in arrayRv)
{
IEnumerable lst = kvp.Value as IEnumerable;
var key = kvp.Key;
foreach (var val in lst)
{
href = href.AddQueryString(key, val);
}
}
return href;
}
public static string AddQueryString(this string url, string name, object value)
{
url = url ?? "";
char join = '?';
if (url.Contains('?'))
join = '&';
return string.Concat(url, join, name, "=", HttpUtility.UrlEncode(value.ToString()));
}
There is a librarly called Unbinder, which you can use to insert complex objects into routes/urls.
It works like this:
using Unbound;
Unbinder u = new Unbinder();
string url = Url.RouteUrl("routeName", new RouteValueDictionary(u.Unbind(YourComplexObject)));
I'm not at my workstation, but how about something like:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = (List<T>)ViewData["stuff"] }, null) %>
or the typed:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = (List<T>)ViewData.Model.Stuff }, null) %>