Make flatpickr input required - datetime

I'm using the amazing flatpickr on a project and need the calendar date to be mandatory.
I'm trying to have all the validation in native HTML, so I was naively trying with just adding the required attribute to the input tag, but that doesn't appear to be working.
Is there a way of natively making a date mandatory with flatpickr or do I need to write some custom checks?

You can easily achieve this by:
Passing allowInput:true in flatpickr config.
As example:
flatpickrConfig = {
allowInput: true, // prevent "readonly" prop
};
From the documentation:
Allows the user to enter a date directly into the input field. By
default, direct entry is disabled.
The downside of this solution is that you should enable the direct entry (but ideally form validation should occur whether or not direct entry is enabled).
But if you don't want to enable the direct entry to solve this problem, you can use the code below as a workaround:
flatpickrConfig = {
allowInput:true,
onOpen: function(selectedDates, dateStr, instance) {
$(instance.altInput).prop('readonly', true);
},
onClose: function(selectedDates, dateStr, instance) {
$(instance.altInput).prop('readonly', false);
$(instance.altInput).blur();
}
};
This code remove the readonly property when it is not in focus so that html validation can occur and add back the readonly prop when it is in focus to prevent manual input. More details about it here.

This is what I came up with to make as complete of a solution as possible. It prevents form submission (when no date selected and input is required), ensures browser native "field required" message pops up and prevents the user typing in the value directly.
flatpickrConfig = {
allowInput: true, // prevent "readonly" prop
onReady: function(selectedDates, dateStr, instance) {
let el = instance.element;
function preventInput(event) {
event.preventDefault();
return false;
};
el.onkeypress = el.onkeydown = el.onkeyup = preventInput; // disable key events
el.onpaste = preventInput; // disable pasting using mouse context menu
el.style.caretColor = 'transparent'; // hide blinking cursor
el.style.cursor = 'pointer'; // override cursor hover type text
el.style.color = '#585858'; // prevent text color change on focus
el.style.backgroundColor = '#f7f7f7'; // prevent bg color change on focus
},
};
There is one disadvantage to this: Keyboard shortcuts are disabled when the flatpickr is open (when the input has focus). This includes F5, Ctrl + r, Ctrl + v, etc. but excludes Ctrl + w in Chromium 88 on Linux for some reason. I developed this using a rather old flatpickr version 3.1.5, but I think it should work on more recent ones too.
In case you want to use altFormat (display one date format to user, send other date format to server), which also implies setting altInput: true, you have to also change the onReady function to use instance.altInput instead of instance.element.
The onReady event listener can probably be attached to the instance after initializing it. However, my intention of using flatpickr with vue-flatpickr-component where you cannot elegantly access the individual flatpickr instances, made me use the config field instead.
I haven't tested it on mobile devices.

After digging a bit into the GitHub repo, I found a closed issue that points out that the issue will not be addressed.
In the same Issue page there is a workaround that seems to do the trick:
$('.flatpickr-input:visible').on('focus', function () {
$(this).blur()
})
$('.flatpickr-input:visible').prop('readonly', false)

copy attr name from prior input type hidden to rendered flatpickr input
just do this
$('[name=date_open]').next('input').attr("name","date_open");
$('[name=date_close]').next('input').attr("name","date_close");

Have been working on this for a couple of days now, finally getting the result I was after.
NOTE: I am using flatpickr with jQuery validation
As you would know flatpickr uses an alternative field for the date input, the actual field where the date is stored is hidden, and this is the key.
jQuery validation has a set of defaults, and by default hidden fields are not subject to validation, which normally makes perfect sense. So we just have to turn on the validation of hidden fields to make this work.
$.validator.setDefaults({
ignore: []
});
So my validator rules are then fairly normal:
var valid = {
rules: { dateyearlevel: {required: true} },
messages: { dateyearlevel: {required: "The date is required"} }
};
$("#myform").validate(valid);
That should allow you to ensure the date is required.
In my situation I wanted my date to only be required is a checkbox was checked. To do this we changed the rule above:
var valid = {
rules: { dateyearlevel: {
required: function() { return $("#mycheckbox").is(":checked") }
} },
messages: { dateyearlevel: {required: "The date is required"} }
};
$("#myform").validate(valid);

In case this helps someone, I'm using parsley.js for frontend validation and it works good with flatpickr
enter image description here

Just to expand a bit more on this, I found the ignore value set as an empty array did the trick for me also. You can just add this to your validate call back. Also displaying was a bit of an issue so I updated the errorPlacement to allow for flatpickr inputs like so.
$('#my-form').validate({
errorPlacement: function (error, element) {
if (element.hasClass('js-flatpickr') && element.next('.js-flatpickr').length) {
error.insertAfter(element.next('.js-flatpickr'));
} else if (element.parent('.input-group').length) {
error.insertAfter(element.parent());
} else {
error.insertAfter(element);
}
},
ignore: [],
rules: {
'startdate': { required: true }
},
messages: {
'startdate': {required: "Start Date is required"}
},
submitHandler: function(form) {
// ajax form post
}
});

in my case vue ( dunno why ) , i would like to comment for comment by #mik13ST
fyi: the default allowInput i think is true, no need to define, i didnt set the properties and my flat-pickr also work on testing.
i use
// this work in flat-pickr || #code_01
<small class="text-danger">
{{ validationContext.errors[0] }}
</small>
instead of
// work for all element except <flat-pickr #code_02 , dunno why not work
<b-form-invalid-feedback>
{{ validationContext.errors[0] }}
</b-form-invalid-feedback>
full code
<validation-provider
#default="validationContext"
name="Waktu Selesai Berkegiatan *"
vid="Waktu Selesai Berkegiatan *"
rules="required"
>
<flat-pickr
id="Waktu Selesai Berkegiatan *"
v-model="item.pip_time_end_rl"
placeholder="Waktu Selesai Berkegiatan *"
class="form-control"
static="true"
:config="dpconfig"
:state="getValidationState(validationContext)"
/>
// put here the message of error ( required ) #code_01 instead of #code_02
</validation-provider>
if younot use composite,
just use
#default="{ errors }" // in validation provider
:state="errors.length > 0 ? false : null" // in element for example flat-pickr
{{ errors[0] }} // to print out the message

Related

Can't get the `data-*-id` attribute to properly record the custom event name via ClickAnalytics plugin

I have a client React app I'm instrumenting in appinsights, and I'm using both the React plugin and the new ClickAnalytics plugin. Everything works, telemetry is flowing, however I'm having trouble getting the data-*-id custom event feature working properly.
The docs say:
The id provided in the data-*-id will be used as the customEvent name. For example, if the clicked HTML element has the attribute "data-sample-id"="button1", then "button1" will be the customEvent name.
I instrument an element as follows (using Semantic UI React):
<Button
data-custom-id="AddDealButton"
as={Link}
color="blue"
icon
labelPosition="right"
size="huge"
>
Clicking that button causes the custom event to record but the name, "AddDealButton", doesn't flow through. I always get not_specified as the event name:
Reading the docs, there is this warning regarding the plugin configuration:
If useDefaultContentNameOrId is false, then the customEvent name will be "not_specified".
So I am initializing the plugin this way:
...
extensions: [reactPlugin, clickPlugin],
extensionConfig: {
[reactPlugin.identifier]: { history: browserHistory },
[clickPlugin.identifier]: { autoCapture: true, useDefaultContentNameOrId: true }
}
...yet the name does not pass. Am I misconfiguring? Any idea what I'm doing wrong?
It turns out the problem was in the initialization configuration I showed above. It should be set up as follows:
...
extensions: [reactPlugin, clickPlugin],
extensionConfig: {
[reactPlugin.identifier]: { history: browserHistory },
[clickPlugin.identifier]: { autoCapture: true, dataTags: { useDefaultContentNameOrId: true } }
}
The resulting event name is not being pulled from my data-custom-id but rather pulled from the content of the Icon element of the Button component, so the event name becomes "Create new deal", but I can figure that out.
Microsoft's docs show a different samples for the npm install method vs the "snippet" method, and so I missed the dataTags sample.

grails controller/action/id automagically turning into controller/index

My problem is that the backend server (written in grails) is automatically converting my request URL to be a different URL. Specifically, it is changing it from /UXChallengeAwards/processSelectedNotifications to /UXChallengeAwards/index.
--
In a template gsp file, I have defined a button that makes a jQuery ajax call when clicked on:
<button class="blue-link"
onclick="jQuery.ajax({type:'POST',
data:jQuery(this).parents('.multiSelectForm').serialize(),
url: '/ici/UXChallengeAwards/processSelectedNotifications/${challenge.id}',
success:function(data,textStatus){},
error:function(xhr,textStatus,errorThrown){}
})" >
The method UXChallengeAwardsController.processSelectedNotifications exists. It performs some work and then redirects to another action in the controller. In fact, this used to work. But somehow in the process of adding a second button I made a change which seems to have broken things.
When the button is now clicked, the request URL gets switched to /ici/UXChallengeAwards/index and a 404 is returned because index does not exist as an action in this controller.
I've googled, and the most common answer for when this happens is that a controller must return some results for the view. But I've seen plenty of examples of redirects in controllers, and I do not see what I am doing wrong. (I did try variants of rendering results, but with no success.)
Here is what my controller action looks like:
def processSelectedNotifications = {
def challenge
def checkboxes = params.list('selectCheckbox');
for (checkbox in checkboxes) {
// the checkbox contains the id of a ChallangeAward that should be published
ChallengeAwards challengeAwards = ChallengeAwards.get(checkbox.toInteger())
if (challengeAwards) {
// grab a challenge for use in the redirect, they are all the same
challenge=challengeAwards.challenge
publish(challengeAwards)
}
}
if (challenge) {
redirect action: 'challengeAwardsRemote', id: challenge.id
return
}
// render a failure message if we got here
render messageNS(code:"UX.ChallengeAwards.Publish.failure")
}
I would really appreciate any insights into what might be wrong, or how to go about tackling this issue. I've checked my UrlMappings, and this is the rule that should handle this controller/method request:
"/$controller/$action?/$id?"{ constraints {} }
Thank you very much!
I'm going to go ahead and answer my own question, in case it is helpful for other newbies.
It turns out that I was not getting an automatic redirect. Rather, I had an error in the button setup code, so that grails was using its default link behavior. (Which is to go to the controller that matches the view, and if no action is specified, use the index method.)
The code above was originally created using a remoteSubmit tag, but I found that the generated code did not support handling multiple forms on a single page very well. So, I copied that generated code and then tweaked it to handle the multiple forms. However, I wanted the styling to match up with what was already in place on the page, so I switched it to be a button. That's when things went awry.
Eventually, I ended up specifying an onClick function for the button, and then writing the ajax submit code in javascript. Which turned out to be much simpler.
Here is what the button specification ended up looking like:
<button type="submit" id="notifications" class="blue-link" >
<i class="fa fa-envelope-o"></i>
<g:messageNS
code="UX.DiscussionBoard.ChallengeAward.Button.notify" />
</button>
And the associated JavaScript:
jQuery(document).ready(function() {
var clkBtn = "";
jQuery('button[type="submit"]').click(function(evt) {
clkBtn = evt.target.id;
});
jQuery('.multiSelectForm').submit(function() {
var url = '/ici/UXChallengeAwards/processSelectedNotifications';
if (clkBtn == 'deletes') {
url ='/ici/UXChallengeAwards/processSelectedDeletes';
}
var errorTarget = jQuery(this).parents().find('.recipientMessage').val();
var requestData = jQuery(this).parents('.multiSelectForm').serialize();
var options = {
data : requestData,
type : 'POST',
url : url,
target : '#awardsTab',
error : function(data) {
jQuery('#' + errorTarget).html(data.responseText).show();
},
success : function(data) {
console.log("in success");
}
};
jQuery(this).ajaxSubmit(options);
return false;
});

Bootboxjs: how to render a Meteor template as dialog body

I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);

Trying to catch hideDropdown event in TextExt.js

I am using TextExtJs for an autocomplete feature where you start typing and the dropdown of suggestions appears below the text input and you can select a suggested option with arrow keys or mouse.
Everything is working great except that I am trying to perform a function after the user selects one of the suggestions. There is a hideDropdown event which I think is the proper event to use for this. Unfortunately I'm not understanding how to do this, this is what I have tried:
$('#usearch').textext({
plugins : 'autocomplete ajax',
ajax : {
url : 'usersuggest.php',
dataType : 'json',
cacheResults : true
},
autocomplete : {
onHideDropdown : function(){
alert('A happened');
},
hideDropdown : function(){
alert('B happened');
}
},
onHideDropdown : function(){
alert('C happened');
},
hideDropdown : function(){
alert('D happened');
}
});
None of these functions with the alert actually ever run. They do not interfere with the suggestion piece of it. How do I attach a callback to this event?
I'm facing the same problem here....
Unfortunately there is no proper solution. The manual is as rudimental as the examples provided on the plugin page.
I managed to bind a kind of "onAddingTag" event, refer to this: http://textextjs.com/manual/plugins/tags.html#istagallowed
$('#textarea').textext().bind('isTagAllowed', function(e, data) {
var valueAdded = data.tag;
data.result = true; //needs to be done, since we're abusing this event
};
Despite the fact that this may help with this issue, your next problem would be: when does the user remove a tag?
Finally I ended up, using another autocomplete library.

How to add a row hyperlink for an extJS Grid?

Can someone please throw some light on how to go about rendering an hyperlink in the cells of a particular column in ExtJS?
I have tried binding the column to a render function in my JS, from which I send back the html:
SELECT
However, with this, the problem is that, once I hit the controller through the link, the navigation is successful, but subsequent navigations to the data-grid show up only empty records.
The records get fetched from the DB successfully through the Spring MVC controller, I have checked.
Please note that this happens only once I use the row hyperlink in the extJS grid to navigate away from the grid. If I come to the grid, and navigate elsewhere and again come back to the grid, the data is displayed fine.
The problem only occurs in case of navigating away from the grid, using the hyperlink rendered in one/any of the cells.
Thanks for your help!
This is for ExtJS 4 and 5.
Use a renderer to make the contents look like a link:
renderer: function (value) {
return ''+value+'';
}
Then use the undocumented, dynamically generated View event cellclick to process the click:
viewConfig: {
listeners: {
cellclick: function (view, cell, cellIndex, record, row, rowIndex, e) {
var linkClicked = (e.target.tagName == 'A');
var clickedDataIndex =
view.panel.headerCt.getHeaderAtIndex(cellIndex).dataIndex;
if (linkClicked && clickedDataIndex == '...') {
alert(record.get('id'));
}
}
}
}
Try something like this:
Ext.define('Names', {
extend: 'Ext.data.Model',
fields: [
{ type: 'string', name: 'Id' },
{ type: 'string', name: 'Link' },
{ type: 'string', name: 'Name' }
]
});
var grid = Ext.create('Ext.grid.Panel', {
store: store,
columns: [
{
text: 'Id',
dataIndex: 'Id'
},
{
text: 'Name',
dataIndex: 'Name',
renderer: function (val, meta, record) {
return '' + val + '';
}
}
...
...
...
However my thanks to - ExtJS Data Grid Column renderer to have multiple values
Instead of using an anchor tag, I would probably use plain cell content styled to look like an anchor (using basic CSS) and handle the cellclick event of the GridPanel to handle the action. This avoids dealing with the anchor's default click behavior reloading the page (which is what I'm assuming is happening).
I created a renderer so it looked like you were clicking on it.
aRenderer: function (val, metaData, record, rowIndex, colIndex, store){
// Using CellClick to invoke
return "<a>View</a>";
},
But I used a cell event to manage the click.
cellclick: {
fn: function (o, idx, column, e) {
if (column == 1) // Doesn't prevent the user from moving the column
{
var store = o.getStore();
var record = store.getAt(idx);
// Do work
}
}
}
For these purposes I use CellActions or RowActions plugin depending on what I actually need and handle cell click through it.
If you want something that looks like an anchor, use <span> instead and do what #bmoeskau suggested.
You can use 'renderer' function to include any HTML you want into cell.
Thanks guys for your response.
AFter debugging the extJS-all.js script, I found the issue to be on the server side.
In my Spring MVC controller, I was setting the model to the session, which in the use-case I mentioned earlier, used to reset the "totalProperty" of Ext.data.XmlStore to 0, and hence subsequent hits to the grid, used to display empty records.
This is because, ext-JS grid, checks the "totalProperty" value, before it even iterates through the records from the dataStore. In my case, the dataStore had data, but the size was reset to null, and hence the issue showed up.
Thanks to all again for your inputs!

Resources