My users are wanting to be able to see the status of various rooms (Y axis) across dates (X axis) in a project timeline format.
Some examples:
I was hoping to maybe utilize the bar chart widget, but it looks like that requires the fields to be numerical (dates won't work).
Like I said, I realize it is a long shot, but I figured I would try.
I was using this library https://docs.dhtmlx.com/scheduler/ to implement scheduler in App Maker project. But it can be switched to bar timeline widget. Here is an example: https://docs.dhtmlx.com/scheduler/samples/06_timeline/02_lines.html
To add the widget to your project, open project settings, find External resources and add to
JavaScript URLs:
https://docs.dhtmlx.com/scheduler/codebase/ext/dhtmlxscheduler_timeline.js
//cdn.dhtmlx.com/scheduler/edge/dhtmlxscheduler.js
Also you need to add library styles to CSS URLs:
//cdn.dhtmlx.com/scheduler/edge/dhtmlxscheduler.css
Now we can init this widget in a project. Add Html widget to your page. For example it has Projects datasource. Check allowUnsafeHtml property and put html code into it:
<div id="schedulerContainer" tabindex="0" aria-label="events calendar" class="dhx_cal_container" style="width:960px; height:555px;">
<div class="dhx_cal_navline">
<div class="dhx_cal_prev_button"> </div>
<div class="dhx_cal_next_button"> </div>
<div class="dhx_cal_today_button"></div>
<div class="dhx_cal_date"></div>
<div class="dhx_cal_tab" name="day_tab" style="right:204px;"></div>
<div class="dhx_cal_tab" name="week_tab" style="right:140px;"></div>
<div class="dhx_cal_tab" name="month_tab" style="right:76px;"></div>
</div>
<div class="dhx_cal_header">Timeline is not loaded</div>
<div class="dhx_cal_data"></div>
</div>
And add widget initialization to onDataLoad event of the Html widget:
initTimeline(widget);
Now let's create client script and implementation of function initTimeline:
function initTimeline(widget) {
const datasource = widget.datasource;
// shceduler is a global variable
scheduler.locale.labels.timeline_tab = "Timeline";
scheduler.locale.labels.section_custom = "Section";
scheduler.config.details_on_create = true;
scheduler.config.details_on_dblclick = true;
scheduler.config.xml_date = "%Y-%m-%d %H:%i";
//===============
//Configuration
//===============
// sections can fetched from datasource items
var sections = [{
key: 1,
label: "James Smith"
},
{
key: 2,
label: "John Williams"
},
{
key: 3,
label: "David Miller"
},
{
key: 4,
label: "Linda Brown"
}
];
scheduler.createTimelineView({
name: "timeline",
x_unit: "minute",
x_date: "%H:%i",
x_step: 30,
x_size: 24,
x_start: 16,
x_length: 48,
y_unit: sections,
y_property: "section_id",
render: "bar"
});
//===============
//Data loading
//===============
scheduler.config.lightbox.sections = [{
name: "description",
height: 130,
map_to: "text",
type: "textarea",
focus: true
},
{
name: "custom",
height: 23,
type: "select",
options: sections,
map_to: "section_id"
},
{
name: "time",
height: 72,
type: "time",
map_to: "auto"
}
];
scheduler.init('schedulerContainer', new Date(2017, 5, 30), "timeline");
/*
this data also can be fetched from datasource and parsed
to proper format using code like this:
const data = datasource.items.map((item) => {
return {
start_date: item.start_date,
end_date: item.end_date,
text: item.name,
section_id: item.category_id
};
});
*/
scheduler.parse([{
start_date: "2017-06-30 09:00",
end_date: "2017-06-30 12:00",
text: "Task A-12458",
section_id: 1
},
{
start_date: "2017-06-30 10:00",
end_date: "2017-06-30 16:00",
text: "Task A-89411",
section_id: 1
},
{
start_date: "2017-06-30 10:00",
end_date: "2017-06-30 14:00",
text: "Task A-64168",
section_id: 1
},
{
start_date: "2017-06-30 16:00",
end_date: "2017-06-30 17:00",
text: "Task A-46598",
section_id: 1
},
{
start_date: "2017-06-30 12:00",
end_date: "2017-06-30 20:00",
text: "Task B-48865",
section_id: 2
},
{
start_date: "2017-06-30 14:00",
end_date: "2017-06-30 16:00",
text: "Task B-44864",
section_id: 2
},
{
start_date: "2017-06-30 16:30",
end_date: "2017-06-30 18:00",
text: "Task B-46558",
section_id: 2
},
{
start_date: "2017-06-30 18:30",
end_date: "2017-06-30 20:00",
text: "Task B-45564",
section_id: 2
},
{
start_date: "2017-06-30 08:00",
end_date: "2017-06-30 12:00",
text: "Task C-32421",
section_id: 3
},
{
start_date: "2017-06-30 14:30",
end_date: "2017-06-30 16:45",
text: "Task C-14244",
section_id: 3
},
{
start_date: "2017-06-30 09:20",
end_date: "2017-06-30 12:20",
text: "Task D-52688",
section_id: 4
},
{
start_date: "2017-06-30 11:40",
end_date: "2017-06-30 16:30",
text: "Task D-46588",
section_id: 4
},
{
start_date: "2017-06-30 12:00",
end_date: "2017-06-30 18:00",
text: "Task D-12458",
section_id: 4
}
], "json");
}
This answer is a general guide how to add this widget. But it may contain errors, so I'd advise you to take a look at the widget documentation as well
Unfortunately, there is no native timeline chart support in App Maker. I would suggest trying to find a JS library that can draw a timeline and use that in your App Maker application. The Google visualization library has a timeline chart I believe and the library is included by default in App Maker applications.
Related
Google Calendar Docs suggest that there is an way to enter 3P links. But, I failed after multiple attempts. Is there any way to do so?
I tried different ways to enter details into conferenceData object. Tried insert, update and patch.
Code Snippet
const insertEvent = await calendar.events.insert({
calendarId: "primary",
sendNotifications: true,
supportsAttachments: true,
requestBody: {
summary: "Hell Yeah",
start: {
dateTime: "2022-03-25T08:30:00.000Z",
timeZone: "Asia/Kolkata",
},
end: {
dateTime: "2022-03-25T09:30:00.000Z",
timeZone: "Asia/Kolkata",
},
conferenceData: {
conferenceId: "93831668388",
conferenceSolution: {
iconUri: "https://st1.zoom.us/static/6.0.5527/image/new/ZoomLogo.png",
key: { type: "addOn" },
name: "Zoom",
},
entryPoints: [
{
entryPointType: "video",
label: "zoom.us/j/93831668388?pwd=UHRqU0VwMjF3d1B0VXU5bFBRYWIxdz09",
passcode: "4gnZXJ",
uri: "https://zoom.us/j/93831668388?pwd=UHRqU0VwMjF3d1B0VXU5bFBRYWIxdz09",
},
],
},
},
});
But the event was created without any zoom meeting links.
The problem with your request is the iconUri. Your request will work without it. The documentation does not specify any requirement for iconUri, however, I tried with multiple formats (png, SVG, jpg, etc.) and it's not clear to me what the restrictions are, even the icon they use for Google Meet will fail. One example of a working iconUri is the one used by the Zoom add-on.
The minimal working example is something like this:
conferenceData: {
conferenceSolution: {
key: { type: "addOn" },
name: "Zoom",
},
entryPoints: [
{
entryPointType: "video",
passcode: "4gnZXJ",
uri: "https://zoom.us/j/93831668388?pwd=UHRqU0VwMjF3d1B0VXU5bFBRYWIxdz09",
},
],
},
Something similar to the one generated by the plugin will be like this:
"conferenceData": {
"entryPoints": [
{
"entryPointType": "video",
"uri": "https://zoom.us/j/93831668388?pwd=UHRqU0VwMjF3d1B0VXU5bFBRYWIxdz09",
"label": "zoom.us/j/93831668388?pwd=UHRqU0VwMjF3d1B0VXU5bFBRYWIxdz09",
"meetingCode": "93831668388",
"passcode": "4gnZXJ"
},
{
"regionCode": "US",
"entryPointType": "phone",
"uri": "tel:+13092053325,,89815701514#",
"label": "+1 309-205-3325",
"passcode": "2753507469"
},
{
"regionCode": "IE",
"entryPointType": "phone",
"uri": "tel:+35316533895,,89815701514#",
"label": "+353 1 653 3895",
"passcode": "2753507469"
},
{
"entryPointType": "more",
"uri": "https://applications.zoom.us/addon/invitation/detail?meetingUuid%3D%252BEfhBWj1RaeatV6Qo4QPeg%253D%253D%26signature%1a2E5Aa06313c4d378f6a231d83a13eec96f1643f81e300de80bf9eb4784b91440%26v%3D1&sa=D&source=calendar&usg=AOvVaw2jZJmsX7bEbWL1wVde7dOM"
}
],
"conferenceSolution": {
"key": {
"type": "addOn"
},
"name": "Zoom Meeting",
"iconUri": "https://lh3.googleusercontent.com/pw/AM-JKLUkiyTEgH-6DiQP85RGtd_BORvAuFnS9katNMgwYQBJUTiDh12qtQxMJFWYH2Dj30hNsNUrr-kzKMl7jX-Qd0FR7JmVSx-Fhruf8xTPPI-wdsMYez6WJE7tz7KmqsORKBEnBTiILtMJXuMvphqKdB9X=s128-no"
},
"conferenceId": "93831668388",
"notes": "Meeting host: <a href=\"mailto:meeting_creator#mail.com\" target=\"_blank\" > meeting_creator#mail.com</a> <br> <br> Join Zoom Meeting: <br> <a href=\"meeting_creator#mail.com\" target=\"_blank\" > https://zoom.us/j/93831668388?pwd=UHRqU0VwMjF3d1B0VXU5bFBRYWIxdz09 </a>"
}
How to define a custom filter-function for a column or cell? Just as an example, assume we have a text value and in the header there is a search input
How to tell the gridview to call a function
class FooComponent {
protected doSomeFilter (value: string, searchQuery: string) {
if (someConditions(value, searchQuery)) {
return true;
}
return false;
}
}
And I would expect to use it like this:
<dxi-column
dataField="myFooProperty"
[(customFilter)]='doSomeFilter'
></dxi-column>
But the GridView doesn't support customFilter method, and nothing similar. Do you know how to achieve such custom filtering in devexpress, looks very simple but I'm struggling with this for hours. Thank you in advance.
This example shows how to filter data using the filter panel. You can use its check box to enable/disable the current filter expression, and clicking on this expression opens the integrated filter builder. Note that changes made in it are reflected in the filter row and header filter, and vice versa, from (https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/FilterPanel/jQuery/Light/), Please see this:
$("#gridContainer").dxDataGrid({
dataSource: orders,
columnsAutoWidth: true,
filterRow: { visible: true },
filterPanel: { visible: true },
headerFilter: { visible: true },
filterValue: [["Employee", "=", "Clark Morgan"], "and", ["OrderDate", "weekends"]],
filterBuilder: {
customOperations: [{
name: "weekends",
caption: "Weekends",
dataTypes: ["date"],
icon: "check",
hasValue: false,
calculateFilterExpression: function() {
return [[getOrderDay, "=", 0], "or", [getOrderDay, "=", 6]];
}
}]
},
filterBuilderPopup: {
position: { of: window, at: "top", my: "top", offset: { y: 10 } },
},
scrolling: { mode: "infinite" },
showBorders: true,
columns: [{
caption: "Invoice Number",
dataField: "OrderNumber",
dataType: "number",
headerFilter: {
groupInterval: 10000
}
}, {
dataField: "OrderDate",
dataType: "date"
}, {
dataField: "SaleAmount",
dataType: "number",
format: "currency",
editorOptions: {
format: "currency",
showClearButton: true
},
headerFilter: {
dataSource: [ {
text: "Less than $3000",
value: ["SaleAmount", "<", 3000]
}, {
text: "$3000 - $5000",
value: [["SaleAmount", ">=", 3000], ["SaleAmount", "<", 5000]]
}, {
text: "$5000 - $10000",
value: [["SaleAmount", ">=", 5000], ["SaleAmount", "<", 10000]]
}, {
text: "$10000 - $20000",
value: [["SaleAmount", ">=", 10000], ["SaleAmount", "<", 20000]]
}, {
text: "Greater than $20000",
value: ["SaleAmount", ">=", 20000]
}]
}
}, {
dataField: "Employee",
dataType: "string"
}, {
caption: "City",
dataField: "CustomerInfo.StoreCity",
dataType: "string"
}, {
caption: "State",
dataField: "CustomerInfo.StoreState",
dataType: "string"
}]
});
Also see this: https://jsfiddle.net/mx1ovwp1/7/
$("#gridContainer").dxDataGrid({
dataSource: employees,
filterRow: {
visible: true,
applyFilter: "auto"
},
groupPanel:{
visible: true
},
searchPanel: {
visible: true,
width: 240,
placeholder: "Search..."
},
headerFilter: {
visible: true
},
paging: {
enabled: false
},
editing: {
mode: "form",
allowUpdating: true
},
columns: [
{
dataField: "Prefix",
caption: "Title",
width: 70
},
"FirstName",
"LastName", {
dataField: "Position",
width: 170
}, {
dataField: "StateID",
caption: "State",
width: 125,
lookup: {
dataSource: states,
displayExpr: "Name",
valueExpr: "ID"
}
}, {
dataField: "BirthDate",
dataType: "date"
}
]
});
Also refer to this link(dxDataGrid - How to implement a custom filter for a date field):
https://supportcenter.devexpress.com/ticket/details/t490195/dxdatagrid-how-to-implement-a-custom-filter-for-a-date-field
For the demo from DevExtreme, see this:
https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/Filtering/jQuery/Light/
I am hitting events list api on my primary calendar using https://developers.google.com/calendar/v3/reference/events/list, but the response I get is as below -
{
"kind": "calendar#events",
"etag": "\"p32cepg73vamue0g\"",
"summary": "xxxxx#gmail.com",
"updated": "2020-01-31T13:37:23.519Z",
"timeZone": "Asia/Calcutta",
"accessRole": "owner",
"defaultReminders": [
{
"method": "popup",
"minutes": 30
},
{
"method": "email",
"minutes": 30
}
],
"nextPageToken": "CigKGnRwaDY1ajllNm5ldmRtcjJkMmlyMmlwNm40GAEggICA5OuK7f8WGg0IABIAGJjswOP6recCIgcIAhCKnqEB",
"items": []
}
There are a lot of events created in my Calendar, but I am not getting any items returned from the API.
screen
I want this look when I switch to viewing the "agenda one day".
What i'm need to configure in the configuration for this view.
UPD: version 3.9
UPD2:
sandbox there
this config file (short version):
const config = {
header: false,
schedulerLicenseKey: "GPL-My-Project-Is-Open-Source",
defaultView: "agendaOneDay",
slotDuration: "00:15:00",
columnFormat: "ddd, DD MMM",
editable: false,
selectable: true,
groupByResource: true,
views: {
agendaOneDay: {
type: "agenda",
duration: { days: 1 },
buttonText: "1"
},
agendaThreeDay: {
type: "agenda",
duration: { days: 3 },
buttonText: "3"
}
},
resources: [
{ id: 5128, title: "Test title 1", eventColor: "#5eb15e" }
],
events: [
{
start: "2018-09-24T09:00:00.000Z",
end: "2018-09-24T09:30:00.000Z",
resourceId: 5128,
title: "test 1",
event: {},
color: "",
textColor: "#191919"
},
{
start: "2018-09-26T09:00:00.000Z",
end: "2018-09-26T09:30:00.000Z",
resourceId: 5128,
title: "test 1",
event: {},
color: "",
textColor: "#191919"
}
]
};
I am using autoform and collection2 package and making a form in meteor. As of now i put some hard-coded option for country-state-city dropdown and insert-update is working fine. Now I want for the first time only country dropdown is enable other two are disable. Based on Country selection the states dropdown will populate and enable. Then based on State selection City Should Populate.
I don't want to do this manually. Is there any way to do this using autoform / collection2 features??? My code sample is as follows:
Collection2 Schema:
country:{
type: String,
label : "Country",
autoform: {
afFieldInput: {
type: "select"
},
options: function () {
return [
{label: 'Country1', value: 'Country1'},
{label: 'Country2', value: 'Country2'},
{label: 'Country3', value: 'Country3'},
{label: 'Country4', value: 'Country4'}
];
}
}
},
state:{
type: String,
label : "State",
autoform: {
afFieldInput: {
type: "select"
},
options: function () {
return [
{label: 'State1', value: 'State1'},
{label: 'State2', value: 'State2'},
{label: 'State3', value: 'State3'},
{label: 'State4', value: 'State4'}
];
}
}
},
city:{
type: String,
label : "City",
autoform: {
afFieldInput: {
type: "select"
},
options: function () {
return [
{label: 'City1', value: 'City1'},
{label: 'City2', value: 'City2'},
{label: 'City3', value: 'City3'},
{label: 'City4', value: 'City4'}
];
}
}
},
HTML ::
{{> afQuickField name='country' template="bootstrap3-horizontal" label-class="col-sm-4" input-col-class="col-sm-8"}}
{{> afQuickField name='state' template="bootstrap3-horizontal" label-class="col-sm-4" input-col-class="col-sm-8"}}
{{> afQuickField name='city' template="bootstrap3-horizontal" label-class="col-sm-4" input-col-class="col-sm-8"}}
Any Help??
I think this is somewhat the idea you have, https://jsfiddle.net/bdhacker/eRv2W/
// Countries
var country_arr = new Array("Afghanistan", "Albania", "Algeria", "American Samoa", "Angola", "Anguilla", "Antartica"...
// States
var s_a = new Array();
s_a[0] = "";
s_a[1] = "Badakhshan|Badghis|Baghlan|Balkh|Bamian|Farah|Faryab|Ghazni|Ghowr|Helmand|Herat|Jowzjan|Kabol|Kandahar|Kapisa|Konar|Kondoz|Laghman|Lowgar|Nangarhar|Nimruz|Oruzgan|Paktia|Paktika|Parvan|Samangan|Sar-e Pol|Takhar|Vardak|Zabol";...
you can extract the data from this and adjust to your app. Hope it helps
i think you can set the inputs of state and city to be disabled
country:{
type: String,
label : "Country",
autoform: {
afFieldInput: {
type: "select"
},
options: function () {
return [
{label: 'Country1', value: 'Country1'},
{label: 'Country2', value: 'Country2'},
{label: 'Country3', value: 'Country3'},
{label: 'Country4', value: 'Country4'}
];
}
}
},
state:{
type: String,
label : "State",
autoform: {
afFieldInput: {
type: "select",
disabled:true
},
options: function () {
return [
{label: 'State1', value: 'State1'},
{label: 'State2', value: 'State2'},
{label: 'State3', value: 'State3'},
{label: 'State4', value: 'State4'}
];
}
}
},
city:{
type: String,
label : "City",
autoform: {
afFieldInput: {
type: "select",
disabled:true
},
options: function () {
return [
{label: 'City1', value: 'City1'},
{label: 'City2', value: 'City2'},
{label: 'City3', value: 'City3'},
{label: 'City4', value: 'City4'}
];
}
}
},
and use Template event to enable the options
Template.YOURTEMPLATENAME.events({
'change input[name="country"]' :function(){
if ($('input[name="country"]').value() != ''){
$('input[name="state"]').attr('disabled','');
}else {
$('input[name="state"]').attr('disabled','disabled');
}
},
'change input[name="state"]' :function(){
if ($('input[name="state"]').value() != ''){
$('input[name="city"]').attr('disabled','');
}else {
$('input[name="city"]').attr('disabled','disabled');
}
}
});