How do I disable future dates when using jQuery datepicker inside Tabulator? - jquery-ui-datepicker

I am attempting to disable future dates on a jQuery datepicker being utilized with Tabulator but to no avail.
var table = new Tabulator("#MyDiv", {
height: "100%",
layout: "fitDataFill",
columns: [
{ title: "Date Worked", field: "DateComp", hozAlign: "center", sorter: "date", editor: dateEditor },
{ title: "Memo", field: "Memo", width: 144, hozAlign: "left", editor: "input" },
]
});
var dateEditor = function (cell, onRendered, success, cancel) {
var cellValue = moment(cell.getValue(), "MM/DD/YYYY").format("YYYY-MM-DD");
input = document.createElement("input");
input.setAttribute("type", "date");
input.style.padding = "4px";
input.style.width = "100%";
input.style.boxSizing = "border-box";
input.value = cellValue;
onRendered(function () {
input.style.height = "100%";
//$(input).datepicker({ endDate: new Date() });
$(input).datepicker({ maxDate: 0 });
input.focus();
});
function onChange() {
if (input.value != cellValue) {
success(moment(input.value, "YYYY-MM-DD").format("MM/DD/YYYY"));
} else {
cancel();
}
};
//submit new value on blur or change
input.addEventListener("blur", onChange);
//submit new value on enter
input.addEventListener("keydown", function (e) {
if (e.keyCode == 13) {
onChange();
}
if (e.keyCode == 27) {
cancel();
}
});
return input;
};
I have attempted a couple of fixes by tweaking the datepicker options list (e.g. maxDate and endDate) but nothing seems to work. The future dates on the datepicker are selectable regardless. Is this a Tabulator issue? Or, a jQuery issue?
I have found similar questions regarding use of the jQuery datepicker on other forums and the recommended solutions always seem to revolve around use of the maxDate and endDate options.
Any assistance is greatly appreciated.

It looks like there is an issue using the datepicker inside of the cell, that I couldn't figure out. An error is thrown about the instance data missing.
Here is an example using flatpickr instead of the jQuery datepicker.
https://jsfiddle.net/nrayburn/65t1dp23/49/
The two most important parts are including a validator, so that users cannot type in a date. (I don't think they ever could, but if somehow they do it will prevent invalid dates.). The other is using the maxDate or equivalent parameter from the date picking library when you create the date picker instance.
Here is a custom validator to prevent any dates in the future. (It may not handle time differences properly in this setup.)
function noFutureDate(cell, value){
const cellValue = moment(new Date(value));
const today = moment();
if (cellValue.diff(today) > 0){
return false;
}
return true;
}
You also have to create a custom editor. Here is what you specifically need for the date picker instance. You can get the rest from the fiddle, but the other parts aren't really related to a date picker specifically.
const input = document.createElement("input");
input.value = cell.getValue();
onRendered(function(){
flatpickr(input, {
maxDate: moment().format('MM/DD/YYYY')
})
input.focus();
});

Related

APEX row selector

I'm displaying my results on an interactive grid. I'd like to be able to select multiple rows and click an edit button that will open up an “edit” form. I am having a number of problems:
Retrieve the car IDs of the rows selected. (I am having trouble accessing column values, I can access item values)
Pass a collection or array of ids to the edit form.
Save the collection.
Added more code in answer box by accident...……..
I made some progress but I am a little stuck. I followed the oracle blog and it was vey helpful. So on the attribute of the region I added the following code:
function (config) {
var $ = apex.jQuery,
toolbarData = $.apex.interactiveGrid.copyDefaultToolbar(),
toolbarGroup = toolbarData.toolbarFind("actions3");
toolbarGroup.controls.push(
{
type: "BUTTON",
action: "updateCar",
label: "Edit Selected Cars",
hot: true,
});
config.toolbarData = toolbarData;
config.initActions = function (actions)
{
// Defining the action for activate button
actions.add(
{
name: "updateCar",
label: "Edit Selected Cars",
action: updateCar
});
}
function updateCar(event, focusElement)
{
var i, records, model, record,
view = apex.region("ig_car").widget().interactiveGrid("getCurrentView");
var vid = "";
model = view.model;
records = view.getSelectedRecords();
if (records.length > 0)
{
for (i = 0; i < records.length; i++)
{
record = records[i];
//alert("Under Development " + record[1]);
vid = vid + record[1] + "||";
apex.item("P18_CAR").setValue(vid);// This is not needed just to test
//the output
// call next page
// pass array as sql source or directly on page
}
}
}
return config;
}
This works. A button is displayed and when selected it gets the values from the interactive grid. The part I am stuck is how to call the next page and pass the multiple values (2 columns) to the page to be displayed and in a query to do an update.
Thank you if you can help me accomplish this!

Knockout not sending updated value

I have a knockout page where I am formatting the input with regex. It makes the input field to a MM/dd/yyyy format. So if a user inputs "1111" it will change the input vbox to show "01/01/2011" or for "01111" it will show "01/01/2011".
The problem I am facing is that my observable only returning the keystroke entered by user and not the fully formatted item. For example , if user is entering "1111" I get back "1111" instead of the "01/01/2011"
Here is the Html segment
<input id="inpEventDt" placeholder="MM/DD/YYYY" class="input-small" data-date-blur="true" data-regex="^((\d{0,2})|(\d{1,2}/?\d{0,2})|(\d{1,2}/?\d{1,2}/?\d{0,4}))$"
type="text" data-bind="textInput: dateofevent"/>
And this is how I have the knockout binding
var ViewModel = function (eventdt ) {
var self = this;
self.dateofevent = ko.observable(eventdt);
}
viewModel = new ViewModel("");
ko.applyBindings(viewModel);
Trying to figure out what I am doing wrong.
I would not try to format the text input while the user is typing, because it makes a hard to understand user interface and non intuitive typing experience.
In addition, it's more complicated, because while typing, the input is likely invalid.
Try instead to format your input on some event (blur for example), while validating it on keystroke:
var viewModel = function() {
var self = this;
var regex = /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/;
this.isValid = ko.observable(false);
this.date = ko.observable("");
this.format = function() {
self.validate(self.date());
// TODO: something else
}
this.validate = function(newVal) {
var matches = newVal.match(regex);
if (!matches || matches.length != 4) {
self.isValid(false);
} else {
self.isValid(true);
}
};
this.date.subscribe(function(newVal) {
self.validate(newVal);
});
this.style = ko.computed(function() {
return self.isValid() ? "valid" : "invalid";
}, this);
};
var vm = new viewModel();
ko.applyBindings(vm);
.invalid {
border: 1px solid red;
}
.valid {
border: 1px solid green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input id="inpEventDt" placeholder="MM/DD/YYYY" class="input-small" data-date-blur="true" type="text" data-bind="textInput: date, event: { blur: format }, css: style" />
<div data-bind="visible: isValid">OK</div>
You should try using a read/write computed for this. Check out the example 3 in the knockout documentation for computed observables.
Also, here is a jsfiddle using moment.js to help with date formatting.
var ViewModel = function (eventdt ) {
var self = this;
self.dateofevent = ko.observable(eventdt);
self.formattedDate = ko.pureComputed({
read: function () {
return moment(self.dateofevent()).format("MM/DD/YYYY");
},
write: function (value) {
self.dateofevent(moment(value).toDate()); // Write to underlying storage
}
});
}
viewModel = new ViewModel(new Date("03/25/2015"));
ko.applyBindings(viewModel);

chartWrapper of dataView of dataTable created by data.join not updating on value change

First, please pardon the novel below, but this is fairly hard to describe.
I have a fairly complex application that I'm working on at the moment using gviz through HtmlService of a GAS application.
This is more of a client side gviz API question though.
I'm loading 2 data tables through the query method and combining them through data.join. This resulting dataTable is the source for a data view.
The view is then used to generate a dashboard with multiple category filters and a single tableChart. I have a button which toggles between two filter sets for both the view rows and columns displayed. The concept is to load all records and toggle between pending and completed showing the relevant columns to the user.
This part is actually working perfectly well. The issue that I have is a data update issue. Every time the visualisation draws, I iterate through the resulting table and attach actions to various columns.
One such action opens a jQueryUI dialog with the rows values and allows the user to make modifications and submit to the server. When it submits, rather than re-querying my data, my approach is to update the dataTable on AJAX success which is much much faster.
Prior to implementing the join, this was based on a single dataTable and when I updated the dataTable, the dataView based on it would update as well and calling draw() on the chartWrapper would update the UI just as expected.
Trouble is that this isn't working on the joined dataTable. I have confirmed in developer tools on the client side that the dataTable is in fact updating the values as is the dataView, but this never propagates to the tableChart.
And here's the really strange part. If use the .getDataTable method of the chartWrapper in dev tools, I can confirm that the record in question is updated! Though the value in the generated visualisation is not.
I've tried redrawing the chartWrapper in my AJAX success to no avail. It does redraw (chartWrapper) as the scroll level changes, but data does not change. I've tried manually redrawing both the chartWrapper and dashboard in dev tools even passing the view as the dataTable. Both do in fact redraw, but neither updates the data.
I've tried redrawing the getDataTable table on the chartWrapper, but that basically disassociates from my dashboard and styles and draws the entire dataset which is not what I need.
Changing the category filters doesn't update the tableChart after filtering back. Even applying the different filters to the dataView and then back doesn't update it. The only way I've found that works is to re-query the entire thing which takes a good 5-10 seconds. An unacceptable delay for every single change. This is fine when first loading, but ideally that should only happen once per user session.
I've spent hours debugging and scouring the (terrible) documentation and everything I've seen seems to indicate that it should work.
I'm starting to wonder if maybe this is some sort of cache issue or if I'm missing some trick that's not explained in the documentation.
The client side code for this particular page is 1100 lines, so hard to post in full. Below is an extremely simplified version of what I'm doing with the key parts included. Would appreciate any advice or suggestions.
Note, I've omitted quite a few parts related to generating the dialog or buttons for the actions, but they are mostly irrelevant.
var loaded = false;
var ready = {pro: false, sec: false};
var data = {};
// Ajax load gviz api
$.ajax({
url: 'https://www.google.com/jsapi?callback',
cache: true,
dataType: 'script',
success: function(){
google.load('visualization', '1', {packages:['controls'], 'callback' : sendQuery
});
return true;
}
});
function sendQuery() {
console.log('query setting');
var opts = {sendMethod: 'auto'};
var urlPro = 'Google Spreadsheet source 1';
queryPro = new google.visualization.Query(urlPro, opts);
queryPro.setQuery('select A,B,C,D,E where(G = \'No\')');//...15k+ rows 30+ cols in reality
var urlSec = 'Google Spreadsheet source 2';
querySec = new google.visualization.Query(urlSec, opts);
querySec.setQuery('select A,B,C,D');//...~200+ rows 30+ cols in reality, rows created with app
queryPro.send(function(response){
console.log('query Pro returned');
if (response.isError()) {
alert('Error in query Pro: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
ready.pro = true;
data.pro = response.getDataTable();
if (ready.pro && ready.sec) {
drawDashboard(false);
}
});
querySec.send(function(response){
if (response.isError()) {
alert('Error in query Sec: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
console.log('query Sec returned');
ready.sec = true;
data.sec = response.getDataTable();
if (ready.pro && ready.sec) {
drawDashboard(false);
}
});
}
function drawDashboard(complete) {
var pendingCols = [0,2,3,4];
var completeCols = [1,2,3,4,5,7,8];
if (!loaded) {
console.log('first load of data');
var joinProCols=[1,2,3,4];
var joinSecCols=[1,2,3];
joined = new google.visualization.data.join(data.pro, data.sec, 'left', [[0, 0]], joinProCols, joinSecCols);
viewActive = new google.visualization.DataView(joined);
var numCols = joined.getNumberOfColumns();
for (var i=0; i<numCols; i++){
ogColName = joined.getColumnLabel(i).replace(/\W/g, '');
if (empTable.hasOwnProperty(ogColName)){ //passed from server; ommitted def in this example
joined.setColumnLabel(i, empTable[ogColName][lang]);
}
joined.setColumnProperty(i, 'ident', ogColName);
}
}
var pendingRows = joined.getFilteredRows([{column: 4, value: null}]);
var completeRows = joined.getFilteredRows([{column: 4, minValue: ''}]);
if (complete) {
viewActive.setColumns(completeCols);
viewActive.setRows(completeRows);
} else {
viewActive.setColumns(pendingCols);
viewActive.setRows(pendingRows);
}
dashboard = new google.visualization.Dashboard(document.getElementById('dashboard-div'));
// options for displayed table
var tableOpts = {
width: '1500px',
height: '100%',
page: 'enable',
pageSize: 40,
cssClassNames: {
headerCell: 'gviz header',
headerRow: 'gviz header',
oddTableRow: 'gviz odd',
tableRow: 'gviz even',
selectedTableRow: 'gviz selected',
hoverTableRow: 'gviz hover',
rowNumberCell: 'gviz rowNum'
}
};
tableChart = new google.visualization.ChartWrapper({
'chartType': 'Table',
'containerId': 'table-div',
'options': tableOpts
});
var picker1 = new google.visualization.ControlWrapper({
'controlType': 'CategoryFilter',
'containerId': 'sel-pick1',
'options': {
'filterColumnLabel': 'Picker1',
'ui': {
'labelStacking': 'vertical',
'selectedValuesLayout': 'belowWrapping',
'caption': 'Picker1'
}
}
});
var picker2 = new google.visualization.ControlWrapper({
'controlType': 'CategoryFilter',
'containerId': 'sel-pick2',
'options': {
'filterColumnLabel': 'Picker2',
'ui': {
'labelStacking': 'vertical',
'selectedValuesLayout': 'belowWrapping',
'caption': 'Picker2'
}
}
});
//...quite a few more pickers in real code
if (complete) {
var picker3 = new google.visualization.ControlWrapper({
'controlType': 'CategoryFilter',
'containerId': 'sel-pick3',
'options': {
'filterColumnLabel': 'picker3',
'ui': {
'labelStacking': 'vertical',
'selectedValuesLayout': 'belowWrapping',
'caption': 'picker3'
}
}
});
//...A couple more pickers in real app here too
}
// Set up dependencies between controls and charts
dashboard.bind(picker1, picker2);
//...bindings for other pickers
if (complete) {
dashboard.bind(picker2, picker3);
dashboard.bind([picker1, picker2, picker3], tableChart);
} else {
dashboard.bind([picker1, picker2], tableChart);
}
// Draw all visualization components of the dashboard and add listeners
google.visualization.events.addListener(tableChart, 'ready', function(){
google.visualization.events.addListener(tableChart.getChart(), 'page', addFields);
google.visualization.events.addListener(tableChart.getChart(), 'sort', addFields);
google.visualization.events.addListener(tableChart.getChart(), 'select', function(e){
//cancel selection here as it won't be useful
tableChart.getChart().setSelection('');
});
//grab the table and inject our custom actions into it
if (complete) {
addFields(true);
} else {
addFields();
}
// show containers
$('.processing.page').hide();
$('#dashboard-div').show();
loaded = true;
});
dashboard.draw(viewActive);
}
//........................................
$('#dataForm').submit(function( event ) {
if ($('#dataForm').valid()){
$('#dataForm').hide();
$('.processing.data.form').show();
google.script.run.withSuccessHandler(onSuccessUpdate).recordData(this);
}
event.preventDefault();
});
//...................................................
function onSuccessUpdate(response){
var numCols = joined.getNumberOfColumns();
for (var i=0; i<numCols; i++){
for (var key in response.form){
if (!response.form.hasOwnProperty(key)) continue;
if (joined.getColumnProperty(i, 'ident') == key){
joined.setValue(response.row, i, response.form[key]);
}
}
}
tableChart.draw();//This should be updating the values!
$('.message.success').html(response.message).show();
$('#dataForm').show();
$('.processing.data.form').hide();
$('#userProfileDialog').dialog('close');
}
Thanks in advance for any insights. This one is driving me crazy.
So it turns out that everything is working exactly as it should and the issue was in fact that I missed a critical facet of how this works.
When updating the joined dataTable values in the loop, I was was updating the 'value' using the setValue() method.
However, the 'value' that the rendered chart is actually reading is the 'formatted value' accessible through the setFormattedValue() method. When updating the value programatically, there doesn't seem to be an automatic update to the formattedValue. I may have incorrectly assumed that or, more likely, have just been completely ignorant to that portion of it since I didn't use it.
Originally, prior to using the join when I only used a single data source and all was working, I was passing the 'options no_format' parameter to the query. In that context, it seems that the API would use the value instead of the formattedValue for the actual table chart.
Once I realised my error, my first attempt at a fix was to add the 'options no_format' parameter to both of my data queries. This didn't seem to work. It looks like the join method automatically generates formatted values even if it's source dataTables don't have any.
Therefore, the correction to the my original code is to use the setFormattedValue method in addition to the setValues method as follows:
function onSuccessUpdate(response){
var numCols = joined.getNumberOfColumns();
for (var i=0; i<numCols; i++){
for (var key in response.form){
if (!response.form.hasOwnProperty(key)) continue;
if (joined.getColumnProperty(i, 'ident') == key){
joined.setValue(response.row, i, response.form[key]);
//Need to also set formatted value
joined.setFormattedValue(response.row, i, response.form[key]);
}
}
}
tableChart.draw();//This should be updating the values!
$('.message.success').html(response.message).show();
$('#dataForm').show();
$('.processing.data.form').hide();
$('#userProfileDialog').dialog('close');
Once both the value and formatted value are updated, the chart updates as expected on draw.
So in the end, this was simply a mistake on my part, but I hope there might be some value to someone with this example code and this particular behaviour of the dataTable generated by the join method.

How can i add additional Data(Type) to chart.js

i had already done adding a click handler to each Segment of my doughnut chart with adding the following Code :
$("#myChart").click(
function(evt){
var activePoints = myNewChart.getSegmentsAtEvent(evt);
var chartelementid = activePoints[0].label;
alert(chartelementid);
//$('.details div').css("display", "none");
//$('#' + chartelementid).show();
}
);
This works fine, when finished it should display an additional Div with Details for this segment.
Unfortunality my labels are more then just Single Words, so i'm struggeling to create div ID's with the same name...
My Idea is to add to every Segment an additional Data like value,label, etc. so it could be an ID. but if i just add the ID information to the Segment it will not exist as variable.
Add DataType:
var dataImprove = [
{
value: 30,
color:"#001155",
highlight: "#1c2f7c",
label: "KnowHow Erhalt / Transfer & Aufbau",
id:"test"
}
]
where can i add in chart.js an additional dataType like shown above my ID to be accessible in the DOM?
kind regards Marco
As an alternative pass a JSON string as your label, then intercept to render. For example:
var canvas = document.getElementById(id);
var d = canvas.getContext("2d");
var chart = new Chart(d).Pie(json, {
segmentStrokeWidth: 1,
tooltipTemplate: "<%=label%>", //default the label
customTooltips: function (tooltip) {
// Hide if no tooltip
if (!tooltip) {
return;
}
var tooltipObj = JSON.parse(tooltip.text);
// etc
already found : between line 999 and 1023 in chart.js before drawing - i've added the line
id: ChartElements[0].id,
so the Data with the name ID is in the DOM avaiable.

Refresh a Grid using dojo

I want to refresh a dojo grid in my web page. I tried .refresh which is given in dojotoolkit.org without success. is there any other convenient way to do refreshing? Thanks in advance.
Maybe this helps. This is the way i refresh my Grid:
if(!registry.byId("GraphGrid")){
var grid = new EnhancedGrid({
id: 'GraphGrid',
store: GraphicStore,
query: { ident: "*" },
structure: layout,
rowSelector: '20px',
plugins: {
indirectSelection: {
headerSelector:true,
width:"40px",
styles:"text-align: center;"
}}
},"GridGraphicInMap");
/*Call startup() to render the grid*/
grid.startup();
dojo.connect(grid, "onRowClick", grid, function(evt){
var idx = evt.rowIndex,
item = this.getItem(idx);
// get a value out of the item
var value = this.store.getValue(item, "geom");
highlightGeometry(value,true);
// do something with the value.
});
}
else {
registry.byId("GraphGrid").setStore(GraphicStore);
}
When i first call my function the grid is generated. Evrytime i call the function later only the store is refreshed.
Regards, Miriam