PHPOffice/PHPPresentation (PowerPoint) how to put a chart (createChartShape) inside a table cell? - powerpoint

By following the docs I found how to draw a chart directly inside the PowerPoint slide like this:
$seriesData = array(
'Monday' => 12,
'Tuesday' => 15,
'Wednesday' => 13,
);
$series = new \PhpOffice\PhpPresentation\Shape\Chart\Series('Downloads', $seriesData);
$lineChart = new \PhpOffice\PhpPresentation\Shape\Chart\Type\Line();
$lineChart->addSeries($series);
$shape = $currentSlide->createChartShape(); // etc.
And this works correctly.
But I have an issue with encapsulating this in a PHP method, and looping it, and inserting the result chart inside a table cell:
$shape = $currentSlide->createTableShape();
$shape->setHeight(800);
// ..etc.
foreach ($datapoints as $datapoint) {
$row = $shape->createRow();
$row->setHeight(100);
$oCell = $row->nextCell();
$oCell->setWidth(240);
// This adds plain text in cell, it works:
$datapointValue = "Some text";
$oCell->createTextRun($datapointValue);
///////
// Instead the plain text I need to somehow add a chart here, with a loop:
// $datapointChart = $this->getMyChart($datapoint); // Chart should be encapsulated in a method like 'getMyChart'
// $oCell->createTextRun($datapointChart);
// .. but I have no idea what should 'getMyChart' return, and which method should I use instead the 'createTextRun' for the cell
///////
}
Basically I have no idea how to use this for a table cell (not directly for the slide):
$shape = $currentSlide->createChartShape();
And if I encapsulate the chart in a PHP method: what should this method return (that should be passed to the cell)?

Related

KendoUI for jQuery - Chart dataBound event does not work

Here's a simplified example of my data:
[{"Name": "Bob", "Count":3}, {"Name": "Steve", "Count":5}]
What I want is a chart title of: Total FOO: 8. So the title must be based on the data. The data is AJAX and this is an ASP.NET MVC application.
In my CSHTML I have:
.DataSource(ds => ds.Read(read => read.Action("MeMethodName", "MyControllerName")))
.Events(events => events.DataBound("setChartTitle('chartName', 'Total FOO')"))
Here's the crazy hack I had to do:
function setChartTitle(name, title) {
let chart = $("#" + name).data("kendoChart");
if (chart) {
let ds = chart.dataSource;
let total = 0;
for (let i = 0; i < ds.data().length; i++) {
total += ds.data()[i].Count;
}
chart.options.title.text = title + ": " + total;
chart.refresh();
} else if (arguments.length < 3) {
// Data source was not found and this was initiated through Kendo. Wait and try again but only once
setTimeout(function () {
sumEntityCount(name, title, "stop");
}, 500);
}
}
This is really bad.
Accessing the kendoChart returns undefined, yet the chart itself called this. This is why I need to check if (chart) above.
This leads to a hacky ELSE block I added where I can call this again with a 500 ms delay. This alone is a bug as 500ms is a random number and may not be enough. I can't ship like this.
To prevent recursion I call the same function with a different parameter.
If the values are found, then I can't just set the chart options. I need to call refresh which redraws everything.
Questions:
Why is the kendoChart data undefined initially? Why has Telerik called dataBound when there's nothing there?!
Isn't there a dataBinding event? I don't want to do this after the fact nor do I want to refresh the whole thing.
The chart itself is passed in when you declare a basic function without calling it:
.events(events => events.Render("someFunction"))
Then declare your function:
function someFunction(sender) {
// sender.chart is what I want
}
But you cannot pass any arguments here. Which means I can't use it.
The hack is to do the following:
.Events(events => events.Render("function(sender) { someFunction(sender, 'param1', 'param2', 'param3'); }"))
This gives it an actual function instead of calling a function. Kendo passes in the sender as expected and you can pass it along with new parameters to your JavaScript.
I also switched to using Render instead of DataBound.

How to determine which row or control is making a DropDownList Read Data call

I have a DropDownListFor control inside a Kendo grid, and I need the value of another element in that row in order to pass a parameter to the DropDownList's server-side read method. It's a similar setup to the example here.
Here is the code which defines the DropDownListFor:
#(Html.Kendo().DropDownListFor(m => m)
.Name("LinkIdentifier")
.OptionLabel("---Select Form, etc.---")
.DataValueField("ID")
.DataTextField("Name")
.AutoBind(false)
.DataSource(source =>
{
source.Read(read =>
{
read.Action("LinkTypeIdentifierDdl", "Alerts").Type(HttpVerbs.Post).Data("getCurrentLinkType");
}).ServerFiltering(true);
})
)
And here is the javascript function which is called in .Data:
function getCurrentLinkType() {
var grid = $("#linkGrid").data("kendoGrid");
var data = grid.dataSource.data();
var dataItem = data[0];
var valueForParameter = dataItem.SomeValue
//--snipped for brevity
}
The problem above is data[0]. It only points to the first row in the grid, which won't be correct if editing any other row. If I use a javascript debugger within this method and look at "this", it's the AJAX call which is referenced, not the dropdown control. I can't specify ".Data("getCurrentLinkType(this)")" as the method.
So, how can I determine which row/control has made the call to getCurrentLinkType?
There is no context passed from the Grid to the DropDownList to the Data function, so you need to figure it out yourself.
I have found 2 ways.
1:
// If your grid is single Selectable(), use the current selection
var dataItem = grid.dataItem(grid.select());
2:
// If your grid is not Selectable or is multi Selectable, get the row/tr closest to the editor and then get the dataItem from that
var dataItem = grid.dataItem($("#LinkIdentifier").closest("tr"));

Kendo UI TreeView: Add only nodes not exisisting in the view

I have tree view on a page which gets data from a ComboBox and a multiselect. The ComboBox contains the name of each ingredient and the multiselect contains the possible amount types which are then used as names for all their child nodes.
The tree looks something like that:
Ingredient 1
100mg
200mg
Ingredient 2
50mg
100mg
Everything works fine except I can add the same value twice because I am not able to validate if a node already exists.
Here is the function I am using to add new elements:
var addElement = function () {
var treeview = $("#ingredientTree").data("kendoTreeView");
var multiselect = $("#ingredientAmount").data("kendoMultiSelect");
var ingredientToAdd= $("#ingredient").val();
// I allways get an empty array at this point.
var exinstingIngredient= treeview.findByText(ingredientToAdd);
var children = new Array();
var amount = multiselect.value();
for (var j = 0; j < amount.length; j++) {
children.push({ text: amount[j] });
}
// it allways adds the items because the length is allways 0
if (exinstingIngredient.length === 0) {
treeview.append({
text: ingredientToAdd,
items: children
});
}
}
I don't understand why it can't find the existing element even I set its name as text and search for this text.
edit:
Here we have the treeview:
#(Html.Kendo().TreeView().TemplateId("treeview-template").Name("ingredientTree"))
That is the source of the ingredients, it handles just plain strings:
#(Html.Kendo().ComboBox()
.Name("ingredient")
.DataSource(source => source.Read(r => r.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "InternationalIngredients" }))))
.Events(events => events.Change("onIngredientChanged"))
)
Following you find the source for amounts, which handles strings to:
#(Html.Kendo().MultiSelect()
.Name("ingredientAmount")
.DataSource(source => source.Read(read => read.Url(Url.HttpRouteUrl("DefaultApi", new { controller = "InternationalIngredientAmount" })).Data("getIngredient")).ServerFiltering(true)))
This is a function to determine the selected ingredient for the service call:
function getIngredient() {
return { ingredient: $("#ingredient").val() }
}
I've found the reason for my problem now. findByText seems to check the Content of the nodes span with class "k-in". Unfortunatly, this Content is modified when you add a template as described here. So if you want to find an element with template, you should use findById or you define your template in a way you can use jQuery.

Slickgrid add new row on demand

Is there a way to add a new row to a slickgrid on demand? For example, have a button on the page that shows the new row when clicked.
It can be done easily using dataView.addItem(params), just replace params with your parameters..
function addNewRow(){
var item = { "id": "new_" + (Math.round(Math.random() * 10000)), "Controller": tempcont, "SrNo1": tempsrno };
dataView.addItem(item);
}
here id, Controller, SrNo1 are ids of colunm
You can add a row dynamically, such as with a button click, by subscribing the "onAddNewRow" listener to your grid object. As many have suggested, it is best to use the dataView plugin to interface with your data. Note that you'll want the "enableAddRow" option set to "true".
Firstly, DataView requires that a unique row "id" be set when adding a new row. The best way to calculate this is by using dataView.getLength() which will give you the number of rows that currently exist in you grid (which we'll use as the id for the new row). The DataView addItem function expects an item object. So we create an empty item object and append the id to it.
Secondly, you'll be only focusing a single cell when you add your data. After you've entered that data (by blurring that cell), DataView will notice that you have not entered data for any of the remaining cells in the row (of course) and will default their values to "undefined". This is not really a desired effect. So what you can do is loop through the columns you've explicitly set and append them to our item object with a value of "" (empty string).
Thirdly, we don't want all the cells to be blank. Actually, we want the original cell's entered data to appear. We have that data so we can set it last so that it will overwrite the "" (empty string) value we previously set.
Lastly, we'll want to update the grid such that it displays all of these changes.
grid.onAddNewRow.subscribe(function (e, args) {
var input = args.item;
var col = Object.keys(input)[0]
var cellVal = input[Object.keys(input)[0]]
// firstly
var item = {};
item.id = dataView.getLength();
// secondly
$.each(columns, function(count, value) {
colName = value.name
console.log(colName)
item[colName] = ""
})
// thirdly
item[col] = cellVal
dataView.addItem(item);
// lastly
grid.invalidateRows(args.rows);
grid.updateRowCount();
grid.render();
grid.resizeCanvas();
});
I hope this helps. Cheers!
You can always add new rows to the grid. All you need to do is add the data object for the row to your grid data.
Assume you create your grid from a data source - myGridData (which is an array of objects).
Just push the new row object to this array. Call the invalidate method on the grid.
Done :)
Beside creating the new row, I needed it to be shown, so that the user can start writing on it. I'm using pagination, so here is what I did:
//get pagination info
var pageInfo = dataView.getPagingInfo();
//add new row
dataView.addItem({id: pageInfo.totalRows});
//got to last page
dataView.setPagingOptions({pageNum: pageInfo.totalPages});
//got to first cell of new row
var aux = totalRows/ pageInfo.pageSize;
var row = Math.round((aux - Math.floor(aux)) * pageInfo.pageSize, 0);
grid.gotoCell(row, 0 ,true);
Before I use pagination, I just needed this:
var newId = dataView.getLength();
dataView.addItem({id: newId});
grid.gotoCell(newId, 0 ,true);
var newRow = {col1: "col1", col2: "col2"};
var rowData = grid.getData();
rowData.splice(0, 0, newRow);
grid.setData(rowData);
grid.render();
grid.scrollRowIntoView(0, false);
Working fine for me.

Getting Titanium Mobile label properties inside a function

Problem: How to determine which of the labels is being displayed in the window.
I created labels using Ti.UI.createLabel and there's 6 of them in the window. This is how I initialise them:
var sitCom = Ti.UI.createlabel({
text: 'Door',
top: 10,
left: 5,
visible: false
});
I have the same properties for other variables, the only difference is the text property.
These labels are hidden and they only get displayed on demand, when the button is pressed. I used the setVisible: true to display them. This is working fine.
I then created a function to loop through these variables to check which one is shown. The idea is that, when the user tap a button, the script then searches for the visible ones, hide the not related ones and show the one that the button is calling for.
function whatIsVisible(){
var newArr = new Array();
var newArr[0] = 'sitCom';
var newArr[1] = 'dutyFree';
var newArr[2] = 'Exclusive';
// ...
var i = 0;
for (i=0; i<=6; i++) {
var v = newArr[i].getVisible();
Ti.UI.info(newArr[i]+ ' is '+v);
}
}
The function shows undefined in the console log.
What am I missing here? Do I have to put an if statement inside the for loop to check the each array ?
thanks for your help
This is happening because you don't have label objects in for loop. Remove ' ' from the variables and just pass var name.

Resources