How to save a table from Datatables to a database in Django? - django

I'm using Datatables to display tables in Django on the server side. After searching for a phrase, I have a button ready to save the current table after filtering to Excel. I would like the second button to create a new table in the database and save the same table to it as for Excel.
I don't know how I can send AJAX data to Django and read it in views in such a way that I can query the database.
javascript:
$(document).ready(function () {
function newexportaction(e, dt, button, config) {
var self = this;
var oldStart = dt.settings()[0]._iDisplayStart;
dt.one('preXhr', function (e, s, data) {
data.start = 0;
data.length = 2147483647;
dt.one('preDraw', function (e, settings) {
// Call the original action function
if (button[0].className.indexOf('buttons-excel') >= 0) {
$.fn.dataTable.ext.buttons.excelHtml5.available(dt, config) ?
$.fn.dataTable.ext.buttons.excelHtml5.action.call(self, e, dt, button, config) :
$.fn.dataTable.ext.buttons.excelFlash.action.call(self, e, dt, button, config);
}
dt.one('preXhr', function (e, s, data) {
settings._iDisplayStart = oldStart;
data.start = oldStart;
});
});
});
dt.ajax.reload();
}
$("#tb").DataTable({
"lengthChange": true,
"paging": true,
"searching": true,
"oLanguage": {
"sSearch": "Szukaj:",
},
"language": {
"processing": "Przetwarzanie",
"lengthMenu": "Pokaż _MENU_ elementów",
"info": "Wyświetlono od _START_ do _END_ z _TOTAL_ elementów",
"zeroRecords": "Nie znaleziono pasujących elementów",
"paginate": {
"first": "Pierwsza",
"last": "Ostatnia",
"next": "Kolejna",
"previous": "Poprzednia"
},
"emptyTable": "Brak danych do wyświetlenia",
},
"processing": true,
"serverSide": true,
buttons: [
{
extend: 'excel',
titleAttr: 'Excel',
title: '',
action: newexportaction
},
],
dom: 'B<"clear">lfrtip',
// "destroy": true,
"pageLength": 15,
"ordering": false,
"ajax": {
"url": "paging2/",
"type": "get"
},
});
});
views.py:
def paging2(request):
draw = int (request.GET.get ('draw')) # record the number of operations
start = int (request.GET.get ('start')) # start position
length = int (request.GET.get ('length')) # length of each page
search_key = request.GET.get ('search[value]') # search keyword
order_column = request.GET.get ('order [0] [column]') # sort field index
order_column = request.GET.get ('order [0] [dir]') #Ordering rule: ase / desc
# sql query all data, then do paging, the speed is slow
# if search_key:
# result = query(search_key)
# else:
# result = select_all()
# data = result[start:start+length]
# count = len(result)
# sql for paging, fast
if search_key:
sql_search = "SELECT NAZWA,TEL,NIP,Adres,WWW, EMAIL, Branza, NAZWA_SCRAPERA FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL AND Branza like '%%%s%%'" % search_key
sql_search_len = "SELECT COUNT(*) FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL AND Branza like '%%%s%%'"% search_key
result, count = query(sql_search, sql_search_len)
data = result[start:start + length]
else:
sql = """
SELECT NAZWA,TEL,NIP,Adres,WWW, EMAIL, Branza, NAZWA_SCRAPERA FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL
LIMIT %s OFFSET %s
"""
data = select_by_page(length, start, sql)
# data = select_by_page(start, start + length)
sql_len = "SELECT COUNT(*) FROM gus_all t1 WHERE NOT EXISTS (SELECT * FROM matka_new t2 WHERE t2.NIP = t1.NIP) AND LENGTH(TEL) = 9 AND `STATUS` = 'AKTYWNY' AND NAZWA_SCRAPERA is NOT NULL"
count = all_count(sql_len)
dic = {
'draw': draw,
'recordsTotal': count,
'recordsFiltered': count,
'data': data,
}
return HttpResponse(json.dumps(dic), content_type='application/json')

Related

shiny app excelR can't control table height

I'm using ExcelR and Shiny. I can't seem to make the table bigger than say, 13 rows.
Here is example code:
shinyApp(
ui = navbarPage("title", selected = "main",
position = "fixed-top",
tags$style(type="text/css", "body {padding-top: 70px;}"),
tabPanel("main", id = "main",
fluidPage(
excelOutput("table", width = "100%", height = "100%")
)
)
),
server = function(input, output, session) {
output$table <-renderExcel(
excelTable(
data = iris,
autoColTypes = FALSE,
pagination = 5,
#autoFill = TRUE,
fullscreen = FALSE,
lazyLoading = TRUE,
search = TRUE
)
)
}
)
pagination seems to have no effect, and if I switch fullscreen on, the rest of the rows are rendered, but I can't see the searchbar, and the padding between the table and the navbar disappears.
I'm running R 3.6.0
I tried using the same attribute you have, and used it in the standard function and it worked, I also removed the pagination setting
so this worked for me...
server = function(input, output, session) {
output$table <-renderExcel(
excelTable(
data = iris,
autoColTypes = FALSE,
fullscreen = FALSE,
tableHeight = 500,
lazyLoading = TRUE,
search = TRUE
)
)
}
I'm going to leave this question open for a few days to see if anyone has a better answer.
For now, I managed to hack a solution by changing the excelTable function.
I added the line paramList$tableHeight <- "500px" to the function, and renamed the function excelTable2.
I found this param on the website for jExcel: jExcel Parameters
I guess I will probably add the height as an input parameter to excelTable2.
excelTable2 = function (data = NULL, columns = NULL, colHeaders = NULL, rowHeight = NULL,
nestedHeaders = NULL, defaultColWidth = NULL, minDimensions = NULL,
columnSorting = TRUE, columnDrag = FALSE, columnResize = TRUE,
rowResize = FALSE, rowDrag = TRUE, editable = TRUE, allowInsertRow = TRUE,
allowInsertColumn = TRUE, allowDeleteRow = TRUE, allowDeleteColumn = TRUE,
allowRenameColumn = TRUE, allowComments = FALSE, wordWrap = FALSE,
selectionCopy = TRUE, mergeCells = NULL, search = FALSE,
pagination = NULL, fullscreen = FALSE, lazyLoading = FALSE,
loadingSpin = FALSE, style = NULL, autoColTypes = TRUE, showToolbar = FALSE,
dateFormat = "DD/MM/YYYY", digits = 4, autoWidth = TRUE,
autoFill = FALSE, getSelectedData = FALSE, ...)
{
paramList <- list()
if (!is.null(data)) {
if (is.data.frame(data) || is.matrix(data)) {
paramList$data <- jsonlite::toJSON(data, dataframe = "values",
na = "null", digits = digits)
}
else {
stop("'data' must be either a matrix or a data frame, cannot be ",
class(data))
}
}
if (is.null(columns) && is.null(colHeaders)) {
if (!is.null(data)) {
warning("Since both column title and colHeaders are not specified 'data' column name will be used as column headers")
paramList$colHeaders = colnames(data)
}
}
else if (is.null(columns) && !is.null(colHeaders)) {
if (!is.vector(colHeaders)) {
stop("'colHeaders' must be a vector, cannot be ",
class(colHeaders))
}
if (!is.null(data)) {
if (ncol(data) != length(colHeaders)) {
stop("length of 'colHeader' should be equal the number of columns in the 'data', 'data' has ",
ncol(data), "but the length of 'colHeader' is ",
length(colHeaders))
}
}
paramList$colHeaders <- jsonlite::toJSON(colHeaders)
}
else if (!is.null(columns)) {
if (!is.data.frame(columns)) {
stop("'columns' must be a dataframe, cannot be ",
class(columns))
}
if (!is.null(data)) {
if (nrow(columns) != ncol(data)) {
stop("number of rows in 'columns' should be equal to number of columns in 'data', expected number of rows in 'columns' to be ",
ncol(data), " but got ", nrow(columns))
}
}
if (!"title" %in% colnames(columns)) {
if (is.null(colHeaders)) {
if (!is.null(data)) {
warning("Since both column title and colHeaders are not specified 'data' column name will be used as column headers")
paramList$colHeaders = jsonlite::toJSON(colnames(data))
}
}
else {
paramList$colHeaders = jsonlite::toJSON(colHeaders)
}
}
paramList$columns <- jsonlite::toJSON(columns)
}
if (autoColTypes && !is.null(data)) {
if (is.null(columns)) {
message("Since 'type' attribute is not specified and autoColTypes is true, detecting type from 'data'")
colTypes <- get_col_types(data)
columns <- data.frame(type = colTypes)
columns <- add_source_for_dropdown_type(data, columns)
paramList$columns <- jsonlite::toJSON(columns)
}
else {
if (!"type" %in% colnames(columns) && autoColTypes) {
message("Since 'type' attribute is not specified and autoColTypes is true, detecting type from 'data'")
colTypes <- get_col_types(data)
columns$type <- colTypes
columns <- add_source_for_dropdown_type(data,
columns)
paramList$columns <- jsonlite::toJSON(columns)
}
}
}
if (!is.null(rowHeight)) {
if (!is.data.frame(rowHeight) && !is.matrix(rowHeight)) {
stop("'rowHeight' must either be a matrix or a dataframe, cannot be ",
class(rowHeight))
}
if (ncol(rowHeight) != 2) {
stop("'rowHeight' must either be a matrix or a dataframe with two columns, but got ",
ncol(rowHeight), " column(s)")
}
paramList$rowHeight <- jsonlite::toJSON(rowHeight, dataframe = "values")
}
if (!is.null(nestedHeaders)) {
if (!is.list(nestedHeaders)) {
stop("'nestedHeaders' must be a list of dataframe(s), cannot be ",
class(nestedHeaders))
}
headerAttributes <- c("title", "colspan")
for (nestedHeader in nestedHeaders) {
if (!is.data.frame(nestedHeader)) {
stop("'nestedHeaders' must be a list of dataframe(s), but got list of ",
class(nestedHeader), "(s)")
}
if (ncol(nestedHeader) < 2 || nrow(nestedHeader) <
1) {
stop("the dataframe(s) in 'nestedHeaders must contain at least two columns and one row, 'title' and 'colspan', but got only ",
ncol(nestedHeader), " column and ", nrow(nestedHeader),
" row")
}
if (!"title" %in% colnames(nestedHeader)) {
stop("one of the column in the dataframe in list of 'nestedHeaders' should have 'title' as header which will be used as title of the nested header")
}
if (!"colspan" %in% colnames(nestedHeader)) {
stop("one of the column in the dataframe in list of 'nestedHeaders' should have 'colspan' as header which will be used to determine the number of column it needs to span")
}
if (!all(colnames(nestedHeader) %in% headerAttributes)) {
warning("unknown headers(s) ", colnames(nestedHeader)[!colnames(nestedHeader) %in%
headerAttributes], " for 'nestedHeader' found, ignoring column with those header(s)")
}
}
paramList$nestedHeaders <- jsonlite::toJSON(nestedHeaders,
dataframe = "rows")
}
if (!is.null(defaultColWidth)) {
if (!is.numeric(defaultColWidth) || length(defaultColWidth) >
1) {
stop("'defaultColWidth' must be a numeric value of length 1 but got ",
class(defaultColWidth), " of length ",
length(defaultColWidth))
}
paramList$defaultColWidth <- defaultColWidth
}
if (!is.null(minDimensions)) {
if (!is.vector(minDimensions)) {
stop("'minDimensions' must be vector but got ",
class(minDimensions))
}
if (length(minDimensions) != 2) {
stop("'minDimensions' must be a vector of length of 2 but got length of ",
length(minDimensions))
}
paramList$minDimensions <- minDimensions
}
for (arg in c("columnSorting", "columnDrag",
"columnResize", "rowResize", "rowDrag",
"editable", "allowInsertRow", "allowInsertColumn",
"allowDeleteRow", "allowDeleteColumn", "allowRenameColumn",
"allowComments", "wordWrap", "selectionCopy",
"search", "fullscreen", "lazyLoading",
"loadingSpin", "showToolbar", "autoWidth",
"autoFill", "getSelectedData")) {
argvalue <- get(arg)
if (!is.null(argvalue)) {
if (is.logical(argvalue)) {
paramList[[arg]] <- argvalue
}
else {
warning("Argument ", arg, " should be either TRUE or FALSE. Ignoring ",
arg, ".", call. = FALSE)
paramList[[arg]] <- NULL
}
}
}
if (!is.null(mergeCells)) {
if (!is.list(mergeCells)) {
stop("expected 'mergeCells' to be a list but got ",
class(mergeCells))
}
for (mergeCell in mergeCells) {
if (!is.vector(mergeCell)) {
stop("expected each parameter in 'mergeCells' list to be a vector but got ",
class(mergeCell))
}
if (length(mergeCell) != 2) {
stop("expected each parameter in 'mergeCells' list to be a vector of length 2 but got vector of length ",
length(mergeCells))
}
}
paramList$mergeCells <- mergeCells
}
if (!is.null(pagination)) {
if (!is.numeric(pagination) || length(pagination) > 1) {
stop("'pagination' must be an integer of length 1 but got ",
class(pagination), " of length ", length(pagination))
}
paramList$pagination <- pagination
}
if (!is.null(style)) {
if (!is.list(style)) {
stop("'style' should be a list but got ", class(style))
}
paramList$style <- style
}
if (!is.null(dateFormat)) {
paramList$dateFormat <- dateFormat
}
paramList$tableHeight <- "500px"
paramList <- append(paramList, list(...))
htmlwidgets::createWidget(name = "jexcel", x = paramList,
width = if (fullscreen)
"100%"
else 0, height = if (fullscreen)
"100%"
else 0, package = "excelR",
)
}
Working app after defining above excelTable2
shinyApp(
ui = navbarPage("title", selected = "main",
position = "fixed-top",
tags$style(type="text/css", "body {padding-top: 70px;}"),
tabPanel("main", id = "main",
fluidPage(
excelOutput("table", width = "100%", height = "100%")
#htmlOutput("table", width = "100%", height = "500px")
)
)
),
server = function(input, output, session) {
output$table <-renderExcel(
excelTable2(
data = iris,
autoColTypes = FALSE,
autoFill = TRUE,
fullscreen = FALSE,
lazyLoading = TRUE,
search = TRUE
)
)
}
)

DRF formatting XLSX content

I am trying to set a different color on every second row in XLSX file. From the documentation I see that I can pass some conditions using body property or get_body() method, but this only allows me to set somewhat "static" conditions. Here is the ViewSet config responsible for rendering the XLSX file:
class MyViewSet(XLSXFileMixin, ModelViewSet):
def get_renderers(self) -> List[BaseRenderer]:
if self.action == "export":
return [XLSXRenderer()]
else:
return super().get_renderers()
#action(methods=["GET"], detail=False)
def export(self, request: Request) -> Response:
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(serializer.data)
# Properties for XLSX
column_header = {
"titles": [
"Hostname", "Operating System", "OS name", "OS family", "OS version", "Domain", "Serial number",
"Available patches",
],
"tab_title": "Endpoints",
"style": {
"font": {
"size": 14,
"color": "FFFFFF",
},
"fill": {
"start_color": "3F803F",
"fill_type": "solid",
}
}
}
body = {
"style": {
"font": {
"size": 12,
"color": "FFFFFF"
},
"fill": {
"fill_type": "solid",
"start_color": "2B2B2B"
},
}
}
OK. I got the answer after some digging through the source code. The render method of XLSXRenderer has this piece of code:
for row in results:
column_count = 0
row_count += 1
flatten_row = self._flatten(row)
for column_name, value in flatten_row.items():
if column_name == "row_color":
continue
column_count += 1
cell = ws.cell(
row=row_count, column=column_count, value=value,
)
cell.style = body_style
ws.row_dimensions[row_count].height = body.get("height", 40)
if "row_color" in row:
last_letter = get_column_letter(column_count)
cell_range = ws[
"A{}".format(row_count): "{}{}".format(last_letter, row_count)
]
fill = PatternFill(fill_type="solid", start_color=row["row_color"])
for r in cell_range:
for c in r:
c.fill = fill
So when I added a field row_color in my serializer as SerializerMethodField I was able to define a function that colors rows:
def get_row_color(self, obj: Endpoint) -> str:
"""
This method returns color value for row in XLSX sheet.
(*self.instance,) extends queryset to a list (it must be a queryset, not a single Endpoint).
.index(obj) gets index of currently serialized object in that list.
As the last step one out of two values from the list is chosen using modulo 2 operation on the index.
"""
return ["353535", "2B2B2B"][(*self.instance,).index(obj) % 2]

AWS RDS Data API executeStatement not return column names

I'm playing with the New Data API for Amazon Aurora Serverless
Is it possible to get the table column names in the response?
If for example I run the following query in a user table with the columns id, first_name, last_name, email, phone:
const sqlStatement = `
SELECT *
FROM user
WHERE id = :id
`;
const params = {
secretArn: <mySecretArn>,
resourceArn: <myResourceArn>,
database: <myDatabase>,
sql: sqlStatement,
parameters: [
{
name: "id",
value: {
"stringValue": 1
}
}
]
};
let res = await this.RDS.executeStatement(params)
console.log(res);
I'm getting a response like this one, So I need to guess which column corresponds with each value:
{
"numberOfRecordsUpdated": 0,
"records": [
[
{
"longValue": 1
},
{
"stringValue": "Nicolas"
},
{
"stringValue": "Perez"
},
{
"stringValue": "example#example.com"
},
{
"isNull": true
}
]
]
}
I would like to have a response like this one:
{
id: 1,
first_name: "Nicolas",
last_name: "Perez",
email: "example#example.com",
phone: null
}
update1
I have found an npm module that wrap Aurora Serverless Data API and simplify the development
We decided to take the current approach because we were trying to cut down on the response size and including column information with each record was redundant.
You can explicitly choose to include column metadata in the result. See the parameter: "includeResultMetadata".
https://docs.aws.amazon.com/rdsdataservice/latest/APIReference/API_ExecuteStatement.html#API_ExecuteStatement_RequestSyntax
Agree with the consensus here that there should be an out of the box way to do this from the data service API. Because there is not, here's a JavaScript function that will parse the response.
const parseDataServiceResponse = res => {
let columns = res.columnMetadata.map(c => c.name);
let data = res.records.map(r => {
let obj = {};
r.map((v, i) => {
obj[columns[i]] = Object.values(v)[0]
});
return obj
})
return data
}
I understand the pain but it looks like this is reasonable based on the fact that select statement can join multiple tables and duplicated column names may exist.
Similar to the answer above from #C.Slack but I used a combination of map and reduce to parse response from Aurora Postgres.
// declarative column names in array
const columns = ['a.id', 'u.id', 'u.username', 'g.id', 'g.name'];
// execute sql statement
const params = {
database: AWS_PROVIDER_STAGE,
resourceArn: AWS_DATABASE_CLUSTER,
secretArn: AWS_SECRET_STORE_ARN,
// includeResultMetadata: true,
sql: `
SELECT ${columns.join()} FROM accounts a
FULL OUTER JOIN users u ON u.id = a.user_id
FULL OUTER JOIN groups g ON g.id = a.group_id
WHERE u.username=:username;
`,
parameters: [
{
name: 'username',
value: {
stringValue: 'rick.cha',
},
},
],
};
const rds = new AWS.RDSDataService();
const response = await rds.executeStatement(params).promise();
// parse response into json array
const data = response.records.map((record) => {
return record.reduce((prev, val, index) => {
return { ...prev, [columns[index]]: Object.values(val)[0] };
}, {});
});
Hope this code snippet helps someone.
And here is the response
[
{
'a.id': '8bfc547c-3c42-4203-aa2a-d0ee35996e60',
'u.id': '01129aaf-736a-4e86-93a9-0ab3e08b3d11',
'u.username': 'rick.cha',
'g.id': 'ff6ebd78-a1cf-452c-91e0-ed5d0aaaa624',
'g.name': 'valentree',
},
{
'a.id': '983f2919-1b52-4544-9f58-c3de61925647',
'u.id': '01129aaf-736a-4e86-93a9-0ab3e08b3d11',
'u.username': 'rick.cha',
'g.id': '2f1858b4-1468-447f-ba94-330de76de5d1',
'g.name': 'ensightful',
},
]
Similar to the other answers, but if you are using Python/Boto3:
def parse_data_service_response(res):
columns = [column['name'] for column in res['columnMetadata']]
parsed_records = []
for record in res['records']:
parsed_record = {}
for i, cell in enumerate(record):
key = columns[i]
value = list(cell.values())[0]
parsed_record[key] = value
parsed_records.append(parsed_record)
return parsed_records
I've added to the great answer already provided by C. Slack to deal with AWS handling empty nullable character fields by giving the response { "isNull": true } in the JSON.
Here's my function to handle this by returning an empty string value - this is what I would expect anyway.
const parseRDSdata = (input) => {
let columns = input.columnMetadata.map(c => { return { name: c.name, typeName: c.typeName}; });
let parsedData = input.records.map(row => {
let response = {};
row.map((v, i) => {
//test the typeName in the column metadata, and also the keyName in the values - we need to cater for a return value of { "isNull": true } - pflangan
if ((columns[i].typeName == 'VARCHAR' || columns[i].typeName == 'CHAR') && Object.keys(v)[0] == 'isNull' && Object.values(v)[0] == true)
response[columns[i].name] = '';
else
response[columns[i].name] = Object.values(v)[0];
}
);
return response;
}
);
return parsedData;
}

Dapper nested list query

I have 3 POCO classes as below;
Continent has many ContinentPart
Continent has many Country
Country has many City
I want to get Continens with ContinentParts, Countries and Cities
using (IDbConnection db = new SqlConnection(_conf["ConnectionStrings:WorkConStr"]))
{
string query = #"SELECT * FROM Continent as c
LEFT JOIN ContinentPart as cp ON c.ContinentId=cp.ContinentId
LEFT JOIN Country as co ON c.ContinentId=co.ContinentId
LEFT JOIN City ci ON co.CountryId=ci.CountryId
ORDER BY c.RecDate DESC";
var continentDictionary = new Dictionary<int, Continent>();
var list = db.Query<Continent, ContinentPart, Country, City, Continent>(
query,
map: (continent, continentPart, country, city) =>
{
Continent m;
if (!continentDictionary.TryGetValue(continent.ContinentId, out m))
{
continentDictionary.Add(continent.ContinentId, m = continent);
}
if (m.ContinentParts == null)
m.ContinentParts = new List<ContinentPart>();
m.ContinentParts.Add(continentPart);
if (m.Countries == null)
m.Countries = new List<Country>();
m.Countries.Add(country);
foreach (var h in m.Countries)
{
if (h.Cities == null)
h.Cities = new List<City>();
h.Cities.Add(city);
}
return m;
},
param: new { },
splitOn: "")
.Distinct()
.ToList();
return list;
}
You have two options here. You can use Multiple Resultsets feature:
https://medium.com/dapper-net/handling-multiple-resultsets-4b108a8c5172
Or, another option, better in my opinion, is to return the joined result as a JSON, for example with a query like the following:
select
continents.continent_id,
continents.continent,
countries.country_id,
countries.country,
cities.city_id,
cities.city
from
dbo.Continent continents
inner join
dbo.Country countries on continents.continent_id = countries.continent_id
inner join
dbo.City cities on countries.country_id = cities.country_id
for
json auto
that returns a JSON like this:
{
"continent_id": 1,
"continent": "Africa",
"countries":
[
{
"country_id": 1,
"country": "Nigeria",
"cities":
[
{
"city_id": 1,
"city": "Lagos"
}, {
"city_id": 2,
"city": "Abuja"
}
]
}
]
}
and that can then be turned into a complex object using Custom Type Handling:
https://medium.com/dapper-net/custom-type-handling-4b447b97c620
Links are to articles I've wrote on the subject.

jqgrid: How to define filter presets / templates inside a combo-box?

I have a jqgrid containing some data to filter. I'd like to define a combo box with some pre-defined filter sets / templates.
If a user selects an item of the combobox, the grid should automatically apply combined filters. Preferably, the combo box should be integrated into a toolbar or jqGrid's pager, but also in the html page would be fine.
For example:
COMBO BOX
Item templates filter parameters
___________
|Expired | << Timeout = true
|Last Week | << OpenDate between 02/13/2012 and 02/16/2012
|Last Month | << OpenDate between 01/01/2012 and 02/16/2012
|......... | ......
Thanks in advance for your help
jqGrid supports Searching Templates in the Advance Searching (see "Searching"/ "Search Templates" in the official jqGrid demo), but there are currently no searching templates support in the Toolbar Filtering.
I find your question very interesting. In the old question I described how one can use generic external filters to send additional information to the server. The way can be good in case of remote data, but it can't be used directly in the local grid or in the grid with the loadonce: true option.
So I created the demo which shows how the filter templates can be implemented in Toolbar Filtering and how to integrated the template in the jqGrid. I used toolbar: [true, "top"] to have additional empty toolbar above the column headers:
In the implementation I used the refreshSerchingToolbar function which I suggested originally here. It's important to understand, that the refreshSerchingToolbar function fill in the filter toolbar only the information which can be exactly represented by the filter. For example the filter for "Closed" rows can be represented in the filter toolbar (see the picture above), but the interval of dates "Last Week" and "Last Month" con't. In the cases the data in the grid will be filtered, but the corresponding fields of the filter toolbar stay empty.
The most important part of the code of the demo you can find below
var $grid = $("#list"),
initDate = function (elem) {
$(elem).datepicker({
dateFormat: 'dd-M-yy',
autoSize: true,
changeYear: true,
changeMonth: true,
showButtonPanel: true,
showWeek: true
});
},
numberTemplate = {formatter: 'number', align: 'right', sorttype: 'number', editable: true/*,
searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'nu', 'nn', 'in', 'ni'] }*/},
dateTemplate = {width: 80, align: 'center', sorttype: 'date',
formatter: 'date', formatoptions: { newformat: 'd-M-Y' }, editable: true, datefmt: 'd-M-Y',
editoptions: { dataInit: initDate },
searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge'], dataInit: initDate }},
yesNoTemplate = {align: 'center', editable: true, formatter: 'checkbox',
edittype: 'checkbox', editoptions: {value: 'Yes:No', defaultValue: 'No'},
stype: 'select', searchoptions: { sopt: ['eq', 'ne'], value: ':Any;true:Yes;false:No' }},
myDefaultSearch = 'cn',
getColumnIndex = function (columnIndex) {
var cm = this.jqGrid('getGridParam', 'colModel'), i, l = cm.length;
for (i = 0; i < l; i++) {
if ((cm[i].index || cm[i].name) === columnIndex) {
return i; // return the colModel index
}
}
return -1;
},
refreshSerchingToolbar = function (myDefaultSearch) {
var filters, i, l, rules, rule, iCol, cmi, control, tagName,
$this = $(this),
postData = $this.jqGrid('getGridParam', 'postData'),
cm = $this.jqGrid('getGridParam', 'colModel');
for (i = 0, l = cm.length; i < l; i++) {
control = $("#gs_" + $.jgrid.jqID(cm[i].name));
if (control.length > 0) {
tagName = control[0].tagName.toUpperCase();
if (tagName === "SELECT") { // && cmi.stype === "select"
control.find("option[value='']")
.attr('selected', 'selected');
} else if (tagName === "INPUT") {
control.val('');
}
}
}
if (typeof (postData.filters) === "string" &&
typeof (this.ftoolbar) === "boolean" && this.ftoolbar) {
filters = $.parseJSON(postData.filters);
if (filters && filters.groupOp === "AND" && typeof (filters.groups) === "undefined") {
// only in case of advance searching without grouping we import filters in the
// searching toolbar
rules = filters.rules;
for (i = 0, l = rules.length; i < l; i++) {
rule = rules[i];
iCol = getColumnIndex.call($this, rule.field);
if (iCol >= 0) {
cmi = cm[iCol];
control = $("#gs_" + $.jgrid.jqID(cmi.name));
if (control.length > 0 &&
(((typeof (cmi.searchoptions) === "undefined" ||
typeof (cmi.searchoptions.sopt) === "undefined")
&& rule.op === myDefaultSearch) ||
(typeof (cmi.searchoptions) === "object" &&
$.isArray(cmi.searchoptions.sopt) &&
cmi.searchoptions.sopt.length > 0 &&
cmi.searchoptions.sopt[0] === rule.op))) {
tagName = control[0].tagName.toUpperCase();
if (tagName === "SELECT") { // && cmi.stype === "select"
control.find("option[value='" + $.jgrid.jqID(rule.data) + "']")
.attr('selected', 'selected');
} else if (tagName === "INPUT") {
control.val(rule.data);
}
}
}
}
}
}
},
templateClosed = {
groupOp: "AND",
rules: [
{ field: "closed", op: "eq", data: "true" }
]
},
templateLastWeek = {
groupOp: "AND",
rules: [
{ field: "invdate", op: "ge", "data": "13-Feb-2012" },
{ field: "invdate", op: "le", "data": "16-Feb-2012"}
]
},
templateLastMonth = {
groupOp: "AND",
rules: [
{ field: "invdate", op: "ge", "data": "16-Jan-2012" },
{ field: "invdate", op: "le", "data": "16-Feb-2012"}
]
},
myFilterTemplateLabel = 'Filter by Template: ',
myFilterTemplateNames = ['Closed', 'Last Week', 'Last Month'],
myFilterTemplates = [templateClosed, templateLastWeek, templateLastMonth],
iTemplate,
cTemplates = myFilterTemplateNames.length,
templateOptions = '',
reloadWithNewFilterTemplate = function () {
var iTemplate = parseInt($('#filterTemplates').val(), 10),
postData = $grid.jqGrid('getGridParam', 'postData');
if (isNaN(iTemplate)) {
$grid.jqGrid('setGridParam', {search: false});
} else if (iTemplate >= 0) {
$.extend(postData, {
filters: JSON.stringify(myFilterTemplates[iTemplate])
});
$grid.jqGrid('setGridParam', {search: true});
}
$grid.trigger('reloadGrid', [{current: true, page: 1}]);
};
$grid.jqGrid({
...
toolbar: [true, "top"],
loadComplete: function () {
var $this = $(this);
if (typeof (this.ftoolbar) !== "boolean") {
// create toolbar if needed
$this.jqGrid('filterToolbar',
{stringResult: true, searchOnEnter: true, defaultSearch: myDefaultSearch});
}
refreshSerchingToolbar.call(this, myDefaultSearch);
}
});
$.extend($.jgrid.search, {
multipleSearch: true,
multipleGroup: true,
recreateFilter: true,
closeOnEscape: true,
closeAfterSearch: true,
overlay: 0,
tmplLabel: myFilterTemplateLabel,
tmplNames: myFilterTemplateNames,
tmplFilters: myFilterTemplates
});
$grid.jqGrid('navGrid', '#pager', {edit: false, add: false, del: false});
for (iTemplate = 0; iTemplate < cTemplates; iTemplate++) {
templateOptions += '<option value="' + iTemplate + '">' +
myFilterTemplateNames[iTemplate] + '</option>';
}
$('#t_' + $.jgrid.jqID($grid[0].id)).append('<label for="filterTemplates">'+
myFilterTemplateLabel + '</label>' +
'<select id="filterTemplates"><option value="">Not filtered</option>' +
templateOptions + '</select>');
$('#filterTemplates').change(reloadWithNewFilterTemplate).keyup(function (e) {
// some web browsers like Google Chrome don't fire "change" event
// if the select will be "scrolled" by keybord. Moreover some browsers
// like Internet Explorer don't change the select option on pressing
// of LEFT or RIGHT key. Another web browsers like Google Chrome do this.
// We make refrech of the grid in any from the cases. If needed one
// could modify the code to reduce unnneded reloading of the grid,
// but for the demo with a few local rows it's such optimization
// isn't really needed
var keyCode = e.keyCode || e.which;
if (keyCode === $.ui.keyCode.PAGE_UP || keyCode === $.ui.keyCode.PAGE_DOWN ||
keyCode === $.ui.keyCode.END || keyCode === $.ui.keyCode.HOME ||
keyCode === $.ui.keyCode.UP || keyCode === $.ui.keyCode.DOWN ||
keyCode === $.ui.keyCode.LEFT || keyCode === $.ui.keyCode.RIGHT) {
reloadWithNewFilterTemplate();
}
});