Closed graemep-nz closed 9 months ago
Never mind, I figured out a way to do it
PlutoGridChangeColumnFilterEvent(
column: columns[0],
filterType: columns[0].defaultFilter,
filterValue: value,
debounceMilliseconds:
gridStateManager!.configuration.columnFilter.debounceMilliseconds)); ```
I also need the same thing but I didn't understand where to call the object you wrote above. Can you give me the complete example if possible?
Thanks a lot
Here's the whole file
import 'package:flutter/material.dart';
import 'package:pluto_grid/pluto_grid.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:math';
import 'dart:convert';
//============================================================
// Bridge app application source file imports follow
//============================================================
import 'app_util.dart';
import 'widget_util.dart';
import 'firestore_data.dart';
import 'device_attributes.dart';
import 'choose_country.dart';
import 'choose_club.dart';
import 'user_login.dart';
// debug print
void _xprint(_){} // no print
void _dbprint(String str) => print(str); // session debug print
const String _info1 =
'If your state/region or town/city do not appear in the lists above, '
'please enter them manually below.';
// this widget requires a logged in user - LoginManager.activeFirebaseUserUID
// UserProfileManager.activeUserProfile, LoginManager.isUserLoggedIn == true
class CreateBridgeClub extends StatefulWidget{
String country;
CreateBridgeClub({super.key, this.country = ""}) {
}
@override
_CreateBridgeClub createState() => _CreateBridgeClub();
}
class _CreateBridgeClub extends State<CreateBridgeClub> {
String countryName = "";
String regionName = "";
String cityName = "";
String errorMessage = "";
// String selectedClub = "";
TextEditingController clubNameTextController = TextEditingController();
TextEditingController clubStreetAddressTextController = TextEditingController();
TextEditingController clubCityTownTextController = TextEditingController();
TextEditingController clubStateTextController = TextEditingController();
HandleDelayedTask readCountryIndexDocTask = HandleDelayedTask.idle();
//bool countryDocHasBeenRead = false;
PlutoGridStateManager? gridStateManager;
List<PlutoRow> plutoRows = [];
// recentlyViewedClub TODO
@override
void initState() {
super.initState();
countryName = widget.country;
if (countryName != "") {
readFirestoreCountryDoc(countryName);
}
}
static const double totalMaxWindowWidth = 560;
static List<PlutoColumn> columns = <PlutoColumn>[
PlutoColumn(
title: 'Club name',
field: 'ClubName',
type: PlutoColumnType.text(),
width: 110, // note - adjust totalMaxWindowWidth
//readOnly: true,
),
PlutoColumn(
title: 'City/Town',
field: 'CityTown',
type: PlutoColumnType.text(),
width: 110,
//readOnly: true,
),
PlutoColumn(
title: 'Region/State',
field: 'RegionState',
type: PlutoColumnType.text(),
width: 140,
//readOnly: true,
),
PlutoColumn(
title: 'Street address',
field: 'StreetAddress',
type: PlutoColumnType.text(),
width: 200,
//readOnly: true,
),
];
void readFirestoreCountryDoc(String country) async {
//countryDocHasBeenRead = false;
firestoreDb = FirebaseFirestore.instance;
DocumentSnapshot<Map<String, dynamic>>? countryDoc;
plutoRows = [];
gridStateManager?.removeAllRows(notify:false);
// TODO try local cache perhaps
await readCountryIndexDocTask.runTask( () async => (countryDoc = await firestoreDb.doc(
FirestorePathnames.WWBridgeClubsIndexColl + "/" + country).get()), 6000);
if (readCountryIndexDocTask.getTaskStatus == TaskStatus.finishOk && countryDoc != null) {
if (countryDoc!.exists) {
if (!AppData.bridgeClubIndexData.containsKey(country)) {
AppData.bridgeClubIndexData[country] = BridgeClubsIndexData();
}
_dbprint(">>>>>>>>>>>>>> read country doc");
copyFromFirestoreCountryDoc(countryDoc!, AppData.bridgeClubIndexData[country]!);
return;
} else {
//readCountryIndexDocTask.notifyListeners(); // this should be redundant, just making sure
}
}
else {
_dbprint("######Read country index doc failed 2");
_dbprint(readCountryIndexDocTask.getTaskMessage);
_dbprint(readCountryIndexDocTask.getTaskStatus.toString());
//readCountryIndexDocTask.notifyListeners(); // this should be redundant, just making sure
}
}
void copyFromFirestoreCountryDoc(DocumentSnapshot<Map<String, dynamic>> countryDoc,
BridgeClubsIndexData indexData ) {
T getField<T>(Map<String, dynamic>? data, T res, String fieldName) {
//print(res.runtimeType);
if (data != null && data.containsKey(fieldName)) {
return data[fieldName] ?? res;
}
return res;
}
if (countryDoc.data() == null) return;
Map<String, dynamic> docData = countryDoc.data()!;
indexData.formatCode = getField(docData, indexData.formatCode, "FormatCode");
indexData.numberOfClubs = getField(docData, indexData.numberOfClubs, "NumberOfClubs");
indexData.lastUpdatedFieldTimestamp
= getField(docData, indexData.lastUpdatedFieldTimestamp, "lastUpdatedFieldTimestamp");
// listofClubs is a json encoded list of strings that are as follows
// <Firestore Document ID> <Club name> <region> <city/town> <street address>
// e.g. ["0zgjwoCkEciypsSiLDM2","Christchurch","Canterbury","Christchurch", "15 Nova Pl]
indexData.listOfClubs = [];
indexData.clubData = [];
plutoRows = [];
if (docData.containsKey("ListOfClubs")) {
int len = docData["ListOfClubs"].length;
for (int k = 0; k < len; ++k) {
var clubData = jsonDecode(docData["ListOfClubs"][k]);
if (clubData.length >= 5) {
indexData.listOfClubs.add(docData["ListOfClubs"][k]);
indexData.clubData.add([for (var x in clubData) x]);
}
}
// sort indexData.clubData
indexData.clubData.sort( (list1, list2) {
var str1 = list1[3] + list1[1]; // city | clubname
var str2 = list2[3] + list2[1];
return str1.compareTo(str2);
});
for (var clubData in indexData.clubData) {
var aRow = PlutoRow(
key: (ValueKey(clubData[1] + clubData[2] + clubData[3])),
cells: {
'ClubName': PlutoCell(value: clubData[1] as String),
'RegionState': PlutoCell(value: clubData[2] as String),
'CityTown': PlutoCell(value: clubData[3] as String),
'StreetAddress': PlutoCell(value: clubData[4] as String),
},
);
plutoRows.add(aRow);
}
}
gridStateManager?.appendRows(plutoRows);
//countryDocHasBeenRead = true;
readCountryIndexDocTask.notifyListeners();
//gridStateManager?.eventManager!.addEvent(event)
}
/// columnGroups that can group columns can be omitted.
//final List<PlutoColumnGroup> columnGroups = [
// PlutoColumnGroup(title: 'Id', fields: ['id'], expandedColumn: true),
// PlutoColumnGroup(title: 'User information', fields: ['name', 'age']),
// PlutoColumnGroup(title: 'Status', children: [
// PlutoColumnGroup(title: 'A', fields: ['role'], expandedColumn: true),
// PlutoColumnGroup(title: 'Etc.', fields: ['joined', 'working_time']),
// ]),
//];
// https://github.com/bosskmk/pluto_grid/discussions/885
void processCountrySelection(String country, String region, String city ) {
_dbprint("cccccccccccccountry2 $country $region $city");
if (country != countryName && country != "") {
countryName = country;
readFirestoreCountryDoc(country);
clubStateTextController.text = "";
regionName = "";
clubCityTownTextController.text = "";
cityName = "";
return;
}
if (region != "") {
clubStateTextController.text = region ?? "";
regionName = region ?? "";
}
if (city != "") {
clubCityTownTextController.text = city ?? "";
cityName = city ?? "";
}
}
void processCreateClubButton() {
// TODO support non English - or don't restrict characters
//final validCharacters2 = RegExp(r"^[a-zA-Z0-9@#&*\ \-_\'\.\,\:\$\(\)\+\|\/\\]+$");
final validCharacters = RegExp(r"^[a-zA-Z0-9@#&*%=?<> _'.,;:$()+|/\-\\]+$");
errorMessage = "";
cityName = clubCityTownTextController.text;
regionName = clubStateTextController.text;
if (countryName == countrySearchPlaceholderString || countryName == "") {
errorMessage += "Please select a country";
}
if (regionName == stateSearchPlaceholderString || regionName == "") {
errorMessage += "\nPlease select a region/state";
}
if (cityName == citySearchPlaceholderString || cityName == "") {
errorMessage += "\nPlease select a town/city";
}
String str = clubNameTextController.text.trim();
clubNameTextController.text = str;
if (clubNameTextController.text.length < 6) {
errorMessage += "\nPlease enter at least six characters for the club name";
} else {
if (!validCharacters.hasMatch(clubNameTextController.text)) {
errorMessage += "\nError: illegal characters in club name";
}
}
str = clubStreetAddressTextController.text.trim();
clubStreetAddressTextController.text = str;
if (clubStreetAddressTextController.text.length < 8) {
errorMessage += "\nPlease enter at least 8 characters for the street address";
} else {
if (!validCharacters.hasMatch(clubStreetAddressTextController.text)) {
errorMessage += "\nError: illegal characters in street address";
}
}
if (errorMessage != "") {
readCountryIndexDocTask.getNotifier.notifyListeners();
return;
}
var clubName = clubNameTextController.text;
for (var cdat in AppData.bridgeClubIndexData[countryName]!.clubData) {
if (clubName == cdat[1] && regionName == cdat[2] && cityName == cdat[3]) {
errorMessage = "Error : This club is already configured";
readCountryIndexDocTask.getNotifier.notifyListeners();
return;
}
// TODO if club name matches but not city, prompt user to confirm first
// i.e. set a flag promptDuplicate
}
Map<String, dynamic> reqRecord = {
"userUID" : LoginManager.activeFirebaseUserUID,
"clubName" : clubNameTextController.text,
"clubCountry" : countryName,
"clubRegion" : regionName,
"clubCity" : cityName,
"clubStreetAddress" : clubStreetAddressTextController.text,
"lastUpdatedFieldTimestamp" : FieldValue.serverTimestamp(),
};
firestoreDb.doc(FirestorePathnames.WWUsersColl + "/" + LoginManager.activeFirebaseUserUID
+ FirestorePathnames.UserDocsColl + "/${FirestoreDocNames.requestCreateClubDocName}")
.update(reqRecord)
.onError((e, _) => _dbprint("error writing document: $e"));
}
final ButtonStyle style = ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 14),
minimumSize: const Size(80, 44),
//backgroundColor: Colors.orangeAccent, foregroundColor: Colors.deepPurple
);
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: readCountryIndexDocTask.getNotifier,
builder: (context2, value, child) {
_dbprint("rebuilding >>>>>>>>>>>>>");
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
print(constraints);
return SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(15),
child: SizedBox(
width: DeviceAttributes.deviceIsPhone ? constraints.maxWidth - 30 :
min(totalMaxWindowWidth, constraints.maxWidth - 30),
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 10),
Text(" Create new Bridge club",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20, color: Colors.lightBlue)
),
SizedBox(height: 22),
SizedBox(
width: DeviceAttributes.standardTextBoxWidthInDP,
child: FittedBox(
alignment: Alignment.topLeft,
//fit: BoxFit.,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: DeviceAttributes.standardTextBoxWidthInDP,
child: Row( // TODO don't need Row
children: [
Expanded(
child: ChooseLocation(key: ValueKey(4441), notifyCallback: processCountrySelection, showStates: true, showCities: true)
),
],
),
),
SizedBox(height: 16),
SizedBox(child: Text(_info1,), width: DeviceAttributes.standardTextBoxWidthInDP),
SizedBox(height: 20),
SizedBox(
height: 40,
width: DeviceAttributes.standardTextBoxWidthInDP,
child: Row(
children: [
SizedBox(
width: DeviceAttributes.standardTextBoxWidthInDP/2 - 6,
child: TextField(
controller: clubStateTextController,
clipBehavior: Clip.none,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.lime),
),
labelText: stateSearchPlaceholderString,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.black12),
),
),
),
),
SizedBox(width:12),
SizedBox(
width: DeviceAttributes.standardTextBoxWidthInDP/2 - 6,
child: TextField(
controller: clubCityTownTextController,
onChanged: (String value) {
gridStateManager!.eventManager!.addEvent(
PlutoGridChangeColumnFilterEvent(
column: columns[1],
filterType: columns[1].defaultFilter,
filterValue: value,
debounceMilliseconds:
gridStateManager!.configuration.columnFilter.debounceMilliseconds));
},
clipBehavior: Clip.none,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.lime),
),
labelText: citySearchPlaceholderString,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.black12),
),
),
),
),
],
),
),
SizedBox(height: 15),
SizedBox(
height: 40,
width: DeviceAttributes.standardTextBoxWidthInDP,
child: TextField(
controller: clubNameTextController,
onChanged: (String value) {
gridStateManager!.eventManager!.addEvent(
PlutoGridChangeColumnFilterEvent(
column: columns[0],
filterType: columns[0].defaultFilter,
filterValue: value,
debounceMilliseconds:
gridStateManager!.configuration.columnFilter.debounceMilliseconds));
},
clipBehavior: Clip.none,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.lime),
),
labelText: 'Club name',
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.black12),
),
),
),
),
SizedBox(height: 12),
SizedBox(
height: 40,
width: DeviceAttributes.standardTextBoxWidthInDP,
child: TextField(
controller: clubStreetAddressTextController,
clipBehavior: Clip.none,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.lime),
),
labelText: 'Street address',
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 1, color: Colors.black12),
),
),
),
),
SizedBox(height: 10),
if (errorMessage != "") Text(errorMessage,style: myStyles.errorTextStyle),
SizedBox(height: 10),
SizedBox(
width:190,
child: Row(
children: [
ElevatedButton(
style: style,
onPressed: () {
processCreateClubButton();
},
child: const Text('Create\nclub'),
),
SizedBox(width: 20),
ElevatedButton(
style: style,
onPressed: () {
selectChooseBridgeClubWidget();
},
child: const Text('Cancel'),
),
]
),
),
],
),
),
),
SizedBox(height: 22),
SizedBox(child: Text('Clubs already in the database for your country'), width: DeviceAttributes.standardTextBoxWidthInDP),
SizedBox(height: 12),
SizedBox(
height: 400, // min(constraints.maxHeight - 200, 900),
//padding: const EdgeInsets.all(15),
//width: 400,
/*
child: FittedBox(
fit: BoxFit.contain,
clipBehavior: Clip.hardEdge,
child: Container(
width: 800,
height: 700,
child: Text("HHHHHHHUUUUuuuuuuKKKKK123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"),
color: Colors.redAccent,
),
)
*/
child: PlutoGrid(
columns: columns,
rows: [], //plutoRows,
//columnGroups: columnGroups,
onLoaded: (PlutoGridOnLoadedEvent event) {
gridStateManager = event.stateManager;
gridStateManager?.setShowColumnFilter(true);
_dbprint("grid loaded >>>>>>>>>>>>>>>>>>>>>>>");
},
onChanged: (PlutoGridOnChangedEvent event) {
print(event);
},
mode: PlutoGridMode.selectWithOneTap, // this makes the table read only
// onSelected: (PlutoGridOnSelectedEvent event) {
// selectedClub = plutoRows[event.rowIdx!].cells['ClubName']!.value;
// chosenClubTextController.text = selectedClub;
// //print(event);
// //print(event.rowIdx);
// },
configuration: const PlutoGridConfiguration(),
createFooter: (stateManager) {
stateManager.setPageSize(8, notify: false); // default 40
return PlutoPagination(stateManager);
},
),
),
],
),
),
),
),
);
}
);
}
);
}
}
Perfect! Thanks a lot! In your experience, would it be possible to search multiple columns from a single textField? for example you write:
onChanged: (String value) { gridStateManager!.eventManager!.addEvent( PlutoGridChangeColumnFilterEvent( column: columns[1], filterType: columns[1].defaultFilter, filterValue: value, debounceMilliseconds: gridStateManager!.configuration.columnFilter.debounceMilliseconds)); },
Would it be possible to search across multiple columns instead of putting columns[1]?
I don't have any experience of that but I don't see why why it wouldn't work. Just call addEvent for each column and see if it works. I'm not actually using plutoGrid any more - I switched to using syncfusion datagrid.
I was also thinking of using syncfusion datagrid but I saw that you have to pay, correct?
Only if your gross revenue exceeds one million dollars or something, you need to have 5 or less developers and less than ten employees https://www.syncfusion.com/products/communitylicense
then yes we should pay... how is your experience with syncfusion datagrid?
syncfusion datagrid vs pluto_grid?
There's a bunch of examples you can try here. https://flutter.syncfusion.com/ I swapped because pluto-grid wasn't being maintained plus there was a slight glitch with the mouse cursor when it was over a filter icon. pluto-grid seemed like high quality, possibly better software than syncfusion, I don't really know which has the most capability. Probably syncfusion will have better support even for the free license I think.
also probably this is the one to use now https://pub.dev/packages/pluto_grid_plus
I'm already using that
I need to be able to set the column filter value programmatically so that when someone types in a textbox outside the grid, the list of items shown in the grid changes to show only those that "contain" the value typed by the user. The row at the top of the grid that shows the current filters would update automatically as the user typed.