DOM-based data storage, best choices/performances? - ajax
This question is about load data embedded into DOM structure.
I am using jQuery2, but the question is valid for any other framework or single Javascript code.
There are two scenarios:
When the data is load once (with the page), no "refresh data" is need.
When data is refreshed by some event.
The average performance can be changed with either one or other
Suppose a typical case of scenario-2, where a page fragment must be reloaded, with new HTML and new data. So, the $('#myDiv').load('newHtmlFragment') will be used any way... And, for jQuery programmer, using AJAX, there are two ways to load an "DOM-based data":
by HTML: expressing all data into the "newHtmlFragment" HTML. Suppose many paragraphs, each like <p id="someId" data-X="someContent">...more content</p>. There are some "verbose overhead" for each data-X1="contentX1" data-X2="contentX2" ..., and is not elegant for webservice script if it is not an XHTML-oriented one (I am using PHP, my data is an array, and I preffer to use json_encode).
by jQuery evaluation: using the same $('#myDiv').load('newHtmlFragment') only for <p id="someId">...more content</p>, with no data-X. A second AJAX load an jQuery script like $('#someId').data(...) and evaluate it. So this is an overhead, for node-selection and data-inclusion, but with big item-data, each data can be enconded by JSON.
by pure JSON: similar to "by jQuery", but the second AJAX load an JSON object like var ALLDATA={'someId1':{...}, 'someId2':{...}, ...}. So this is an overhead for a static function that executes something like $('#myDiv p').each(function(){... foreach ... $(this).data('x',ALLDATA[id]['x']);}) retrive selection, but with big data, all data can be enconded by JSON.
The question: what the best choice? It depends on scenarios or another context parameter? There are signfificative performance tradeoffs?
PS: a complete answer needs to address the issue of performance... If no significative performance differences, the best choice relies on "best programming style" and software engineering considerations.
More context, if you need as reference to answer. My practical problem is at scenario-1, and I am using the second choice, "by jQuery script", executing:
$('#someId1').data({'bbox':[201733.2,7559711.5,202469.4,7560794.9],'2011-07':[3,6],'2011-08':[2,3],'2011-10':[4,4],'2012-01':[1,2],'2012-02':[12,12],'2012-03':[3,6],'2012-04':[6,12],'2012-05':[3,4],'2012-06':[2,4],'2012-07':[3,5],'2012-08':[10,11],'2012-09':[7,10],'2012-10':[1,2],'2012-12':[2,2],'2013-01':[6,10],'2013-02':[19,26],'2013-03':[2,4],'2013-04':[5,8],'2013-05':[4,5],'2013-06':[4,4]});
$('#someId2').data({'bbox':[197131.7,7564525.9,198932.0,7565798.1],'2011-07':[39,51],'2011-08':[2,3],'2011-09':[4,5],'2011-10':[13,14],'2011-11':[40,42],'2011-12':[21,25],'2012-01':[10,11],'2012-02':[26,31],'2012-03':[27,35],'2012-04':[8,10],'2012-05':[24,36],'2012-06':[4,7],'2012-07':[25,30],'2012-08':[9,11],'2012-09':[42,52],'2012-10':[4,7],'2012-11':[17,22],'2012-12':[7,8],'2013-01':[21,25],'2013-02':[5,8],'2013-03':[8,11],'2013-04':[28,40],'2013-05':[55,63],'2013-06':[1,1]});
$('#...').data(...); ... more 50 similar lines...
This question can be discussed from different aspects. Two that I can think of right now would be software engineering and end-user experience. A comprehensive solution covering first can also cover the later but as usually it's not possible to come up with such a solution (due to its cost) these two hardly overlap.
Software engineering point of view
In this POV it is strongly suggested that different parts of the system to be as isolated as possible. This means you better postpone the marriage of data and view as late as you can. It helps you to divide your developers into two separate groups; those who know server-side programming and have no idea how HTML (or any other interface layer technology) works and those who are solely experienced on HTML and Javascript. Just this division alone is a blessing for management and it helps greatly in big projects where teamwork is essential. It also helps the maintenance and expansion of the system, all the things software engineering aims at.
User experience point of view
As good as the previous solution sounds, it comes with (solvable) drawbacks. As you mentioned in your question, if we are to load view and data separately, it elevates the number of requests we have to send to server to retrieve them. It imposes two problems, first the overhead that comes with each request and second the delay user has to wait for each request to be responded. The second is more obvious so let's start with that. With all the advances to the Internet and bandwidths, yet our users' exceeding expectations enforce us to consider this delay. One way to reduce the number of requests would be your first choice: data within HTML fragments. Multiple number of requests also has an overhead problem as well. This can be explained by HTTP protocol's handshake (both on client-side and server-side) and by the fact that each request will lead to loading the session on server which in a large scale could be pretty considerable. So again your first option could be the answer to this problem.
Tie breaker
Having both sides of the story said, what then? The ultimate solution is a combination of both where data and view are married on client but they are downloaded at the same time. To be honest I don't know of such a library. But the principle is simple, you need a mechanism to package data and empty HTML fragments within the same response and combine them into what user will see on client. This solution is costy (to implement) but it is sort of the cost that once paid you can benefit from it for a life time.
It depends on how you are going to use the data that is stored. Consider this example:
Example
You have 300 items in a database (say server access logs of the last 300 days). Now you want to display 300 <div> tags, each tag representing one database-item.
Now there are 2 options to do this:
<div data-stat1="stat1" data-stat2="stat2" data-stat3="stat3">(...)</div>
<!-- again, this is repeated 300 times -->
<script>
// Example on how to show "stat1" value in all <div>s
function showStat1() {
for(var i=1; i<=300; i+= 1) {
var theID = '#id-' + i;
jQuery(theID).text(jQuery(theID).data('stat1'));
}
}
</script>
OR
<div id="id-1">(...)</div>
<!-- repeat this 300 times, for all DB items -->
<script>
data = { // JSON data which is displayed in the <div> tags
'1': ['stat1', 'stat2', 'stat3'],
// same for all 300 items
}
// Example on how to show "stat1" value in all <div>s
function showStat1() {
for(var i=1; i<=300; i+= 1) {
var theID = '#id-' + i;
jQuery(theID).text(data[i][0]);
}
}
</script>
Which scenario is better?
Case 1:
The data is directly coded into the DOM elements, which makes this one an easy to implement solution. You can see in my examples that the first code block takes a lot less code to generate and the code is less complex.
This solution is very powerful, when you want to store data that is directly related to an DOM element (as you do not have to create a logical connection between JSON and DOM): It is very simple to access data for the correct element.
However, your main concern is performance - this is NOT the way to go. Because every time you access data, you have to first select the correct DOM element and attribute via javascript, which takes considerable amount of time. So the simplicity costs you a lot of performance overhead when you want to read/write data in the element.
Case 2:
This scenario very cleanly separates the DISPLAY from the DATA storage. It has major advantages compared to the first case.
A) Data should not be mingled with the display elements - imagine you want to switch from <div> to <table> and suddenly you have to rewrite all javascript to correctly select the correct table cells
B) Data is directly accessible without traversing the DOM tree. Imagine you want to calculate an average sum of all values. In first case you need to loop all DOM elements and read value from them. In the second you simply loop a normal array
C) The data can be loaded or refreshed via ajax. You only transfer the JSON object but not all the HTML stuff that is required for displaying the data
D) Performance is way better, mostly because of the above mentioned points. Javascript is a lot better in handling simple arrays or (not too complex) JSON objects than it is in filtering data out of the DOM tree.
In general this solution is more than twice as fast as the first case. You will also discover - even though it is not obvious - that the second case is also easier to code and maintain! The code simply is easier to read and understand, as you clearly separate the data from the UI elements...
Performance comparison
You can compare the two solutions on this JSPerf scenario:
http://jsperf.com/performance-on-data-storage
Taking it further
For implementation of the second case I generally use this approach:
I generate the HTML code which will serve as the UI. It often happens that I have to generate HTML via javascript, but now I assume that the DOM tree will not change after the page is loaded
At the end of the HTML I include a tag with a JSON object for initial page display
Via jQuery onReady event I then parse the JSON object and update the UI elements as required (e.g. populating the table with data)
When data is loaded dynamically I simply transfer a new JSON object with the new data and use exact the same javascript function as in step 3 to display the new results.
Example of the HTML file:
<div>ID: <span class="the-id">-</span></div>
<div>Date: <span class="the-date">-</span></div>
<div>Total page hits: <span class="the-value">-</span></div>
<button type="button" class="refresh">Load new data</button>
<script src="my-function.js"></script>
<script>
function initial_data() {
return {"id":"1", "date":"2013-07-30", "hits":"1583"};
}
</script>
The "my-function.js" file:
jQuery(function initialize_ui() {
// Initialize the global variables we will use in the app
window.my_data = initial_data();
window.app = {};
app.the_id = jQuery('.the-id');
app.the_date = jQuery('.the-date');
app.the_value = jQuery('.the-value');
app.btn_refresh = jQuery('.refresh');
// Add event handler: When user clicks refresh button then load new data
app.btn_refresh.click(refresh_data);
// Display the initial data
render_data();
});
function render_data() {
app.the_id.text(my_data.id);
app.the_date.text(my_data.date);
app.the_value.text(my_data.hits);
}
function refresh_data() {
// For example fetch and display the values for date 2013-07-29
jQuery.post(url, {"date":"2013-07-29"}, function(text) {
my_data = jQuery.parseJSON(text);
render_data();
});
}
I did not test this code. Also there is crucial error handling and other optimization missing. But it helps to illustrate the concepts that I try to describe
Related
How To Query Through <slot> Using Cypress While Testing Web Components
After years of testing one global DOM for end-to-end testing, I'm finding it very difficult, if not impossible, to test web components that use slots. Before I explain the problem, I want to say that I cannot change the generated markup to improve things as they are. <wc-1 attributes-etc=""> <wc-2 attributes-etc=""> <slot> <wc-3 attributes-etc=""> <slot> ...eventually get to an input... <input type="text" name="firstName" /> There are a buttload of nested web components from some kind of form builder, and there are also plenty of slots used. The web components have attributes but the slots never do, so I use the web component name for querying. document.querSelector('wc-1') .shadowRoot.querySelector('wc-2') .shadowRoot.querySelector('slot') // Yields <slot>...</slot> All fine to this point and Cypress has a .shadow() command I used, but I'm testing with just devtools here to see all the properties the slot has. document.querSelector('wc-1') .shadowRoot.querySelector('wc-2') .shadowRoot.querySelector('slot') .shadowRoot // Yields "null". // I don't know how to get to the .lightDOM? of wc-2? Any property I try ends up being null or having 0 elements in the returned value. Using other front-end tools and the global DOM, I can always cy.get('div[data-testid="the-nested-element-i-want"]').type('important words') in one command. So my main question is: How do people test these things once web components start piling up? Or don't do this and just test the web components in isolation/unit tests since it's so hard to query the nested shadow DOMs? The main goal is to eventually get to a form input to cy.get('input[name"firstName"]').type('John'). Can someone give me the chained docuement.querySelector() command to get to <wc-3> in my example?
The answer involves assignedNodes(): https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes The assignedNodes() property of the HTMLSlotElement interface returns a sequence of the nodes assigned to this slot... It made no difference for me to use that vs. assignedElements(). So, all you have to do is use that method once you've queried down to the slot you need. For my example, the answer is: const wc-3 = document.querySelector('wc-1').shadowRoot .querySelector('wc-2').shadowRoot .querySelector('slot').assignedNodes() .map((el) => el.shadowRoot)[0] And then you can keep going down the chain...I know I only have one un-named slot, so that's why I grab it from the returned .map(). Props to this Q&A for pointing me on the right direction: Web components: How to work with children?
There will be no DOM content in your <slot>, as there is no DOM content moved to slots. lightDOM content is reflected in slots, but remains invisible! in lightDOM. (that is why you also style slotted content in lightDOM) From the docs: 𝘾𝙤𝙣𝙘𝙚𝙥𝙩𝙪𝙖𝙡𝙡𝙮, 𝙙𝙞𝙨𝙩𝙧𝙞𝙗𝙪𝙩𝙚𝙙 𝙣𝙤𝙙𝙚𝙨 𝙘𝙖𝙣 𝙨𝙚𝙚𝙢 𝙖 𝙗𝙞𝙩 𝙗𝙞𝙯𝙖𝙧𝙧𝙚. 𝙎𝙡𝙤𝙩𝙨 𝙙𝙤𝙣'𝙩 𝙥𝙝𝙮𝙨𝙞𝙘𝙖𝙡𝙡𝙮 𝙢𝙤𝙫𝙚 𝘿𝙊𝙈; 𝙩𝙝𝙚𝙮 𝙧𝙚𝙣𝙙𝙚𝙧 𝙞𝙩 𝙖𝙩 𝙖𝙣𝙤𝙩𝙝𝙚𝙧 𝙡𝙤𝙘𝙖𝙩𝙞𝙤𝙣 𝙞𝙣𝙨𝙞𝙙𝙚 𝙩𝙝𝙚 𝙨𝙝𝙖𝙙𝙤𝙬 𝘿𝙊𝙈. So to test if something is "in" a slot you need to check for slot=? attributes on lightDOM elements and double check if that <slot name=? > actually exists in shadowDOM Or vice versa Or hook into the slotchange Event, but that is not Testing pseudo code: for the vice-versa approach; can contain errors.. its pseudo code.. function processDOMnode( node ){ if (node.shadowRoot){ // query shadowDOM let slotnames = [...node.shadowRoot.querySelectorAll("slot")].map(s=>s.name); // query lightDOM slotnames.forEach( name =>{ let content = node.querySelectorAll(`[slot="${name}"]`); console.log( "slot:" , name , "content:" , content ); }); // maybe do something with slotnames in lightDOM that do NOT exist in shadowDOM // dive deeper this.shadowRooot.children.forEach(shadownode => processDOMnode(shadownode)); } }
Increase performance of large tables from Plone search results
The traditional way of Zope to handle large search results is batched output: The first batchsize items are displayed, and to get the next chunk of data, you click on a "next" link to get the next chunk from the server. Nowadays there are cool Javascript solutions which allow client-side sorting and filtering of tables, e.g. Datatables. These work fine; but if the table is large, and Zope generates the complete HTML, it sometimes takes a long time before the page loads (seems like the search is reasonably fast, but the TAL engine is the performance bottleneck). So, how is this tackled best? Generate the whole table from JSON? (needs Javascript for anything to work) Use the standard paging, and replace it by a client-side table solution if Javascript is available? provide the data of pages 2+ by JSON provide all data by JSON let the table engine load contents for next page or filtering Is there some plug-in solution to apply such enhancements to standard views (like folder contents)? I have a page which contains about 1600 items and takes 60s+ to load, which certainly needs to be improved ... Any pointers and/or code snippets? Thank you!
You'll have to roll your own customizations. The new folder contents in plone 5 works only with json data; however, it does not return the whole folder resultset at a time--it still does paging server side. I've actually not encountered anyone the use-case of sorting thousands of results client-side. I usually do that server side and bring in the data with ajax. You'll want to generate json data structures yourself from catalog results(brains). Something like this in a view perhaps: import json from Products.CMFCore.utils import getToolByName catalog = getToolByName(context, 'portal_catalog') query = { 'path': { 'query': '/'.join(context.getPhysicalPath()), 'depth': 1 } } result = [] for brain in catalog(**query): result.append({ 'id': brain.id, 'uid': brain.UID, 'title': brain.Title, 'url': brain.getURL() }) return json.dumps(result) Along with JavaScript. There are plenty of libraries to present json data in tables. Things you can investigate if you're interested: https://github.com/plone/plone.app.content/blob/master/plone/app/content/browser/vocabulary.py https://github.com/plone/mockup/tree/master/mockup/patterns/structure
Coldfusion Coldbox - create and cache drop down options from queries
I am looking for opinions and alternate ideas, as what I am doing is working, but wanted to ask if it was optimal I have a site that when the index handler is called, it populates the request collection with specific queries from database tables so that I can build drop downs for the user to select. i am querying two models and putting their results in their respective variables, then loop thru them in the view to create the drop down index Handler function index(event, rc, prc){ event.paramValue("debug",0); rc.stages = getmodel("tms_proposal_stage").select(); rc.plannedGiftTypes = getmodel("tms_funding_type").select(); event.setLayout('layout.bootstrap'); } index view <div class="form-group"> <label for="proposal_stage" class="control-label">Proposal Stage Code</label> <select id="proposal_stage" name="proposal_stage" class="form-control"> <cfloop query="rc.stages"> <option value="#stage_code#">#short_desc#</option> </cfloop> </select> </div> I understand that two queries isnt that costly, but if I needed to run 100 of these that would have scalability issues. These query result sets do not change much, so I was thinking, shouldnt these be cached or stored and accessed a different way? I thought about html5 local storage, which I have used but not in this regard. I also considered making a new handler function that makes all of these database calls and is cached, then referenced by other functions anyways, all thoughts are appreciated
You have several options available to you. Since you're using ColdBox, you have CacheBox readily available to you. https://github.com/ColdBox/cbox-refcards/raw/master/CacheBox/CacheBox-Refcard.pdf A very simple way to do inline caching of the data would be to inject a CacheBox provider at the top of your component: component { property name="cache" inject="cachebox:default"; } Then use the cache API in your event to store and retrieve data. My favorite method is getOrSet() because it wraps it up in a single call. rc.stages = cache.getOrSet( objectKey="stages", produce=function(){ return getmodel("tms_proposal_stage").select(); } ); The closure is only executed if the key is not already in the cache. http://wiki.coldbox.org/wiki/WhatsNew:CacheBox-1.6.cfm#Get_Or_Set_Baby Another approach is to cache the full HTML. To do this, create a viewlet that only outputs the HTML for your form control. Create an event that returns the output of just that view like so: function stagesFormInput(event, rc, prc) cache=true { var stagesData = getmodel("tms_proposal_stage").select(); return renderView(view="viewlets/stages", args={ stagesData : stagesData } ); } Note, I'm passing stageData directly into the view so it doesn't pollute the rc or prc. This data will be available in your viewlet as "args.stagesData". Also note the "cache=true" in the method declaration. That's the magic that will tell ColdBox to cache this event (inside CacheBox's "template" provider"). You can specify a timeout, but this will use the default. Now, enabled eventCaching in your /config/ColdBox.cfc file. coldbox={ eventCaching = true } http://wiki.coldbox.org/wiki/ConfigurationCFC.cfm#Application_Aspects And finally, in your main view or layout, just run your new viewlet everywhere you want the cached HTML to be output. #runEvent("viewlets.stagesFormInput")# This is a little bit more setup, but is more powerful since it caches the full HTML snippet which is really ideal. I also have an entire sample app that demos this inside a working app. You can check it out here: https://github.com/bdw429s/ColdBox-Viewlet-Sample
Improve Script performance by caching Spreadsheet values
I am trying to develop a webapp using Google Apps Script to be embedded into a Google Site which simply displays the contents of a Google Sheet and filters it using some simple parameters. For the time being, at least. I may add more features later. I got a functional app, but found that filtering could often take a while as the client sometimes had to wait up to 5 seconds for a response from the server. I decided that this was most likely due to the fact that I was loading the spreadsheet by ID using the SpreadsheetApp class every time it was called. I decided to cache the spreadsheet values in my doGet function using the CacheService and retrieve the data from the cache each time instead. However, for some reason this has meant that what was a 2-dimensional array is now treated as a 1-dimensional array. And, so, when displaying the data in an HTML table, I end up with a single column, with each cell being occupied by a single character. This is how I have implemented the caching; as far as I can tell from the API reference I am not doing anything wrong: function doGet() { CacheService.getScriptCache().put('data', SpreadsheetApp .openById('####') .getActiveSheet() .getDataRange() .getValues()); return HtmlService .createTemplateFromFile('index') .evaluate() .setSandboxMode(HtmlService.SandboxMode.IFRAME); } function getData() { return CacheService.getScriptCache().get('data'); } This is my first time developing a proper application using GAS (I have used it in Sheets before). Is there something very obvious I am missing? I didn't see any type restrictions on the CacheService reference page...
CacheService stores Strings, so objects such as your two-dimensional array will be coerced to Strings, which may not meet your needs. Use the JSON utility to take control of the results. myCache.put( 'tag', JSON.stringify( myObj ) ); ... var cachedObj = JSON.parse( myCache.get( 'tag' ) );
Cache expires. The put method, without an expirationInSeconds parameter expires in 10 minutes. If you need your data to stay alive for more than 10 minutes, you need to specify an expirationInSeconds, and the maximum is 6 hours. So, if you specifically do NOT need the data to expire, Cache might not be the best use. You can use Cache for something like controlling how long a user can be logged in. You could also try using a global variable, which some people would tell you to never use. To declare a global variable, define the variable outside of any function.
Browser history for Flash (or AJAX)
What is the best tool / practice to enable browser history for Flash (or AJAX) websites? I guess the established practice is to set and read a hash-addition to the URL like http://example.com/#id=1 I am aware of the Flex History Manager, but was wondering if there are any good alternatives to consider. Would also be interested in a general AJAX solution or best practice.
SWFAddress has been widely used and tested. It makes it almost trivial (given you plan ahead) to handle deeplinking in Flash. It provides a JS and AS library that work together and make the whole process pretty foolproof. You'd want to look at something like RSH for AJAX.
I've used swfadress for some small stuff.
For AJAX, something like Really Simple History is great.
This will seem a bit roundabout, but I'm currently using the dojo framework for that. There's a dojo.back that was very useful when my UI was mostly JS/HTML. Now that I've gone to flex for more power, fluid animations, and browser stability, the only thing I've need to keep using has been the back URL. FlexBuilder seemed to have it's own browser history in default projects. Also, the Flex 3 Cookbook has a recipe for using mx.managers.HistoryManager to create your own custom history management. I have plans to give this a try someday to remove our dependence on the dojo.back, but haven't had time yet.
I've rolled my own solutions that were ultra-simple like this: (function() { var oldHash, newHash; function checkHash() { // Grab the hash newHash = document.location.hash; // Check to see if it changed if (oldHash != newHash) { // Trigger a custom event if it changed, // passing the old and new values as // metadata on the event. $(document).trigger('hash.changed', { old: oldHash, new: newHash }); // Update the oldHash for the next check oldHash = newHash; } } // Poll the hash every 10 milliseconds. // You might need to alter this time based // on performance window.setInterval(checkHash, 10); })(jQuery); Then you just need to have event handlers for the 'hash.changed' event to respond accordingly based on what the new value is. The approach works will in super simple cases.