I have a quite complex .Net page with an intensive usage of a third party libraries (DevExpress).
My page is composed of several parts:
A "Result Browser" to let user select the content of two widgets:
A Table widget
A Graphic widget
A timer (AspxTimer) to refresh the "Result browser" contents
A time widget which gives time every second
I make an intensive use of CallBacks (AspxCallBack) to minimize the volume of data to transfer from/to the server for the refresh of the browser/table and graphic.
During tests, each element separately is working well but one of the feature of the table is to re-calculate sub totals of the table when user click on a specific button. I have marked the correct subTotal cells (containing the span) during table construction with appropriate properties so I can find them on client side with javascript (<span ... SubTotal='true'>0.0</span>) and have a vector of class (code, number) to store actual numbers do recalculate subTotal from.
. Here is my code:
function recalcSubTotal() {
$('span[SubTotal="true"]').each(function() {
var subSpan = $(this);
var sTrends = subSpan.attr('trendsTotal');
var Trends = sTrends.split('|');
var subTotal = 0.0;
for (var i = 0; i < Trends.length - 1; i++) {
subTotal += Decision[Trends[i]];
}
subSpan.html(subTotal.toFixed(1));
});
}
This works pretty well in an isolated page but when mixing all this stuff in a single page I randomely have NaN (Not a numer) codes returned by this func or wrong totals, click again the action button and it can work correctly. Nothing else but the relative complexity and parallel usage of javascript+callbacks can really explain this behavior. I managed to avoid any parallel Callback (chaining them) but can't do that with client side events or date/time timer. I wonder If there is a general clean way of dealing with client side parallel run (timer+callbacks+user actions).
Thanks for any clue on this topic.
Try to call your function recalcSubTotal() with the methode setTimeout(), like this :
setTimeout(function(){
recalcSubTotal();
}, 0);
Related
I am coding a Greasemonkey script to click the play button for a script in Google Apps Script every five 5 minutes to avoid the execution time limit set by Google.
I was able to identify with the script when the time is over but am unable to click the "run" button by using JavaScript. I have been inspecting the button with Google Chrome and tried a few things but I couldn't make it work.
Can anyone please help me?
I guess clicking any button in the toolbar of Google Sheets would be exactly the same..
Thanks!
You are approaching this in a completely wrong way. You should be including the possibility to restart the execution with a trigger internally in your script. I will show you how I did it. Keep in mind that my script is quite large and it performs a loop and I had to make it remember where in the loop it stopped, so it could continue with the same data.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Purpose: Check if there is enough time left for another data output run
// Input: Start time of script execution
// Output: Boolean value if time is up
function isTimeUp(start, need) {
var cutoff = 500000 // in miliseconds (5 minutes)
var now = new Date();
return cutoff - (now.getTime() - start.getTime()) < need;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here the start is simply a new Date() that you create when you start the script. The need is simply an average time it takes for my script to perform 1 loop. If there is not enough time for a loop, we will cut off the script with another function.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Purpose: Store current properties and create a trigger to start the script after 1 min
// Input: propertyCarrier object (current script execution properties)
// Output: Created trigger ID
function autoTrigger(passProperties, sysKeys) {
var sysProperties = new systemProperties();
if (typeof sysKeys === 'undefined' || sysKeys === null) {
sysKeys = new systemKeys();
}
var triggerID = ScriptApp.newTrigger('stateRebuild')
.timeBased()
.after(60000)
.create()
.getUniqueId();
Logger.log('~~~ RESTART TRIGGER CREATED ~~~');
//--------------------------------------------------------------------------
// In order to properly retrieve the time later, it is stored in milliseconds
passProperties.timeframe.start = passProperties.timeframe.start.getTime();
passProperties.timeframe.end = passProperties.timeframe.end.getTime();
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Properties are stored in User Properties using JSON
PropertiesService.getUserProperties()
.setProperty(sysKeys.startup.rebuildCache, JSON.stringify(passProperties));
//--------------------------------------------------------------------------
Logger.log('~~~ CURRENT PROPERTIES STORED ~~~');
return triggerID;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Yours can be more simplistic if you do not need to remember where you stopped (judging by your current implementation you do not care whether you start from the beginning or not).
The trigger you create should either aim at the main function or if you need to pass on the data like I do, you will need to have a separate starter function to get the data back from the user properties and pass it on.
I've been using Google Web Designer for a few months and I have a question. I don't know if it's possible to do in GWD:
I want the index.html file to load a different random page, choosing between 3 pages. When you hit reload, it should load another random page, and so on. The pages don't need to appear in order. I'm trying to find out how this can be done but I had no success so far.
This can be accomplished with a custom JavaScript event handler.
The <gwd-doubleclick> element fires an adinitialized event before any content is displayed, which we can use to make sure our changes are applied before the user sees the first page. It also provides a .goToPage(n) method which we can use to switch pages. (goToPage has additional arguments that can be used to control animation between pages, but we can ignore those because we want the default behaviour of instantly jumping.)
Start by adding a new event handler.
target: document.body
event: Google Ad: Ad Initialized
action: Custom: Add Custom Action
configuration: a name of your choice (such as gwd.goToRandomPage), for the following code:
var pages = 3; // adjust as appropriate
var targetPage = Math.floor(Math.random() * pages);
event.target.goToPage(targetPage);
In code view you can see that this produces something like the following:
// This script block is auto-generated. Please do not edit!
gwd.actions.events.registerEventHandlers = function(event) {
gwd.actions.events.addHandler('document.body', 'adinitialized', gwd.goToRandomPage, false);
};
gwd.actions.events.deregisterEventHandlers = function(event) {
gwd.actions.events.removeHandler('document.body', 'adinitialized', gwd.goToRandomPage, false);
};
You could choose to skip the GWD UI and use the standard JavaScript event handling APIs to accomplish the same thing, with something along the lines of:
document.body.addEventListener('adinitialized', function() {
var pages = 3; // adjust as appropriate
var targetPage = Math.floor(Math.random() * pages);
event.target.goToPage(targetPage);
});
However, you probably want to avoid this in general, because it will prevent GWD from handling things like element renaming automatically.
If you'd like to jump to one of a specific set of pages, instead of selecting from all pages, you could use an array of page IDs instead.
var pageIds = ['page1_1', 'page1_2'];
var targetPage = pageIds[Math.floor(Math.random() * pageIds.length)];
event.target.goToPage(targetPage);
For future reference, you can find most of the component APIs described in the documentation. Questions about GWD that do not involve code or are otherwise unsuitable for Stack Overflow should be asked on the GWD support forum instead.
I have a Website which only needs to support IE11
It is a single page application, which has about 200 table rows and each table row has 5 child rows
There is a pulsing function that updates the table as records come in. Table rows are skipped over if no update comes in.
However, when receiving large updates (which should only occasionally happening), the application will hang as it slowly processes the javascript. I've tried to limit the javascript as much as possible, but still have a long running function.
I am a back end developer by nature, and was wondering if anyone had any tips to help support large table ajax updates for IE since IE so poorly handles JS.
Here is the function
function writeTableLines(tempRows){
/* This Function takes care of updating the text and coloring of
required dynamic fields.
All other values are not dynamically written.
*/
for( i in tempRows){
//i is the computer name
tempValues = tempRows[i];
// For Row
selector = "[id='"+i+"']";
// Network Name
network_selector = "[id='"+i+"_network']";
$(network_selector).text(tempValues['network']);
if (tempValues['network_color']){
$(network_selector).addClass(tempValues['network_color']);
$(selector).find('.name').addClass(tempValues['network_color']);
}else{
$(network_selector).removeClass('warning');
$(selector).find('.name').removeClass('warning');
}
// Boot Time
boot_selector = "[id='"+i+"_boot']";
$(boot_selector).text(tempValues['boot']);
if (tempValues['boot_color']){
$(boot_selector).addClass(tempValues['boot_color']);
$(selector).find('.name').addClass(tempValues['boot_color'])
}else{
$(boot_selector).removeClass('issue');
$(selector).find('.name').removeClass('issue');
}
// Last Checked In Timestamp
check_in_selector = "[id='"+i+"_checked_in']";
$(check_in_selector).text(tempValues['checked_in']);
if (tempValues['service_unresponsive']){
$(check_in_selector).addClass('redline');
$(selector).find('.name').addClass('redline');
}else{
$(check_in_selector).removeClass('redline');
$(selector).find('.name').removeClass('redline');
}
util_selector = $(selector).find('td.util').find('a');
$(util_selector).text(tempValues['util'])
if (tempValues['util_class']){
$(util_selector).addClass(tempValues['util_class']);
}else{
$(util_selector).removeClass('redline warning');
}
workgroup_selector = $(selector).find('td.workgroup');
if (($.trim(tempValues['workgroup'])) != $.trim($(workgroup_selector).text())){
if ((tempValues['workgroup'] != selected) && (selected != 'All')){
$(workgroup_selector).addClass('warning');
}else{
$(workgroup_selector).removeClass('warning');
}
}
$(workgroup_selector).text(tempValues['workgroup'])
toggle_links(i, tempRows[i]);
$('#connectionGrid').trigger('updateAll', [false]);
}
}
this function iterates over only received data.
For each row item that was received, update the text of the cell, and add coloring as necessary.
I'm thinking I might just be screwed since its IE, but am open to all suggestions and ideas.
Thanks for reading
Image of the rows - child rows only available when expanded, but still need updates
So In case anyone was wondering.
the last line $('#connectionGrid').trigger('updateAll', [false]); was being executed inside the loop.
As opposed to just once when the loop was finished.
Woops
So I've been stuck on this for quite a while. I asked a similar question here: How exactly does done() work and how can I loop executions inside done()?
but I guess my problem has changed a bit.
So the thing is, I'm loading a lot of streams and it's taking a while to process them all. So to make up for that, I want to at least load the streams that have already been processed onto my webpage, and continue processing stream of tweets at the same time.
loadTweets: function(username) {
$.ajax({
url: '/api/1.0/tweetsForUsername.php?username=' + username
}).done(function (data) {
var json = jQuery.parseJSON(data);
var jsonTweets = json['tweets'];
$.Mustache.load('/mustaches.php', function() {
for (var i = 0; i < jsonTweets.length; i++) {
var tweet = jsonTweets[i];
var optional_id = '_user_tweets';
$('#all-user-tweets').mustache('tweets_tweet', { tweet: tweet, optional_id: optional_id });
configureTweetSentiment(tweet);
configureTweetView(tweet);
}
});
});
}};
}
This is pretty much the structure to my code right now. I guess the problem is the for loop, because nothing will display until the for loop is done. So I have two questions.
How can I get the stream of tweets to display on my website as they're processed?
How can I make sure the Mustache.load() is only executed once while doing this?
The problem is that the UI manipulation and JS operations all run in the same thread. So to solve this problem you should just use a setTimeout function so that the JS operations are queued at the end of all UI operations. You can also pass a parameter for the timeinterval (around 4 ms) so that browsers with a slower JS engine can also perform smoothly.
...
var i = 0;
var timer = setInterval(function() {
var tweet = jsonTweets[i++];
var optional_id = '_user_tweets';
$('#all-user-tweets').mustache('tweets_tweet', {
tweet: tweet,
optional_id: optional_id
});
configureTweetSentiment(tweet);
configureTweetView(tweet);
if(i === jsonTweets.length){
clearInterval(timer);
}
}, 4); //Interval between loading tweets
...
NOTE
The solution is based on the following assumptions -
You are manipulating the dom with the configureTweetSentiment and the configureTweetView methods.
Ideally the solution provided above would not be the best solution. Instead you should create all html elements first in javascript only and at the end append the final html string to a div. You would see a drastic change in performance (Seriously!)
You don't want to use web workers because they are not supported in old browsers. If that's not the case and you are not manipulating the dom with the configure methods then web workers are the way to go for data intensive operations.
I have a situation where I render items from server in GWT grid on FireFox 3.6+. I have approximately 200 items and I load them in loop:
users = myService.getUsers();
for(User user : users){
myPanel.addUser(user); // Pseudocode. Actually i add some labels and text fields...
}
It tooks a long time to change DOM in this loop, so I got "Unresponsive script" notification. But I can not refactor code or make pagination, I need all 200 items on 1 page loaded at once.
Are there any ways to suppress this notification? To notify browser that my script is not hang, but doing something useful?
UPD Ok, here is my code closer. It is a code from big project, and we have many custom components, so, I think, it has no real value.
myService.getUsersDetails(searchCriteria, new MyCallback<List<User>>)
{
#Override
protected void response(List<User> result)
{
gridExpanderPresenter.clear();
int i = 0;
for (User user: result)
{
UserDetailsView detailsView = detailsViewProvider.get();
gridExpanderPresenter.setPresenter(UsersPresenter.this);
gridExpanderPresenter.add(detailsView.asWidget()); //Here is DOM manipulation i mentioned
if (i++ % 2 == 1)
{
detailsView.setOdd(); //Setting style here
}
detailsView.setData(user);
}
}
});
And I think this can help me...
You should add Users before attaching myPanel to the DOM, or if it's already attached and needs to be updated, remove myPanel from the DOM, add all users and reattach myPanel.
What happens when you add 200 users while myPanel is attached is that your browser has to update DOM 200 times which is quite costly (recalculating and repainting the screen).
Several options:
batch your updates using Scheduler.scheduleIncremental; rendering only 10 users or so at a time (don't worry, the Scheduler will run your code as many times as possible in a row before yielding, so it might actually render 50, 100 or 150 users at a time, and then continue 10ms (yes, milliseconds) after)
switch to CellTable; this is a major refactoring, but it'll give you much better performance than anything else you could do based on widgets (Label, TextBox, etc.)