如果元素接触,将它们的宽度分成两半?

时间:2013-01-25 00:49:59

标签: javascript jquery css css3

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

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

有问题,然而。如果您在某个时间跨度之间创建了一个“事件”,其中已存在一个“事件”,则它们会重叠。显然,这是默认行为,但我想要发生的事情是,如果在事件已占用的一系列日期之间创建事件,它们会并排排列,以便它们不会重叠。

这类似于iCal app for mac中的行为: enter image description here

现在我首先想到实现这样一个目标就是使用碰撞检测,但是所有的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);

    }

这采用javascript new Date()格式的两个参数,一个用于开始日期,一个用于结束日期。

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

1 个答案:

答案 0 :(得分: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个共享空间的事件(每个块都是一个事件),那么您的视觉表示可能看起来像这样:

naive rendering

但是,如果您的活动形状奇怪,可能会导致您的日历看起来很奇怪。请考虑以下事项:

possible scenario

在这种情况下,事件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);
}