How to use pagination in Kendo grid with partial views in mvc5 - asp.net-mvc-5

I am using Kendo grid to show some user information. I have 3 tabs in view Admin and each of the tab is partial view. say customer,user,portfolio. i am loading the kendo grid while calling the main view itself which is Admin View through view Model which consists 3 classes
public class AdminViewModel
{
public IEnumerable<CustUser> CustomerUsers { get; set; }
public IEnumerable<Customer> Customers { get; set; }
public IEnumerable<Portfolio> Portfolios { get; set; }
}
And i am passing this view model to each of the 3 partial views. And its working fine but the problem is when i want to go to the next page in Grid. Its just returning the Json Data on the page.
My kendo grid:
<div class="col-xs-12">
#try
{
#(Html.Kendo().Grid(Model.CustomerUsers)
.Name("User")
.Columns(columns =>
{
columns.Bound(p => p.CustUserID).Visible(false);
columns.Bound(p => p.FirstName);
columns.Bound(p => p.LastName);
columns.Bound(p => p.CustUserName);
columns.Bound(p => p.EmailAddress);
columns.Bound(p => p.IsActive);
columns.Bound(p => p.ModifiedDt).Visible(false);
columns.Bound(p => p.CreatedDt).Visible(false);
columns.Command(command =>
{
//command.Edit();
//command.Destroy().Text("Del");
command.Custom("Delete").Click("ConfirmDeleteUnit").Text("<span class='fa fa-trash-o'></span>").HtmlAttributes(new { #style = "width:40px !important;min-width:0px !important;" }); ;
}).Width(160);
})
.Pageable()
.Sortable()
.Scrollable(scrollable => scrollable.Virtual(true))
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(25)
.Events(events => events.Error("error_handler"))
.Model(model =>
{
model.Id(i => i.CustUserID);
})
.Read(read => read.Action("LazyCustUser_Read", "Admin")
)
//.Update(update => update.Action("InlineEditUnit_Update", "Unit"))
//.Destroy(update => update.Action("InlineEditUnit_Destroy", "Unit"))
)
)
}
catch (Exception exe)
{
CSP.Web.Helpers.Log objLog = new CSP.Web.Helpers.Log();
objLog.LogError(exe.Message.ToString() + this.ToString());
}
</div>
and My Admin Controller
public async Task<ActionResult> Index()
{
if (Session["custUserID"] != null)
{
try
{
var custUsers = await _custUserRepo.GetAllCustUsers();
var customers = await _CustomerRepo.GetAllCustomers();
var portfolios = await _portfolioRepo.GetPortFolios();
if (custUsers == null)
return RedirectToAction("ShowError", "Error");
adminViewModel.CustomerUsers = custUsers;
}
catch (Exception exe)
{
objError.LogError(exe.Message);
return RedirectToAction("ShowError", "Error");
}
return View(adminViewModel);
}
else
{
return RedirectToAction("Index", "Account");
}
}
public async Task<ActionResult> LazyCustUser_Read([DataSourceRequest] DataSourceRequest request)
{
if (Session["custUserID"] != null)
{
try
{
var custUser = await _custUserRepo.GetAllCustUsers();
return Json(custUser.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
catch (Exception exe)
{
Log objLog = new Log();
objLog.LogError(exe.InnerException.ToString());
return RedirectToAction("ShowError", "Error");
}
}
else
{
return RedirectToAction("Index", "Account");
}
}

Related

Get Large Set of Data (~20000 item) from mongodb and display it in angular 7 Admin Products Page

I'm trying to get ~20000 items from Mongodb and display them at my Angular 7 Project in Admin Products Page in a table
The Problem is that the website takes too much time and sometimes it crashes
Is there a way to get them as 1000 item after another, get them fastly, or paginate them as 0-1000 item in a page 1 and 1000-2000 in page 2?
I searched for it and I didn't find any useful resource or even a similar question here.
I found that I could limit number of get items in mongodb through this code:
ITEMS_COLLECTION.find({}).limit(1000).toArray((err, allItems) => {
items = allItems
})
I don't want to just limit it to 1000, I want get all of them and display them without crashing the browser or not to be so slow.
This is the Item Page: src > Item.js
function getItems() {
let items
Server().then((server_data) => {
server_data.ITEMS_COLLECTION.find({}).limit(1000).toArray((err, allItems) => {
items = allItems
})
})
/*eslint no-undef: 0*/
return new Promise(resolve => {
setTimeout(() => {
resolve(items)
}, 4000)
})
}
This is the server page: src > server.js
app.get('/activeProducts', (req, res) => {
Item.getActiveItems()
.then(active_items => {
res.send(active_items);
})
.catch(err => {
throw new CustomError('Could not get Active Items', err);
});
});
This is the Products Service:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { LoginService } from './login.service';
import { Router } from '#angular/router';
import { MatDialog, MatDialogRef } from '#angular/material';
import { environment } from '../../environments/environment';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
Authorization: 'my-auth-token'
})
};
#Injectable()
export class ProductsService {
products = this.http.get(
` ${environment.link_url_with_backend}/activeProducts`
);
cached_products;
constructor(
private loginService: LoginService,
private router: Router,
private http: HttpClient,
public dialogRef: MatDialog
) {
this.products.subscribe(data => {
console.log(data);
this.cached_products = data;
});
}
}
This is the Products Component:
export class ProductsComponent implements OnInit, DoCheck {
constructor(private productService: ProductsService) {}
products;
ngOnInit() {
this.products = this.productService.cached_products;
}
}
This is the Products HTML:
<div
class="products-container wrapper"
>
<app-product
*ngFor="let product of products"
[product]="product"
style="width: 360px;"
></app-product>
</div>
First of All In The Backend you need to get the first 100 for example:
function getFirst100Items() {
let items
ITEMS_COLLECTION
.find({})
.limit(100)
.sort({id: 1})
.toArray( (err, allItems) => {
items = allItems
})
return new Promise(resolve => {
setTimeout(() => {
resolve(items)
}, 2000)
})
}
Then you can add load more function for example:
function getMore100Items(loadedItems) {
let items
server_data.ITEMS_COLLECTION
.find({ id: { $gte: loadedItems } })
.limit(100)
.sort({id: 1})
.toArray( (err, allItems) => {
items = allItems
})
return new Promise(resolve => {
setTimeout(() => {
resolve(items)
}, 2000)
})
}
function getItemsCount() {
let itemsCounts
server_data.ITEMS_COLLECTION.countDocuments()
.then( (counts) => {
itemsCounts = counts
})
return new Promise(resolve => {
setTimeout(() => {
resolve({itemsCounts})
}, 1000)
})
}
Then You Specify the express routes
app.get('/first/100products', (req, res) => {
Item.getFirst100Items()
.then(items => {
res.send(items);
})
.catch(err => {
throw new CustomError('Could not get Items', err);
});
});
app.post('/loadmore/products', (req, res) => {
loaded_items = req.body.loadedItems
res.send({loaded_items})
});
app.get('/loadmore/products', (req, res) => {
setTimeout(() => {
Item.getMore100Items(loaded_items)
.then(items => {
res.send(items);
})
.catch(err => {
throw new CustomError('Could not get Items', err);
});
}, 2000);
});
Second In Angular 7
Parent Component
loadedItems = 0;
#ViewChild(AdminTableComponent) adminTable;
constructor(public dialog: MatDialog, private http: HttpClient) {
this.http
.get(` ${environment.link_url_with_backend}/first/100products`)
.subscribe((data: {}[]) => {
this.products_data = data;
this.dataSource = new MatTableDataSource(this.products_data);
});
}
ngOnInit() {}
loadMore() {
this.http
.get(` ${environment.link_url_with_backend}/products/length`)
.subscribe((itemsCount: any) => {
if (this.loadedItems < itemsCount.itemsCounts - 100) {
this.adminTable.isLoad = true;
this.loadedItems += 100;
this.http
.post(
`${environment.link_url_with_backend}/loadmore/products`,
JSON.stringify({ loadedItems: this.loadedItems }),
httpOptions
)
.subscribe(data => {
console.log(data);
});
this.http
.get(` ${environment.link_url_with_backend}/loadmore/products`)
.subscribe((items: {}[]) => {
items.map(product => {
this.products_data.push(product);
this.dataSource = new MatTableDataSource(this.products_data);
this.adminTable.isLoad = false;
this.adminTable.dataSource.sort = this.adminTable.sort;
this.adminTable.dataSource.paginator = this.adminTable.paginator;
return;
});
});
} else {
this.adminTable.isLoad = false;
this.adminTable.isLoadMore = false;
alert('No More Products to Get');
return;
}
});
}
ChildComponent
loadMoreItems() {
this.loadMore.emit('loadMore');
}
#Input() dataSource;
#Input() displayedColumns;
#Input() dialogComponent;
#Output() loadMore = new EventEmitter();
isLoad = false;
isLoadMore = false;
And you can continue from here
Hope this helps!
Note: All this is just an example so don't take it exactly

How to use Backend Object in FrontEnd TypeScript (MEAN Stack)

Using MongoDB, express.js, angular4, node.js
A string I retrieve is well retrieved, but not the same as a full object...
account.service.ts (full, )
import { Injectable } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
const jwtDecode = require('jwt-decode');
import { User } from '../../models/user.model';
import { AuthService } from './auth.service';
#Injectable()
export class AccountService {
constructor(private http: HttpClient,
private authService: AuthService) {}
user: any[];
currentUser() {
if(this.authService.isAuthenticated()){
const token = localStorage.getItem('token');
const decoded = jwtDecode(token);
return decoded.user;
}
};
getProfile() {
const id = this.currentUser();
return this.http.get("http://localhost:3000/user/" + id).
map(
(response: Response) => {
const data = response.json();
return data;
}
)
.catch(
(error: Response) => {
console.log(error);
return Observable.throw(error.json());
}
)
}
user-profile.component.ts
export class UserProfileComponent implements OnInit {
id: string;
user: any;
constructor(private account: AccountService) {}
ngOnInit() {
this.id = this.account.currentUser();
this.user = this.account.getProfile()
.subscribe(user => {
this.user = user;
return this.user;
});
}
logUser() {
console.log(this.id);
console.log(this.user);
}
}
user-profile.component.html
<p>{{user}}</p>
<p>User with ID {{id}} Loaded</p>
<a (click)="logUser()">Log User Test</a>
HTML file shows:
[object Object]
User with ID 59ca916323aae527b8ec7fa2 Loaded
What I get from clicking "log User" link is the retrieved ID string and the user object:
59ca916323aae527b8ec7fa2
[{...}] //clicking this reveals all of the object's details.
But I can't make that step of getting those details and presenting them in the HTML as I successfully managed with the ID... I mean, {{user.anything}} doesn't fetch the user's data as it should
May I have some assistance?
Change your getProfile() to,
getProfile() {
const id = this.currentUser();
return this.http.get("http://localhost:3000/user/" + id).
map(
(response) => response.json()
)
.catch(
(error: Response) => {
console.log(error);
return Observable.throw(error.json());
}
)
}
Also, in ngOnInit() change this one,
this.user = this.account.getProfile()
.subscribe((user) => {
this.user = user;
});
See if it gives you the right output.
EDIT
Change this one,
this.user = this.account.getProfile()
.subscribe((user) => {
this.user = JSON.parse(JSON.stringify(user));
});
EDIT-2
this.user = this.account.getProfile()
.subscribe((user) => {
this.user = JSON.stringify(user);
this.userObj = JSON.parse(JSON.stringify(user));
// also try, this.userObj = user; if above line didn't work
});
Define another property in component as ,
userObj: any;
Refer to the object in template as this
{{ userObj[0]?.email }}

angular not showing created object until I refresh page

Please help, I have a message service that is fetching my messages from the database and displaying them on a user profile page. When I create or delete a message, I can only see the changes when I refresh the browser.
The page updates dynamically before I apply a filter to sort by Id, why is the filter unbinding my array of messages?
profile component:
export class ProfileComponent implements OnInit {
user: User;
userId: any;
messages: any;
constructor(private authService: AuthService, private messageService: MessageService) {}
ngOnInit() {
this.authService.getUser().subscribe(
(user: User) => {
this.user = user;
this.userId = user.userId;
}
);
this.messageService.getMessages()
.subscribe(
(messages: Message[]) => {
this.messages = messages.filter(x => x.userId == this.userId);
//this.messages = messages; //this works perfect, but shows all users messages
}
);
}
}
Seems like your 1st API call(getUser method) is taking more time, it is getting finished after the getMessages method.
You should somehow make them dependent. You could convert getUser Observable to promise using toPromise method.
ngOnInit() {
this.authService.getUser()
.toPromise()
.then(
(user: User) => {
this.user = user;
this.userId = user.userId;
}
)
.then(() => {
this.messageService.getMessages()
.subscribe(
(messages: Message[]) => {
this.messages = messages.filter(x => x.userId == this.userId);
}
);
});
}
Change your code to this :
messages: Array<any> = [];
this.authService.getUser().subscribe(
(user: User) => {
this.user = user;
this.userId = user.userId;
this.messageService.getMessages()
.subscribe(
(messages: Message[]) => {
this.messages = messages.filter(x => x.userId === this.userId);
//this.messages = messages; //this works perfect, but shows all users messages
}
);
}
);
You just need to nest the other observables , so it will be called once the first finishes it calls.

Implementation of Yii2 Menu widget with nested set behavior

Can anyone help me in the implementation of nested set behavior https://github.com/creocoder/yii2-nested-sets with Yii2 Menu Widget?
Basically I want the hierarchical sidebar navigation menu like this http://www.surtex-instruments.co.uk/surgical/general-surgical-instruments
Thanks.
In your Model class define this functions:
public static function getTree($selected, $search)
{
$categories = Categories::find()->orderBy('lft')->asArray()->all();
$tree = [];
$index = 0;
foreach ($categories as $category) {
if ($category['parent_category_id'] === NULL) {
$tree[$index]['text'] = $category['category_' . Yii::$app->language];
if ($search) {
$tree[$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'search' => $search, 'string' => $category['category_' . Yii::$app->language]]);
} else {
$tree[$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'string' => $category['category_' . Yii::$app->language] ]);
}
$tree[$index]['id'] = $category['category_id'];
if ($selected) {
if ($selected['category_id'] == $category['category_id']) {
$tree[$index]['state']['selected'] = true;
}
if ($selected['lft'] >= $category['lft'] && $selected['rgt'] <= $category['rgt']) {
$tree[$index]['state']['expanded'] = true;
}
}
if ($category['lft'] + 1 != $category['rgt']) {
Categories::getNodes($tree[$index], $categories, $selected, $search);
}
$index++;
}
}
return $tree;
}
private static function getNodes(&$tree, $categories, $selected, $search)
{
$index = 0;
foreach ($categories as $category) {
if ($tree['id'] == $category['parent_category_id']) {
$tree['nodes'][$index]['text'] = $category['category_' . Yii::$app->language];
if ($search) {
$tree['nodes'][$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'search' => $search, 'string' => $category['category_' . Yii::$app->language]]);
} else {
$tree['nodes'][$index]['href'] = Url::to(['products', 'category' => $category['category_id'], 'string' => $category['category_' . Yii::$app->language]]);
}
$tree['nodes'][$index]['id'] = $category['category_id'];
if ($selected) {
if ($selected['category_id'] == $category['category_id']) {
$tree['nodes'][$index]['state']['selected'] = true;
}
if ($selected['lft'] >= $category['lft'] && $selected['rgt'] <= $category['rgt']) {
$tree['nodes'][$index]['state']['expanded'] = true;
}
}
if ($category['lft'] + 1 != $category['rgt']) {
Categories::getNodes($tree['nodes'][$index], $categories, $selected, $search);
}
$index++;
}
}
}
and use this extension execut/yii2-widget-bootstraptreeview
In the controller file get the menu like this:
public function actionProducts($category = false)
{
...
$data = Categories::getTree($category,'');
In your view file
<?php
...
use execut\widget\TreeView;
...
$onSelect = new JsExpression(<<<JS
function (undefined, item) {
window.location.href = item.href;
}
JS
);
?>
<?= TreeView::widget([
'data' => $data,
'template' => TreeView::TEMPLATE_SIMPLE,
'clientOptions' => [
'onNodeSelected' => $onSelect,
'onhoverColor' => "#fff",
'enableLinks' => true,
'borderColor' => '#fff',
'collapseIcon' => 'fa fa-angle-down',
'expandIcon' => 'fa fa-angle-right',
'levels' => 1,
'selectedBackColor' => '#fff',
'selectedColor' => '#2eaadc',
],
]); ?>

Should I bind html.dropdownlistfor in POST too in MVC 5?

This is my get
[Authorize]
public ActionResult SendMessage()
{
var model = new MessageModel();
var subjects = this.GetDistributionLists();
if (subjects != null) {
ViewBag.Subjects = new SelectList(subjects, "Value", "Text");
}
//Get patients list
var patients = this.GetPatientsList();
if (patients != null)
{
ViewBag.Patients = new SelectList(patients, "Value", "Text");
}
if (Request.IsAjaxRequest())
{
return PartialView("SendMessagePartial");
}
else
{
return View();
}
}
This is my post
[HttpPost]
[Authorize]
public ActionResult SendMessage(MessageModel model)
{
try {
if (ModelState.IsValid)
{
XmlDocument requestXml = XmlUtil.CreateRequestDocument("message", new
{
Message = model.MessageBody,
Type = model.Subject,
PatientID = model.PatientId,
RecipientID = model.RecipientId,
IsUrgent = model.IsUrgent ? "1" : "0"
});
//save message logic here
}
}
catch (Exception ex)
{
ModelState.AddModelError("ServerMessage", ex.Message);
}
if (Request.IsAjaxRequest())
{
return PartialView("SendMessagePartial", model);
}
else
{
return View(model);
}
}
This is my model
public class MessageModel
{
[DisplayName("RecipientId")]
public int RecipientId { get; set; }
[DisplayName("Sender")]
[StringLength(255)]
public string Sender { get; set; }
[DisplayName("SenderId")]
public int SenderId { get; set; }
[DisplayName("Message")]
[StringLength(4000)]
[Required]
public string MessageBody { get; set; }
[DisplayName("Subject")]
[StringLength(255)]
[Required]
public string Subject { get; set; }
[DisplayName("Patient")]
[Required]
public int PatientId { get; set; }
public bool IsUrgent { get; set; }
}
My View has a dropdownlistfor like
#Html.DropDownListFor(m => m.Subject, (SelectList)ViewBag.Subjects, new { #class = "form-control" })
When I GET, everything is fine.
When I POST, the data gets saved but in UI I get an error saying
The ViewData item that has the key 'Subject' is of type
'System.String' but must be of type 'IEnumerable'
Options are not posted back in the form. You will have to create them again:
[HttpPost]
[Authorize]
public ActionResult SendMessage(MessageModel model)
{
try {
if (ModelState.IsValid)
{
XmlDocument requestXml = XmlUtil.CreateRequestDocument("message", new
{
Message = model.MessageBody,
Type = model.Subject,
PatientID = model.PatientId,
RecipientID = model.RecipientId,
IsUrgent = model.IsUrgent ? "1" : "0"
});
//save message logic here
}
}
catch (Exception ex)
{
ModelState.AddModelError("ServerMessage", ex.Message);
}
var subjects = this.GetDistributionLists();
if (subjects != null) {
ViewBag.Subjects = new SelectList(subjects, "Value", "Text");
}
if (Request.IsAjaxRequest())
{
return PartialView("SendMessagePartial", model);
}
else
{
return View(model);
}
}

Resources