3

I am trying to write a custom datepicker, where the default drop downs for months and year ranges (enabled through changeMonth and changeYear options) are replaced by custom drop downs. It is something like this:

Fiddle: http://jsfiddle.net/gGV3v/

$("#myId").datepicker({
   ...default settings...
   beforeShow: function() {
      ...here I replace the default dropdowns by custom dropdowns...
      ...something like...
      $(".ui-datepicker-month").replaceWith("#custom-html-block");
      $(".ui-datepicker-year").replaceWith("#another-custom-html-block");
    }
 });

On choosing either month or year from the custom dropdowns, I want to change the date in the view accordingly. So I construct the date string from the current month and year (the date for the new month/year combo defaults to 1 here), and I call

$("#custom-html-block .custom-dropdown-option").on("click",function() {
     ...construct newDateString...
     $("#myId").datepicker("setDate",newDateString)
});

$("#another-custom-html-block .custom-dropdown-option").on("click",function() {
     ...construct newDateString...
     $("#myId").datepicker("setDate",newDateString)
});

I want the span with text foo to remain intact when setting the new date programmatically on clicking it.


The problem is: it wipes off the custom drop downs and the default ones come again. I tried to do things like this:

$("#myId").datepicker($.extend({setDate: newDateString},oldSettings))

But it still doesn't work. How do I make this work?

myfunkyside
  • 3,810
  • 1
  • 16
  • 31
SexyBeast
  • 7,303
  • 27
  • 100
  • 185
  • 3
    Would you mind providing a [fiddle](http://jsfiddle.net)? – Thorsten Jun 18 '14 at 17:23
  • Umm, it is a huge code, with many tons of css and html embedded. If I filter them out, it will reduce two what I have provided here.. :( – SexyBeast Jun 18 '14 at 17:24
  • Does selecting a date do cause the dropdowns to be reset as well? – Robbert Jun 18 '14 at 17:24
  • No, since he drop down is popped up on focusing on a text input, the moment I click a date, it fades away. When i focus on the text input again, a new instance springs up with the custom drop downs. So no, on selecting a date, until it fades away, I don't seem them being replaced. However, if you try to navigate to the previous or next months through the buttons on top left and top right, the new month comes and the drop downs are changed to default. – SexyBeast Jun 18 '14 at 17:26
  • Why are you using custom dropdowns to begin with? Are you just trying to have more control over the look of the dropdowns? – apaul Jun 18 '14 at 17:41
  • If you can't provide a Fiddle then could you perhaps simply link to a live sample? – Etheryte Jun 18 '14 at 17:52
  • @apaul34208, yes, I want more control over the look and feel of the entire widget.. – SexyBeast Jun 18 '14 at 18:32
  • Couldn't you just also place those `.replaceWith`-lines in the onselect/onclick-functions? – myfunkyside Jun 18 '14 at 18:35
  • Interesting idea. I supposed I can, but even a 0 second delay is occasionally showing the replacement for a split second.. – SexyBeast Jun 18 '14 at 18:40
  • Couldn't you just re-style the existing dropdowns with css? – apaul Jun 18 '14 at 18:49
  • @apaul34208, it is not possible to style HTML controls like radio and checkbox buttons, select option etc through CSS. – SexyBeast Jun 18 '14 at 18:51
  • http://stackoverflow.com/questions/1895476/how-to-style-a-select-dropdown-with-css-only-without-javascript – apaul Jun 18 '14 at 19:12
  • Umm, the given fiddle doesn't seem to work. Plus it is way too complicated, not to mention I will be fiddling with the controls jQuery UI is providing. So the results may not be as expected. Whereas here I m replacing the entire content with a custom one, so I am in total control.. – SexyBeast Jun 18 '14 at 19:14

2 Answers2

1

You can set the value of the function $.datepicker._generateMonthYearHeader, the downside it that will be global for all datepicker in the page.

The sample is below:

$.datepicker._generateMonthYearHeader = function(inst, drawMonth, drawYear, minDate, maxDate,
        secondary, monthNames, monthNamesShort) {


    var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
        changeMonth = this._get(inst, "changeMonth"),
        changeYear = this._get(inst, "changeYear"),
        showMonthAfterYear = this._get(inst, "showMonthAfterYear"),
        html = "<div class='ui-datepicker-title'>",
        monthHtml = "";

    // year selection
    if ( !inst.yearshtml ) {
        inst.yearshtml = "";
        if (secondary || !changeYear) {
            html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
        } else {
            // determine range of years to display
            years = this._get(inst, "yearRange").split(":");
            thisYear = new Date().getFullYear();
            determineYear = function(value) {
                var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) :
                    (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) :
                    parseInt(value, 10)));
                return (isNaN(year) ? thisYear : year);
            };
            year = determineYear(years[0]);
            endYear = Math.max(year, determineYear(years[1] || ""));
            year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
            endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
            inst.yearshtml += "<span class = 'dummy'>Foo</span> <select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>"
            for (; year <= endYear; year++) {
                inst.yearshtml += "<option value='" + year + "'" +
                    (year === drawYear ? " selected='selected'" : "") +
                    ">" + year + "</option>";
            }
            inst.yearshtml += "</select>";


            html += inst.yearshtml;
            inst.yearshtml = null;
        }
    }

And your fiddle: http://jsfiddle.net/gGV3v/2/

UPDATE:

Without the buttons, and using a better approach, now you have control of all generated HTML:

var oldGenerateHTML = $.datepicker._generateHTML;

function newGenerateHTML(inst) {
    var html = oldGenerateHTML.call(this, inst);
    var $html = $(html);
    $html.find('[data-handler=prev]').remove();
    $html.find('[data-handler=next]').remove();
    $html.find('[data-handler=selectMonth]').replaceWith('<span class="dummy">Foo</span>');
    return $html;
}

$.datepicker._generateHTML = newGenerateHTML;

Fiddle also updated: http://jsfiddle.net/gGV3v/4/

sergiogarciadev
  • 1,971
  • 1
  • 20
  • 34
  • Works like a charm as far as I can see. Just an edit, your code doesn't change the date because you forgot to include the text **foo** in the ``. Here is the updated fiddle: http://jsfiddle.net/gGV3v/2/ – SexyBeast Jun 18 '14 at 18:50
  • Sure, I missed that part. I updated the answer, thanks. – sergiogarciadev Jun 18 '14 at 19:02
  • But I went through the answer once more, it also takes care of the part where the user navigates through months through the buttons on top left and top right. Awesome! – SexyBeast Jun 18 '14 at 19:04
  • As an aside, can you add some more comments to the `if-else` conditionals? – SexyBeast Jun 18 '14 at 19:20
  • That was get from the [datepicker.js](https://github.com/jquery/jquery-ui/blob/master/ui/datepicker.js) source. I update the answer with a simplified version. – sergiogarciadev Jun 18 '14 at 19:29
  • I am still not very clear on the variety of variables in the `_generateMonthYearHeader` method. Particularly what does `inst.yearshtml` mean and what does the `if ( !inst.yearshtml ) {...}` conditional mean. Can you add some further comments? – SexyBeast Jun 19 '14 at 13:17
1

In general this is why extend sucks. The parent (in your case datepicker) doesn't know anything about childs (your custom html). Everytime it refresh the view, your changes are lost. This problem is usually solved using composition. The goal is to create your own widget (I think that was the term in jQuery) that have datepicker as a local variable and to control _generateMonthYearHeader function calls. It's easy to say in theory but in practice (specially case with jQuery) is hard to achive. Easier solution will be to proxy the function.

//preserve _generateHTML because after it finish, html is visible and .ui-datepicker-month and .ui-datepicker-year are in dom tree
fn = $.datepicker._generateHTML; //preserve original function
$.datepicker._generateHTML = function(inst) {
    //call original function
    fn.call(this, inst);
    //do custom changes
    //you'll need better selectors in case of multiple datepicker instances
    $(".ui-datepicker-month").replaceWith("#custom-html-block");
    $(".ui-datepicker-year").replaceWith("#another-custom-html-block");   
}

Personally I dislike this approach (I prefer composition as I said) but in your case it'll be easier to implement. The reason its easier is because generateMonthYearHeader is called from many different functions.

P.S. Not tested

Ivan
  • 2,242
  • 1
  • 18
  • 16