1

我正在开发一个包含月视图和日视图的 Javascript/jQuery 日历。单击日期将更改日期,这将更新日视图中的日期变量。

日视图分为从午夜到晚上 11:00 的半小时部分。单击任何半小时<tr>(日视图是一个表格)将在单击的时间和未来一个小时之间创建一个事件,并div在日历顶部附加一个跨越时间范围并定位在正确起点的事件点(每个像素是一分钟...)

但是,有一个问题。如果您在已经存在的某个时间跨度之间创建一个“事件”,它们就会重叠。显然,这是默认行为,但我希望发生的是,如果在已被事件占用的日期范围之间创建事件,则它们并排对齐,以免重叠。

这类似于在 Mac 的 iCal 应用程序中看到的行为: 在此处输入图像描述

现在我实现这样一个目标的第一个想法是使用碰撞检测,但是所有用于此目标的 jQuery 插件都很臃肿或要求元素可拖动。

然后我认为CSS中可能有一种方法可以做到这一点,如果两个元素重叠,它们会均匀地分割宽度。

然后我认为这太荒谬了,所以我想知道如何尽可能轻松地实现这一目标。

我将在 jsFiddle 中发布完整的代码,但最重要的函数insertEvent如下所示:

    function insertEvent(start, end){

        var end_minutes = new Date(end).getMinutes();
        var end_border = new Date(new Date(end).setMinutes(end_minutes + 2));

        //$(".day_date").html(start + "<br />" + end);
        var diff = Math.abs(end_border - new Date(start));
        var minutes = Math.floor((diff/1000)/60);

        var start_element = $("td").find("[data-date='" + start + "']");
        var offset = start_element.offset().top - $(".second").offset().top;

        var this_element = $("<div class='event' style='height:" + minutes + "px;margin-top:" + offset + "px;'></div>");

        $(".right").prepend(this_element);

    }

这需要 javascriptnew Date()格式的两个参数,一个用于开始日期,一个用于结束日期。

小提琴:http: //jsfiddle.net/charlescarver/HwdwL/

4

1 回答 1

1

我在您的方法中看到的问题之一是没有存储数据的结构。我以前用 Javascript 构建了一个日历,这并不容易。首先,确保您对日历事件有某种抽象。就像是:

function CalendarEvent(startDateTime, endDateTime) {
  this.startDateTime = startDateTime;
  this.endDateTime = endDateTime;
}

CalendarEvent.prototype.start = function() {
  return this.startDateTime.getTime();
};

CalendarEvent.prototype.end = function() {
  return this.endDateTime.getTime();
};

CalendarEvent.new = function(startDateTime, endDateTime) {
  // This is a little factory method. It prevents calendar events 
  // from having end times that fall before the start time.
  // USE THIS TO INSTANTIATE A NEW CALENDAR EVENT
  if(endDateTime.getTime() < startDateTime.getTime()) {
    throw new Error("End time falls before start time");
  }
  return new CalendarEvent(startDateTime, endDateTime);
};

CalendarEvent.compare = function(eventOne, eventTwo) {
  // this is a class method to compare two events
  // If used with sort it will sort by startDateTime
  return eventOne.start() - eventTwo.start();
};

// ... add any other methods you need

接下来,您将要对日历事件进行排序。我会按开始时间排序。然后,一旦对它进行排序,您实际上可以在进行更改时重新渲染所有内容。只要您正确排序,确定日历事件是否发生冲突就这么简单:

CalendarEvent.prototype.intersects = function(otherEvent) {
  // If the other event starts after this one ends 
  // then they don't intersect
  if(otherEvent.start() > this.end()) {
    return false;
  }
  // If the other event ends before this one starts
  // then they don't intersect
  if(otherEvent.end() < this.start()) {
    return false;
  }
  // Everything else is true
  return true;
};

因为数据已排序,所以您知道如果两个或更多日历事件相交,它们将不得不共享空间。当然,在划分空间时,您必须考虑一些事情。您是否想要一个简单的实现,您只需从左到右平均共享空间(左侧具有最早的开始时间)。如果是这样,如果它有 4 个共享空间的事件(每个块都是一个事件),您的视觉表示可能看起来像这样:

幼稚的渲染

但是,如果您的事件具有奇怪的形状,它们可能会导致您的日历看起来很奇怪。考虑以下:

可能的情况

在这种情况下,事件 2 占用了大量垂直空间,并且事件 1 下方的所有空间都未使用。也许为了更好的用户体验,你不希望这种事情发生。如果是这样,您应该相应地设计您的渲染算法。请记住,在您遇到的每个更改上重新渲染可能是最容易的,但这完全取决于您如何存储数据。如果您不将数据存储在某种易于遍历的结构中,那么您将无法做这种事情。

但是要完成对您问题的回答,这是一个相当幼稚的示例。我还没有测试过它,所以这是一个很大的假设。它并不完全完整,您必须自己编辑渲染。这只是为了让您了解如何使其工作。它肯定看起来更漂亮:

function renderCalendarEvents(calendarEvents) {
  // Sort the calendar events (assuming calendarEvents is an array)
  var sortedEvents = calendarEvents.sort(CalendarEvent.compare);
  var index = 0;
  // renderEvents is an anonymous function that will be called every time 
  // you need to render an event
  // it returns it's columnDivisor.
  var renderEvent = function(position) {
    var currentEvent = sortedEvents[index];
    var nextEvent = sortedEvents[index + 1];
    // The default column divisor is determined by
    // the current x-position + 1
    var columnDivisor = position + 1;
    // Increment before any recursion
    index += 1;
    // Check if nextEvent even exists
    if(nextEvent) {
      // If the nextEvent intersects with the current event
      // then recurse
      if(currentEvent.intersects(nextEvent)) {
        // We need to tell the next event that it starts at the
        // column position that is immediately +1 to the current event
        columnDivisor = renderEvent(position + 1);
      }
    }
    // placeEvent() is some function you can call to actually place 
    // the calendar event element on the page
    // The position is the x-position of the current event
    // The columnDivisor is a count of the amount of events sharing this column
    placeEvent(currentEvent, position, columnDivisor);
    return columnDivisor;
  };
  while(true) {
    // render events until we're done
    renderEvent(0);
    if(index >= sortedEvents.length) {
      break;
    }
  }
}

本质上,这个特定算法的想法是,如果列表中的 nextEvent 存在并且该事件与 currentEvent 相交,那么我们需要拆分 currentEvent 的宽度。它继续递归,直到它找不到更多的交叉点,然后它会返回递归调用链。我跳过了实际的 DOM 操作逻辑,因为真正困难的部分是确定需要拆分多少实际列才能使这些事件适合。所以希望这一切都有意义。

编辑:

更清楚地说,为了将其添加到您现有的代码中,我将insertEvent用类似的东西替换您的函数。我不会为你写下所有的逻辑,所以你必须自己写一些。但这只是乐趣的一半:-)。

function insertEvent(start, end) {
  var newEvent = Calendar.new(start, end);
  // you'll have to store the array somewhere.
  // i'm just assuming some kind of global right now
  eventsArray.push(newEvent);
  // You'll want to destroy any event elements
  destroyCurrentEventElements();
  // Now run the rendering function
  renderCalendarEvents(eventsArray);
}
于 2013-01-25T22:55:47.837 回答