Selecting rows from DataTables fails unless search UI is visible - datatables

I want to hide the search user interface, but when I do so, the api based row selection fails. Works fine if searching is enabled. When I read the docs, I thought I was disabling the UI, but it is obviously the search feature instead.
I actually have DataTables for schools and school districts, and want to only display the schools from a specific district when used clicks on district table to select a district. User can also select a school from the school table
// DataTable Initialization for school table
dTable = $tblSchools.DataTable({
select: {
style: 'single',
items: 'row',
},
paging: false,
searching: true, // selecting rows only works when enabled - makes sense
info: false,
destroy: true
});
// snippet from event handler on 'select' event of district table
var dTable = $tblSchools.DataTable();
var district = '123'; // real code messier, console.log(district) shows it works
// district number is always column 1
// the next line is what fails (silently, no exception)
dTable.column(1).search(district, false, false).draw();
Environment, jQuery 1.9.1, DataTables 1.10.12, select plugin 1.2.0
Is there some setting I have overlooked to hide the search UI without disabling the search function entirely.
If I have to do this a different way, would appreciated being pointed to a simple equivalent way that allows me to hide the DataTables search UI, but show schools based on district.

Is there some setting I have overlooked to hide the search UI without
disabling the search function entirely
Yes. Remove the f flag from the dom defaults :
dTable = $tblSchools.DataTable({
dom: 'lrtip',
...
})
Now the filter input / "search UI" is removed but you can still perform searches by code.

Related

KeystoneJS `filter` vs `Item` list access control

I am trying to understand more in depth the difference between filter and item access control.
Basically I understand that Item access control is, sort of, higher order check and will run before the GraphQL filter.
My question is, if I am doing a filter on a specific field while updating, for instance a groupID or something like this, do I need to do the same check in Item Access Control?
This will cause an extra database query that will be part of the filter.
Any thoughts on that?
The TL;DR answer...
if I am doing a filter on a specific field [..] do I need to do the same check in Item Access Control?
No, you only need to apply the restriction in one place or the other.
Generally speaking, if you can describe the restriction using filter access control (ie. as a graphQL-style filter, with the args provided) then that's the best place to do it. But, if your access control needs to behave differently based on values in the current item or the specific changes being made, item access control may be required.
Background
Access control in Keystone can be a little hard to get your head around but it's actually very powerful and the design has good reasons behind it. Let me attempt to clarify:
Filter access control is applied by adding conditions to the queries run against the database.
Imagine a content system with lists for users and posts. Users can author a post but some posts are also editable by everyone. The Post list config might have something like this:
// ..
access: {
filter: {
update: () => ({ isEditable: { equals: true } }),
}
},
// ..
What that's effectively doing is adding a condition to all update queries run for this list. So if you update a post like this:
mutation {
updatePost(where: { id: "123"}, data: { title: "Best Pizza" }) {
id name
}
}
The SQL that runs might look like this:
update "Post"
set title = 'Best Pizza'
where id = 234 and "isEditable" = true;
Note the isEditable condition that's automatically added by the update filter. This is pretty powerful in some ways but also has its limits – filter access control functions can only return GraphQL-style filters which prevents them from operating on things like virtual fields, which can't be filtered on (as they don't exist in the database). They also can't apply different filters depending on the item's current values or the specific updates being performed.
Filter access control functions can access the current session, so can do things like this:
filter: {
// If the current user is an admin don't apply the usual filter for editability
update: (session) => {
return session.isAdmin ? {} : { isEditable: { equals: true } };
},
}
But you couldn't do something like this, referencing the current item data:
filter: {
// ⚠️ this is broken; filter access control functions don't receive the current item ⚠️
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId ? {} : { isEditable: { equals: true } };
},
}
The benefit of filter access control is it doesn't force Keystone to read an item before an operation occurs; the filter is effectively added to the operation itself. This can makes them more efficient for the DB but does limit them somewhat. Note that things like hooks may also cause an item to be read before an operation is performed so this performance difference isn't always evident.
Item access control is applied in the application layer, by evaluating the JS function supplied against the existing item and/or the new data supplied.
This makes them a lot more powerful in some respects. You can, for example, implement the previous use case, where authors are allowed to update their own posts, like this:
item: {
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId || item.isEditable;
},
}
Or add further restrictions based on the specific updates being made, by referencing the inputData argument.
So item access control is arguably more powerful but they can have significant performance implications – not so much for mutations which are likely to be performed in small quantities, but definitely for read operations. In fact, Keystone won't let you define item access control for read operations. If you stop and think about this, you might see why – doing so would require reading all items in the list out of the DB and running the access control function against each one, every time a list was read. As such, the items accessible can only be restricted using filter access control.
Tip: If you think you need item access control for reads, consider putting the relevant business logic in a resolveInput hook that flattens stores the relevant values as fields, then referencing those fields using filter access control.
Hope that helps

Error trying to reorder items within another list in Keystone 6

I'm using KeystoneJS v6. I'm trying to enable functionality which allow me to reorder the placement of images when used in another list. Currently i'm setting up the image list below, however I'm unable to set the defaultIsOrderable to true due to the error pasted.
KeystoneJS list:
Image: list({
fields: {
title: text({
validation: { isRequired: true },
isIndexed: 'unique',
isFilterable: true,
isOrderable: true,
}),
images: cloudinaryImage({
cloudinary: {
cloudName: process.env.CLOUDINARY_CLOUD_NAME,
apiKey: process.env.CLOUDINARY_API_KEY,
apiSecret: process.env.CLOUDINARY_API_SECRET,
folder: process.env.CLOUDINARY_API_FOLDER,
},
}),
},
defaultIsOrderable: true
}),
Error message:
The expected type comes from property 'defaultIsOrderable' which is declared here on type 'ListConfig<BaseListTypeInfo, BaseFields<BaseListTypeInfo>>'
Peeking at the definition of the field shows
defaultIsOrderable?: false | ((args: FilterOrderArgs<ListTypeInfo>) => MaybePromise<boolean>);
Looking at the schema API docs, the defaultIsOrderable lets you set:
[...] the default value to use for isOrderable for fields on this list.
You're trying to set this to true but, according to the relevant section of the field docs, the isOrderable field option already defaults to true.
I believe this is why the defaultIsOrderable type doesn't allow you to supply the true literal – doing so would be redundant.
So that explains the specific error your getting but I think you also may have misunderstood the purpose of the orderBy option.
The OrderBy Option
The field docs mention the two effects the field OrderBy option has:
If true (default), the GraphQL API and Admin UI will support ordering by this field.
Take, for example, your Image list above.
As the title field is "orderable", it is included in the list's orderBy GraphQL type (ImageOrderByInput).
When querying the list, you can order the results by the values in this field, like this:
query {
images (orderBy: [{ title: desc }]) {
id
title
images { publicUrl }
}
}
The GraphQL API docs have some details on this.
You can also use the field to order items when listing them in the Admin UI, either by clicking the column heading or selecting the field from the "sort" dropdown:
Note though, these features order items at runtime, by the values stored in orderable fields.
They don't allow an admin to "re-order" items in the Admin UI (unless you did so by changing the image titles in this case).
Specifying an Order
If you want to set the order of items within a list you'd need to store separate values in, for example, a displayOrder field like this:
Image: list({
fields: {
title: text({
validation: { isRequired: true },
isIndexed: 'unique',
isFilterable: true,
}),
displayOrder: integer(),
// ...
},
}),
Unfortunately Keystone doesn't yet give you a great way to manage this the Admin UI (ie. you can't "drag and drop" in the list view or anything like that). You need to edit each item individually to set the displayOrder values.
Ordering Within a Relationship
I notice your question says you're trying to "reorder the placement of images when used in another list" (emphasis mine).
In this case you're talking about relationships, which changes the problem somewhat. Some approaches are..
If the relationship is one-to-many, you can use the displayOrder: integer() solution shown above but the UX is worse again. You're still setting the order values against each item but not in the context of the relationship. However, querying based on these order values and setting them via the GraphQL API should be fairly straight forward.
If the relationship is many-to-many, it's similar but you can't store the "displayOrder" value in the Image list as any one image may be linked to multiple other items. You need to store the order info "with" the relationship itself. It's not trivial but my recent answer on storing additional values on a many-to-many relationship may point you in the right direction.
A third option is to not use the relationship field at all but to link items using the inline relationships functionality of the document field. This is a bit different to work with - easier to manage from the Admin UI but less powerful in GraphQL as you can't traverse the relationship as easily. However it does give you a way to manage a small, ordered set of related items in a many-to-many relationship.
You can save an ordered set of ids to a json field. This is similar to using a document field but a more manual.
Hopefully that clears up what's possible with the current "orderBy" functionality and relationship options. Which of these solutions is most appropriate depends heavily on the specifics of your project and use case.
Note too, there are plans to extend Keystone's functionality for sorting and reordering lists from both the DX and UX perspectives.
See "Sortable lists" on the Keystone roadmap.

Can I retrieve column/search specific data from a jQuery DataTable?

I am currently working on a page for a web app that displays member data in a jQuery DataTable. I am building a custom plugin for the DataTable that allows for a wide range of filtering per table column.
My current task is to be able to retrieve filtered data from the DataTable, although not updating that data on the table. I know already it is possible to retrieve all filtered data by doing:
var data = $table.dataTable().$('tr', { filter: 'applied' });
This gets data for any text in the search box and any filters applied to columns. I am also aware this call gets the jQuery selectors of cells. That's what I need. But I need more...
My questions are:
Can I get data by only applying what's in the search box, without any other current filters applied to columns? I tried:
var data = $table.dataTable().$('tr', { search: 'applied' });
But that returns the same as { filter : 'applied' }.
Can I target specific columns for getting data, such as:
var data = $table.dataTable().$('tr', { filter: 'applied', columns: [1, 2, 5] });
Can I target specific columns AND what's in the search box?
My plugin needs to able to keep track of data with various combinations of column/search filters applied.
For DataTables 1.9
There is a fnFilter() API method but it applies filtering to the table which is not what you want.
Alternatively you may want to use fnGetData() to get the data for the whole table and filter it yourself.
For DataTables 1.10
There is a search() API method but it applies filtering to the table when used with draw() which is not what you want.
Also there is filter() API method. It is not exactly what you're looking for but very close, however you would need to perform the searching yourself.
You can retrieve the content of the search box as follows:
var searchVal = $('.dataTables_filter input', $table).val();
Then you would need to do the searching yourself, shown below is a simplistic approach which may not match how DataTables perform the search internally.
To search first and second column only, specify [0,1] to columns() method. If no parameters are specified, all columns will be searched instead.
var filteredData = $table.DataTable()
.columns([0, 1])
.data()
.eq( 0 )
.filter( function ( value, index ) {
returrn (value.search(new RegExp(searchVal, "i")) !== -1)
? true : false;
} );
According to the manual, variable filteredData would contain a new API instance with the values from the result set which passed the test in the callback.
To retrieve data for all or selected columns, use columns().data().

canEdit not working in dgrid while loading by default

I have a tree grid with following ui requirements on edit.
Cost column for certain rows are editable.
Editable rows should be available for edit by default always and not
based on any event.
Each row has min max range. As and when the user enters a value that
needs to be validated.
Here is the column structure I have defined for dgrid.
var columns = [
tree({label: "Name", field:"name" }),
{ label : "Description", field:"description" },
editor({label: "Cost", field: "cost", canEdit : function(rowItem){ return rowItem.isEditable;}}, dijit.form.NumberTextBox),
{label:"Min - Max Range", field:"minRange", get:getMinMax, id:'minMax'}
];
Though the tree and edit is working fine, I have few issues to be resolved.
When editOn is not provided for editor, the column is made editable
by default. However, canEdit is getting invoked only when we provide
spl event in editOn parameter. Is there a way to get canEdit invoked
even during default load.
I need to set a range constraint for NumberTextBox dynamically for
each row. Is there an easy way to set the constraint based on row
value.
Thank you very much for your help
As for canEdit invoked when editOn is false check:
https://github.com/SitePen/dgrid/issues/623
As for dynamic set value based on row values you can try :
on the widget level Extend the Widget : in
startup after inherited(arguments)
var _row=this.grid.grid.row(this.domNode.parentNode);
this.query={myParam:_row.data.maxRange};
Tsemach.

query single dropdown menu with multiple dropdown menus via rails

I'm having some difficulty figuring out the following problem. I apologize if this writeup is slightly confusing, but I'll try to explain it as best as I can.
I'm relatively new to rails, and I'm trying to implement a query on my site. One portion of my site allows users to select a State from a dropdown menu when filling out a form.
Another portion of the site, (the portion that implements the query) allows users to query for a form based on the State associated with it. The page is essentially only 4 dropdown boxes, and a submit button. This query should allow users to find forms from multiple States by selecting the desired States from the available dropdown menus. Meaning a user can query for forms from 4 different states, and it would return all forms where the state on the form matches the state from one of the four dropdown menus.
So, a user can select "Georgia" from state_1 (name of dropdown), "Texas" from state_2, "California" from state_3, and "Ohio" from state_4. Once a user submits their search, I would like to return all the forms available that match the user's dropdown selections.
I'm running into some issues at present. here is the code I currently have:
def self.search(params)
form = self.scoped
form = form.where(:state => params[:state_1]) if params[:state_1].present?
form = form.where(:state => params[:state_2]) if params[:state_2].present?
form = form.where(:state => params[:state_3]) if params[:state_3].present?
form = form.where(:state => params[:state_4]) if params[:state_4].present?
form
end
The issue i am running into is the following: If I run a query with only a selection on state_1, then it returns all of the fields associated with that state (which is excellent); however, if I try to run a query with selections on multiple dropdown menus (for example, "Georgia" on state_1 and "Texas" on state_2) no forms are returned.
Again, I sincerely apologize if this is confusing, and I'm grateful for your time. I would appreciate any help and I'll do my best to respond to any questions as prompt as possible. Thank you so much!
If you have both :state_1 and :state_2 set, then you'll be doing this:
form = form.where(:state => params[:state_1]).where(state => params[:state_2])
And that's saying
state = 'TX' AND state = 'GA'
Each time you call .where, you're appending another condition with a conjunction (i.e. AND). I think you're after this:
states = [:state_1, :state_2, :state_3, :state_4].map { |k| params[k] }.reject(&:blank?)
form = form.where(:state => states) if(!states.empty?)
That will turn into SQL sort of like this:
state IN ('TX', 'GA')
and that seems to be what you're after. The map will extract the param values with for the specified state keys and then the reject will filter out the blank ones (i.e. false, nil, empty, or all whitespace).