Customizing Day Cell content in FullCalendar - reactjs

I am using fullcalendar with react. I am trying to customize the dayGrid view. According to the Content Injection docs for react I can use custom content for the rendering of both the date and the header cells. The dayCellContent "hook" states that:
Generated content is inserted inside the inner-most wrapper of the day cell. It does not replace the cell.
I've provided an implementation for the dayCellContent and noticed that my content gets injected into the following structure:
<td class="fc-daygrid-day fc-day fc-day-wed fc-day-past rot_time-off_day-cell" data-date="2021-04-07">
<div class="fc-daygrid-day-frame fc-scrollgrid-sync-inner">
<div class="fc-daygrid-day-top">
<a class="fc-daygrid-day-number">
...custom content goes here
</a>
</div>
<div class="fc-daygrid-day-events"></div>
<div class="fc-daygrid-day-bg"></div>
</div>
</td>
Now, the problem is that this structure lets you insert content ONLY in the upper right corner of the date cell due to the positioning of the element. Furthermore, it is in an anchor element.
Example:
function renderDayCell(dayCellContent: DayCellContentArg) {
return (
<div>
{dayCellContent.dayNumberText}
</div>
);
}
Is there a clean way to customize the whole content of the cell somehow? I've seen a couple of sites using fullcalendar that have their content inserted directly into the td. Not sure if this is version dependent or they're using the alternative JS approach based on domNodes or html. I am using version 5.6.0 of fullcalendar.

I had the same requirement although not using React. I solved it using a manual manipulation of the DOM elements as suggested above. I have used jQuery for the select and manipulation. It is posted here in case anyone would like to see an example of how this can be achieved using DOM manipulation.
I implemented dayCellContent to make the day-cell DOM element easily identifiable by wrapping it in a span, with a unique id attribute based on the day of year number:
dayCellContent: function(info, create) {
const element = create('span', { id: "fc-day-span-"+info.date.getDayOfYear() }, info.dayNumberText);
return element;
},
This dayCellContent implementation makes no visible difference to the calendar but makes it easier to identify the elements to be modified in the DOM.
I then implemented dayCellDidMount to do the DOM manipulation by finding the appropriate cells and selecting their parent’s parent:
dayCellDidMount: function(info) {
let element = "<div style='position: absolute; left: 4px; top: 4px;'><a href='https://www.w3schools.com/'>TEST-"+info.dayNumberText+"</a></div>";
$('#fc-day-span-'+info.date.getDayOfYear()).parent().parent().prepend(element);
},
In this case I have just put a link to w3c in the top left of the cell with test text which also includes the day number. It results in cells that look like this:
Clearly the CSS could be improved and should be moved out to the CSS definitions but it illustrates the point.
Warning: This approach makes assumptions about the DOM structure that FullCalendar generates. The generated HTML may change in future versions of the product which could invalidate it. If you go this way then be careful when doing a FullCalendar update.
Note that the getDayOfYear function is from the ext-all.js library. Any way of uniquely identifying the day will work.

ngAfterViewInit(){
// Your CSS as text
var styles =.fc td, .fc th { vertical-align: top; padding: 0; height: 100px; } a{ color:#3d1cba; }
let styleSheet = document.createElement("style");
styleSheet.innerText = styles;
document.head.appendChild(styleSheet);
let arrTD = document.querySelectorAll('td.fc-timeline-slot');
let arrTR= document.querySelectorAll('td.fc-timeline-lane.fc-resource');
let arrInject= document.querySelectorAll('td.fc-timeline-lane.fc-resource>div.fc-timeline-lane-frame');
console.log(arrTR);
let k=-1;
arrTR.forEach(eachTR => {
let i=1;
let str = '';
k++;
let data_resource_id= eachTR.getAttribute('data-resource-id');
console.log(data_resource_id);
arrTD.forEach(eachTD => {
let k=100*(i-1);
i=i+1;
let data_date= eachTD.getAttribute('data-date');
console.log(data_date);
let data_resource_id= eachTR.getAttribute('data-resource-id');
console.log(data_resource_id);
str = str + '<span data-date="'+data_date+'" data-resource-id="'+data_resource_id+'" class="plus_icon" style="position:relative;top: 0px; left: '+k+'px !important;width:500px;height:500px;z-index:3;-moz-border-radius:100px;border:1px solid #ddd;-moz-box-shadow: 0px 0px 8px #fff;">+</span>';
});
arrInject[k].innerHTML=str;
});
let elementList = this.elRef.nativeElement.querySelectorAll('span.plus_icon');
for(let i=0;i<elementList.length;i++){
elementList[i].addEventListener('click', this.plusClick.bind(this));
}
}

Related

Bootstrap Datetime Picker in Backgrid Cell Not Rendering Correctly

I'm trying to use this Bootstrap Datetime Picker (version 4.17.37) inside a Backgrid cell, but I'm having a problem with the way the widget is rendering. The widget appears as a thin popup right below the cell, but seems to have no content inside. However, if you log the widget's inner html to the console, the content is there. It seems to me that there is something unusual going on inside the place function of the datetime picker, shown here.
This is my code for the custom Backgrid cell and its editor.
// Editor for the datetime picker cell.
var DatetimePickerCellEditor = Backgrid.InputCellEditor.extend({
events: {},
initialize: function() {
Backgrid.InputCellEditor.prototype.initialize.apply(this, arguments);
var input = this;
var timezone = 'America/Lima',
format = 'YYYY-MM-DD HH:mm';
this.$el.datetimepicker({
format: format,
timeZone: timezone
}).on('dp.show', function(event) {
event.preventDefault();
var column = input.column.get('name'),
date = input.model.get(column);
input.$el.data("DateTimePicker").date(date);
}).on('dp.hide', function(event) {
event.preventDefault();
var dateObj = event.date,
dateLocal = null;
if (dateObj) {
var dateString = dateObj.format(format);
dateLocal = moment.tz(dateString, timezone);
}
var command = new Backgrid.Command({}),
column = input.column.get("name");
// Save date in model as string in local time.
input.model.set(column, dateLocal.format());
input.model.trigger("backgrid:edited",
input.model,
input.column,
command);
command = input = null;
}).on('dp.error', function(error) {
console.log(error)
});
}
});
// Cell to display the datetime picker.
var DatetimePickerCell = Backgrid.Cell.extend({
editor: DatetimePickerCellEditor
});
This produces the following result:
Upon inspection, the element that the datetime picker was using as a parent is an ancestor <div class="col-lg-12 col-md-12">. I then tried to solve the problem by giving the cell a { position: relative } CSS property. That indeed made the datetime picker use the cell as a parent, but produced the following result visually:
You can see that the picker appears to be below the cell, but is not visible.
Playing around with other properties, such as widgetParent and widgetPositioning gave similar results.
I had same problem.
(I know that this is an old question, but I use backbone/backgrid today. I have a lot of additional issues with angular, vue or react on data grids, I find that this combo is the best, and simplest, until these days).
well, You only must add this after backbone/backgrid css to your th, td containers.
.backgrid th,
.backgrid td {
display: table-cell;
position: relative;
overflow: visible;
}
I use it in an backbone/backgrid project and it works perfect.
It's incredible, there is and display: none style overrrided, but, for any reason it continues makeing this fail.
I hope this could help some one else.
one capture:

How to handle tree like structure in angularjs?

I am trying to implement a hierarchical structure in the form of a tree in a complex angular.js web app. I am using nested ng-repeats to render the structure but I am getting significant performance related issues in IE 10 and minor performance issues in chrome. Data that will be used will contain as many as 5,000 entries at the final level.
Based on my research I think following could be the reasons behind it:
Large number of watcher elements.
To tackle this I have already implemented one time data binding and number of watchers is not that high.
Browser repaint time:
ng-repeat adds the elements to the DOM one by one. This could result in overloading of browser engine to render complex HTML multiple number of times causing large amount of lags.
To tackle this I have applied lazy loading sort of technique by rendering the child only when one node is collapsed. Still I am experiencing an observable delay in rendering of nodes where the number of nodes to be rendered is large.
CSS classes:
I tried implementing the tree structure by stripping off all the classes from node elements. This resulted in significant improvement but removing classes is not really an option. Also if I give inline style to the elements then it also results in better performance.
Performance issues of Angular material:
Angular material is the integral part of my web app. After looking into the issues submitted by angular material users for large amount of ng-repeats to which fixes have already been rolled out. But upgrading to latest version didn't help either.
Please refer to this image for the table design. Template used for the creation of tree is as follows:
<li ng-repeat="item in ::item.childLevelDetails" >
<div >
<a ng-click="toggle(this)" class="icon icon-stream-add-2">
<span></span>
</a>
<div class="unitTextDiv">{{::item.title}}</div>
</div>
<ol ng-include= "'tree_node'">
</li>
Request you to suggest any possible solutions to this problem.
angular-ui-grid can be considered as one option. It uses virtualization and renders only the rows that are visible. So, it performs well with huge number of rows too. It comes with a great documentation and examples http://ui-grid.info/docs/#/tutorial
Refer to the grouping example to make sure that this helps for your use case http://ui-grid.info/docs/#/tutorial/209_grouping
Time to use stick with this potion and finalize according to black magic!
You can try this recursive sample I did for you.
Using ng-if for showing/hidding element will reduce the amount of watches.
Here find the Fiddler
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', ['$scope','$timeout', 'getWatchCount' , function ($scope ,$timeout, getWatchCount ){
$scope.tree = [
{title:'Element level 1',
elements: [
{ title: 'Element level 1.1'},
{ title: 'Element level 1.2',
elements: [
{ title: 'Element level 1.2.2'},
{ title: 'Element level 1.2.2'},
]}
]},
{title:'Element level 2'}
]
//NEXT CODE ONLY USED FOR COUNTING WATCHES//
$scope.countWatches = function(){
$scope.numberOfWatches = getWatchCount();
}
$timeout(function(){$scope.countWatches()} , 0 );
// I return the count of watchers on the current page.
function getWatchCount() {
// Keep track of the total number of watch bindings on the page.
var total = 0;
// There are cases in which two different ng-scope markers will actually be referencing
// the same scope, such as with transclusion into an existing scope (ie, cloning a node
// and then linking it with an existing scope, not a new one). As such, we need to make
// sure that we don't double-count scopes.
var scopeIds = {};
// AngularJS denotes new scopes in the HTML markup by appending the classes "ng-scope"
// and "ng-isolate-scope" to appropriate elements. As such, rather than attempting to
// navigate the hierarchical Scope tree, we can simply query the DOM for the individual
// scopes. Then, we can pluck the watcher-count from each scope.
// --
// NOTE: Ordinarily, it would be a HUGE SIN for an AngularJS service to access the DOM
// (Document Object Model). But, in this case, we're not really building a true AngularJS
// service, so we can break the rules a bit.
angular.forEach(
document.querySelectorAll( ".ng-scope , .ng-isolate-scope" ),
countWatchersInNode
);
return( total );
// ---
// PRIVATE METHODS.
// ---
// I count the $watchers in to the scopes (regular and isolate) associated with the given
// element node, and add the count to the running total.
function countWatchersInNode( node ) {
// Get the current, wrapped element.
var element = angular.element( node );
// It seems that in earlier versions of AngularJS, the separation between the regular
// scope and the isolate scope where not as strong. The element was flagged as having
// an isolate scope (using the ng-isolate-scope class); but, there was no .isolateScope()
// method before AngularJS 1.2. As such, in earlier versions of AngularJS, we have to
// fall back to using the .scope() method for both regular and isolate scopes.
if ( element.hasClass( "ng-isolate-scope" ) && element.isolateScope ) {
countWatchersInScope( element.isolateScope() );
}
// This class denotes a non-isolate scope in later versions of AngularJS; but,
// possibly an isolate-scope in earlier versions of AngularJS (1.0.8).
if ( element.hasClass( "ng-scope" ) ) {
countWatchersInScope( element.scope() );
}
}
// I count the $$watchers in the given scope and add the count to the running total.
function countWatchersInScope( scope ) {
// Make sure we're not double-counting this scope.
if ( scopeIds.hasOwnProperty( scope.$id ) ) {
return;
}
scopeIds[ scope.$id ] = true;
// The $$watchers value starts out as NULL until the first watcher is bound. As such,
// the $$watchers collection may not exist yet on this scope.
if ( scope.$$watchers ) {
total += scope.$$watchers.length;
}
}
}
}]);
myApp.factory(
"getWatchCount",
function() {
// I return the count of watchers on the current page.
function getWatchCount() {
var total = 0;
// AngularJS denotes new scopes in the HTML markup by appending the
// class "ng-scope" to appropriate elements. As such, rather than
// attempting to navigate the hierarchical Scope tree, we can simply
// query the DOM for the individual scopes. Then, we can pluck the
// watcher-count from each scope.
// --
// NOTE: Ordinarily, it would be a HUGE SIN for an AngularJS service
// to access the DOM (Document Object Model). But, in this case,
// we're not really building a true AngularJS service, so we can
// break the rules a bit.
angular.element( ".ng-scope" ).each(
function ngScopeIterator() {
// Get the scope associated with this element node.
var scope = $( this ).scope();
// The $$watchers value starts out as NULL.
total += scope.$$watchers
? scope.$$watchers.length
: 0
;
}
);
return( total );
}
// For convenience, let's serialize the above method and convert it to
// a bookmarklet that can easily be run on ANY AngularJS page.
getWatchCount.bookmarklet = (
"javascript:alert('Watchers:'+(" +
getWatchCount.toString()
.replace( /\/\/.*/g, " " )
.replace( /\s+/g, " " ) +
")());void(0);"
);
return( getWatchCount );
}
);
ul{
list-style-type: none;
}
li{
font-size:13px;
}
.arrow{
width: 0;
height: 0;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
border-right: 7px solid transparent;
cursor: pointer;
margin-left: 5px;
border-left: 7px solid #000;
display: inline-block;
transition:all 0.3s;
}
.arrow.expand {
transform: rotate(45deg);
transform-origin: 20% 50%;
margin-top: 0;
}
.arrow.none {
border-left: 7px solid #ccc;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl" >
<p>
<strong>Watch Count:</strong> {{ numberOfWatches }}
</p>
<script type="text/ng-template" id="elementTree">
<li>
<div class="arrow"
ng-class="{expand:element.isOpen,none:!element.elements}"
ng-click="$apply(element.isOpen = !element.isOpen) ; countWatches()">
</div>
{{element.title}}
</li>
<div ng-if="element.isOpen">
<ul
ng-repeat="element in element.elements"
ng-include="'elementTree'">
</ul
</div>
</script>
<ul ng-repeat="element in tree"
ng-include="'elementTree'">
</ul>
</div>

Running header with nested generated content

This is a question about Paged Media.
I want to mix a string-set with running elements. It works in PrinceXML but not in PDFReactor. My question, is this possible in PDFReactor?
HTML
<p class="head">
<span class="first">Repeatable title</span>
<span class="divider">|</span>
<span class="last"></span>
</p>
CSS
p.head {
position: running(heading);
font-size: 8pt;
margin: 0;
padding: 0;
}
#page {
size: A4;
#top-left {
content: element(heading);
}
}
So far everything is peachy. But when I try to define a string-set from a H1 and try to write this into span.last this isn't working.
h1 {
string-set: doctitle content();
}
p.head span.last {
content: string(doctitle);
}
This is possible with PDFreactor as well. Just the syntax is a bit different. PDFreactor does not support the content() function for the string-set property with Named Strings. Instead it uses the self value which works like content() or content(text) (see http://www.pdfreactor.com/product/doc_html/index.html#NamedStrings )
There is a second issue. You are setting the content property on the span element itself. Usually in CSS, creating generated content with the content property is actually only allowed for page margin boxes and pseudo elements such as ::before and ::after. This is also how browsers support it. Not sure why this works in Prince.
So basically you just have to make 2 minor adjustments to your style sheet to make this work in PDFreactor:
h1 {
string-set: doctitle self;
}
p.head span.last::before {
content: string(doctitle);
}

Setting class values in Angular template

I'm a bit of an Angular novice an I'm struggling with something I thought would be very simple.
I have a template that is used in a read only version of a form in my app. This template displays a status field that would be styled (text/background colour) based on its value, e.g
'New issue' - orange
'In progress' - green
'Overdue' - red
etc...
My css is something like;
.status-bar {padding: 3px; border: solid 1px #333}
.new-issue {background-color: orange; color: #000}
.in-progress {background-color: green; color: #fff}
.overdue {background-color: red; color: #fff}
The issue status field is available through the controller and i use code something like this to display it.
<div class="status-bar">{{issue.status}}</div>
All works fine.
I then naively tried to simply insert the class name as an expression, e.g
<div class="status-bar {{issue.status}}">{{issue.status}}</div>
Thinking that would give me a this kind of output..
<div class="status-bar overdue">overdue</div>
But it doesn't work. I've looked at the ng-class directive, and other options but can't work this out.
Ideally I need a solution that would allow me to append/insert a class name based on a value (not a true/false like ng-class). So I'd like my oputput HTML to be like the following...
<div class="status-bar in-progress">In Progress</div>
OR
<div class="status-bar new-issue">New Issue</div>
OR
<div class="status-bar overdue">overdue</div>
etc...
The range of status values may change so my class names must be calculated as per the above pattern
e.g,
<div class="status-bar another-status">Another Status</div>
So I need a way to hyphenate and lowercase my issue.status value and then add it as a class name. I presume a directive is the way forward, which would be ideal so I can use it in other views.
This is so easy to do after the fact in jQuery etc..., but I can't see the 'Angular' way to do it?!
Many thanks upfront for any help provided...
ng-class with a custom filter is what you are looking for...
HTML
<h1 ng-class="class1">{{class1 | titleCase}}</h1>
JS
app.filter('titleCase', function () {
return function (input) {
var words = input.split('-');
for (var i = 0; i < words.length; i++) {
words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1);
}
return words.join(' ');
}
});
and here is working PLUNKER...
ng-class should work :
<div class="status-bar" ng-class="issue.status">{{issue.status}}</div>
Working fiddle : http://jsfiddle.net/w2SFr/2/
What You are looking for is probably http://docs.angularjs.org/api/ng/directive/ngClass. You may also want to look at AngularJS ngClass conditional

how to handle combo boxes of ExtJS in selenium webdriver

Hi i have a ExtJS based UI. I have come to know that in ExtJS the combo box is not a real combo box but a combination of input text field, image of drop down box and a list. Now i am able to identify the control but i am stuck at selecting the value from the list. In the HTML source i see that the list is appearing as a seperate div and gets attached at the end of the source when we click on the drop down. find below the HTML source of the drop down control.
{
<div id="ext-gen678" class="x-form-field-wrap x-form-btn-plugin-wrap" style="width: 556px;">
<div id="ext-gen676" class="x-form-field-wrap x-form-field-trigger-wrap x-trigger-wrap-focus" style="width: 521px;">
<input id="ext-gen677" type="hidden" name="GHVOg:#concat#~inputFld~ISGP_UNIV:ft_t_isgp.prnt_iss_grp_oid:0" value="">
<input id="GHVOg:Mixh8:0" class="x-form-text x-form-field gs_dropDown_input gs_req x-form-invalid x-form-focus" type="text" autocomplete="off" size="24" style="width: 504px;">
<img id="trigger-GHVOg:Mixh8:0" class="x-form-trigger x-form-arrow-trigger" alt="" src="../../ext/resources/images/default/s.gif">
}
find below the HTML source of the drop down list:
<div id="ext-gen726" class="x-layer x-combo-list x-resizable-pinned" style="position: absolute; z-index: 12007; visibility: visible; left: 294px; top: 370px; width: 554px; height: 123px; font-size: 11px;">
<div id="ext-gen727" class="x-combo-list-inner" style="width: 554px; margin-bottom: 8px; height: 114px;">
<div class="x-combo-list-item"></div>
<div class="x-combo-list-item">12h Universe</div>
<div class="x-combo-list-item">1h Universe</div>
<div class="x-combo-list-item">24h Universe</div>
<div class="x-combo-list-item">2h Universe</div>
<div class="x-combo-list-item x-combo-selected">4h Universe</div>
Now i have problem selecting the value from the list as the div element of the list is not attached to the control.
Also please refer the screen shot, where i have multiple similar controls [Named "Add Security to Universe"]
In the screen shot you can see multiple drop downs [Add security to Universe] highlighted and all the drop downs have same value appearing in the list. so how can i identify these values from the drop down list.
I was wondering how ExtJS maintains mapping of the drop down div elements with the combo Box widget so that i could use the same logic for identifying the list. Can anyone tell me how can i go about doing this thing in selenium webdriver?
Did you notice that there will be only one visible x-combo-list on the page? (Let me know if you can open up two combo lists at the same time)
Therefore you only need to care about the visible one x-combo-list.
Css selector: .x-combo-list[style*='visibility: visible;'] .x-combo-list-item
Xpath: //*[contains(#class, 'x-combo-list') and contains(#style, 'visibility: visible;')]//*[contains(#class, 'x-combo-list-item')]
// untested java code, just for the logic
public void clickComboItem(WebElement input, String target) {
input.click(); // click input to pop up the combo list
List<WebElement> comboItems = driver.findElements(By.cssSelector(".x-combo-list[style*='visibility: visible;'] .x-combo-list-item"));
for (int i = 0; i <= comboItems.size(); i++) {
WebElement item = comboItems.get(i);
if (item.getText().eqauls(target)) {
item.click();
break;
}
}
}
// compilable C# version
public void ClickComboItem(IWebElement input, string target) {
input.Click();
IList<IWebElement> comboItems = driver.findElements(By.CssSelector(".x-combo-list[style*='visibility: visible;'] .x-combo-list-item"));
comboItems.First(item => item.Text.Trim() == target).Click();
}
What i can suggest is :
you catch all your inputs like :
List<WebElement> inputList = driver.findElements(By.cssSelector("input cssSelector")); // you must complete this cssSelector
WebElement input = inputList.get(0); // get the 1st input
input.click(); //click on the first input and the option list appears.
you catch all "options" like :
List<WebElement> optionList = driver.findElements(By.cssSelector(".x-combo-list-item")); // get all options
WebElement option = optionList.get(1);
option.click();
input.sendKeys(option.getText()); //getText() get the html inner value
This is just an example in Java and you can actually use a loop foreach if you want to automat this populate for all your inputs.
I use JavaScriptExecutor, my SelectRandomOption looks like this:
public void SelectRandomOption()
{
String randomOptionIndex = "Math.floor(Math.random()*Ext.getCmp('" + ExtJSIdOfComboBox + "').getStore().getCount()-1)";
String randomOptionValue = "Ext.getCmp('" + ExtJSIdOfComboBox + "').getStore().getAt(" + randomOptionIndex + ").getData()['model']";
String jsScript = "Ext.getCmp('" + ExtJSIdOfComboBox + "').setValue(" + randomOptionValue + ");";
js.ExecuteScript(jsScript);
}
I basically used the marked answer above however it needed a bit of adapting for Ext Js 4.1.
It's essentially the same approach but you need to look for a visible div marked with class "x-boundlist"
I used xpath and used queries that looked something like this:
.//div[#class[contains(.,'x-boundlist')]]
and then retrieve and click on an li matching your desired entry:
.//li[normalize-space(text())='combobox entry text']
I've put normalize-space in there as xpath seems to have real problems if you dont trim strings. That methods does a left + right trim and also removes duplicate spaces eg
' blah blah ' would change to 'blah blah'.

Resources