I am using pdf lib to generate pdf and want put a dyanamic table in flutter - flutter

I am trying to add a dynamic table with a loop in pdf, but it is not working.
pdfLib.Table.fromTextArray(
context: context,
data: <List<String>>[
<String>[
'#',
'Product Name',
'HSN',
'Qty',
'Unit Price',
'MRP',
'Disc%',
'Disc Amnt',
'Taxable Amnt',
'SGST%',
'SGST Amnt',
'CGST%',
'CGST Amnt',
'Net Amnt'
],
_list(),
],
),
_list here is a function which returns list of strings but it is only able to add one row in table,
for (var i = 0; i < _items.length; i++) {
return <String>[
(i + 1).toString(),
_items[i].title,
'HSN',
_items[i].quantity.toString(),
_items[i].price.toString(),
_items[i].mrp.toString(),
_items[i].discount.toString(),
_item[i].gst,
_item[i].amount
_item[i].sGst,
_item[i].someitem,
_item[i].cGst,
_items[i].price,
_items[i].quantity];
}
Can someone please help me with this?
Thanks

_list needs to return a List<List<String>> as it need to return multiple string lists - one for each item. It can be written with List.map() like:
List<List<String>> _list() {
return _items
.map((item) => <String>[
item.title,
'HSN',
item.quantity.toString(),
// etc
])
.toList();
}
which maps each item into a string list, and returns the list of lists.
You need to change how you use that to use a spread operator ... so that the list of items is expanded into the overall list, like this:
var data = <List<String>>[
<String>[
'#',
'Product Name',
'HSN',
'Qty',
'Unit Price',
'MRP',
'Disc%',
'Disc Amnt',
'Taxable Amnt',
'SGST%',
'SGST Amnt',
'CGST%',
'CGST Amnt',
'Net Amnt'
],
..._list(),
];
}
Or, you could inline the whole operation with the for operator:
var data = <List<String>>[
<String>[
'#',
'Product Name',
'HSN',
'Qty',
'Unit Price',
'MRP',
'Disc%',
'Disc Amnt',
'Taxable Amnt',
'SGST%',
'SGST Amnt',
'CGST%',
'CGST Amnt',
'Net Amnt'
],
for (var item in _items)
<String>[
item.title,
'HSN',
item.quantity.toString(),
// etc
],
];
}

There are several ways to do it, I prefer to fill the List separately, like:
`List<List<String>> salidas = new List();
salidas.add(<String>['Title1','Title2', ... , 'Title n']);
for(var indice=0;indice<records.length;indice++) {
var recind = {
'field1': records[indice].Stringfield1,
'field2': records[indice].Stringfield2,
...
'fieldn': records[indice].Stringfieldn
};
salidas.add(recind);
}
...
fpdf.Table.fromTextArray(context: context,data: salidas),
`

// Generate Dynamic PDF
_generatePdfAndView(context) async {
final pw.Document pdf = pw.Document(deflate: zlib.encode);
pdf.addPage(
pw.MultiPage(
build: (context) => [
pw.Table.fromTextArray(
context: context,
data: <List<String>>[
// These will be your columns as Parameter X, Parameter Y etc.
<String>[
'Parameter',
'Price',
],
for (int i = 0; i < featureNames.length; i++)
<String>[
// ith element will go in ith column means
// featureNames[i] in 1st column
featureNames[i],
// featureValues[i] in 2nd column
featureValues[i].toString(),
],
],
),
],
),
);
}
Hope that will work for you.

Related

Yii2 ChartJs throws A non-numeric value encountered error at page

I have installed a 2amigos/yii2-chartjs-widget to my yii2 project via composer and after installing it I am trying to tun the example as shown in the documentation.
<?php
ChartJs::widget([
'type' => 'pie',
'id' => 'structurePie',
'options' => [
'height' => 200,
'width' => 400,
],
'data' => [
'radius' => "90%",
'labels' => ['Label 1', 'Label 2', 'Label 3'], // Your labels
'datasets' => [
[
'data' => ['35.6', '17.5', '46.9'], // Your dataset
'label' => '',
'backgroundColor' => [
'#ADC3FF',
'#FF9A9A',
'rgba(190, 124, 145, 0.8)'
],
'borderColor' => [
'#fff',
'#fff',
'#fff'
],
'borderWidth' => 1,
'hoverBorderColor'=>["#999","#999","#999"],
]
]
],
'clientOptions' => [
'legend' => [
'display' => false,
'position' => 'bottom',
'labels' => [
'fontSize' => 14,
'fontColor' => "#425062",
]
],
'tooltips' => [
'enabled' => true,
'intersect' => true
],
'hover' => [
'mode' => false
],
'maintainAspectRatio' => false,
],
'plugins' =>
new \yii\web\JsExpression('
[{
afterDatasetsDraw: function(chart, easing) {
var ctx = chart.ctx;
chart.data.datasets.forEach(function (dataset, i) {
var meta = chart.getDatasetMeta(i);
if (!meta.hidden) {
meta.data.forEach(function(element, index) {
// Draw the text in black, with the specified font
ctx.fillStyle = rgb(0, 0, 0);
var fontSize = 16;
var fontStyle = normal;
var fontFamily = Helvetica;
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
// Just naively convert to string for now
var dataString = dataset.data[index].toString()+'%';
// Make sure alignment settings are correct
ctx.textAlign = center;
ctx.textBaseline = middle;
var padding = 5;
var position = element.tooltipPosition();
ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);
});
}
});
}
}]')
])
?>
But when I refresh my page I am getting this error
A non-numeric value encountered
The error occurs at var dataString = dataset.data[index].toString()+'%';
I have been trying to solve this issue but I couldn't find anything
Any help would be highly appreciated.
Its because you are not escaping the quotes correctly you need to escape the single quotes in the statement
var dataString = dataset.data[index].toString()+' % ';
because your outer quotes in the new yii\db\Expression() are using single quotes to wrap the whole javascript, so change the line to
var dataString = dataset.data[index].toString()+\' % \';

Word addin inserting complex list structure with styling in a contentControl using office.js done or not?

I'm trying to insert a complex list structure into a contentControl in MS Word using the javascript API. The structure is build according to an object that contains nested arrays: Different items containing sub items that contain different properties. These items arrays can change in size so it needs to be generic. Maybe the Office.js API isn't really build for what I am trying to achieve and I should be using either insertHTML (with the structure build in HTML) or OOXML.
This is the structure I already build
The function that produced this:
import ContentControl = Word.ContentControl;
import {formatDate} from '../formatters';
let firstItem = true;
let listId;
export async function resolveItems(contentControl: ContentControl, data: Array<any>, t) {
Word.run( contentControl, async (context) => {
contentControl.load('paragraphs');
await context.sync();
for (const item of data) {
if (firstItem) {
const firstPara = contentControl.paragraphs.getFirst();
firstPara.insertText('ITEM (' + formatDate(item.date) + ')', 'Replace');
firstItem = false;
const contactList = firstPara.startNewList();
contactList.load('id');
await context.sync().catch((error) => {
console.log('Error: ' + error);
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
listId = contactList.id;
} else {
const otherItem = contentControl.insertParagraph('ITEM (' + formatDate(item.date) + ')', 'End');
otherItem.load(['isListItem']);
await context.sync();
otherItem.attachToList(listId, 0);
}
for (const subItem of item.subItems) {
let descriptionParaList = new Array();
let subItemPara = contentControl.insertParagraph(subItem.title + ' (' + formatDate(subItem.date) + ')', 'End');
subItemPara.load(['isListItem']);
await context.sync();
if (subItemPara.isListItem) {
subItemPara.listItem.level = 1;
} else {
subItemPara.attachToList(listId, 1);
}
let descriptions = subItem.descriptions;
for (const description of descriptions) {
let descriptionPara = contentControl.insertParagraph('', 'End');
descriptionPara.insertText(t(description.descriptionType) + ': ', 'Start').font.set({
italic: true
});
descriptionPara.insertText(description.description, 'End').font.set({
italic: false
});
descriptionPara.load(['isListItem', 'leftIndent']);
descriptionParaList.push(descriptionPara);
}
await context.sync();
for (const subItemPara of descriptionParaList) {
if (subItemPara.isListItem) {
subItemPara.detachFromList();
}
subItemPara.leftIndent = 72;
}
}
}
return context.sync().catch((error) => {
console.log('Error: ' + error);
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
});}
The data structure looks like this:
'LAST_5_Items': [
{
'date': '2019-03-14T14:51:29.506+01:00',
'type': 'ITEM',
'subItems': [
{
'date': '2019-03-14T14:51:29.506+01:00',
'title': 'SUBITEM 1',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
},
{
'date': '2019-03-14T14:51:29.506+01:00',
'title': 'SUBITEM 2',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
}
]
},
{
'date': '2019-03-14T14:16:26.220+01:00',
'type': 'ITEM',
'subItems': [
{
'date': '2019-03-14T14:16:26.220+01:00',
'title': 'SUBITEM 1',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
},
{
'date': '2019-03-14T14:16:26.220+01:00',
'title': 'SUBITEM 2',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
},
{
'descriptionType': 'SUBJECTIVE',
'description': 'Subjective 1',
'additionalInformation': ''
},
{
'descriptionType': 'SUBJECTIVE',
'description': 'Subjective 2',
'additionalInformation': ''
},
{
'descriptionType': 'OBJECTIVE',
'description': 'Objective 1',
'additionalInformation': ''
},
{
'descriptionType': 'OBJECTIVE',
'description': 'Objective 2',
'additionalInformation': ''
},
{
'descriptionType': 'EVALUATION',
'description': 'Evaluation',
'additionalInformation': ''
},
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
}
]
}
]
What I am trying to achieve is a template resolver. The addin will allow the user to put placeholder (ContentControls), tags like First name, Last name,... and the 5 last contacts (the one I am now describing) in the document and when he resolves the file it will fetch all the data needed and start replacing the ContentControls with this structured layout.
Yes the code works, but I highly doubt that the code is well structured with so many context.sync() calls. It is too slow and it is not usable.
I need so many context.sync() because I need the properties of the List ID and if a Paragraph belongs to a list.
Is there a better way to achieve what I am trying to achieve using the office.js API?
Ideally the queue should be synced once so the user would not see the content being added and changed in a very strange way like it is now behaving.
Thanks
There is a technique for avoiding having context.sync inside a loop. The basic idea is that you have two loops, with a single context.sync in between them. In the first loop, you create an array of objects. Each object contains a reference to one of the Office objects that you want to process (e.g., change, delete, etc.) It looks like paragraphs in your case. But the object has other properties. Each of them holds some item of data that you need to process the object. These other items may be properties of other Office objects or they may be other data. Also, either in your loop or just after it, you load all the properties you're going to need of all the Office objects in the objects in your array. (This is usually easier to do inside the loop.)
Then you have context.sync.
After the sync, you loop through the array of objects that you created before the context.sync. The code inside this second loop processes the first item in each object (which is the Office object that you want to process) using the other properties of the object you created.
Here are a couple of examples of this technique:
My answer to this StackOverflow question: Document not in sync after replace text.
This code sample:
https://github.com/OfficeDev/Word-Add-in-Angular2-StyleChecker/blob/master/app/services/word-document/word.document.service.js.

CKEditor Link dialog modification

I am trying to add a drop down to CKEditor's link dialog. When selected, the drop down should insert corresponding class name to the link.
CKEDITOR.on( 'dialogDefinition', function( ev ) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if ( dialogName == 'link' ) {
var infoTab = dialogDefinition.getContents( 'info' );
infoTab.add({
type: 'select',
label: 'Display link as a button',
id: 'buttonType',
'default': '',
items: [
['- Not button -', ''],
['Button one', 'btn-primary'],
['Button two', 'btn-success'],
['Button three', 'btn-danger']
],
commit: function(data) {
data.className = this.getValue();
}
});
}
});
I have a feeling commit function is not doing the job, but cannot figure out how to make it work. I saw a code that almost does the same thing as I want at http://www.lxg.de/code/simplify-ckeditors-link-dialog. I tried it and it does not work either.
I am using CKEditor 4.3.2.
I appreciate your help in advance.
If you console.log the data object in link dialog's onOk, you'll find quite a different hierarchy. Element classes are in data.advanced.advCSSClasses. But even if you decide to override (or extend) the value of this property in your commit, your string will be nuked by the original commit of advCSSClasses input field ("Advanced" tab) anyway. So the approach got to be a little bit different:
Always store the value of the select in data.
Override commit of advCSSClasses input field to consider stored value.
Remember to execute the original commit of advCSSClasses input.
Here we go:
CKEDITOR.on( 'dialogDefinition', function( ev ) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if ( dialogName == 'link' ) {
var infoTab = dialogDefinition.getContents( 'info' ),
advTab = dialogDefinition.getContents( 'advanced' ),
advCSSClasses = advTab.get( 'advCSSClasses' );
infoTab.add( {
type: 'select',
label: 'Display link as a button',
id: 'buttonType',
'default': '',
items: [
['- Not button -', ''],
['Button one', 'btn-primary'],
['Button two', 'btn-success'],
['Button three', 'btn-danger']
],
commit: function( data ) {
data.buttonType = this.getValue();
}
});
var orgAdvCSSClassesCommit = advCSSClasses.commit;
advCSSClasses.commit = function( data ) {
orgAdvCSSClassesCommit.apply( this, arguments );
if ( data.buttonType && data.advanced.advCSSClasses.indexOf( data.buttonType ) == -1 )
data.advanced.advCSSClasses += ' ' + data.buttonType;
};
}
});
Now you got to only write a setup function which will detect whether one of your button classes is present to set a proper value of your select field once the dialog is open.

Yii CJuiAutoComplete data from Ajax request does not display list choice

Autocomplete does not display data from sourceUrl;
Controller
public function actionTestAutoComplete() {
$r = array(
array("label"=>"Test 1", "value"=>"Test 1") ,
array("label"=>"Test 2", "value"=>"Test 2") ,
array("label"=>"Test 3", "value"=>"Test 3") ,
array("label"=>"Test 4", "value"=>"Test 4") ,
);
echo CJSON::encode($r);
}
View
$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
'name' => 'test_autocomplete',
'source'=>$this->createUrl("testAutocomplete"),
'value' => "",
'options' => array(
'minChars'=>1,
'autoFill'=>false,
'focus'=> 'js:function( event, ui ) {
$( "#test_autocomplete" ).val( ui.item.label );
return false;
}',
'select'=>'js:function( event, ui ) {
return false;
}'
),
'htmlOptions'=>array( 'autocomplete'=>'off'),
));
The action testAutocomplete works in debug, but the autocomplete does not display data from ajax.
The code bellow work fine.
$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
'name' => 'test_autocomplete',
'source'=>array(
array('label'=>'test 1', 'value'=>'teste 1'),
array('label'=>'test 2', 'value'=>'teste 2'),
array('label'=>'test 3', 'value'=>'teste 3'),
),
'value' => "",
'options' => array(
'minChars'=>1,
'autoFill'=>false,
'focus'=> 'js:function( event, ui ) {
$( "#test_autocomplete" ).val( ui.item.label );
return false;
}',
'select'=>'js:function( event, ui ) {
return false;
}'
),
'htmlOptions'=>array( 'autocomplete'=>'off'),
));
Please, I need help,
Thanks
Try using sourceURL instead source option.
Try this. I have tested this and it works fine.
On view side code...
<?php $this->widget('zii.widgets.jui.CJuiAutoComplete', array(
'name' => 'test_autocomplete',
'source'=>$this->createUrl('Controller class name here/AutoCompleteLookup'), // always define the correct path in Url..
'value' => "",
'options' => array(
'minChars'=>1,
'autoFill'=>false,
'focus'=> 'js:function( event, ui ) {
$( "#test_autocomplete" ).val( ui.item.label );
return false;
}',
'select'=>'js:function( event, ui ) {
return false;
}'
),
'htmlOptions'=>array( 'autocomplete'=>'off'),
)); ?>
The rest of your code is OK, but here were some mistakes which I have fixed.

cakePHP + extjs row editor and REST

I've implemented REST routing in cakePHP to properly route REST style requests to the proper methods in my controller.
This is what I've added to my routes.php
Router::mapResources(array('object_fields'));
This properly routes the REST requests to my index/add/edit/delete methods inside my controller.
In my EXTJS grid I am using the row editor with a restful store to achieve CRUD behavior.
Here is the code for my grid
myapp.object_field_grid = Ext.extend(Ext.grid.GridPanel, {
closable: true,
stripeRows: true,
frame: true,
viewConfig: {
forceFit: true
},
editor: new Ext.ux.grid.RowEditor({
saveText: 'Update',
}),
onAdd : function(btn, ev){
var u = new this.store.recordType({
name : '',
type: '',
});
this.editor.stopEditing();
this.store.insert(0, u);
this.editor.startEditing(0);
},
onDelete : function(){
},
initComponent: function() {
var proxy = new Ext.data.HttpProxy({
url: 'object_fields/',
});
var reader = new Ext.data.JsonReader({
totalProperty: 'totalCount',
successProperty: 'success',
idProperty: 'id',
root: 'data',
messageProperty: 'message'
}, [
{name: 'id'},
{name: 'name', allowBlank: false},
{name: 'type', allowBlank: false},
]);
var writer = new Ext.data.JsonWriter({
encode: false,
});
var store = new Ext.data.Store({
baseParams: {id: this.object_id},
id: 'object_fields',
restful: true,
proxy: proxy,
reader: reader,
writer: writer,
});
store.load();
var object_field_columns = [
// {header: "id", width: 250, sortable: true, dataIndex: 'id', editor: new Ext.form.TextField({})},
{header: "name", width: 250, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({})},
{header: "type", width: 250, sortable: true, dataIndex: 'type', editor: new Ext.form.ComboBox({editable: false, store:['STRING', 'NUMBER']})},
];
var config = {
columns: object_field_columns,
store: store,
plugins: [this.editor],
//autoHeight: true,
height: 200,
tbar: [{
text: 'Add',
iconCls: 'silk-add',
handler: this.onAdd,
scope: this,
}, '-', {
text: 'Delete',
iconCls: 'silk-delete',
handler: this.onDelete,
scope: this,
}, '-'],
}
Ext.apply(this, Ext.apply(this.initialConfig, config));
myapp.object_field_grid.superclass.initComponent.apply(this, arguments);
},
onRender: function() {
this.store.load();
myapp.object_field_grid.superclass.onRender.apply(this, arguments);
}
});
Ext.reg('object_field_grid', myapp.object_field_grid); // register xtype
My GET/POST requests are being properly routed to my index/add methods inside my controller and I am able to easily retrieve the paramaters that I pass it in the request.
My problem is with the update functionality PUT request. The PUT request does get successfully routed to my edit method inside the controller.
This is what the request looks like in firebug
http://server.local/object_fields/20
JSON
data
Object { name="test7777777777", more...}
id
"18"
Source
{"id":"18","data":{"name":"test7777777777","id":"20"}}
Inside my edit method I'm not receiving my array that I passed through the PUT request.
When I dump $this->params inside my edit method this is what is in the array.
([id] => 20
[named] => Array
(
)
[pass] => Array
(
[0] => 20
)
[controller] => object_fields
[action] => edit
[[method]] => PUT
[plugin] =>
[url] => Array
(
[ext] => html
[url] => object_fields/20
)
[form] => Array
(
)
[isAjax] => 1
)
How can I properly receive my array through the PUT request inside my edit method?
UPDATE:
I am able to retrieve my array using the following code inside the edit method
function edit($id){
$this->autoRender = false;
echo 'edit';
$raw = '';
$httpContent = fopen('php://input', 'r');
while ($kb = fread($httpContent, 1024)) {
$raw .= $kb;
}
fclose($httpContent);
$params = array();
parse_str($raw, $params);
print_r($params);
}
The question is now why does cakePHP not do this automaticly?
put this in your app_controller.php:
public function beforeFilter() {
if (
isset($this->RequestHandler) &&
$this->RequestHandler->requestedWith('json') &&
(
$this->RequestHandler->isPost() ||
$this->RequestHandler->isPut()
)
) {
$jsonData = json_decode(utf8_encode(trim(file_get_contents('php://input'))), true);
if (is_array($jsonData)) {
$this->data = $jsonData;
unset($jsonData);
}
}
}