Skip to content

Instantly share code, notes, and snippets.

@minhhungit
Last active August 10, 2020 17:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save minhhungit/4915e4d6cf96c9868a7648f7d2f7cacd to your computer and use it in GitHub Desktop.
Save minhhungit/4915e4d6cf96c9868a7648f7d2f7cacd to your computer and use it in GitHub Desktop.
bootstrap-popover-with-serenity-form
.popover.i-am-a-class.Date {
width: 600px;
max-width: unset;
color:red;
}
/* Modules/Common/Helpers/J.HelpPopover.ts */
//===================================================
// Copyright @ 2020
// Author : Hung Vo (it.minhhung@gmail.com)
// Time : 2020, August 06
// Description : HelpPopover
//===================================================
namespace J {
export class HelpPopover {
public buttons: HelpPopoverItem[] = [];
constructor(
public form: Serenity.PrefixedContext,
public propertyItems: Serenity.PropertyItem[],
public klassName?: string,
public bootstrapPopoverOption?: any) {
this.klassName = Q.coalesce(klassName, "");
}
public init() {
if (this.form && this.propertyItems) {
this.propertyItems.forEach((editor, idx) => {
let popoverEditorOptionKey = "j_custom_help_popover";
if (editor.editorParams && editor.editorParams[popoverEditorOptionKey]) {
let helpData = JSON.parse(editor.editorParams[popoverEditorOptionKey]);
let button = this.createHelpPopoverButton(this.form[editor.name], helpData, this.bootstrapPopoverOption);
if (button) {
this.buttons.push({
editor: this.form[editor.name],
helpButton: button
});
}
}
});
}
}
public createHelpPopoverButton(editorField: Serenity.Widget<any> | string, opt: HelpPopoverOption, bootstrapPopoverOption?: any): JQuery {
//console.log(this.form);
let editor: any;
if (typeof editorField == "string") {
editor = this.form[editorField];
}
else {
editor = editorField;
}
if (!editor) {
return;
}
if (editor && editor.element) {
let fieldName = editor.element.attr("id").split("_").pop();
let trigger: string = Q.coalesce(opt?.trigger, "hover");
let position: "caption" | "editor" = Q.coalesce(opt?.position, "editor");
let html: boolean = Q.coalesce(opt?.html, false);
bootstrapPopoverOption = Q.extend({ delay: { "show": 10, "hide": 100 } }, bootstrapPopoverOption || {});
//let enumEditor: Serenity.EnumEditor = (Q as any).safeCast(editor, Serenity.EnumEditor);
//if (enumEditor) {
// console.log(enumEditor.items);
//}
//else {
// let lookup: Serenity.LookupEditor = (Q as any).safeCast(editor, Serenity.LookupEditor);
// if (lookup) {
// //console.log(lookup.items);
// }
//}
let helpButton: JQuery;
let uniqueId: string = J.createGuid();
let helpKlass = "j-custom-inplace-help-button";
let helpPopoverKlass = "j-custom-help-popover";
if (position == "caption") {
helpButton = $(`<a tabindex="0" role="button" class="${helpKlass}" data-toggle="popover" style="padding-left: 5px"><i class="fa fa-question-circle" style="color: #3c8dbc;"></i></a>`);
}
else {
helpButton = $(`<a tabindex="0" role="button" class="inplace-button ${helpKlass}" data-toggle="popover"><b><i class="fa fa-question-circle" style="color: #3c8dbc"></i></b></a>`);
}
helpButton.attr("j-popover-id", uniqueId);
helpButton.attr("j-popover-field-name", fieldName);
helpButton.addClass(this.klassName);
// https://bootstrapdocs.com/v3.3.6/docs/javascript/#popovers
if (opt?.content) {
helpButton.attr('data-content', opt?.content);
}
if (opt?.title) {
helpButton.attr('title', `${Q.text(opt?.title)}`);
}
helpButton.attr('data-placement', Q.coalesce(opt?.placement, "auto"))
.attr("data-trigger", trigger)
.attr('data-html', `${html}`)
.attr('data-container', "body")
.click(e => {
let target = $(e.target);
if (!target.hasClass(helpKlass)) {
target = target.closest(`.${helpKlass}`);
}
//if (target.parent().hasClass("inplace-button")) {
// target = target.parent();
//}
//else {
// if (target.parent().parent().hasClass('inplace-button')) {
// target = target.parent().parent();
// }
//}
(target as any).popover(bootstrapPopoverOption)
.data('bs.popover')
.tip()
.addClass(helpPopoverKlass)
.addClass(`${this.klassName}`)
.addClass(`j-popover-id-${uniqueId}`)
.addClass(`${fieldName}`);
(target as any).popover("toggle");
});
//registryEvents && registryEvents(helpButton);
//helpButton.on('hidden.bs.popover', function () {
// Q.notifyInfo("hidden.bs.popover");
//});
//helpButton.on('show.bs.popover', function () {
// Q.notifyInfo("show.bs.popover");
//});
if (position == "caption") {
helpButton.appendTo(editor.getGridField().find(".caption"));
}
else {
helpButton.insertBefore(editor.getGridField().find(".vx"));
}
(helpButton as any).popover(bootstrapPopoverOption)
.data('bs.popover')
.tip()
.addClass(helpPopoverKlass)
.addClass(`${this.klassName}`)
.addClass(`j-popover-id-${uniqueId}`)
.addClass(`${fieldName}`);
(helpButton as any).popover();
return helpButton;
}
return null;
}
}
export function createGuid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
export class HelpPopoverItem {
editor: Serenity.Widget<any>;
helpButton: JQuery;
}
export class HelpPopoverOption {
content: string;
title?: string;
trigger?: string // "click" | "hover" | "focus" | "manual";
placement?: "top" | "bottom" | "left" | "right" | "auto";
position?: "caption" | "editor";
html?: boolean;
}
}
/* Modules/Common/Helpers/J.HelpPopoverAttribute.cs */
//===================================================
// Copyright @ 2020
// Author : Hung Vo (it.minhhung@gmail.com)
// Time : 2020, August 06
// Description : HelpPopover
//===================================================
using Newtonsoft.Json;
using Serenity.ComponentModel;
namespace [YOUR_NAMESPACE]
{
public partial class HelpPopoverAttribute : EditorOptionAttribute
{
private const string optionKey = "j_custom_help_popover";
public HelpPopoverAttribute(string content)
: base(optionKey, BuildHelpData(content))
{
}
public HelpPopoverAttribute(string content, string title)
: base(optionKey, BuildHelpData(content, title))
{
}
public HelpPopoverAttribute(string content = null, string title = null, string trigger = null, string placement = null, string position = null, bool html = false)
: base(optionKey, BuildHelpData(content: content, title: title, trigger: trigger, placement: placement, position: position, html: html))
{
}
static string BuildHelpData(string content)
{
return JsonConvert.SerializeObject(new { content });
}
static string BuildHelpData(string content, string title)
{
return JsonConvert.SerializeObject(new { content, title });
}
static string BuildHelpData(string content = null, string title = null, string trigger = null, string placement = null, string position = null, bool? html = false)
{
return JsonConvert.SerializeObject(new { content, title, trigger, placement, position, html });
}
}
public class HelpPopoverTypes
{
public class Trigger
{
public const string Click = "click";
public const string Hover = "hover";
public const string Focus = "focus";
public const string Manual = "manual";
}
public class Placement
{
public const string Top = "top";
public const string Bottom = "bottom";
public const string Left = "left";
public const string Right = "right";
public const string Auto = "auto";
}
public class Position
{
public const string Caption = "caption";
public const string Editor = "editor";
}
}
}
//===================================================
// Copyright @ 2020
// Author : Hung Vo (it.minhhung@gmail.com)
// Time : 2020, August 06
// Description : HelpPopover
//===================================================
/* wwwroot/Content/site/site.less */
.popover.j-custom-help-popover {
display: block !important;
z-index: 1101;
/*min-width: 400px*/
}
//===================================================
// Copyright @ 2020
// Author : Hung Vo (it.minhhung@gmail.com)
// Time : 2020, August 06
// Description : HelpPopover
//===================================================
export class TimelogRecordDialog extends Serenity.EntityDialog<TimelogRecordRow, any> {
protected getFormKey() { return TimelogRecordForm.formKey; }
// ...
protected form = new TimelogRecordForm(this.idPrefix);
constructor() {
super();
let helpPopover = new J.HelpPopover(this.form, this.getPropertyItems(), "i-am-a-class").init();
// or
// let helpPopover = new J.HelpPopover(this.form, this.getPropertyItems(), "i-am-a-class");
// helpPopover.createHelpPopoverButton(TimelogRecordRow.Fields.UserId, { content: "My content", title: "My title" });
// or
//let helpPopover = new J.HelpPopover(this.form, this.getPropertyItems(), "i-am-a-class");
//helpPopover.klassName = "xxx";
//helpPopover.init();
//(helpPopover.buttons[1].helpButton as any).on('hidden.bs.popover', function () {
// Q.notifyWarning("test");
//});
}
}
//===================================================
// Copyright @ 2020
// Author : Hung Vo (it.minhhung@gmail.com)
// Time : 2020, August 06
// Description : HelpPopover
//===================================================
namespace YOUR_NAME_SPACE.Default.Forms
{
using Serenity;
using Serenity.ComponentModel;
using Serenity.Data;
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.IO;
using YOUR_NAME_SPACE.Default.Entities;
[FormScript("Default.TimelogRecord")]
[BasedOnRow(typeof(Entities.TimelogRecordRow), CheckNames = true)]
public class TimelogRecordForm
{
[ReadOnly(true)]
public Int32? UserId { get; set; }
[HelpPopover(
content: "Content uses html format <strong>Hello World</strong> <i class='fa fa-question-circle'></i><br />New line.",
title: "This is title",
placement: "top",
html: true)]
public Int64 ProjectId { get; set; }
[HelpPopover("Another content with html = false and default palcement (auto) <a href='https://google.com' target='_blank'>Link</a>", trigger: "hover focus", html: true)]
public DateTime Date { get; set; }
//[HelpPopover(
// content: "Help button is placed at field caption and will show after clicking on button",
// position: "caption",
// trigger: "focus")]
public TimelogRecordStatus Status { get; set; }
[HelpPopover(
content: "Help button is placed at field caption and will show after clicking on button",
position: "caption",
trigger: "focus")]
public List<TimelogTaskItemRow> TaskList { get; set; }
[HalfWidth, ReadOnly(true), HideOnInsert(true)]
public DateTime CreatedDate { get; set; }
[HalfWidth, ReadOnly(true), HideOnInsert(true)]
public Int32 CreatedBy { get; set; }
[HalfWidth, ReadOnly(true), HideOnInsert(true)]
public DateTime EditedDate { get; set; }
[HalfWidth, ReadOnly(true), HideOnInsert(true)]
public Int32 EditedBy { get; set; }
}
}
@minhhungit
Copy link
Author

UPDATE: fixed trigger focus issue: it didn't dismiss-on-next-click

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment