I want to add another row below the title (in the header, not in events) without necessarily altering the fullcalendar .css file
Is there a way to add using eventAfterRender, to alter the .css and add another element?
I want to add a row with the text "Only open events are displayed" below the month/year title. I am using just the basic view on the following page:
http://fullcalendar.io/js/fullcalendar-2.3.1/demos/basic-views.html
Unfortunately, I cannot upload a jsfiddle of my calendar as it is embedded within an iOS app that has custom commands for fetching data, it only works within the app. Just struggling with the basics, in what to append and how. I am using the base files (fullcalendar.css).
Any guidance appreciated.
Actually, I solved this myself. I am clearing the title and re-rendering after all event. I have added a line below the heading with some html showing the number of open activities in the database (openactivities global variable).
You can add sub heading after the toolbar by using the following:
eventAfterAllRender: function (view, element) {
if (view.type === "agendaWeek") {
$("#calendar").find('.fc-toolbar > div > h2').empty().append("<div>"+
view.start.format('MMM DD') + " - " + view.end.format('MMM DD') +
"<br/><h6 style=\"font-size:18px\">Number of open activities: " +
openactivities + "</div>");
} else if (view.type === "agendaDay") {
$("#calendar").find('.fc-toolbar > div > h2').empty().append("<div>"+
view.start.format('DD MMMM YYYY') + "<br/><h6 style=\"font-size:18px\"
>Number of open activities: " + openactivities + "</div>");
} else if (view.type === "month") {
$("#calendar").find('.fc-toolbar > div > h2').empty().append("<div>"+
view.start.format('MMMM YYYY') + "<h6 style=\"font-size:18px\">
<br/>Number of open activities: " + openactivities + "</div>");
}
}
I needed to use .empty first and enter the entire format as appending the div without clearing first was causing issues. It didn't appear to at first but the heading was sometimes duplicated when dragging events and dropping.
I also tried other methods, afterRender appeared to be ok but the heading wasn't displayed the first time the calendar was displayed.
My calendar is working, embedded in an iOS app using drag / drop as a front end to our application mySQL database. Unfortunately, my experience with HTML and JavaScript is limited, I'm not a programmer....
I hope I have put this to bed now, thanks to the following post:
FullCalendar Custom/Override Header title
I have finally solved this in 2.0 using the following code to append the h2 div that the title is placed in, that was the only way I could get it to work without it appending every time I switched weeks/views and adding multiple sub-headings.
The code I use passes a global variable (openactivities) to list the total number of open activities in my database.
eventAfterAllRender: function (view) {
$("#calendar").find('.fc-toolbar > div > h2').append(""Number of open": "+openactivities+""); },
Related
I have found vis.js extremely useful but now need to add custom 'symbols' (up indicator, down indicator, etc) to the edges (start / end) of a range element used in a timeline.
I can add separate elements to precede / succeed other elements but this involves quite a bit more effort and stacking is affected etc.
Or... I can place html text / symbols inside a table with css forcing it to the start / end of the element but this does not seem to display correctly always.
What would be the suggested approach to resolve this?
You should provide a template function in the options that will render your range item.
The content returned by your template function may be any valid HTML, so it's up to you to use a div, table, ... whatever to add and position your extra symbols.
Docs:
https://visjs.org/docs/timeline/#Templates
Timeline supports templates to format item contents. Any template engine (such as handlebars or mustache) can be used, and one can also manually build HTML. In the options, one can provide a template handler. This handler is a function accepting an item's data as the first argument, the item element as the second argument and the edited data as the third argument, and outputs formatted HTML.
Example:
var options = {
template: function (item, element, data) {
return '<h1>' + item.header + data.moving?' '+ data.start:'' + '</h1><p>' + item.description + '</p>';
},
onMoving: function (item, callback) {
item.moving = true;
}
};
Within the author it displays a breadcrumb, and I know you can modify its display to either some other static text or localisation, but I'm wondering if it's possible to dynamically show an attribute, or execute some other context-specific xpath dynamically.
As a test I can change the breadcrumb using the localisation editor variable ${i18n()}.
cc_config.xml
<elementRenderings platform="webapp">
<render element="num" as="${i18n(test)}" annotation="${i18n(test)}"/>
translation-cc.xml
<key value="test">
<comment></comment>
<val lang="en_US">Year</val>
"Year" is actually a num element.
However, trying any other variable, even 'more static' ones like ${cf} or ${tp} simply render the variable text literally, instead of evaluating it.
cc_config.xml
<elementRenderings platform="webapp">
<render element="paragraph" as="${xpath_eval(./#eId)}" annotation="${xpath_eval(./#eId)}"/>
<render element="p" as="${tp}" annotation="${tp}"/>
(paragraphs do have an eId attribute)
As you can see, I tried using annotation; but these tooltips also simply display the variable literally.
I also fiddled and tried a bunch of xpath stuff, like #eId/.#eId//#eId, but I think there's some restriction in using the Content Completion Configuration File with respect to editor variables.
So is the thinking right but I am doing something wrong, or is it not the right way but there is some other way to affect the breadcrumb? Maybe with the schema?
The element display names in cc_config.xml file do not support most of the editor variables. Most of them, like ${cf} (current file) and ${tp} (total number of pages) don't make sense to be used when rendering the name of an element.
The xpath_eval would make sense - the display name of an element may depend on its attributes (e.g. the #id attribute), it's index in the document (e.g. 'Section 3'), etc. We have a feature request registered for this case and I added your vote to it.
As a partial workaround you can use a JS API to compute the display name of the element based on the element original name and its attributes:
goog.events.listen(workspace, sync.api.Workspace.EventType.BEFORE_EDITOR_LOADED, function(e) {
e.options.elementNameEnhancer = function(elemName, attrs) {
var displayString = elemName;
var attr = attrs['id'];
if (attr) {
displayString += ' (#' + attr.attributeValue + ')';
}
return displayString;
};
});
I've looked on Google's product forums, and I can't find anything. The help text field is designed for brief text, but I want to insert a mulit-paragraph article. Without paragraph breaks, I wind up with a bunch of text that's difficult to read.
This has been bugging me for a long time and I've came up with a not so elegant but efficient solution based on Apps Script. Pavel Agarkov had the same idea! My version also works with multiple occurences and can be re-run if Google Forms removes the line breaks when you edit the text.
When editing a form, open the Script Editor from the main menu.
Create a new script, replace the content with the code below. Save it and return to your form.
Reload the page. You will notice a new option in the main menu, looking like this
That "Scripts" menu was added by our script. Don't use it for now, it won't do much.
When editing content, use fours spaces as a placeholder for line breaks.
Run the script from the Scripts menu. Now celebrate 👯♀️
Some things worth noting:
You will get a permission request the first time you run the script. It's ok, read the message and do what you have to do.
Once the line breaks are there, Google Forms, god bless its heart, will remove them every time you edit the field. Mildly infuriating. Just run the script again.
The script you need to use is:
// From https://stackoverflow.com/questions/22207368/
function onOpen() {
var ui = FormApp.getUi();
ui.createMenu('Scripts')
.addItem('Replace 4+ spaces with line breaks in Title and Description', 'addLineBreaks')
.addToUi();
}
function addLineBreaks() {
var theForm = FormApp.getActiveForm();
var theQuestions = theForm.getItems();
var thePlaceholder = new RegExp(/\s{4,99}|\n/, 'gi');
for (i = 0; i < theQuestions.length; i++) {
var theText = theQuestions[i].getHelpText();
if (theText.search(thePlaceholder) > 0 ) {
theQuestions[i].setHelpText(theText.replace(thePlaceholder,' \n'));
}
theText = theQuestions[i].getTitle();
if (theText.search(thePlaceholder) > 0 ) {
theQuestions[i].setTitle(theText.replace(thePlaceholder,' \n'));
}
}
}
I found that you can't do it through the editor but it is possible via the script.
Go to main menu -> script editor;
past the following code to the editor;
function addLineBreaks()
{
var form = FormApp.getActiveForm();
// find form items you need
var questions = form.getItems(FormApp.ItemType.MULTIPLE_CHOICE);
for(i = 0; i < questions.length; i++)
{
var title = questions[i].getTitle();
// if no replacement has been done yet
if(title.indexOf("\n") < 0)
{
// this will add line break after <double space> like in markdown
questions[i].setTitle(title.replace(" ", " \n"));
}
}
}
then set up trigger to start this method on form open.
I struggled with this question myself for too long!
However, when you know how its simple:
Go to "Add Item"
Choose "Section Header"
This option allows you to put paragraphed text into your Form.
As of June, 2018, the following work (but only the second option is documented):
Just put new lines in the description and it will be shown in the form - try using two for a paragraph.
If you want a bit more style - add a 'Title and Description' - see the second option in the tool bar showing 'Tᴛ'. The Title will always add extra space (even if it's empty) and will show any title as inverted, larger, text. You can disable the description if you just want a 'heading' followed by questions.
None of the above solutions worked for me, SO I added a unicode character https://www.compart.com/en/unicode/U+2002 pasted 4 to 5 times and this is how it looks
Sorry for the bad news, but this seems impossible to me.
I found the answer! While in the box into which you are entering text, go to Properties in the Developer tab. You will get a drop-down menu. At the bottom of the menu is "Plain Test Properties" with a check box for "Allow carriage returns (multiple paragraphs).
This is a better solution but based on the above. It allows you to edit the form which amazingly the above solutions don't:
// Version 2020-10-07a: by Dennis Bareis
// Handles "{nl}" in form & question descriptions
// Forms API: https://developers.google.com/apps-script/reference/forms
// Based on https://stackoverflow.com/questions/22207368/
// This code #: https://stackoverflow.com/a/64216993/3972414
// [0] ... -> Script Editor -> Create New Script
// [1] Paste into script editor
// [2] Run onOpen()
// [3] On first run authorise script
// [4] This adds 2 scripts under a new button in the edit form UI
// (to the left of the "Send" button)
// [5] Use "START" before re-editing form
// [6] Use "END" to publish the changes
// 5&6 required as otherwise you end up with "line1Line2Line3" etc
String.prototype.replaceAll = function(search, replacement)
{
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};
//This doesn't perform the function on open, just adds it to the UI, you run when finished.
function onOpen()
{
var ui = FormApp.getUi();
ui.createMenu('Scripts')
.addItem('[1] Prepare for RE-EDITING this form (restore {nl})', 'editFormStart')
.addItem('[2] Publish the form (finished editing, remove {nl})', 'editFormEnd')
.addToUi();
}
function editFormStart()
{
swapLineBreaks("\n", "{nl}")
}
function editFormEnd()
{
swapLineBreaks("{nl}", "\n")
}
function swapLineBreaks(FromText, ToText)
{
var form = FormApp.getActiveForm();
// find form items you need
var oForm = FormApp.getActiveForm();
var questions = oForm.getItems();
// Fix the form's description
var formDesc = oForm.getDescription()
oForm.setDescription(formDesc.replaceAll(FromText, ToText))
// Work through each question
for(i = 0; i < questions.length; i++)
{
//var QTitle = questions[i].getTitle();
//questions[i].setTitle( QTitle.replaceAll(FromText, ToText));
var QText = questions[i].getHelpText();
questions[i].setHelpText(QText.replaceAll(FromText, ToText));
}
}
I'm very new to meteor, so apologies if I'm missing something very basic here.
I thought it would be fun to create a very simple textpad style app to check out meteor. I took the todo app and changed the data structures to be 'folders' and 'docs' rather than 'lists' and 'todos', so I have a list of folders and when you click on the folder you get a list of the documents in that folder.
I've then added some code to show the 'content' attribute of a single 'doc' when one of the docs in the list is clicked.
I'm using ace to add some pretty print to the content of the doc (https://github.com/ajaxorg/ace). I've set ace up to work with a hidden textarea containing the plaintext version of my document, and the editor object takes this text and pretty prints it.
The problem with ace is that I don't want the template containing the ace editor to be replaced every time the contents of the doc changes (as it takes half a second to reinitialise, which is a crappy experience after every character is typed!). Instead, I want to update the textarea template and then use the ace API to tell the editor to update it's input based on what is in the textarea.
Now, this is probably the wrong way to approach the problem, but I've ended up using two templates. The first contains a textarea containing doc.contents, which is reactive to the underlying model:
<template name="doc_content">
<textarea name="editor">{{content}}</textarea>
</template>
The second one contains the 'editor' div which ace uses to display the pretty printed text.
<template name="doc_init">
<div id="editor"></div>
</template>
The idea is that the first template will update every time the user types (on all clients), and the second template is only ever re-loaded for each new doc we load.
Template.doc_content.content = function() {
var doc_id = Session.get('viewing_itemname');
if (!doc_id) {
return {};
}
var doc = Docs.findOne({_id:doc_id});
if (doc && doc.content) {
// #1 Later
var editor = Session.get('editor');
if (editor) {
editor.getSession().setValue(doc.content);
}
return doc.content;
} else {
return '';
}
};
When you enter text into the editor div I make a call to Docs.update(doc_id, {$set: {content: text}});, which updates the value in the textarea on each client. All good so far.
editor.getSession().on('change', function(){
var text = editor.getSession().getValue();
Docs.update(doc_id, {$set: {content: text}});
});
What I want to do, for all clients other than the client which made the change, is to subscribe to the change for that doc and call editor.getSession().setContent() with the text which has just been changed, taking the text from the textarea and using it to fill the editor.
I've tried to do this by making that call from the template containing the textarea (as this changes whenever the doc is updated - see #1 above). However, this puts the clients into an infinite loop because changing the value in the editor causes another call to Docs.update.
Obviously this doesn't happen when you render a template, so I'm assuming there's some magic in meteor which can prevent this happening, but I'm not sure how.
Any thoughts?
TIA!
There's a lot to absorb in your question, but if I understand correctly, you might simply be after Deps.autorun:
Deps.autorun(function () {
var doc_id = Session.get('viewing_itemname');
if (!doc_id) {
return {};
}
var doc = Docs.findOne({_id:doc_id});
// do stuff with doc
});
Deps.autorun is really useful in that it will get re-run if any of its
dependencies change. These dependencies are limited to those that are "reactive"
such as Collections and Sessions, or anything that implements the reactive API.
In your case, both Session.get and findOne are reactive so if their values
change at all, Deps.autorun will run the function again.
The Portlets in Plone are quite handy but I'd like to be able to provide some method to users to be able to temporarily hide/show the portlets column. That is, by clicking a button, the portlets column should collapse and you see the content page in full width. Then clicking again and the portlets panel on the left expands and the main content page width shrinks to accommodate.
I've observed the HTML ID of the portlets column is "portal-column-one" and I tried adding a button to the page that runs javascript to set the visibility property of that element to "hidden" but this seemed to have no effect. I was able to go into Firebug and add style="visibility:hidden;" to the "portal-column-one" element and it had the effect of making the region invisible w/o resizing the page.
I am using Plone 4.1. I have the site configured with navigation portlet on all pages except the main page which has Navigation, Review List and Recent Changes.
So it seems it must be possible to embed some javascript in the page (I was thinking of adding this to the plone.logo page which I've already customized). But I guess its more complicated than the few stabs I've made at it.
Thanks in advance for any advice.
Solution (Thanks to input from Ulrich Schwarz and hvelarde):
The solution I arrived at uses JavaScript to set CSS attributes to show/hide the Portlets Column (Left side) and expand the content column to fill the space the porlets column filled.
I started by customizing the Plone header template to add a link for the user to toggle the view of the Porlets column. I also put the necessary javascript functions in this header.
To customize the header, go to the following page (need to be logged in as Admin of your Plone site):
http://SERVER/SITE/portal_view_customizations/zope.interface.interface-plone.logo
Where:
SERVER is the address and port of your site (e.g. localhost:8080)
SITE is the short name of your Plone Site
To create this page:
Go to Site Setup (as Admin)
Go to Zope Management Interface
Click on "portal_view_customizations"
Click on "plone.logo" (or at least this is where I choose to put the button so it would be located just above the navigation Portlet)
Add the following to the page:
<script>
function getById(id) {
return document.getElementById(id);
}
function TogglePortletsPanel() {
var dispVal = getById('portal-column-one').style.display
if( dispVal == "none") { // Normal display
SetPortletsPanelState("inline");
} else { // Full Screen Content
SetPortletsPanelState("none");
}
}
function SetPortletsPanelState(dispVal) {
var nav = getById('portal-column-one');
var content = getById('portal-column-content');
if( dispVal == "none") { // Normal display
nav.style.display='none';
content.className='cell width-full position-0';
// Set cookie to updated value
setCookie("portletDisplayState","none",365);
} else { // Full Screen Content
nav.style.display='inline';
content.className='cell width-3:4 position-1:4';
// Set cookie to updated value
setCookie("portletDisplayState","inline",365);
}
}
function InitializePortletsPanelState() {
var portletDisplayState=getCookie("portletDisplayState");
//alert("portletDisplayState="+portletDisplayState)
if (portletDisplayState!=null) SetPortletsPanelState(portletDisplayState);
}
function setCookie(c_name,value,exdays) {
//alert(c_name+"="+value);
// cookie format: document.cookie = 'name=value; expires=Thu, 2 Aug 2001 20:47:11 UTC; path=/'
var exdate=new Date();
exdate.setDate(exdate.getDate() + exdays);
var exp= ((exdays==null) ? "" : "; expires="+exdate.toUTCString());
document.cookie=c_name + "=" + escape(value) + exp + "; path=/";
}
function getCookie(c_name) {
var i,x,y,ARRcookies=document.cookie.split(";");
for (i=0;i<ARRcookies.length;i++) {
x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
x=x.replace(/^\s+|\s+$/g,"");
if (x==c_name) return unescape(y);
}
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {oldonload(); }
func();
}
}
}
addLoadEvent(InitializePortletsPanelState);
</script>
<a style="font-size:50%;" href="javascript:TogglePortletsPanel();">Toggle Portlets Panel</a>
6. Save the page
Notes:
I got the names of the plone div elements using Firebug.
I also used Firebug to experiment with different settings to speed up prototyping. For example, editing the HTML inline to verify settings do as expected.
There is a slight but of delay until the Left Portlet panel is hidden. This is only obvious on Safari for me (which is probably due to how fast it is) but not on Firefox or IE.
Maybe it's just a matter of setting the right property: you want display:none, not visibility:hidden.
But even then, the content area will probably not reflow automatically, you'll need to (dynamically) change the class on it as well.
Specifically, you'll need to put classes width-full and position-0 on portal-column-content, instead of width-1:2 and position-1:4.
This must be achieved client side by javascript (jquery).
You must first read documentation about the css grid framework used by plone: deco.gs. The website is down so, git clone this repo: https://github.com/limi/deco.gs and open pages in a webbrowser
Note: you just have to change css classes on the containers.
Try adi.fullscreen, it respects Plone's css-structure as Ulrich Schwarz thoughtfully mentioned.