-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat(table): add field data formatter prop #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
lib/components/table.vue
Outdated
sortDesc: { | ||
type: Boolean, | ||
default: true | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might get prop mutation errors on sortBy
and sortDesc
due to the listeners on head click that update these values.
You might need local copies of these in data (i.e. localSort
and localDesc
), and then use the new .sync
modifiers on the props, and emit updated:sort-by
and update:sort-desc
events to update the users copy of the props. And then user watchers on the props to update the local copies.
And then in the _items
computed prop, look at the local copies and not the prop versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you like, I can tweak the props/data to handle this.
Just thinking you might want to break out the changes into separate PRs... one for the formatter function, and one for the new props for sorting, and one for the api-url |
return toString(a[sortBy]).localeCompare(toString(b[sortBy]), undefined, { | ||
numeric: true | ||
}); | ||
return ((a[sortBy] < b[sortBy]) && -1) || ((a[sortBy] > b[sortBy]) && 1) || 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
For your formatter function, you could have it default to a built in function that just returns the field data, and then the user can provide their own formatter if they want to do javascript based formatting. Basically just pass the formatter function two parameters, the field |
It was supposed as an alternative to slots, so if it not defined, then slots used.... In case if i make a default builtin func, then could be that slots probably will never work? |
Hmmm... The output of the built in function could be passed to the default content of the slots |
lib/components/table.vue
Outdated
> | ||
<template v-for="(field,key) in fields"> | ||
<td v-if="hasFormatter(field)" :key="key" :class="tdClass(field, item, key)" | ||
v-html="callFormatter(field, item, key)"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works well. You probably don't need to send the field
object (as the user probably has a reference to it in their app.
But maybe you could pass field
last, as most would probably only need key
and the item
record data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, i understand, will remove field param, and check how it will work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although it might be handy to have it still in there for some people, but as the 3rd argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, ok, accepted))
…callback function 2. Add api-url prop to pass to callback item provider function 3. Move sortBy and sortDesc props to Props instead Data, to allow define custom sorting on mount 4. Some refactor
a7f564f
to
fc32ccd
Compare
this._providerSetLocal(items); | ||
}); | ||
} else { | ||
// Provider returned Array data | ||
this._providerSetLocal(data); | ||
} | ||
}, | ||
hasFormatter(field) { | ||
return field.formatter && ((typeof (field.formatter) === 'function') || (typeof (field.formatter) === 'string')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so you are allowing each field to have it's own formatter function (via the fields objects)
I originally thought you were going to have a global field Formatter function (a prop), which would return a format based on the key, item, and field passed to it.
Your solution allows for more flexibility. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and what is the option for typeof (field.formatter) === 'string'
handle?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I check if supplied value is string or function, to determine if remote formatter is correctly provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If provided formatter func declared in global scope and provided as Function
to formatter prop, then we directly call it.
return field.formatter(item[key]); | ||
|
||
if (field.formatter && (typeof (this.$parent[field.formatter]) === 'function')) | ||
return this.$parent[field.formatter](item[key]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what do these two lines handle?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, just making a guess... does this allow calling a method defined in the parent (when field.formatter is a string)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, if formatter supplied as String
, then we search for it in parent's methods, this solution have a potential bug, when we have table component in a some wrapper component, like b-table, but i don't find any more flexible solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could a user do this:
{
data: {
fields: {
foo: { sortble: true, formatter this.fooFormatter },
bar: { sortble: true, formatter this.barFormatter }
}
},
methods: {
fooFormatter(item, key, field) {
return item.a + item.b;
},
barFormatter(item, key, field) {
return item.a - item.b;
},
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the field formatter calling signature should include item
, key
, field
as args.
That way a single formatter function could be used to handle multiple columns, based on the key
. That way a user could provide either unique formatters, or a single generic formatter method depending on their preference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then we must pass these vars from child component.
and this declaration won't work, as parent functions aren't in children's scope. in case we want to use parent's methods, we must supply it as String, Function declaration only for global scoped which will be available to child
data: {
fields: {
foo: { sortble: true, formatter this.fooFormatter },
bar: { sortble: true, formatter this.barFormatter }
}
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the main goal of this is to provide a way to modify single field by simple func... don;t like idea of all-in-one heavy variant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But making the extra args available would make rendering for "virtual" columns (columns that don't have a value in items) be possible (i.e. a virtual column that is comprised of data from different fields within the item record)
Maybe make the formatter function use this signature:
formatter(value, item, key)
And the user can then ignore the extra item
, key
args if not needed.
Just thinking about adding a bit of flexibility to the formatter functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we will have two ways of handling table data: slots and formatter callback.
Callbacks will give more reusability, as we can move formatter functions out to standalne module and use it as mixin at components and even as global mixin to app's Vueinstance,
while slots will allow to insert another components into table cell and also format field value and create virtual columns in case we don't need to reuse it at whole app.
The real use case:
methods in Vue instance:
|
How would one use a formatter function for a virtual column? i.e. a column that |
ie.: data: (
fields: {
name: { sortable: true },
birthdate: { sortable: true, formatter: 'formatDate')
age: { formatter: 'calcAge' }
},
items: {
{ name: 'bob', birthdate: { year: 1967, month: 12, day: 7 } },
{ name: 'jane', birthdate: { year: 1992, month: 4, day: 20 } )
}
},
methods: {
formatDate(value) {
return `${value.year}/${value.month}/${value.day}`;
}
calcAge(value, item) { // value is null/undefined in this case
const d = item.birthdate;
const bday = new Date(d.year, d.month-1, d.day);
const diff = Date.now() - bday.getTime();
return Math.abs((new Date(diff)).getUTCFullYear() - 1970);
}
} Without access to the |
docs/components/table/README.md
Outdated
@@ -147,6 +148,7 @@ Supported field properties: | |||
| `thClass` | String or Array | Class name (or array of class names) to add to header/footer `<th>` cell | |||
| `tdClass` | String or Array | Class name (or array of class names) to add to data `<td>` cells in the column | |||
| `thStyle` | Object | JavaScript object representing CSS styles you would like to apply to the table field `<th>` | |||
| `formatter` | String or Function | A formatter callback function, can be used instead of slots |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be used instead of slots, but only for fields that have a real value
. Currently one cannot use this for creating data for a virtual column.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, and by design these featues, slots and formatter are mutually exclusive. Ie. if we define formatter at items array, then we cant use slots, and slots are used only if formatter is not defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that should be added to the documentation ;-)
Hmmmm.... |
Workaround: dc8d238 😆 New SSR optimizations for |
TODO: modify docs, examples.