Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Custom Date Ranges in the Template #72

Closed
kylestetz opened this issue Jan 30, 2014 · 26 comments
Closed

Feature: Custom Date Ranges in the Template #72

kylestetz opened this issue Jan 30, 2014 · 26 comments
Labels

Comments

@kylestetz
Copy link
Owner

@palodelincak brought up the point in issue #71 that creating a view in increments other than one-month-at-a-time is difficult, bordering on unreasonable.

I am going to move forward implementing an option that allows you to choose an arbitrary length of days to have available in the template. Here's what it could look like:

$('#calendar').clndr({
  template: $('#template').html(),
  length: {
    days: 7
  }
});

Specifying length.days here tells clndr "I want you to give me 7 days at a time in my template". Thus the clndr-next-button will increment the data by 7 days instead of a month. I'm thinking there will be a different set of callbacks specific to this situation, perhaps onNextIncrement and onPreviousIncrement.

Specifying a concrete number of days doesn't work well if you want to show several months at a time, so:

$('#calendar').clndr({
  template: $('#template').html(),
  length: {
    months: 3
  }
});

This would behave almost exactly like clndr does now, except that you would get three months in your template instead of one. Perhaps there are some template functions that would make sorting the days out into months a little easier.

Feedback is welcome as always- I'm pretty sure @sylouuu will have something to say about this!

@zgreen
Copy link

zgreen commented Jan 30, 2014

This would be a terrific feature.

@piuccio
Copy link

piuccio commented Feb 3, 2014

Then maybe it should also be possible to configure the length of increments.

I have a use case in which I display two calendars (so length: 2 months) but the next button increments only of 1 month.

So you start with (Jan, Feb) click next and you have (Feb, Mar), (Mar, Apr) and so on.

@kylestetz
Copy link
Owner Author

@puccio absolutely yes! Here's an updated API draft.

$().clndr({
  template: $('#template').html(),
  lengthOfTime: {
    days: 14,
    incrementBy: 7
  }
});

In reviewing the codebase, this all is possible but will require a pretty insane amount of refactoring... The idea of a month is hardcoded in a lot of ways. I think it's absolutely worth it though.

@palodelincak
Copy link
Contributor

Hey there! Any progress on this feature (I can't wait for it :-))?

@kylestetz
Copy link
Owner Author

@palodelincak ha! Glad you're excited. I've been planning it in my spare time and thought I might be able to start developing on Sunday, however it is going to take some time since it is a big refactor. (Are you interested in contributing?)

Also, gotta admit, I'm putting most of my energy into a new project I'll be releasing soon, so that is definitely competing for my attention!

@palodelincak
Copy link
Contributor

@kylestetz I was trying to hack it myself, but I gave up - it's too big for me. Glad to hear you're going to work on it! I'm sorry, but can you be more specific with release date, please (I know it's probably not possible)? It'll be done in one week, one month, one year? :-) I need this info very badly.

@kylestetz
Copy link
Owner Author

I can guarantee you it will be done by March 1st. Wish I could promise you something sooner but that's pretty realistic.

@palodelincak
Copy link
Contributor

March 1st will be awesome. Thanks!

@MerlinMason
Copy link

Hi there!

Firstly I want to say thanks for all your work on this, it's a great idea for a plugin and I love being able to use templates.

I was wondering if as part of the custom date ranges feature you're discussing here, if it'd be possible to create a year view. Something like...

$().clndr({
    template: $('#template').html(),
    lengthOfTime: {
        months: 12,
        incrementBy: 12
    },
    startWithMonth: "2014-01-01"
});

And then in the template you could have a nested loop to go through months and days.

So that would give an entire year view of 2014. What do you think?

@kylestetz
Copy link
Owner Author

Yep, that example would work perfectly. You could also use days: 365 (or 366, you can use moment to get the correct number of days this year).

I'm setting it up so that if you specify lengthOfTime.months it will give you a months array in the template that looks like this:

months = [
  {
    month: moment(), // the month as a moment object
    days: Array // same as the days array you are used to in clndr
  }
]

Which will be really easy to loop over.

@MerlinMason
Copy link

Brilliant, thanks so much! Really looking forward to this 👍

@kylestetz
Copy link
Owner Author

Hey guys

Good news: I think we're all set with this new feature. It was sort of tricky and I'm not aesthetically pleased with the resulting code, but it works!

I just pushed a branch I've been working from... Before I merge it, would any of you guys mind giving it a test run? I'm not 100% sure I've covered every edge case so it'd be awesome if you guys could mess around with it.

https://github.com/kylestetz/CLNDR/tree/intervals

I haven't written docs for it yet, however if you look at /example/test.js and /example/test.html the last two examples are a three-month and a two-week calendar. The final API I settled on is:

lengthOfTime: {
  days: #, // length of calendar in days
  months: #, // length of calendar in months
  interval: #, // amount to move the calendar by for each next/previous click
  startDate: String or moment() // the date to use as a starting point...
  // by default it's the current month using the `month` option,
  // and it's the first weekday before today using the `days` option (e.g. Sunday)
}

Using the months option the data in the template is in the format:

months: [
  {
    month: moment(), // a moment object set to the start of this particular month
    days: Array // the regular clndr days array
  }
]

You'll have to write a custom template to take advantage of this stuff. Let me know if you guys have any questions!

P.S. Thanks for your support & enthusiasm- you guys make this a fun project to work on.

@MerlinMason
Copy link

Hey Kyle, thanks for this!

I'm stuck working on another project at the moment but will have a play with this tonight and see if I can get it running. Looking forward to it, thanks again!

@zgreen
Copy link

zgreen commented Feb 17, 2014

I'll play around with the new branch this week.

@MerlinMason
Copy link

Hey Kyle,

Ok so I've had a play around with the branch, seems to be working which is great, thanks!

There's a couple of things I have questions on which may be either something I'm just missing, or useful improvements.

To give some context, I'm building a year planner as part of an app, that displays the events for the currently selected year.

So first question, is there, or do you think there's a reasonable use case for an option that will output a full 31 days for each month to allow for a grid-like layout (with an appropriate day.classes hook for styling purposes). I currently have the below screengrab:

screenshot 2014-02-17 20 10 41

Secondly, and apologies as I realise this is unrelated, but is there a destroy method? I'm currently displaying only one year, and when the year is changed from elsewhere in the app I need to hit the API and re-render the clndr.

Thanks for your help! Let me know if you have a paypal, I'd like to buy you a beer / coffee!

@kylestetz
Copy link
Owner Author

answer to first question: are you just trying to fill in those extra boxes at the end? You could check the length of each month in your template, find the remainder (31 - month[].days.length) and do a lil while loop to generate some empty boxes... This is something best solved in the template.

answer to second question: I found a bug! Which I fixed, please grab the updated version. There is an API for programmatically controlling the calendar. When making your clndr instance, save it to a variable:

var myCalendar = $('#calendar').clndr();

and you can call:

myCalendar.nextYear()
myCalendar.previousYear()
myCalendar.setYear(2015)
// etc...

The README has some more docs on these.

You are welcome! Will you perchance be attending JSFest in SF next month? I will be there, drinking the beers.

@MerlinMason
Copy link

Thanks for getting back to me.

First question, yep, just fill the extra boxes... Sounds sane to just write something in the template for that. Had to tweak it slightly as the days.length array also contains stuff from the neighbouring months:

var daysToBeAdded = 31;
_.each(oneMonth.days, function(day) {
    if (day.date) { 
        daysToBeAdded-- 
    }
});

Second question, ahh brilliant, I'll just pass in a new events array and switch the year then which will solve the problem.

Unfortunately I'm based in London so probably won't be making it to SanFran, sounds good though!

@palodelincak
Copy link
Contributor

Wow! Thanks for this!

I was able to create one day, two weeks and one month view based on my requirements - so your improvement is simply the best!

I would love to have .destroy() (as @MerlinBB) method. And why? I'm going to allow users to change calendar views between one day => two weeks... So I must destroy one instance and initialise other instance. Right know I'm just killing plugin data and removing template children, but some callbacks are still working...

Or instead of destroy() method would be nice to have ability to re-render it with new options.

@kylestetz
Copy link
Owner Author

@palodelincak you could probably maintain two separate calendar instances and just hide the one you aren't using... They can share callbacks and events if necessary.

HOWEVER

I tried to write this thing with the ability to change lengthOfTime options on the fly, so I think you should test it out. All of my tests are passing. Here's what you do, assuming you have a variable called clndr that refers to your clndr instance, and you are currently set to view one day at a time.

You can change the lengthOfTime property like this:

clndr.options.lengthOfTime.days = 14;

Now it's set up to view two weeks, but we have to trigger a render. We can't just call clndr.render(), since we need to tell clndr to redefine the start and end points of the interval. We can force it to use the Sunday (or Monday, depending on your locale) before this day to sync it up with a typical week. We use setIntervalStart, which sets the starting point for the two weeks and calls render internally.

clndr.setIntervalStart(clndr.intervalStart.weekday(0));

(as of this update clndr maintains two variables called clndr.intervalStart and clndr.intervalEnd that define the interval, even in the traditional month view)

To switch back to a one day view:

clndr.options.lengthOfTime.days = 1;
clndr.setIntervalStart(/* whichever day you want to switch to, as a moment object */);

So the last piece of this is that you probably have two different templates for the day and the weeks view. That's where we can use clndr.options.extras, which is a container for anything we want to have access to within a template. When you first instantiate your clndr, create a boolean variable in extras:

$('#calendar').clndr({
  template: $(),
  lengthOfTime: {
    days: 14,
    interval: 7
  },
  extras: {
    dayView: false
  }
});

Now in your template you can access extras.dayView. When you switch the length of time, switch this variable along with it...

clndr.options.lengthOfTime.days = 1;
clndr.options.extras.dayView = true;
clndr.setIntervalStart(/* whichever day you want to switch to, as a moment object */);

...and now your template can be set up like this:

<% if(extras.dayView){ %>
  <markup for the single day view />
<% } else { %>
  <markup for the week view />
<% } %>

Let me know how that works for you!

@palodelincak
Copy link
Contributor

Works like a charm. Many thanks! This plugin is limitless 👍

I think this one is a bug - variable newMonth is not defined there:
https://github.com/kylestetz/CLNDR/blob/intervals/src/clndr.js#L976

Is there some way of getting whole date range in month including days in adjacent months? For example - first row in February begins with 27th January and last row ends with 9th March. I would like to load events for adjacent days - from 27th Jan to 9th March and not just for 1st Feb - 28th Feb. I was looking at source code but filling blank (adjacent) boxes is hardcoded to days array and this array is not available in clndr callbacks.

PS: I'm not kidding, but this is the best 3rd party plugin I have ever worked with!

@kylestetz
Copy link
Owner Author

Thanks for catching that bug, just patched it up.

Adjacent days are controlled by the showAdjacentMonths option (it's true by default) when you are in the classic month view or the lengthOfTime.months view. As long as that option is true, your events will come through in those adjacent days. You can also use the option forceSixRows to force a six row grid (to give your calendar a consistent height), which might show you an additional week sometimes.

Is this not what you are seeing? All the tests for adjacent months are passing at the moment. Perhaps tell me a little bit more about the setup and what you're seeing that seems broken.

P.S. thanks :)

@nightgrey
Copy link

Hey Kyle, first: I love CLNDR. All sorts of jQuery plugins should be done this way. Awesome piece of code you did there! <3

I'm note 100% sure - but I guess I encountered a bug in the intervals branch.

Plugin call (nothing special going on here)

    dentalClndr = jQuery('.clndr').clndr({
        // the template: this could be stored in markup as a <script type="text/template"></script>
        // or pulled in as a string
        template: jQuery('#clndrTemplate').html(),

        lengthOfTime: {
            months: 6,
            interval: 6,
            startDate: moment().subtract('months', 1).startOf('month')
        },
        weekOffset: 0,
        startWithMonth: moment(),
        events: [
                { date: '2014-04-24', title: 'Event 1', category: 'Workshop', className: 'workshop'},
                { date: '2014-04-10', title: ' Event 2', category: 'Team Workshop', className: 'teamWorkshop'},
                { date: '2014-04-12', title: 'Event 3', category: 'Prakt. Arbeitskurs', className: 'praktArbeitskurs' },
        ],
        showAdjacentMonths: false,
        adjacentDaysChangeMonth: false
    });

Template

        <script id="clndrTemplate" type="text/template">
            <div class="multi-month-controls">
                <!-- <div class="clndr-previous-year-button">&laquo;</div> -->
                <div class="clndr-previous-button">&lsaquo;</div>
                <div class="clndr-next-button">&rsaquo;</div>
                <!-- <div class="clndr-next-year-button">&raquo;</div> -->
            </div>
            <% _.each(months, function(oneMonth){ %>
                <div class="clndr-month">
                    <div class="month"><%= oneMonth.month.format('MMMM') %></div>
                    <div class="days">
                        <% _.each(oneMonth.days, function(day) { %>
                            <div class="<%= day.classes %> <% _.each(day.events, function(event) { %><%= event.className %><% }); %>">
                                <%= moment(day.date).format('D dd') %>
                                <% _.each(day.events, function(event) { %>
                                    <%= event.title %> (<%= event.category %>)
                                <% }); %>
                            </div>
                        <% }); %>
                    </div>
                </div>
            <% }); %>
        </script>

The important piece of code in the template is <%= moment(day.date).format('D dd') %> - I use this to format the date output. It works perfectly, but it output's Invalid date (see screenshot below).

(Also something I encountered: In another issue you suggested to use <%= day.date.format('D dd') %> but that does not work, although I see day.date is a moment object in firebug when I log it via <%= console.log(day.date) %> - the error message in the JS console is Uncaught TypeError: Cannot call method 'format' of null)

And that's a screenshot of the output:
clndr

You see that many "Invalid date"s? It's looking like the plugin tries to output the adjacent months.. it isn't supposed to that, isn't it? ;)

Edit: Until it's fixed I'm manually fixing it with <% if(day.date != null) { %><%= moment(day.date).format('D dd') %><% } %> - if you log day.date you'll see some null objects.

Regards
Nico

@jonagoldman
Copy link

Hi, any news on this? I see the 'intervals' branch is kind of old. Is this already implemented in the master branch?
Thanks.

@kylestetz
Copy link
Owner Author

Hey- no news unfortunately. I've been busy and have prioritized other projects over this update, partly because I'd rather refactor the entire codebase to 2.0 than add features to this version. Due to the relatively few people who were looking for this feature, I didn't see it as a huge priority. I ran into some issues given that clndr was originally written with months in mind and this was intended to replace months with intervals.

(My ideal situation with clndr would be to find some equally-motivated collaborators who would be willing to hash out and write 2.0 from scratch in a modular, testable fashion that can live and grow for a long time!)

I'll have some time next month when I can definitely take some action on this branch. Thanks for checking in!

@ikhan009
Copy link

Hi kylestetz
there is little confusion on mini calander.

  1. how i set event for future date
  2. there i want to show popup and user can select campaign and schedule on that date
  3. when click on any event it show monthwise event, i need day wise event to show, down is
    screanshot.
    http://screencast.com/t/GCEZLzksQzu

hope you will guide me.

@kylestetz
Copy link
Owner Author

Just realized I can close this! This feature was added in v1.2.11 thanks to the hard work of @mikegioia.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants