I'm currently modifying a open source project (link) to fit my need.
my English is bad, maybe it will be clearer if you can click on the link page, it will take half of minute to load, browser may seems to be freeze for a moment.
this page uses slickgrid , when hovering mouse over table rows , it will render details of that row on a fixed-position layer on page(the bottom left). there is only one such detail layer on the page, when you move your mouse to another row, this layer will change to show details of that row. following code is how it achieve this :
this.grid.onMouseEnter.subscribe( function (e) {}) // use slickgrid to set event listeners
// inside the handler function
from event e, get the row number row.rc
// then calls the staticOverlayDetails function do the actual work. it will renders the presentation layer.
window.setTimeout(function(){staticOverlayDetails(rc.row);}, 30);
so this staticOverlayDetails function will render the layer ( a div element ) to show the detail of row number rc.row, which is where mouse cursor is at.
I want to collect all the rendered result layer html code so I can combine them into a single page for easy read. that means, I want to hover my mouse on row one, wait for layer to be rendered, copy and save the div element, then move mouse to the next row , wait for the layer to be rendered with this new row , ...repeat until all row are done.
function staticOverlayDetails pseudo code:
function staticOverlayDetails (rown) {
//step 1:generate html text structure for the row, but leaves an element blank, this element is a text description for the row. the text will be loaded from a txt file
...
...
// html_text contains an `div` for description text which is currently empty, the text need to be fetched from a text file url, the text file name is based on the content of row. for the purpose of when the text is fetched, the callingback function need to know where to insert and avoid insert wrongly ( if the mouse has already moved to another row, the layer is showing details of another row, so the description text mismatch), so here it set a random string as the unique id of the description `div`, so later the `insert` function can use the id string as selector to insert text.
description_selector = random_id
html_text= html_code_generated
//step 2:
// this function fetches a , setting a callback function `insert` to insert the content.
url=generate text file url from row content
load_description( url, description_selector )
//step 3: present html
$(fixed-layer).html(txt)
//at this time , the description div is blank. the callback function will insert content into div when fetched.
}
function load_description (url, description_selector) {
function insert ( descrp_text ) {
$(description_selector).html(descrp_text).hide.slide_down()
}
$.get(url, insert, 'text')
}
my plan is to loop through rown to change the fixed layer, then dumps out the fixed layer :
for ( i=0; i < row_counts ; i++ ){
staticOverlayDetails(i)
console.log($(fixed_layer_selector).html())
but the async $.get() call makes this attempt impossible. because only when insert function finished , the content in layer is stable and ready to be dumped, if I use this loop, when $.get returns, the presentation layer is already showing later layers, I don't know how to wait for it.
how can I make my code waiting for the calling back function of $.get() to finish, so I can save the full details for the row, then continue to loop with the next row ?
according to link , I tried to modify the $.get request in the function load_content_for_an_element sync :
function load_description (url, description_selector) {
function insert ( descrp_text ) {
$(description_selector).html(descrp_text).hide.slide_down()
}
$.get({url:url, success:insert, sync:false});
}
but the browser make request to https://host.address/[object%20Object] and it returns with 404 error. seems it convert the passed object to string, then treat it as url to fetch.
some additional info:
1, because this project has several tables each with different rendering logic, I want to make the changes as small as possible, that's why I want to dump the generated presentation layer instead of changing the generating code itself. all different tables render the same layer, that's the easiest way to do it.
2, I want to dump the code to generate an page to read, only need to dump once , so there is no worries about performance. actually I can use a mouse macro to do it , but I think that's too dumb, I want to learn the javascript way to do it :)
I'm so sorry my English is bad and I want to express such a complex situation, so please ask for more infos if my words are not clear, I'm so sorry to see other people waste time because of my mistake words...
Promises are your friend. Here's an example of how you would structure your code to use them:
// Set up a staging area
var staticOverlays = [];
loadStaticOverlayDetails(index) {
return $.get(url, insert, data....).then(function (data) {
staticOverlays[index] = data;
});
}
// Request your data
var promises = [];
for ( var i=0; i < 100; i++ ) {
promises.push(loadStaticOverlayDetails(i));
}
// Wait for the data to complete loading
$.when(promises).then(function insert() {
for ( i=0; i < staticOverlays.length; i++ ) {
var overlay = staticOverlays[i];
// step 1:generate html text structure for the row, but leaves an element blank
var txt = html_code_generated
// insert data into element in fixed layer;
$(fixed-layer).html(txt);
}
});
As an aside, if you're really making 100+ http requests you should consider moving some of this logic to the server because that many http requests will hurt your user experience...
Related
I have report view where a bunch of controls located, there is an example code:
#Html.DevExpress().DropDownEdit(
settings =>{
settings.Name = "SomeList";
settings.Width = 100;
settings.SetDropDownWindowTemplateContent(c =>{
#Html.DevExpress().ListBox(
...
).BindList(ViewData["SomeList"]).Render();
some code...
).GetHtml()
When it render first time it works fine, but in business logic there is another drop down let's call it dropdown #2. When user select some values in dropdown#2 JS handle this action send request to the server to get actual value for first dropdown, then clear all rendered values from dropdown #1 and insert new items.
And problem in performance lies in how devexpress create new items on client side. From backend there is now 8000 new items can be added to dropdown #1 and it keep growing. And user must wait 5-10 seconds when browser render it. I tried to research what is happening when new item is creating but there are a lot of devexpress function which call another functions.
JS code look's like:
SomeList.BeginUpdate();
l = data.SomeList.length;
while (x < l) {
SomeList.AddItem(data.SomeList[x].Name, data.SomeList[x].Id);
x++;
}
SomeList.EndUpdate();
BeginUpdate and EndUpdate is devexpress functions that prohibbit browser render control while adding new items. Without this function it takes eternity to finish adding new items.
I know start point of problem it is - item creating code SomeList.AddItem(...).
So my question: is there way to do something like console.trace(SomeList.AddItem(...)) and see full trace which will be executed on first code pass?
Also is there way to determine which function call take alot of time to execute?
This is more of an architectural issue, but basically, I have a server-side React app that renders a bunch of charts and tables with page breaks in-between, so that a puppeteer instance can open the page then print, and send that printed report back to the user in another app.
I need to be able to take some data that is normally rendered into a table format on this app, but make it printable so that the data extends as far as possible before a page break is required, then renders a new table past the page break (so it appears on a new page when printing), and continues until all of the data is rendered into the report. Essentially, I need pagination on a table, without the user interaction that pagination usually comes with.
The thing I'm struggling with is that the length of the data is dynamic, and so are the widths and heights of the rows.
Any suggestions on how to tackle this? The only thing I can think of so far is to basically hide the table, and measure the height of it after every row is attached, and compare that to the max height (the height of a standard letter size in pixels), and if it exceeds it, remove the row, add a page break, then start a new table.
Thanks in advance.
EDIT:
FYI, The solution mentioned here doesn't apply: How to apply CSS page-break to print a table with lots of rows?
This needs to be an entirely new table because I have custom headers and footers that are going above and below it (showing metadata like the name of the chart, how many rows are shown out of how many total, etc.), so it can't just be one continuous table that's split.
Here's a codepen with a shell of what I'm trying to do. If you open in debug view, and print it, you'll see in the print preview that the table is split up across two pages, but the footer I created will only be on the second page (where it needs to be on both pages, after the table). Additionally, the footer needs to display the dynamic count of rows that were able to fit on the page, so it can't be a static part of the table as a tfoot element. https://codepen.io/nicholaswilson/pen/GRWNzMa
So I'm trying to figure out now if I can mount the table to the DOM, but hide it, and calculate the height as I add rows to it so I can try my original method above. But I'm also open to other suggestions.
Alright, I think I got it. Still needs some tweaking (and there are probably more performant ways to do it) but this is my concept: https://codepen.io/nicholaswilson/pen/abJpLYE. Currently I'm splitting the tables after they've exceeded the height, but I'll be fixing that later. The concept is here.
Basically, the idea is to build a 4D array to represent the instances of tables that need to be rendered. Then in componentDidMount() and componentDidUpdate(), I can add new tables to the state as needed:
componentDidMount() {
const { tableData } = this.props;
if (this.state.currentRowIndex === 0) {
// just starting out
this.setState((state, props) => {
const tables = state.tables;
tables.push([tableData.data[state.currentRowIndex]]); // push first new table and first row
return {
tables,
currentRowIndex: state.currentRowIndex + 1,
currentTableIndex: 0
};
});
}
}
componentDidUpdate() {
const { tableData } = this.props;
if (this.state.currentRowIndex < tableData.data.length) {
this.setState((state, props) => {
const tables = state.tables;
const currentTableHeight = this.tableRefs[this.state.currentTableIndex]
.clientHeight;
console.log(
`Table ${this.state.currentTableIndex} height: ${currentTableHeight}`
);
if (currentTableHeight > MAX_TABLE_HEIGHT_IN_PIXELS) {
// push a new table instead based on this condition
tables.push([tableData.data[state.currentRowIndex]]);
return {
tables,
currentRowIndex: state.currentRowIndex + 1,
currentTableIndex: state.currentTableIndex + 1
};
} else {
tables[state.currentTableIndex].push(
tableData.data[state.currentRowIndex]
); // push new row to existing table
return {
tables,
currentRowIndex: state.currentRowIndex + 1,
currentTableIndex: state.currentTableIndex
};
}
});
}
}
See the codepen for the rest of the implementation.
I have an Angular 4 application in which I need to add the following functionality:
There is a component with a list of objects. When the user double clicks on one of them, the app retrieves from a DB a list of objects and it should scroll to where the object appears.
I'd like to know how I could move to the desired position in the data once that it has been displayed in the browser. Right now, I have the following code:
let objElement = document.querySelector("#object_"+searchItem._objectID);
if (objElement){
objElement.scrollIntoView();
console.log("****** SCROLLING TO OBJECT");
}
The problem is that, the first time that I load the data from the DB, it seems that 'document.querySelector' returns null, as if the HTML wasn't 100% constructed yet, so it doesn't scroll to the position. If I try to locate the element again, it scrolls perfectly (as it doesn't reload the data from the DB).
Is there a "more Angular" way of doing this? I'm trying to find an example like this in the Angular Router documentation but I can't find anything...
EDIT:
To make things clearer, this is the pseudo-code that I run when the user selects an object:
if(selectedObject IS IN currentLoadedObjects) {
scrollTo(selectedObject); // This function runs the code above
}
else { // The object is in a different list, so retrieve it from the DB
ObjectService.getObjectListFromDB(selectedObject)
.subscribe((returnedList) => {
displayObjectList(returnedList); // Basically, this function parses the returned data, which is displayed in the template using an *ngFor loop
scrollTo(selectedObject);
});
}
As you can see, I try to scroll to the object inside the 'subscribe' method, once that I have the data from the database and after I've parsed it. The object list is pretty big, so it takes 1-2 seconds to be displayed in the browser.
Thanks!
I created a select from with popularity,high_to_low and low_to_high as options. I want the page to respond to these options dynamically using the ajax code
var http_option = createRequestObject();
function verifyRequest()
{
var option = document.getElementById("option").value;
if ( option )
{
var url = 'respond.pl?option='+option;
http_option.open('get', url );
http_option.onreadystatechange = handleResponse;
http_option.send(null);
}
}
function handleResponse()
{
if(http_option.readyState == 4 && http_option.status == 200)
{
var response = http_option.responseText; // Text returned FROM perl script
if(response) { // UPDATE ajaxTest content
document.getElementById("id_id").innerHTML = response;
}
}
If the value of the option is 1, images are displayed as stored in DB.
If the option is 2, images are to be displayed in descending order and
If the option is 3, images are to be displayed in ascending order
The respond.pl contain appropriate code for this sorting according to options and display images in a specified div tag.
The problem is that the page responds to the options only once and the next time on changing the option, the value of options shows "on" and not the numerals 1,2,3
I need this not only to sort images but also for faceting. If this is not the right option suggest the appropriate methods used for it.
It looks like your server-side program is returning HTML. And I think that's probably a mistake in this situation. I suggest returning JSON instead. Then, you can create an onChange event handler for your selector which simply re-orders the display. There are almost certainly a number of jQuery plugins that do this without you needing to write very much code.
The short description of the functionality that we are trying to achieve: we have a list of source objects on the left, a person can drag new items from the list to a list on the right, items thus get added to the list on the right; they can also remove items from the list on the right. The list on the right then gets saved whenever it is changed. (I don't think the specifics of how/where it is being saved matter...)
I am having a problem with a bit of timing in the JavaScript vs. DOM elements realm of things. Items that are already on the list on the right can be removed. We have some code that fires on a 'remove/delete' type icon/button on a DOM element, that is supposed to remove the element from the DOM visually and permanently (i.e. it doesn't need to be brought back with a 'show'). This visual change should then also show up in the JSON object that is built when the JS traverses the DOM tree to build the new updated list.
However, this chunk of JS code that runs immediately after this .remove() is called, the element that should have just been removed still shows up in the JSON object. This is not good.
Here are what I believe to be the relevant bits of code operating here. This lives in a web browser; much of this is in the document.ready() function. A given list can also have subsections, hence the sub-list parts and loops.
The on-click definition:
$('body').on('click', '.removeLine', function() {
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
var List=$(this).closest('article'); //Another parent object, containing all the
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
}
);
sendList(List); // This builds and stores the list based on the DOM elements
});
And then later on, this function definition:
function sendList(List) {
var ListArray=[],
subListArray=[],
itemsArray = [],
subListName = "";
var ListTitle = encodeText(List.find('.title').html());
// loop through the subLists
List.find('.subList').each(
function(index, element) {
subListName=($(this).find('header > .title').html()); // Get sublist Title
subListID=($(this).attr('id')); // Get subList ID
// loop through the line items
itemsArray=[];
$(this).find('.itemSearchResult').each(
function(index, element) {
// Build item Array
if( $(this).attr('data-itemid')!= item ) {
itemArray.push( $(this).attr('data-itemid'));
}
}
);
// Build SubList Array with items Array
subListArray.push(
{
"subListName": subListName,
"subListID" : subListID,
"items" : itemsArray
}
);
}
); <!-- end SubList Loop -->
// Complete List Array with subListArray
ListArray ={
"ListName": ListTitle,
"ListID": List.attr('id'),
"subLists": subListArray
};
// Send New List to DataLists Object - the local version of storage
updateDataLists(ListArray);
// Update remote storage
window.location= URLstring + "&$Type=List" + "&$JSON=" + JSON.stringify(ListArray) + "&$objectID=" + ListArray.ListID;
};
It seems to be the interaction of the 'parent.remove()' step and then the call to 'sendList()' that get their wires crossed. Visually, the object on screen looks right, but if we check the data being sent to the storage, it comes through WITH the object that was visually removed.
Thanks,
J
PS. As you can probably tell, we are new at the Javascript thing, so our code may not be terribly efficient or proper. But...it works! (Well, except for this issue. And we have run into this issue a few times. We have a workaround for it, but I would rather understand what is going on here. Learn the deeper workings of JS so we don't create these problems in the first place.)
There's a few things going on here, but I'm going to explain it by approaching it from an asynchronous programming perspective.
You are calling sendList before the element gets removed from the DOM. Your element doesn't get removed from the DOM until after your fadeOut callback gets executed (which takes 300ms).
Your sendList function gets called immediately after you begin the fadeOut, but your program doesn't wait to call sendList until your fadeOut is finished - that's what the callback is for.
So I would approach it by calling sendList in the callback, after your DOM element has been removed like this:
$('body').on('click', '.removeLine', function() {
var el = $(this); //maintain a reference to $(this) to use in the callback
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
sendList(el.closest('article'));
}
);
});