From ac63afbd95b87475f9f6bf4eae76540cb70d05fb Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 9 Jan 2012 22:05:28 +0100 Subject: Insert new contacts alphabetically correct in the list. Added some tipsys to the editor. --- apps/contacts/css/styles.css | 2 +- apps/contacts/js/interface.js | 27 +++++++++++++++++++++++---- apps/contacts/templates/part.details.php | 5 +++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/contacts/css/styles.css b/apps/contacts/css/styles.css index c890be85824..7b56767bab0 100644 --- a/apps/contacts/css/styles.css +++ b/apps/contacts/css/styles.css @@ -4,7 +4,7 @@ #contacts_details_name { font-weight:bold;font-size:1.1em;margin-left:25%;} #contacts_details_photo { margin:.5em 0em .5em 25%; } -#contacts_deletecard {position:absolute;top:15px;right:0;} +#contacts_deletecard {position:absolute;top:15px;right:15px;} #contacts_details_list { list-style:none; } #contacts_details_list li { overflow:visible; } #contacts_details_list li p.contacts_property_name { width:25%; float:left;text-align:right;padding-right:0.3em;color:#666; } diff --git a/apps/contacts/js/interface.js b/apps/contacts/js/interface.js index b9f75bdf71a..187c8941dcf 100644 --- a/apps/contacts/js/interface.js +++ b/apps/contacts/js/interface.js @@ -236,7 +236,10 @@ $(document).ready(function(){ Contacts.UI.Addressbooks.overview(); return false; }); - + + /** + * Open blank form to add new contact. + */ $('#contacts_newcontact').click(function(){ $.getJSON('ajax/showaddcard.php',{},function(jsondata){ if(jsondata.status == 'success'){ @@ -250,14 +253,28 @@ $(document).ready(function(){ }); return false; }); - + + /** + * Add and insert a new contact into the list. + */ $('#contacts_addcardform input[type="submit"]').live('click',function(){ $.post('ajax/addcard.php',$('#contacts_addcardform').serialize(),function(jsondata){ if(jsondata.status == 'success'){ $('#rightcontent').data('id',jsondata.data.id); $('#rightcontent').html(jsondata.data.page); $('#leftcontent .active').removeClass('active'); - $('#leftcontent ul').append('
  • '+jsondata.data.name+'
  • '); + var item = '
  • '+jsondata.data.name+'
  • '; + var added = false; + $('#leftcontent ul li').each(function(){ + if ($(this).text().toLowerCase() > jsondata.data.name.toLowerCase()) { + $(this).before(item).fadeIn('fast'); + added = true; + return false; + } + }); + if(!added) { + $('#leftcontent ul').append(item); + } } else{ alert(jsondata.data.message); @@ -265,7 +282,6 @@ $(document).ready(function(){ }, 'json'); return false; }); - $('.contacts_property [data-use="edit"]').live('click',function(){ var id = $('#rightcontent').data('id'); var checksum = $(this).parents('.contacts_property').first().data('checksum'); @@ -338,4 +354,7 @@ $(document).ready(function(){ // element has gone out of viewport } }); + + $('.action').tipsy(); + $('.button').tipsy(); }); diff --git a/apps/contacts/templates/part.details.php b/apps/contacts/templates/part.details.php index afad0b7f64c..1482c063685 100644 --- a/apps/contacts/templates/part.details.php +++ b/apps/contacts/templates/part.details.php @@ -86,3 +86,8 @@ + -- cgit v1.2.3 From aadb81a32ac521058cb4bafe052f39f24ac9dc8e Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 9 Jan 2012 23:14:11 +0100 Subject: OC_DB::insertid was being called too late to get the correct result. --- apps/contacts/lib/vcard.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php index beb291b481e..6a248ff59e4 100644 --- a/apps/contacts/lib/vcard.php +++ b/apps/contacts/lib/vcard.php @@ -130,10 +130,11 @@ class OC_Contacts_VCard{ $stmt = OC_DB::prepare( 'INSERT INTO *PREFIX*contacts_cards (addressbookid,fullname,carddata,uri,lastmodified) VALUES(?,?,?,?,?)' ); $result = $stmt->execute(array($id,$fn,$data,$uri,time())); + $newid = OC_DB::insertid('*PREFIX*contacts_cards'); OC_Contacts_Addressbook::touch($id); - return OC_DB::insertid('*PREFIX*contacts_cards'); + return $newid; } /** -- cgit v1.2.3 From 60653c05440121a149a2709d719060cf77c2c58d Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 9 Jan 2012 23:16:07 +0100 Subject: Return standard thumbnail even if contact couldn't be found as it could be because it was just added, --- apps/contacts/thumbnail.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/contacts/thumbnail.php b/apps/contacts/thumbnail.php index 622718d65c6..e49098ce820 100644 --- a/apps/contacts/thumbnail.php +++ b/apps/contacts/thumbnail.php @@ -46,8 +46,8 @@ $l10n = new OC_L10N('contacts'); $card = OC_Contacts_VCard::find( $id ); if( $card === false ){ - OC_Log::write('contacts','thumbnail.php. Contact could not be found.',OC_Log::ERROR); - //echo $l10n->t('Contact could not be found.'); + OC_Log::write('contacts','thumbnail.php. Contact could not be found: '.$id,OC_Log::ERROR); + getStandardImage(); exit(); } @@ -55,7 +55,6 @@ if( $card === false ){ $addressbook = OC_Contacts_Addressbook::find( $card['addressbookid'] ); if( $addressbook === false || $addressbook['userid'] != OC_USER::getUser()){ OC_Log::write('contacts','thumbnail.php. Wrong contact/addressbook - WTF?',OC_Log::ERROR); - //echo $l10n->t('This is not your contact.'); // This is a weird error, why would it come up? (Better feedback for users?) exit(); } -- cgit v1.2.3 From e316cddf650379b5b6cde600dd058d33169ddbb1 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 10 Jan 2012 14:59:31 +0100 Subject: More fixes on saving TYPE parameters. Use jQuery Dialog for error messages instead of alert() --- apps/contacts/ajax/addcard.php | 17 ++++++++- apps/contacts/ajax/addproperty.php | 2 + apps/contacts/js/interface.js | 76 ++++++++++++++++++++++---------------- apps/contacts/templates/index.php | 3 -- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/apps/contacts/ajax/addcard.php b/apps/contacts/ajax/addcard.php index 9d782246a0a..54e4faa6ed9 100644 --- a/apps/contacts/ajax/addcard.php +++ b/apps/contacts/ajax/addcard.php @@ -54,11 +54,24 @@ foreach( $add as $propname){ $value = $values[$propname]; if( isset( $parameters[$propname] ) && count( $parameters[$propname] )){ $prop_parameters = $parameters[$propname]; - } else{ $prop_parameters = array(); } - $vcard->addProperty($propname, $value, $prop_parameters); + $vcard->addProperty($propname, $value); //, $prop_parameters); + $line = count($vcard->children) - 1; + foreach ($prop_parameters as $key=>$element) { + if(is_array($element) && strtoupper($key) == 'TYPE') { + // FIXME: Maybe this doesn't only apply for TYPE? + // And it probably shouldn't be done here anyways :-/ + foreach($element as $e){ + if($e != '' && !is_null($e)){ + $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key,$e); + } + } + } else { + $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key,$element); + } + } } $id = OC_Contacts_VCard::add($aid,$vcard->serialize()); diff --git a/apps/contacts/ajax/addproperty.php b/apps/contacts/ajax/addproperty.php index 0122cf019c7..0f76add3c9b 100644 --- a/apps/contacts/ajax/addproperty.php +++ b/apps/contacts/ajax/addproperty.php @@ -48,6 +48,8 @@ foreach ($parameters as $key=>$element) { $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key,$e); } } + } else { + $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key,$element); } } diff --git a/apps/contacts/js/interface.js b/apps/contacts/js/interface.js index 187c8941dcf..4a27073c156 100644 --- a/apps/contacts/js/interface.js +++ b/apps/contacts/js/interface.js @@ -18,9 +18,6 @@ * You should have received a copy of the GNU Affero General Public * License along with this library. If not, see . * - * TODO: - * If you add a contact, its thumbnail doesnt show in the list. But when you add another one it shows up, but not for the second contact added. - * Place a new contact in correct alphabetic order */ @@ -31,6 +28,20 @@ Contacts={ $('#carddav_url').show(); $('#carddav_url_close').show(); }, + messageBox:function(title, msg) { + var $dialog = $('
    ') + .html(msg) + .dialog({ + autoOpen: true, + title: title,buttons: [ + { + text: "Ok", + click: function() { $(this).dialog("close"); } + } + ] + } + ); + }, Addressbooks:{ overview:function(){ if($('#chooseaddressbook_dialog').dialog('isOpen') == true){ @@ -85,7 +96,8 @@ Contacts={ Contacts.UI.Contacts.update(); Contacts.UI.Addressbooks.overview(); } else { - alert('Error: ' + data.message); + Contacts.UI.messageBox('Error', data.message); + //alert('Error: ' + data.message); } }); } @@ -114,37 +126,29 @@ Contacts={ } }, Contacts:{ + /** + * Reload the contacts list. + */ update:function(){ $.getJSON('ajax/contacts.php',{},function(jsondata){ if(jsondata.status == 'success'){ $('#contacts').html(jsondata.data.page); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error',jsondata.data.message); + //alert(jsondata.data.message); } }); - /* - var contactlist = $('#contacts'); - var contacts = contactlist.children('li').get(); - //alert(contacts); - contacts.sort(function(a, b) { - var compA = $(a).text().toUpperCase(); - var compB = $(b).text().toUpperCase(); - return (compA < compB) ? -1 : (compA > compB) ? 1 : 0; - }) - $.each(contacts, function(idx, itm) { contactlist.append(itm); }); - */ - setTimeout(Contacts.UI.Contacts.lazyupdate(), 500); + setTimeout(Contacts.UI.Contacts.lazyupdate, 500); }, + /** + * Add thumbnails to the contact list as they become visible in the viewport. + */ lazyupdate:function(){ - //alert('lazyupdate'); $('#contacts li').live('inview', function(){ if (!$(this).find('a').attr('style')) { - //alert($(this).data('id') + ' has background: ' + $(this).attr('style')); $(this).find('a').css('background','url(thumbnail.php?id='+$(this).data('id')+') no-repeat'); - }/* else { - alert($(this).data('id') + ' has style ' + $(this).attr('style').match('url')); - }*/ + } }); } } @@ -168,7 +172,8 @@ $(document).ready(function(){ $('#leftcontent li[data-id="'+jsondata.data.id+'"]').addClass('active'); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }); return false; @@ -183,7 +188,8 @@ $(document).ready(function(){ $('#rightcontent').empty(); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }); return false; @@ -197,7 +203,8 @@ $(document).ready(function(){ $('#contacts_addproperty').hide(); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }); return false; @@ -226,7 +233,8 @@ $(document).ready(function(){ $('#contacts_addpropertyform').before(jsondata.data.page); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }, 'json'); return false; @@ -248,7 +256,8 @@ $(document).ready(function(){ .find('select').chosen(); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }); return false; @@ -277,7 +286,8 @@ $(document).ready(function(){ } } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }, 'json'); return false; @@ -291,7 +301,8 @@ $(document).ready(function(){ .find('select').chosen(); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }); return false; @@ -303,7 +314,8 @@ $(document).ready(function(){ $('.contacts_property[data-checksum="'+jsondata.data.oldchecksum+'"]').replaceWith(jsondata.data.page); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } },'json'); return false; @@ -317,7 +329,8 @@ $(document).ready(function(){ $('.contacts_property[data-checksum="'+checksum+'"]').remove(); } else{ - alert(jsondata.data.message); + Contacts.UI.messageBox('Error', jsondata.data.message); + //alert(jsondata.data.message); } }); return false; @@ -357,4 +370,5 @@ $(document).ready(function(){ $('.action').tipsy(); $('.button').tipsy(); + //Contacts.UI.messageBox('Hello','Sailor'); }); diff --git a/apps/contacts/templates/index.php b/apps/contacts/templates/index.php index 90143f25fa6..24484231af4 100644 --- a/apps/contacts/templates/index.php +++ b/apps/contacts/templates/index.php @@ -31,7 +31,4 @@ OC_Util::addStyle('contacts','formtastic');
    -
    - t("There was a fail, while parsing the file."); ?> -
    -- cgit v1.2.3 97'>297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
    <% @gantt.view = self %>
    <div class="contextual">
    </div>
    
    <h2><%= @query.new_record? ? l(:label_gantt) : @query.name %></h2>
    
    <%= form_tag({:controller => 'gantts', :action => 'show',
                 :project_id => @project, :month => params[:month],
                 :year => params[:year], :months => params[:months]},
                 :method => :get, :id => 'query_form') do %>
    <%= hidden_field_tag 'set_filter', '1' %>
    <%= hidden_field_tag 'gantt', '1' %>
    
    <div id="query_form_with_buttons" class="hide-when-print">
    <div id="query_form_content">
      <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
        <legend onclick="toggleFieldset(this);" class="icon icon-<%= @query.new_record? ? "expended" : "collapsed" %>"><%= l(:label_filter_plural) %></legend>
        <div style="<%= @query.new_record? ? "" : "display: none;" %>">
          <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
        </div>
      </fieldset>
    
      <fieldset id="options" class="collapsible collapsed">
        <legend onclick="toggleFieldset(this);" class="icon icon-collapsed"><%= l(:label_options) %></legend>
        <div style="display: none;">
          <table>
            <tr>
              <td>
                <fieldset>
                  <legend>
                    <%= l(:field_column_names) %>
                  </legend>
                  <label for="draw_selected_columns">
                    <%= check_box 'query', 'draw_selected_columns', :id => 'draw_selected_columns', 'data-enables' => 'span.query-columns select, span.query-columns input' %>
                      <%= l(:label_display) %>
                  </label>
                  <%= render_query_columns_selection(@query) %>
                </fieldset>
              </td>
            </tr>
            <tr>
              <td>
                <fieldset>
                  <legend><%= l(:label_related_issues) %></legend>
                  <label for="draw_relations">
                    <%= check_box 'query', 'draw_relations', :id => 'draw_relations' %>
                    <% rels = [IssueRelation::TYPE_BLOCKS, IssueRelation::TYPE_PRECEDES] %>
                    <% rels.each do |rel| %>
                      <% color = Redmine::Helpers::Gantt::DRAW_TYPES[rel][:color] %>
                      <%= content_tag(:span, '&nbsp;&nbsp;&nbsp;'.html_safe,
                                      :style => "background-color: #{color}") %>
                      <%= l(IssueRelation::TYPES[rel][:name]) %>
                    <% end %>
                  </label>
                </fieldset>
              </td>
              <td>
                <fieldset>
                  <legend><%= l(:label_gantt_progress_line) %></legend>
                  <label for="draw_progress_line">
                    <%= check_box 'query', 'draw_progress_line', :id => 'draw_progress_line' %>
                    <%= l(:label_display) %>
                  </label>
                </fieldset>
              </td>
            </tr>
          </table>
        </div>
      </fieldset>
    </div>
    
    <p class="contextual">
      <span>
        <%= gantt_zoom_link(@gantt, :in) %>
        <%= gantt_zoom_link(@gantt, :out) %>
      </span>
      <span>
        <%= link_to_previous_month(@gantt.year_from, @gantt.month_from, :accesskey => accesskey(:previous)) %> | <%= link_to_next_month(@gantt.year_from, @gantt.month_from, :accesskey => accesskey(:next)) %>
      </span>
    </p>
    
    <p class="buttons">
      <%= number_field_tag 'months', @gantt.months, :min => 1, :max => Setting.gantt_months_limit.to_i, :autocomplete => false %>
      <%= l(:label_months_from) %>
      <%= select_month(@gantt.month_from, :prefix => "month", :discard_type => true) %>
      <%= select_year(@gantt.year_from, :prefix => "year", :discard_type => true) %>
      <%= hidden_field_tag 'zoom', @gantt.zoom %>
    
      <%= link_to_function l(:button_apply), '$("#query_form").submit()',
                           :class => 'icon icon-checked' %>
      <%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 },
                  :class => 'icon icon-reload' %>
      <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
        <%= link_to_function l(:button_save),
                             "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit();",
                             :class => 'icon icon-save' %>
      <% end %>
    <% if !@query.new_record? && @query.editable_by?(User.current) %>
      <%= link_to l(:button_edit), edit_query_path(@query, :gantt => 1), :class => 'icon icon-edit' %>
      <%= delete_link query_path(@query, :gantt => 1) %>
    <% end %>
    </p>
    </div>
    <% end %>
    
    <%= error_messages_for 'query' %>
    <% if @query.valid? %>
    <%
      zoom = 1
      @gantt.zoom.times { zoom = zoom * 2 }
    
      subject_width = 330
      header_height = 18
    
      headers_height = header_height
      show_weeks = false
      show_days  = false
      show_day_num = false
    
      if @gantt.zoom > 1
        show_weeks = true
        headers_height = 2 * header_height
        if @gantt.zoom > 2
            show_days = true
            headers_height = 3 * header_height
            if @gantt.zoom > 3
              show_day_num = true
              headers_height = 4 * header_height
            end
        end
      end
    
      # Width of the entire chart
      g_width = ((@gantt.date_to - @gantt.date_from + 1) * zoom).to_i
      @gantt.render(:top => headers_height + 8,
                    :zoom => zoom,
                    :g_width => g_width,
                    :subject_width => subject_width)
      g_height = [(20 * (@gantt.number_of_rows + 6)) + 150, 206].max
      t_height = g_height + headers_height
    %>
    
    <% if @gantt.truncated %>
      <p class="warning"><%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %></p>
    <% end %>
    
    <table class='gantt-table'>
    <tr>
    <td style="width:<%= @query.draw_selected_columns ? subject_width + 1 : subject_width + 2 %>px;" class="gantt_subjects_column">
      <%
        style  = ""
        style += "position:relative;"
        style += "height: #{t_height + 24}px;"
        style += "width: #{subject_width + 1}px;"
      %>
      <%= content_tag(:div, :style => style, :class => "gantt_subjects_container #{'draw_selected_columns' if @query.draw_selected_columns}") do %>
        <%
          style  = ""
          style += "width: #{subject_width + 1}px;"
          style += "height: #{headers_height}px;"
          style += 'background: #eee;'
        %>
        <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %>
        <%
          style  = ""
          style += "z-index: 1;"
          style += "width: #{subject_width + 1}px;"
          style += "height: #{t_height}px;"
          style += 'overflow: hidden;'
        %>
        <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %>
        <%= content_tag(:div, :class => "gantt_subjects") do %>
          <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
            <%= hidden_field_tag 'back_url', url_for(:params => request.query_parameters), :id => nil %>
            <%= @gantt.subjects.html_safe %>
          <% end %>
        <% end %>
      <% end %>
    </td>
    <%
      @query.columns.each do |column|
        next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name)
        column_name = column.name.to_s.tr('.', '_')
    %>
      <td class="gantt_<%= column_name %>_column gantt_selected_column <%= 'last_gantt_selected_column' if @query.columns.last == column %>" id="<%= column_name %>">
        <%
          style = "position: relative;"
          style += "height: #{t_height + 24}px;"
        %>
        <%= content_tag(:div, :style => style, :class => "gantt_#{column_name}_container gantt_selected_column_container") do %>
          <%
            style = "height: #{t_height}px;"
            style += 'overflow: hidden;'
          %>
          <%= content_tag(:div, '', :style => style, :class => "gantt_hdr") %>
          <%
            style = "height: #{headers_height}px;"
            style += 'background: #eee;'
          %>
          <%= content_tag(:div, content_tag(:p, column.caption, :class => 'gantt_hdr_selected_column_name'), :style => style, :class => "gantt_hdr") %>
          <%= content_tag(:div, :class => "gantt_#{column_name} gantt_selected_column_content") do %>
            <%= @gantt.selected_column_content({:column => column, :top => headers_height + 8, :zoom => zoom, :g_width => g_width}).html_safe %>
          <% end %>
        <% end %>
      </td>
    <% end %>
    <td>
    <div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area">
    <%
      style  = ""
      style += "width: #{g_width - 1}px;"
      style += "height: #{headers_height}px;"
      style += 'background: #eee;'
    %>
    <%= content_tag(:div, '&nbsp;'.html_safe, :style => style, :class => "gantt_hdr") %>
    
    <% ###### Months headers ###### %>
    <%
      month_f = @gantt.date_from
      left = 0
      height = (show_weeks ? header_height : header_height + g_height)
    %>
    <% @gantt.months.times do %>
      <%
        width = (((month_f >> 1) - month_f) * zoom - 1).to_i
        style  = ""
        style += "left: #{left}px;"
        style += "width: #{width}px;"
        style += "height: #{height}px;"
      %>
      <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %>
        <%= link_to "#{month_f.year}-#{month_f.month}",
                    @gantt.params.merge(:year => month_f.year, :month => month_f.month),
                    :title => "#{month_name(month_f.month)} #{month_f.year}" %>
      <% end %>
      <%
        left = left + width + 1
        month_f = month_f >> 1
      %>
    <% end %>
    
    <% ###### Weeks headers ###### %>
    <% if show_weeks %>
      <%
        left = 0
        height = (show_days ? header_height - 1 : header_height - 1 + g_height)
      %>
      <% if @gantt.date_from.cwday == 1 %>
        <%
          # @date_from is monday
          week_f = @gantt.date_from
        %>
      <% else %>
        <%
          # find next monday after @date_from
          week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1)
          width = (7 - @gantt.date_from.cwday + 1) * zoom - 1
          style  = ""
          style += "left: #{left}px;"
          style += "top: 19px;"
          style += "width: #{width}px;"
          style += "height: #{height}px;"
        %>
        <%= content_tag(:div, '&nbsp;'.html_safe,
                        :style => style, :class => "gantt_hdr") %>
        <% left = left + width + 1 %>
      <% end %>
      <% while week_f <= @gantt.date_to %>
        <%
          width = ((week_f + 6 <= @gantt.date_to) ?
                      7 * zoom - 1 :
                      (@gantt.date_to - week_f + 1) * zoom - 1).to_i
          style  = ""
          style += "left: #{left}px;"
          style += "top: 19px;"
          style += "width: #{width}px;"
          style += "height: #{height}px;"
        %>
        <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %>
          <%= content_tag(:small) do %>
            <%= week_f.cweek if width >= 16 %>
          <% end %>
        <% end %>
        <%
          left = left + width + 1
          week_f = week_f + 7
        %>
      <% end %>
    <% end %>
    
    <% ###### Day numbers headers ###### %>
    <% if show_day_num %>
      <%
        left = 0
        height = g_height + header_height*2 - 1
        wday = @gantt.date_from.cwday
        day_num = @gantt.date_from
      %>
      <% (@gantt.date_to - @gantt.date_from + 1).to_i.times do %>
        <%
          width =  zoom - 1
          style = ""
          style += "left:#{left}px;"
          style += "top:37px;"
          style += "width:#{width}px;"
          style += "height:#{height}px;"
          style += "font-size:0.7em;"
          clss = "gantt_hdr"
          clss << " nwday" if @gantt.non_working_week_days.include?(wday)
        %>
        <%= content_tag(:div, :style => style, :class => clss) do %>
          <%= day_num.day %>
        <% end %>
        <%
         left = left + width+1
         day_num = day_num + 1
         wday = wday + 1
         wday = 1 if wday > 7
        %>
      <% end %>
    <% end %>
    
    <% ###### Days headers ####### %>
    <% if show_days %>
      <%
        left = 0
        height = g_height + header_height - 1
        top = (show_day_num ? 55 : 37)
      %>
      <% (@gantt.date_from..@gantt.date_to).each do |g_date| %>
        <%
          width =  zoom - 1
          style  = ""
          style += "left: #{left}px;"
          style += "top: #{top}px;"
          style += "width: #{width}px;"
          style += "height: #{height}px;"
          style += "font-size:0.7em;"
          clss = "gantt_hdr"
          clss << " nwday" if @gantt.non_working_week_days.include?(g_date.cwday)
        %>
        <%= content_tag(:div, :style => style, :class => clss) do %>
          <%= day_letter(g_date.cwday) %>
        <% end %>
        <%
          left = left + width + 1
        %>
      <% end %>
    <% end %>
    
    <%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%>
      <%= hidden_field_tag 'back_url', url_for(:params => request.query_parameters), :id => nil %>
      <%= @gantt.lines.html_safe %>
    <% end %>
    
    <% ###### Today red line (excluded from cache) ###### %>
    <% if User.current.today >= @gantt.date_from and User.current.today <= @gantt.date_to %>
      <%
        today_left = (((User.current.today - @gantt.date_from + 1) * zoom).floor() - 1).to_i
        style  = ""
        style += "position: absolute;"
        style += "height: #{g_height}px;"
        style += "top: #{headers_height + 1}px;"
        style += "left: #{today_left}px;"
        style += "width:10px;"
        style += "border-left: 1px dashed red;"
      %>
      <%= content_tag(:div, '&nbsp;'.html_safe, :style => style, :id => 'today_line') %>
    <% end %>
    <%
      style  = ""
      style += "position: absolute;"
      style += "height: #{g_height}px;"
      style += "top: #{headers_height + 1}px;"
      style += "left: 0px;"
      style += "width: #{g_width - 1}px;"
    %>
    <%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %>
    </div>
    </td>
    </tr>
    </table>
    
    <span class="pagination">
      <ul class="pages">
        <li class="previous page">
          <%= link_to("\xc2\xab " + l(:label_previous),
                                     {:params => request.query_parameters.merge(@gantt.params_previous)},
                                     :accesskey => accesskey(:previous)) %>
        </li><li class="next page">
          <%= link_to(l(:label_next) + " \xc2\xbb",
                                     {:params => request.query_parameters.merge(@gantt.params_next)},
                                     :accesskey => accesskey(:next)) %>
        </li>
      </ul>
    </span>
    
    <% other_formats_links do |f| %>
      <%= f.link_to_with_query_parameters 'PDF', @gantt.params %>
      <%= f.link_to_with_query_parameters('PNG', @gantt.params) if @gantt.respond_to?('to_image') %>
    <% end %>
    <% end # query.valid? %>
    
    <% content_for :sidebar do %>
      <%= render :partial => 'issues/sidebar' %>
    <% end %>
    
    <% html_title(l(:label_gantt)) -%>
    
    <% content_for :header_tags do %>
      <%= javascript_include_tag 'raphael' %>
      <%= javascript_include_tag 'gantt' %>
    <% end %>
    
    <%= javascript_tag do %>
      var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>;
      $(function() {
        disable_unavailable_columns('<%= Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.map(&:to_s).join(',') %>'.split(','));
        drawGanttHandler();
        resizableSubjectColumn();
        drawSelectedColumns();
        $("#draw_relations, #draw_progress_line, #draw_selected_columns").change(drawGanttHandler);
        $('div.gantt_subjects .expander').on('click', ganttEntryClick);
      });
      $(window).resize(function() {
        drawGanttHandler();
        resizableSubjectColumn();
      });
    <% end %>
    <%= context_menu %>