How to add button or images to dojo grid - json

I have a dojo grid with a json datastore (mysql resultset converted into json format). Currently my grid show 5 columns as shown below in the figure:
I have column named 'Action'. The rows under this 'Action' column should contain buttons or images(edit icon, delete icon) with hyperlinks such as edit.php?id=1 for edit, or delete.php?id=1 for delete.
Here is my dojo grid code:
<span dojoType="dojo.data.ItemFileReadStore" data-dojo-id="studentsStore" url="http://localhost/newsmis/public/studentManagement/student/fetchdata/data/1"></span>
<table dojoType="dojox.grid.DataGrid" id="studentsGrid" data-dojo-id="studentsGrid" columnReordering="true" sortFields="['idstudents','first_name','middle_name','last_name']" store="studentsStore" clientSort="true" selectionMode="single" rowHeight="25" noDataMessage="<span class='dojoxGridNoData'>No students found</span>">
<thead>
<tr>
<th field="idstudents" width="20%">Student ID</th>
<th field="first_name" width="20%">First Name</th>
<th field="middle_name" width="20%">Middle Name</th>
<th field="last_name" width="20%">Last Name</th>
<th field="action" width="20%">Action</th>
</tr>
</thead>
</table>
My json data format is
{"identifier":"idstudents","items":[{"idstudents":"11","first_name":"Pradip","middle_name":"Maan","last_name":"Chitrakar"}]}
How can i do it? Please suggest me some ideas

The one way I know is, that defining formatting method for that column in grid structure. So instead of defining the structure of the grid declaratively, define in JavaScript object like below
var structure = [
{
name: "First Name",
field: "first_name"
},
{
name: "Action",
field: "_item",
formatter: function(item){
var btn = new dijit.form.Button({
label: "Edit"
});
return btn;
}
}
]
and set this structure to the grid
<table dojoType="dojox.grid.DataGrid" id="studentsGrid" data-dojo-id="studentsGrid" columnReordering="true" sortFields="['idstudents','first_name','middle_name','last_name']" store="studentsStore" clientSort="true" selectionMode="single" rowHeight="25" noDataMessage="<span class='dojoxGridNoData'>No students found</span>" structure=structure >

Here is the working example,
Image in dojo Grid

As documentation of dojox.grid.DataGrid stays:
Beginning with Dojo 1.7, you should use dgrid or gridx, next-generation grid components that take full advantage of modern browsers and object stores.
piece of code example:
columns: [
{
field: "id",
label: "ID"
},
{
field: "name",
label: "Name"
},
{
field: "options",
label: "Options",
renderCell: function (obj) {
var cellContent = domConstruct.create("div",{});
var btn = new Button({
label: "Cell " + obj.id,
name: "idBtn"
})
btn.placeAt(cellContent);
on(btn, "click", function (evt) {
console.log(obj);
});
return cellContent;
}
}
]
This is JSfiddle example how to do it in dgrid by using function renderCell in column properties of dgrid.
renderCell(object, value, node, options) - An optional function that will be called to render the value into the target cell. object refers to the record from the grid’s store for the row, and value refers to the specific value for the current cell (which may have been modified by the column definition’s get function). node refers to the table cell that will be placed in the grid if nothing is returned by renderCell; if renderCell returns a node, that returned node will be placed in the grid instead. (Note: if formatter is specified, renderCell is ignored.)

If you don't want a button, but only an image icon, you can return a span-element from the formatter function like this:
formatter: function(value) {
var myValueClass = "dijitNoValue";
...
myValueClass = "ValueClass1";
...
return "<span class='dijitReset dijitInline dijitIcon " + myValueClass + "'></span>";
}
An css class has to be defined like
.ValueClass1 {
background-image: url('val_image1.png');
background-repeat: no-repeat;
width: 18px;
height: 18px;
text-align: center;
background-position: 1px;
}

Related

How can I create a table of variable height in which every single cell contains an input field?

As I said in the title, I need to create a table in which every cell has a numeric input. So I created a component and I want to capture in the ts file what the user inserted in every cell (I also need to know in which cell he inserted the data too). In addition, the table should have a scalable number of rows(I need to populate the table with data from db, so please let me know if there is a way to precompile it)
Here I have added the html I tried even though it's not enough to do what I want to do.
<form #login="ngForm" (ngSubmit)="submit(login.value)" novalidate>
<table class="rtable">
<tr>
<th></th>
<th *ngFor="let column of months">
{{column}}
</th>
</tr>
<tr *ngFor="let row of FigProfs">
{{row.nome}}
<td *ngFor="let column of mesi">
<input type="number" #{{column}}_{{row}}="ngModel" required name="{{column}}_{{row}}" class="formControl" ngModel>
</td>
</tr>
</table>
<button class="btnsubmit" type="submit">GO</button>
This table has months in the heading and some characteristics on the left side. I need to let the user edit the cell of the table and I have to bring this information to my ts code, example:
"january/characteristic x -> data written from the user " ;
so I want to have in my ts all the data that I inserted down here (the heigth of the table is variable so I cannot insert form controls for the many inputs I have in the pic rn)
I know this code is probably completely wrong but I don't know how to do it in an easier way.
Thank you in advice for your help, I really appreciate it.
Well done. Thank you for adding some code. For reference, I really just used information from Angular's Reactive Forms below.
I did want to re-use some of your sample code, but unfortunately couldn't get it to adapt in a logical way. But you should be able to use the below to get it adapted as you need it.
My assumption is that you are using the Angular CLI to generate your components, and thus your .ts and .html are already referenced and created properly. In your something.component.ts, you will need to add something similar to:
import { FormGroup, FormControl } from '#angular/forms';
import { Component, OnInit } from '#angular/core';
//Create an interface for the data
interface testIntf {
index: string,
fname: string,
lname: string,
}
...skipping the existing stuff...
export class SomethingComponent implements OnInit {
//Some sample data for headings
headings = ["Index", "First name", "Surname"];
//Some sample data for the form to be pre-filled. Note the use of the interface. Going forward you will likely import your data using Observables.
dataArray: testIntf[] = [
{"index": "1", "fname": "Test", "lname": "One"},
{"index": "2", "fname": "Tester", "lname": "Two"},
{"index": "3", "fname": "", "lname": ""},
{"index": "4", "fname": "", "lname": ""},
]
//Your form related stuff!!
//Note where this matches in the html
//Create an empty form
dataForm = new FormGroup({});
//Note that ngOnInit must be imported and implemented above
ngOnInit(): void {
this.generateControls(this.dataArray);
}
//Add controls based on number of entries in array
generateControls(x: testIntf[]) {
var y = x.length
for (let i = 0; i < y; i++){
var z = x[i].index;
this.dataForm.addControl("firstName"+z, new FormControl);
this.dataForm.addControl("lastName"+z, new FormControl);
}
}
//Getting your form data when submitted
onSubmit() {
console.log(this.dataForm.value); //Push the form data into console.log of your browser
}
}
And now in your something.component.html:
<form [formGroup]="dataForm" (ngSubmit)="onSubmit()">
<table class="rtable">
<tr>
<th *ngFor="let column of headings"> <!--Processs the "headings" array in the ts file-->
{{column}}
</th>
</tr>
<tr *ngFor="let row of dataArray">
<td>
{{row.index}}
</td>
<td>
<input id="first-name{{row.index}}" type="text" formControlName="firstName{{row.index}}" value="{{row.fname}}">
</td>
<td>
<input id="last-name{{row.index}}" type="text" formControlName="lastName{{row.index}}" value="{{row.lname}}">
</td>
</tr>
</table>
<button type="submit">Submit</button>
</form>
<p>Form Status: {{ dataForm.status }}</p> <!--Echo form data locally-->>
The information from your database can easily replace the "dataArray". Just update your headings, and add the necessary extra input fields to "dataForm".
Once you are comfortable with that, I would encourage you to look at Angular's Material for tables.
EDIT
Going forward, should you want to add more fields, you would edit the following (I have used "telNo" as an example):
Add extra fields to the form in something.component.ts:
generateControls(x: testIntf[]) {
var y = x.length
for (let i = 0; i < y; i++){
var z = x[i].index;
this.dataForm.addControl("firstName"+z, new FormControl);
this.dataForm.addControl("lastName"+z, new FormControl);
this.dataForm.addControl("telNo"+z, new FormControl);
}
}
Add extra form field in your table in something.component.html:
<td>
<input id="tel-no{{row.index}}" type="text" formControlName="telNo{{row.index}}" value="{{row.telno}}">
</td>
Obviously your dataArray will also need to be updated to have a "telno" field:
{"index": "1", "fname": "Test", "lname": "One", "telno": "1234587"},
And do not forget to update your interface:
interface testIntf {
index: string,
fname: string,
lname: string,
telno: string,
}

Rendering TRs in Vue using a custom component

While I can render an HTML table fine using v-for and inline mustache syntax, I cannot achieve the same result using a component.
Vue / the browser removes the wrapping TABLE tag and inserts TRs outside a TABLE context, so they do not render properly:
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Vue table rows inline (works)</h2>
<table border="1">
<tr>
<th>Name</th>
<th>Value</th>
</tr>
<tr v-for="(row, index) in mydata">
<td>{{row.name}}</td>
<td>{{row.value}}</td>
</tr>
</table>
<h2>Vue table rows using component (broken)</h2>
<table border="1">
<tr>
<th>Name</th>
<th>Value</th>
</tr>
<my-row v-for="(row, index) in mydata" :key="row.name" :name="row.name" :val="row.value"></my-row>
</table>
</div>
<script>
Vue.component('my-row', {
props: ['name', 'val'],
template: '<tr><td>{{name}}</td><td>{{val}}</td></tr>'
})
new Vue({
el: "#app",
data: {
mydata: [{
name: "A",
value: 1
},
{
name: "B",
value: 2
},
{
name: "C",
value: 3
}
]
}
})
</script>
You can see this also at https://jsfiddle.net/MCAU/eywraw8t/128217/
What do I need to do to get the component version to work? (Adding a TBODY doesn't make any difference.)
Oh I have now found https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats which explains that TRs are a special case and require the following syntax instead:
<tr is="my-row" v-for="(row, index) in mydata" :key="row.name" :name="row.name" :val="row.value"></tr>
using functionnal component as suggested in : Vue js error: Component template should contain exactly one root element may do the trick.
copy/paste here :
if, for any reasons, you don't want to add a wrapper (in my first case it was for <tr/> components), you can use a functionnal component.
Instead of having a single components/MyCompo.vue you will have few files in a components/MyCompo folder :
components/MyCompo/index.js
components/MyCompo/File.vue
components/MyCompo/Avatar.vue
With this structure, the way you call your component won't change.
components/MyCompo/index.js file content :
import File from './File';
import Avatar from './Avatar';
const commonSort=(a,b)=>b-a;
export default {
functional: true,
name: 'MyCompo',
props: [ 'someProp', 'plopProp' ],
render(createElement, context) {
return [
createElement( File, { props: Object.assign({light: true, sort: commonSort},context.props) } ),
createElement( Avatar, { props: Object.assign({light: false, sort: commonSort},context.props) } )
];
}
};
And if you have some function or data used in both templates, passed them as properties and that's it !
I let you imagine building list of components and so much features with this pattern.

Angular ng-repeat with nested json objects?

I have a JSON object, represented as such:
{
"orders" : [
{
"ordernum" : "PRAAA000000177800601",
"buyer" : "Donna Heywood"
"parcels" : [
{
"upid" : "UPID567890123456",
"tpid" : "TPID789456789485"
},
{
"upid" : "UPID586905486090",
"tpid" : "TPID343454645455"
}
]
},
{
"ordernum" : "ORAAA000000367567345",
"buyer" : "Melanie Daniels"
"parcels" : [
{
"upid" : "UPID456547347776",
"tpid" : "TPID645896579688"
},
{
"upid" : "UPID768577673366",
"tpid" : "TPID784574333345"
}
]
}
]
}
I need to do a repeater on the second level of this, a list of the "upid" numbers.
I know already how to get the top level
<li ng-repeat="o in orders">{{o.ordernum}}</li>
But I am unclear on the sequence to loop a level down. For example, this is wrong:
<li ng-repeat="p in orders.parcels">{{p.upid}}</li>
I also know how to nest repeaters to get this, but in this case i don't need to display the top level at all.
CLARIFICATION
The goal here is to have one list with the 4 "upid" numbers (there are 2 for each parcel, and there are 2 parcels in the order).
Actually its same answer of #sylwester. The better way to put it in filter. And you can reuse it by passing propertyName parameter.
In your case we passed parcels
JS
myApp.filter('createarray', function () {
return function (value, propertyName) {
var arrayList = [];
angular.forEach(value, function (val) {
angular.forEach(val[propertyName], function (v) {
arrayList.push(v)
});
});
return arrayList;
}
});
HTML
<ul>
<li ng-repeat="o in ordersList.orders | createarray: 'parcels'">{{o.upid}}</li>
</ul>
Here is working Fiddle
You can just create new array 'parcels' like in demo below:
var app = angular.module('app', []);
app.controller('homeCtrl', function($scope) {
$scope.data = {
"orders": [{
"ordernum": "PRAAA000000177800601",
"buyer": "Donna Heywood",
"parcels": [{
"upid": "UPID567890123456",
"tpid": "TPID789456789485"
}, {
"upid": "UPID586905486090",
"tpid": "TPID343454645455"
}]
}, {
"ordernum": "ORAAA000000367567345",
"buyer": "Melanie Daniels",
"parcels": [{
"upid": "UPID456547347776",
"tpid": "TPID645896579688"
}, {
"upid": "UPID768577673366",
"tpid": "TPID784574333345"
}]
}]
};
$scope.parcels = [];
angular.forEach($scope.data.orders, function(order) {
angular.forEach(order.parcels, function(parcel) {
$scope.parcels.push(parcel)
})
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="homeCtrl">
<ul>
<li ng-repeat="o in parcels">{{o.upid}}</li>
</ul>
</div>
</div>
Seems like you just need a double-nested for loop -
<ul>
<div ng-repeat="o in orders">
<li ng-repeat="p in o.parcels">{{p.upid}}</li>
</div>
</ul>
The HTML might be a little ugly here, but I'm not sure what exactly you are going for. Alternatively you could just create a new array of the parcels via mapping.
Searching a lot for nice and simple solution for iterating dynamically. I came up with this
JAVASCRIPT (angular): a person is an example of nested object. the is_object function will be use in the HTML view.
$scope.person = {
"name": "john",
"properties": {
"age": 25,
"sex": "m"
},
"salary": 1000
}
// helper method to check if a field is a nested object
$scope.is_object = function (something) {
return typeof (something) == 'object' ? true : false;
};
HTML: define a template for simple table. the 1st TD is the key which is displayed. another TD (2 or 3, but never both) will be show the value if its not an object (number / string), OR loop again if its an object.
<table border="1">
<tr ng-repeat="(k,v) in person">
<td> {{ k }} </td>
<td ng-if="is_object(v) == false"> {{ v }} </td>
<td ng-if="is_object(v)">
<table border="1">
<tr ng-repeat="(k2,v2) in v">
<td> {{ k2 }} </td>
<td> {{ v2 }} </td>
</tr>
</table>
</td>
</tr>
</table>
The reason that <li ng-repeat="p in orders.parcels">{{p.upid}}</li> does not work the way you expect is because the parcels array is an object inside each individual order in your order array, i.e. it is not an object of the orders array itself.
If your orders array is defined on the $scope of a controller, then you create the array on the $scope variable:
$scope.allParcels = $scope.orders
.map(function (elem) {
return elem.parcels;
}) // get an array where each element is an array of parcels.
.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue);
}); // concat each array of parcels into a single array of parcels
then on the template, you can use <li ng-repeat='p in allParcels'>{{p.upid}}</li>
If, however, you do not want to place the array on the $scope, I believe you can do something similar to this:
<li ng-repeat="p in orders
.map(function (elem) {
return elem.parcels;
})
.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue);
})">{{p.upid}}</li>
although I'm not 100% sure that Angular will evaluate the .map/.reduce in the ng-repeat expression (also having an array generated this way in an ng-repeat is ill-advised since angular would have to constantly generate a new array via map/reduce on each $digest cycle).

tablesorter select box with a custom match function

I have an HTML table with a column, in which an unordered list of strings is present. I am using tablesorter with the filter plugin to interact with the list. I want each string in all cells of the column to be present in a select filter in the header of the table. If the user selects an option, those rows should be displayed, where this string appears in the unordered list of the particular column cell.
Let me give you an example. I have the following list:
<table>
<thead>
<tr>
<th>name</th>
<th>profession</th>
</tr>
</thead>
<tbody>
<tr>
<td>Carl</td>
<td>
<ul>
<li>Builder</li>
<li>Professor</li>
</ul>
</td>
</tr>
<tr>
<td>Lisa</td>
<td>
<ul>
<li>Programmer</li>
<li>Professor</li>
</ul>
</td>
</tr>
</tbody>
</table>
In the table header, I want tablesorter to display a select box with the three professions. If, say, professor is selected, both rows should be shown. If programmer is selected, only Lisa would appear. How would I reach that?
Use the filter_functions widget option as follows (demo):
$(function () {
$('table').tablesorter({
theme: 'blue',
widgets: ['filter'],
widgetOptions: {
filter_functions: {
1: {
"Programmer": function (e, n) { return /programmer/.test(n); },
"Professor" : function (e, n) { return /professor/.test(n); },
"Builder" : function (e, n) { return /builder/.test(n); }
}
}
}
});
});
Update: If you don't want to hardcode because the professions vary, then you can use the filter_selectSource option to grab all of the column text, extract out each item from the list then return it as an array (demo):
HTML (make sure to include these class names)
<th class="filter-select filter-match">profession</th>
Script
$(function () {
$('table').tablesorter({
theme: 'blue',
widgets: ['filter'],
widgetOptions: {
filter_selectSource: function (table, column, onlyAvail) {
// get an array of all table cell contents for a table column
var arry = [],
array = $.tablesorter.filter.getOptions(table, column, onlyAvail);
// split content if multiple professions are encountered
$.each( array, function(i, n){
// look for carriage returns; split into two separate professions
if (/\n/.test(n)) {
n = n.split(/\n/);
}
arry.push(n);
});
// flatten array
return $.map(arry, function(n){ return n; });
}
}
});
});

Multiple call to onselect from select element with Angular ng-repeat

Im using an HTML select element to display an entity's field that has enumerated values in it.
Within the page, I have multiple fields for the entity, therefore multiple select elements.
Im using an Angular ng-repeat to display each field, by creating a select within the ng-repeat on my table row. I want to capture the "onselect" event when an item is selected from any of the drop downs, the trouble is, whenever the user selects a value in one drop down, the event fires for all drop downs on the page.
My html:
<div ng-app>
<div ng:controller="TodoCtrl">
<table>
<tr ng-repeat="prop in customForm">
<td>{{prop.legend}}</td>
<td>
<select ng-name="prop.name" ng-model="entity[prop.name]"
ng-options="val as val for val in prop.enumeratedValues"
onselect="{{fireSelectEvent(prop.name)}}"></select>
</td>
</tr>
<tr><td>Calls Made</td></tr>
<tr ng-repeat="call in callLog">
<td>{{call}}</td>
</tr>
</table>
</div>
</div>
My Controller:
//'use strict';
function TodoCtrl($scope) {
$scope.customForm =
[
{name:"aval", legend: "A Value", enumeratedValues: ["1","2","3"], editable: true},
{name:"bval", legend: "B Value", enumeratedValues: ["4","5","6"], editable: true},
{name:"cval", legend: "C Value", enumeratedValues: ["7","8","9"], editable: true}
];
$scope.entity = {};
$scope.callLog = [];
$scope.fireSelectEvent = function( propName )
{
console.log("Prop=" + propName + " value=" + $scope.entity[propName]);
$scope.callLog.push( propName );
}
}
My fiddle:
http://jsfiddle.net/utgwG/
Good luck. We're all counting on you.
What is the onselect attribute? You're seeing fireSelectEvent fire that many times, because you've bound the onselect attribute using Angular interpolation.
What I'm guessing you want to do is: ng-change="fireSelectEvent(prop.name)"
Your updated fiddle: http://jsfiddle.net/utgwG/2/