I have created a custom calendar view for one of my projects using angular. I tried to use a full calendar library but it doesn't give many customization options. This is my custom view in stackblitz. I want to render events. but I don't understand how to do that.
my expectation is this
it's only a idea. You has events like
events:any[]=[{
startDate:'2020-01-06',
endDate:'2020-01-08',
event:'Hello word'
}]
When you render the calendar add "data attributes" and put a template variable reference to allow us to get it using ViewChildren -get the "row" because will be important when a event call in two differents weeks. It's interesting you give as data-day attribute the day you show. For this create a function like
getDate(day:any,row:number)
{
if (day.isSameMonth)
return this.currentMonth.getFullYear()+'-'+('0'+(this.currentMonth.getMonth()+1)).slice(-2)
+'-'+('0'+day.day).slice(-2)
if (row==0)
{
return this.currentMonth.getMonth()?
this.currentMonth.getFullYear()+'-'+
('0'+this.currentMonth.getMonth()).slice(-2)+'-'+
('0'+day.day).slice(-2):
(this.currentMonth.getFullYear()-1)+'-12-'+('0'+day.day).slice(-2)
}
return this.currentMonth.getMonth()!=11?
this.currentMonth.getFullYear()+'-'+
('0'+this.currentMonth.getMonth()+2).slice(-2)+'-'+
('0'+day.day).slice(-2):
(this.currentMonth.getFullYear()+1)+'-01-'+('0'+day.day).slice(-2)
}
<div #daysCell
[attr.data-day]="getDate(days,i)"
[attr.data-row]="i"
[ngClass] = "{'today-cell':days.isToday === true}">
{{days.day}}
</div>
Render the events in a div as
<div *ngFor="let evento of events;let i=index">
<div #event [attr.data-event]="i" class="event">{{evento.event}}</div>
</div>
Well, the funny part. We get the "cells" and the "events" as
#ViewChildren('daysCell') cells:QueryList<ElementRef>
#ViewChildren('event') eventsCells:QueryList<ElementRef>
And in a function renderEvent you use Renderer2 to give position left top-rigth to your elements
renderEvents(){
const cells=this.cells.map(x=>{
return {
pos:x.nativeElement.getBoundingClientRect(),
day:x.nativeElement.getAttribute('data-day'),
row:x.nativeElement.getAttribute('data-row'),
}
})
this.eventsCells.forEach((x:any,index:number)=>{
const cellStart=cells.find(c=>c.day==this.events[index].startDate)
const cellEnd=cells.find(c=>c.day==this.events[index].endDate)
this.render.setStyle(x.nativeElement,'top',(cellStart.pos.top+1)+'px')
this.render.setStyle(x.nativeElement,'left',(cellStart.pos.left+1)+'px')
this.render.setStyle(x.nativeElement,'width',
(cellEnd.pos.left-cellStart.pos.left+cellEnd.pos.width)+'px')
})
}
Well, it's only an aprox. You need check if the event are in two weeks, if a event it's showed or not... and another ajusts (I hope minnor adjusts)
You can see in stackblitz
Update if we want to place more than one event in a date, we need add a propertie to the "events", e.g. we can called "row", so, the "top" becomes like
this.render.setStyle(
x.nativeElement,
"top",
(this.events[index].row
? this.events[index].row *
(2 + x.nativeElement.getBoundingClientRect().height) +
cellStart.pos.top
: cellStart.pos.top + 1) + "px"
);
Now, the dificult is know how give the value to "row". For this, we need loop over the events, store in an array the days that are ocupped and, if one is ocuped increment the row. puff. make a function
getEventsRows() {
const datesOcupped: any[] = [];
this.events.forEach(x => {
x.row = 0;
const startDate = new Date(x.startDate).getTime();
const endDate = new Date(x.endDate).getTime();
for (let date = startDate;date <= endDate;date = date + 24 * 60 * 60 * 1000) {
const dateOcupped = datesOcupped.length?
datesOcupped.find(x => x.time == date): null;
if (!dateOcupped) {
datesOcupped.push({
time: date,
row: 0
});
} else dateOcupped.row++;
x.row = dateOcupped? dateOcupped.row > x.row?
dateOcupped.row: x.row: 0;
}
});
}
Related
I'm currently trying to render a specific class across two lightning-badge components that is suppose to change both badges from inverse to success, but am getting this instead:
When the value on the left badge equals the value on the right (so in this case both are 3), they should both be green, otherwise they should both be grey. They should never be seperate colours.
The value on the left increases as the user saves a record and is checked on status of "Completed". For some reason only the class on the second badge is being updated with the new class that includes slds-theme_success. I may be missing something small, but just haven't been able to figure it out. Please see code below:
badgeClass = "slds-badge_inverse slds-var-m-horizontal_x-small slds-col";
get patientsCompleted() {
if(this.records) {
let completedArr = this.records.filter(value => value.fields.Status__c.value == "Completed");
if(completedArr.length === this.patientsTotal) {
this.badgeClass = "slds-badge_inverse slds-theme_success slds-var-m-horizontal_x-small slds-col";
}
return completedArr.length;
}
};
get patientsTotal(){
if(this.records) {
return this.records.length;
}
};
<span class="slds-col_bump-left">
<div class="slds-grid slds-gutters">
<lightning-badge class={badgeClass} label={patientsCompleted}></lightning-badge>
<div class="slds-col"> of </div>
<lightning-badge class={badgeClass} label={patientsTotal}></lightning-badge>
</div>
</span>
Have you tried moving badgeClass to a getter? Something like this:
get patientsCompleted() {
if(this.records) {
let completedArr = this.records.filter(value => value.fields.Status__c.value == "Completed");
// No longer needed
// if(completedArr.length === this.patientsTotal) {
// this.badgeClass = "slds-badge_inverse slds-theme_success slds-var-m-horizontal_x-small slds-col";
// }
return completedArr.length;
}
};
get patientsTotal(){
if(this.records) {
return this.records.length;
}
};
get badgeClass() {
let baseClass = "slds-badge_inverse slds-var-m-horizontal_x-small slds-col";
return this.patientsCompleted === this.patientsTotal ? `${baseClass} slds-theme_success` : `${baseClass}`
}
I suspect LWC field tracking has some precautionary mechanism and didn't trigger the update.
I am not sure but perhaps if 0 records are available you want the badges to remain gray? In that case include this.patientsTotal > 0 in the get badgeClass() {...}.
Happy coding.
I'm having a little issue with FullCalendar v5, I configured it with the dayGridMonth View and I would like to listen when the user changes the current month ...
For example, if he is seeing February and click on next he'll see march, so I was expected for a handler like onChange or onMonthChange but I didn't find anything in the documentation to do something like this ...
I figured how to get around the problem by making my own prev / next buttons and triggering my custom handler on the click ... But I would like to know if there is a vanilla way to do it?
Thanks for your answers.
As mentionned by #Devsi Odedra, the answer was datesSet
The doc : https://fullcalendar.io/docs/datesSet
This is my actual code if it can help someone :
new Calendar(document.getElementById("calendar"), {
plugins: [ dayGridPlugin, interactionPlugin ],
datesSet: event => {
// As the calendar starts from prev month and end in next month I take the day between the range
var midDate = new Date((event.start.getTime() + event.end.getTime()) / 2).getMonth()
var month = `0${ midDate.getMonth() + 1 }`.splice(0, -1)
doSomethingOnThisMonth(month, midDate.getFullYear())
}
});
function doSomethingOnThisMonth(month, year) {
fetch(`myApi.com/something?month=${ month }&year=${ year }`)
.then((result) => {
// Do something
})
}
I am using ng2-dragula for drag and drop feature. I am seeing issue when I drag and drop first element(or any element) at the end and then try to add new item to the array using addNewItem button, new item is not getting added to the end. If i don't drop element to the end, new item is getting added at the end in UI.
I want new items to be displayed at the bottom in any scenario. Any help is appreciated.
This issue is not reproducible with Angular 7. I see this happening with Angular 9
JS
export class SampleComponent {
items = ['Candlestick','Dagger','Revolver','Rope','Pipe','Wrench'];
constructor(private dragulaService: DragulaService) {
dragulaService.createGroup("bag-items", {
removeOnSpill: false
});
}
public addNewItem() {
this.items.push('New Item');
}
}
HTML
<div class="container" [dragula]='"bag-items"' [(dragulaModel)]='items'>
<div *ngFor="let item of items">{{ item }}</div>
</div>
<button id="addNewItem" (click)="addNewItem()">Add New Item
I edited the stackblitz from the comment to help visualize the issue. This seems to be triggered when a unit is dragged to the bottom of the list. Updated stackblitz : https://stackblitz.com/edit/ng2-dragula-base-ykm8fz?file=src/app/app.component.html
ItemsAddedOutOfOrder
You can try to restore old item position on drop.
constructor(private dragulaService: DragulaService) {
this.subscription = this.dragulaService.drop().subscribe(({ name }) => {
this.dragulaService.find(name).drake.cancel(true);
});
}
Forked Stackblitz
Explanation
There is some difference between how Ivy and ViewEngine insert ViewRef at specific index. They relay on different beforeNode
Ivy always returns ViewContainer host(Comment node)ref if we add item to the end:
export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: LContainer): RNode|
null {
const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
if (nextViewIndex < lContainer.length) {
const lView = lContainer[nextViewIndex] as LView;
const firstTNodeOfView = lView[TVIEW].firstChild;
if (firstTNodeOfView !== null) {
return getFirstNativeNode(lView, firstTNodeOfView);
}
}
return lContainer[NATIVE]; <============================= this one
}
ViewEngine returns last rendered node(last <li/> element)ref
function renderAttachEmbeddedView(
elementData: ElementData, prevView: ViewData|null, view: ViewData) {
const prevRenderNode =
prevView ? renderNode(prevView, prevView.def.lastRenderRootNode!) : elementData.renderElement;
...
}
The solution might be reverting the dragged element back to original container so that we can let built-in ngForOf Angular directive to do its smart diffing.
Btw, the same technique is used in Angular material DragDropModule. It remembers position of dragging element and after we drop item it inserts it at its old position in the DOM which is IMPORTANT.
I wanted to create a list of settings that a user can change in the HTML procedurally through javascript.
Much like this: Quality: - 0 +
My approach to this was making an Option class with a value property and prev() and next() methods that change the value within its range. I'm extending this class so it can be a Range, Bool, etc. This is working fine.
My issue is that I can't seem to be able to incorporate this into the HTML. My current solution works only for the last option created, the others don't trigger the onclick event functions, and even if they did I don't feel like this is the right approach to it. How can I make this work in an elegant way?
I have tried the solution shown in this question but it prevents me from accessing the class instance with this.
class UIManager {
constructor (wrapperID, settings) {
this.wrapper = document.getElementById(wrapperID)
this.settings = settings
}
updateUI () {
this.wrapper.innerHTML = ``
for (let id = 0; id < this.settings.options.length; ++id) {
let option = this.settings.options[id]
this.wrapper.innerHTML += `
<li>
<div class="label">
${option.name}
</div>
<div class="option">
<input id="prev${id}" class="open" type="button" value="<">
${option.value}
<input id="next${id}" class="close" type="button" value=">">
</div>
</li>
`
let prevButton = document.getElementById(`prev${id}`)
let nextButton = document.getElementById(`next${id}`)
prevButton.onclick = _ => {
this.settings.options[id].prev()
this.updateUI()
}
nextButton.onclick = _ => {
this.settings.options[id].next()
this.updateUI()
}
}
}
}
The answer to the question you linked as a potential solution is usable so long as you bind the class instance member this to the anonymous function using Function.protoype.bind.
The code would look something like this (using the previously linked answer as the starting point):
for ( var i = 0; i < itemLists.length; i++ ) (function(i){
itemLists[i].onclick = function() {
// do something using `this`
}
}).bind(this)(i);
You mentioned in the comments that this didn't work, but that it was related to overwriting innerHTML and not due to the binding.
Hope this gives a small part of the larger picture.
Try closure to store the values required for functions created from inside the loop.
Something like this :
prevButton.onclick = (function(settings, updateUI) {
return function() {
settings.options[id].prev();
updateUI();
};
})(this.settings, this.updateUI);
nextButton.onclick = (function(settings, updateUI) {
return function() {
settings.options[id].next();
updateUI();
}
})(this.settings, this.updateUI);
I am trying to build an SAPUI5 application using TreeTable and I'm facing some problems to use its methods.
In my app, I have a button which triggers this method.
onChangeViewContext: function(oEvent) {
.........
.........
var aViewContext = oContext.oModel.getProperty(sPath + "/ViewContext");
var aDataModel = oContext.oModel.getProperty("/ApplicationCollection/" + sAppId + "/DataModel");
var oStructure = this._createParentChildStructure(aDataModel);
var oTreeModel = this.getView().getModel("treeModel");
oTreeModel.setData(oStructure);
this._oViewDetailLine = oSource.getParent().getParent().getParent();
this._oViewDetailLine.setVisible(false);
this.byId("idSelectElementsPanel").setVisible(true);
this._setSelectedItems(aViewContext, oTree);
}
What I'm trying to do here is only bind the rows with my treeModel, get tree table object and send it to my _setSelectedItems method which below.
_setSelectedItems: function(aViewContext, oTree) {
oTree.clearSelection();
var sElementName;
var aSelectedIndices = [];
var aElements = [];
var aRows = oTree.getRows();
aRows.forEach(function(row) {
if (row._oNodeState !== undefined) {
aElements.push(row.getCells()[0].getText());
}
});
I need to get rows array here because I will use it for setting selected items of tree table. The problem is when "onChangeViewContext" triggered, oTable.getRows() returns an empty array. But when I click cancel button (which just hides my tree table, nothing more) and then trigger "onChangeViewContext" function again, I can get the rows array completely.
Even on the first call when I try to get table's model, I can get the treeModel and its data correctly.
I've tried to refresh bindings, aggregations etc. But no luck.
By the way, I'm using row binding in my xml view like this :
<t:TreeTable id="idSelectElementsTree" rows="{path: 'treeModel>/'}" selectionMode="MultiToggle" enableSelectAll="false"
rowSelectionChange="onSelectElement">
I'm really drowning here so any any help would be appreciated.
Edit : rest of the setSelectedIndexes function :
aViewContext.forEach(function(name) {
sElementName = name;
if (aElements.indexOf(sElementName) !== -1) {
aSelectedIndices.push(aElements.indexOf(sElementName));
}
});
aSelectedIndices.forEach(function(idx) {
if (oTree.getRows()[idx]._bHasChildren) {
oTree.expand(idx);
}
oTree.addSelectionInterval(idx, idx);
});
What could help here is to add an event rowsUpdated="onRowsUpdated" to the table in the XML view. This event is triggered after the table has been loaded and will hence provide you with the data via;
this.getView().byId("sTableId").getRows();
The difference to your approach is that the event would not be triggered by the press of a button but automatically, as the table is rendered. You can then also use this function to trigger another one as per your use case.