I have a script (below) that asynchronously updates markup on setInterval; markup which is generated with jQuery from XML data. This is my attempt at creating a UI in which users can view to see changes happen to the XML data in real-time. However, this is seeming like a round about way of acheiving the desired effect compared to Web Workers API; I am finding out that my AJAX script and setInterval function are unreliable; the script appears to freeze or not respond at certain initial loads and after running for long periods of time points . How can I modify my code to use workers instead of AJAX or setInterval?
setInterval(refreshXml, 1500);
function refreshXml() {
var req = $.get('Administration/data/people.xml');
req.done(function(xml) {
// Update the global XML variable used to create buttons.
window.peopleXml = xml;
// Clear existing buttons.
$('#loadMe').empty();
// Display a button for each XML person entity.
$(xml).find('fullName').each(function(index) {
var fullName = $(this).text();
$('<button>', {
'class': 'mybutton',
value: $(this).siblings('id').text(),
text: fullName
}).appendTo('#loadMe');
});
// Update any person divs that were already visible.
$('#toadMe .person').each(function() {
// Grabs the ID from data-person-id set earlier.
var id = $(this).data('person-id');
show_person(id);
});
});
}
function show_person(id) {
$('#person-detail-' + id).remove();
get_person(id).appendTo('#toadMe');
}
function get_person(id) {
var $person = $(window.peopleXml).find('id:contains(' + id + ')').parent();
var $div = $('<div>', {
'class': 'person',
'data-person-id': id,
id: 'person-detail-' + id
});
$('<h1>', { text: $person.find('firstName').text() }).appendTo($div);
$('<h1>', { text: $person.find('lastName').text() }).appendTo($div);
$('<h1>', { text: $person.find('age').text() }).appendTo($div);
$('<h1>', { text: $person.find('hometown').text() }).appendTo($div);
$('<h1>', { text: $person.find('job').text() }).appendTo($div);
return $div;
}
$(document).on('click', '.mybutton', function() {
$('#toadMe').empty();
show_person(this.value);
});
The name of the above script is home.js and here is an example of an index page (index.html) and a worker (my_task.js):
// index.html
<script>
var myWorker = new Worker("my_task.js");
myWorker.onmessage = function (oEvent) {
console.log("Worker said : " + oEvent.data);
};
myWorker.postMessage("ali");
// my_task.js
postMessage("I\'m working before postMessage(\'ali\').");
onmessage = function (oEvent) {
postMessage("Hi " + oEvent.data);
};
How can I implement home.js in a way in which index.html and my_task.js are implemented? Thanks a ton, I am really just looking for a way to get starting using workers as the next level up since I just recently learned AJAX. Also, I know this could possibly be seen as a broad question so I am willing to improve my question upon request and suggestions.
Related
So I'm getting data from a back-end through an AJAX GET method, and presenting it in a list(below) in html. I tried to put the button tag in there and I get the buttons on the list but I'm not sure how to use the delegate and others to make it work.
So how can I put independent buttons that send the users to a details page about the cafeteria in that list? (This is just a personal project)
$(function(){
var $cafeterias = $('#cafeterias');
var $Name = $('#CName');
var $Location = $('#CLocation');
function DispCafeteria(cafeteria) {
$cafeterias.append('<li> Name: '+cafeteria.Name+'Location: '+cafeteria.Location+'<button id="Details">Details</button>'+'</li>');
}
$.ajax({
type: 'GET',
url: 'some url',
success: function (cafeterias) {
$.each (cafeterias, function (i, cafeteria){
DispCafeteria(cafeteria);
});
},
error: function() {
alert('Error while loading cafeterias');
}
});
});
A few nit pick:
Don't name functions with a leading capital letter unless your planning to instantiate it (constructor function). It confuses people since this is the common de facto standard.
Avoid nested callback logic. Use a promise interface instead. (jQuery has one).
Don't construct interactive DOM elements with HTML strings. It makes it difficult to attach events to it. An exception is using event delegation which in your example is a better more performant way to do that.
You should break your problem space down. Separate concerns into smaller chunks.
Fetch AJAX Data:
function fetchData() {
return $.ajax({
type: 'GET',
url: 'some url'
});
}
Handle errors:
function handleErrors(err) {
alert('Error while loading cafeterias');
}
Construct the DOM:
function cafeteriaToString(cafeteria) {
return 'Name: ' + cafeteria.name +
' Location: ' + cafeteria.location;
}
function constructDataTable(cafeterias) {
$.each(cafeterias, function (i, cafeteria) {
var $button = $('<button/>')
.text('Details')
.data('details-id', i);
$('<li/>')
.text(cafeteriaToString(cafeteria))
.append($button);
});
}
Attach a delegated event:
function handleButtonClicks(e) {
var detailsId = $(this).data('details-id');
// Do something with detailsId
}
Putting it all together:
function init() {
fetchData()
.then(constructDataTable)
.fail(handleErrors);
$('ul').on('click', 'li>button', handleButtonClicks);
}
test.view.js
timeDBox = new sap.ui.commons.DropdownBox({layoutData: new sap.ui.layout.GridData({linebreak: true}),
change: function(oEvent){
oController.getKeyEqChart();
},
}),
new sap.ui.core.HTML({
content: "<div id=\"chart1\"></div>",
afterRendering: function(e){
console.log("chart1 create"+timeDBox.getValue());
chart1DivReady = true;
oController.getchart();
}
})
test.controller.js
onInit: function() {
var modelDataEvent = {"genericTableModel":[{"xtime":"1"},{"xtime":"2"},{"xtime":"3"},{"xtime":"4"},{"xtime":"5"},{"xtime":"8"},{"xtime":"10"}]}
var oTemplate11 = new sap.ui.core.ListItem({text : "{xtime}", key : "{xtime}"});
timeDBox.setModel(new sap.ui.model.json.JSONModel(modelDataEvent));
timeDBox.bindItems("/genericTableModel", oTemplate11);
timeDBox.getModel().refresh();
this.getchart();
},
getchart: function(){
var jsonObjToSend = {} ;
jsonObjToSend["dialogue"] = "terminal";
jsonObjToSend["cid"] = "key_equipment ";
var srachmap = {} ;
srachmap["xtime"] = timeDBox.getValue();
jsonObjToSend["search"] = srachmap; this.doAjax("/uri/uri",jsonObjToSend).done(this.updateKeyEqChart);
},
updateKeyEqChart: function(modelData) {
var svg = d3.select("#chart1").append("svg")
1) if i call getchart method from onInit, chart1 id is not created when executing this method
2) if i call getchart chart from oController.getchart() at that time timeDBox.getValue() value is not created which is required to get chart data
},
I am using a drop down list in my application which is populated from database.
Following things happen after the drop down gets populated:
Once the drop down gets populated I use the value of the drop down to render a chart by doing another ajax call to the db.
If the drop down is not populated by the time the flow reaches there then later the chart is not rendered but with time the drop down gets rendered as the ajax where I send param from drop down is null as the drop down is not ready.
So how to make the control wait till the drop down is populated and then go the chart call.
I am not 100% sure that I understand your questions right and the code sample being almost unreadable doesn't help.
But I think onInit might not be the lifecycle hook you are looking for.
If it is a one time deal, I would use onAfterRendering:
onAfterRendering: function() {
// Code
}
If this has to be executed everytime you navigate to this page, then I would add onAfterShow/onBeforeShow delegates in the onInit function.
onInit: function () {
view.addEventDelegate({
/**
* use either or in your case
*/
onAfterShow: function (oEvt) {
// If you use a busy dialog, you want to close it here
},
onBeforeShow: function (oEvt) {
}
});
},
Hope this helps.
Ok, so I need some insight into working with History.js and jQuery.
I have it set up and working (just not quite as you'd expect).
What I have is as follows:
$(function() {
var History = window.History;
if ( !History.enabled ) {
return false;
}
// Capture all the links to push their url to the history stack and trigger the StateChange Event
$('.ajax-link').click(function(e) {
e.preventDefault();
var url = this.href; //Tells us which page to load
var id = $(this).data('passid'); //Pass ID -- the ID in which to save in our state object
e.preventDefault();
console.log('url: '+url+' id:'+id);
History.pushState({ 'passid' : id }, $(this).text(), url);
});
History.Adapter.bind(window, 'statechange', function() {
console.log('state changed');
var State = History.getState(),
id = State.data.editid; //the ID passed, if available
$.get(State.url,
{ id: State.data.passid },
function(response) {
$('#subContent').fadeOut(200, function(){
var newContent = $(response).find('#subContent').html();
$('#subContent').html(newContent);
var scripts = $('script');
scripts.each(function(i) {
jQuery.globalEval($(this).text());
});
$('#subContent').fadeIn(200);
});
});
});
}); //end dom ready
It works as you'd expect as far as changing the url, passing the ID, changing the content. My question is this:
If I press back/forward on my browser a couple times the subContent section will basically fadeIn/fadeOut multiple times.
Any insight is appreciated. Thanks
===================================================
Edit: The problem was in my calling all of my <script> and Eval them on each statechange. By adding a class="no-reload" to the history controlling script tag I was able to do:
var scripts = $('script').not('.no-reload');
This got rid of the problem and it now works as intended. Figure I will leave this here in case anyone else runs into the same issue as I did.
The problem was in my calling of all of my <script> and Eval them on each statechange. By adding a class="no-reload" to the history controlling script tag I was able to do:
var scripts = $('script').not('.no-reload');
This got rid of the problem and it now works as intended. Figure I will leave this here in case anyone else runs into the same issue as I did.
I'm working on a magento project, and I'm trying to load more products on the click of the more button.
I can see them loading but then it will just load a blank page after it.
I have no idea what is happening or why.
This is the code I have
var loadMore = Class.create({
initialize: function (list, href, pattern) {
var that = this;
this.list = list;
this.list.insert({ after : '<div class="more"><span id="more_button" class="more-button">More</span></div>'});
this.href = href.readAttribute('href');
this.button = $('more_button');
this.holder = new Element('div', { 'class': 'response-holder' });
this.button.observe('click', function () {
if ( !that.button.hasClassName('loading') ) {
new Ajax.Request(that.href, {
onCreate: function () {
that.button.addClassName('loading');
},
onSuccess: function(response) {
if (200 == response.status) {
that.holder.update(response.responseText).select(pattern).each(function(elem) {
that.list.insert({ bottom : elem });
}),
that.href = that.holder.select('.next-page')[0].readAttribute('href');
that.button.removeClassName('loading');
if ( !that.href ) {
that.button.up().remove();
}
}
}
});
}
});
}
});
If anyone can help me out that would be awesome!
Thanks in advance.
I've having the same problem in my magento Iphone orginal theme, but the error is because of code injection, mostly "script" tags from google analytics, clicktale and similar stuff.
what i've done to fix it was to "parse" the ajax response and modify the opening "script" tag with the html entity:
below the line 117 (aprox in iphone.js)
if (200 == response.status) {
that.holder.update(response.responseText).select(pattern).each(function(elem) {
replace with this:
str = response.responseText;
str = str.replace(/<script/gi, '<script');
that.holder.update(str).select(pattern).each(function(elem) {
Might I suggest you rewrite your code and use thiz for that? Your code is extremely hard to read.
I do not see any reason to use the onCreate event of the Ajax Request, which by the way is reserved for Ajax Responders (per spec: http://prototypejs.org/doc/latest/ajax/Ajax/Request/)
Instead, you can add this classname at the moment you enter into !that.button.hasClassName('loading') ...
if ( !that.button.hasClassName('loading') ) {
that.button.addClassName('loading');
new Ajax.Request(that.href, {
....
There is a lot more going on behind the scene, like your CSS, magento of course, but also containing and parent html elements so it is very difficult to give any sound advice. What have you done in order to debug this?
Karl..
I've got two RequireJS modules, one for fetching data from an external service, one in charge of passing a callback to the first module.
Here is the first very basic module:
define(["jquery"], function($) {
return {
/**
* Retrieves all the companies that do not employs the provided employee
* #param employeeId ID of the employee
* #param successCallback callback executed on successful request completion
* #return matching companies
*/
fetchCompanies: function(employeeId, successCallback) {
var url = '/employees/' + employeeId + '/nonEmployers';
return $.getJSON(url, successCallback);
}
};
});
And the most interesting one, that will generate a new drop-down and inject it into the specified DOM element (this is the one under test):
define([
'jquery',
'vendor/underscore',
'modules/non-employers',
'text!tpl/employeeOption.tpl'], function($, _, nonEmployers, employeeTemplate) {
var updateCompanies = function(selectedEmployeeId, companyDropDownSelector) {
nonEmployers.fetchCompanies(selectedEmployeeId, function(data) {
var template = _.template(employeeTemplate),
newContents = _.reduce(data, function(string,element) {
return string + template({
value: element.id,
display: element.name
});
}, "<option value='-1'>select a client...</option>\n");
$(companyDropDownSelector).html(newContents);
});
};
return {
/**
* Updates the dropdown identified by companyDropDownSelector
* with the companies that are non employing the selected employee
* #param employeeDropDownSelector selector of the employee dropdown
* #param companyDropDownSelector selector of the company dropdown
*/
observeEmployees: function(employeeDropDownSelector, companyDropDownSelector) {
$(employeeDropDownSelector).change(function() {
var selectedEmployeeId = $(employeeDropDownSelector + " option:selected").val();
if (selectedEmployeeId > 0) {
updateCompanies(selectedEmployeeId, companyDropDownSelector);
}
});
}
};
});
I'm trying to test this last module, using Jasmine-fixtures and using waitsFor, to asynchronously check that the set-up test DOM structure has been modified. However, the timeout is always reached.
If you can spot what's wrong in the following test, I'd be most grateful (gist:https://gist.github.com/fbiville/6223bb346476ca88f55d):
define(["jquery", "modules/non-employers", "modules/pages/activities"], function($, nonEmployers, activities) {
describe("activities test suite", function() {
var $form, $employeesDropDown, $companiesDropDown;
beforeEach(function() {
$form = affix('form[id=testForm]');
$employeesDropDown = $form.affix('select[id=employees]');
$employeesDropDown.affix('option[selected=selected]');
$employeesDropDown.affix('option[value=1]');
$companiesDropDown = $form.affix('select[id=companies]');
$companiesDropDown.affix('option');
});
it("should update the company dropdown", function() {
spyOn(nonEmployers, "fetchCompanies").andCallFake(function(employeeId, callback) {
callback([{id: 42, name: "ACME"}, {id: 100, name: "OUI"}]);
});
activities.observeEmployees('#employees', '#companies');
$('#employees').trigger('change');
waitsFor(function() {
var companiesContents = $('#companies').html(),
result = expect(companiesContents).toContain('<option value="42">ACME</option>');
return result && expect(companiesContents).toContain('<option value="100">OUI</option>');
}, 'DOM has never been updated', 10000);
});
});
});
Thanks in advance!
Rolf
P.S.: replacing $(employeeDropDownSelector).change by $(employeeDropDownSelector).on('change', and/or wrapping the activities.observeEmployees call (and $('#employees').trigger('change');) with a domReady yields the same result
P.P.S.: this error is the cause -> SEVERE: runtimeError: message=[An invalid or illegal selector was specified (selector: '[id='employees'] :selected' error: Invalid selector: *[id="employees"] *:selected).] sourceName=[http://localhost:59811/src/vendor/require-jquery.js] line=[6002] lineSource=[null] lineOffset=[0].
P.P.P.S.: it seems HtmlUnit doesn't support CSS3 selectors (WTF?), and even forcing the latest published version as jasmine-maven-plugin dependency won't change anything...
Is there any way to change jasmine plugin runner ?
OK guys.
Solution found:
upgrade (if not already) to jasmine-maven-plugin v1.3.1.1 (or later)
configure phantomjs instead of this crappy HtmlUnit (add PhantomJS binaries to your project)
if you've got use of ':focus' selector in your code, beware of this bug, replace it with $(mySelector).get(0) == document.activeElement
also, do not forget to wrap your code blocks by run(function() { /* expect */ }) if they are positioned after and depend on your waitsFor condition.
Finally, all should be well.
See how is the test now:
define(["jquery",
"modules/nonEmployers",
"modules/pages/activities"], function($, nonEmployers, activities) {
describe("activities test suite", function() {
var $form, $employeesDropDown, $companiesDropDown;
beforeEach(function() {
$form = affix('form[id=testForm]');
$employeesDropDown = $form.affix('select[id=employees]');
$employeesDropDown.affix('option[selected=selected]');
$employeesDropDown.affix('option[value=1]');
$companiesDropDown = $form.affix('select[id=companies]');
$companiesDropDown.affix('option');
spyOn(nonEmployers, "fetchCompanies").andCallFake(function(employeeId, callback) {
callback([{id: 42, name: "ACME"}, {id: 100, name: "OUI"}]);
});
});
it("should update the company dropdown", function() {
$(document).ready(function() {
activities.observeEmployees('#employees', '#companies');
$('#employees option[selected=selected]').removeAttr("selected");
$('#employees option[value=1]').attr("selected", "selected");
$('#employees').trigger('change');
waitsFor(function() {
var dropDown = $('#companies').html();
return dropDown.indexOf('ACME') > 0 && dropDown.indexOf('OUI') > 0;
}, 'DOM has never been updated', 500);
runs(function() {
var dropDown = $('#companies').html();
expect(dropDown).toContain('<option value="42">ACME</option>');
expect(dropDown).toContain('<option value="100">OUI</option>');
});
});
});
});
});
Creating modules this way is really difficult. I'd recommend not using fixtures and not rendering anywhere actually. Instead using detached DOM elements to do all the work is much easier.
Imagine if your code looked closer to this:
define([
'jquery',
'vendor/underscore',
'modules/non-employers',
'text!tpl/employeeOption.tpl'], function($, _, nonEmployers, employeeTemplate) {
return {
init: function() {
this.$companies = $('<select class="js-companies"></select>');
},
render: function(data) {
var template = _.template(employeeTemplate),
newContents = _.reduce(data, function(string,element) {
return string + template({
value: element.id,
display: element.name
});
}, "<option value='-1'>select a client...</option>\n");
this.$companies.empty().append(newContents);
return this;
});
observeEmployees: function(employeeDropDownSelector) {
$(employeeDropDownSelector).change(function() {
var selectedEmployeeId = $(employeeDropDownSelector + " option:selected").val();
if (selectedEmployeeId > 0) {
nonEmployers.fetchCompanies(selectedEmployeeId, function(data) {
this.render(data);
}
}
});
}
};
});
The above is not complete. It is just to give you an idea of another way to approach your problem. Now instead of a fixture all you need to do is inspect this.$companies and you will be done. I think the main problem though is that your functions are not simple enough. The concern of each function should be extremely specific. Your updateCompanies function is doing things like creating a template, fetching data then passing it to an anonymous function, which can't be spied on, that anonymous function iterates on an object, then you change some already existing DOM element. That sounds exhausting. All that function should do is look at some precompiled template send it an object. The template should loop on the object using {{each}} then return. Your function then empties and append the newContents and returns it self so the next function down can choose what it should do with this.$companies. Or if this.$companies has already been append to the page nothing needs to be done at all.