Track clicks on Elementor elements to Matomo - elementor

How to enable Matomo to track clicks on mailto links and tel links made with Elementor?
The Matomo script is added to Wordpress with the plugin "WP-Matomo Integration". The Matomo goals are listening to clicks to external websites with the pattern mailto:(.*) respectively tel:(.*). Isn't the Matomo client script supposed to fetch the goals from the tracking server and listen to these events? Is there another way to trigger those events?

I've created a script to search for elements with the class "matomogoal" and send the id of a goal to Matomo. It will push the goal id from the attribute matomogoalid to matomo, which you can get from your Matomo instance under Goals -> Manage Goals.
function trackClickGoal() {
var goalId = this.getAttribute("matomogoal");
_paq.push(['trackGoal', goalId]);
}
if (typeof _paq != "undefined") {
var elements = document.getElementsByClassName("matomogoal");
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', trackClickGoal, false);
}
} else { console.error('matomo client script is is not initialized'); }
<!-- HTML is supposed to look similar to this -->
<div class="matomogoal" matomogoal="goalid">
</div>
You can set those attributes by editing the link in Elementor. In the left Elementor panel click on "Advanced and set the "CSS Classes" field to matomogoal:
Then click on "Attributes" and set the value of "Custom Attributes" to matomogoal|goalid (while using the goal's ID you want to track with this click):
This also works with multiple goals on one page.

Related

Salesforce - Call external API on button click and update custom field from response

So I'll start by saying I'm a C# .Net/Javascript developer with a lot of experience, but I have zero experience with Salesforce. Never ever seen it before today. So, I've been asked by another team to add a custom button to a Contact object, which when clicked calls an external API and updates a custom field in the Contact with the response. It was pitched as "just write some Javascript that calls an API when a button is clicked, it's literally embedded into the page, 15 minute job...".
Following what appears to be quite an outdated document, I've ended up in the Object Manager, selected the Contact object and I'm into the Buttons, Links and Actions page. I'm assuming before this was done using the Execute Javascript behaviour, which in the Lightning version I'm advised against using. So after much Googling I've read about APEX classes, Visualforce Components, Lightning Components, the Salesforce REST API, etc, etc. Not a 15 min job.
Essentially the requirements are to embed a button (or action, or..?) into this Contact page, so that when the sales guy clicks it, it gathers some of the Contact's details and uses them to form an API call to an external service. The data will be used to form a response, which must then be read (as JSON, then parsed) and written into a custom field on the Contact.
What would be the best approach for developing a solution? In the Apex Debug environment I've put together the code to call the API and parse the JSON response, I'm assuming I need to wrap this in an Apex class, with a method that calls this code and returns the response. What I'm not sure of is how to call into this from the button, and update the field in the Contact.
Do I need to do all that from the Apex Class method? passing in a reference to the Contact, or is there another component that needs to sit in between and do this.
Am I right in assuming I'll need to use the Salesforce API to update the Contact?
Any pointers appreciated.
Oh man. It can be a 15 min job but it's definitely "easy when you know how" or have some examples ;)
What's your user interface, Classic or Lightning? Do they have plans to migrate to Lightning soon? I'm assuming it's Lightning if you figured out the "Execute JavaScript" hacks are passé.
Do you care where the button/action will be? Is the top right corner with all other buttons fine or do you want it to be droppable to pretty much any area in the page?
Does the API callout need username, password, maybe certificate? It'll determine whether you need just to whitelist the endpoint on firewall (Setup -> Remote Site Settings) or you'll need something more advanced (Setup -> Named Credentials).
Do you have SFDX command line (CLI), VSCode / are determined to install some tooling? The Lightning Web Components are cutting edge, most sleek etc but you can't create them straight in the browser (at least not yet), you need tooling. Visualforce is OK but nothing special for this use case, Aura components are bit clunky to write - but you can do both in Developer Console without extra tooling.
Parsing the JSON response - depends how complex it is, you can hand-craft parser with JSON.deserializeUntyped() but life's too short for this. Here's nice apex code generator similar to what you'd get from parsing WSDL: https://json2apex.herokuapp.com/
We'll try to do Aura component way. It's ugly, LWC is future but hey, it'll get you started.
Go to Setup -> Remote Site Settings and add new entry with https://en.wikipedia.org/
Create new Apex class:
public with sharing class Stack63364119 {
static final String endpoint = 'https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=';
#AuraEnabled
public static String doCallout(Id contactId){
if(contactId == null){
throw new MyException('Missing record id');
}
List<Contact> contacts = [SELECT MailingCountry FROM Contact WHERE Id = :contactId];
if(contacts.isEmpty() || String.isBlank(contacts[0].MailingCountry)){
throw new MyException('Could not find the contact');
}
Contact c = contacts[0];
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint + c.MailingCountry);
req.setMethod('GET');
HTTPResponse res = new Http().send(req);
System.debug(res.getStatus());
System.debug(res.getBody());
// no special parsing, just chuck it into Description field
// no error handling
if(res.getStatusCode() == 200){
c.Description = res.getBody().abbreviate(32000);
update c;
}
return res.getBody();
}
public class MyException extends Exception{}
}
Make new "Lighning Component" in developer console (it'll be Aura, not LWC). You can tick the last checkbox about "lightning quick action". Name can be same as class but doesn't have to be.
For component (~ html part) paste this
<!-- Loosely based on https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_actions_call.htm -->
<aura:component controller="Stack63364119" implements="force:hasRecordId,force:lightningQuickAction" >
<!-- in the name of all that is holy do not name the JS function same as the Apex class function, it'll give you very cryptic errors to debug -->
<aura:handler name="init" value="{!this}" action="{!c.runCallout}"/>
</aura:component>
For controller (~ JavaScript) paste this
({
runCallout : function(cmp) {
let action = cmp.get('c.doCallout');
action.setParams({contactId : cmp.get('v.recordId')});
action.setCallback(this, function(response){
let state = response.getState();
if (state === "SUCCESS") {
alert('Saved OK: ' + response.getReturnValue());
$A.get("e.force:closeQuickAction").fire(); // if you want to self-close
} else if (state === "ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " + errors[0].message);
}
} else {
console.log("Unknown error");
}
}
});
$A.enqueueAction(action);
}
})
Finally go Object Manager -> Contact -> Buttons Links and Actions. Create new Quick Action.
And add it to page layout(s)!
It should get you started. Maybe you'll decide to split it a bit, Apex would only do the callout, return results to UI and if user is happy - updating the contact can be done with one of these: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/data_service_save_record.htm. Separation of concerns blah blah (but will the user be happy with 2 clicks).
P.S. If you tweak it and it dies but it's hard to see any JavaScript errors - it'll be because default is to run in release mode, SF rewrites your source code a bit, optimises, polyfills for "browsers" like IE11... Go to Setup -> Debug Mode and enable for your user.
P.P.S. In sandbox / dev org it's good idea to go Setup -> Session Settings -> and untick "Enable secure and persistent browser caching to improve performance". Your component will be always fresh, saves some frantic hitting Ctrl+R. Don't do it in prod ;)
I will write to you my approach
Button on the record page/page layout -> lighting component or flow -> Apex class for collecting data -> apex class request & response API(don't forget to add the endpoint to remote site settings)-> parse response and update contact (you can use queries and DML operations inside Apex)
lighting component it will be very simple just have a the apex class as the controller example
<aura:component implements="force:appHostable,lightning:isUrlAddressable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" controller="contactController" access="global" >
<aura:handler name="init" value="{!this}" action="{!c.fetchContact}"/>
</aura:component>
controller
({
fetchContact : function(component, event, helper) {
helper.fetchContactHelper(component, event, helper);
}
})
helper
({
fetchAccHelper : function(component, event, helper) {
var action = component.get("c.fetchContacts");
action.setParams({
});
action.setCallback(this, function(response){
var state = response.getState();
if (state === "SUCCESS") {
}
});
$A.enqueueAction(action);
}
})
assuming is apex function is fetchContacts and class contactController

Google App Script / Gmail add-on : How to display a window with text and buttons over Gmail?

I made a Google App script to answer automatically to my emails (a kind of clever email robot assistant). Nevertheless, I would like to check each answer made by the robot before sending.
So I would like to have a window over Gmail showing the user email and the robot answer, and two buttons "send" "skip". In this way, I could check the answer prepared by the robot and either send it or skip it (or potentially edit it).
How to display a window with text, editText and buttons over GMail from Google App Script ?
Thanks !
Regards.
You have to check Gmail Add-on : https://developers.google.com/gsuite/add-ons/gmail
For a first start you can check the codelab from Google, it will give you the code to set a first add-on in 5 minutes then you can adapt it to your needs : https://codelabs.developers.google.com/codelabs/apps-script-intro/
Stéphane
An easy solution would be to have the robot save the e-mail as 'draft'. That way, you can easily check the emails before manually sending them.
If you are still interested in creating the gmail add-on (which could display the original email, response, and buttons for sending or editing), you may be interested in building card-based interfaces. These will appear to the right of your Gmail web client, and will look like the following:
The code used to display such interface (with two buttons, one that automatically sends the email and another one that opens the editor on it) is the following:
function buildAddOn(e) {
// Activate temporary Gmail add-on scopes.
var accessToken = e.messageMetadata.accessToken;
GmailApp.setCurrentMessageAccessToken(accessToken);
return buildDraftCard(getNextDraft());
}
function buildDraftCard(draft) {
if (!draft) {
var header = CardService.newCardHeader().setTitle('Nothing to see here');
return CardService.newCardBuilder().setHeader(header).build();
} else {
var header = CardService.newCardHeader()
.setTitle(draft.getMessage().getSubject());
var section = CardService.newCardSection();
var messageViewer = CardService.newTextParagraph()
.setText(draft.getMessage().getBody());
var sendButton = CardService.newTextButton()
.setText('Send')
.setOnClickAction(CardService.newAction()
.setFunctionName('sendMessage')
.setParameters({'draftId': draft.getId()})
);
var editButton = CardService.newTextButton()
.setText('Edit')
.setOnClickAction(CardService.newAction()
.setFunctionName('editMessage')
.setParameters({'draftId': draft.getId()})
);
var buttonSet = CardService.newButtonSet()
.addButton(sendButton)
.addButton(editButton);
section.addWidget(messageViewer);
section.addWidget(buttonSet)
return CardService.newCardBuilder()
.setHeader(header)
.addSection(section)
.build();
}
}
function sendMessage(e) {
GmailApp.getDraft(e.parameters.draftId).send();
return CardService.newActionResponseBuilder().setNavigation(
CardService.newNavigation()
.popToRoot()
.updateCard(buildDraftCard(getNextDraft()))
).build();
}
function editMessage(e) {
var messageId = GmailApp.getDraft(e.parameters.draftId).getMessageId();
var link = "https://mail.google.com/mail/#all/" + messageId;
return CardService.newActionResponseBuilder().setOpenLink(
CardService.newOpenLink()
.setUrl(link)
.setOnClose(CardService.OnClose.RELOAD_ADD_ON)
).build();
}
function getNextDraft() {
return GmailApp.getDrafts().pop()
}
And the appsscript.json configuration is the following:
{
"oauthScopes": [
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://mail.google.com/"
],
"gmail": {
"name": "Gmail Add-on Draft Autoresponse UI",
"logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/label_googblue_24dp.png",
"contextualTriggers": [{
"unconditional": {
},
"onTriggerFunction": "buildAddOn"
}],
"openLinkUrlPrefixes": [
"https://mail.google.com/"
],
"primaryColor": "#4285F4",
"secondaryColor": "#4285F4"
}
}
Bear in mind however, that these interfaces at the moment still have some limitations. They can only be displayed whilst having a message open, and the HTML formatting of the email may look a bit off. You can find more information on how to test & run the code above by following this link.

how to get infinite scroll with remote data with angularjs

Currently i am developing a listings portal here i am using angularjs as frontend and through this i am calling api to load data right now when a user hit all listings link whole data is loading and showing at this point the data is very less so problem but if data is large it will take some loading time, my requirement is when a user first click the link it has to show only top 50 listings and if user scrolls down the page then again api has to call for next 50 listings and so on how can i achieve this with angularjs.
Hope this helps.
$scope.limit=50;
$scope.loadMore1=true;
$scope.count=0;
$scope.loadMore=function()
{
$scope.limit=$scope.limit+100;
if($scope.limit>=$scope.count) {
$scope.loadMore1=false;
} else {
$scope.loadMore1=true;
}
$("html, body").animate({ scrollTop: $(document).height() }, 1000);
}
Inside your ajax call or api call
if(result.data.items.length<=50)
{
$scope.loadMore1=false;
} else {
$scope.count=result.data.items.length;
$scope.loadMore1=true;
}
And finally HTML list which loads certain number of results
<li ng-repeat="r in results | limitTo:limit"></li>
<button class="loadMore" ng-show="loadMore1" ng-click="loadMore()">Load more Items...</button>

How to get different data in ng-repeat ? - AngularJS

I'm using AngularJS and I have created an web app.
I don't know how to get values from one specific link.
I have an admin page where I have all users and I get them using ng-repeat
<div ng-repeat="user in allusers">
But I have created routes for example user level and I have added them to http://mylink.com/user_level/john#doe.com -- (that's example so I will get user level via user email)
The recommended approach would be to have the service that retrieves the users, send the user's level in the user object itself.
But, if this is not an option and you have to make separate service calls to get the user's level, this is how you would do this.
function getUserLevels(users) {
for(var i=0; i<users.length; i++) {
getUserLevelForUser(users[i]);
}
}
function getUserLevelForUser(user) {
$http.get(BASE_URL + '/' + user.email)
.then(function (response) {
user.userLevel = response.data.userLevel;
});
}
getUserLevels($scope.users);
Since I do not have access to your API, I've created a plunker app that invokes the Open Movie Database API. In this demo, instead of the user's level, I will be fetching a movie's director. Also, instead of using the user's email in the url, I will be using the movie name.
See working plunker here

Tracking VideoJS with Google Tag Manager

I would like to track VideoJS using Google Tag Manager. Since I am fairly new to GTM I have no idea how to proceed. I have only done some basic stuff like tracking mailto links and PDF downloads using tutorials.
I found a project on Github called videojs-ga which looks promising, now how would I correctly connect and set this up in GTM? Idealy we would end up tracking the play count and how long the video's get watched. There is no need to track multiple videos per page since there is only one video on a single page at any time.
Also if there is a better way to track VideoJS with GTM I am open to suggestions!
I suggest you make a fork from the code in that repository/ download the file from src and make the changes locally. In line 104 where it says:
sendbeacon = function(action, nonInteraction, value) {
if (window.ga) {
ga('send', 'event', {
'eventCategory': eventCategory,
'eventAction': action,
'eventLabel': eventLabel,
'eventValue': value,
'nonInteraction': nonInteraction
});
} else if (window._gaq) {
_gaq.push(['_trackEvent', eventCategory, action, eventLabel, value, nonInteraction]);
} else if (options.debug) {
console.log("Google Analytics not detected");
}
};
you replace the stuff within the sendbeacton function object in the following way:
sendbeacon = function(action, nonInteraction, value) {
dataLayer.push(
'eventCategory': eventCategory,
'eventAction': action,
'eventLabel': eventLabel,
'event' : 'videojs'
);
};
(make sure your dataLayer variable is declared somewhere before your GTM code). Create a script tag that links the modified script to our page.
Then create three variables of the "dataLayer" type which read their values from eventCategory, eventAction and eventLabel respectively. Set up a Google Analytics tag and configure it for event tracking, and pass in the variables to the respective fields. Create a trigger type custom event, event eq 'videojs' and use it to fire the GA event tracking tag.

Resources