I'm trying to post kendo grid data on clicking a button. I want to save the data to an excel file, the default option provided by kendo for saving grid data fails when the dataset is large. So, I'm trying to do the excel operations on server side. My issue is I'm not able to pass the data to the controller.
Code:
View
#(Html.Kendo().Grid(Model)
.Name("OutputCashGrid")
.Events(ev => ev.Edit("onEdit"))
.Columns(columns =>
{
columns.Bound(p => p.ClientID).Hidden();
columns.Bound(p => p.LoadID).Title("Loadid").Hidden();
columns.Bound(p => p.Level1).Title("Level1").Width(130);
columns.Bound(p => p.Level2).Title("Level2").Width(130);
columns.Bound(p => p.AsOfDate).Title("AsOfDate").Width(150).Format("{0:d}").Width(130);
})
.ToolBar(toolbar =>
{
toolbar.Save().HtmlAttributes(new { id = "Save" }).SaveText("Save External Balances and Comments");
toolbar.Custom()
.Text("Export To Excel")
.HtmlAttributes(new { #class = "export" })
.Url("javascript:myFunction()");
})
.Editable(editable => editable.Mode(GridEditMode.InCell)) // Use in-cell editing mode.
.HtmlAttributes(new { style = "height: 550px;" })
.Groupable()
//.Excel(excel => excel
// .AllPages(true)
// .FileName("CashSummary_" + #ViewBag.goClientName + "_" + #ViewBag.Asofdate.ToString("MM/dd/yyyy") + ".xlsx")
// .Filterable(true)
// .ProxyURL(Url.Action("Excel_Export_Save", "SaveRec"))
// ) //this fails for large datasets
.Reorderable(r => r.Columns(true))
.Sortable()
.ColumnMenu()
.DataSource(dataSource => dataSource
.Ajax()
.Events(e2 => e2.Change("vChange"))
.PageSize(50)
.ServerOperation(false)
.Batch(true) // Enable batch updates.
.Model(model =>
{
model.Id(p => p.OutputcashID); // Specify the property which is the unique identifier of the model.
//model.Field(p => p.OutputcashID).Editable(false); // Make the ProductID property not editable.
.Update("Editing_Update", "SaveRec",new { loadid = #loadid,clientid=#clientid })
)
.Pageable(pageable => pageable
.Refresh(true)
.Input(true)
.Numeric(false)
)
.Resizable(resize => resize.Columns(true))
.Selectable()
)
I tried using Url("Action","Controller") in the custom toolbar it hits the action but doesn't pass the data. When i decorate the action with HttpPost it gives me 404 error.
When calling with js function
function myFunction() {
var grid = $("#OutputCashGrid").getKendoGrid();
var data = JSON.stringify(grid.dataSource.view());
$.ajax({
url: '/SaveRec/Excel_save',
type: 'POST',
data: data,
async: true,
processData: false
});
}
Controller:
// [AcceptVerbs(HttpVerbs.Post)] get 404 error when using http.post, without this it hits the action but doesn't pass data.
public ActionResult Excel_save([DataSourceRequest]DataSourceRequest request,[Bind(Prefix = "models")]IEnumerable<OutputCash> results)
{
//gives me null for results
var WFSEntities=new WFSEntities();
var grid = WFSEntities.OutputCashes;
var gridresults = grid.ToDataSourceResult(request).Data; //returns all the data from the table.
//Create new Excel workbook
var workbook = new HSSFWorkbook();
//Create new Excel sheet
var sheet = workbook.CreateSheet();
//create excel sheet, removed code for brevity
return File(output.ToArray(), //The binary data of the XLS file
"application/vnd.ms-excel", //MIME type of Excel files
"Output.xls"); //Suggested file name in the "Save as" dialog which will be displayed to the end user
}
To do the entire operation server side, you don't pass the data to the controller from the ajax call. The controller takes in the URL you pass and you grab the data for the export in the controller. After it has been pulled fresh server side, you pass to your worksheet generator. The trickiest part is passing in any filters/pages that you want applied to the data before the controller action grabs it. It is also beneficial to have a loading image present while the server side processing takes place.
Related
I'm using the great Tabular package. https://github.com/Meteor-Community-Packages/meteor-tabular.
I'm making use of the client-side selector helper to Reactively change my table by having Server modify the query for my dataset.
I have multiple HTML inputs that act as filters and am populating a ReactiveDict with the values. A search-button click event triggers the ReactiveDict to get populated with an Object using .set
Initialization of ReactiveDict
Template.tbl.onCreated(function () {
const self = this;
self.filters = new ReactiveDict({});
});
Population of ReactiveDict
'click #search-button'(e, template) {
//clear to 'reset' fields in ReactiveDict that could've been cleared by User
template.filters.clear();
const searchableFields = getSearchableFields();
//Initialize standard JS Obj that ReactiveDict will then be set to
const filterObj = {};
//Loop through search fields on DOM and populate into Obj if they have a val
for (let field of searchableFields) {
const value = $(`#${field}-filter`).val();
if (value) {
filterObj[field] = new RegExp(escapeStringRegex(value.trim()), 'i');
}
}
if (Object.keys(filterObj).length) {
template.filters.set(filterObj);
}
},
Selector Helper
selector: () => {
const filters = Template.instance().filters.all();
const selector = { SOME_DEFAULT_OBJ, ...filters };
return selector;
},
I'm noticing the server doesn't notice any changes from a ReactiveDict if all keys remain the same.
I'm testing this by logging in the serve-side's changeSelector md and verifying that my logging does not occur if just a value in selector has changed.
Is there a solution to this?
I.e. {foo:'foo'} to {foo:'bar'} should reactively trigger the server to re-query but it does not. But {foo:'foo'} to {bar:'bar'} would get triggered.
Is this an issue with how I'm using the ReactiveDict or is this on the Tabular side?
Thanks
I have 2 SP lists (A and B).
List A has filter buttons next to each list item. When a user clicks a button it should filter List B, only showing the related items.
List A has an Id column which List B matches it's column (MasterItems) with List A's Id.
Here's the code I'm using:
public _getListItems() {
sp.web.lists.getByTitle("ListA").items.get().then((items: any[]) => {
let returnedItems: IListAItem[] = items.map((item) => { return new ListAItem(item); });
this.setState({
Items: returnedItems,
ListAItems: returnedItems,
});
});
sp.web.lists.getByTitle("ListB").items.get().then((items: any[]) => {
let returnedItems: IListBItem[] = items.map((item) => { return new ListBItem(item); });
this.setState({
ListBItems: returnedItems, //This brings in the items from ListB so they can be filtered on this.state.ListB when clicked
});
});
}
private _editItem = (ev: React.MouseEvent<HTMLElement>) => {
this._getListItems(); //This attempts to reset the list when another filter is clicked, but is half working!
const sid = Number(ev.currentTarget.id);
const sid2 = 'DIBR'+sid;
let _item = this.state.ListBItems.filter((item) => { return item.MasterItem == sid2; });
if (_item && _item.length > 0) {
sp.web.lists.getByTitle("ListB").items.get().then((items: any[]) => {
let returnedItems: IListBItem[] =
items.filter(i => _item.some(other => other.Id === i.Id)).map(
(item) => new ListBItem(item)
);
this.setState({
ListBItems: returnedItems,
});
});
}
}
The problem is that when the button is clicked next to an item, it filters correctly on first click!
but if filtered again on the same or different item it will sometimes unset the filter and mix results, other times it will filter correctly. So I'm suspecting I've made a state problem here, but can't seem to discover why.
Regards,
T
UPDATE: I've added a clear filter button which makes things work, but would like the user to be able to click on filter to filter instead of having to clear it each time.
I am doing the same in my SharePoint list
so basically I always set the clear filter function before the filter function,
for example:
function myFilter(){
//my filter code goes here
}
function clearFilter(){
//the clear filter code goes here
}
lets say you are running the function on an item select or a button click or text input change, set the clear filter to run before the filter.
function funcGroup{
clearFilter();
setTimeout(() => {
myFilter();
}, 300);
}
or
function funcGroup{
setTimeout(() => {
clearFilter();
}, 300);
myFilter();
}
I am using this scenario with my SharePoint lists and its working perfect...
I am trying to set the value (the text within the button) of a command column in a Telerik MVC grid.
I have this code:
columns.Command(o => o.Custom("ActivationCode").Text("\\#=ActivationCode\\#").Click("showDetails")).Width(180);
What I am trying to do is have the text inside the button render as the value of the underlying data model. Example value would be AY63P9 for example.
Using the Text() method it just renders the value in that function, it does not insert the bound value.
I have also tried using
columns.Command(o => o.Custom("ActivationCode").Click("showDetails")).Width(180).Title("\\#=ActivationCode\\#");
But I receive the same error message - "ActivationCode is not defined"
You can name the button using jQuery.
Declare DataBound event for the grid as below,
#(Html.Kendo().Grid<Models.TempTable>()
.Name("tempData")
.Columns(columns =>
{
columns.Bound(p => p.Name).Width(100);
columns.Bound(p => p.Status).Width(100);
columns.Command(command =>
{
command.Custom("ActivationCode").Text("");
}).Width(100);
})
.Events(e => { e.DataBound("tempData_DataBound"); })
In DataBound method, get the value you want to assign. For example, get the first column value (Name column) and assign it to the button.
function tempData_DataBound(e) {
var tmpTable = $('#tempData').find('table tbody');
var rowCnt = e.sender._data.length;
for (var i = 0; i < rowCnt; i++) {
var _tempName = $(tmpTable).find('tr:eq('+ i +') td:eq(0)').html();
$(tmpTable).find('tr:eq(' + i + ')').find('.k-grid-ActivationCode').text(_tempName);
}
}
The result is
Relative newbie; forgive me if my etiquette and form here aren't great. I'm open to feedback.
I have used create-react-native-app to create an application using PouchDB (which I believe ultimately uses AsyncStorage) to store a list of "items" (basically).
Within a TabNavigator (main app) I have a StackNavigator ("List screen") for the relevant portion of the app. It looks to the DB and queries for the items and then I .map() over each returned record to generate custom ListView-like components dynamically. If there are no records, it alternately displays a prompt telling the user so. In either case, there is an "Add Item" TouchableOpacity that takes them to a screen where they an add a new item (for which they are taken to an "Add" screen).
When navigating back from the "Add" screen I'm using a pattern discussed quite a bit here on SO in which I've passed a "refresh" function as a navigation param. Once the user uses a button on the "Add" screen to "save" the changes, it then does a db.post() and adds them item, runs the "refresh" function on the "List screen" and then navigates back like so:
<TouchableOpacity
style={styles.myButton}
onPress={() => {
if (this.state.itemBrand == '') {
Alert.alert(
'Missing Information',
'Please be sure to select a Brand',
[
{text: 'OK', onPress: () =>
console.log('OK pressed on AddItemScreen')},
],
{ cancelable: false }
)
} else {
this.createItem();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsScreen');
}
}
}
>
And all of this works fine. The "refresh" function (passed as onGoBack param) works fine... for this screen. The database is called with the query, the new entry is found and the components for the item renders up like a charm.
Each of the rendered ListItem-like components on the "List screen" contains a react-native-slideout with an "Edit" option. An onPress for these will send the user to an "Item Details" screen, and the selected item's _id from PouchDB is passed as a prop to the "Item Details" screen where loadItem() runs in componentDidMount and does a db.get(id) in the database module. Additional details are shown from a list of "events" property for that _id (which are objects, in an array) which render out into another bunch of ListItem-like components.
The problem arises when either choose to "Add" an event to the list for the item... or Delete it (using another function via [another] slideout for these items. There is a similar backward navigation, called in the same form as above after either of the two functions is called from the "Add Event" screen, this being the "Add" example:
async createEvent() {
var eventData = {
eventName: this.state.eventName.trim(),
eventSponsor: this.state.eventSponsor.trim(),
eventDate: this.state.eventDate,
eventJudge: this.state.eventJudge.trim(),
eventStandings: this.state.eventStandings.trim(),
eventPointsEarned: parseInt(this.state.eventPointsEarned.trim()),
};
var key = this.key;
var rev = this.rev;
await db.createEvent(key, rev, eventData);
}
which calls my "db_ops" module function:
exports.createEvent = function (id, rev, eventData) {
console.log('You called db.createEvent()');
db.get(id)
.then(function(doc) {
var arrWork = doc.events; //assign array of events to working variable
console.log('arrWork is first assigned: ' + arrWork);
arrWork.push(eventData);
console.log('then, arrWork was pushed and became: ' + arrWork);
var arrEvents = arrWork.sort((a,b)=>{
var dateA = new Date(a.eventDate), dateB = new Date(b.eventDate);
return b.eventDate - a.eventDate;
})
doc.events = arrEvents;
return db.put(doc);
})
.then((response) => {
console.log("db.createEvent() response was:\n" +
JSON.stringify(response));
})
.catch(function(err){
console.log("Error in db.createEvent():\n" + err);
});
}
After which the "Add Event" screen's button fires the above in similar sequence to the first, just before navigating back:
this.createEvent();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsDetails');
The "refresh" function looks like so (also called in componentDidMount):
loadItem() {
console.log('Someone called loadItem() with this.itemID of ' + this.itemID);
var id = this.itemID;
let totalWon = 0;
db.loadItem(id)
.then((item) => {
console.log('[LOAD ITEM] got back data of:\n' + JSON.stringify(item));
this.setState({objItem: item, events: item.events});
if (this.state.events.length != 0) { this.setState({itemLoaded: true});
this.state.events.map(function(event) {
totalWon += parseInt(event.eventPointsEarned);
console.log('totalWon is ' + totalWon + ' with ' +
event.eventPointsEarned + ' having been added.');
});
};
this.setState({totalWon: totalWon});
})
.catch((err) => {
console.log('db.loadItem() error: ' + err);
this.setState({itemLoaded: false});
});
}
I'm at a loss for why the List Screen refreshes when I add an item... but not when I'm doing other async db operations with PouchDB in what I think is similar fashion to modify the object containing the "event" information and then heading back to the Item Details screen.
Am I screwing up with Promise chain someplace? Neglecting behavior of the StackNavigator when navigating deeper?
The only other difference being that I'm manipulating the array in the db function in the non-working case, whereas the others I'm merely creating/posting or deleting/removing the record, etc. before going back to update state on the prior screen.
Edit to add, as per comments, going back to "List screen" and the opening "Item Details" does pull the database data and correctly shows that the update was made.
Further checking I've done also revealed that the console.log in createEvent() to print the response to the db call isn't logging until after some of the other dynamic rendering methods are getting called on the "Item Details" screen. So it seems as though the prior screen is doing the get() that loadItem() calls before the Promise chain in createEvent() is resolving. Whether the larger issue is due to state management is still unclear -- though it would make sense in some respects -- to me as this could be happening regardless of whether I've called my onGoBack() function.
Edit/bump: I’ve tried to put async/await to use in various places in both the db_ops module on the db.get() and the component-side loadItem() which calls it. There’s something in the timing of these that just doesn’t jive and I am just totally stuck here. Aside from trying out redux (which I think is overkill in this particular case), any ideas?
There is nothing to do with PDB or navigation, it's about how you manage outer changes in your depending (already mounted in Navigator since they are in history - it's important to understand - so componentDidMount isn't enough) components. If you don't use global state redux-alike management (as I do) the only way to let know depending component that it should update is passing corresponding props and checking if they were changed.
Like so:
//root.js
refreshEvents = ()=> { //pass it to DeleteView via screenProps
this.setState({time2refreshEvents: +new Date()}) //pass time2refreshEvents to EventList via screenProps
}
//DeleteView.js
//delete button...
onPress={db.deleteThing(thingID).then(()=> this.props.screenProps.refreshEvents())}
//EventList.js
...
constructor(props) {
super(props);
this.state = {
events: [],
noEvents: false,
ready: false,
time2refreshEvents: this.props.screenProps.time2refreshEvents,
}
}
static getDerivedStateFromProps(nextProps, currentState) {
if (nextProps.screenProps.time2refreshEvents !== currentState.time2refreshEvents ) {
return {time2refreshEvents : nextProps.screenProps.time2refreshEvents }
} else {
return null
}
}
componentDidMount() {
this._getEvents()
}
componentDidUpdate(prevProps, prevState) {
if (this.state.time2refreshEvents !== prevState.time2refreshEvents) {
this._getEvents()
}
}
_getEvents = ()=> {
//do stuff querying db and updating your list with actual data
}
So i have a mobile app with 5 tabs one of which is the search tab and i would like that every time i click on the search tab the search comes automatically enabled and the keyboard pops up also meaning that the cursor will be ready in place.I search in the html in the browser and found out that everytime i click on the search bar to write something a new class is added called input-focused so i tried to add this automatically using a methos called enable and which is calling the method searchEnabled but still its not working... any ideas?
<f7-searchbar
#searchbar:enable="searchEnabled"
#searchbar:search="search"
search-container=".search-list"
search-in=".item-title"
></f7-searchbar>
methods: {
search (searchbar, query) {
if (query === '') {
this.results = []
} else {
this.filter.q = query
Search.filter(this.filter).then(({data}) => {
this.results = data
})
.catch((error) => {
console.log('error:', error)
})
}
},
searchEnabled (searchbar) {
searchbar.$inputEl[0].classList.add(['input-focused'])
}
}