1

月表示と日表示を含むJavascript/jQueryカレンダーに取り組んでいます。日をクリックすると日付が変更され、日ビューの日付変数が更新されます。

日ビューは、深夜から午後11時までの30分セグメントに分割されます。30分<tr>(日ビューはテーブル)をクリックすると、クリックされた時間から将来の1時間までの間にイベントが作成されdiv、カレンダーの上部に、時間の範囲にまたがり、正しい開始位置に配置されたイベントが追加されます。ポイント(各ピクセルは1分です...)

ただし、問題があります。すでに存在する特定の期間の間に「イベント」を作成すると、それらは重複します。これは明らかにデフォルトの動作ですが、私がやりたいのは、イベントがすでに占有されている日付の範囲の間にイベントが作成された場合、それらが重ならないように並べて配置することです。

これは、Mac用のiCalアプリで見られる動作に似ています。 ここに画像の説明を入力してください

このような目標を達成するための最初の考えは、衝突検出を使用することでしたが、このためのすべてのjQueryプラグインは肥大化しているか、要素をドラッグ可能にする必要があります。

次に、CSSでこれを行う方法があるのではないかと思いました。2つの要素が重なっている場合、それらは幅を均等に分割します。

それから、それはばかばかしいほど遠いものだと思ったので、どうすればこれをできるだけ簡単に達成できるのだろうかと思います。

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()形式の2つのパラメーターを取ります。1つは開始日用、もう1つは終了日用です。

フィドル: http: //jsfiddle.net/charlescarver/HwdwL/

4

1 に答える 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;
};

データは並べ替えられているため、2つ以上のカレンダーイベントが交差する場合は、スペースを共有する必要があることがわかります。確かに、スペースを分割するときは、いくつかのことを考える必要があります。スペースを左から右に均等に共有する(開始時間が最も早い左)という単純な実装が必要ですか。その場合、スペースを共有する4つのイベントがある場合(各ブロックはイベントです)、視覚的表現は次のようになります。

ナイーブレンダリング

ただし、イベントの形がおかしい場合は、カレンダーがおかしくなる可能性があります。次のことを考慮してください。

考えられるシナリオ

この場合、イベント2は多くの垂直方向のスペースを占有し、イベント1の下のスペースはすべて未使用です。たぶん、より良いUXのために、そのようなことが起こらないようにする必要があります。その場合は、それに応じてレンダリングアルゴリズムを設計する必要があります。発生したすべての変更で再レンダリングするのがおそらく最も簡単であることを覚えておいてください。ただし、それはデータの保存方法がすべてです。簡単に移動できるような構造でデータを保存しないと、このようなことはできません。

しかし、あなたの質問への答えを完成させるために、ここにかなり素朴な例があります。私はそれをテストしていないので、これはそれが機能しているというかなり大きな仮定です。完全ではありませんが、自分でレンダリングを編集する必要があります。これは、それを機能させる方法のアイデアを提供するためだけのものです。それは間違いなくきれいに見えるかもしれません:

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 に答える