Here is a class I made which uses YQL to do Google Translate.
var Translator = {
source: 'ro', // default
target: 'en', // default
url: 'http://query.yahooapis.com/v1/public/yql?q=select * from google.translate where q="',
urlRemaining: '";&format=json&diagnostics=true&env=store://datatables.org/alltableswithkeys&callback=',
diacritics: Array(),
newCharacters: Array(),
replaceAll: function( string, replace, replaceWith ) {
return string.replace( new RegExp( replace, 'g' ), replaceWith );
},
replaceDiacritics: function( text ) {
string = text;
// diacritics and newCharacters should be arrays of the same length
// diacritics should only be specified in lowercase - uppercased version will be assumed
// durring the process
for ( i = 0; i < this.diacritics.length; i++ ) {
string = this.replaceAll( string, this.diacritics[i], this.newCharacters[i] );
string = this.replaceAll( string, this.diacritics[i].toUpperCase(), this.newCharacters[i].toUpperCase() );
}
return string;
},
translate: function( text, target, source ) {
target = target || this.target;
source = source || this.source;
return $.ajax({
url: this.url + encodeURIComponent( this.replaceDiacritics( text ) ) + '" and source="' + source + '" and target="' + target + this.urlRemaining,
dataType: 'json',
cache: false
});
},
spitResult: function( x, container ) {
x.success(function(realData) {
$report = realData.query.results.json.sentences;
$result = '';
if ($.isArray($report)) {
for (i = 0; i < $report.length; i++) {
$result += $report[i].trans;
}
} else {
$result = $report.trans;
}
if (container instanceof jQuery) {
container.html($result);
} else {
container.innerHTML = $result;
}
});
}
}
And now I'm calling it on a set of elements in the page
promises = Array();
Translator.diacritics = Array('ă', 'â', 'î', 'ș', 'ț');
Translator.newCharacters = Array('a', 'a', 'i', 's', 't');
$('.translate').each(function() {
$this = $(this);
promises[promises.length] = Translator.translate($this.html(), 'en', 'ro');
Translator.spitResult(promises[promises.length-1], $this);
});
This is working no problem with Firefox and Chrome. However, as usual, Internet Explorer (9 in my case) seems to be the problem. From what I've been able to deduce it resides in the promise resolver (Translate.spitResult) - which is called, but no data seem to be passed to it. I looked at it in the console. The promise array element is populated with 3 objects (which I am sure is normal), but it is:
readyState: 0
responseJSON: undefined, status: 0
statusText: "No Transfer".
I tried removing the diacritics function (now I'm not exactly sure why, because there should have been a response anyway), I also tried cache: false mode on the ajax call, but to no avail.
Does anyone know what could be the matter ?
Thank you in advance.
yes Internet Explorer is your problem...
Check http://caniuse.com/#search=promise
I think you can use a polyfill (https://github.com/taylorhakes/promise-polyfill) if that's the problem, never tried a polyfill for promises but it will work like a charm for sure
Related
I have a DataTable and I want to change language of the datatable if the user selects an English version of the site I want to translate datatable to English, or Spanish.
So far my code looks like this:
var langMap = {
en: {
path: 'English',
mods: {
sLengthMenu: "Display _MENU_ records per page - custom test"
}
},
es: {
path: 'Spanish',
mods: {
sLengthMenu: "Mostrar _MENU_ registros - algo muy especial..."
}
}
};
function getLanguage() {
var lang = 'es' //$('html').attr('lang');
var result = null;
var path = '//cdn.datatables.net/plug-ins/1.10.13/i18n/';
$.ajax({
async: false,
url: path + langMap[lang].path + '.json',
success: function(obj) {
result = $.extend({}, obj, langMap[lang].mods)
}
})
return result
}
What I am trying to achieve is this value var lang = 'es' not be hardcoded so, I want to check if the URL contains /es or /en and update that value.
Something like this:
function getLanguage() {
if ( document.location.href.indexOf('/en') > -1 ) {
var lang = 'es';
}
var result = null;
var path = '//cdn.datatables.net/plug-ins/1.10.13/i18n/';
$.ajax({
async: false,
url: path + langMap[lang].path + '.json',
success: function(obj) {
result = $.extend({}, obj, langMap[lang].mods)
}
})
return result
}
Can somebody try to help me with this?
As long as you aren't supporting should-be-dead browsers (i.e. IE), and if it is part of the query string, you can use URLSearchParams for this.
const qs = window.location.search;
const params = new URLSearchParams(qs);
const lang = params.get('lang');
If it's part of the url itself, then you'll have to parse your url. You can use the window.location.pathname to get your url path without the domain. Say it looks like this: https://my.domain.com/some/path/en
// remove prepended '/', then break it up
const pathbits = window.location.pathname.slice(1).split('/');
const lang = pathbits[2];
You can use this function parse_query_string() from this answer https://stackoverflow.com/a/979995/6426512 to get your parameters
then do some simple logic like this
var query_string = window.location.search.substring(1);
var lang = parse_query_string(query_string);
if ( lang==='es') {
//do something;
}
else{
//do something
}
But note this is for newer browsers like the referenced answer says. For older browsers (including Internet Explorer), you can use https://github.com/ungap/url-search-params or the code from the original version of this answer that predates URL:
I need to make 3 or less ajax calls, and the responses need to be appended to the dom in the same order they were requested.
I have the following function, but the problem is that the responses that I get are not necessarily in the correct order when they get appended to the dom.
I wouldn't want to use the async: false property because it blocks the UI and it's a performance hit of course.
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
});
}
};
You need to append the article to a certain position, based on for example the i variable you have. Or you could wait for all of the requests and then append them in order. Something like this:
mod.getArticles = function( ){
var load = function( id ) {
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
});
};
var onDone = function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
};
var requests = [];
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
requests.push(load(mod.vars.pushIds[i]));
}
$.when.apply(this, requests).done(function() {
var results = requests.length > 1 ? arguments : [arguments];
for( var i = 0; i < results.length; i++ ){
onDone(results[i][0]);
}
});
};
Here is an example using i to append them in the proper order when they all finish loading:
mod.getArticles = function( ){
// initialize an empty array of proper size
var articles = Array(3 - mod.vars.ajaxCount);
var completed = 0;
//mod.vars.ajaxCount could start at 0-2
for( var i = mod.vars.ajaxCount; i < 3; i++ ){
// prevent i from being 3 inside of done callback
(function (i){
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[i];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
completed++;
if (data.length) {
// store to array in proper index
articles[i - mod.vars.ajaxCount] = data;
} else {
console.error('get article ajax output error');
}
// if all are completed, push in proper order
if (completed == 3 - mod.vars.ajaxCount) {
// iterate through articles
for (var j = mod.vars.ajaxCount; j < 3; j++) {
// check if article loaded properly
if (articles[j - mod.vars.ajaxCount]) {
mod.appendArticle(articles[j - mod.vars.ajaxCount]);
}
}
}
});
}(i));
}
};
var success1 = $.ajax...
var success2 = $.ajax...
var success3 = $.ajax...
$.when(success1, success2, success3).apply(ans1, ans2, ans3) {
finalDOM = ans1[0]+ans2[0]+ans3[0];
}
Check this for more reference. This is still async, but it waits for all of them to complete. You know the order of invocation already, as its done through your code, so add the dom elements accordingly.
Solutions that rely solely on closures will work up to a point. They will consistently append the articles of a single mod.getArticles() call in the correct order. But consider a second call before the first is fully satisfied. Due to asynchronism of the process, the second call's set of articles could conceivably be appended before the first.
A better solution would guarantee that even a rapid fire sequence of mod.getArticles() calls would :
append each call's articles in the right order
append all sets of articles in the right order
One approach to this is, for each article :
synchronously append a container (a div) to the DOM and keep a reference to it
asynchronously populate the container with content when it arrives.
To achieve this, you will need to modify mod.appendArticle() to accept a second parameter - a reference to a container element.
mod.appendArticle = function(data, $container) {
...
};
For convenience, you may also choose to create a new method, mod.appendArticleContainer(), which creates a div, appends it to the DOM and returns a reference to it.
mod.appendArticleContainer = function() {
//put a container somewhere in the DOM, and return a reference to it.
return $("<div/>").appendTo("wherever");
};
Now, mod.getArticles() is still very simple :
mod.getArticles = function() {
//Here, .slice() returns a new array containing the required portion of `mod.vars.pushIds`.
//This allows `$.map()` to be used instead of a more cumbersome `for` loop.
var promises = $.map(mod.vars.pushIds.slice(mod.vars.ajaxCount, 3), function(id) {
var $container = mod.appendArticleContainer();//<<< synchronous creation of a container
return $.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML'
}).then(function(data) {
if (data.length) {
mod.appendArticle(data, $container);//<<< asynchronous insertion of content
} else {
return $.Deferred().reject(new Error("get article ajax output error"));
}
}).then(null, function(e) {
$container.remove();//container will never be filled, so can be removed.
console.error(e);
return $.when(); // mark promise as "handled"
});
});
return $.when.apply(null, promises);
};
mod.getArticles() now returns a promise of completion to its caller, allowing further chaining if necessary.
Try utilizing items within mod.vars array as indexes; to set as id property of $.ajaxSettings , set returned data at this.id index within an array of responses. results array should be in same order as mod.vars values when all requests completed.
var mod = {
"vars": [0, 1, 2]
};
mod.getArticles = function () {
var results = [];
var ids = this.vars;
var request = function request(id) {
return $.ajax({
type: "GET",
url: "/ajax/article/" + id + "/",
// set `id` at `$.ajaxSettings` ,
// available at returned `jqxhr` object
id: id
})
.then(function (data, textStatus, jqxhr) {
// insert response `data` at `id` index within `results` array
console.log(data); // `data` returned unordered
// set `data` at `id` index within `results
results[this.id] = data;
return results[this.id]
}, function (jqxhr, textStatus, errorThrown) {
console.log("get article ajax error", errorThrown);
return jqxhr
});
};
return $.when.apply(this, $.map(ids, function (id) {
return request(id)
}))
.then(function () {
$.map(arguments, function (value, key) {
if (value.length) {
// append `value`:`data` returned by `$.ajax()`,
// in order set by `mod.vars` items:`id` item at `request`
mod.appendArticle(value);
} else {
console.error("get article ajax output error");
};
})
});
};
mod.getArticles();
jsfiddle http://jsfiddle.net/6j7vempp/2/
Instead of using a for loop. Call your function in response part of previous function.
//create a global variable
var counter = 0;
function yourFunc(){
mod.getArticles = function( ){
//mod.vars.ajaxCount could start at 0-2
//mod.vars.pushIds is an array with the ids to be ajaxed in
var id = mod.vars.pushIds[counter ];
$.ajax({
url: '/ajax/article/' + id + '/',
type: "GET",
dataType: 'HTML',
error: function() {
console.error('get article ajax error');
}
}).done( function( data ) {
if (data.length) {
mod.appendArticle( data );
} else {
console.error('get article ajax output error');
}
//increment & check your loop condition here, so that your responses will be appended in same order
counter++;
if (counter < 3)
{ yourFunc(); }
});
};
}
I'm faced same problem i'm solve this problem using following way.
just use async for get sequence wise response
<script type="text/javascript">
var ajax1 = $.ajax({
async: false,
url: 'url',
type: 'POST',
data: {'Data'},
})
.done(function(response) {
console.log(response);
});
I am trying to analyze anchor links ( their text property ) in PhantomJS.
The retrieval happens here:
var list = page.evaluate(function() {
return document.getElementsByTagName('a');
});
this will return an object with a property length which is good (the same length I get when running document.getElementsByTagName('a'); in the console). But the vast majority of the elements in the object have the value of null which is not good.. I have no idea why this is happening.
I have been playing with converting to a real array thru slice which did no good. I have tried different sites, no difference. I have dumped the .png file to verify proper loading and the site is properly loaded.
This is obviously not the full script, but a minimal script that shows the problem on a well known public site ;)
How can I retrieve the full list of anchors from the loaded page ?
var page = require('webpage').create();
page.onError = function(msg, trace)
{ //Error handling mantra
var msgStack = ['PAGE ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
});
}
console.error(msgStack.join('\n'));
};
phantom.onError = function(msg, trace)
{ //Error handling mantra
var msgStack = ['PHANTOM ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function(t) {
msgStack.push(' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function ? ' (in function ' + t.function +')' : ''));
});
}
console.error(msgStack.join('\n'));
phantom.exit(1);
};
function start( url )
{
page.open( url , function (status)
{
console.log( 'Loaded' , url , ': ' , status );
if( status != 'success' )
phantom.exit( 0 );
page.render( 'login.png');
var list = page.evaluate(function() {
return document.getElementsByTagName('a');
});
console.log( 'List length: ' , list.length );
for( var i = 0 ; i < list.length ; i++ )
{
if( !list[i] )
{
console.log( i , typeof list[i] , list[i] === null , list[i] === undefined );
//list[i] === null -> true for the problematic anchors
continue;
}
console.log( i, list[i].innerText , ',' , list[i].text /*, JSON.stringify( list[i] ) */ );
}
//Exit with grace
phantom.exit( 0 );
});
}
start( 'http://data.stackexchange.com/' );
//start( 'http://data.stackexchange.com/account/login?returnurl=/' );
The current version of phantomjs permits only primitive types (boolean, string, number, [] and {}) to pass to and from the page context. So essentially all functions will be stripped and that is what DOM elements are. t.niese found the quote from the docs:
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
Closures, functions, DOM nodes, etc. will not work!
You need to do a part of the work inside of the page context. If you want the innerText property of every node, then you need to map it to a primitive type first:
var list = page.evaluate(function() {
return Array.prototype.map.call(document.getElementsByTagName('a'), function(a){
return a.innerText;
});
});
console.log(list[0]); // innerText
You can of course map multiple properties at the same time:
return Array.prototype.map.call(document.getElementsByTagName('a'), function(a){
return { text: a.innerText, href: a.href };
});
I have written a UserScript for Greasemonkey (Firefox) and am testing it for compatibility with Chrome's Tampermonkey, and getting errors in the developer console:
Uncaught TypeError: Cannot read property 'profile_url' of undefined
Uncaught TypeError: Cannot read property 'encoded_name' of undefined
The errors seem to be referencing the onreadystatechanged callback of GM_xmlhttpRequest which is called like this:
var flairs = document.querySelectorAll('span.flair');
var steam_re = /(?:(?:https?:\/\/)?www\.)?(?:steam|pc)(?:community\.com\/?(?:(id|profiles)\/?)?|[\s\-_]*id)?[\/:\s\|]*(.{2,}?)(?:[\/|:\-\[(] ?(?:\/?(?:ghost|enforcer|tech|mm|master))+[\[)]?)?$/i
function get_text(e) { return e.innerText || e.textContent; }
function set_text(e, t) {
if (e.innerText)
e.innerText = t;
else
e.textContent = t;
}
var parser = new DOMParser();
for (var i = 0; i < flairs.length; i++) {
var text = get_text(flairs[i]);
var match = steam_re.exec(text);
if (match == null || match.length < 3)
continue;
var type = match[1] || 'id';
var name = encodeURIComponent(match[2]);
var url = 'http://steamcommunity.com/' + type + '/' + name;
var xml_url = url + '?xml=1';
GM_xmlhttpRequest({
method: 'GET',
url: xml_url, // Link to a steam profile with ?xml=1 added
accept: 'text/xml',
context: {
flair_index: i,
flair_text: text, // textContent of span element
encoded_name: name,
profile_url: url, // Link to steam profile
query_url: xml_url
},
onreadystatechange: function(response) {
if (response.readyState != 4)
return;
// Attempt to fall back to alternate forms of context,
// none of which works. response.context works on Firefox/Greasemonkey.
var context = response.context || this.context || context;
var doc = parser.parseFromString(response.responseText, 'text/xml');
var validProfile = doc.documentElement.nodeName == 'profile';
var a = document.createElement('a');
a.href = validProfile ?
context.profile_url : // TypeError here, context is undefined
('http://steamcommunity.com/actions/SearchFriends?K=' + context.encoded_name);
a.className += (validProfile ? 'steam-profile-link' : 'steam-profile-search-link');
var a_text = document.createTextNode(context.flair_text);
a.appendChild(a_text);
set_text(flairs[context.flair_index], '');
flairs[context.flair_index].appendChild(a);
}
});
}
The function itself is called fine, and the callback is invoked, but once I try to access the context var inside it, it's undefined.
It all works as expected in Firefox. What it does is iterating over span elements that have the "flair" class and checking with a regex if they contain a Steam username, and if so, makes it a link to their SteamCommunity page. (Full source on github). The script runs on /r/PaydayTheHeistOnline.
I have tested using an array defined outside the function to store the data instead of using the context property passed to xmlhttpRequest, but I'm getting the exact same error.
Update:
Tampermonkey now reports that this feature is fixed as of version 3.8.4116 (in beta at the moment). See:
The bug report
The change log
Older/generic workaround:
The context property is a relatively new feature of GM_xmlhttpRequest(), in Firefox. I doubt it's been implemented in Tampermonkey yet; see Tampermonkey's docs for GM_xmlhttpRequest().
Meanwhile, the tried-and-true method for this kind of thing is to use a closure.
Change your GM_xmlhttpRequest() call to something like:
( function (flair_index, flair_text, encoded_name, profile_url, query_url) {
GM_xmlhttpRequest ( {
method: 'GET',
url: xml_url, // Link to a steam profile with ?xml=1 added
accept: 'text/xml',
onreadystatechange: function (response) {
if (response.readyState != 4)
return;
var doc = parser.parseFromString (response.responseText, 'text/xml');
var validProfile = doc.documentElement.nodeName == 'profile';
var a = document.createElement ('a');
a.href = validProfile ?
profile_url :
('http://steamcommunity.com/actions/SearchFriends?K=' + encoded_name);
a.className += (validProfile ? 'steam-profile-link' : 'steam-profile-search-link');
var a_text = document.createTextNode (flair_text);
a.appendChild (a_text);
set_text (flairs[flair_index], '');
flairs[flair_index].appendChild (a);
}
} );
} ) (
i,
text, // textContent of span element
name,
url, // Link to steam profile
xml_url
);
Can't understand why my key=value pairs transform into symbols and in my ajax GET call I have:
GET /admin_schedule/get_schedule_db/?0=%5B&1=o&2=b&3=j&4=e&5=c&6=t&7=+&8=O&9=b&10=j&11=e&12=c&13=t&14=%5D&15=%22&16=%26&17=t&18=e&19=a&20=c
Instead of:
GET /admin_schedule/get_schedule_db/?teacherArray[]=128&teacherArray[]=134...
My code:
var eventss = '';
$("input[type='checkbox'][name='teacher']").each( function() {
if(this.checked) {
eventss += "&teacherArray[]=" + $(this).attr("value");
}
});
events1.data += eventss;
ajax for fullcalendar eventSources:
var events1 = {
url: '/admin_schedule/get_schedule_db/',
type: 'GET',
data: {sch_teacher_id: (sch_teacher_id) ? sch_teacher_id : $('.teacher').val() },
success: function (response) {
return response;
}
};
And then fetch fullcalendar with events
eventSources: [
events1,
events2,
events3
],
Concatenating a string with an object is almost never a good idea as Object#toString always returns "[object Object]". Unless you override toString in your object, the object is cast to string as this string (meaning its content is lost) before it's concatenated. Moreover, the resulting string is not a valid query string.
Instead of
eventss += "&teacherArray[]=" + $(this).attr("value");
...
events1.data += eventss;
try creating an empty teacherArray in data and
events1.data.teacherArray.push($(this).attr("value"));
Also consider using $("#my-form").serialize()
I solved this problem! (thx Jan Dvorak for your comments!).
1. variable now is an array:
var sch_teacher_id = new Array("<?php echo $sch_teacher_id; ?>");
2. empty the array each time before loop:
$('.teacher').change(function (event) {
events1.data.sch_teacher_id = [];
events2.data.sch_teacher_id = [];
events3.data.sch_teacher_id = [];
if($(this).val()) {
$("input[type='checkbox'][name='teacher']").each( function() {
if(this.checked) {
events1.data.sch_teacher_id.push($(this).attr("value"));
events2.data.sch_teacher_id.push($(this).attr("value"));
events3.data.sch_teacher_id.push($(this).attr("value"));
}
});
}
$calendar.fullCalendar('refetchEvents');
});