As discussed in the comments, I personally wouldn't worry too much about importing templates, the $compile
function, or anything that complex for what is relatively simple markup.
I've created a (very) basic barebones plunker here, to demonstrate what my approach would be. I've used moment.js to deal with dates, because I am terrible with vanilla javascript date manipulation and find it very clumsy.
This is the template:
<div class="controls">
<button ng-click="prevMonth()"><-</button>
<span>{{selected.format('MMMM')}}</span>
<button ng-click="nextMonth()">-></button>
</div>
<div class="month">
<span class="day" ng-repeat="day in selected.days">
{{day.number}}
</span>
</div>
The template relies on the scope having a selected
property which is a moment object. All it does is creates some buttons change the month and displays the month name at the top, then creates a span for each day in that month.
A tiny bit of CSS puts the days in rows of 7:
span.day{
float: left;
width: 25px;
}
span.day:nth-child(7n+1){
clear:left;
}
This is the directive's link
function:
link: function(scope, element, attributes){
// Set the selection to now initially.
scope.selected = moment();
generateDaysArray = function(){
// -- REMOVED FOR BREVITY -- //
return days;
}
// Watch the month for changes and update the days array
scope.$watch(
function(){
return scope.selected.month();
},
function(newVal, oldVal){
scope.selected.days = generateDaysArray();
}
)
// Control button actions
scope.nextMonth = function(){
scope.selected.add('month', 1)
}
scope.prevMonth = function(){
scope.selected.subtract('month', 1)
}
}
There is really nothing special there, but do note that the $watch
command used takes advantage of the fact that you can pass a function instead of a string (though looking at it now you could just use 'selected.month()'
as a string).
Generating the days array is slightly more complex if you want to align the dates with days of the week, which I started to do, but didn't finish. My plan was for each object in the array to have a isPreviousMonth
property which would conditionally apply a different style using ng-class
, and would look up the correct number as well, rather than just using a ~
for the offset days!
There is a lot more to do to finish this off - not least making the dates selectable and integrating with ngModel
, but hopefully this example has given you an idea of one way to approach the problem of templating. There are many other ways you could do this, but I find this the most intuitive.
Extension
To address your question regarding having multiple months, I've expanded the plunker a bit.
I added an attribute to the directive:
<date-picker num-months="3"></date-picker>
And modified the template to ng-repeat
certain parts depending on the number of months you specify in the attribute:
<div class="controls">
<button ng-click="prevMonth()"><-</button>
<span ng-repeat="month in months" class="month-title">
{{month.moment.format('MMMM')}}
</span>
<button ng-click="nextMonth()">-></button>
</div>
<div class="month" ng-repeat="month in months">
<span class="day" ng-repeat="day in month.days">
{{day.number}}
</span>
</div>
So this instead requires an array of months, each with 2 properties: a moment, and an array of days. I added some quick and dirty javascript to generate that, but I won't paste it here as there are almost certainly much neater ways of doing it!
Note that at present we're still watching scope.selected
for changes in the month, which would lead to the (probably unwanted) behaviour that if you selected a date not in the first month, the calendar would rearrange itself. I think instead of using a $watch
in this case, I'd just add to the click events to handle redrawing the calendar properly.
PS This addresses the cloning part but not the scrolling part. For scrolling, I would recommend adding one extra month on either end of the months array, then having an ng-show
condition that means only the middle month(s) is shown. You can then take advantage of angular's inbuilt animations (this is a good reference) to scroll them in/out using css. If you want more info about that contact me through the chat I opened for this question.
link
function. If you don't use that approach, and you don't want sub-directives, you'll have to start looking at using$compile
, which is more complicated (imo). – Ed Hinchliffe Aug 9 '14 at 16:30renderMonth()
is called. Just change models in a scope, and that will reflect on the UI automatically. – runTarm Aug 9 '14 at 16:40