Ag-Grid : Access to the row node/data in the browser's Developer Tools - google-chrome-devtools

When debugging my application with Chrome's Developer Tools I would like to be able to access the row node or the column on a given ag-cell.
I can do it if there is an Angular cellRenderer (using ng.getComponent($0) I can access my Angular component and the params). But is it possible with the default cellRenderer ?

I'd recommend a simple solution: use the cellClicked callback.
(cellClicked)="cellClicked($event)"
and in your component:
cellClicked(params)
{
console.log(params.node)
}
I've created a StackBlitz for your here.

This answer works only if you are using AgGrid within Angular.
I created a function (here in Typescript) that can be included in your app code.
You can then call-it from the Dev Tools:
select an element inside the AgGrid (which must be created using an Angular Component)
in the console, run getAgGridCellInfo($0) (as you would call ng.getComponent($0))
Code to be copied inside your app's code :
/**
* For Dev tool's console, provides an easy access to a cell's Node and column.
* Just :
* * select an element inside the AgGrid (which must be created using an <ag-grid-angular> Angular Component)
* * in the console, run getAgGridCellInfo($0) (as you would call ng.getComponent($0))
*
* #param element HTMLElement ($0 usually)
* #return an object with some information on the row, column and cell
*/
function getAgGridCellInfo(element: HTMLElement) {
if (!element) {
console.error('Call with $0 parameter, after selecting a DOM element inside an AgGrid');
return;
}
function getInheritedAttribute(el: HTMLElement, attributeName: string) {
if (!el) {
throw new Error('Could not find attribute ' + attributeName + ' in parents of ' + element);
}
const elVal = el.getAttribute(attributeName);
return elVal != null ? elVal : getInheritedAttribute(el.parentElement, attributeName);
}
function getAgGridCompontent(el: HTMLElement) {
const comp = window['ng'].getComponent(el);
return comp && comp.constructor.name === 'AgGridAngular' ? comp : getAgGridCompontent(el.parentElement);
}
const rowId = getInheritedAttribute(element, 'row-id');
const colId = getInheritedAttribute(element, 'col-id');
const agGridComp = getAgGridCompontent(element);
const api: GridApi = agGridComp?.api;
const rowNode = api?.getRowNode(rowId);
const colDef = api?.getColumnDef(colId);
const value = colDef && rowNode && api.getValue(colId, rowNode);
return {
api: api,
rowId: rowId,
colId: colId,
rowNode: rowNode,
colDef: colDef,
value: value,
data: rowNode?.data
};
}
window['getAgGridCellInfo'] = getAgGridCellInfo;

Related

Wagtail - how to get tags to work with `telepath` (tags in streamfield)?

I can use tags in regular page fields without any issue. When using tags within blocks (within a streamfield), the UI works and the tags are saved BUT the current page tags do not show up when loading the page in the admin. That's because the current value is not in the template anymore, it's in a JSON loaded via telepath.
I can confirm that the tags are saved and present in the data passed to initBlockWidget in the page source but these are ignored. Also, if I used a regular text field instead of the tag-widget, I can see the saved-values in the admin.
This is the code I have (which used to be enough before the refactor with telepath).
from wagtail.admin.widgets import AdminTagWidget
class TagBlock(TextBlock):
#cached_property
def field(self):
field_kwargs = {"widget": AdminTagWidget()}
field_kwargs.update(self.field_options)
return forms.CharField(**field_kwargs)
I think the following link is what I need to complete somehow to get it to work: https://docs.wagtail.io/en/stable/reference/streamfield/widget_api.html#form-widget-client-side-api
I've tried with this:
class AdminTagWidgetAdapter(WidgetAdapter):
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
register(AdminTagWidgetAdapter(), AdminTagWidget)
And under js/admin/admin-tag-widget-adapter.js:
console.log("adapter"); // this shows up in the console
class BoundWidget { // copied from wagtail source code
constructor(element, name, idForLabel, initialState) {
var selector = ':input[name="' + name + '"]';
this.input = element.find(selector).addBack(selector); // find, including element itself
this.idForLabel = idForLabel;
this.setState(initialState);
}
getValue() {
return this.input.val();
}
getState() {
return this.input.val();
}
setState(state) {
this.input.val(state);
}
getTextLabel(opts) {
const val = this.getValue();
if (typeof val !== 'string') return null;
const maxLength = opts && opts.maxLength;
if (maxLength && val.length > maxLength) {
return val.substring(0, maxLength - 1) + '…';
}
return val;
}
focus() {
this.input.focus();
}
}
// my code here:
class AdminTagWidget {
constructor(html, idPattern) {
this.html = html;
this.idPattern = idPattern;
}
boundWidgetClass = BoundWidget;
render(placeholder, name, id, initialState) {
console.log("RENDER", placeholder, name, id, initialState); // this does not show
var html = this.html.replace(/__NAME__/g, name).replace(/__ID__/g, id);
var idForLabel = this.idPattern.replace(/__ID__/g, id);
var dom = $(html);
$(placeholder).replaceWith(dom);
// eslint-disable-next-line new-cap
return new this.boundWidgetClass(dom, name, idForLabel, initialState);
}
}
console.log("here") // does show in the console
// variants I've tried:
//window.telepath.register('wagtail.admin.widgets.tags.AdminTagWidget', AdminTagWidget);
//window.telepath.register('wagtail.widgets.AdminTagWidget', AdminTagWidget);
window.telepath.register('path.where.its.used.AdminTagWidget', AdminTagWidget)
The log from my custom render method does not show. It seems that I'm not calling the right path within window.telepath.register but I don't know how what the string is supposed to be...
I'm not even sure if this is the right way forward.
Notes:
it works in regular field, the question is about tags in blocks
I'm using Wagtail version 2.13.2 but I've also tried with 2.15 without any difference.
In the console, I can log window.telepath and see my custom widget. It's just not "applied" to anything
Your WidgetAdapter class needs a js_constructor attribute:
class AdminTagWidgetAdapter(WidgetAdapter):
js_constructor = 'myapp.widgets.AdminTagWidget'
class Media:
js = [
"wagtailadmin/js/vendor/tag-it.js",
"js/admin/admin-tag-widget-adapter.js",
]
Any string value will work here - it just needs to uniquely identify the class, so it's recommended to use a dotted module-like path to avoid colliding with others. This then matches the string you pass to window.telepath.register on the Javascript side:
window.telepath.register('myapp.widgets.AdminTagWidget', AdminTagWidget)

Middleware to add time to all actions

Using Redux Toolkit, I have many action creators that have prepare callbacks adding a unixtime property to the payload. This property gets the Date.now() value. Since I'm continually adding more actions requiring a time, I'm thinking I'll instead add middleware like the following:
import { Middleware } from 'redux';
import { RootState } from '../sliceReducer'; // type of my app's Redux state
const unixtimeMiddleware: Middleware<{}, RootState> = () => (next) => (action) => {
if (action && !action.meta) {
action.meta = {};
}
action.meta.unixtime = Date.now();
return next(action);
};
export default unixtimeMiddleware;
My question is:
Is this the right type for the middleware?
Possibly related to (1), is there a way to automatically have meta: { unixtime: number } added to all RTK action types, or do I need to create types that extend the built-in action types like:
import { PayloadAction } from '#reduxjs/toolkit';
/**
* An extension of Redux Toolkit's PayloadAction which includes a .meta.unixtime property.
*
* #template P The type of the action's payload.
* #template M Union to the action's base meta type { unixtime: number } (optional)
* #template E The type of the action's error (optional)
*
* #public
*/
export type PayloadActionUnixtime<P, M = never, E = never> = PayloadAction<
P,
string,
{ unixtime: number } & ([M] extends [never] ? {} : M),
E
>;
(Side note: I chose the name unixtime over something like timestamp to reduce the likelihood someone would think it is a string like YYYY-MM-DD HH-MM-SS)

Dynamics CRM 365 : Downloading a Word Document Template via a Button on the Ribbon

Currently users have to click the ellipses, word templates, and finally quote to download the word template.
To make it easier for our users we would like to have the document download when pressing the "print quote" button on the ribbon.
Is this possible? If so how would I go about doing this? I understand how to edit the ribbon using the ribbon workbench. I need to know how to download a word template using the ribbon.
If the solution is using the ribbon workbench, what command can I enter to get the word template to download?
When you click the templates flyout, it's dynamically populated through an invocation of /AppWebServices/DocumentTemplate.asmx, which returns the XML for the menu.
The flyout for Word Templates in the Incident home page grid looks like this:
<Menu Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu">
<MenuSection Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu.CreateTemplates" Title="Create Word Template" Sequence="10" DisplayMode="Menu16">
<Controls Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu.CreateTemplates.Controls">
<Button Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu.CreateTemplates.Controls.00000000-0000-0000-0000-000000000000" Command="incident|NoRelationship|HomePageGrid|Mscrm.WordTemplate.CreateWordTemplate.Grid" Sequence="10" ToolTipDescription="Create Word Template" Alt="Create Word Template" LabelText="Create Word Template" />
</Controls>
</MenuSection>
<MenuSection Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu.WordTemplates" Title="Word Templates" Sequence="20" DisplayMode="Menu16">
<Controls Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu.WordTemplates.Controls">
<Button Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu.WordTemplates.Controls.9b77c5b0-1033-4741-a01c-afdbdb1c3f22" Command="incident|NoRelationship|HomePageGrid|Mscrm.WordTemplate.TemplatesMenu.Grid" Sequence="10" ToolTipDescription="Case Summary" Alt="Case Summary" LabelText="Case Summary" />
</Controls>
</MenuSection>
</Menu>
I don't have the means to try it out at the moment, but I'd try and "copy" the last <Button>:
<Button Id="incident|NoRelationship|HomePageGrid|Mscrm.HomepageGrid.incident.WordTemplates.Menu.WordTemplates.Controls.9b77c5b0-1033-4741-a01c-afdbdb1c3f22" Command="incident|NoRelationship|HomePageGrid|Mscrm.WordTemplate.TemplatesMenu.Grid" Sequence="10" ToolTipDescription="Case Summary" Alt="Case Summary" LabelText="Case Summary" />
It's possible to do this using only supported features of CRM (of course I'm sure it's also possible to do using unsupported javascript, but I don't have time currently to investigate this). The steps that you should take to achieve the functionality you want:
Create new process of type Action, bound to entity that you want to
create a template for (the reason why I suggest Action here, is that
it can be easily invoked using JavaScript and CRM WebAPI)
In this Action add single step - invoke an Action and choose
built-in action "SetWordTemplate"
Set Properties of this action - choose the template that you need
and dynamically set the target to current entity (using Dynamic
Values assistant) If you never used this action - it simply creates
a given word template and adds it as an annotation to your entity
Now you need to write logic inside your button (I'm assuming you
know how to add a button using Ribbon Workbench or whatever)
Call your action using WebAPI
Find annotation that was just created for your entity with the
attached document
Download the attachment (you can show some prompt for the user or
simply force the download the file, user will have to save it)
Delete the annotation
Maybe not a one-liner, but keeps you in the supported zone...
ExecuteWordMerge = function (wordtemplateid, entitytypecodeint, ids, templatetype, fieldforfilename, filenameoverride) {
try {
Xrm.Page.ui.clearFormNotification("worderror");
var funcpath = Xrm.Page.context.getClientUrl() + "/_grid/print/print_data.aspx";
if (typeof ids !== "object") {
var tids = ids;
ids = new Array();
ids.push(tids);
}
var wordTemplateId = wordtemplateid;//"f1f7b994-543b-e711-8106-c4346bac2908" test data;
var currentEntityTypeCode = entitytypecodeint;//"10063" test data;
var templateType = (templatetype || 9940); //9940 is global and 9941 is personal
var fieldForFileName = (fieldforfilename || "");
var formdata = "exportType=MergeWordTemplate&selectedRecords=" + encodeURIComponent(JSON.stringify(ids)) +
"&associatedentitytypecode=" + currentEntityTypeCode + "&TemplateId=" + wordTemplateId + "&TemplateType=" + templateType;
var req = new XMLHttpRequest();
req.open("POST", funcpath, true);
req.responseType = "arraybuffer";
req.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
req.setRequestHeader("Accept-Language", "en-US,en;q=0.8");
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.onreadystatechange = function () {
if (this.readyState == 4) {/* complete */
req.onreadystatechange = null;
if (this.status >= 200 && this.status <= 299) {//200 range okay
var mimetype = (2 === 2) ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var blob = new Blob([req.response], { type: mimetype });
var fileNameTemplate = req.getResponseHeader('content-disposition').split('filename=')[1].replace(/'/g, "");
var dloadurl = URL.createObjectURL(blob);
var filename = (fieldForFileName !== "" && Xrm.Page.getAttribute(fieldForFileName) !== null && Xrm.Page.getAttribute(fieldForFileName).getValue() !== "") ?
Xrm.Page.getAttribute(fieldForFileName).getValue() : fileNameTemplate;
filename = filenameoverride || filename;
//new code, prevent IE errors
if (navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(blob, filename);
return;
}
else if (window.navigator.msSaveBlob) { // for IE browser
window.navigator.msSaveBlob(blob, filename);
return;
}
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = dloadurl;
a.download = filename;
a.click();
URL.revokeObjectURL(dloadurl);
//window.location = dloadurl;//we can use just this instead of creating an anchor but we don't get to the name the file
}
else {
Xrm.Page.ui.setFormNotification("An Error occurred generating the word document, please contact support if the issue persists,code: " + this.status, "ERROR", "worderror");
}
}
};
req.send(formdata);
}
catch (err) {
Xrm.Page.ui.setFormNotification("An Error occurred generating the word document, please contact support if the issue persists. " + err.message, "ERROR", "worderror");
}
}
Just to simplify #TeamEASI.com answer a little here is what I did.
Add a button to the ribbon using XRMToolBox Ribbon Workbench 2016.
Create a JS web resource like the one bellow.
/*
* Author: Matthew Hunt
* File: vsi_DownloadTemplate.js
* Date: 12/20/2017
* Project: CRM USA
* Description: DownloadTemplate() allows the user to download a document template
* via a button on the ribbon.
*
* #param entitytypecode: the type code of the entity. In the ribbon workbench set a
* CRM parameter with value PrimaryEntityTypeCode. ex: 1063
*
* #param templateid: the id for the template you want to download. I had to go to
* the database to find this and pass it as a string parameter in the ribbon workbench.
* For example:
* SELECT DocumentTemplateId, Name FROM dbo.DocumentTemplateBase WHERE Name Like '%Quote%';
* returns something like 4AB391A4-D247-E711-80D3-005056914EA2
* Unforunatly, anytime the template is updated, you'll probably have to get the new id.
*
* #param templatetype: the code for the template type. Pass this value in the ribbon
* workbench as a int param. ex: 9940 is a documenttemplate
*
* #param filename: the resulting name of the file that will be downloaded to the users
* computer. Pass this value in the ribbon workbench as a string param. ex: Quote.docx
*
*/
function DownloadTemplate(entitytypecode, templateid, templatetype, filename){
// retrieve the entity id from the current page
var entityid = new Array();
entityid.push(Xrm.Page.data.entity.getId());
// try and make a request for the document template
try{
// clear the page of any previous errors
Xrm.Page.ui.clearFormNotification("docerror");
// the path that will be used to retrieve the word template
var funcpath = Xrm.Page.context.getClientUrl() + "/_grid/print/print_data.aspx";
// open the request to create the template
var req = new XMLHttpRequest();
req.open("POST", funcpath, true);
req.responseType = "arraybuffer";
req.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
req.setRequestHeader("Accept-Language", "en-US,en;q=0.8");
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// on completion, run the bellow function
req.onreadystatechange = function () {
// request complete
if (this.readyState == 4) {
req.onreadystatechange = null;
// check if we got back a 200 from the request
if (this.status >= 200 && this.status <= 299) {
// add the download url to an a tag and then click the a tag
// to download the document
var mimetype = (2 === 2) ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var blob = new Blob([req.response], { type: mimetype });
var dloadurl = URL.createObjectURL(blob);
var a = document.createElement("a");
// if ie, because ie sucks
if (navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(blob, filename);
// else a browser that doesn't suck
} else {
document.body.appendChild(a);
a.style = "display: none";
a.href = dloadurl;
a.download = filename;
a.click();
URL.revokeObjectURL(dloadurl);
}
}
};
// compile the data to send with the request
var formdata = "exportType=MergeWordTemplate&selectedRecords=" + encodeURIComponent(JSON.stringify(entityid)) +
"&associatedentitytypecode=" + entitytypecode + "&TemplateId=" + templateid + "&templatetype=" + templatetype;
// make the request to create the template
req.send(formdata);
}catch (err) {
PrintError(err.message);
}
}
/*
* PrintError() is a helper method to display any errors to the user.
*/
function PrintError(msg){
Xrm.Page.ui.setFormNotification("An Error occurred generating the word document, please contact support if the issue persists. " + msg, "ERROR", "docerror");
}
IE fix: .click() giving access denied in IE11
Create a command using XRMToolBox Ribbon Workbench 2016 with the following parameters to execute the JS when the button is clicked.
With the new version of CRM this javascript code, need to be amened to remove the unsupported API, as well some addtional changes to be able to work also with CHROME.
below my working version,
/*
* Author: Matthew Hunt
* Changes: Philippe Guarino
* File: vsi_DownloadTemplate.js
* Date: 22/09/2021
* Project: CRM USA
* Description: DownloadTemplate() allows the user to download a document template
* via a button on the ribbon.
*
* #param entitytypecode: the type code of the entity. In the ribbon workbench set a
* CRM parameter with value PrimaryEntityTypeCode. ex: 1063
*
* #param templateid: the id for the template you want to download. I had to go to
* the database to find this and pass it as a string parameter in the ribbon workbench.
* For example:
* SELECT DocumentTemplateId, Name FROM dbo.DocumentTemplateBase WHERE Name Like '%Quote%';
* returns something like 4AB391A4-D247-E711-80D3-005056914EA2
* Unforunatly, anytime the template is updated, you'll probably have to get the new id.
*
* #param templatetype: the code for the template type. Pass this value in the ribbon
* workbench as a int param. ex: 9940 is a documenttemplate
*
* #param filename: the resulting name of the file that will be downloaded to the users
* computer. Pass this value in the ribbon workbench as a string param. ex: Quote.docx
*
*/
function DownloadTemplate(entitytypecode, templateid, templatetype, filename, formContext)
{
// var formContext = executionContext.getFormContext(); // get formContext
// retrieve the entity id from the current page
var entityid = new Array();
entityid.push(formContext.data.entity.getId());
// try and make a request for the document template
try
{
// clear the page of any previous errors
formContext.ui.clearFormNotification("docerror");
// the path that will be used to retrieve the word template
var globalContext = Xrm.Utility.getGlobalContext();
var funcpath = globalContext.getClientUrl() + "/_grid/print/print_data.aspx";;
// open the request to create the template
var req = new XMLHttpRequest();
req.open("POST", funcpath, true);
req.responseType = "arraybuffer";
req.setRequestHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
req.setRequestHeader("Accept-Language", "en-US,en;q=0.8");
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
// on completion, run the bellow function
req.onreadystatechange = function ()
{
// request complete
if (this.readyState == 4)
{
req.onreadystatechange = null;
// check if we got back a 200 from the request
if (this.status >= 200 && this.status <= 299)
{
// add the download url to an a tag and then click the a tag
// to download the document
var mimetype = (2 === 2) ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
var blob = new Blob([req.response],
{
type: mimetype
});
var dloadurl = (window.URL ? URL : webkitURL).createObjectURL(blob);
var a = document.createElement("a");
// if ie, because ie sucks
if (navigator.msSaveOrOpenBlob)
{
navigator.msSaveOrOpenBlob(blob, filename);
// else a browser that doesn't suck
}
else
{
document.body.appendChild(a);
a.style = "display: none";
a.href = dloadurl;
a.download = filename;
a.click();
URL.revokeObjectURL(dloadurl);
}
}
}
};
// compile the data to send with the request
var formdata = "exportType=MergeWordTemplate&selectedRecords=" + encodeURIComponent(JSON.stringify(entityid)) +
"&associatedentitytypecode=" + entitytypecode + "&TemplateId=" + templateid + "&templatetype=" + templatetype;
// make the request to create the template
req.send(formdata);
}
catch (err)
{
PrintError(err.message);
}
}
/*
* PrintError() is a helper method to display any errors to the user.
*/
function PrintError(msg)
{
Xrm.Page.ui.setFormNotification("An Error occurred generating the word document, please contact support if the issue persists. " + msg, "ERROR", "docerror");
}
To get the entity code, run below query:
SELECT coalesce(OriginalLocalizedName,name) AS DisplayName, Name AS SchemaName, ObjectTypeCode
FROM EntityLogicalView
ORDER BY ObjectTypeCode
and for Template ID:
SELECT DocumentTemplateId, Name FROM dbo.DocumentTemplateBase

Fluidtypo3: Use custom field as title of FCE

I don't want to use the default header in my FCE's, but only custom flux fields. In the backend list views my FCE's are shown as "[no title]" because the default header is not filled. This leads to much confusion for editors.
How can I define one of my custom flux fields to be used as title for the FCE in TYPO3 Backend list views etc.?
You can't just use a field from the flexform, because all fields from the FCE are stored in the same field in the database (pi_flexform).
What you can do is to render the content element title with a user function. It is registered with a line like this in the TCA config:
$GLOBALS['TCA']['tt_content']['ctrl']['label_userFunc'] = 'Vendor\\Extkey\\Utility\\ContentElementLabelRenderer->getContentElementTitle';
The user function itself could look like this:
<?php
namespace Vendor\Extkey\Utility;
/**
* This class renders a human readable title for FCEs,
* so one is able to find a content element by its headline.
*/
class ContentElementLabelRenderer implements \TYPO3\CMS\Core\SingletonInterface {
/**
* #var \TYPO3\CMS\Extbase\Service\FlexFormService
* #inject
*/
protected $flexFormService = null;
/**
* Returns the content element title for a given content element
*/
public function getContentElementTitle(&$params) {
if (null === $this->flexFormService) {
$this->flexFormService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Service\\FlexFormService');
}
if (ctype_digit($params['row']['uid']) && 'fluidcontent_content' === $params['row']['CType']) {
// If this is a FCE, parse the flexform and template name and generate the
// title in a template specific way.
$row = $params['row'];
$additionalRowData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('pi_flexform, tx_fed_fcefile', 'tt_content', 'uid = ' . $row['uid']);
$flexFormContent = $this->flexFormService->convertFlexFormContentToArray($additionalRowData['pi_flexform']);
$lastColonPosition = strrpos($additionalRowData['tx_fed_fcefile'], ':');
$contentElementType = (FALSE === $lastColonPosition) ? 'invalidtype' : substr($additionalRowData['tx_fed_fcefile'], $lastColonPosition + 1);
switch ($contentElementType) {
case 'Image.html':
$params['title'] = 'Image: "' . ($flexFormContent['title'] ?: $flexFormContent['subtitle']) . '"';
break;
default:
$params['title'] = 'Unknown content element type';
break;
}
}
else {
// If this is not a FCEm, print out "normal"
// title. Not the real thing, but comes pretty close, hopefully.
$params['title'] = $params['row']['header'] ?: ($params['row']['subheader'] ?: $params['row']['bodytext']);
}
}
}
This produces a maintainance problem though: Every time you add or change a content element, you have to update this file.

hitCallback is not working with Disconnect, Ghostery, etc

I following code in my html:
Sign up
It is not working then Disconnect or similar plugin installed in Chrome or other browser. How to solve this issue?
I have used decoded version of Universal Analytics code and put calling hitCallback there:
<!-- Google Analytics -->
<script>
/**
* Creates a temporary global ga object and loads analy tics.js.
* Paramenters o, a, and m are all used internally. They could have been declared using 'var',
* instead they are declared as parameters to save 4 bytes ('var ').
*
* #param {Window} i The global context object.
* #param {Document} s The DOM document object.
* #param {string} o Must be 'script'.
* #param {string} g URL of the analytics.js script. Inherits protocol from page.
* #param {string} r Global name of analytics object. Defaults to 'ga'.
* #param {DOMElement?} a Async script tag.
* #param {DOMElement?} m First script tag in document.
*/
(function(window, document, strScript, url, variableName, scriptElement, firstScript) {
window['GoogleAnalyticsObject'] = variableName; // Acts as a pointer to support renaming.
// Creates an initial ga() function. The queued commands will be executed once analytics.js loads.
window[variableName] = window[variableName] || function() {
(window[variableName].q = window[variableName].q || []).push(arguments);
// If user uses Disconnect, Ghostery, DoNotTrackMe or similar plugin we shoud make hitCallback to work
if(typeof arguments[2] == "object" && typeof arguments[2].hitCallback == "function") {
arguments[2].hitCallback();
} else if (typeof arguments[5] == "object" && typeof arguments[5].hitCallback == "function") {
arguments[5].hitCallback();
}
};
// Sets the time (as an integer) this tag was executed. Used for timing hits.
window[variableName].l = 1 * new Date();
// Insert the script tag asynchronously. Inserts above current tag to prevent blocking in
// addition to using the async attribute.
scriptElement = document.createElement(strScript),
firstScript = document.getElementsByTagName(strScript)[0];
scriptElement.async = 1;
scriptElement.src = url;
firstScript.parentNode.insertBefore(scriptElement, firstScript)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-XXXXXXXX-1', 'auto'); // Creates the tracker with default parameters.
ga('send', 'pageview'); // Sends a pageview hit.
</script>
<!-- End Google Analytics -->
The following article discusses this problem in detail:
http://veithen.github.io/2015/01/24/outbound-link-tracking.html