GTM not firing DOM and Window Loaded triggers - reactjs

I recently added a dataLayer on our product and checkout pages for Google Analytics enhanced ecommerce, following the conventions found on Simo's blog. However, something strange has happened since we've added the dataLayer - the gtm.load and gtm.dom calls are no longer firing.
If I do a hard refresh, clear my cookies and go through the flow one time, everything seems to fire properly. However, once I go back to home page or some other part of the site, I only see the gtm.js getting fired and none of my triggers work.
For example, the navigation flow is home page -> product listing -> product detail. The site is a React single page app so I have a history event trigger to fire GA tags. If I'm a new user, and I go through those 3 pages, I will see the following fire in the dataLayer (as expected): gtm.js, gtm.dom, gtm.load, gtm.history. However, if I go back to the home page and go through the flow again, the gtm.dom and gtm.load dataLayer objects disappear and as a result my GTM triggers never fire. See images below for examples of what the dataLayer looks like on the product detail page for the above flow.
I'm at a loss here as I've never seen gtm.load not fire
Data Layer code on product listing page:
var window.dataLayer = window.dataLayer || []
dataLayer.push({
'ecommerce': {
'currencyCode':'CAD',
'impressions': [ //the array of products present within the listing
{ 'id':'123456', // Product SKU
'name':'GOOD FOOD', // Product Name
'price':'7.20', // Display price - use only xxxx.xx formatting
'brand':'No Name', // Product Brand
'category':'Food',
'position':0, // Product position within the list
'list':'Products page' // leave as is
}, { //second product
'id':'456890',
'name':'ABC Nuts',
'price':'8.50',
'brand':'ABC',
'category':'Food',
'position':1,
'list':'Products page'
},
...
{
'id':'1001010',
'name':'Fish Oil',
'price':'95.00',
'brand':'Zenn',
'category':'Oil',
'position':21,
'list':'Products page'
}
]
}
}); </script>
dataLayer on Product Detail Page:
<script>
var window.dataLayer = window.dataLayer || []
dataLayer.push({
'event': 'detail',
'ecommerce' : { 'detail' :
{ 'products' : [{
'name': 'Product ABC', // Full product name
'id': '123456', // Product SKU
'price': '15.25', // Price
'brand': 'Noname', // Brand of product
'category': 'Food', // Product category: Oil, Capsule, Flower or Accessory
'variant': '5g' // Size
}]
}
}
});
</script>

Since you're in a SPA, gtm.load and gtm.dom will only happen once, you need to base your triggers on something else.

Related

How to add 3rd party editor to Wagtail 2.2+?

We have been using Froala editor within Wagtail for years, and even though Draftail is nice, the end user wants to continue to use Frola, especially as a license fee has been paid for it, and it offers extra functionality that they use.
I have used the following which works great for any version of Wagtail prior to version 2.2:
https://github.com/jaydensmith/wagtailfroala/pull/5/commits/d64d00831375489cfacefa7af697a9e76fb7f175
However in Wagtail version 2.2 this has changed:
https://docs.wagtail.org/en/stable/releases/2.2.html#javascript-templates-in-modal-workflows-are-deprecated, this causes selecting images within Froala to no longer work correctly. You get to pick the image, but it fails to move onto the 2nd dialog to select alignment and then click insert.
It looks like wagtailfroala/static/froala/js/froala.js needs to be changed to add onload to the ModalWorkflow.
$.FE.RegisterCommand('insertImage', {
title: 'Insert Image',
undo: false,
focus: true,
refreshAfterCallback: false,
popup: true,
callback: function () {
var editor = this;
return ModalWorkflow({
url: window.chooserUrls.imageChooser + '?select_format=true',
responses: {
imageChosen: function(imageData) {
editor.edit.off();
var $img = $(imageData.html);
$img.on('load', function() {
_loadedCallback(editor, $(this));
});
// Make sure we have focus.
// Call the event.
editor.edit.on();
editor.events.focus(true);
editor.selection.restore();
editor.undo.saveStep();
// Insert marker and then replace it with the image.
if (editor.opts.imageSplitHTML) {
editor.markers.split();
} else {
editor.markers.insert();
}
var $marker = editor.$el.find('.fr-marker');
$marker.replaceWith($img);
editor.html.wrap();
editor.selection.clear();
editor.events.trigger('contentChanged');
editor.undo.saveStep();
editor.events.trigger('image.inserted', [$img]);
}
}
});
},
plugin: 'image'
});
But what needs to be added to make it work? I can't find any documentation or examples on how do to this? Please help or point me in the direction of the documentation for how this should be done.
Thank you so much in advance.

Problem with sorting for pagination in Meteor after viewing a document detail

I've written server-side pagination for documents in Meteor 1.8.1; on my Home page you see a paginated list of documents. If you click on one of the 10 displayed documents, you navigate to the detail page for that document. The problem is that if you then click Home from the detail page, to return to the paginated list, the document which you just viewed appears at the top of the page, regardless of its name. For example if you view "B Document" then click Home, the list is:
B Document
A Document
...
I'm applying a client-side sort but nothing I have tried causes the recently-viewed document to appear in the correct place on the page. Only refreshing the Home page fixes the sort to show "A Document" first.
name_sort is a field I added for case-insensitive sorting, it is generated as name.toLowerCase() when the document is created.
Here's my publish.js:
Meteor.publish('documents', (skip = 0, limit = 10) => {
return Documents.find({},
{
'fields': {
'name': 1,
'name_sort': 1,
},
'sort': { 'name_sort': 1 },
'skip': skip,
'limit': limit,
});
});
Meteor.publish('document', function (_id = undefined) {
return Documents.find(
{ _id },
{
'fields': {
'name': 1,
'name_sort': 1,
},
},
);
});
And here's how my React Home page accesses the data:
const Tracker = withTracker(({ pageSkip, dispatch }) => {
dispatch(setIsLoading(true));
Meteor.subscribe('documents', pageSkip, 10, {
'onReady': () => {
dispatch(getDocumentCount());
dispatch(setIsLoading(false));
},
});
return {
''documents:': Documents.find({}, {
'sort': { 'name_sort': 1 },
'limit': 10,
}).fetch(),
};
})(Home);
Any idea how I can fix this? It must be something simple but I can't figure it out. Thanks!
Update: I've got the sorting kind of working - I had forgotten to publish the name_sort field - but there is still a problem that the document I viewed in detail appears at the top of the list on the Home page, then it then either disappears or jumps into its correct place, depending on whether it should appear on this page. I guess this must be because the document is still in MiniMongo from before, and the list is only corrected when the new subscription is ready. It looks pretty bad if there is any kind of server lag.
What I'd like to do is clear MiniMongo when the Home page loads but I can't find a way to do that? I tried stopping the single-document subscription with this.stop() but that didn't fix the problem.
The solution I found is to set a flag "isLoading" to true when the component subscribes to the publication, and set it to false when the subscription is ready. Then use this flag to hide the list of documents until the subscription is ready; this glosses over the awkward time when the document from the previous page is still in MiniMongo.

.push() is creating duplicate table filter dropdowns when page is revisited

I have custom table with a filters property. 3 of the filters are hard coded, but 1 of the filters is gathered through a POST request from our API because their filter options are frequently updated in our database by non-engineering folks.
My issue is that when you hit the back button to get to the page with this custom table, a duplicate Gateways filters is added to the page. This item disappears when you refresh the page, which demonstrated to me that I am running into an issue with the State. How can I make it so the Gateways filter is only displayed once?
Here is the code for the table:
<CredcapTable
defaultSort="created_at"
cols={cols}
filters={filters}
searchFunc={this.searchFunc}
recordMappingFunc={this.recordMappingFunc}
/>
Here is the function that pulls the Gateways filters from the database, which is called in componentDidMount:
private loadFilters = () => {
searchGateways(this.props.app.api, new SearchRequest()).then((res: SearchResults<Gateway>) => {
const opts = new Array<any>();
opts.push({ label: "All", value: "*" });
res.results.map((g: Gateway) => {
opts.push({ label: `${g.name} [${g.code}]`, value: g.id });
});
filters.push({
field: "gateway_id",
label: "Gateway",
options: opts
});
this.setState({ filters });
});
};

AngularJS ng-repeat with different objects

I'm new to AngularJS and have written an app which calls an specific API. It gets an object back like this:
posts = [
posts = [...]
users = [...]
]
That's the HTML template:
<div id="{{'post-' + $index}}" ng-repeat="post in posts.posts">
<article>
<h1>{{post.user.username}}</h1>
<span>{{post.date | date: "dd.MM.yy, HH:mm"}}</span>
<p>{{post.content}}</p>
</article>
</div>
I want to show different posts (from the posts object) including the username (which is in the users object). How can I tell Angular that the posts are in the posts object and the proper usernames are in the users object?
ngRepeat creates a new scope for each entry. That scope will contain $index for the current offset in the array. Since it's a new scope the variable posts is now in the $parent scope.
Assuming posts and users contain parallel data and are arrays. You can lookup data in users using $index.
<h1>{{$parent.posts.users[$index].username}}</h1>`
Without seeing the structure of the posts and users object, it's hard to say exactly, but the general idea is to use the users object, with the user_id from the post object, so either something like this:
<h1>{{users[post.user.id].username}}</h1>
Or like this:
<h1>{{getUserNameForPost(post)}}</h1>
$scope.getUserNameForPost = function(post) {
//find the user based of the post however you want
user = $scope.users.filter(function(u) { return u.id = post.user });
return user[0].username;
}
If possible, I would suggest you change the structure of the returned JSON object, to something similar to the one below:
posts = [
{
id : "post id",
title: "Post title",
content: "Post content",
date : "post date",
user : {
id: "user id",
name: "user name"
}
},
....
....
]
This should work.

Backbone: fetch information to model by demand

I want to make a Model-View behaviour in backbone.js, so that not all information will be loaded at the begining. For example I have a player infromation in the Player Model:
var Player = Backbone.Model.extend({
initialize: function() {
},
defaults: {
name: "",
surname: "",
someOtherInfo: ...
}
});
I want to show the players list in a table, where only player name and surname will be shown, however, if user clicks a player, more detailed information will be shown, by fetching other attributes (someOtherInfo).
Is there a way to do it when calling fetch?
Your API methods returning the list/collection can return different data than the API method for fetching a specific model. Just populate the collection with only the data needed for the list and when an item is selected fetch that specific model to fill in the blanks.

Resources