I'm using the [jQTouch-Calendar plugin] (https://github.com/thingsinjars/jQTouch-Calendar) together with DZ's jQTouch fork and I'm having trouble with the scroller bouncing back up to the top of the calendar when I try to look at the events of a particular date.
Here's my DOM structure:
<div id="events" class="hide_tabbar">
<div class="toolbar"> ... </div>
<div class="s-scrollwrapper">
<div class="content-area">
<ul id="event-list">
<!-- Dynamic Content -->
</ul>
</div>
</div>
</div>
And here's the JS code that generates the pane:
function buildEvents() {
console.log("preparing events view");
var content = "<ul>";
for (var x = 0; x < events.length; x++) {
// 2011-05-30 00:00:00.0
var eventdate = new Date(events[x].eventdate.replace(
/^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d).(\d)$/,
'$2/$3/$1 $4:$5:$6')
);
if (isNaN(eventdate)) continue;
content += "<li class='arrow'>";
content += "<time datetime="+ eventdate.format("isoUtcDateTime")+">";
content += "<a class='event slide plain' href='#event-details' onClick='getEventDetails(\""+events[x].eventrefid+"\")'>";
content += "<span>"+events[x].eventrefid+"</span>";
content += "</a></time></li>";
}
content += "</ul>";
$("#event-list").html(content);
$('#event-list').getCalendar(); //This is the important bit
}
It generates the calendar fine but when I have a couple of events on a day (which causes the page to be bigger than the scroller area, the scroller snaps back up to the top when I try to scroll down. Help?
Related
As you've probably found, there appears to be no equivalent way to add the following Excel form and associated VBA code to Google Sheets or Scripts or Forms:
Is there some add-in that can be used to pop up this image and its controls? This has to be used many times in an accounting sheet to categorize expenditures at tax time.
It may not look exactly the same but I was able to construct a custom dialog in a short period of time to show how HTML service can be used to produce similar results.
First I construct an HTML template that contains the 2 combo boxes with multiple lines.
HTML_Test.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= include('CSS_Test'); ?>
</head>
<body>
<div id="left">
<label for="expenseCategory">Expense Category</label><br>
<select id="expenseCategory" size="10">
</select>
</div>
<div id="middle">
<label for="expenseSubCategory">Expense Sub Category</label><br>
<select id="expenseSubCategory" size="10">
</select>
</div>
<?!= include('JS_Test'); ?>
</body>
</html>
Then a CSS file to contain all my element formatting.
CSS_Test.html
<style>
#expenseCategory {
width: 90%;
}
#expenseSubCategory {
width: 90%;
}
#left {
width: 25%;
float: left;
}
#middle {
width: 50%;
float: left;
}
</style>
And a javascript file for client side javascript. I've simply hard coded some data to show how the select elements are filled in but this could just as easily be done using template scripting, or google.script.run
<script>
var expenses = [["A","1","2","3"],
["B","4","5"],
["C","6","7","8","9","10"]
];
function expenseCategoryOnClick() {
try {
let expenseCategory = document.getElementById('expenseSubCategory');
expenseCategory.options.length = 0;
expenses[this.selectedIndex].forEach( (expense,index) => {
if( index > 0 ) {
let option = document.createElement("option");
let text = document.createTextNode(expense);
option.appendChild(text);
expenseCategory.appendChild(option);
}
}
);
}
catch(err) {
alert("Error in expenseCategoryOnClick: "+err)
}
}
(function () {
// load first expense
let expenseCategory = document.getElementById('expenseCategory');
expenseCategory.addEventListener("click",expenseCategoryOnClick);
expenses.forEach( expense => {
let option = document.createElement("option");
let text = document.createTextNode(expense[0]);
option.appendChild(text);
expenseCategory.appendChild(option);
}
);
expenseCategory = document.getElementById('expenseSubCategory');
expenses[0].forEach( (expense,index) => {
if( index > 0 ) {
let option = document.createElement("option");
let text = document.createTextNode(expense);
option.appendChild(text);
expenseCategory.appendChild(option);
}
}
);
}
)();
</script>
Then there is the server side code bound to a spreadsheet.
Code.gs
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu("Test");
menu.addItem("Show Test","showTest");
menu.addToUi();
}
// include(filename) required to include html files in the template
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
function showTest() {
var html = HtmlService.createTemplateFromFile("HTML_Test");
html = html.evaluate();
html.setWidth(800);
SpreadsheetApp.getUi().showModalDialog(html,"Test");
}
The dialog looks like this. Many more html elements can be added as needed. This just shows the basics. This may be more difficult than an wysiwig html editor but I find I have better control of the appearance and function of my pages this way. Notice I clicked "C" and the sub category is filled in automatically.
I am making a to do list. Each list item includes a remove button when created.
But I cannot access these remove buttons in my script, because it is not included in my DOM, although I used '.appendChild'. Can anyone help?
const buttonSubmit = document.querySelector('#button-submit');
const form = document.querySelector('form');
const icons = document.querySelector('#icons');
let toDoList = document.querySelector('#todolist');
const input = document.querySelector('#formtext');
form.addEventListener('submit', (e) => {
e.preventDefault();
const newListItem = document.createElement('li');
newListItem.innerHTML = '<span>' + input.value + '</span>' +
'<span id="icons">' +
'<button id="check" class="buttonlist">' + '<img src="checked.png" alt="">' + '</button>' +
'<button id="remove" class="buttonlist">' + '<img src="remove.png" alt="">' + '</button>' +
'<button id="edit" class="buttonlist">' + '<img src="edit.png" alt="">' + '</button>' + '</span>';
toDoList.appendChild(newListItem);
form.reset();
})
const buttonCheck = document.querySelector('#check');
const buttonEdit = document.querySelector('#edit');
const buttonRemove = document.getElementById('remove');
buttonRemove.addEventListener('click', function() {
alert('remove list item');
})
<div class="container-box">
<h1>To Do List</h1>
<br>
<form action="">
<input type="text" id="formtext" name="formtext">
<button id="button-submit">Add Item</button>
</form>
<br><br><br><br>
<!-- Dynamic list here -->
<ul id="todolist"> </ul>
</div>
Problems
ids must be unique, every time you add a task to the list -- after the first one everything is invalid HTML. When directed to an id the browser will find the first id then stop and ignore the duplicate ids. Use class and/or name attributes for any replicated tags.
The reason why the remove button doesn't work is because the reference to the button was defined when it didn't exist.
Figure I
// After page is loaded...
const buttonRemove = document.getElementById('remove');
// Console will tell you buttonRemove is null
// User has not entered any data nor has user clicked the add button
Moreover, even if that was fixed by referencing the button after it was created, binding it as shown on Figure II will only work for the first button only.
Figure II
buttonRemove.addEventListener('click', function() {
alert('remove list item'); // Don't use alert() use console.log()
})
Solution
Reference tags after they are created. In the OP (Original Post), the contents of the <li> is rendered htmlString which makes referencing newly created tags problematic plus binding to dynamically created tags individually should be avoided if it's feasible and practical in which in most cases it is.
To handle events for an unknown amount of dynamically created tags, bind the event to a static ancestor tag, which in the OP is <ul> or any of it's parent tags (even <body>, document, and window but it's best to be as close as possible). Then make it so the event handler controls which tags respond and how. See Appendix located at the very end of this answer for more details.
There are two examples:
Example A - revised OP code
Example B - a todo list using HTMLFormElement interface, see Appendix
Both examples have commented step-by-step details
Example A
// Reference <form>, <ul>, and <input>
const form = document.querySelector('form');
const list = document.querySelector('ul');
const input = document.querySelector('#text');
// Bind <form> to submit event
form.addEventListener('submit', (e) => {
// Stop default behavior of <form> during submit ecent
e.preventDefault();
// Create <li> and <output>
const item = document.createElement('li');
const out = document.createElement('output');
// Assign value of <input> to the value of <output>
out.value = input.value;
// Add <output> to <li> -- <li> to <ul>
item.append(out);
list.append(item);
/*
Run a for loop 3 times -- on each iteration...
...create an <input> and assign type as "button"...
...buttons [name] and [value] is determined by current index...
...add button to <li>
*/
for (let i = 0; i < 3; i++) {
let btn = document.createElement('input');
btn.type = 'button';
let cmd = i === 0 ? 'done' : i === 1 ? 'edit' : i === 2 ? 'remove' : false;
btn.name = cmd;
btn.value = cmd;
item.append(btn);
}
// Reset <form>
form.reset();
});
// Bind <ul> to click event
list.addEventListener('click', manageList);
// Event handler always passes event object by default
function manageList(e) {
// Reference the tag user clicked
const clk = e.target;
// If user clicked a remove button remove it's parent tag
if (clk.name === 'remove') {
clk.parentElement.remove();
}
if (clk.name === 'edit') {
console.log('EDIT');
}
if (clk.name === 'done') {
console.log('DONE');
}
}
li {
display: flex;
align-items: center
}
[type='button'] {
text-transform: capitalize
}
<form>
<input id="text" name="text" type="text">
<button>Add Item</button>
</form>
<br>
<ul></ul>
Example B
// Bind <form> to click event
document.forms.todo.onclick = taskList;
// Event handler akways passes the event object
function taskList(e) {
// Reference the tag user clicked
const clk = e.target;
// Reference all form controls
const IO = this.elements;
/*
If the user clicked the add button...
...reference the <ul>...
...create <li> and <output>...
...add text from <input> to <output>...
...add <output> to <li>...
...Run a for loop 3 times -- on each iteration...
...create an <button> and assign type as "button"...
...buttons [name] and text is determined by current index...
...add button to <li>...
...add <li> to <ul>...
...clear <input>
*/
if (clk.name == 'add') {
const list = IO.list.firstElementChild;
const item = document.createElement('li');
const text = document.createElement('output');
text.value = IO.data.value;
item.append(text);
for (let i = 0; i < 3; i++) {
let btn = document.createElement('button');
btn.type = 'button';
let cmd = i === 0 ? 'done' : i === 1 ? 'edit' : i === 2 ? 'remove' : false;
btn.name = cmd;
btn.textContent = cmd;
item.append(btn);
}
list.append(item);
IO.data.value = '';
}
/*
If the user clicked a remove button...
...find the <li> ancestor of remove button and remove
it thereby removing the <output> and itself as well
*/
if (clk.name === 'remove') {
clk.closest('li').remove();
}
if (clk.name === 'done') {
console.log('DONE');
}
if (clk.name === 'edit') {
console.log('EDIT');
}
}
<form id='todo'>
<input id='data' required><button name='add' type='button'>Add</button>
<fieldset id='list'>
<ul></ul>
</fieldset>
</form>
Appendix
Events
Event delegation
HTMLFormElement
HTMLFormControlsCollection
Form Controls
Does anyone have an example for using multiple canvas in the same page?
I have something like this in the HTML:
<div style="height: 138px" *ngFor="let item of listItems; let i = index">
<canvas #pieCanvas id="pieCanvas{{i}}" style="width: 100px !important; height: 100px !important"></canvas>
</div>
In the .ts file:
#ViewChild("pieCanvas") pieCanvas: ElementRef;
for (var j = 0; j < chartcount; j++)
{
let htmlRef = document.getElementById('pieCanvas'+j);
this.pieCanvas = new Chart(htmlRef, piechartdata);
}
Getting always null is not an object (evaluating 'item.length') error.
With only one chart it works perfect, but there I use sth. like
this.barCanvas = new Chart(this.barCanvas.nativeElement......
I Googled, but couldn't find a solution.
Thanks for your help!
I have found the solution....finally!!!
In html:
<canvas #barCanvas id="barCanvaslist{{i}}"></canvas>
Then in ts:
#ViewChildren('barCanvas') Canvaslist: QueryList;
charts: any;
and afterwards:
this.Canvaslist.changes.subscribe(c =>
{ c.toArray().forEach(item =>
{
this.Canvaslist = new Chart(item.nativeElement, pieData[j]);
j = j+1;
})
});
this does the trick
I have custom editor toolbar plugin which inserts html tags. The tag opens with div. Every time when page is saved or published the editor adds a new p tag above it.
here is the image
IMG tag drifts after publish I see a new p tag is inserted.
The Img being replaced by an html before saving
<p>
<div class="ssimage_code">
<img src="src_path0" alt=""/ >
<img src="src_path1" alt="" / >
</div>
</p>
I want to see if it gets solved by replacing a p tag instead of a img tag.
How do I access the parent tag from Node?
getParent does not work
Code
ed.serializer.addNodeFilter('img', function(nodes, name, args) {
var i = nodes.length, node;
while (i--) {
node = nodes[i];
if ((node.attr('class') || '').indexOf('slider_toimg') !== -1) {
self.imgToslidercode(node, args);
}
}
});
imgToslidercode:function(node,args){
insertcode = '';
node.replace(insertcode);
}
What I am looking here is?
node.getParent().replace(insertcode);
I'm trying to use the theme_advanced_styles command within TinyMCE to add classes to selections of text within the TinyMCE editor. The problem is that if the paragraph contains bullets, then the style is applied throughout them (as well as to each individual paragraph).
What I want is just for the entire selection I made to have the style class added to the start of it. Ie if my style class is 'expandCollapse' I want:
<p class="expandCollapse">some content... some content... some content... some content... som content... some content... some content...
<ul>
<li>asdsadsadsadsasda</li>
<li>asdsadsa</li>
<li>sada</li>
</ul>
asome content... some content... some content... some content... some content... some content... some content... some content... </p>
But what I get is:
<p class="expandCollapse">some content... some content... some content... some content... some content... some content... some content...
<ul>
<li class="expandCollapse">asdsadsadsadsasda</li>
<li class="expandCollapse">asdsadsa</li>
<li class="expandCollapse">sada</li>
</ul>
</p>
<p class="expandCollapse">asome content... some content... some content... some content... some content... some content... some content... some content... </p>
Any ideas anyone?!
So I had to answer my own question as I needed an answer very quickly. It appears the behaviour I was experiencing is intentional? and certainly not something that has been removed in the very latest versions of TinyMCE (both 3.x and 4.x after testing).
With this in mind I ended up having to make a plugin to do what I wanted.
I borrowed a huge amount of code by Peter Wilson, from a post he made here: http://www.tinymce.com/forum/viewtopic.php?id=20319 So thanks very much for this Peter!
I ended up slightly changing the rules from my original question in that my solution adds an outer wrapping div around all the content I want to select. This method also allowed me to reliably then grab the required areas of html with jQuery in my front-end site.
My version of Peter's code is just very slightly modified from the original in order to add a class to the DIV, rename it, use a different button etc.
The plugin works perfectly and allows for a div to be created wrapping any amount of content within TinyMCE. The divs inserted have the class name I need also applied to it.
Add 'customDiv' to your plugin AND button bar for it to appear.
(function() {
tinymce.create("tinymce.plugins.Div", {
init : function(editor, url) {
editor.addCommand("mceWrapDiv", function() {
var ed = this, s = ed.selection, dom = ed.dom, sb, eb, n, div, bm, r, i;
// Get start/end block
sb = dom.getParent(s.getStart(), dom.isBlock);
eb = dom.getParent(s.getEnd(), dom.isBlock);
// If the document is empty then there can't be anything to wrap.
if (!sb && !eb) {
return;
}
// If empty paragraph node then do not use bookmark
if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR'))
bm = s.getBookmark();
// Move selected block elements into a new DIV - positioned before the first block
tinymce.each(s.getSelectedBlocks(s.getStart(), s.getEnd()), function(e) {
// If this is the first node then we need to create the DIV along with the following dummy paragraph.
if (!div) {
div = dom.create('div',{'class' : 'expandCollapse'});
e.parentNode.insertBefore(div, e);
// Insert an empty dummy paragraph to prevent people getting stuck in a nested block. The dummy has a '-'
// in it to prevent it being removed as an empty paragraph.
var dummy = dom.create('p');
e.parentNode.insertBefore(dummy, e);
//dummy.innerHTML = '-';
}
// Move this node to the new DIV
if (div!=null)
div.appendChild(dom.remove(e));
});
if (!bm) {
// Move caret inside empty block element
if (!tinymce.isIE) {
r = ed.getDoc().createRange();
r.setStart(sb, 0);
r.setEnd(sb, 0);
s.setRng(r);
} else {
s.select(sb);
s.collapse(1);
}
} else
s.moveToBookmark(bm);
});
editor.addButton("customDiv", {
//title: "<div>",
image: url + '/customdiv.gif',
cmd: "mceWrapDiv",
title : 'Wrap content in expand/collapse element'
});
}
});
tinymce.PluginManager.add("customDiv", tinymce.plugins.Div);
})();