【问题标题】:How can I determine if value from array lies between values at all parallel indices of two additional arrays?如何确定数组中的值是否位于两个附加数组的所有并行索引的值之间?
【发布时间】:2015-02-01 22:23:55
【问题描述】:

我有四个数组,具有以下角色:

  1. 所有现有的开始日期(未选中)
  2. 所有现有的结束日期(未选中)
  3. 所选项目的开始日期(选中)
  4. 所选项目结束日期(选中)

1 和 2 包含集合中所有未选择事件的开始和结束时间戳池。

当与事件相关的复选框被选中时,3 和 4 将填充开始和结束时间戳,当它们被取消选中时,时间戳被删除。

结果应该是,如果未选中项的日期范围与新选中项的日期范围冲突,则阻止它们被选中并直观地显示它们是禁用的选项。

我目前将所有未选中项的值与比较数组中的最近检查日期进行比较,但它忽略了之前的值。这意味着我可以选择一个,冲突的日期被禁用,但是当我选择另一个可用的选项时,以前禁用的日期被重新启用。

我不是 100% 确定如何确保将所有未选中的项目与所有选中的项目进行比较,并根据日期是否冲突来禁用。在现有循环中添加嵌套 for 循环是解决此问题的最佳方法吗?

附言我知道有一些奇怪的选择器和额外的工作来格式化日期,但我这样做的人坚持认为日期格式是 MON ## - ##(同月)和 MON ## - MON ##(不同月)和日期包含在复选框标签中的一个小标签中。

    var camp_dates;
    var camp_start;
    var camp_end;
    var other_camp_dates;
    var other_camp_start;
    var other_camp_end;
    var checked_start = [];
    var checked_end = [];
    var year = new Date().getFullYear();

    // Change checkbox apply filters
    $("#gform_11 input[type=checkbox]").click(function(){
        //Reset list of checked items
        checked_start = [];
        checked_end = [];

        camp_dates = $("label[for='" + $(this).attr("id") + "'] small").html().split("-");

        camp_start = camp_dates[0].split(" ");

        camp_end = camp_dates[1].split(" ");

        //If no month in end date, assume same month as first (Ex. Jul 06-22 == Jul 06 - Jul 22)
        if (camp_end.length == 1){
            camp_end.unshift(camp_start[0]);
        }

        //rejoin day and months, add year to parse for timestamp
        camp_start = Date.parse(camp_start.join(" ") + ", " + year);
        camp_end = Date.parse(camp_end.join(" ") + ", " + year);

        //Take empty start and arrays and add dates for selections
        $(".gfield_checkbox input:checked").each(function(){
            //All currently checked items
            checked_start.push(camp_start);
            checked_end.push(camp_end);
        });

        $(".gfield_checkbox input:not(:checked) + label small").each(function(){

            //Gen values for all unselected items
            other_camp_dates = $(this).html().split("-");
            other_camp_start = other_camp_dates[0].split(" ");
            other_camp_end = other_camp_dates[1].split(" ");

            //If no month in end date, assume same month as first
            if (other_camp_end.length == 1){
                other_camp_end.unshift(other_camp_start[0]);
            }

            //rejoin day and months, add year to parse for timestamp
            other_camp_start = Date.parse(other_camp_start.join(" ") + ", " + year);
            other_camp_end = Date.parse(other_camp_end.join(" ") + ", " + year);

            // Loop through arrays of start/end dates and compare to each unselected item - apply fade, disable, color
            var i;
            for (i = 0; i < checked_start.length; i++) {
                if ( other_camp_start >= checked_start[i] && other_camp_start < checked_end[i] ||
                    other_camp_end > checked_start[i] && other_camp_end <= checked_end[i] ){
                    // If there is conflict
                    $(this).css("color", "red").parent().fadeTo("slow",0.5).siblings("input").not(":checked").attr("disabled", true);
                } else {
                    $(this).css("color", "#7E7E7E").parent().fadeTo("slow",1).siblings("input").attr("disabled", false);
                }
            }
        });
    });

http://codepen.io/wjramos/pen/BywyRY

【问题讨论】:

  • 查看您的 HTML 会有所帮助
  • 更好的是,您认为您可以创建一个我们可以使用的JS fiddle demo 吗?示例日期和结构可以帮助我们更好地理解问题?
  • 在原帖中包含一支笔,将其剥离到最低限度
  • 顺便说一句 - 你应该真正满足跨新年的开始范围,例如 12 月 29 日至 1 月 1 日。可能不太可能......但可能。
  • 是的,这听起来可能很混乱。如果由于年份换行而导致结束日期小于开始日期,那么在结束时间戳上简单地添加一年(31,536,000 秒)是否有意义?

标签: javascript jquery arrays loops for-loop


【解决方案1】:

如果我理解正确,这比代码所暗示的要简单。

首先,可以通过编写parseDates() 函数来消除用于解析日期的重复代码,该函数:

  • 返回具有.start.end 属性的对象。
  • 可用作.map() 回调,一次用于选中复选框,一次用于未选中复选框。

然后,剩下要做的就是检查所有未检查日期与嵌套循环中的所有已检查日期,并管理未检查项目的禁用状态。这样做的一个主要因素是只有在所有禁用的项目都已知时才重新启用项目 - 即在两个嵌套循环完成之后。

代码应该是这样的:

// Change checkbox apply filters
$("#gform_11 input[type=checkbox]").click(function(){
    var year = new Date().getFullYear();

    //A utility function for parsing out start and end dates
    function parseDates() {
        var dates = $(this).html().split("-"),
            start = dates[0].split(" "),
            end = dates[1].split(" ");
        //If no month in end date, assume same month as first (Ex. Jul 06-22 == Jul 06 - Jul 22)
        if (end.length == 1) {
            end.unshift(start[0]);
        }
        //return an object with .start and .end properties
        return {
            start: Date.parse(start.join(" ") + ", " + year), //rejoin 
            end: Date.parse(end.join(" ") + ", " + year) //rejoin 
        };
    }

    //A utility function for comparing a checked date with an unchecked date
    function compareDateRanges(checked, unchecked) {
        return ( unchecked.start >= checked.start && unchecked.start < checked.end ) ||
            ( unchecked.end > checked.start && unchecked.end <= checked.end )
    }

    var $checked = $(".gfield_checkbox input:checked");
    var $unchecked = $(".gfield_checkbox input:not(:checked)").removeClass('disabled');

    var checkedDates = $checked.siblings("label").find("small").map(parseDates).get();//make array of start-end objects for checked inputs
    var uncheckedDates = $unchecked.siblings("label").find("small").map(parseDates).get();//make array of start-end objects for unchecked inputs

    for(var i=0; i<checkedDates.length; i++) {
        for(var j=0; j<uncheckedDates.length; j++) {
            if(compareDateRanges(checkedDates[i], uncheckedDates[j])) {
                // If there is conflict
                $unchecked.eq(j).addClass('disabled').attr('disabled', true).siblings("label").find("small").css('color', 'red').parent().fadeTo('slow', 0.5);
            }
        }
    }
    //when all disabled elements are known, all others can be eneabled.
    $unchecked.not(".disabled").attr('disabled', false).siblings("label").find("small").css('color', '#7E7E7E').parent().fadeTo('slow', 1);
});

Demo

编辑 1

为了满足可能跨越新一年的日期范围:

//A utility function for parsing out start and end dates
function parseDates() {
    var dates = $(this).siblings("label").find("small").html().split("-"),
        start = dates[0].split(" "),
        end = dates[1].split(" ");
    //If no month in end date, assume same month as first (Ex. Jul 06-22 == Jul 06 - Jul 22)
    if (end.length == 1) {
        end.unshift(start[0]);
    }
    var obj = {
        start: Date.parse(start.join(" ") + ", " + year), //rejoin 
        end: Date.parse(end.join(" ") + ", " + year) //rejoin 
    }
    // Test for the date range spanning a New Year.
    // If necessary, reparse `end` with next year's date
    if(obj.end < obj.start) {
        obj.end = Date.parse(end.join(" ") + ", " + (year + 1));
    }
    //return an object with .start and .end properties
    return obj;
}

编辑 2

要在页面加载时执行,请触发第一个复选框的点击处理程序:

$("#gform_11 input[type=checkbox]").click(function() {
    ... ...
}).eq(0).triggerHandler('click');

第一个复选框是否被选中并不重要,因为无论元素的 :checked 状态如何,所有内容都已计算完毕。

【讨论】:

  • 谢谢,我花了一点时间来消化它,但这是一个聪明的解决方案,我可以从中学到很多东西。简洁并实现了我以前从未使用过的 JS 的特性
  • 非常聪明的方法。
  • 一旦您对.map(parseDates).get() 有所了解,剩下的就非常简单了......并且旨在保持原始代码的淡入淡出。
  • 触发点击事件完美!我还无法确定跨新年的日期是否有效。 parseDates 函数中似乎也有一个错字,因为 .siblings("label").find("small") 不起作用,因为该函数是在您的原始代码中调用的,例如 .siblings("label").find("small").map(parseDates).get();。在函数内部还是外部进行遍历更有意义?
  • 对不起,我忘了说,我把.siblings("label").find("small")移到了函数内部,因为它在两个调用中都是一样的,它们变成了-var checkedDates = $checked.map(parseDates).get();var uncheckedDates = $unchecked.map(parseDates).get();
【解决方案2】:

您启用/禁用复选框的方式似乎有问题。您正在遍历每个未选择的项目,如果它们与选定的项目冲突,则禁用它们,如果没有,则启用它们。

这是一个问题的原因:假设您有一个未选中的日期与已选中的日期冲突。您禁用未选中的。

但稍后在循环中,当您与另一个检查它并且没有冲突时,您重新启用它。如果它在循环中的某处被禁用,它应该保持禁用状态。

另一个问题:如果您有评论“//Take empty start and arrays and add dates for selections”,您会一遍又一遍地推送相同的值(camp_startcamp_end),而没有获得每个选定的日期。

这是一个修订版:

jQuery(document).ready(function($){

    var camp_dates,
        other_camp_dates,
        checked_dates = [],
        year = new Date().getFullYear();

    // Change checkbox apply filters
    $("#gform_11 input[type=checkbox]").click(function(){
        //Reset list of checked items
        checked_dates = [];

        camp_dates = formatDates( $("label[for='" + $(this).attr("id") + "'] small").html() );

        //Take empty checked_dates and add dates for selections
        $(".gfield_checkbox input:checked + label small").each(function(){
            //All currently checked items
            checked_dates.push( formatDates( $(this).html() ) );
        });

        // For each unchecked item
        $(".gfield_checkbox input:not(:checked) + label small").each(function(){
            //Get the dates
            other_camp_dates = formatDates( $(this).html() );

            // Enable the checkbox before matching it with all checked_dates
            // If we don't do that now, imagine we have a date that conflicts (we disable it),
            // and the next one does not conflict : we reenable it. Not what we want.
            $(this).css("color", "#7E7E7E").parent().css("opacity",1).siblings("input").attr("disabled", false);

            // Loop through arrays of checked_dates and compare to the current unchecked item
            var i, l = checked_dates.length;
            for (i = 0; i<l; i++) {
                if ( other_camp_dates.start >= checked_dates[i].start && other_camp_dates.start < checked_dates[i].end ||
                    other_camp_dates.end > checked_dates[i].start && other_camp_dates.end <= checked_dates[i].end ){

                    //Conflict
                    $(this).css("color", "red").parent().css("opacity",.5).siblings("input").not(":checked").attr("disabled", true);
                }
                // If there is no conflict for this one, there may be one for a previous one,
                // so we don't enable it here
            }
        });
    });

    // It's messy enough to make it a function and not rewrite it
    function formatDates(str){
        var dates = str.split("-"),
            start_date = dates[0].split(" "),
            end_date = dates[1].split(" ");
        if (end_date.length == 1){
            end_date.unshift(start_date[0]);
        }
        start_date = Date.parse(start_date.join(" ") + ", " + year);
        end_date = Date.parse(end_date.join(" ") + ", " + year);
        return {
            "start" : start_date,
            "end"   : end_date
        };
    }
}); // jQuery(document).ready

JS Fiddle Demo

【讨论】:

  • 谢谢,这非常清楚地解释了为什么我的方法没有给出预期的结果,并且您修改后的示例很简洁,但并没有完全不同。
猜你喜欢
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
  • 2016-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多