I have very little experience with Magento2, but am striking a problem that potentially requires some insight from somebody who does.
I only have front-end access.
I am including the following magento-init block:
<script type="text/x-magento-init">
{
"*":
{
"Magento_Customer\/js\/customer-data":
{
"sectionLoadUrl": "http:\/\/www.example.com\/customer\/section\/load\/",
"cookieLifeTime": "3600",
"updateSessionUrl": "http:\/\/www.example.com\/customer\/account\/updateSession\/"
}
}
}
</script>
in the body of my page, which I would expect to populate a sectionLoadUrl under Magento_Customer/js/customer-data.
Just before my closing </body> tag, I include a file which executes the following function:
function highlightWishlistHearts() {
require(['Magento_Customer/js/customer-data'], function(customerData) {
customerData.reload(['wishlist'], false).complete(function(response) {
var customerWishlist = jQuery.parseJSON(response.responseText).wishlist.items;
for (var i = 0, wishlistLength = customerWishlist.length; i < wishlistLength; i++) {
var productURL = customerWishlist[i].product_url;
jQuery('.product-item-actions').each(function() {
if (productURL === jQuery(this).data('product-url')) {
jQuery(this).children('.towishlist.remove').addClass('inwishlist');
}
});
}
});
});
}
The purpose of this function is to add a class to those products on the page that belong to the user's wishlist.
However, this throws a console error, specifying that sectionLoadUrl is undefined. If I instead call the function from inside a setTimeout, it executes as I would expect, after the specified timeout of 5s.
I want to avoid using a setTimeout, so would encourage any ideas around how I can resolve this issue. Is there perhaps a way to force the magento-init block to be executed when it is defined? Is there perhaps another dependency I could add to my require block that would force this to wait a sufficient length of time? Is there more that I need to be considering? I have tried including the magento-init block in the head of the page, but didn't see any better of a result.
I eventually found that the reload() function was making a request to http://www.example.com/customer/section/load/?sections=wishlist&update_section_id=false, so I instead performed an AJAX request to this page, and read from the JSON on there. Got rid of the setTimeout, as was desired.
Related
Is there anyway to call a function or trigger an event from html loaded from an append() call? I have a tag It is filled when a user licks on a list of things like this. in my index.html I have something like this:
function doThis(someData) {
$.get("/url/"+someData, function(htmlFromServer) {
$("#something").append(htmlFromServer);
});
}
function doSomething(moreData) {
alert(moreData);
}
I want to be able to do something like this in the returned html
<div>
<p>This is an important message</p>
<script>
doSomething("this message is different for each page");
</script>
</div>
I want to be able to call one function, but depending on what is returned, I alert a different message. I want the front end to call one endpoint, but what happens next is dynamic. I don't want to do a huge if block in my doThis(), or worse, a function for each possibility that "someData" may have.
You could pass a function to doThis. You also probably meant to concatenate the someData.
function doThis(someData, cb) {
$.get("/url/" + someData, function(htmlFromServer) {
$("#something").append(htmlFromServer);
cb()
});
}
doThis('somepath', () => console.log('append successful'));
I got this to work by defining all my functions in a javascript file and import like usual.
I then make a decision on the back end that all of the possible html that is returned by the jQuery get() call, has a hidden form field with the id of "x" and the value being the function name I want to call after the .append(htmlFromServer) is done.
So i have this function in a .js file defined int the head as usual. I then do this:
$.get("/url/"+someData, function(htmlFromServer) {
$("#myDiv").append(htmlFromServer).append(function() {
var functionToCall = $("#x").val(); // x is the id of the element
window[functionToCall]();
});
}
And I now have a generic way of handling different data depending on the server response without a big if block.
If I am here asking it is because we are stuck on something that we do not know how to solve. I must admit, we already searched in StackOverflow and search engines about a solution.. but we didn't manage to implement it / solve the problem.
I am trying to create a JavaScript function that:
detects in my html page all the occurrences of an html tag: <alias>
replaces its content with the result of an Ajax call (sending the
content of the tag to the Ajax.php page) + localStorage management
at the end unwraps it from <alias> tag and leaves the content returned from ajax call
the only problem is that in both cases it skips some iterations.
We have made some researches and it seems that the "problem" is that Ajax is asynchronous, so it does not wait for the response before going on with the process. We even saw that "async: false" is not a good solution.
I leave the part of my script that is interested with some brief descriptions
// includes an icon in the page to display the correct change
function multilingual(msg,i) {
// code
}
// function to make an ajax call or a "cache call" if value is in localStorage for a variable
function sendRequest(o) {
console.log(o.variab+': running sendRequest function');
// check if value for that variable is stored and if stored for more than 1 hour
if(window.localStorage && window.localStorage.getItem(o.variab) && window.localStorage.getItem(o.variab+'_exp') > +new Date - 60*60*1000) {
console.log(o.variab+': value from localStorage');
// replace <alias> content with cached value
var cached = window.localStorage.getItem(o.variab);
elements[o.counter].innerHTML = cached;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(window.localStorage.getItem(o.variab),o.counter);
} else {
console.log(o.variab+': starting ajax call');
// not stored yet or older than a month
console.log('variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language);
$.ajax({
type: 'POST',
url: my_ajax_url,
data: 'variable='+o.variab+'&api_key='+o.api_key+'&lang='+o.language,
success: function(msg){
// ajax call, storing new value and expiration + replace <alias> inner html with new value
window.localStorage.setItem(o.variab, msg);
var content = window.localStorage.getItem(o.variab);
window.localStorage.setItem(o.variab+'_exp', +new Date);
console.log(o.variab+': replacement from ajax call');
elements[o.counter].innerHTML = content;
// including icon for multilingual post
console.log(o.variab+': calling multilingual function');
multilingual(msg,o.counter);
},
error: function(msg){
console.warn('an error occured during ajax call');
}
});
}
};
// loop for each <alias> element found
//initial settings
var elements = document.body.getElementsByTagName('alias'),
elem_n = elements.length,
counter = 0;
var i = 0;
for(; i < elem_n;i++) {
var flag = 0;
console.info('var i='+i+' - Now working on '+elements[i].innerHTML);
sendRequest({
variab : elements[i].innerHTML,
api_key : settings.api_key,
language : default_lang,
counter : i
});
$(elements[i]).contents().unwrap().parent();
console.log(elements[i].innerHTML+': wrap removed');
}
I hope that some of you may provide me some valid solutions and/or examples, because we are stuck on this problem :(
From our test, when the value is from cache, the 1st/3rd/5th ... values are replaced correctly
when the value is from ajax the 2nd/4th .. values are replaced
Thanks in advance for your help :)
Your elements array is a live NodeList. When you unwrap things in those <alias> tags, the element disappears from the list. So, you're looking at element 0, and you do the ajax call, and then you get rid of the <alias> tag around the contents. At that instant, element[0] becomes what used to be element[1]. However, your loop increments i, so you skip the new element[0].
There's no reason to use .getElementsByTagName() anyway; you're using jQuery, so use it consistently:
var elements = $("alias");
That'll give you a jQuery object that will (mostly) work like an array, so the rest of your code won't have to change much, if at all.
To solve issues like this in the past, I've done something like the code below, you actually send the target along with the function running the AJAX call, and don't use any global variables because those may change as the for loop runs. Try passing in everything you'll use in the parameters of the function, including the target like I've done:
function loadContent(target, info) {
//ajax call
//on success replace target with new data;
}
$('alias').each(function(){
loadContent($(this), info)
});
I'm relatively new to JavaScript, so please bear with me.
I run an instance of the Blackboard Learn LMS. Feel sorry for me later. Blackboard displays different modules to end users to show different pieces of information. The Announcements module contains non-editable code that sends an Ajax request to display all system-wide and course-specific announcements for that particular user:
<div id="Announcements">
<div id="div_1_1"> </div>
<script type="text/javascript">
Event.observe(window, 'load', function () {
new Ajax.Request('/webapps/portal/execute/tabs/tabAction', {
method: 'post',
parameters: 'action=refreshAjaxModule&modId=_1_1&tabId=_2830_1&tab_tab_group_id=_155_1',
onSuccess: function (transport) {
try {
var res = transport.responseXML.getElementsByTagName('contents')[0].firstChild.nodeValue;
$('div_1_1').innerHTML = res.stripScripts();
page.globalEvalScripts(res, true);
} catch (e) {
$('div_1_1')
.innerHTML = 'Module information is temporarily unavailable. Please reload the page. <!--' + e.toString()
.escapeHTML()
.gsub('-', '-') + '-->';
}
},
onFailure: function (transport) {
$('div_1_1').innerHTML = 'Error loading module.';
}
});
});
</script>
</div>
The module brings up a lot of redundant information that I'd like to hide. Since there's no way to edit that particular code, I've been trying to figure out a way to do one of the following:
Modify the module's contents with additional scripting from another source on the page.
Copy the module's contents to a new, editable module, and display that one instead,
Both methods have proven impossible because the script in the Announcements module only runs once the page has loaded entirely, so there's no way to run a script afterward or wait until the process has completed.
Any ideas on how I might be able to modify the contents without editing its code directly?
I ended up using the DOMSubtreeModified event:
<script type="text/javascript">
$j = jQuery.noConflict();p
$j("#div_1_1").on("DOMSubtreeModified", function () {
var div = document.getElementById("div_1_1");
var inner = div.innerHTML;
inner = inner.substring(
inner.indexOf("<!-- Display course/org announcements -->")
);
div.innerHTML = inner;
});
</script>
i've a strange problem with JS (probably a noob bug), but i'm stuck with it
In function fillInVersionsList, if i put an alert("tempo") or a break in firebug, i can access to my datas in parameter (ie : alert(pSimulator.simulatorData['LastVersion']) and i've the right result. The problem is that if i don't put an alert/firebug break before my access to datas, i've a JS error pSimulator.simulatorData is undefined.
$(document).ready(function() {
var simulator = new Simulator();
// Load SimulatorData into the simulator class
initSimulatorData(simulator);
// Fill in datas into VersionsList (2nd arg = Id of the list)
fillInVersionsList(simulator, $('#VersionsList'));
});
function initSimulatorData(pSimulator)
{
$.ajax({
url: "getData.php?action=init",
success: function(data) {
pSimulator.initSimulatorData(data);
}
});
}
function fillInVersionsList(pSimulator, pSelect)
{
//alert("tempo");
alert(pSimulator.simulatorData['LastVersion']);
pSelect.html('<option>test</option>')
}
function Simulator()
{
var simulatorData;
this.initSimulatorData = function(pSimulatorData)
{
this.simulatorData = pSimulatorData;
}
}
Is there something to solve this problem?
Thanks in advance
I suspect initSimulatorData is loading some data asynchronously.
Adding the alert gives it long enough for the data to be loaded.
You will need to add some sort of callback function, eg:
initSimulatorData(simulator, function () {
// Fill in datas into VersionsList (2nd arg = Id of the list)
fillInVersionsList(simulator, $('#VersionsList'));
});
Whats looks like from your problem is that simulator is taking time to initialize and when fillInVersionsList is called pSimulator is still not completely initalized.
When you put an alert it is getting some time delay by which time simulator is initalized.
Check if there is any callback method after simulator is completely initialized and then call fillInVersionsList method after that.
what does initSimulatorData(simulator) does? Is there any asynchronous code invloved in this?
I recently ran into a familiar javascript/jQuery timing bug and spent too long debugging it. What I need is a smarter debugging path for this problem.
In specific, my issue was that user inputs were supposed to be causing a Mongo database call and the results were sent, after a little math, to displayed outputs. But the displayed outputs were crazily wrong. However, once I added a FireBug break point the problem went away. At that point I knew I had a timing issue, but not how to solve it.
Here are the relavant pieces of code before the error:
handleDataCallBack : function(transport) {
var response = $.parseJSON(transport);
if(!hasErrors) { this.updatePage(response); }
},
accessDatabase : function(){
var params = { ... };
DAL.lookupDatabaseInfo(this.handleCallBackOutputPanel, this, params);
},
calculateValues: function() {
// some numerical values were updated on the page
}
onDomReady : function() {
// ...
//bind drop-down select change events
$('#someDropDown').change(function() {
me.accessDatabase();
me.calculateValues();
});
}
To fix the problem, all I had to do was move the "calculateValues" method from the onDomReady inside the call back:
handleDataCallBack : function(transport) {
var response = $.parseJSON(transport);
this.calculateValues();
if(!hasErrors) { this.updatePage(response); }
},
The problem was that the database hadn't responded before the calculations were started. Sure, that's easy to spot in retrospect. But what methods can I use to debug asynchronous timing issues in javascript/jQuery in the future? This seems well outside the context of IDE tools. And FireBug didn't help. Are there any tools for tracking down asynchronous web development issues? Or maybe some time-tested methods?
i assume your problem is caused here:
$('#someDropDown').change(function() {
me.accessDatabase();
me.calculateValues();
});
this issue is that your calculations are done just right after the call. seeing that the DB call is async, calculate does not wait for it. however, you can do it using "callbacks". i see you do try to implement it and yes, it is correct. however, i find this more elegant:
calculateValues: function() {
// some numerical values were updated on the page
},
//since this is your general callback hander
//you hand over the return data AND the callbackAfter
handleDataCallBack: function(transport, callbackAfter) {
var response = $.parseJSON(transport);
//you may need to use apply, im lost in scoping here
callbackAfter();
//or
callbackAfter.apply(scope);
if (!hasErrors) {
this.updatePage(response);
}
},
accessDatabase: function(callbackAfter) {
var params = {};
//pass callbackAfter to the function,
//after this is done, pass it to the handler
DAL.lookupDatabaseInfo(this.handleCallBackOutputPanel, this, params, callbackAfter);
},
onDomReady: function() {
$('#someDropDown').change(function() {
me.accessDatabase(function() {
//send over what you want done after.
//we'll call it "callbackAfter" for easy tracing
me.calculateValues();
});
});
}