Adding glyphicon icons to form fields - parsley.js

I'm wanting to add icons to form fields like bootstrap has: http://getbootstrap.com/css/?#forms-control-validation
I was able to get the class to display properly on the form-group by adjusting the options:
successClass: 'has-success',
errorClass: 'has-error',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
but i'm unable to figure out the best way to add the error or checkmark glyphicon. I assume it may have something to do with the errorWrapper / errorContainer but there isn't one for successWrapper/container

I ended up coming up with something else:
var bootstrapParsleyOptions = {
successClass: 'has-success has-feedback',
errorClass: 'has-error has-feedback',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
};
$.extend(true, ParsleyUI, {
enableBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
window.Parsley.on('form:init', function () {
$(this.$element).find(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
});
window.Parsley.on('field:validated', function () {
var element = this.$element;
if (this.validationResult == true) {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-remove').addClass('glyphicon-ok');
$(element).siblings(".sr-only").text("(success)");
} else {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-ok').addClass('glyphicon-remove');
$(element).siblings(".sr-only").text("(error)");
}
});
},
clearBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
}
});
To enable it:
$("#form").parsley(bootstrapParsleyOptions);
ParsleyUI.enableBootstrap();
To reset it:
$("#form").parsley(bootstrapParsleyOptions).reset();
ParsleyUI.enableBootstrap();

I imagine that you can obtain what you want with CSS, something like
.parsley-success::before { content: '√'; }

Related

Add another handler for "SyntheticEvent"

There's a 3rd-party library that creates table rows and buttons inside those rows. Both <tr> and <button> are constructed with an "onClick" property, but the authors forgot to call stopPropagation() and so when clicking a button it also triggers the <tr> handler:
render: (_text: any, record: TableRecord) => {
return createElement(
"button",
{
className: button.actionButtonClass,
onClick: () => {
onClickHandler(
record,
button.actionButtonOnClickAction,
button.actionButtonOnClickMf,
button.actionButtonOnClickNf,
button.actionButtonOnClickForm,
button.actionButtonOnClickOpenPageAs
);
}
I can't alter the code above, but I tried to add another handler:
mxtreetable.componentDidUpdate = function() {
mxtreetabledom.querySelectorAll(".actionButton").forEach((btn) => {
if (!btn._my_have_stopPropagation) {
btn._my_have_stopPropagation = true;
btn.addEventListener("click", function(ev) {
ev.stopPropagation();
});
}
});
};
However, I learned that PointerEvent is not handled directly. It first bubbles to the container, then in the container's click handler it's wrapped with SyntheticEvent and that SyntheticEvent is passed back to the button. So calling stopPropagation() on the native event will prevent what we defined in the "onClick" property.
How do I subscribe to SynteticEvent so I can call stopPropagation on that event?
Upd This is what I came up with. It's full of hacks
// https://github.com/mendixlabs/mendix-tree-table/issues/35 BEGIN
function installOnClickHook(props) {
if (!props.onClick._my_have_stopPropagation) {
var oldOnClick = props.onClick;
props.onClick = function(ev) {
oldOnClick.apply(this, arguments);
ev.stopPropagation();
};
props.onClick._my_have_stopPropagation = true;
}
}
mxtreetable.componentDidUpdate = function() {
mxtreetabledom.querySelectorAll(".actionButton").forEach((btn) => {
var props = FindReactProps(btn);
installOnClickHook(props);
});
};
function FindReactProps(dom) {
const key = Object.keys(dom).find(key=>{
return key.startsWith("__reactProps$")
});
// Somebody assigns another __reactProps to <button> without componentDidUpdate() on parent component
dom._my_props = dom[key];
Object.defineProperty(dom, key, {configurable: true, get: function() {
return this._my_props;
}, set: function (x) {
this._my_props = x;
installOnClickHook(x);
} });
return dom[key];
}
// https://github.com/mendixlabs/mendix-tree-table/issues/35 END

How to redraw specific DOM asynchronously in Mithril

I have sub-components that are updated by injecting state from the parent component.
I need to populate the model using an asynchronous function when the value of the parent component changes.
And I want to draw a new subcomponent after the asynchronous operation is finished.
I checked the change of the parent component value in onbeforeupdate, executed the asynchronous function, and then executed the redraw function, but this gets stuck in an infinite loop.
...
async onbeforeupdate((vnode)) => {
if (this.prev !== vnode.attrs.after) {
// Update model data
await asyncRequest();
m.redraw();
}
}
view() {
return (...)
}
...
As far as I can tell onbeforeupdate does not seem to support async calling style, which makes sense because it would hold up rendering. The use-case for onbeforeupdate is when you have a table with 1000s of rows. In which case you'd want to perform that "diff" manually. Say by comparing items length to the last length or some other simple computation.
This sort of change detection should happen in your model when the parent model changes, trigger something that changes the child model. Then the child view will return a new subtree which will be rendered.
In this small example the list of items are passed directly from the parent to the child component. When the list of items increases, by pressing the load button, the updated list of items is passed to the child and the DOM is updated during the redraw. There is another button to toggle if the decision to take a diff in the view should be done manually or not.
You can see when the views are called in the console.
The second example is the more common/normal Mithril usage (with classes).
Manual Diff Decision Handling
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class AppModel {
/* Encapsulate our model. */
constructor() {
this.child = {
items: [],
manualDiff: true,
};
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.child.items[this.child.items.length] = values[i];
}
}
getChildItems() {
return this.child.items;
}
toggleManualDiff() {
this.child.manualDiff = !this.child.manualDiff;
}
getManualDiffFlag() {
return this.child.manualDiff;
}
}
function setupApp(model) {
/* Set our app up in a namespace. */
class App {
constructor(vnode) {
this.model = model;
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
manualDiff: this.model.getManualDiffFlag(),
items: this.model.getChildItems(),
}),
m('button[type=button]', {
onclick: (e) => {
this.model.toggleManualDiff();
}
}, 'Toggle Manual Diff Flag'),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.model.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
constructor(vnode) {
this.lastLength = vnode.attrs.items.length;
}
onbeforeupdate(vnode, old) {
if (vnode.attrs.manualDiff) {
// Only perform the diff if the list of items has grown.
// THIS ONLY WORKS FOR AN APPEND ONLY LIST AND SHOULD ONLY
// BE DONE WHEN DEALING WITH HUGE SUBTREES, LIKE 1000s OF
// TABLE ROWS. THIS IS NOT SMART ENOUGH TO TELL IF THE
// ITEM CONTENT HAS CHANGED.
let changed = vnode.attrs.items.length > this.lastLength;
if (changed) {
this.lastLength = vnode.attrs.items.length;
}
console.log("changed=" + changed + (changed ? ", Peforming diff..." : ", Skipping diff..."));
return changed;
} else {
// Always take diff, default behaviour.
return true;
}
}
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
}
// Inject our model.
setupApp(new AppModel());
</script>
</body>
</html>
Normal Usuage
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class App {
constructor(vnode) {
this.items = [];
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.items[this.items.length] = values[i];
}
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
items: this.items
}),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
</script>
</body>
</html>
It should work. Maybe something is wrong with updating this.prev

Autocomplete off on Angular Directive for Date Picker

I have a directive for JQuery Date picker which injects date picker into input HTML control. This was developed by a previous developer and I am pretty new to Angular at this moment.
My question is that is there any way to prevent showing auto complete on all the date pickers that we inject via this directive?
export class DanialDatePickerDirective implements ControlValueAccessor {
constructor(protected el: ElementRef, private renderer: Renderer) { }
#Input() dateformat: string = "DD-MMM-YY";
#Input() ngModel: any;
#Input() setDefaultDate: boolean;
onModelChange: Function = () => { };
onModelTouched: Function = () => { };
writeValue(value: any) {
if (value) {
var ff = new Date(value);
$(this.el.nativeElement).datepicker("setDate", ff);
}
else {
$(this.el.nativeElement).datepicker("setDate", "");
}
}
registerOnChange(fn: Function): void {
this.onModelChange = fn;
}
registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}
onBlur() {
this.onModelTouched();
}
ngAfterViewInit() {
var self = this;
$(this.el.nativeElement).datepicker({
dateFormat: 'dd-M-y',
changeMonth: true,
changeYear: true,
showOtherMonths: true,
selectOtherMonths: true
});
if (this.setDefaultDate) {
var ff = new Date(self.ngModel);
setTimeout(function () {
$(self.el.nativeElement).datepicker("setDate", ff);
}, 200);
}
$(this.el.nativeElement).on('change', (e: any) => {
var model = e.target.value;
var date = null;
var monthstring = '';
if (model.indexOf("-") > 0){
monthstring = model.substring(model.indexOf("-") + 1, 5);
}
if (isNaN(parseInt(monthstring))) {
var tt = moment(model, "DD-MMM-YY").format('YYYY-MM-DD');
date = tt;
model = moment(model, "DD-MMM-YYYY").format('MM-DD-YYYY')
}
else {
date = moment(model, "DD-MM-YYYY").format('YYYY-MM-DD');
model = moment(model, "DD-MM-YYYY").format('MM-DD-YYYY')
}
$(".ui-datepicker a").removeAttr("href");
self.onModelChange(date);
self.writeValue(date.toString());
});
}
}
The only approach who works for me:
First, make sure to set autocomplete="off" on both, the input element itself and the parent form.
Second, make sure to assign an unique name to your input field always.
This can be achieved by simply generating a random number and using this number in the name of the field.
private getUniqueName() {
return Math.floor(Math.random() * Date.now());
}
Explanation:
In the past, many developers would add autocomplete="off" to their
form fields to prevent the browser from performing any kind of
autocomplete functionality. While Chrome will still respect this tag
for autocomplete data, it will not respect it for autofill data.
https://developers.google.com/web/updates/2015/06/checkout-faster-with-autofill.
So autocomplete="off" solves the autocomplete issue. But to solve the autofill you need to play dirty with the browser by changing the name of the input over an over again, that way the browser will never know how to autofill ;)

How do I create an ag-Grid cell editor using React and TypeScript?

I see that the ag-grid-react repo has types, and I also see that the ag-grid-react-example repo has examples. But how do I put the two together and create a cell editor with React and Types?
I'm guessing it's something like this but I can't make TypeScript happy:
class MyCellEditor implements ICellEditorReactComp {
public getValue() {
// return something
}
public render() {
const { value } = this.props
// return something rendering value
}
}
I implemented ICellEditor and used ICellEditorParams for prop definitions. For example, this MyCellEditor example from their documentation:
// function to act as a class
function MyCellEditor () {}
// gets called once before the renderer is used
MyCellEditor.prototype.init = function(params) {
// create the cell
this.eInput = document.createElement('input');
this.eInput.value = params.value;
};
// gets called once when grid ready to insert the element
MyCellEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
MyCellEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
// returns the new value after editing
MyCellEditor.prototype.getValue = function() {
return this.eInput.value;
};
// any cleanup we need to be done here
MyCellEditor.prototype.destroy = function() {
// but this example is simple, no cleanup, we could
// even leave this method out as it's optional
};
// if true, then this editor will appear in a popup
MyCellEditor.prototype.isPopup = function() {
// and we could leave this method out also, false is the default
return false;
};
became this:
class MyCellEditor extends Component<ICellEditorParams,MyCellEditorState> implements ICellEditor {
constructor(props: ICellEditorParams) {
super(props);
this.state = {
value: this.props.eGridCell.innerText
};
}
// returns the new value after editing
getValue() {
// Ag-Grid will display this array as a string, with elements separated by commas, by default
return this.state.value;
};
// Not sure how to do afterGuiAttached()
// if true, then this editor will appear in a popup
isPopup() {
return true;
};
render() {
return (
<div>
hello
</div>
);
}
}

Ag-Grid header Component adding custom header templates

I'm still using Angular 1.x and ag-Grid (non-enterprise).
But I'm trying to add tooltips from angular ui-bootstrap. So I need to add an attribute to all headers: "uib-tooltip='headerValue' "
The issue is that it wants me to implement a whole new component. But the example component they put on the ag-grid website is super complicated and different from the default functionality. Why isn't there an easy way of doing these things?
Why deprecate headerTemplate?
So even if I want a slight change from the default headers, I basically need to take over responsibility for everything. And I don't even have the original version of header component as example code.
I just want to add some new HTML to the header that doesn't involve taking responsibility for sorting, field, menu etc.
I know this is old but...I was in this exact situation, where I was wondering why it wasn't provided... I went ahead and built my own which wasn't extremely hard. Since I think someone might find a default header component useful: here's one that is essentially the default
Note you'll need font awesome for the icons and make sure to use the custom component in your column options.
function customHeaderComponent() {
}
customHeaderComponent.prototype.init = function (agParams) {
this.agParams = agParams;
this.eGui = document.createElement('div');
this.eGui.innerHTML = '' +
'<span class="customHeaderLabel">' + this.agParams.displayName + '</span> ' +
'<span class="customSortDownLabel inactive"><i class="fa fa-caret-down""></i> </span>' +
'<span class="customSortUpLabel inactive"><i class="fa fa-caret-up""></i> </span>'+
'<span class="customFilterLabel inactive"><i class="fa fa-filter"></i> </span>'+
'<span class="customHeaderMenuButton"><i class="fa fa-tasks""></i></span>'
;
this.eMenuButton = this.eGui.querySelector(".customHeaderMenuButton");
this.eLabel = this.eGui.querySelector(".customHeaderLabel");
this.eSortDownButton = this.eGui.querySelector(".customSortDownLabel");
this.eSortUpButton = this.eGui.querySelector(".customSortUpLabel");
this.eFilterInd = this.eGui.querySelector(".customFilterLabel");
if (this.agParams.enableMenu) {
this.onMenuClickListener = this.onMenuClick.bind(this);
this.eMenuButton.addEventListener('click', this.onMenuClickListener);
} else {
this.eGui.removeChild(this.eMenuButton);
}
if (this.agParams.enableSorting) {
this.eLabel.addEventListener('click', this.changeSort.bind(this));
this.onSortChangedListener = this.onSortChanged.bind(this);
this.agParams.column.addEventListener('sortChanged', this.onSortChangedListener);
this.onSortChanged();
}
var self = this;
self.eFilterInd.hidden = !self.agParams.column.isFilterActive();
this.agParams.column.addEventListener('filterChanged', function() {
self.eFilterInd.hidden = !self.agParams.column.isFilterActive();
});
};
customHeaderComponent.prototype.changeSort = function (event) {
this.agParams.progressSort(event);
}
customHeaderComponent.prototype.onSortChanged = function () {
function deactivate(toDeactivateItems) {
toDeactivateItems.forEach(function (toDeactivate) {
toDeactivate.hidden = true;
});
}
function activate(toActivate) {
toActivate.hidden = false;
}
if (this.agParams.column.isSortAscending()) {
deactivate([this.eSortUpButton]);
activate(this.eSortDownButton)
} else if (this.agParams.column.isSortDescending()) {
deactivate([this.eSortDownButton]);
activate(this.eSortUpButton)
} else {
deactivate([this.eSortUpButton, this.eSortDownButton]);
}
};
customHeaderComponent.prototype.getGui = function () {
return this.eGui;
};
customHeaderComponent.prototype.onMenuClick = function () {
this.agParams.showColumnMenu(this.eMenuButton);
};
customHeaderComponent.prototype.onSortRequested = function (order, event) {
this.agParams.setSort(order, event.shiftKey);
};
customHeaderComponent.prototype.destroy = function () {
if (this.onMenuClickListener) {
this.eMenuButton.removeEventListener('click', this.onMenuClickListener)
}
};

Resources