I am using UI-grid, and I have a bunch of JS date objects like so:
"dob": new Date('1942-11-19')
I want to be able to filter the column by date when you click the "sort ascending/descending" buttons. As such, I have set the colDef up like so:
{
field: 'dob'
, displayName: 'D.O.B.'
, width: '130'
, editableCellTemplate: '<div><form name="inputForm"><input type="INPUT_TYPE" ng-class="\'colt\' + col.uid" ui-grid-editor ng-model="MODEL_COL_FIELD" style="border-bottom-color: #74B3CE; border-bottom-width: 2px;"></form></div>'
, headerCellClass: $scope.highlightFilteredHeader
, cellTemplate: '<div class="ui-grid-cell-contents" >{{grid.getCellValue(row, col)| date:\'MM-dd-yyyy\'}}</div>'
, cellFilter: 'date'
, type: 'date'
},
however, the column simply does not sort correctly. I even tried to set up a function to sort it from an external button like so:
function mostRecent(){
console.log('clicked mostRecent');
$scope.gridApi.grid.sortColumn(
$scope.gridApi.grid.getColumn('dob'), uiGridConstants.DESC
);
$scope.gridApi.grid.notifyDataChange(uiGridConstants.dataChange.ALL); //this line updates the rest of the columns accordingly
};
But it also causes a mish-mush sort that is not correct. Does anyone know what the issue is? I thought it might have to do with my cellTemplate, but after removing the template, there wasn't a difference...
Yes you are right, ui-grid doesn't support sorting of Date type columns.
However you can define a sortingAlgorithm in the columnDef.
Here is how your column definition should look like:
...
columnDefinition.sortingAlgorithm = function (firstDateString, secondDateString) {
var dateFormat = 'YYYY-MM-DD';
return function (firstDateString, secondDateString, dateFormat) {
if (!firstDateString && !secondDateString) {
return 0;
}
if (!firstDateString) {
return 1;
}
if (!secondDateString) {
return -1;
}
var firstDate = $window.moment(firstDateString, dateFormat);
if (!firstDate.isValid()) {
throw new Error('Invalid date: ', firstDateString);
}
var secondDate = $window.moment(secondDateString, dateFormat);
if (!firstDate.isValid()) {
throw new Error('Invalid date: ', secondDateString);
}
if (firstDate.isSame(secondDate)) {
return 0;
} else {
return firstDate.isBefore(secondDate) ? -1 : 1;
}
};
};
...
Please note that in this example Moment.js is used. It is a very useful library so you might probably find also another place in your project where to use it.
$scope.gridOptions = {
data: 'gridData',
columnDefs: [
{field: 'name', displayName: 'Name'},
{field:'age',
displayName:'Birth Date',
sortFn: function (aDate, bDate) {
var a=new Date(aDate);
var b=new Date(bDate);
if (a < b) {
return -1;
}
else if (a > b) {
return 1;
}
else {
return 0;
}
}
}]
};
Try this
http://plnkr.co/edit/0VD3X5YvuNSWAZlig95X?p=info
reference :
https://github.com/angular-ui/ui-grid/issues/222
You can define the Sorting Algorithm for the date fields in UI Grid like below
columnDefs: [
{
field: 'DateFrom', displayName: 'From',
sortingAlgorithm: function (aDate, bDate, rowA, rowB, direction) {
var a = new Date(moment(aDate, "DD-MM-YYYY").format("YYYY-MM-DD"));
//here DD-MM-YYYY is the current format in which the dates are returned
var b = new Date(moment(bDate, "DD-MM-YYYY").format("YYYY-MM-DD"));
if (a < b) {
return -1;
}
else if (a > b) {
return 1;
}
else {
return 0;
}
}
}
]
We can sort the ui-grid column containing date field in a simplest way.
Make use of cellTemplate in this way:
{
name: "Date",
field: 'date',
cellTemplate:'<div>{{row.entity.date | date:"dd/MM/yyyy"}}</div>'
},
So, you can choose any format for date, for eg. date:"dd-MM" etc.
Related
In my Angular application I need to swap from momentjs to dayjs.
Because I am using material I have to replace moment-date-adapter with a dayjs-date-adapter, so I write my own date adapter but I can't understand how momentjs can parse a date like 12122020 without any separator (you can see it in action here).
I try to achieve it by setting this MatDateFormats, with an array of dateinput.
But I don't know if it is the best solution because I don't see it in moment-date-adapter
MatDateFormats = {
parse: {
dateInput: ['D/M/YYYY', 'DMYYYY'],
},
display: {
dateInput: 'DD/MM/YYYY',
monthYearLabel: 'MMMM YYYY',
dateA11yLabel: 'DD/MM/YYYY',
monthYearA11yLabel: 'MMMM YYYY',
}
}
This is my dayjs-date-adapter
export interface DayJsDateAdapterOptions {
/**
* Turns the use of utc dates on or off.
* Changing this will change how Angular Material components like DatePicker output dates.
* {#default false}
*/
useUtc?: boolean;
}
/** InjectionToken for Dayjs date adapter to configure options. */
export const MAT_DAYJS_DATE_ADAPTER_OPTIONS = new InjectionToken<DayJsDateAdapterOptions>(
'MAT_DAYJS_DATE_ADAPTER_OPTIONS', {
providedIn: 'root',
factory: MAT_DAYJS_DATE_ADAPTER_OPTIONS_FACTORY
});
export function MAT_DAYJS_DATE_ADAPTER_OPTIONS_FACTORY(): DayJsDateAdapterOptions {
return {
useUtc: false
};
}
/** Creates an array and fills it with values. */
function range<T>(length: number, valueFunction: (index: number) => T): T[] {
const valuesArray = Array(length);
for (let i = 0; i < length; i++) {
valuesArray[i] = valueFunction(i);
}
return valuesArray;
}
/** Adapts Dayjs Dates for use with Angular Material. */
export class DayjsDateAdapter extends DateAdapter<Dayjs> {
private localeData: {
firstDayOfWeek: number,
longMonths: string[],
shortMonths: string[],
dates: string[],
longDaysOfWeek: string[],
shortDaysOfWeek: string[],
narrowDaysOfWeek: string[]
};
constructor(#Optional() #Inject(MAT_DATE_LOCALE) public dateLocale: string,
#Optional() #Inject(MAT_DAYJS_DATE_ADAPTER_OPTIONS) private options?:
DayJsDateAdapterOptions) {
super();
this.initializeParser(dateLocale);
}
private get shouldUseUtc(): boolean {
const {useUtc}: DayJsDateAdapterOptions = this.options || {};
return !!useUtc;
}
// TODO: Implement
setLocale(locale: string) {
super.setLocale(locale);
const dayJsLocaleData = this.dayJs().localeData();
this.localeData = {
firstDayOfWeek: dayJsLocaleData.firstDayOfWeek(),
longMonths: dayJsLocaleData.months(),
shortMonths: dayJsLocaleData.monthsShort(),
dates: range(31, (i) => this.createDate(2017, 0, i + 1).format('D')),
longDaysOfWeek: range(7, (i) => this.dayJs().set('day', i).format('dddd')),
shortDaysOfWeek: dayJsLocaleData.weekdaysShort(),
narrowDaysOfWeek: dayJsLocaleData.weekdaysMin(),
};
}
getYear(date: Dayjs): number {
return this.dayJs(date).year();
}
getMonth(date: Dayjs): number {
return this.dayJs(date).month();
}
getDate(date: Dayjs): number {
return this.dayJs(date).date();
}
getDayOfWeek(date: Dayjs): number {
return this.dayJs(date).day();
}
getMonthNames(style: 'long' | 'short' | 'narrow'): string[] {
return style === 'long' ? this.localeData.longMonths : this.localeData.shortMonths;
}
getDateNames(): string[] {
return this.localeData.dates;
}
getDayOfWeekNames(style: 'long' | 'short' | 'narrow'): string[] {
if (style === 'long') {
return this.localeData.longDaysOfWeek;
}
if (style === 'short') {
return this.localeData.shortDaysOfWeek;
}
return this.localeData.narrowDaysOfWeek;
}
getYearName(date: Dayjs): string {
return this.dayJs(date).format('YYYY');
}
getFirstDayOfWeek(): number {
return this.localeData.firstDayOfWeek;
}
getNumDaysInMonth(date: Dayjs): number {
return this.dayJs(date).daysInMonth();
}
clone(date: Dayjs): Dayjs {
return date.clone();
}
createDate(year: number, month: number, date: number): Dayjs {
const returnDayjs = this.dayJs()
.set('year', year)
.set('month', month)
.set('date', date);
return returnDayjs;
}
today(): Dayjs {
return this.dayJs();
}
parse(value: any, parseFormat: string): Dayjs | null {
if (value && typeof value === 'string') {
return this.dayJs(value, parseFormat, this.locale);
}
return value ? this.dayJs(value).locale(this.locale) : null;
}
format(date: Dayjs, displayFormat: string): string {
if (!this.isValid(date)) {
throw Error('DayjsDateAdapter: Cannot format invalid date.');
}
return date.locale(this.locale).format(displayFormat);
}
addCalendarYears(date: Dayjs, years: number): Dayjs {
return date.add(years, 'year');
}
addCalendarMonths(date: Dayjs, months: number): Dayjs {
return date.add(months, 'month');
}
addCalendarDays(date: Dayjs, days: number): Dayjs {
return date.add(days, 'day');
}
toIso8601(date: Dayjs): string {
return date.toISOString();
}
deserialize(value: any): Dayjs | null {
let date;
if (value instanceof Date) {
date = this.dayJs(value);
} else if (this.isDateInstance(value)) {
// NOTE: assumes that cloning also sets the correct locale.
return this.clone(value);
}
if (typeof value === 'string') {
if (!value) {
return null;
}
date = this.dayJs(value).toISOString();
}
if (date && this.isValid(date)) {
return this.dayJs(date);
}
return super.deserialize(value);
}
isDateInstance(obj: any): boolean {
return dayjs.isDayjs(obj);
}
isValid(date: Dayjs): boolean {
return this.dayJs(date).isValid();
}
invalid(): Dayjs {
return this.dayJs(null);
}
private dayJs(input?: any, format?: string, locale?: string): Dayjs {
if (!this.shouldUseUtc) {
return dayjs(input, format, locale, false);
}
return dayjs(input, {format, locale, utc: this.shouldUseUtc}, locale, false).utc();
}
private initializeParser(dateLocale: string) {
if (this.shouldUseUtc) {
dayjs.extend(utc);
}
dayjs.extend(LocalizedFormat);
dayjs.extend(customParseFormat);
dayjs.extend(localeData);
}
}
The dateInput that you use in the parse property of MatDateFormats is used in the parse function of your dayjs-date-adapter. Right now you supply an array as dateInput, but your function expects a string. Dayjs (unlike moment) cannot handle an array of formats. If you want to use an array, to support multiple formats, you must figure out which format of the array to use in your parse function. The easiest way to do this is probably just to loop over your possible formats and return the dayjs object if it is valid.
Something like this (note I have not tested this):
parse(value: any, parseFormats: string[]): Dayjs | null {
if (value && typeof value === 'string') {
parseFormats.forEach(parseFormat => {
const parsed = this.dayJs(value, parseFormat, this.locale);
if (parsed.isValid()) {
return parsed;
}
}
// return an invalid object if it could not be parsed with the supplied formats
return this.dayJs(null);
}
return value ? this.dayJs(value).locale(this.locale) : null;
}
Note in my own adapter I altered the private dayJs function a little bit, because providing locale also in the format options gave me some weird behavior. I didn't need the utc options, so I ended up using:
private dayJs(input?: any, format?: string, locale?: string): Dayjs {
return dayjs(input, format, locale);
}
An alternative to the approach above would be to just supply 1 dateInput (like : dateInput: 'D/M/YYYY'). And then make the parse function a little bit more flexible. I ended up with this:
parse(value: any, parseFormat: string): Dayjs | null {
if (value && typeof value === 'string') {
const longDateFormat = dayjs().localeData().longDateFormat(parseFormat); // MM/DD/YYY or DD-MM-YYYY, etc.
// return this.dayJs(value, longDateFormat);
let parsed = this.dayJs(value, longDateFormat, this.locale);
if (parsed.isValid()) {
// string value is exactly like long date format
return parsed;
}
const alphaNumericRegex = /[\W_]+/;
if (!alphaNumericRegex.test(value)) {
// if string contains no non-word characters and no _
// user might have typed 24012020 or 01242020
// strip long date format of non-word characters and take only the length of the value so we get DDMMYYYY or DDMM etc
const format = longDateFormat.replace(/[\W_]+/g, '').substr(0, value.length);
parsed = this.dayJs(value, format, this.locale);
if (parsed.isValid()) {
return parsed;
}
}
const userDelimiter = alphaNumericRegex.exec(value) ? alphaNumericRegex.exec(value)![0] : '';
const localeDelimiter = alphaNumericRegex.exec(longDateFormat) ? alphaNumericRegex.exec(longDateFormat)![0] : '';
const parts = value.split(userDelimiter);
const formatParts = longDateFormat.split(localeDelimiter);
if (parts.length <= formatParts.length && parts.length < 4) {
// right now this only works for days, months, and years, if time should be supported this should be altered
let newFormat = '';
parts.forEach((part, index) => {
// get the format in the length of the part, so if a the date is supplied 1-1-19 this should result in D-M-YY
// note, this will not work if really weird input is supplied, but that's okay
newFormat += formatParts[index].substr(0, part.length);
if (index < parts.length - 1) {
newFormat += userDelimiter;
}
});
parsed = this.dayJs(value, newFormat);
if (parsed.isValid()) {
return parsed;
}
}
// not able to parse anything sensible, return something invalid so input can be corrected
return this.dayJs(null);
}
return value ? this.dayJs(value).locale(this.locale) : null;
}
If you only want to support number only inputs (like 28082021) beside your specified input, you need the if statement with !alphaNumericRegex.test(value). This piece of code takes out any delimiters (like - or /) from your formatting string and also makes sure string with only days or days and months are supported (28 or 2808 for example). It will use the current month and year to fill up the missing values. If you only want to support full day-month-year strings you can omit the .substr part.
The piece of code below this if statement causes different types of user input to be supported, like 28-08-2021, 28/08/2021, 28 08 2021, 28-08-21, 28/08 etc..
I'm sure it won't work for every language, but it works for the most used userinputs in my language (dutch).
Hope this helps someone who has been struggling with this as well!
I have the follow problem: I need to consume a REST service (3rd party, not mine) and show the result data to the user using an Ext.grid.Panel.
The problem is I have no idea of the data structure and content ( it is a JSON from Geoserver's queryLayer ) so I can't have a store/grid/model field definition to respect the ExtJS MVC design.
So how can I be more flexible in this situation? I try to add a row to the grid by hand but after read https://www.sencha.com/forum/showthread.php?48625-how-can-i-insert-a-row-in-GRID I think it is a kind of crime to do
You can add a conversion layer for dynamic fields in the model class. The conversion will provide a string readable format for data you don't know the structure.
Ext.define('AppName.DynamicRow', {
extend: 'Ext.data.Model',
fields: [{
name: 'fixed1',
type: 'string'
}, {
name: 'fixed2',
type: 'string'
}, {
name: 'dynamic',
type: 'string',
calculate: function (data) {
Ext.Object.getAllKeys(data)
.map(function(key) {
return key + ': ' + data[key];
})
.join(', ');
}
}]
});
Then you will show all unstructured data in a grid column simply adding 'dynamic' field as dataIndex.
My workaround:
First, receive the data using a function to concentrate all creation stuff:
function addGrid ( title, data ) {
var storeColumns = getStoreColumnsFromJson( data[0] );
var gridColumns = getGridColumnsFromJson( data[0] );
var store = createStore( data, storeColumns );
var grid = createGrid( title, store, gridColumns );
myContainerWindowPanel.add( grid );
}
Now, I need to take a data sample (first row) to get the column names from the JSON data to the grid and its store:
function getStoreColumnsFromJson ( obj ) {
var keys = [];
for (var key in obj) {
if ( obj.hasOwnProperty(key) ) {
keys.push({name : key});
}
}
return keys;
}
function getGridColumnsFromJson ( obj ) {
var keys = [];
for (var key in obj) {
if ( obj.hasOwnProperty(key) ) {
keys.push({text: key, dataIndex: key});
}
}
return keys;
}
Now I'll create the grid and the store. I will not use the Model simply because this worked without it. If someone have a strong advice to create the model I'll appreciate.
function createGrid ( title, store, columnNames ) {
var dummyGrid = Ext.create('Ext.grid.Panel', {
border: false,
title : title,
store : store,
frame: false,
margin: "10 0 0 0",
flex:1,
loadMask: true,
columns:columnNames,
autoHeight:true
});
return dummyGrid;
}
function createStore ( storeData, columns ) {
var arrData = [];
var theData = storeData;
if ( !$.isArray( storeData ) ) {
arrData.push( storeData );
theData = arrData;
}
var store = Ext.create('Ext.data.Store',{
fields: columns,
autoLoad: true,
data: theData
});
return store;
}
I want to prefix a $ to the default y-axis label. My bar chart is using values in the millions so the chart is returning value-MM (80MM, 30MM). What I would like to do is format the y-axis like $-value-MM ($80MMm $30MM). I have tried the code below and can't get it to work?
yAxis: [{ // Primary yAxis
labels: {
formatter: function () {
return '$' + this.value;
}
},
title: {
text: 'Revenue',
If I understand the question correctly, your data already has 'MM' suffix and you want to add the prefix '$'.
Try,
yAxis: {
labels: {
format: '${value}'
}
}
One rather elaborate way to achieve this is to re-use the code Highcharts uses in their internal defaultLabelFormatter for axis that are numeric, and use it in the axis formatter.
An example of this, with your added prefix (JSFiddle):
yAxis: {
labels: {
formatter: function() {
var numericSymbols = Highcharts.getOptions().lang.numericSymbols;
var i = numericSymbols && numericSymbols.length;
var numericSymbolDetector = this.axis.isLog ? this.value : this.axis.tickInterval;
var UNDEFINED, ret, multi;
while (i-- && ret === UNDEFINED) {
multi = Math.pow(1000, i + 1);
if (numericSymbolDetector >= multi && (this.value * 10) % multi === 0 && numericSymbols[i] !== null) {
ret = Highcharts.numberFormat(this.value / multi, -1) + numericSymbols[i];
}
}
if (ret === UNDEFINED) {
if (Math.abs(this.value) >= 10000) {
ret = Highcharts.numberFormat(this.value, -1);
} else {
ret = Highcharts.numberFormat(this.value, -1, UNDEFINED, '');
}
}
return "$"+ret; // Adding the prefix
}
},
}
A experimental short form of this would be to call the defaultLabelFormatter with the essential parts of the context it requires. An example of this (JSFiddle):
yAxis: {
labels: {
formatter: function() {
return "$" + this.axis.defaultLabelFormatter.call({
axis: this.axis,
value: this.value
});
}
},
}
As the context is incomplete it wouldn't work as expected if your axis was datetime or categories or perhaps logarithmical, but should work for numeric axis. For the full picture I suggest looking at the full defaultLabelFormatter implementation.
Select2 Jquery Plugin
I was having hard time how to override the default message for minimum length input in jquery Select2.
by default the plugin gives the following message.
Default Text
Please enter 1 more characters
My requirement was to show, the following text
Required Text
Enter 1 Character
please share the solution.
Thanks.
The accepted answer does not work for Select2 v4. Expanding on the comment by #IsaacKleinman, the way to override the default messages for an individual Select2 instance is through the language property:
var opts = {
language: {
inputTooShort: function(args) {
// args.minimum is the minimum required length
// args.input is the user-typed text
return "Type more stuff";
},
inputTooLong: function(args) {
// args.maximum is the maximum allowed length
// args.input is the user-typed text
return "You typed too much";
},
errorLoading: function() {
return "Error loading results";
},
loadingMore: function() {
return "Loading more results";
},
noResults: function() {
return "No results found";
},
searching: function() {
return "Searching...";
},
maximumSelected: function(args) {
// args.maximum is the maximum number of items the user may select
return "Error loading results";
}
}
};
$('#mySelect').select2(opts);
To override the functions globally, call the set function on the defaults (according to the docs):
$.fn.select2.defaults.set("key", "value")
However, in our code we do it like this:
$.fn.select2.defaults.defaults['language'].searching = function(){
return 'Custom searching message'
};
I don't know why we don't follow the docs, but it works.
Solution
Here is the solution that i have found out.
Prior to v4
Initialize
$("input[name='cont_responsible'],input[name='corr_responsible'],input[name='prev_responsible'],input[name='pfmea_responsible']").select2({
minimumInputLength: 1,
formatInputTooShort: function () {
return "Enter 1 Character";
},
});
Note
Do not forget to add this code in your document. ready function.
$(document).ready(function () {
});
I shared my solution, any better solutions are welcome.
Thanks.
Using v4 and onwards
The following worked for V4. #Isaac Kleinman
language: { inputTooShort: function () { return ''; } },
You can try this on version 4.0 or higher
you can see reference for answer frome this link :
issues reference
$("#select2").select2({
minimumInputLength: 1,
language: {
inputTooShort: function() {
return 'Please Add More Text';
}
}
});
If you are using django-select2, just add attributes to your form in forms.py:
widget=BookSelect2Widget(
attrs={'data-minimum-input-length': 1}
)
Override the function behaviour like below
$.fn.select2.defaults = $.extend($.fn.select2.defaults, {
formatMatches: function(matches) {
return matches + $filter('translate')('label.matches.found');
},
formatNoMatches: function() {
return $filter('translate')('noMatches.found');
},
formatInputTooShort: function(input, min) {
var n = min - input.length;
return $filter('translate')('label.please.enter ') + n + $filter('translate')(' more.characters') + (n == 1 ? "" : "s");
},
formatInputTooLong: function(input, max) {
var n = input.length - max;
return $filter('translate')('please.delete ') + n + $filter('translate')('')('delete.characters') + (n == 1 ? "" : "s");
},
formatSelectionTooBig: function(limit) {
return $filter('translate')('select.only') + limit + $filter('translate')('select.item ') + (limit == 1 ? "" : "s");
},
formatLoadMore: function(pageNumber) {
return $filter('translate')('load.results');
},
formatSearching: function() {
return $filter('translate')('label.search');
}
});
}
Is there a way to customize jQuery UI spinner, so that A-Z letters (or any custom range) is possible?
Yes, this is possible. Here's a simple example using A-Z, adapted from the provided time example:
$.widget("ui.alphaspinner", $.ui.spinner, {
options: {
min: 65,
max: 90
},
_parse: function(value) {
if (typeof value === "string") {
return value.charCodeAt(0);
}
return value;
},
_format: function(value) {
return String.fromCharCode(value);
}
});
Usage:
$("#my-input").alphaspinner();
Example: http://jsfiddle.net/4nwTc/1/
The above example creates a new widget called alphaspinner that inherits from spinner. You can do this for just one spinner with the following:
$(function() {
var spinner = $("#alpha-spinner").spinner({
min: 65,
max: 90
}).data("spinner");
spinner._parse = function (value) {
if (typeof value === "string") {
return value.charCodeAt(0);
}
return value;
};
spinner._format = function (value) {
return String.fromCharCode(value);
}
});
Example: http://jsfiddle.net/4nwTc/2/
I built up on Andrews code and built a spinner widget that takes a string array for input.
You can see the solution here.