How to working with data from client side using Observables - telerik

I am using the Grid of Kendo (Angular 2) for Add/Edit/Delete a Row in the grid:
http://www.telerik.com/kendo-angular-ui/components/grid/editing/
In the original Code, the data is obtained from a rest service like this:
private fetch(action: string = "", data?: Product): Observable<Product[]> {
return this.jsonp
.get(`http://demos.telerik.com/kendo-ui/service/Products/${action}? callback=JSONP_CALLBACK${this.serializeModels(data)}`)
.map(response => response.json());
}
But, I want to work with a array for add/edit/delete rows in memory. Next, I want to do click in the button submit and send the data (with all my changes) to the server.
My solution for this is like this:
https://gist.github.com/joedayz/9e318a47d06a7a8c2170017eb133a87e
Overview:
I declare an array:
private view: Array = [{ProductID: 1, ProductName: "pelotas", Discontinued: undefined, UnitsInStock: 80}];
and override the fetch method like this:
private fetch(action: string = "", data?: Product): Observable<Product[]> {
/*return this.jsonp
.get(`http://demos.telerik.com/kendo-ui/service/Products/${action}?callback=JSONP_CALLBACK${this.serializeModels(data)}`)
.map(response => response.json());*/
debugger;
if(action=="create"){
var product : Product = new Product(-1, data.ProductName, data.Discontinued, data.UnitsInStock);
this.view.push(product);
}else if(action=="update"){
var indice = this.view.indexOf(data);
if(indice>=0)
this.view[indice] = (JSON.parse(JSON.stringify(data)));
}else if(action=="destroy"){
var indice = this.view.indexOf(data);
if(indice>=0)
this.view.splice(indice, 1);
}
return Observable.of(this.view);
}
My Question is: Exists some way of communicate the create/update/delete of items in my array of a simple or reactive form to my grid?

As you are using in-memory array you do not need to use Observables. The Grid component is already bound to the array, thus it is just necessary to manipulate the data. For example:
export class AppComponent {
public dataItem: Product;
#ViewChild(GridEditFormComponent) protected editFormComponent: GridEditFormComponent;
private view: Array<Product> = [{ ProductID: 1, ProductName: "pelotas", Discontinued: undefined, UnitsInStock: 80 }];
public onEdit(dataItem: any): void {
this.dataItem = Object.assign({}, dataItem);
}
public onCancel(): void {
this.dataItem = undefined;
}
public addProduct(): void {
this.editFormComponent.addProduct();
}
public onSave(product: Product): void {
if (product.ProductID === undefined) {
this.createProduct(product);
} else {
this.saveProducts(product);
}
}
public onDelete(e: Product): void {
this.deleteProduct(e);
}
public saveProducts(data: Product): void {
var index = this.view.findIndex(x => x.ProductID === data.ProductID);
if (index !== -1) {
this.view = [
...this.view.slice(0, index),
data,
...this.view.slice(index + 1)
];
}
}
public createProduct(data: Product): void {
this.view = [...this.view, data];
}
public deleteProduct(data: Product): void {
this.view = this.view.filter(x => x.ProductID !== data.ProductID);
}
}

Related

How to make ajax call on end of each block with infinite scrolling in ag-grid?

I am using ag-grid with angular 4.
I am using infinite scrolling as the rowModelType. But since my data is huge, we want to first call just 100 records in the first ajax call and when the scroll reaches the end, the next ajax call needs to be made with the next 100 records? How can i do this using ag-grid in angular 4.
This is my current code
table-component.ts
export class AssaysTableComponent implements OnInit{
//private rowData;
private gridApi;
private gridColumnApi;
private columnDefs;
private rowModelType;
private paginationPageSize;
private components;
private rowData: any[];
private cacheBlockSize;
private infiniteInitialRowCount;
allTableData : any[];
constructor(private http:HttpClient, private appServices:AppServices) {
this.columnDefs = [
{
headerName: "Date/Time",
field: "createdDate",
headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: true,
checkboxSelection: true,
width: 250,
cellRenderer: "loadingRenderer"
},
{headerName: 'Assay Name', field: 'assayName', width: 200},
{headerName: 'Samples', field: 'sampleCount', width: 100}
];
this.components = {
loadingRenderer: function(params) {
if (params.value !== undefined) {
return params.value;
} else {
return '<img src="../images/loading.gif">';
}
}
};
this.rowModelType = "infinite";
//this.paginationPageSize = 10;
this.cacheBlockSize = 10;
this.infiniteInitialRowCount = 1;
//this.rowData = this.appServices.assayData;
}
ngOnInit(){
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
//const allTableData:string[] = [];
//const apiCount = 0;
//apiCount++;
console.log("assayApiCall>>",this.appServices.assayApiCall);
const assaysObj = new Assays();
assaysObj.sortBy = 'CREATED_DATE';
assaysObj.sortOder = 'desc';
assaysObj.count = "50";
if(this.appServices.assayApiCall>0){
console.log("this.allTableData >> ",this.allTableData);
assaysObj.startEvalulationKey = {
}
}
this.appServices.downloadAssayFiles(assaysObj).subscribe(
(response) => {
if (response.length > 0) {
var dataSource = {
rowCount: null,
getRows: function (params) {
console.log("asking for " + params.startRow + " to " + params.endRow);
setTimeout(function () {
console.log("response>>",response);
if(this.allTableData == undefined){
this.allTableData = response;
}
else{
this.allTableData = this.allTableData.concat(response);
}
var rowsThisPage = response.slice(params.startRow, params.endRow);
var lastRow = -1;
if (response.length <= params.endRow) {
lastRow = response.length;
}
params.successCallback(rowsThisPage, lastRow);
}, 500);
}
}
params.api.setDatasource(dataSource);
this.appServices.setIsAssaysAvailable(true);
this.appServices.assayApiCall +=1;
}
else{
this.appServices.setIsAssaysAvailable(false)
}
}
)
}
}
I will need to call this.appServices.downloadAssayFiles(assaysObj) at the end of 100 rows again to get the next set of 100 rows.
Please suggest a method of doing this.
Edit 1:
private getRowData(startRow: number, endRow: number): Observable<any[]> {
var rowData =[];
const assaysObj = new Assays();
assaysObj.sortBy = 'CREATED_DATE';
assaysObj.sortOder = 'desc';
assaysObj.count = "10";
this.appServices.downloadAssayFiles(assaysObj).subscribe(
(response) => {
if (response.length > 0) {
console.log("response>>",response);
if(this.allTableData == undefined){
this.allTableData = response;
}
else{
rowData = response;
this.allTableData = this.allTableData.concat(response);
}
this.appServices.setIsAssaysAvailable(true);
}
else{
this.appServices.setIsAssaysAvailable(false)
}
console.log("rowdata>>",rowData);
});
return Observable.of(rowData);
}
onGridReady(params: any) {
console.log("onGridReady");
var dataSource = {
getRows: (params: IGetRowsParams) => {
this.info = "Getting datasource rows, start: " + params.startRow + ", end: " + params.endRow;
console.log(this.info);
this.getRowData(params.startRow, params.endRow)
.subscribe(data => params.successCallback(data));
}
};
params.api.setDatasource(dataSource);
}
Result 1 : The table is not loaded with the data. Also for some reason the service call this.appServices.downloadAssayFiles is being made thrice . Is there something wrong with my logic here.
There's an example of doing exactly this on the ag-grid site: https://www.ag-grid.com/javascript-grid-infinite-scrolling/.
How does your code currently act? It looks like you're modeling yours from the ag-grid docs page, but that you're getting all the data at once instead of getting only the chunks that you need.
Here's a stackblitz that I think does what you need. https://stackblitz.com/edit/ag-grid-infinite-scroll-example?file=src/app/app.component.ts
In general you want to make sure you have a service method that can retrieve just the correct chunk of your data. You seem to be setting the correct range of data to the grid in your code, but the issue is that you've already spent the effort of getting all of it.
Here's the relevant code from that stackblitz. getRowData is the service call that returns an observable of the records that the grid asks for. Then in your subscribe method for that observable, you supply that data to the grid.
private getRowData(startRow: number, endRow: number): Observable<any[]> {
// This is acting as a service call that will return just the
// data range that you're asking for. In your case, you'd probably
// call your http-based service which would also return an observable
// of your data.
var rowdata = [];
for (var i = startRow; i <= endRow; i++) {
rowdata.push({ one: "hello", two: "world", three: "Item " + i });
}
return Observable.of(rowdata);
}
onGridReady(params: any) {
console.log("onGridReady");
var datasource = {
getRows: (params: IGetRowsParams) => {
this.getRowData(params.startRow, params.endRow)
.subscribe(data => params.successCallback(data));
}
};
params.api.setDatasource(datasource);
}

ng2: reserve Original value if validation failed

I am trying to force the user to fill in the description when they update an item. It validates, shows error message when validation fails, stops running execution and doesn't update an item.
Please see the series of screenshot below:
However, my item is still updated even if the validation fails. It seems to me that since an object is reference in the memory, it's still updated even if it doesn't run updateTodo() method from the Todoservice.
Is it because I am just hardcoding my items just for the testing? I am very new to Angular and I don't want to implement webAPIs at this point yet.
I tried to use Object.assign({}, copy) in getTodoItem(id: number) to clone and decouple my todoItem from the list but the error message showing that it's not observable.
How can I preserve the values of Objects in the list if the validation fails? In real life application, Since we retrieve the data from the database (or webapi cache) whenever index/list component is navigated, this problem shouldn't occur. Is my assumption right?
todoService.ts
import { Itodo } from './todo'
const TodoItems: Itodo[] = [
{ todoId: 11, description: 'Silencer' },
{ todoId: 12, description: 'Centaur Warrunner' },
{ todoId: 13, description: 'Lycanthrope' },
{ todoId: 14, description: 'Sniper' },
{ todoId: 15, description: 'Lone Druid' }
]
#Injectable()
export class TodoService {
getTodoItems(): Observable<Itodo[]> {
return Observable.of(TodoItems);
}
getTodoItem(id: number): Observable<Itodo> {
return this.getTodoItems()
.map((items: Itodo[]) => items.find(p => p.todoId === id));
//let copy = this.getTodoItems()
// .map((items: Itodo[]) => items.find(p => p.todoId === id));
//return Object.assign({}, copy);
}
addNewTodo(model: Itodo): number {
return TodoItems.push(model); // return new length of an array
}
updateTodo(model: Itodo) : number {
let idx = TodoItems.indexOf(TodoItems.filter(f => f.todoId == model.todoId)[0]);
return TodoItems.splice(idx, 1, model).length; // return the count of affected item
}
}
todo-edit.component.ts -- EditItem() is the main
import { Subscription } from 'rxjs/Subscription';
import { Itodo } from './todo'
import { TodoService } from './todo.service';
#Component({
moduleId: module.id,
templateUrl: "todo-edit.component.html"
})
export class TodoEditComponent implements OnInit, OnDestroy {
todoModel: Itodo;
private sub: Subscription;
Message: string;
MessageType: number;
constructor(private _todoService: TodoService,
private _route: ActivatedRoute,
private _router: Router) {
}
ngOnInit(): void {
this.sub = this._route.params.subscribe(
params => {
let id = +params['id'];
this.getItem(id);
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
getItem(id: number) {
this._todoService.getTodoItem(id).subscribe(
item => this.todoModel = item,
error => this.Message = <any>error);
}
EditItem(): void {
this.todoModel.description = this.todoModel.description.trim();
if (!this.todoModel.description) {
this.Message = "Description must not be blank.";
this.MessageType = 2;
return;
}
console.log('valid: update now.');
let result = this._todoService.updateTodo(this.todoModel);
if (result > 0) {
this.Message = "An Item has been updated";
this.MessageType = 1;
}
else {
this.Message = "Error occured! Try again.";
this.MessageType = 2;
}
}
}
Working Solution
Object.assign it's the right method to use. I was using it wrongly in the service to clone it. You need to use it in your component, not in the service.
getItem(id: number) {
//Object.assign clone and decouple todoModel from the ArrayList
this._todoService.getTodoItem(id).subscribe(
item => this.todoModel = Object.assign({}, item),
error => this.Message = <any>error);
}
Validation does not prevent updating items, it just checks actual values for validity. You should create copy of object for editing to be able to rollback changes. You can use Object.assign({}, item) or JSON.parse(JSON.stringify(...)).

How can I automatically map a json object to fields based off a viewmodel mapped to fields?

I have a view that is loaded with a blank viewmodel initially. I want to populate that already rendered view with a json object (obtained view ajax post) that was based off the viewmodel for that view.
Is there a way of automatically doing this?
Is there a way of doing it in reverse? (fields to matching viewmodel json object)
The only way I am aware of taking data return from an ajax call and putting it in a field is manually
$('#TextField1').val(result.TextField1);
etc..
to send it back to the controller you can do
data: $('form').serialize(),
this will take all of the fields in that form and send them back to the controller
Ok it looks like this will suit my needs.
I need to follow a convention of naming containers the same name as their respective properties as well as putting a class on them to indicate that they contain subfields.
function MapJsonObjectToForm(obj, $container) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var $field = $container.find('#' + key);
if ($field.is('div')) {
MapJsonObjectToForm(obj[key], $field);
} else {
if (obj[key] == null) {
if ($field.hasClass('select2-offscreen')) {
$field.select2('val', '');
$field.select2().trigger('change');
} else {
$field.val("");
}
} else {
if ($field.hasClass('select2-offscreen')) {
$field.select2('val', obj[key]);
$field.select2().trigger('change');
} else {
$field.val(obj[key]);
}
}
}
}
}
}
function MapFormToJsonObject(containerid) {
var obj = {};
$('.dataitem').each(function () {
var exclude = "s2id";
if ($(this).attr("ID").substring(0, exclude.length) !== exclude) {
var parents = $(this).parents(".has-sub-fields");
if (parents.length > 0) {
obj = FindParents(obj, parents.get(), $(this).attr("ID"), $(this).val());
} else {
obj[$(this).attr("ID")] = $(this).val();
}
}
});
return obj;
}
function FindParents(obj, arr, id, value) {
if (arr.length == 0) {
obj[id] = value;
return obj;
}
var parentID = $(arr[arr.length - 1]).attr("ID");
arr.pop();
if (obj[parentID] == null) {
obj[parentID] = {};
}
obj[parentID] = FindParents(obj[parentID], arr, id, value);
return obj;
}

Can I display contents of Application or Cache objects using Glimpse in an MVC project?

The ASP.NET WebForms trace output has a section for Application State. Is it possible to see the same using Glimpse?
In my home controller's Index() method, I tried adding some test values, but I don't see the output in any of the Glimpse tabs.
ControllerContext.HttpContext.Application.Add("TEST1", "VALUE1");
ControllerContext.HttpContext.Cache.Insert("TEST2", "VALUE2");
I didn't see anything in the documentation either.
I don't think that there is an out-of-the-box support for this, but it would be trivial to write a plugin that will show this information.
For example to show everything that's stored in the ApplicationState you could write the following plugin:
[Glimpse.Core.Extensibility.GlimpsePluginAttribute]
public class ApplicationStateGlimpsePlugin : IGlimpsePlugin
{
public object GetData(HttpContextBase context)
{
var data = new List<object[]> { new[] { "Key", "Value" } };
foreach (string key in context.Application.Keys)
{
data.Add(new object[] { key, context.Application[key] });
}
return data;
}
public void SetupInit()
{
}
public string Name
{
get { return "ApplicationState"; }
}
}
and then you get the desired result:
and to list everything that's stored into the cache:
[Glimpse.Core.Extensibility.GlimpsePluginAttribute]
public class ApplicationCacheGlimpsePlugin : IGlimpsePlugin
{
public object GetData(HttpContextBase context)
{
var data = new List<object[]> { new[] { "Key", "Value" } };
foreach (DictionaryEntry item in context.Cache)
{
data.Add(new object[] { item.Key, item.Value });
}
return data;
}
public void SetupInit()
{
}
public string Name
{
get { return "ApplicationCache"; }
}
}

JqGrid Treegrid sorting issue

I hope you can help me, I have a structure like this:
- root A
-child_A1
-child_A1_1
-child_A1_2
-child_A1_3
-child_A2
-child_A2_1
-child_A2_2
-child_A2_3
- root B
- child_B1
-child_B1_1
-child_B1_2
-child_B1_3
But when I show the data in TreeGrid, it shows like this:
- root A
-child_A1
-child_A2
-child_A1_1
- root B
- child_B1
-child_B1_1
-child_B1_2
-child_B1_3
-child_A1_2
-child_A1_3
-child_A2_1
-child_A2_2
-child_A2_3
Anybody knows why..??? please help, I search information about this error but don`t have luck....
Here's my JavaScript:
<script type="text/javascript">
$(document).ready(function () {
var lastsel;
$(function () {
jQuery('#tree').jqGrid({
url: '/Ubicacion/TreeGrid/',
datatype: 'json',
height: 250,
colNames: ['Nombre', 'Descripcion'],
colModel: [
{ name: 'Nombre', index: 'Nombre', width: 100, sortable: true, editable: true, edittype: "text"},
{ name: 'Descripcion', index: 'Descripcion', width: 80, editable: true, edittype: "text" }
],
caption: 'Listado de Ubicaciones',
treeGridModel: 'adjacency',
sortname: 'Nombre',
loadonce: true,
height: 'auto',
width: '500',
pager: "#pager",
treeGrid: true,
ExpandColumn: 'Id',
ExpandColClick: true,
});
});
});
And here is the server side function that I used for generate json string:
public ActionResult TreeGrid(string sidx, string sord, int? page, int? rows)
{
List<Ubicacion> ubicacion = new List<Ubicacion>();
ubicacion = UbicacionRepository.GetAll().ToList<Ubicacion>();
int pageIndex = Convert.ToInt32(page) - 1;
int totalrecords = ubicacion.Count();
JsonResult json = new JsonResult();
json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
json.Data = new
{
sidx = "Nombre",
sord = "asc",
page = page,
records = totalrecords,
rows = (from ubi in ubicacion
select new
{
cell = new string[]
{
ubi.Nombre,
ubi.Descripcion,
ubi.Nivel.ToString(),
ubi.IdPadre == 0 ? "null" : ubi.IdPadre.ToString(),
ubi.Nivel < 2 ? "false" : "true",
"false",
"true"
}
})
};
return json;
}
And here's the json generated:
{"total":1,"page":null,"records":18,"rows":[
{"cell":["Parent A","ubicacion","0","null","false","false","true"]},
{"cell":["Child A1","ubicacion","1","1","false","false","true"]},
{"cell":["Child A2","ubicacion","1","1","false","false","true"]},
{"cell":["Child A1_1","ubicacion","2","2","true","false","true"]},
{"cell":["Parent B","ubicacion","0","null","false","false","true"]},
{"cell":["Child B1","ubicacion","1","5","false","false","true"]},
{"cell":["Child B1_1","ubicacion","2","6","true","false","true"]},
{"cell":["Child B1_2","ubicacion","2","6","true","false","true"]},
{"cell":["Child B1_3","ubicacion","2","6","true","false","true"]},
{"cell":["Child A1_2","ubicacion","2","2","true","false","true"]},
{"cell":["Child_A1_3","ubicacion","2","2","true","false","true"]},
{"cell":["Child A2_1","ubicacion","2","3","true","false","true"]},
{"cell":["Child A2_2","ubicacion","2","3","true","false","true"]},
{"cell":["Child A2_3","ubicacion","2","3","true","false","true"]}
]}
I got it! you need to order recursively the list, because it's rendering in the exact order you extracted from your db..
private static List<MENU> Listado = new List<MENU>();
private static List<MENU> lstOrdenada = new List<MENU>();
public List<MENU> MenuRecursivo()
{
//the whole list of MENU
Listado = (from m in db.MENU where m.men_eliminado == "N" select m).ToList();
// a list where we'll put the ordered items
lstOrdenada = new List<MENU>();
foreach (MENU item in Listado.Where(x => x.ID_MENU == x.id_menu_padre).ToList()) // in my case, only the root items match this condition
{
lstOrdenada.Add(item);
GMenuHijo(item.ID_MENU, ref lstOrdenada);
}
return lstOrdenada;
}
`
Then, for each root item, recursively find the next levels:
private static void GMenuHijo(int idPadre, ref List<MENU> lst)
{
List<MENU> listado2 = Listado.Where(x => x.id_menu_padre == idPadre && x.ID_MENU != x.id_menu_padre).ToList();
if (listado2.Count > 0)
{
foreach (MENU item in listado2)
{
lst.Add(item);
GMenuHijo(item.ID_MENU, ref lst);
}
}
}
I encountered this same problem. It would appear that jqGrid expects the data to be already sorted in the tree structure (i.e. it does not perform the sort at the client) but I may be wrong. Below are some extensions I created to perform a tree sort of a generic IEnumerable that contains objects with the specified ID and Parent ID properties. Objects will null in the Parent ID property are placed at the root.
public static class TreeSortExtensions
{
public static IEnumerable<T> OrderByTreeStructure<T>(
this IEnumerable<T> source,
string objectIDProperty,
string parentIDPropery)
{
IEnumerable<T> result = source;
if (!string.IsNullOrEmpty(objectIDProperty) && !string.IsNullOrEmpty(parentIDPropery))
{
result = source.GetChildrenOfTreeNode(null, objectIDProperty, parentIDPropery, true);
}
return result;
}
public static IEnumerable<T> GetChildrenOfTreeNode<T>(
this IEnumerable<T> source,
object parent,
string property,
string parentProperty,
bool recurse)
{
if (!string.IsNullOrEmpty(property) && !string.IsNullOrEmpty(parentProperty))
{
IEnumerable<T> children;
if (parent == null)
{
children = source.Where(x => x.GetPropertyValue(parentProperty) == null);
}
else
{
var parentIDValue = parent.GetPropertyValue(property);
children = source.Where(x => (x.GetPropertyValue(parentProperty) != null) &&
(x.GetPropertyValue(parentProperty).Equals(parentIDValue)));
}
foreach (T child in children)
{
yield return child;
if (recurse)
{
var grandChildren = source.GetChildrenOfTreeNode(child, property, parentProperty, true).ToArray();
foreach (T grandchild in grandChildren)
{
yield return grandchild;
}
}
}
}
}
public static object GetPropertyValue(this object obj, string property)
{
return obj.GetType().GetProperty(property).GetValue(obj, null);
}
}
Note that the "parent" argument is of type object and not T. This allows a null to be passed as the parent of root level objects.
Usage:
var result1 = someEnumerable.OrderByTreeStructure("SomeIDProperty", "SomeParentIDProperty");
var result2 = someDbContext.SomeTable.OrderByTreeStructure("ID", "ParentID");

Resources