From d8f9de9363208670bfce129e6c3fc9c3448af7b3 Mon Sep 17 00:00:00 2001 From: Ana Date: Fri, 10 Jun 2016 16:33:57 +0200 Subject: [PATCH 1/4] new schedule --- .../stylesheets/osem-schedule-print.css | 6 - app/assets/stylesheets/osem-schedule.css.scss | 47 ++++++ app/controllers/conference_controller.rb | 8 + app/models/conference.rb | 10 ++ app/models/event.rb | 14 ++ app/models/event_type.rb | 1 + app/views/conference/_schedule.html.erb | 146 +++++++++++------- app/views/conference/_schedule_item.html.haml | 29 ++-- app/views/conference/schedule.html.erb | 2 +- 9 files changed, 182 insertions(+), 81 deletions(-) diff --git a/app/assets/stylesheets/osem-schedule-print.css b/app/assets/stylesheets/osem-schedule-print.css index 54fc3593..3cbbb129 100644 --- a/app/assets/stylesheets/osem-schedule-print.css +++ b/app/assets/stylesheets/osem-schedule-print.css @@ -46,14 +46,8 @@ .event img, .img-circle { display: none; } -h5 { - font-size: 10px; - line-height: 12px; - margin: 0px; -} .table th, .table td { line-height: 12px; - padding: 3px; } .schedule-speaker { diff --git a/app/assets/stylesheets/osem-schedule.css.scss b/app/assets/stylesheets/osem-schedule.css.scss index 80511f33..f7dd5afa 100644 --- a/app/assets/stylesheets/osem-schedule.css.scss +++ b/app/assets/stylesheets/osem-schedule.css.scss @@ -75,3 +75,50 @@ a.unstyled-link { background-image: linear-gradient(to top, #f7faf2 24%, #53ab4a 97%, #53ab4a 100%); } + +.schedule-dropdown{ + margin-left: 20%; + margin-right: 20%; + margin-top: 20px; + margin-bottom: 20px; +} + +.schedule-dropdown > button{ + width: 100%; +} + +.schedule-dropdown > .dropdown-menu{ + width: 100%; +} + +.event-elipsis{ + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; /* number of lines to show */ +} + +.room-elipsis{ + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 4; /* number of lines to show */ +} + +.break-words{ + /* These are technically the same, but use both */ + overflow-wrap: break-word; + word-wrap: break-word; + + /* Warning: Needed for oldIE support, but words are broken up letter-by-letter */ + -ms-word-break: break-all; + word-break: break-all; + + /* Non standard for webkit */ + word-break: break-word; + + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} diff --git a/app/controllers/conference_controller.rb b/app/controllers/conference_controller.rb index 25d55175..894e912e 100644 --- a/app/controllers/conference_controller.rb +++ b/app/controllers/conference_controller.rb @@ -17,6 +17,14 @@ class ConferenceController < ApplicationController @events_xml = @events.scheduled.order(start_time: :asc).group_by{ |event| event.start_time.to_date } @dates = @conference.start_date..@conference.end_date + @number_columns = 1 + @conf_start = 9 + conf_end = 20 + @intervals = @number_columns * 60 / EventType::LENGTH_STEP + 1 + @width = 85 / @intervals + @step_minutes = EventType::LENGTH_STEP.minutes + @carousel_number = ((conf_end - @conf_start) / @number_columns.to_f).ceil + if @dates == Date.current @today = Date.current.strftime('%Y-%m-%d') else diff --git a/app/models/conference.rb b/app/models/conference.rb index f0b73047..c90a8ea2 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -81,6 +81,16 @@ class Conference < ActiveRecord::Base result end + ## + # Check if there is any event in the program scheduled. + # + # ====Returns + # * +false+ -> If there aren't schedules events + # * +true+ -> If there are schedules events + def scheduled? + self.program.events.where.not(start_time: nil).any? + end + ## # Checks if the user is registered to the conference # diff --git a/app/models/event.rb b/app/models/event.rb index 1e118095..b9c01089 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -223,6 +223,20 @@ class Event < ActiveRecord::Base (100 * result.values.count(true) / result.values.compact.count).to_s end + ## + # Returns the number of columns this event should span in the schedule + # + def span + self.event_type.length / EventType::LENGTH_STEP + end + + ## + # Returns end of the event + # + def end_time + self.start_time + self.event_type.length.minutes + end + private ## diff --git a/app/models/event_type.rb b/app/models/event_type.rb index 9a5c4c84..548d2006 100644 --- a/app/models/event_type.rb +++ b/app/models/event_type.rb @@ -10,6 +10,7 @@ class EventType < ActiveRecord::Base alias_attribute :name, :title + # If LENGTH_STEP > 60 the public schedule must be changed LENGTH_STEP = 15 private diff --git a/app/views/conference/_schedule.html.erb b/app/views/conference/_schedule.html.erb index 1d036378..b614f31f 100644 --- a/app/views/conference/_schedule.html.erb +++ b/app/views/conference/_schedule.html.erb @@ -1,72 +1,93 @@
-

Schedule for <%= @conference.title %>

-
+ <% end %> + + + + + + + + + + + + + <% end %> + <%= javascript_tag do %> $(function() { // add a hash to the URL when the user clicks on a tab $('a[data-toggle="tab"]').on('click', function(e) { + $('.li-dropdown-schedule').removeClass('active'); + $('.schedule-dropdown').find('button').text($(this).text()); history.pushState(null, null, $(this).attr('href')); }); // navigate to a tab when the history changes @@ -85,4 +106,21 @@ $(function() { hash && $('ul.nav a[href="' + hash + '"]').tab('show'); }); +$('.carousel').on('slid.bs.carousel', '', +function(){ + $(this).children('.left.carousel-control').show(); + $(this).children('.right.carousel-control').show(); + if($(this).find('.first').hasClass('active')) { + $(this).children('.left.carousel-control').hide(); + } else if($(this).find('.last').hasClass('active')) { + $(this).children('.right.carousel-control').hide(); + } +}); + + +$(document).ready(function(){ + $('.carousel').children('.left.carousel-control').hide(); +}); + + <% end %> diff --git a/app/views/conference/_schedule_item.html.haml b/app/views/conference/_schedule_item.html.haml index 743095ca..e52c9df4 100644 --- a/app/views/conference/_schedule_item.html.haml +++ b/app/views/conference/_schedule_item.html.haml @@ -1,24 +1,13 @@ -%td.event{ style: "width: #{95 / @rooms.length}%; cursor:pointer", | - rowspan: span[room.id], | +%td.event{ width: "#{ width * span }%" , | + height: "75px" , | + colspan: span, | + style: "cursor:pointer; padding:0px;" , | role: "button" } | - %a.unstyled-link{href: url_for(conference_program_proposal_path(@conference.short_title, event[0].id))} - - if speaker = event[0].speakers.first + %a.unstyled-link{href: url_for(conference_program_proposal_path(@conference.short_title, event.id))} + %div{ class:"event-elipsis break-words", style: "font-size: 11px; height:45px; line-height: 15px; overflow: hidden;"} + = event.title + - if speaker = event.speakers.first = image_tag speaker.gravatar_url, :class => "img-circle pull-right", | :alt => speaker.name, | :title => speaker.name, | - :style => "padding:8px;" | - %h5 - %span.fa.fa-comment - = event[0].title - - unless event[0].subtitle.blank? - %span.schedule-subtitle - = event[0].subtitle - %span.schedule-speaker - %span.fa.fa-user - = speaker.name - - if event[0].track - %span.schedule-track - %span.fa.fa-road - %span.label{ style: "background-color: #{event[0].track.color}" } - = event[0].track.name - + :style => "padding:5px 2px; width:25px" | diff --git a/app/views/conference/schedule.html.erb b/app/views/conference/schedule.html.erb index 9390cf5d..ceaf506d 100644 --- a/app/views/conference/schedule.html.erb +++ b/app/views/conference/schedule.html.erb @@ -1,5 +1,5 @@
- <% if @conference.program.events.scheduled.any? %> + <% if @conference.scheduled? %>
<%= javascript_tag do %> +// change of active tab and the button title when a date clicked $(function() { - // add a hash to the URL when the user clicks on a tab - $('a[data-toggle="tab"]').on('click', function(e) { + $('.date-tab').on('click', function(e) { $('.li-dropdown-schedule').removeClass('active'); $('.schedule-dropdown').find('button').text($(this).text()); - history.pushState(null, null, $(this).attr('href')); - }); - // navigate to a tab when the history changes - window.addEventListener("popstate", function(e) { - var activeTab = $('[href=' + location.hash + ']'); - if (activeTab.length) { - activeTab.tab('show'); - } else { - $('.nav-tabs a[href=#<%= j @today %>]').tab('show'); - } }); }); +// use the date tag in the url to select a tab and the title of the button $(function() { var hash = window.location.hash; - hash && $('ul.nav a[href="' + hash + '"]').tab('show'); + if(hash && !(hash === '#schedule')){ + hash && $('ul a[href="' + hash + '"]').tab('show'); + $('button.dropdown-toggle').text(hash.substr(1)); + } }); +// hide the right and left controls when neccesary after moving the carousel $('.carousel').on('slid.bs.carousel', '', function(){ $(this).children('.left.carousel-control').show(); @@ -117,10 +67,8 @@ function(){ } }); - $(document).ready(function(){ - $('.carousel').children('.left.carousel-control').hide(); + // hide the left control when the page is ready + $('.carousel').children('.left.carousel-control').hide(); }); - - <% end %> diff --git a/app/views/conference/_schedule_item.html.haml b/app/views/conference/_schedule_item.html.haml index e52c9df4..39f2a147 100644 --- a/app/views/conference/_schedule_item.html.haml +++ b/app/views/conference/_schedule_item.html.haml @@ -1,13 +1,10 @@ %td.event{ width: "#{ width * span }%" , | - height: "75px" , | colspan: span, | - style: "cursor:pointer; padding:0px;" , | role: "button" } | %a.unstyled-link{href: url_for(conference_program_proposal_path(@conference.short_title, event.id))} - %div{ class:"event-elipsis break-words", style: "font-size: 11px; height:45px; line-height: 15px; overflow: hidden;"} + %div{ class:"event-elipsis break-words event-title"} = event.title - if speaker = event.speakers.first - = image_tag speaker.gravatar_url, :class => "img-circle pull-right", | + = image_tag speaker.gravatar_url, :class => "img-circle pull-right speaker-pic", | :alt => speaker.name, | - :title => speaker.name, | - :style => "padding:5px 2px; width:25px" | + :title => speaker.name From 82c2c400b6677c4afd6581a0f9028e768f38e8f5 Mon Sep 17 00:00:00 2001 From: Ana Date: Thu, 16 Jun 2016 13:04:44 +0200 Subject: [PATCH 3/4] Swipe support added to the carousel --- app/assets/javascripts/application.js | 1 + app/assets/javascripts/jquery.mobile.custom.min.js | 3 +++ app/views/conference/_schedule.html.erb | 8 ++++++++ 3 files changed, 12 insertions(+) create mode 100644 app/assets/javascripts/jquery.mobile.custom.min.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 54c752a0..fc21cd27 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,6 +12,7 @@ // //= require jquery //= require jquery_ujs +//= require jquery.mobile.custom.min //= require waypoints/jquery.waypoints //= require dataTables/jquery.dataTables //= require dataTables/bootstrap/3/jquery.dataTables.bootstrap diff --git a/app/assets/javascripts/jquery.mobile.custom.min.js b/app/assets/javascripts/jquery.mobile.custom.min.js new file mode 100644 index 00000000..058c3453 --- /dev/null +++ b/app/assets/javascripts/jquery.mobile.custom.min.js @@ -0,0 +1,3 @@ +/*! jQuery Mobile v1.4.5 | Copyright 2010, 2014 jQuery Foundation, Inc. | jquery.org/license */ + +(function(e,t,n){typeof define=="function"&&define.amd?define(["jquery"],function(r){return n(r,e,t),r.mobile}):n(e.jQuery,e,t)})(this,document,function(e,t,n,r){(function(e,t,n,r){function T(e){while(e&&typeof e.originalEvent!="undefined")e=e.originalEvent;return e}function N(t,n){var i=t.type,s,o,a,l,c,h,p,d,v;t=e.Event(t),t.type=n,s=t.originalEvent,o=e.event.props,i.search(/^(mouse|click)/)>-1&&(o=f);if(s)for(p=o.length,l;p;)l=o[--p],t[l]=s[l];i.search(/mouse(down|up)|click/)>-1&&!t.which&&(t.which=1);if(i.search(/^touch/)!==-1){a=T(s),i=a.touches,c=a.changedTouches,h=i&&i.length?i[0]:c&&c.length?c[0]:r;if(h)for(d=0,v=u.length;di||Math.abs(n.pageY-p)>i,d&&!r&&P("vmousecancel",t,s),P("vmousemove",t,s),_()}function I(e){if(g)return;A();var t=C(e.target),n,r;P("vmouseup",e,t),d||(n=P("vclick",e,t),n&&n.isDefaultPrevented()&&(r=T(e).changedTouches[0],v.push({touchID:E,x:r.clientX,y:r.clientY}),m=!0)),P("vmouseout",e,t),d=!1,_()}function q(t){var n=e.data(t,i),r;if(n)for(r in n)if(n[r])return!0;return!1}function R(){}function U(t){var n=t.substr(1);return{setup:function(){q(this)||e.data(this,i,{});var r=e.data(this,i);r[t]=!0,l[t]=(l[t]||0)+1,l[t]===1&&b.bind(n,H),e(this).bind(n,R),y&&(l.touchstart=(l.touchstart||0)+1,l.touchstart===1&&b.bind("touchstart",B).bind("touchend",I).bind("touchmove",F).bind("scroll",j))},teardown:function(){--l[t],l[t]||b.unbind(n,H),y&&(--l.touchstart,l.touchstart||b.unbind("touchstart",B).unbind("touchmove",F).unbind("touchend",I).unbind("scroll",j));var r=e(this),s=e.data(this,i);s&&(s[t]=!1),r.unbind(n,R),q(this)||r.removeData(i)}}}var i="virtualMouseBindings",s="virtualTouchID",o="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),u="clientX clientY pageX pageY screenX screenY".split(" "),a=e.event.mouseHooks?e.event.mouseHooks.props:[],f=e.event.props.concat(a),l={},c=0,h=0,p=0,d=!1,v=[],m=!1,g=!1,y="addEventListener"in n,b=e(n),w=1,E=0,S,x;e.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10,resetTimerDuration:1500};for(x=0;xMath.floor(e.pageY)||e.pageX===0&&Math.floor(i)>Math.floor(e.pageX))i-=n,s-=r;else if(se.event.special.swipe.horizontalDistanceThreshold&&Math.abs(t.coords[1]-n.coords[1])n.coords[0]?"swipeleft":"swiperight";return l(r,"swipe",e.Event("swipe",{target:i,swipestart:t,swipestop:n}),!0),l(r,s,e.Event(s,{target:i,swipestart:t,swipestop:n}),!0),!0}return!1},eventInProgress:!1,setup:function(){var t,n=this,r=e(n),s={};t=e.data(this,"mobile-events"),t||(t={length:0},e.data(this,"mobile-events",t)),t.length++,t.swipe=s,s.start=function(t){if(e.event.special.swipe.eventInProgress)return;e.event.special.swipe.eventInProgress=!0;var r,o=e.event.special.swipe.start(t),u=t.target,l=!1;s.move=function(t){if(!o||t.isDefaultPrevented())return;r=e.event.special.swipe.stop(t),l||(l=e.event.special.swipe.handleSwipe(o,r,n,u),l&&(e.event.special.swipe.eventInProgress=!1)),Math.abs(o.coords[0]-r.coords[0])>e.event.special.swipe.scrollSupressionThreshold&&t.preventDefault()},s.stop=function(){l=!0,e.event.special.swipe.eventInProgress=!1,i.off(f,s.move),s.move=null},i.on(f,s.move).one(a,s.stop)},r.on(u,s.start)},teardown:function(){var t,n;t=e.data(this,"mobile-events"),t&&(n=t.swipe,delete t.swipe,t.length--,t.length===0&&e.removeData(this,"mobile-events")),n&&(n.start&&e(this).off(u,n.start),n.move&&i.off(f,n.move),n.stop&&i.off(a,n.stop))}},e.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe.left",swiperight:"swipe.right"},function(t,n){e.event.special[t]={setup:function(){e(this).bind(n,e.noop)},teardown:function(){e(this).unbind(n)}}})}(e,this)}); \ No newline at end of file diff --git a/app/views/conference/_schedule.html.erb b/app/views/conference/_schedule.html.erb index d5eb93b2..f38c9022 100644 --- a/app/views/conference/_schedule.html.erb +++ b/app/views/conference/_schedule.html.erb @@ -70,5 +70,13 @@ function(){ $(document).ready(function(){ // hide the left control when the page is ready $('.carousel').children('.left.carousel-control').hide(); + + // carousel swipe + $(".carousel-inner").swiperight(function() { + $(this).parent().carousel('prev'); + }); + $(".carousel-inner").swipeleft(function() { + $(this).parent().carousel('next'); + }); }); <% end %> From 028d43088557f03e2bf2f161061c0a5bfa32c33d Mon Sep 17 00:00:00 2001 From: Ana Date: Fri, 17 Jun 2016 14:00:31 +0200 Subject: [PATCH 4/4] Schedule height fits a 600px screen and the schedule is hiden if there is only one room --- .../stylesheets/osem-schedule-print.css | 4 +- app/assets/stylesheets/osem-schedule.css.scss | 95 ++++--------------- app/helpers/application_helper.rb | 37 ++++++++ app/models/conference.rb | 10 -- app/views/conference/_carousel.html.erb | 10 +- app/views/conference/_schedule.html.erb | 6 +- app/views/conference/_schedule_item.html.haml | 8 +- app/views/conference/schedule.html.erb | 2 +- 8 files changed, 74 insertions(+), 98 deletions(-) diff --git a/app/assets/stylesheets/osem-schedule-print.css b/app/assets/stylesheets/osem-schedule-print.css index cad70a1c..54fc3593 100644 --- a/app/assets/stylesheets/osem-schedule-print.css +++ b/app/assets/stylesheets/osem-schedule-print.css @@ -50,11 +50,11 @@ h5 { font-size: 10px; line-height: 12px; margin: 0px; - } +} .table th, .table td { line-height: 12px; padding: 3px; - } +} .schedule-speaker { font-weight: normal; diff --git a/app/assets/stylesheets/osem-schedule.css.scss b/app/assets/stylesheets/osem-schedule.css.scss index b92769fd..cc48da5d 100644 --- a/app/assets/stylesheets/osem-schedule.css.scss +++ b/app/assets/stylesheets/osem-schedule.css.scss @@ -15,7 +15,7 @@ background-image: -webkit-gradient( ); cursor: pointer; -padding: 2px; +padding: 3px 2px; } #schedule td.event:hover @@ -94,18 +94,10 @@ a.unstyled-link { width: 100%; } -.event-elipsis{ +.elipsis{ text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; - -webkit-line-clamp: 3; /* number of lines to show */ -} - -.room-elipsis{ - text-overflow: ellipsis; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 5; /* number of lines to show */ } .break-words{ @@ -126,24 +118,15 @@ a.unstyled-link { hyphens: auto; } -.room { +.room, .event-title{ font-size: 11px; - height: 70px; - line-height: 14px; - overflow: hidden; -} - -.event-title{ - font-size: 11px; - height: 42px; - line-height: 14px; + line-height: 17px; overflow: hidden; } td.room{ width: 15%; padding: 3px !important; - height: 77px; max-width:15%; } @@ -153,33 +136,27 @@ th.date{ } .speaker-pic{ - padding: 5px 2px; - width: 25px; + padding: 4px 2px; +} + +.schedule-table{ + width: 100%; + margin:0px; + padding:0px; +} + +td.no-padding{ + padding:0px !important; } /* Small devices (tablets, 768px and up) */ @media (min-width: 768px) { - .room { + .room, .event-title{ font-size: 12px; - height: 75px; - line-height: 15px; - } - - .event-title{ - font-size: 12px; - height: 45px; - line-height: 15px; - } - - .speaker-pic{ - padding: 5px 3px; - width: 30px; } td.room{ width: 12%; - height: 90px; - padding: 5px !important; max-width:12%; } @@ -188,69 +165,39 @@ th.date{ } td.event{ - padding: 5px !important; + padding: 3px 4px !important; } } /* Medium devices (desktops, 992px and up) */ @media (min-width: 992px) { - .room { + .room, .event-title{ font-size: 13px; - height: 80px; - line-height: 16px; - } - - .event-title{ - font-size: 13px; - height: 48px; - line-height: 16px; - } - - .speaker-pic{ - padding: 5px 4px; - width: 35px; } td.room{ width: 10%; - height: 100px; - padding: 6px !important; max-width: 10%; } td.event{ - padding: 6px !important; + padding: 3px 5px !important; } } /* Large devices (large desktops, 1200px and up) */ @media (min-width: 1200px) { - .room { + .room, .event-title{ font-size: 14px; - height: 85px; - line-height: 17px; - } - - .event-title{ - font-size: 14px; - height: 51px; - line-height: 17px; - } - - .speaker-pic{ - padding: 5px; - width: 39px; } td.room{ width: 9%; - height: 105px; - padding: 7px !important; max-width: 9%; } td.event{ - padding: 7px !important; + padding: 3px 6px !important; } th.date{ diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ad88edec..5004347c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -298,4 +298,41 @@ module ApplicationHelper yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000 (yiq >= 128) ? 'black' : 'white' end + + def td_height(rooms) + td_height = 500 / rooms.length + # we want all least 3 lines in events and td padding = 3px, speaker picture height >= 25px + # and line-height = 17px => (17 * 3) + 6 + 25 = 82 + td_height < 82 ? 82 : td_height + end + + def room_height(rooms) + room_lines(rooms) * 17 + end + + def room_lines(rooms) + # line-height = 17px, td padding = 3px + (td_height(rooms) - 6) / 17 + end + + def event_height(rooms) + event_lines(rooms) * 17 + end + + def event_lines(rooms) + # line-height = 17px, td padding = 3px, speaker picture height >= 25px + (td_height(rooms) - 31) / 17 + end + + def speaker_height(rooms) + # td padding = 3px + speaker_height = td_height(rooms) - 6 - event_height(rooms) + # The speaker picture is a circle and the width must be <= 37 to avoid making the cell widther + speaker_height >= 37 ? 37 : speaker_height + end + + def speaker_width(rooms) + # speaker picture padding: 4px 2px; and we want the picture to be a circle + speaker_height(rooms) - 4 + end end diff --git a/app/models/conference.rb b/app/models/conference.rb index c90a8ea2..f0b73047 100644 --- a/app/models/conference.rb +++ b/app/models/conference.rb @@ -81,16 +81,6 @@ class Conference < ActiveRecord::Base result end - ## - # Check if there is any event in the program scheduled. - # - # ====Returns - # * +false+ -> If there aren't schedules events - # * +true+ -> If there are schedules events - def scheduled? - self.program.events.where.not(start_time: nil).any? - end - ## # Checks if the user is registered to the conference # diff --git a/app/views/conference/_carousel.html.erb b/app/views/conference/_carousel.html.erb index be89386b..fbdc8fa1 100644 --- a/app/views/conference/_carousel.html.erb +++ b/app/views/conference/_carousel.html.erb @@ -1,13 +1,13 @@ <% intervals = number_columns * 60 / EventType::LENGTH_STEP + 1 %> <% width = 85 / intervals %> -<% carousel_number = (conf_period / number_columns.to_f).ceil %> +<% carousel_number = (@conf_period / number_columns.to_f).ceil %>