Template binding with nested for loops WinJS - winjs

I've got an issue where I'm using template.render to render an array of items based on a html template. Each item in the array also contains another array, that I want to bind to another template, within the parent element for the area. I know I can use a grid layout for groups, but I'm trying to accomplish this another way, so please, no suggestions to use a different control, I'm just curious as to why the following doesn't work correctly.
//html templates
<div id="area-template" data-win-control="WinJS.Binding.Template">
<h1 class="area-title" data-win-bind="innerHTML:title"></h1>
<div class="items">
</div>
</div>
<div id="item-template" data-win-control="WinJS.Binding.Template">
<h2 class="item-title" data-win-bind="innerHTML:title"></h2>
</div>
// JS in ready event
var renderer = document.getElementsByTagName('section')[0];
var area_template = document.getElementById('area-template').winControl;
var item_template = document.getElementById('item-template').winControl;
for (var i = 0; i < areas.length; i++) {
var area = areas.getAt(i);
area_template.render(area, renderer).done(function (el) {
var item_renderer = el.querySelector('.items');
for (var j = 0; j < area.items.length; j++) {
var item = area.items[j];
item_template.render(item, item_renderer).done(function (item_el) {
});
}
});
}
So what should happen, is that after it renders the area, in the "done" function the newly created element (el) gets returned, I'm then finding it's ".items" div to append the items to. However, this appends all the items to the first div created. If it was the last div, it might make more sense due to closures, but the fact it happens on the first one is really throwing me off!
What's interesting, is that if I replace my template render function using document.createElement and el.appendChild, it does display correctly e.g: (in the done of area render)
area_template.render(area, renderer).done(function (el) {
var item = area.items[j];
var h2 = document.createElement('h2');
h2.innerText = item.title;
el.appendChild(h2);
}
although I've realised this is el it is appending it to, not the actual .items div of the el
I'm not quite sure what could be going on here. It appears the value of el is getting updated correctly, but el.querySelector is either always returning the wrong ".items" div or it's getting retained somewhere, however debugging does show that el is changing during the loop. Any insight would be greatly appreciated.
thanks

I've worked out what is going on here. The "el" returned in the render promise is not the newly created element as I thought. It's the renderer and the newly created html together. Therefore el.querySelector('.items') is always bringing back the first '.items' it finds. I must have misread the docs, but hopefully someone else will find this information useful in case they have the same error.
I guess one way around this would be to do item_rendered = el.querySelectorAll('.items')[i] and return the numbered '.items' based on the position in the loop
e.g
for (var i = 0; i < areas.length; i++) {
var area = areas.getAt(i);
area_template.render(area, renderer).done(function (el) {
var item_renderer = el.querySelectorAll('.items')[i];
for (var j = 0; j < area.items.length; j++) {
var item = area.items[j];
var h2 = document.createElement('h2');
h2.innerText = item.title;
item_renderer.appendChild(h2);
}
});
}

Related

How can I output this function on a browser?

The function outputs correctly on online code editors but I am not successful in replicating the output on my browser. What's the correct way of outputting it to my browser? I have tried numerous methods. Here is the function I want to output.
function countdown(i) {
console.log(i);
if (i <= 1) { // base case
return;
} else { // recursive case
countdown(i - 1);
}
}
countdown(5); // This is the initial call to the function.
Here is my most recent attempt at output on my web browser
function countDown(i) {
document.getElementById("recursiveFuncAttempt").innerHTML = i;
if (i <= 1) {
return;
} else {
cat = countDown(i - 1);
return document.getElementById("recursiveFuncAttempt").innerHTML = cat;
}
}
countDown(5);
<div>
countdown attempt
<button onclick="countDown()">click me</button>
<p id="recursiveFuncAttempt"></p>
</div>
Grouping your code and the comments together...
Your original code was correct but instead of logging to the console you should add the value to the text content of a page element.
Logging the different values in the console - line by line - gives an appearance of time passing which updating the text content of a DOM element wouldn't give you. All you would see is the last digit in the sequence because the function would work faster than your eyes can see.
Therefore a a timeout is needed to pause execution for n time before calling the function again.
You can simplify the code a little by eliminating the else part of the condition.
// Cache the element
const div = document.querySelector('div');
// Add a default value to count if a value
// is not passed into the function
function countdown(count = 5) {
// If count is zero just return
if (count < 1) return;
// Otherwise update the text content
// of the cached element
div.textContent = count;
// Wait one second (1000ms), and call the function
// with a decremented count
setTimeout(countdown, 1000, --count);
}
countdown();
div { font-size: 5em; color: blue; font-weight: 700;}
<div></div>

angular return uploaded excel file in HTML grid

I am struggling to get my code to display in HTML. I know I am probably missing something really simple, but I am stuck.
I have an Angular 7 app. I want to retrieve the xlsx data (worksheet) and display it in HTML. Simple right? My issue is that all the examples are in browser js and I need to retrieve it from a component. Here's the kicker, I already have the method and can print out json and html via console. log, but cannot find how to return it to an editable grid in HTML.
component.ts
fileUpload() {
let fileReader = new FileReader();
fileReader.onload = (e) => {
this.arrayBuffer = fileReader.result;
var data = new Uint8Array(this.arrayBuffer);
var arr = new Array();
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
var workbook = XLSX.read(bstr, { type: "binary" });
var first_sheet_name = workbook.SheetNames[0];
var worksheet = workbook.Sheets[first_sheet_name];
console.log(XLSX.utils.sheet_to_json(worksheet, { raw: true }));
console.log(XLSX.utils.sheet_to_html(worksheet));
}
fileReader.readAsArrayBuffer(this.file);
}
html
<input type="file" style="display: inline-block;" (change)="incomingfile($event)" placeholder="Upload file" accept=".xls,.xlsx,.ods">
Upload
Currently I can retrieve any workbook/worksheet, but I simply cannot find how to return it to a ui grid of some sort from the .ts file.
I have been through numerous tutorials including ui-grid, ng-grid and https://redstapler.co/sheetjs-tutorial-convert-excel-html-table/
I appreciate I am probably being a dumbass, but any help would be appreciated. I cannot use a commercial component and simply need to render this in an editable table on my html page. I will then look at changing column headers and exporting the file via the backend SpringBoot REST to Mongo (all working)
I am aware that there are numerous similar questions on SO, but the working examples do not seem to display the data(even without any errors). Could be a versioning error etc. I also tried ui-grid, but came up with this issue: 'readAsArrayBuffer' on 'FileReader': parameter 1 is not of type 'Blob'. The example here is however also in .js and not via a typescript component: parameter is not of type 'Blob'
I am simply not able to bind the output to the HTML

Getting element through attribute value in javaScript

I want to get the element through javascript based on attribute value, as per the example I want to get element through attribute "doc-cid" and the value of "doc-cid" is dynamic.
<p align="center" style="font-family:times;" doc-cid="11303">
<font size="2" doc-cid="11304">55</font></p>
<p id="demo">Click the button to change the text of a list item.</p>
<button onclick="myFunction()">Try it</button>
<script>
function myFunction()
{
var list = document.getElementsByTagName("doc-cid=\"11303\"")
alert(list.getAttribute("doc-cid"));
}
</script>
I'll start with a few pointers about your above code, and follow through with a solution.
POINTERS
doc-cid is not a "TagName", it is a custom attribute. hence, your function trying to getElementsByTagName will always fail for "doc-cid".
doc-cid can by dynamic (or not) it doesn't matter. A lookup function will always get the CURRENT DOM value of your element (unless you specifically make it do otherwise).
I suggest you use the new "data-*" attribute in html, it keeps your markup valid (if that is important to you). The use would be as follows:
your content
SOLUTION
function getElementByAttribute (attribute, value, start) {
var start = start || document,
cache, //store found element
i;
function lookup(start) {
// if element has been found and cached, stop
if (cache) {
return;
} else if (start.getAttribute(attribute) === value) { // check if current element is the one we're looking for
cache = start;
return;
} else if (start.childElementCount > 0) { // if the current element has children, loop through those
lookup(start.children[0]);
} else if (start.nextElementSibling) { // if the current element has a sibling, check it
lookup(start.nextElementSibling);
} else {
return;
}
}
lookup(start);
return cache;
}
You simply give the function the attribute name you are looking up, the value you need to match and the starting point of the lookup (if no starting point is specified it'll start at the very beginning of your page (much slower).
Below is an example for your markup:
// you have no easy to get starting point, so we'll traverse the DOM
getElementByAttribute('doc-cid', '11303');
If you want to start at a better node, you can add a wrapper div element and give it id="wrapper" then you could call the function as follows:
var start = document.getElementById('wrapper');
getElementByAttribute('doc-cid', '11303', start);
Hope this helps.

Customize the quick launch items for certain pages in sharepoint

I have requirement where client wants to customize the items in quick launch for only certain
pages.So, I want to change the items in the quick launch with some other items for a few pages.(Not about cahnging the style of quick launch. Its about the replacingthe content in quick launch)
I hope using CEWP, I can achive this.But I am not much aware how to do it.
You can have two approachs here:
1) creating a webpart to replace the quicklaunch: This way you can read the Navigation from SPWeb, and build it your own.
2) Using jQuery to change the html loading the page. In this approach, I would apply a 'display:none' to quicklaunch, make the changes in html, and then 'display:block' back. The con in this solution is that you must rely on the names/titles/urls of the items, so if an admin changes, it could break it.
I had followed following steps to achive the goal
1.. Added a CEWP in the page
Created a text file with Following script and added it to shared dcouments
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
function startClock(){
var div= document.getElementById('s4-leftpanel-content');
var spans= div.getElementsByTagName('span');
for (index = spans.length - 1; index >= 0; index--) {
spans[index].parentNode.removeChild(spans[index]);
}
var urls= div.getElementsByTagName('a');
for (index = urls.length - 1; index >= 0; index--) {
urls[index].parentNode.removeChild(urls[index]);
}
var pTag = document.createElement('p');
pTag.innerHTML = "HR Report";
div.appendChild(pTag);
var aTag = document.createElement('ul');
div.appendChild(aTag);
var newLi = document.createElement('li');
aTag.appendChild(newLi);
var a= document.createElement('a');
a.setAttribute('href',"url");
a.innerHTML = "report2";
newLi.appendChild(a);
//do onload work
}
if(window.addEventListener){
window.addEventListener('load',startClock,false); //W3C
}
else{
window.attachEvent('onload',startClock); //IE
}
</script>
enter code here
Paste the url text file in shared documents in CEWP as content link(Edit web part >>content link>>paste url)
Now, existing items in the Quick Launch is removed and new items are added

TinyMCE Setting focus in text part

Consider the following HTML:
<div id="block-container">
<div id="some-background"></div>
<div id="text-div">Focus should be here when this HTML goes into the editor</div>
</div>
I want the caret be in the text-div -- more precisely in the first text element -- when it opens in the TinyMCE editor.
There could be a way to add some class like ".default-focused" to such element and set focus based on the class. Is there any other (generalized) way to achieve this?
The reason why I can't go with the ".default-focused" way:
1. It could be huge task to add class considering the amount of data I have and
2. More importantly, user can change the HTML and can remove the class.
Well, if you know in which element the caret is to be placed you may use this short function
// sets the cursor to the specified element, ed ist the editor instance
// start defines if the cursor is to be set at the start or at the end
setCursor: function (ed, element, start) {
var doc = ed.getDoc();
if (typeof doc.createRange != "undefined") {
var range = doc.createRange();
range.selectNodeContents(element);
range.collapse(start);
var win = doc.defaultView || doc.parentWindow;
var sel = win.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof doc.body.createTextRange != "undefined") {
var textRange = doc.body.createTextRange();
textRange.moveToElementText(element);
textRange.collapse(start);
textRange.select();
}
},

Resources