HtmlContentExtension.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. using FluentValidation.Internal;
  2. using GreenTree.Strohrmann.ERP.Core.Extension;
  3. using Microsoft.AspNetCore.Html;
  4. using Microsoft.AspNetCore.Mvc.ModelBinding;
  5. using Microsoft.AspNetCore.Mvc.Rendering;
  6. using Microsoft.EntityFrameworkCore.Infrastructure;
  7. using Newtonsoft.Json;
  8. using Newtonsoft.Json.Linq;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Globalization;
  12. using System.IO;
  13. using System.Linq;
  14. using System.Linq.Expressions;
  15. using System.Reflection;
  16. using System.Text;
  17. using System.Threading.Tasks;
  18. namespace GreenTree.Strohrmann.ERP.Web.Extension
  19. {
  20. public static class HtmlContentExtension
  21. {
  22. #region Inputs
  23. /// <summary>
  24. /// Return HTML markup for the <paramref name="expression"/>, using a display template for a bootstrap extension token field.
  25. /// See <see href="https://sliptree.github.io/bootstrap-tokenfield/">bootstrap-tokenfield</see> for more information of the
  26. /// input type.
  27. /// </summary>
  28. /// <typeparam name="TModel">Model type.</typeparam>
  29. /// <typeparam name="TValue">Model value type.</typeparam>
  30. /// <param name="htmlHelper">HtmlHelper context.</param>
  31. /// <param name="expression">Expression for the requested model enumeration value.</param>
  32. /// <param name="delimiter">The delimiter string used to seperate token values.</param>
  33. /// <param name="htmlAttributes">Additional HTML attributes.</param>
  34. /// <param name="availableValues">The available values for the tokenbox.</param>
  35. /// <param name="allowOnlyOnce"><c>True</c> when every available value can only be selected once.</param>
  36. public static IHtmlContent TokenBoxFor<TModel, TValue>(
  37. this IHtmlHelper<TModel> htmlHelper,
  38. Expression<Func<TModel, TValue>> expression,
  39. string delimiter = "|",
  40. object htmlAttributes = null,
  41. string[] availableValues = null,
  42. bool allowOnlyOnce = true) where TValue : IEnumerable<string>
  43. {
  44. var attributes = htmlAttributes == null
  45. ? new string[0]
  46. : htmlAttributes.GetType().GetProperties()
  47. .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributes)))
  48. .ToArray();
  49. var func = expression.Compile();
  50. var id = Guid.NewGuid().ToShortString();
  51. var scriptBuilder = new StringBuilder();
  52. var memberInfo = expression.Body.NodeType == ExpressionType.MemberAccess
  53. ? ((MemberExpression)expression.Body).Member
  54. : null;
  55. var type = expression.Body.GetType();
  56. if (availableValues != null)
  57. {
  58. var autoComplete = JsonConvertWithoutQuotes(new
  59. {
  60. autocomplete = new
  61. {
  62. source = availableValues,
  63. delay = 200
  64. },
  65. showAutocompleteOnFocus = true
  66. });
  67. scriptBuilder.AppendFormat(
  68. "<script>" +
  69. " $('#{0}').tokenfield({1})",
  70. id, autoComplete);
  71. if (allowOnlyOnce)
  72. {
  73. scriptBuilder.Append(
  74. ".on('tokenfield:createtoken', function (e) { " +
  75. " if ($(this).tokenfield('getTokens').some(t => t.value === e.attrs.value)) return false;" +
  76. "})");
  77. }
  78. if (memberInfo != null)
  79. {
  80. var inputCreationScriptPart = String.Format(
  81. "function (e) {{ " +
  82. " $(\"[name = '{1}'\").remove();" +
  83. " $(this).tokenfield('getTokens').forEach(" +
  84. " function (obj) {{ $('#{0}').after('<input type=\"hidden\" name=\"{1}\" value=\"' + obj.value + '\" />'); }});" +
  85. "}}", id, memberInfo.Name);
  86. scriptBuilder.AppendFormat(
  87. ".on('tokenfield:createdtoken', {0})", inputCreationScriptPart);
  88. scriptBuilder.AppendFormat(
  89. ".on('tokenfield:removedtoken', {0})", inputCreationScriptPart);
  90. }
  91. scriptBuilder.Append(";</script>");
  92. }
  93. else
  94. {
  95. scriptBuilder.AppendFormat("<script>$('#{0}').tokenfield();</script>", id);
  96. }
  97. if (htmlHelper.ViewData.Model == null)
  98. {
  99. return new HtmlString(
  100. String.Format(
  101. "<input id='{0}' class='tokenBox' value='{1}' data-delimiter='{2}' {3} /> {4}",
  102. id,
  103. String.Empty,
  104. delimiter,
  105. String.Join(" ", attributes),
  106. scriptBuilder));
  107. }
  108. else
  109. {
  110. var values = func(htmlHelper.ViewData.Model);
  111. var valueInputs = values
  112. .Select(v => String.Format(
  113. "<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />",
  114. memberInfo == null
  115. ? id
  116. : memberInfo.Name,
  117. v));
  118. return new HtmlString(
  119. String.Format(
  120. "<input id='{0}' class='tokenBox' value='{1}' data-delimiter='{2}' {3} /> {4} {5}",
  121. id,
  122. values == null
  123. ? String.Empty
  124. : String.Join(delimiter, values),
  125. delimiter,
  126. String.Join(" ", attributes),
  127. scriptBuilder,
  128. String.Join(" ", valueInputs)));
  129. }
  130. }
  131. /// <summary>
  132. /// Return HTML markup for the <paramref name="expression"/>, using a bootstrap badge containing 'Yes' or 'No'.
  133. /// See <see href="https://getbootstrap.com/docs/4.1/components/badge/">bootstrap badge</see> for more information of the badge.
  134. /// </summary>
  135. /// <typeparam name="TModel">Model type.</typeparam>
  136. /// <param name="htmlHelper">HtmlHelper context.</param>
  137. /// <param name="expression">Expression for the requested model bool value.</param>
  138. public static IHtmlContent YesNoBadgeFor<TModel>(
  139. this IHtmlHelper<TModel> htmlHelper,
  140. Expression<Func<TModel, bool>> expression)
  141. {
  142. var func = expression.Compile();
  143. if (htmlHelper.ViewData.Model == null)
  144. {
  145. return new HtmlString("<span class='badge badge-secondary'>N/A</span>");
  146. }
  147. else
  148. {
  149. return
  150. func(htmlHelper.ViewData.Model)
  151. ? new HtmlString("<span class='badge badge-success'>Ja</span>")
  152. : new HtmlString("<span class='badge badge-danger'>Nein</span>");
  153. }
  154. }
  155. /// <summary>
  156. /// Return HTML markup for the <paramref name="expression"/>, using a display template for a country selection field.
  157. /// </summary>
  158. /// <typeparam name="TModel">Model type.</typeparam>
  159. /// <typeparam name="TValue">Model value type.</typeparam>
  160. /// <param name="htmlHelper">HtmlHelper context.</param>
  161. /// <param name="expression">Expression for the requested model enumeration value.</param>
  162. /// <param name="htmlAttributes">Additional HTML attributes.</param>
  163. public static IHtmlContent CountrySelectionFor<TModel, TValue>(
  164. this IHtmlHelper<TModel> htmlHelper,
  165. Expression<Func<TModel, TValue>> expression,
  166. object htmlAttributes = null)
  167. {
  168. var attributes = htmlAttributes == null
  169. ? new string[0]
  170. : htmlAttributes.GetType().GetProperties()
  171. .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributes)))
  172. .ToArray();
  173. var func = expression.Compile();
  174. var funcVal = htmlHelper.ViewData.Model == null
  175. ? String.Empty
  176. : func(htmlHelper.ViewData.Model).ToString();
  177. var memberInfo = expression.Body.NodeType == ExpressionType.MemberAccess
  178. ? ((MemberExpression)expression.Body).Member
  179. : null;
  180. var name = memberInfo == null
  181. ? Guid.NewGuid().ToShortString()
  182. : memberInfo.Name;
  183. var selectBuilder = new StringBuilder();
  184. selectBuilder.AppendFormat("<select id=\"{0}\" name =\"{0}\" {1}>", name, String.Join(" ", htmlAttributes));
  185. var countries = CultureInfo.GetCultures(CultureTypes.AllCultures)
  186. .Where(c => c.LCID != CultureInfo.InvariantCulture.LCID && !c.IsNeutralCulture)
  187. .Select(c => new RegionInfo(c.LCID))
  188. .Distinct()
  189. .OrderBy(r => r.DisplayName);
  190. foreach (var country in countries)
  191. {
  192. if (country.EnglishName == funcVal)
  193. selectBuilder.AppendFormat("<option selected value=\"{0}\">{1}</option>", country.EnglishName, country.DisplayName);
  194. else
  195. selectBuilder.AppendFormat("<option value=\"{0}\">{1}</option>", country.EnglishName, country.DisplayName);
  196. }
  197. selectBuilder.Append("</select>");
  198. return new HtmlString(selectBuilder.ToString());
  199. }
  200. /// <summary>
  201. /// Generates a Span HTML element
  202. /// </summary>
  203. /// <param name="htmlHelper">HtmlHelper context.</param>
  204. /// <param name="content">The span content text.</param>
  205. /// <param name="htmlAttributes">Additional HTML attributes.</param>
  206. /// <returns></returns>
  207. public static IHtmlContent Span(this IHtmlHelper htmlHelper, string content, object htmlAttributes = null)
  208. {
  209. var attributes = htmlAttributes == null
  210. ? new string[0]
  211. : htmlAttributes.GetType().GetProperties()
  212. .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributes)))
  213. .ToArray();
  214. var htmlString = String.Format("<span {0}>{1}</span>", String.Join(" ", attributes), content);
  215. return new HtmlString(htmlString);
  216. }
  217. /// <summary>
  218. /// Return HTML markup for the <paramref name="valueExpression"/>, using a display template for a search selection field.
  219. /// </summary>
  220. /// <typeparam name="TModel">Model type.</typeparam>
  221. /// <typeparam name="TValue">Model value type.</typeparam>
  222. /// <param name="htmlHelper">HtmlHelper context.</param>
  223. /// <param name="valueExpression">Expression for the requested model value.</param>
  224. /// <param name="textExpression">Expression for the requested model text.</param>
  225. /// <param name="searchActionUrl">POST action URL that returns a model enumeration as JSON result.</param>
  226. /// <param name="htmlAttributesValue">Additional HTML attributes for the value input.</param>
  227. /// <param name="htmlAttributesText">Additional HTML attributes for the text input.</param>
  228. /// <returns></returns>
  229. public static IHtmlContent SearchFor<TModel, TValue>(
  230. this IHtmlHelper<TModel> htmlHelper,
  231. Expression<Func<TModel, TValue>> valueExpression,
  232. Expression<Func<TModel, string>> textExpression,
  233. string searchActionUrl,
  234. object htmlAttributesValue = null,
  235. object htmlAttributesText = null)
  236. {
  237. var attributesText = htmlAttributesText == null
  238. ? new string[0]
  239. : htmlAttributesText.GetType().GetProperties()
  240. .Where(p => p.Name != "name")
  241. .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributesText)))
  242. .ToArray();
  243. var attributesValue = htmlAttributesValue == null
  244. ? new string[0]
  245. : htmlAttributesValue.GetType().GetProperties()
  246. .Where(p => p.Name != "name")
  247. .Select(p => String.Format("{0}='{1}'", p.Name.Replace("_", "-"), p.GetValue(htmlAttributesValue)))
  248. .ToArray();
  249. var id = RandomId();
  250. var valFunc = valueExpression.Compile();
  251. var funcVal = htmlHelper.ViewData.Model == null
  252. ? default
  253. : valFunc(htmlHelper.ViewData.Model);
  254. var isDefaultValue = funcVal == null || (funcVal != null && funcVal.ToString() == default(TValue).ToString());
  255. var textFunc = textExpression.Compile();
  256. var funcText = htmlHelper.ViewData.Model == null
  257. ? default
  258. : textFunc(htmlHelper.ViewData.Model);
  259. var memberInfoText = textExpression.Body.NodeType == ExpressionType.MemberAccess
  260. ? ((MemberExpression)textExpression.Body).Member
  261. : null;
  262. var nameText = memberInfoText == null
  263. ? Guid.NewGuid().ToShortString()
  264. : memberInfoText.Name;
  265. if (htmlAttributesText != null && htmlAttributesText.GetType().GetProperty("name") != null)
  266. nameText = htmlAttributesText.GetType().GetProperty("name").GetValue(htmlAttributesText).ToString();
  267. var memberInfoValue = valueExpression.Body.NodeType == ExpressionType.MemberAccess
  268. ? ((MemberExpression)valueExpression.Body).Member
  269. : null;
  270. var nameValue = memberInfoValue == null
  271. ? Guid.NewGuid().ToShortString()
  272. : memberInfoValue.Name;
  273. if (htmlAttributesValue != null && htmlAttributesValue.GetType().GetProperty("name") != null)
  274. nameValue = htmlAttributesValue.GetType().GetProperty("name").GetValue(htmlAttributesValue).ToString();
  275. var scriptBuilder = new StringBuilder();
  276. scriptBuilder.AppendFormat(
  277. "<script type='text/javascript'>\n" +
  278. "$(document).ready(function() {{\n" +
  279. " $(\"#text_{0}\").on('keypress', function (e) {{\n" +
  280. " if (e.keyCode == 13)\n" +
  281. " {{\n" +
  282. " search_{0}();\n" +
  283. " e.preventDefault();\n" +
  284. " }};\n" +
  285. " }});\n" +
  286. " $(\"#text_{0}\").on('keydown', function (e) {{\n" +
  287. " if (e.keyCode == 8 && $(\"#value_{0}\").val() != '') {{\n" +
  288. " $(\"#text_{0}\").val('');\n" +
  289. " $(\"#value_{0}\").val('');\n" +
  290. " $(\"#text_{0}\").next().children('.fa-check-circle').fadeOut('fast', function() {{\n" +
  291. " $(\"#text_{0}\").next().children(\".fa-ellipsis-h\").fadeIn('fast');\n" +
  292. " }});\n" +
  293. " e.preventDefault();\n" +
  294. " }};\n" +
  295. " }});\n" +
  296. "}});\n" +
  297. "\n" +
  298. "function search_{0}()\n" +
  299. "{{\n" +
  300. " $.ajax({{\n" +
  301. " method: 'POST',\n" +
  302. " url: '{1}',\n" +
  303. " data:\n" +
  304. " {{\n" +
  305. " SearchTerm: $('#text_{0}').val()\n" +
  306. " }},\n" +
  307. " success: function(data) {{\n" +
  308. " $(\"#searchModal_{0}\").find('.list-group').empty();\n" +
  309. " if (data.length > 0)\n" +
  310. " {{\n" +
  311. " $(data).each(function(index, elem) {{\n" +
  312. " $(\"#searchModal_{0}\").find('.list-group').append(\n" +
  313. " \"<a href='#' class='list-group-item list-group-item-info list-group-item-action'" +
  314. " data-toggle='list' data-val='{{0}}' data-text='{{1}}' ondblclick='select_{0}()'>{{1}}</a>\"\n" +
  315. " .format(elem.searchId, elem.searchText));\n" +
  316. " }});\n" +
  317. " }}\n" +
  318. " else\n" +
  319. " {{\n" +
  320. " $(\"#searchModal_{0}\").find('.list-group').append(\n" +
  321. " \"'<button type='button' class='list-group-item'>Keine Treffer</button>'\");\n" +
  322. " }}\n" +
  323. " $(\"#searchModal_{0}\").modal('show');\n" +
  324. " }},\n" +
  325. " error: function(msg) {{\n" +
  326. " \n" +
  327. " }}\n" +
  328. " }});\n" +
  329. "}}\n" +
  330. "\n" +
  331. "function select_{0}()\n" +
  332. "{{\n" +
  333. " $(\"#searchModal_{0}\").modal('hide');\n" +
  334. " var val = $(\"#searchModal_{0}\").find('a.active').attr('data-val');\n" +
  335. " var text = $(\"#searchModal_{0}\").find('a.active').attr('data-text');\n" +
  336. " $(\"#value_{0}\").val(val);\n" +
  337. " $(\"#text_{0}\").val(text);\n" +
  338. " $(\"#text_{0}\").next().children('.fa-ellipsis-h').fadeOut('fast', function() {{\n" +
  339. " $(\"#text_{0}\").next().children('.fa-check-circle').fadeIn('fast');\n" +
  340. " $(\"#text_{0}\").focus();\n" +
  341. " }});\n" +
  342. "}}" +
  343. "</script>",
  344. id,
  345. searchActionUrl
  346. );
  347. var contentBuilder = new StringBuilder();
  348. contentBuilder.AppendFormat(
  349. "<div class='input-group'>\n" +
  350. "<div class='input-group-prepend'>\n" +
  351. "<button class='btn btn-info fas fa-search' type='button' onclick='search_{0}()'></button>\n" +
  352. "</div>\n" +
  353. "<input id='text_{0}' type='text' name='{1}' value='{2}' class='form-control' placeholder='Suchbegriff'\n" +
  354. " aria-label='Suchbegriff' aria-describedby='basic-addon1' {5} />\n" +
  355. "<div class='input-group-append'>\n" +
  356. " <span class='input-group-text bg-success text-white rounded-right fas fa-cust-lh fa-check-circle' {7}></span>\n" +
  357. " <span class='input-group-text rounded-right fas fa-cust-lh fa-ellipsis-h' {8}></span>\n" +
  358. "</div>\n" +
  359. "</div>\n" +
  360. "\n" +
  361. "<input id='value_{0}' type='hidden' name='{3}' value='{4}' {6} />\n" +
  362. "\n" +
  363. "<div id='searchModal_{0}' class='modal fade'>\n" +
  364. "<div class='modal-dialog'>\n" +
  365. "<div class='modal-content'>\n" +
  366. "<div class='modal-header'>\n" +
  367. "<h4 class='modal-title'>Suchergebnisse</h4>\n" +
  368. "</div>\n" +
  369. "<div class='modal-body overflow-auto' style='max-height: 400px'>\n" +
  370. "<div class='list-group'>\n" +
  371. "</div>\n" +
  372. "</div>\n" +
  373. "<div class='modal-footer'>\n" +
  374. "<div class='btn btn-secondary' data-dismiss='modal'>Schließen</div>\n" +
  375. "<div class='btn btn-success' onclick='select_{0}()'>Auswählen</div>\n" +
  376. "</div>\n" +
  377. "</div>\n" +
  378. "</div>\n" +
  379. "</div>",
  380. id,
  381. nameText,
  382. funcText,
  383. nameValue,
  384. funcVal,
  385. String.Join(" ", attributesText),
  386. String.Join(" ", attributesValue),
  387. (htmlHelper.ViewData.Model == null || isDefaultValue ? "style='display: none'" : String.Empty),
  388. (htmlHelper.ViewData.Model == null || isDefaultValue ? String.Empty : "style='display: none'")
  389. );
  390. var htmlString = String.Format("{0}\n{1}", scriptBuilder.ToString(), contentBuilder.ToString());
  391. return new HtmlString(htmlString);
  392. }
  393. #endregion
  394. #region Helper
  395. /// <summary>
  396. /// Generates a random short ID string
  397. /// </summary>
  398. /// <param name="htmlHelper">HtmlHelper context.</param>
  399. public static string RandomId(this IHtmlHelper htmlHelper)
  400. {
  401. return Guid.NewGuid().ToShortString();
  402. }
  403. /// <summary>
  404. /// Generates a random short ID string
  405. /// </summary>
  406. public static string RandomId()
  407. {
  408. return Guid.NewGuid().ToShortString();
  409. }
  410. /// <summary>
  411. /// Converts an object in a JS literal object format
  412. /// </summary>
  413. /// <param name="value">The object to be converted.</param>
  414. private static string JsonConvertWithoutQuotes(object value)
  415. {
  416. var serializer = new JsonSerializer();
  417. var stringWriter = new StringWriter();
  418. using (var writer = new JsonTextWriter(stringWriter))
  419. {
  420. writer.QuoteName = false;
  421. serializer.Serialize(writer, value);
  422. }
  423. return stringWriter.ToString();
  424. }
  425. #endregion
  426. }
  427. }