<commit_before>angular.module('feedbackcontrol', [])
.directive('transform', function(FeedbackSignal) {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind('click', function(e) {
                var currentUrl = window.location.pathname;
                var me = $(element);
                var data = FeedbackSignal.data;

                if(currentUrl.match(/inbox\/all|deleted/g)) {
                    if(data.id.length > 0) {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }
                }

                if(currentUrl.match(/published/g)) {

                    if(me.attr('return-policy') == 1) {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }

                    if(data.status == "feature") {
                        var state = {
                            activate: {'background-position': '-64px -31px'}
                          , deactivate_sibling: {'background-position': '-32px 0px'}
                          , activation_color: {'background-color': '#ffffe0'}
                          , state_change_inbox: '{"status": "inbox", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "feature"}'
                          , state_change: '{"status": "publish", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "publish"}'
                          , present_status: "feature"
                        }
                        published_state(me, '.publish', 'publish feedback', state);
                    }

                    if(data.status == "publish") {
                        var state = {
                            activate: {'background-position': '-32px -31px'}
                          , deactivate_sibling: {'background-position': '-64px 0px'}
                          , activation_color: {'background-color': '#fff'}
                          , state_change_inbox: '{"status": "inbox", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "publish"}'
                          , state_change: '{"status": "feature", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "feature"}'
                          , present_status: "publish"
                        }
                        published_state(me, '.feature', 'feature feedback', state);
                    }

                    if(data.status == "delete" || data.status == "fileas") {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }

                }

                if(currentUrl.match(/filed/g)) {

                    var data_status = data.status;

                    if(data_status == "publish" || data_status == "feature" || data_status == "delete") {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }

                    if(data_status == "fileas") {
                        $(me).parents(".dashboard-feedback")
                               .children("div.feedback-contents")
                               .children(".responsive-padding")
                               .children(".feedback-details")
                               .children(".filed")
                               .html($(me).text())
                    }

                }

                e.preventDefault();
            });
        }
    }
})
.directive('undo', function(FeedbackSignal, FeedbackControlService) {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind("click", function(e) {
                $(".checky-box-container").hide();
                if (FeedbackSignal.data.id instanceof Array) {
                    for(var i=0; i < FeedbackSignal.data.id.length ; i++) {
                        var feedback = $(".dashboard-feedback[feedback=" + FeedbackSignal.data.id[i] + "]");
                        feedback.show();
                        $(feedback).parents('.feedback-group').show();
                    }
                } else {
                    var feedback = $(".dashboard-feedback[feedback=" + FeedbackSignal.data.id + "]");
                    feedback.show();
                    $(feedback).parents('.feedback-group').show();

                    var checkbox = $(".feed-checkbox[value=" + FeedbackSignal.data.id + "]");

                    if(checkbox.is(":checked")) {
                        checkbox.click();
                    }
                }

                if($(".sorter-checkbox").is(":checked")) {
                    $(".sorter-checkbox").click();
                }

                if($('.feed-checkbox').is(":checked")) {
                    $('.feed-checkbox').prop("checked", false);
                }

                $(".dashboard-feedback").css({'background-color': '#FFF'});

                FeedbackControlService.expunge();
                e.preventDefault();
            });
        }
    }
})
.directive('close', function(FeedbackControlService) {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind("click", function(e) {
                $(".checky-box-container").hide();
                FeedbackControlService.expunge();
                e.preventDefault();
            });
        }
    }
})
.directive('feedcheckbox', function() {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            var checkbox = $(element);

            checkbox.bind('change', function() {
                if(checkbox.is(":checked")) {
                    checkbox.parents('.dashboard-feedback').css({'background-color': '#F7F7F7'});
                } else {
                    checkbox.parents('.dashboard-feedback').css({'background-color': '#FFF'});
                }
            });

        }
    }
})
.directive('statuschange', function(FeedbackService) {
    return {
        restrict: 'A'
      , scope: {
            feedid: "=feedid"
        }
      , link: function(scope, element, attrs) {
            $(element).bind('change', function() {
                FeedbackService.inline_change($(this).val(), scope.feedid, 'status');
            })
        }
    }
})
.directive('prioritychange', function(FeedbackService) {
    return {
        restrict: 'A'
      , scope: {
            feedid: "=feedid"
        }
      , link: function(scope, element, attrs) {
            $(element).bind('change', function() {
                FeedbackService.inline_change($(this).val(), scope.feedid, 'priority');
            })
        }
    }
})
.directive('deleteBlock', function() {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind('click', function(e) {
                $(this).parents('.custom-att').fadeOut(400, function() {
                     var new_uploaded_images = new Array;
                     var new_attached_link;

                     var div = $(this).parents('.uploaded-images-and-links');
                     var images = div.find('.uploaded_image');
                     var media_attachment = div.find('.attached_link');

                     var remove_image = {
                        'small_url'    :$(this).find('.small-image-url').val(),
                        'medium_url'   :$(this).find('.medium-image-url').val(),
                        'large_url'    :$(this).find('.large-image-url').val()
                     }

                    if(images.length > 0){
                        var us = images.not(":hidden");
                        for(var i=0; i < us.length; i++) {
                            new_uploaded_images.push({
                                'name': $(us[i]).find('.image-name').val()
                            });
                        }
                    }

                    if(!media_attachment.is(":hidden")) {
                        new_attached_link = {
                            title           : media_attachment.find('.link-title').val(),
                            description     : media_attachment.find('.link-description').val(),
                            image           : media_attachment.find('.link-image').val(),
                            url             : media_attachment.find('.link-url').val(),
                            video           : media_attachment.find('.link-video').val(),
                        }
                    }

                    var remaining = {
                        uploaded_images : new_uploaded_images
                      , attached_link   : new_attached_link
                    }

                    var attachment_data = {
                        feedbackId      : div.find('.attachment_feedback_id').val()
                      , attachments     : remaining
                      , remove_image    : remove_image
                    }

                    //place this in service you lazy bastard!
                    $.ajax({
                        type: "POST"
                      , url: "/inbox/update_feedback_attachment"
                      , dataType: "json"
                      , data: attachment_data
                    });
                });
                e.preventDefault();
            })
        }
    }
})
.directive('flag', function() {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind('click', function(e) {
                var policy = $(this).attr("return-policy");
                if(policy == 1) {
                    $(this).removeAttr("style");
                    $(this).attr("return-policy", 0);
                } else {
                    $(this).attr("style", "background-position: -194px -31px");
                    $(this).attr("return-policy", 1);
                }
                e.preventDefault();
            });
        }
    }
})

function published_state(obj, sibling_id, msg, state) {
    //kill me....
    obj.parents('.dashboard-feedback').css(state.activation_color);
    obj.css(state.activate);
    obj.children('.action-tooltip').children('span').html("Return to Inbox");
    obj.siblings(sibling_id).css(state.deactivate_sibling);
    obj.siblings(sibling_id).children('.action-tooltip').children('span').html(msg);
    obj.siblings(sibling_id).attr('return-policy', 0);
    obj.siblings(sibling_id).attr('data-feed', state.state_change);
    obj.attr('return-policy', 1);
    obj.attr('data-feed', state.state_change_inbox);

    var origin_cat_data = obj.siblings('.save')
                               .children('div.the-categories-menu')
                               .children('.the-categories-menu-content')
                               .children(".the-categories")
                               .children(".grids")
                               .children('li')
                               .children('a');

    for(var i=0;i<origin_cat_data.length;i++) {
        var feed = $.parseJSON(origin_cat_data[i]);
        feed.status = "fileas";
        feed.origin = state.present_status

        var repackaged_json = JSON.stringify(feed);
        console.log(repackaged_json);
    }
    /*
    var feed = $.parseJSON(origin_cat_data);
    feed.status = "fileas";
    feed.origin = state.present_status

    var repackaged_json = JSON.stringify(feed);
    obj.siblings('.save')
       .children('div.the-categories-menu')
       .children('.the-categories-menu-content')
       .children(".the-categories")
       .children(".grids")
       .children('li')
       .children('a').attr('data-feed', repackaged_json);
    */
}

function hide_the_children(obj) {
    obj.parents('.dashboard-feedback').hide();
    var child_count = obj.parents('.feedback-group').children('.dashboard-feedback:visible');
    if(child_count.length == 0) {
        obj.parents('.feedback-group').hide();
    }
}
<commit_msg>wqewq<commit_after>angular.module('feedbackcontrol', [])
.directive('transform', function(FeedbackSignal) {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind('click', function(e) {
                var currentUrl = window.location.pathname;
                var me = $(element);
                var data = FeedbackSignal.data;

                if(currentUrl.match(/inbox\/all|deleted/g)) {
                    if(data.id.length > 0) {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }
                }

                if(currentUrl.match(/published/g)) {

                    if(me.attr('return-policy') == 1) {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }

                    if(data.status == "feature") {
                        var state = {
                            activate: {'background-position': '-64px -31px'}
                          , deactivate_sibling: {'background-position': '-32px 0px'}
                          , activation_color: {'background-color': '#ffffe0'}
                          , state_change_inbox: '{"status": "inbox", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "feature"}'
                          , state_change: '{"status": "publish", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "publish"}'
                          , present_status: "feature"
                        }
                        published_state(me, '.publish', 'publish feedback', state);
                    }

                    if(data.status == "publish") {
                        var state = {
                            activate: {'background-position': '-32px -31px'}
                          , deactivate_sibling: {'background-position': '-64px 0px'}
                          , activation_color: {'background-color': '#fff'}
                          , state_change_inbox: '{"status": "inbox", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "publish"}'
                          , state_change: '{"status": "feature", "id": ' + data.id + ', "catid": ' + data.catid + ', "origin": "feature"}'
                          , present_status: "publish"
                        }
                        published_state(me, '.feature', 'feature feedback', state);
                    }

                    if(data.status == "delete" || data.status == "fileas") {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }

                }

                if(currentUrl.match(/filed/g)) {

                    var data_status = data.status;

                    if(data_status == "publish" || data_status == "feature" || data_status == "delete") {
                        hide_the_children(me);
                        $(".checky-box-container").show();
                    }

                    if(data_status == "fileas") {
                        $(me).parents(".dashboard-feedback")
                               .children("div.feedback-contents")
                               .children(".responsive-padding")
                               .children(".feedback-details")
                               .children(".filed")
                               .html($(me).text())
                    }

                }

                e.preventDefault();
            });
        }
    }
})
.directive('undo', function(FeedbackSignal, FeedbackControlService) {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind("click", function(e) {
                $(".checky-box-container").hide();
                if (FeedbackSignal.data.id instanceof Array) {
                    for(var i=0; i < FeedbackSignal.data.id.length ; i++) {
                        var feedback = $(".dashboard-feedback[feedback=" + FeedbackSignal.data.id[i] + "]");
                        feedback.show();
                        $(feedback).parents('.feedback-group').show();
                    }
                } else {
                    var feedback = $(".dashboard-feedback[feedback=" + FeedbackSignal.data.id + "]");
                    feedback.show();
                    $(feedback).parents('.feedback-group').show();

                    var checkbox = $(".feed-checkbox[value=" + FeedbackSignal.data.id + "]");

                    if(checkbox.is(":checked")) {
                        checkbox.click();
                    }
                }

                if($(".sorter-checkbox").is(":checked")) {
                    $(".sorter-checkbox").click();
                }

                if($('.feed-checkbox').is(":checked")) {
                    $('.feed-checkbox').prop("checked", false);
                }

                $(".dashboard-feedback").css({'background-color': '#FFF'});

                FeedbackControlService.expunge();
                e.preventDefault();
            });
        }
    }
})
.directive('close', function(FeedbackControlService) {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind("click", function(e) {
                $(".checky-box-container").hide();
                FeedbackControlService.expunge();
                e.preventDefault();
            });
        }
    }
})
.directive('feedcheckbox', function() {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            var checkbox = $(element);

            checkbox.bind('change', function() {
                if(checkbox.is(":checked")) {
                    checkbox.parents('.dashboard-feedback').css({'background-color': '#F7F7F7'});
                } else {
                    checkbox.parents('.dashboard-feedback').css({'background-color': '#FFF'});
                }
            });

        }
    }
})
.directive('statuschange', function(FeedbackService) {
    return {
        restrict: 'A'
      , scope: {
            feedid: "=feedid"
        }
      , link: function(scope, element, attrs) {
            $(element).bind('change', function() {
                FeedbackService.inline_change($(this).val(), scope.feedid, 'status');
            })
        }
    }
})
.directive('prioritychange', function(FeedbackService) {
    return {
        restrict: 'A'
      , scope: {
            feedid: "=feedid"
        }
      , link: function(scope, element, attrs) {
            $(element).bind('change', function() {
                FeedbackService.inline_change($(this).val(), scope.feedid, 'priority');
            })
        }
    }
})
.directive('deleteBlock', function() {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind('click', function(e) {
                $(this).parents('.custom-att').fadeOut(400, function() {
                     var new_uploaded_images = new Array;
                     var new_attached_link;

                     var div = $(this).parents('.uploaded-images-and-links');
                     var images = div.find('.uploaded_image');
                     var media_attachment = div.find('.attached_link');

                     var remove_image = {
                        'small_url'    :$(this).find('.small-image-url').val(),
                        'medium_url'   :$(this).find('.medium-image-url').val(),
                        'large_url'    :$(this).find('.large-image-url').val()
                     }

                    if(images.length > 0){
                        var us = images.not(":hidden");
                        for(var i=0; i < us.length; i++) {
                            new_uploaded_images.push({
                                'name': $(us[i]).find('.image-name').val()
                            });
                        }
                    }

                    if(!media_attachment.is(":hidden")) {
                        new_attached_link = {
                            title           : media_attachment.find('.link-title').val(),
                            description     : media_attachment.find('.link-description').val(),
                            image           : media_attachment.find('.link-image').val(),
                            url             : media_attachment.find('.link-url').val(),
                            video           : media_attachment.find('.link-video').val(),
                        }
                    }

                    var remaining = {
                        uploaded_images : new_uploaded_images
                      , attached_link   : new_attached_link
                    }

                    var attachment_data = {
                        feedbackId      : div.find('.attachment_feedback_id').val()
                      , attachments     : remaining
                      , remove_image    : remove_image
                    }

                    //place this in service you lazy bastard!
                    $.ajax({
                        type: "POST"
                      , url: "/inbox/update_feedback_attachment"
                      , dataType: "json"
                      , data: attachment_data
                    });
                });
                e.preventDefault();
            })
        }
    }
})
.directive('flag', function() {
    return {
        restrict: 'A'
      , link: function(scope, element, attrs) {
            $(element).bind('click', function(e) {
                var policy = $(this).attr("return-policy");
                if(policy == 1) {
                    $(this).removeAttr("style");
                    $(this).attr("return-policy", 0);
                } else {
                    $(this).attr("style", "background-position: -194px -31px");
                    $(this).attr("return-policy", 1);
                }
                e.preventDefault();
            });
        }
    }
})

function published_state(obj, sibling_id, msg, state) {
    //kill me....
    obj.parents('.dashboard-feedback').css(state.activation_color);
    obj.css(state.activate);
    obj.children('.action-tooltip').children('span').html("Return to Inbox");
    obj.siblings(sibling_id).css(state.deactivate_sibling);
    obj.siblings(sibling_id).children('.action-tooltip').children('span').html(msg);
    obj.siblings(sibling_id).attr('return-policy', 0);
    obj.siblings(sibling_id).attr('data-feed', state.state_change);
    obj.attr('return-policy', 1);
    obj.attr('data-feed', state.state_change_inbox);

    var origin_cat_data = obj.siblings('.save')
                               .children('div.the-categories-menu')
                               .children('.the-categories-menu-content')
                               .children(".the-categories")
                               .children(".grids")
                               .children('li')
                               .children('a');

    for(var i=0;i<origin_cat_data.length;i++) {
        var feed = $.parseJSON(origin_cat_data[i].attr('data-feed'));
        feed.status = "fileas";
        feed.origin = state.present_status
        console.log(feed);

        //var repackaged_json = JSON.stringify(feed);
        //console.log(repackaged_json);
    }
    /*
    var feed = $.parseJSON(origin_cat_data);
    feed.status = "fileas";
    feed.origin = state.present_status

    var repackaged_json = JSON.stringify(feed);
    obj.siblings('.save')
       .children('div.the-categories-menu')
       .children('.the-categories-menu-content')
       .children(".the-categories")
       .children(".grids")
       .children('li')
       .children('a').attr('data-feed', repackaged_json);
    */
}

function hide_the_children(obj) {
    obj.parents('.dashboard-feedback').hide();
    var child_count = obj.parents('.feedback-group').children('.dashboard-feedback:visible');
    if(child_count.length == 0) {
        obj.parents('.feedback-group').hide();
    }
}
<|endoftext|>


<commit_before>importScripts('/scripts/sw/workbox/workbox-sw.js');

workbox.setConfig({
    modulePathPrefix: '/scripts/sw/workbox/'
  });
workbox.skipWaiting();
workbox.clientsClaim();

workbox.precaching.precacheAndRoute([]);

// cache images
workbox.routing.registerRoute(
    /\.(?:png|PNG|gif|GIF|jpg|JPG|jpeg|JPEG|svg|SVG)$/,
    workbox.strategies.cacheFirst({
        cacheName: 'images',
        plugins: [
        new workbox.expiration.Plugin({
            maxEntries: 60,
            maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
        }),
        ],
    }),
);

// cache giphy
workbox.routing.registerRoute(
  'https://media.giphy.com/media/3oz8xBkRsgPTnbK1GM/giphy.gif',
  workbox.strategies.staleWhileRevalidate()
);

// cache pages for one hour
workbox.routing.registerRoute(
    /\/(dashboard|news|courses)\/$/,
    workbox.strategies.networkFirst({
        cacheName: 'pages',
        maxAgeSeconds: 60 * 60,
        networkTimeoutSeconds: 3,
        plugins: [
            new workbox.expiration.Plugin({
              maxEntries: 50,
              maxAgeSeconds: 60 * 60,
            }),
            new workbox.cacheableResponse.Plugin({
              statuses: [0, 200],
            }),
          ],
    })
);

const queue = new workbox.backgroundSync.Queue('logsQueue');

function customHeaderRequestFetch(event) {
    return new Promise((resolve, reject) =>{
        event.request.blob().then(blob =>{
            const newRequest = new Request(event.request.url, {
                headers: {
                    'sw-enabled': true
                },
                method: 'POST',
                body: blob
            });
            resolve(fetch(newRequest.clone()) | queue.addRequest(newRequest));
        });
    });
}

self.addEventListener('fetch', event => {
    if (event.request.url.endsWith('/logs/')){
        event.respondWith(customHeaderRequestFetch(event));
    }
});

// calendar events
// https://developers.google.com/web/tools/workbox/modules/workbox-broadcast-cache-update
workbox.routing.registerRoute(
    '/calendar/events/',
    workbox.strategies.staleWhileRevalidate({
        plugins: [
            new workbox.broadcastUpdate.Plugin('event-updates')
        ]
    })
);
<commit_msg>send response on cache<commit_after>importScripts('/scripts/sw/workbox/workbox-sw.js');

workbox.setConfig({
    modulePathPrefix: '/scripts/sw/workbox/'
  });
workbox.skipWaiting();
workbox.clientsClaim();

workbox.precaching.precacheAndRoute([]);

// cache images
workbox.routing.registerRoute(
    /\.(?:png|PNG|gif|GIF|jpg|JPG|jpeg|JPEG|svg|SVG)$/,
    workbox.strategies.cacheFirst({
        cacheName: 'images',
        plugins: [
        new workbox.expiration.Plugin({
            maxEntries: 60,
            maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
        }),
        ],
    }),
);

// cache giphy
workbox.routing.registerRoute(
  'https://media.giphy.com/media/3oz8xBkRsgPTnbK1GM/giphy.gif',
  workbox.strategies.staleWhileRevalidate()
);

// cache pages for one hour
workbox.routing.registerRoute(
    /\/(dashboard|news|courses)\/$/,
    workbox.strategies.networkFirst({
        cacheName: 'pages',
        maxAgeSeconds: 60 * 60,
        networkTimeoutSeconds: 3,
        plugins: [
            new workbox.expiration.Plugin({
              maxEntries: 50,
              maxAgeSeconds: 60 * 60,
            }),
            new workbox.cacheableResponse.Plugin({
              statuses: [0, 200],
            }),
          ],
    })
);

const queue = new workbox.backgroundSync.Queue('logs');

function customHeaderRequestFetch(event) {
    let newRequest;
    return new Promise((resolve, reject) => {
        event.request.blob().then(blob => {
            newRequest = new Request(event.request.url, {
                headers: {
                    'sw-enabled': true
                },
                method: 'POST',
                body: blob
            });
            resolve(fetch(newRequest.clone())
            .catch(_ =>
                queue.addRequest(newRequest)
                    .then(_ =>
                        new Response('cached')
                    )
                )
            );
        });
    });
}

self.addEventListener('fetch', event => {
    if (event.request.url.endsWith('/logs/')){
        event.respondWith(customHeaderRequestFetch(event));
    }
});

// calendar events
// https://developers.google.com/web/tools/workbox/modules/workbox-broadcast-cache-update
workbox.routing.registerRoute(
    '/calendar/events/',
    workbox.strategies.staleWhileRevalidate({
        plugins: [
            new workbox.broadcastUpdate.Plugin('event-updates')
        ]
    })
);
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "c9", "settings", "ui", "menus", "ace",
        "ace.gotoline", "tabManager"
    ];
    main.provides = ["ace.status"];
    return main;

    function main(options, imports, register) {
        var c9        = imports.c9;
        var Plugin    = imports.Plugin;
        var settings  = imports.settings;
        var ui        = imports.ui;
        var tabs      = imports.tabManager;
        var menus     = imports.menus;
        var gotoline  = imports["ace.gotoline"];
        var aceHandle = imports.ace;

        var skin    = require("text!./skin.xml");
        var markup  = require("text!./statusbar.xml");
        var menuAml = require("text!./menu.xml");

        var aceWhitespace = require("ace/ext/whitespace");
        var lang = require("ace/lib/lang");

        /***** Generic Load *****/

        // Set up the generic handle
        var deps       = main.consumes.slice(0, main.consumes.length - 1);
        var handle     = new Plugin("Ajax.org", deps);
        var handleEmit = handle.getEmitter();

        var statusbars = {};
        var menuItem, menu, menuTabs;

        handle.on("load", function(){
            settings.on("read", function(e){
                settings.setDefaults("user/ace/statusbar", [["show", "true"]]);

                if (settings.getBool("user/ace/statusbar/@show"))
                    handleEmit("show");
            }, handle);

            menuItem = new apf.item({
                test : "1",
                type : "check",
                checked : "[{settings.model}::user/ace/statusbar/@show]"
                // -> if you're looking for disabled, check the init function :-)
                // the moment that someone clicks this thing well call preinit
                // (its already called if the user has it checked on IDE load)
            });

            menus.addItemByPath("View/Status Bar", menuItem, 600, handle);

            aceHandle.on("create", function(e){
                if (e.editor.type != "ace")
                    return;

                var editor = e.editor;
                var statusbar;

                editor.on("draw", function(){
                    statusbar = new Statusbar(editor);
                }, editor);
                editor.on("unload", function h2(){
                    if (statusbar) statusbar.unload();
                }, editor);
            });

            ui.insertMarkup(null, menuAml, handle);

            menu     = handle.getElement("menu");
            menuTabs = handle.getElement("menuTabs");

            var currentSession;
            function setCurrentSession(menu){
                var node = menu.opener;
                while (node && node.localName != "tab")
                    node = node.parentNode;
                if (!node) return;

                var tab = node.cloud9pane.getTab();
                currentSession = tab.document.getSession();
            }

            function setOption(name, value){
                if (currentSession) {
                    currentSession.session.setOption(name, value);
                    currentSession.statusBar.update();
                }
            }

            function getOption(name){
                return currentSession && currentSession.session.getOption(name);
            }

            // Checkboxes
            menu.on("afterrender", function(e){
                var itmSbWrap   = window.itmSbWrap;
                var itmSbWrapVP = window.itmSbWrapVP;

                itmSbWrap.on("click", function(){
                    setOption("wrap", itmSbWrap.checked
                        ? itmSbWrapVP.checked || "printMargin"
                        : false);
                });
                itmSbWrapVP.on("click", function(){
                    setOption("wrap", itmSbWrap.checked
                        ? itmSbWrapVP.checked || "printMargin"
                        : false);
                });

                function update(e){
                    if (!e || e.value){
                        setCurrentSession(menu);

                        var wrap = getOption("wrap");
                        itmSbWrap.setAttribute("checked", !ui.isFalse(wrap));
                        itmSbWrapVP.setAttribute("checked", wrap != "printMargin");
                    }
                }

                menu.on("prop.visible", update);
                update();
            });

            // Menu Tab functionality
            var handlers = [
                function(){ setOption("useSoftTabs", this.checked) },
                function(){},
                function(){ setOption("tabSize", 2) },
                function(){ setOption("tabSize", 3) },
                function(){ setOption("tabSize", 4) },
                function(){ setOption("tabSize", 8) },
                function(){
                    var tab = tabs.focussedTab;
                    if (!tab) return;
                    var session = tab.document.getSession();
                    aceWhitespace.detectIndentation(session.session);
                    var useSoftTabs = session.session.getOption("useSoftTabs");
                    var tabSize     = session.session.getOption("tabSize");
                    menuTabs.childNodes[0].setAttribute("checked", useSoftTabs);
                    if (tabSize < 9)
                        menuTabs.childNodes[tabSize + 1].setAttribute("selected", true);
                    session.statusBar.update();
                },
                // Tabs to Spaces
                function(){
                    var tab = tabs.focussedTab;
                    if (!tab) return;
                    var session = tab.document.getSession();
                    aceWhitespace.convertIndentation(session.session, " ");
                    session.statusBar.update();
                },
                // Spaces to Tabs
                function(){
                    var tab = tabs.focussedTab;
                    if (!tab) return;
                    var session = tab.document.getSession();
                    aceWhitespace.convertIndentation(session.session, "\t");
                    session.statusBar.update();
                }
            ];

            menuTabs.on("afterrender", function(e){
                var items = menuTabs.selectNodes("a:item");
                items.forEach(function(node, idx){
                    node.on("click", handlers[idx]);
                });

                var itmTabSize  = window.itmTabSize;
                itmTabSize.on("afterchange", function(){
                    setOption("tabSize", this.value);
                    update();
                });

                var lut = [0,0,2,3,4,0,0,0,5];

                function update(e){
                    if (e && !e.value)
                        return;

                    setCurrentSession(menuTabs);

                    items[0].setAttribute("checked", getOption("useSoftTabs"));

                    var tabSize = getOption("tabSize") || 1;
                    items.forEach(function(node, idx){
                        node.setAttribute("selected", "false");
                    });
                    if (lut[tabSize])
                        items[lut[tabSize]].setAttribute("selected", "true");
                    itmTabSize.setAttribute("value", getOption("tabSize"));
                }

                menuTabs.on("prop.visible", update);
                update();
            });
        });

        /***** Methods *****/

        var drawn = false;
        function draw(){
            if (drawn) return;
            drawn = true;

            // Import Skin
            ui.insertSkin({
                name         : "c9statusbar",
                data         : skin,
                "media-path" : options.staticPrefix + "/images/",
                "icon-path"  : options.staticPrefix + "/icons/"
            }, handle);
        };

        function getStatusbar(editor){
            return statusbars[editor.name];
        }

        function show(){
            settings.set("user/ace/statusbar/@show", "true");
        }

        function hide(){
            settings.set("user/ace/statusbar/@show", "false");
        }

        /***** Register and define API *****/

        /**
         * Manages the status bar instances for ace.
         * @singleton
         **/
        handle.freezePublicAPI({
            /**
             * Show all the status bars.
             */
            show : show,

            /**
             * Hide all the status bars.
             */
            hide : hide,

            /**
             * Retrieve the status bar that belongs to an ace editor.
             * @param {Editor} ace  The ace editor the status bar belongs to.
             * @return {ace.status.Statusbar}
             */
            getStatusbar : getStatusbar,

            /**
             * Inserts CSS for the statusbar.
             * @private
             */
            draw : draw
        });

        /***** Initialization *****/

        var counter = 0;

        function Statusbar(editor){
            var plugin = new Plugin("Ajax.org", deps);
            var emit   = plugin.getEmitter();

            var showRange;

            var bar, lblSelection, lblStatus, lblRowCol, lblTabs, lblSyntax; // ui elements

            statusbars[editor.name] = plugin;

            var loaded = false;
            function load(){
                if (loaded) return false;
                loaded = true;

                function updateBarVisible(){
                    if (!settings.getBool("user/ace/statusbar/@show")) {
                        bar && bar.hide();
                        menuItem.enable();
                    }
                    else {
                        draw();
                        bar.show();
                        menuItem.enable();
                    }
                }

                settings.on("user/ace/statusbar", updateBarVisible, plugin);

                handle.on("show", show);

                if (settings.getBool("user/ace/statusbar/@show"))
                    draw();

                editor.on("documentLoad", function(e){
                    var session = e.doc.getSession();
                    session.statusBar = plugin;
                    session.session.on("changeMode", function(e){ statusUpdate.schedule(); });
                }, plugin);
                editor.on("documentActivate", function(e){ statusUpdate.schedule(); }, plugin);
                editor.on("documentUnload", function(e){
                    delete e.doc.getSession().statusBar;
                }, plugin);
            }

            var drawn = false;
            function draw(){
                if (drawn) return;
                drawn = true;

                handle.draw();

                // Create UI elements
                var htmlNode = editor.aml; //ace.container.parentNode.host;
                ui.insertMarkup(htmlNode, markup, plugin);

                function setTheme(e){
                    var theme    = e.theme;
                    if (!theme) return;

                    var cssClass = theme.cssClass;
                    var isDark   = theme.isDark;

                    var bg = ui.getStyleRule("." + cssClass, "backgroundColor");

                    bar.setAttribute("class", isDark ? "ace_dark" : "");
                    if (bg) {
                        // bg = bg.replace(/rgb\((.*)\)/, "rgba($1, 0.9)");
                        bar.$ext.style.backgroundColor = bg;
                    }
                }
                editor.on("themeChange", setTheme);

                bar          = plugin.getElement("bar");
                lblSelection = plugin.getElement("lblSelectionLength");
                lblStatus    = plugin.getElement("lblEditorStatus");
                lblRowCol    = plugin.getElement("lblRowCol");
                lblTabs      = plugin.getElement("lblTabs");
                lblSyntax    = plugin.getElement("lblSyntax");

                // For editor search of submenus
                bar.editor = editor;

                // Set sub menus
                var button = plugin.getElement("btnSbPrefs");
                button.setAttribute("submenu", menu);

                lblTabs.setAttribute("submenu", menuTabs);

                var mnuSyntax = menus.get("View/Syntax").menu;
                lblSyntax.setAttribute("submenu", mnuSyntax);
                lblSyntax.on("mousedown", function(){
                    if (editor.activeDocument)
                        tabs.focusTab(editor.activeDocument.tab);
                });

                // Click behavior for the labels
                lblSelection.on("click", function(){
                    showRange = !showRange;
                    updateStatus();
                });

                lblRowCol.on("click", function(){
                    gotoline.gotoline(null, null, true);
                });

                // Hook into ace
                var ace = editor.ace;
                if (!ace.$hasStatusBar) {
                    // Throttle UI updates
                    ace.on("changeSelection", function() { selStatusUpdate.schedule() });
                    ace.on("changeStatus", function() { statusUpdate.schedule() });
                    ace.renderer.on("scrollbarVisibilityChanged", function(e, renderer) {
                        bar.$ext.style.right = renderer.scrollBarV.getWidth() + 5 + "px";
                        bar.$ext.style.bottom = renderer.scrollBarH.getHeight() + 3 + "px";
                    })(null, ace.renderer);
                    ace.$hasStatusBar = true;

                    var theme = editor.theme;
                    setTheme({theme: theme});
                }

                // Update status information
                updateStatus();

                emit("draw");
            }

            var selStatusUpdate = lang.delayedCall(updateSelStatus, 10);
            var statusUpdate    = lang.delayedCall(updateStatus, 10);

            /***** Helper Functions *****/

            function updateSelStatus(){
                var ace = editor.ace;
                if (!ace) return;

                if (!ace.selection.isEmpty()) {
                    var range = ace.getSelectionRange();
                    var selLen;

                    if (showRange) {
                        selLen = "(" +
                            (range.end.row - range.start.row) + ":" +
                            (range.end.column - range.start.column) + ")";
                    }
                    else {
                        selLen = "(" + ace.session.getTextRange(range).length + " Bytes)";
                    }

                    lblSelection.setAttribute("caption", selLen);
                }
                else {
                    lblSelection.setAttribute("caption", "");
                }

                var cursor = ace.selection.lead;
                lblRowCol.setAttribute("caption", (cursor.row + 1) + ":" + (cursor.column + 1));
            }

            function updateStatus() {
                var ace = editor.ace;
                if (!ace) return;

                updateSelStatus();

                lblTabs.setAttribute("caption",
                    (ace.getOption("useSoftTabs") ? "Spaces" : "Tabs") + ": "
                      + ace.getOption("tabSize")); // "\\[" + + "\\]");

                var status = "";
                if (ace.$vimModeHandler)
                    status = ace.$vimModeHandler.getStatusText();
                else if (ace.commands.recording)
                    status = "REC";

                lblStatus.setAttribute("caption", status);

                var caption = aceHandle.getSyntaxCaption(ace.session.syntax);
                lblSyntax && lblSyntax.setAttribute("caption", caption);
            }

            /***** Lifecycle *****/

            plugin.on("load", function(){
                load();
            });
            plugin.on("enable", function(){

            });
            plugin.on("disable", function(){

            });
            plugin.on("unload", function(){
                loaded = false;
            });

            /***** Register and define API *****/

            /**
             * The status bar for ace editors.
             * @class ace.status.Statusbar
             **/
            plugin.freezePublicAPI({
                /**
                 * Redraw the display of the statusbar
                 */
                update : updateStatus
            });

            plugin.load("acestatus" + counter++);

            return plugin;
        }

        register(null, {
            "ace.status" : handle
        });
    }
});<commit_msg>Added alternative bindings<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "Plugin", "c9", "settings", "ui", "menus", "ace",
        "ace.gotoline", "tabManager"
    ];
    main.provides = ["ace.status"];
    return main;

    function main(options, imports, register) {
        var c9        = imports.c9;
        var Plugin    = imports.Plugin;
        var settings  = imports.settings;
        var ui        = imports.ui;
        var tabs      = imports.tabManager;
        var menus     = imports.menus;
        var gotoline  = imports["ace.gotoline"];
        var aceHandle = imports.ace;

        var skin    = require("text!./skin.xml");
        var markup  = require("text!./statusbar.xml");
        var menuAml = require("text!./menu.xml");

        var aceWhitespace = require("ace/ext/whitespace");
        var lang = require("ace/lib/lang");

        /***** Generic Load *****/

        // Set up the generic handle
        var deps       = main.consumes.slice(0, main.consumes.length - 1);
        var handle     = new Plugin("Ajax.org", deps);
        var handleEmit = handle.getEmitter();

        var statusbars = {};
        var menuItem, menu, menuTabs;

        handle.on("load", function(){
            settings.on("read", function(e){
                settings.setDefaults("user/ace/statusbar", [["show", "true"]]);

                if (settings.getBool("user/ace/statusbar/@show"))
                    handleEmit("show");
            }, handle);

            menuItem = new apf.item({
                test : "1",
                type : "check",
                checked : "user/ace/statusbar/@show"
                // -> if you're looking for disabled, check the init function :-)
                // the moment that someone clicks this thing well call preinit
                // (its already called if the user has it checked on IDE load)
            });

            menus.addItemByPath("View/Status Bar", menuItem, 600, handle);

            aceHandle.on("create", function(e){
                if (e.editor.type != "ace")
                    return;

                var editor = e.editor;
                var statusbar;

                editor.on("draw", function(){
                    statusbar = new Statusbar(editor);
                }, editor);
                editor.on("unload", function h2(){
                    if (statusbar) statusbar.unload();
                }, editor);
            });

            ui.insertMarkup(null, menuAml, handle);

            menu     = handle.getElement("menu");
            menuTabs = handle.getElement("menuTabs");

            var currentSession;
            function setCurrentSession(menu){
                var node = menu.opener;
                while (node && node.localName != "tab")
                    node = node.parentNode;
                if (!node) return;

                var tab = node.cloud9pane.getTab();
                currentSession = tab.document.getSession();
            }

            function setOption(name, value){
                if (currentSession) {
                    currentSession.session.setOption(name, value);
                    currentSession.statusBar.update();
                }
            }

            function getOption(name){
                return currentSession && currentSession.session.getOption(name);
            }

            // Checkboxes
            menu.on("afterrender", function(e){
                var itmSbWrap   = window.itmSbWrap;
                var itmSbWrapVP = window.itmSbWrapVP;

                itmSbWrap.on("click", function(){
                    setOption("wrap", itmSbWrap.checked
                        ? itmSbWrapVP.checked || "printMargin"
                        : false);
                });
                itmSbWrapVP.on("click", function(){
                    setOption("wrap", itmSbWrap.checked
                        ? itmSbWrapVP.checked || "printMargin"
                        : false);
                });

                function update(e){
                    if (!e || e.value){
                        setCurrentSession(menu);

                        var wrap = getOption("wrap");
                        itmSbWrap.setAttribute("checked", !ui.isFalse(wrap));
                        itmSbWrapVP.setAttribute("checked", wrap != "printMargin");
                    }
                }

                menu.on("prop.visible", update);
                update();
            });

            // Menu Tab functionality
            var handlers = [
                function(){ setOption("useSoftTabs", this.checked) },
                function(){},
                function(){ setOption("tabSize", 2) },
                function(){ setOption("tabSize", 3) },
                function(){ setOption("tabSize", 4) },
                function(){ setOption("tabSize", 8) },
                function(){
                    var tab = tabs.focussedTab;
                    if (!tab) return;
                    var session = tab.document.getSession();
                    aceWhitespace.detectIndentation(session.session);
                    var useSoftTabs = session.session.getOption("useSoftTabs");
                    var tabSize     = session.session.getOption("tabSize");
                    menuTabs.childNodes[0].setAttribute("checked", useSoftTabs);
                    if (tabSize < 9)
                        menuTabs.childNodes[tabSize + 1].setAttribute("selected", true);
                    session.statusBar.update();
                },
                // Tabs to Spaces
                function(){
                    var tab = tabs.focussedTab;
                    if (!tab) return;
                    var session = tab.document.getSession();
                    aceWhitespace.convertIndentation(session.session, " ");
                    session.statusBar.update();
                },
                // Spaces to Tabs
                function(){
                    var tab = tabs.focussedTab;
                    if (!tab) return;
                    var session = tab.document.getSession();
                    aceWhitespace.convertIndentation(session.session, "\t");
                    session.statusBar.update();
                }
            ];

            menuTabs.on("afterrender", function(e){
                var items = menuTabs.selectNodes("a:item");
                items.forEach(function(node, idx){
                    node.on("click", handlers[idx]);
                });

                var itmTabSize  = window.itmTabSize;
                itmTabSize.on("afterchange", function(){
                    setOption("tabSize", this.value);
                    update();
                });

                var lut = [0,0,2,3,4,0,0,0,5];

                function update(e){
                    if (e && !e.value)
                        return;

                    setCurrentSession(menuTabs);

                    items[0].setAttribute("checked", getOption("useSoftTabs"));

                    var tabSize = getOption("tabSize") || 1;
                    items.forEach(function(node, idx){
                        node.setAttribute("selected", "false");
                    });
                    if (lut[tabSize])
                        items[lut[tabSize]].setAttribute("selected", "true");
                    itmTabSize.setAttribute("value", getOption("tabSize"));
                }

                menuTabs.on("prop.visible", update);
                update();
            });
        });

        /***** Methods *****/

        var drawn = false;
        function draw(){
            if (drawn) return;
            drawn = true;

            // Import Skin
            ui.insertSkin({
                name         : "c9statusbar",
                data         : skin,
                "media-path" : options.staticPrefix + "/images/",
                "icon-path"  : options.staticPrefix + "/icons/"
            }, handle);
        };

        function getStatusbar(editor){
            return statusbars[editor.name];
        }

        function show(){
            settings.set("user/ace/statusbar/@show", "true");
        }

        function hide(){
            settings.set("user/ace/statusbar/@show", "false");
        }

        /***** Register and define API *****/

        /**
         * Manages the status bar instances for ace.
         * @singleton
         **/
        handle.freezePublicAPI({
            /**
             * Show all the status bars.
             */
            show : show,

            /**
             * Hide all the status bars.
             */
            hide : hide,

            /**
             * Retrieve the status bar that belongs to an ace editor.
             * @param {Editor} ace  The ace editor the status bar belongs to.
             * @return {ace.status.Statusbar}
             */
            getStatusbar : getStatusbar,

            /**
             * Inserts CSS for the statusbar.
             * @private
             */
            draw : draw
        });

        /***** Initialization *****/

        var counter = 0;

        function Statusbar(editor){
            var plugin = new Plugin("Ajax.org", deps);
            var emit   = plugin.getEmitter();

            var showRange;

            var bar, lblSelection, lblStatus, lblRowCol, lblTabs, lblSyntax; // ui elements

            statusbars[editor.name] = plugin;

            var loaded = false;
            function load(){
                if (loaded) return false;
                loaded = true;

                function updateBarVisible(){
                    if (!settings.getBool("user/ace/statusbar/@show")) {
                        bar && bar.hide();
                        menuItem.enable();
                    }
                    else {
                        draw();
                        bar.show();
                        menuItem.enable();
                    }
                }

                settings.on("user/ace/statusbar", updateBarVisible, plugin);

                handle.on("show", show);

                if (settings.getBool("user/ace/statusbar/@show"))
                    draw();

                editor.on("documentLoad", function(e){
                    var session = e.doc.getSession();
                    session.statusBar = plugin;
                    session.session.on("changeMode", function(e){ statusUpdate.schedule(); });
                }, plugin);
                editor.on("documentActivate", function(e){ statusUpdate.schedule(); }, plugin);
                editor.on("documentUnload", function(e){
                    delete e.doc.getSession().statusBar;
                }, plugin);
            }

            var drawn = false;
            function draw(){
                if (drawn) return;
                drawn = true;

                handle.draw();

                // Create UI elements
                var htmlNode = editor.aml; //ace.container.parentNode.host;
                ui.insertMarkup(htmlNode, markup, plugin);

                function setTheme(e){
                    var theme    = e.theme;
                    if (!theme) return;

                    var cssClass = theme.cssClass;
                    var isDark   = theme.isDark;

                    var bg = ui.getStyleRule("." + cssClass, "backgroundColor");

                    bar.setAttribute("class", isDark ? "ace_dark" : "");
                    if (bg) {
                        // bg = bg.replace(/rgb\((.*)\)/, "rgba($1, 0.9)");
                        bar.$ext.style.backgroundColor = bg;
                    }
                }
                editor.on("themeChange", setTheme);

                bar          = plugin.getElement("bar");
                lblSelection = plugin.getElement("lblSelectionLength");
                lblStatus    = plugin.getElement("lblEditorStatus");
                lblRowCol    = plugin.getElement("lblRowCol");
                lblTabs      = plugin.getElement("lblTabs");
                lblSyntax    = plugin.getElement("lblSyntax");

                // For editor search of submenus
                bar.editor = editor;

                // Set sub menus
                var button = plugin.getElement("btnSbPrefs");
                button.setAttribute("submenu", menu);

                lblTabs.setAttribute("submenu", menuTabs);

                var mnuSyntax = menus.get("View/Syntax").menu;
                lblSyntax.setAttribute("submenu", mnuSyntax);
                lblSyntax.on("mousedown", function(){
                    if (editor.activeDocument)
                        tabs.focusTab(editor.activeDocument.tab);
                });

                // Click behavior for the labels
                lblSelection.on("click", function(){
                    showRange = !showRange;
                    updateStatus();
                });

                lblRowCol.on("click", function(){
                    gotoline.gotoline(null, null, true);
                });

                // Hook into ace
                var ace = editor.ace;
                if (!ace.$hasStatusBar) {
                    // Throttle UI updates
                    ace.on("changeSelection", function() { selStatusUpdate.schedule() });
                    ace.on("changeStatus", function() { statusUpdate.schedule() });
                    ace.renderer.on("scrollbarVisibilityChanged", function(e, renderer) {
                        bar.$ext.style.right = renderer.scrollBarV.getWidth() + 5 + "px";
                        bar.$ext.style.bottom = renderer.scrollBarH.getHeight() + 3 + "px";
                    })(null, ace.renderer);
                    ace.$hasStatusBar = true;

                    var theme = editor.theme;
                    setTheme({theme: theme});
                }

                // Update status information
                updateStatus();

                emit("draw");
            }

            var selStatusUpdate = lang.delayedCall(updateSelStatus, 10);
            var statusUpdate    = lang.delayedCall(updateStatus, 10);

            /***** Helper Functions *****/

            function updateSelStatus(){
                var ace = editor.ace;
                if (!ace) return;

                if (!ace.selection.isEmpty()) {
                    var range = ace.getSelectionRange();
                    var selLen;

                    if (showRange) {
                        selLen = "(" +
                            (range.end.row - range.start.row) + ":" +
                            (range.end.column - range.start.column) + ")";
                    }
                    else {
                        selLen = "(" + ace.session.getTextRange(range).length + " Bytes)";
                    }

                    lblSelection.setAttribute("caption", selLen);
                }
                else {
                    lblSelection.setAttribute("caption", "");
                }

                var cursor = ace.selection.lead;
                lblRowCol.setAttribute("caption", (cursor.row + 1) + ":" + (cursor.column + 1));
            }

            function updateStatus() {
                var ace = editor.ace;
                if (!ace) return;

                updateSelStatus();

                lblTabs.setAttribute("caption",
                    (ace.getOption("useSoftTabs") ? "Spaces" : "Tabs") + ": "
                      + ace.getOption("tabSize")); // "\\[" + + "\\]");

                var status = "";
                if (ace.$vimModeHandler)
                    status = ace.$vimModeHandler.getStatusText();
                else if (ace.commands.recording)
                    status = "REC";

                lblStatus.setAttribute("caption", status);

                var caption = aceHandle.getSyntaxCaption(ace.session.syntax);
                lblSyntax && lblSyntax.setAttribute("caption", caption);
            }

            /***** Lifecycle *****/

            plugin.on("load", function(){
                load();
            });
            plugin.on("enable", function(){

            });
            plugin.on("disable", function(){

            });
            plugin.on("unload", function(){
                loaded = false;
            });

            /***** Register and define API *****/

            /**
             * The status bar for ace editors.
             * @class ace.status.Statusbar
             **/
            plugin.freezePublicAPI({
                /**
                 * Redraw the display of the statusbar
                 */
                update : updateStatus
            });

            plugin.load("acestatus" + counter++);

            return plugin;
        }

        register(null, {
            "ace.status" : handle
        });
    }
});<|endoftext|>


<commit_before>/**
 * A timetable view of a TripPattern and Calendar.
 */

var GtfsEditor = GtfsEditor || {};

(function(G, $, ich) {
  /**
   * A model representing a cell in the table. We need a toString because Handsontable calls toString before
   * calling the editor setValue method, so the string needs to be something useful.
   */
  var StopTimeCell = Backbone.Model.extend({
    defaults: {
      stopTime: null,
      // is this the arrival time cell, or the departure time cell?
      arr: null,
      trip: null
    },
    toString: function() {
      var st = this.get('stopTime');

      if (st === null)
        return 'does-not-stop';

      var time = this.get('arr') ? st.arrivalTime : st.departureTime

      return String(time !== null ? time : 'no-time');
    }
  })

  /**
   * A renderer that displays the time as a time rather than seconds since midnight.
   */
   var stopTimeRenderer = function(instance, td, row, col, prop, value, cellProperties) {
     // TODO: 12h time
     // TODO: single-time view
     // time is seconds since midnight
     var text;
     if (value.get('stopTime') === null) {
       text = '-';
     } else {

       var arr = value.get('arr');
       var st = value.get('stopTime');
       var time = arr ? st.arrivalTime : st.departureTime;

       if (time === null) {
         // time is to be interpolated by consumer
         text = '';
       } else {
         var secs = time % 60;
         var mins = (time - secs) % (60 * 60) / 60;
         var hours = (time - secs - mins * 60) / (60 * 60);

         // TODO: template
         text =
           '
' +
           '' + hours + '' +
           '' + (mins < 10 ? '0' + mins : mins) + '' +
           '' + (secs < 10 ? '0' + secs : secs) + '' +
           '
';
       }
     }

     Handsontable.renderers.HtmlRenderer(instance, td, row, col, prop, text, cellProperties);
   };

  /**
   * Edit a time
   */
  var StopTimeEditor = Handsontable.editors.TextEditor.prototype.extend();

  StopTimeEditor.prototype.setValue = function(time) {
    if (time == 'does-not-stop') {
      value = '-';
    } else if (time == 'no-time') {
      value = '';
    } else {

      time = Number(time);

      var secs = time % 60;
      var mins = (time - secs) % (60 * 60) / 60;
      var hours = (time - secs - mins * 60) / (60 * 60);

      var value = hours + ':' + (mins < 10 ? '0' + mins : mins) + ':' + (secs < 10 ? '0' + secs : secs);
    }

    Handsontable.editors.TextEditor.prototype.setValue.apply(this, [value]);
    $(this.TEXTAREA).addClass('time');
  };

  // select everything, since folks are usually overwriting
  StopTimeEditor.prototype.focus = function() {
    Handsontable.editors.TextEditor.prototype.focus.apply(this, arguments);
    this.TEXTAREA.setSelectionRange(0, this.TEXTAREA.value.length);
    $(this.TEXTAREA).css('font-family', 'mono');
  };

  /**
   * Parse time in many formats
   * return null if there is no time
   * return false if the vehicle does not stop here
   * No time means "the vehicle does stop here, but it's up to the consumer to interpolate when"
   */
  var parseTime = function(time) {
    // get things into a standard format
    // everything lower case
    // no whitespace
    // get rid of the m on am, pm, and mm (post midnight)
    // we replace semicolons with colons, to allow folks to enter 3;32;21 pm, to be easier on the fingers
    // than having to type shift each time
    time = time.toLowerCase().replace(/([apm])(m?)/, '$1').replace(';', ':').replace(/\W/g, '');

    if (time === '')
      return null;

    // TODO: handle this properly on re-render
    if (time == '-')
      return false;

    // separate the numbers from the letter
    var match = /([0-9:]{3,})([apm]?)/.exec(time);

    // figure out how much to add on for AM/PM/past-midnight
    var additionalSeconds = 0;
    if (match[2] == 'p')
      additionalSeconds = 12 * 60 * 60;
    else if (match[2] == 'm')
      additionalSeconds = 24 * 60 * 60;

    // am and 24-hour cases are implied

    // parse out hours, minutes and seconds
    var rawTime = match[1];
    var hours, minutes, seconds = 0;

    // if we have colons, use them as separators
    if (rawTime.indexOf(':') != -1) {
      var components = rawTime.split(':');
      hours = Number(components[0]);
      minutes = Number(components[1]);
      seconds = components.length >= 3 ? Number(components[2]) : 0;
    } else {

      // no colons, infer things positionally
      if (rawTime.length >= 5) {
        // we have seconds
        seconds = Number(rawTime.slice(-2));
        rawTime = rawTime.slice(0, -2);
      }

      var minutes = Number(rawTime.slice(-2));
      var hours = Number(rawTime.slice(0, -2));
    }

    // patch up 12am, 12pm, since they break the pattern
    if (hours == 12 && match[2] == 'p')
    // 12pm is noon
      additionalSeconds = 0;

    if (hours == 12 && match[2] == 'a')
    // 12am is 00:00:00
      additionalSeconds = -12 * 60 * 60;

    return hours * 60 * 60 + minutes * 60 + seconds + additionalSeconds;
  };

  /**
   * all of the valid time formats:
   * 1342 332p 332 PM 1:01:03 PM 10:10:10 etc.
   * 332m means 3 hours, thirty-two minutes past midnight and is used for overnight runs
   * (e.g. starting at 11:55 PM and ending at 1:31 M, or 1:31 the next day).
   *
   * Note that 2090102 is not a valid time, while 209:01:02 is. Though 209 hours past midnight on the day
   * of the start of the trip is in fact a valid time, it's almost always a typo when entered without colons, so
   * we disallow it. If you're running the trans-Siberian railway (or another long-distance service), you'll just
   * have to enter the colons.
   *
   * Semicolons and colons are treated as equivalent, for ease of typing.
   */
  var validTimeFormat =
    /^\W*$|^\W*-\W*|^\W*[0-9]{1,2}([0-5][0-9]){1,2}\W*[apmAPM]?[mM]?\W*$|^\W*[0-9]{1,}([:;][0-5][0-9]){1,2}\W*[apmAPM]?[mM]?\W*$/;

  G.TripPatternScheduleView = Backbone.View.extend({
    initialize: function(attr) {
      this.calendar = attr.calendar;
      this.pattern = attr.pattern;

      // consistency check
      var tripPatternId = null;
      var serviceCalendarId = null;
      var first = true;
      this.collection.each(function(trip) {
        if (first) {
          tripPatternId = trip.get('pattern').id;
          serviceCalendarId = trip.get('serviceCalendar').id;
          first = false;
        } else {
          if (trip.get('pattern').id != tripPatternId ||
            trip.get('serviceCalendar').id != serviceCalendarId) {
            throw new Error("Trip pattern or service calendars differ between trips");
          }
        }
      });

      // Handsontable needs the collection to have a splice method
      // inspired by code at http://handsontable.com/demo/backbone.html
    this.collection.splice = function (idx, size) {
      var toRemove = this.toArray().slice(idx, size + 1);
      this.remove(toRemove);
      // collections are not ordered, since this one has a comparator; thus it doesn't matter if we insert stuff in the middle
      // backbone will just move it around anyhow, and handsontable doesn't seem to care if things don't end up where it put them
      this.add(arguments.slice(2));
      return toRemove;
    };

      // event handlers
      _.bindAll(this, 'saveAll', 'newTrip');
    },

    // combination of getAttr and setAttr for handsontable
    attr: function(name) {
      var instance = this;
      var ret = {
        data: function(trip, val) {
          if (name.indexOf('stop:') === 0) {
            // we need to return a stop time, so first parse out the column header
            var sp = name.split(':');
            var stopId = sp[1];
            var stopSeq = sp[2];
            var arr = sp[3] == 'arr';

            // find the stopTime with this stop id and stop sequence
            // note that this makes an assumption about stop_sequence: when a stop is ommitted,
            // its sequence number is omitted as well
            var st = _.find(trip.get('stopTimes'), function(st) {
              return st.stop.id == stopId && st.stopSequence == stopSeq;
            });

            if (_.isUndefined(val)) {
              return new StopTimeCell({
                arr: arr,
                stopTime: st,
                trip: trip
              });
            } else {
              if (st == null) {
                // find the appropriate pattern stop
                var ps = _.find(instance.pattern.get('patternStops'), function (ps) {
                  return ps.stop.id == stopId && ps.stopSequence == stopSeq;
                });

                st = instance.makeStopTime(ps);

                // find the index of the previous stop
                var largestIndex = -1;
                _.each(trip.get('stopTimes'), function (eachSt, idx) {
                  if (eachSt.stopSequence < st.stopSequence && idx > largestIndex) {
                    largestIndex = idx;
                  }
                });

                // insert
                trip.get('stopTimes').splice(largestIndex + 1, 0, st);
                trip.set('stopTimes', trip.get('stopTimes'));
              }

              if (arr) {
                st.arrivalTime = parseTime(val);
              } else {
                st.departureTime = parseTime(val);
              }

              // keep track of modifications
              // Backbone doesn't know quite how to handle nested objects inside models, so we track state ourselves
              trip.modified = true;
            }
          } else if (name == 'tripId') {
            if (_.isUndefined(val)) {
              return trip.get('gtfsTripId');
            } else {
              trip.set('gtfsTripId', val);
              trip.modified = true;
            }
          } else if (name == 'blockId') {
            if (_.isUndefined(val)) {
              return trip.get('blockId');
            } else {
              trip.set('blockId', val);
              trip.modified = true;
            }
          } else if (name == 'tripHeadsign') {
            if (_.isUndefined(val)) {
              return trip.get('tripHeadsign');
            } else {
              trip.set('tripHeadsign', val);
              trip.modified = true;
            }
          } else if (name == 'modified') {
            return trip.modified ? '*' : '';
          }
        },
      };

      if (name.indexOf('stop:') === 0) {
        ret.validator = validTimeFormat;
        ret.renderer = stopTimeRenderer;
        ret.editor = StopTimeEditor;
        ret.allowInvalid = false;
      }

      return ret;
    },

    saveAll: function() {
      var instance = this;
      var deferreds = [];

      this.collection.each(function(trip) {
        if (trip.modified) {
          deferreds.push(
            trip.save().done(function() {
              trip.modified = false;
            })
          );
        }
      });

      $.when.apply($, deferreds).always(function () {
        instance.$container.handsontable('render');
      });
    },

    // create a new trip based on the pattern
    newTrip: function () {
      var trip = new G.Trip();
      trip.set('pattern', this.pattern.toJSON());
      trip.set('serviceCalendar', this.calendar.toJSON());
      trip.set('route', this.pattern.get('route'));
      trip.set('useFrequency', false);
      var stopTimes = [];

      // prepopulate stop times based on the pattern
      // TODO: midnight is a bad initial time. what is a good intial time?
      var currentTime = 0;

      var instance = this;
      _.each(this.pattern.get('patternStops'), function (patternStop) {
        var st = instance.makeStopTime(patternStop);

        currentTime += patternStop.defaultTravelTime;
        st.arrivalTime = currentTime;
        currentTime += patternStop.defaultDwellTime;
        st.departureTime = currentTime;

        stopTimes.push(st);
      });

      trip.set('stopTimes', stopTimes);

      // flag so it gets saved
      trip.modified = true;

      this.collection.add(trip);

      // trigger redraw, show the new trip
      this.$container.handsontable('render');
    },

    /** Make a stop time from a pattern stop */
    makeStopTime: function (patternStop) {
      var st = {};

      st.stop = patternStop.stop;
      st.patternStop = patternStop;
      st.stopSequence = patternStop.stopSequence;

      st.arrivalTime = st.departureTime = null;

      return st;
    },

    render: function() {
      // render the template
      this.$el.html(ich['timetable-tpl']());

      // figure out what columns we're rendering
      var columns = [this.attr('modified'), this.attr('tripId'), this.attr('blockId'), this.attr('tripHeadsign')];
      // TODO: i18n
      var headers = ['', 'Trip ID', 'Block ID', 'Trip headsign'];

      var instance = this;

      // note: the merged header cells aren't actually merged, but depend on a really awful hack (in timetable.css) where the width of
      // two columns is hardcoded as the header width. As a side effect, these numbers must be exactly twice the width of the time
      // display cells
      var colWidths = [15, 150, 150, 150];

      _.each(this.pattern.get('patternStops'), function(patternStop, idx) {
        // we put stopSequence here for loop routes
        columns.push(instance.attr('stop:' + patternStop.stop.id + ':' + patternStop.stopSequence + ':arr'));
        columns.push(instance.attr('stop:' + patternStop.stop.id + ':' + patternStop.stopSequence + ':dep'));
        headers.push(patternStop.stop.stopName);
        // dummy header, will be overwritten when cells are merged
        headers.push('');

        colWidths.push(75);
        colWidths.push(75);
      });

      // make a new trip, and since it's new it's been modified (from its previous state of non-existence)
      var schema = function() {
        var t = new G.Trip();
        t.modified = true;
        return t;
      };

      this.$container = this.$('#timetable');
      this.$container.handsontable({
        data: this.collection,
        dataSchema: schema,
        // we'll be defining interaction on our own
        // also adding a column doesn't make sense
        // and adding a row (trip) causes display issues with handsontable
        contextMenu: false,
        columns: columns,
        colHeaders: headers,
        colWidths: colWidths
      });

      // add the event handlers
      this.$('.save').click(this.saveAll);
      this.$('.new-trip').click(this.newTrip);

      return this;
    }
  });
})(GtfsEditor, jQuery, ich);
<commit_msg>remove invalid flag on edit.<commit_after>/**
 * A timetable view of a TripPattern and Calendar.
 */

var GtfsEditor = GtfsEditor || {};

(function(G, $, ich) {
  /**
   * A model representing a cell in the table. We need a toString because Handsontable calls toString before
   * calling the editor setValue method, so the string needs to be something useful.
   */
  var StopTimeCell = Backbone.Model.extend({
    defaults: {
      stopTime: null,
      // is this the arrival time cell, or the departure time cell?
      arr: null,
      trip: null
    },
    toString: function() {
      var st = this.get('stopTime');

      if (st === null)
        return 'does-not-stop';

      var time = this.get('arr') ? st.arrivalTime : st.departureTime

      return String(time !== null ? time : 'no-time');
    }
  })

  /**
   * A renderer that displays the time as a time rather than seconds since midnight.
   */
   var stopTimeRenderer = function(instance, td, row, col, prop, value, cellProperties) {
     // TODO: 12h time
     // TODO: single-time view
     // time is seconds since midnight
     var text;
     if (value.get('stopTime') === null) {
       text = '-';
     } else {

       var arr = value.get('arr');
       var st = value.get('stopTime');
       var time = arr ? st.arrivalTime : st.departureTime;

       if (time === null) {
         // time is to be interpolated by consumer
         text = '';
       } else {
         var secs = time % 60;
         var mins = (time - secs) % (60 * 60) / 60;
         var hours = (time - secs - mins * 60) / (60 * 60);

         // TODO: template
         text =
           '
' +
           '' + hours + '' +
           '' + (mins < 10 ? '0' + mins : mins) + '' +
           '' + (secs < 10 ? '0' + secs : secs) + '' +
           '
';
       }
     }

     Handsontable.renderers.HtmlRenderer(instance, td, row, col, prop, text, cellProperties);
   };

  /**
   * Edit a time
   */
  var StopTimeEditor = Handsontable.editors.TextEditor.prototype.extend();

  StopTimeEditor.prototype.setValue = function(time) {
    if (time == 'does-not-stop') {
      value = '-';
    } else if (time == 'no-time') {
      value = '';
    } else {

      time = Number(time);

      var secs = time % 60;
      var mins = (time - secs) % (60 * 60) / 60;
      var hours = (time - secs - mins * 60) / (60 * 60);

      var value = hours + ':' + (mins < 10 ? '0' + mins : mins) + ':' + (secs < 10 ? '0' + secs : secs);
    }

    Handsontable.editors.TextEditor.prototype.setValue.apply(this, [value]);
    $(this.TEXTAREA).addClass('time');
  };

  // select everything, since folks are usually overwriting
  StopTimeEditor.prototype.focus = function() {
    Handsontable.editors.TextEditor.prototype.focus.apply(this, arguments);
    this.TEXTAREA.setSelectionRange(0, this.TEXTAREA.value.length);
    $(this.TEXTAREA).css('font-family', 'mono');
  };

  /**
   * Parse time in many formats
   * return null if there is no time
   * return false if the vehicle does not stop here
   * No time means "the vehicle does stop here, but it's up to the consumer to interpolate when"
   */
  var parseTime = function(time) {
    // get things into a standard format
    // everything lower case
    // no whitespace
    // get rid of the m on am, pm, and mm (post midnight)
    // we replace semicolons with colons, to allow folks to enter 3;32;21 pm, to be easier on the fingers
    // than having to type shift each time
    time = time.toLowerCase().replace(/([apm])(m?)/, '$1').replace(';', ':').replace(/\W/g, '');

    if (time === '')
      return null;

    // TODO: handle this properly on re-render
    if (time == '-')
      return false;

    // separate the numbers from the letter
    var match = /([0-9:]{3,})([apm]?)/.exec(time);

    // figure out how much to add on for AM/PM/past-midnight
    var additionalSeconds = 0;
    if (match[2] == 'p')
      additionalSeconds = 12 * 60 * 60;
    else if (match[2] == 'm')
      additionalSeconds = 24 * 60 * 60;

    // am and 24-hour cases are implied

    // parse out hours, minutes and seconds
    var rawTime = match[1];
    var hours, minutes, seconds = 0;

    // if we have colons, use them as separators
    if (rawTime.indexOf(':') != -1) {
      var components = rawTime.split(':');
      hours = Number(components[0]);
      minutes = Number(components[1]);
      seconds = components.length >= 3 ? Number(components[2]) : 0;
    } else {

      // no colons, infer things positionally
      if (rawTime.length >= 5) {
        // we have seconds
        seconds = Number(rawTime.slice(-2));
        rawTime = rawTime.slice(0, -2);
      }

      var minutes = Number(rawTime.slice(-2));
      var hours = Number(rawTime.slice(0, -2));
    }

    // patch up 12am, 12pm, since they break the pattern
    if (hours == 12 && match[2] == 'p')
    // 12pm is noon
      additionalSeconds = 0;

    if (hours == 12 && match[2] == 'a')
    // 12am is 00:00:00
      additionalSeconds = -12 * 60 * 60;

    return hours * 60 * 60 + minutes * 60 + seconds + additionalSeconds;
  };

  /**
   * all of the valid time formats:
   * 1342 332p 332 PM 1:01:03 PM 10:10:10 etc.
   * 332m means 3 hours, thirty-two minutes past midnight and is used for overnight runs
   * (e.g. starting at 11:55 PM and ending at 1:31 M, or 1:31 the next day).
   *
   * Note that 2090102 is not a valid time, while 209:01:02 is. Though 209 hours past midnight on the day
   * of the start of the trip is in fact a valid time, it's almost always a typo when entered without colons, so
   * we disallow it. If you're running the trans-Siberian railway (or another long-distance service), you'll just
   * have to enter the colons.
   *
   * Semicolons and colons are treated as equivalent, for ease of typing.
   */
  var validTimeFormat =
    /^\W*$|^\W*-\W*|^\W*[0-9]{1,2}([0-5][0-9]){1,2}\W*[apmAPM]?[mM]?\W*$|^\W*[0-9]{1,}([:;][0-5][0-9]){1,2}\W*[apmAPM]?[mM]?\W*$/;

  G.TripPatternScheduleView = Backbone.View.extend({
    initialize: function(attr) {
      this.calendar = attr.calendar;
      this.pattern = attr.pattern;

      // consistency check
      var tripPatternId = null;
      var serviceCalendarId = null;
      var first = true;
      this.collection.each(function(trip) {
        if (first) {
          tripPatternId = trip.get('pattern').id;
          serviceCalendarId = trip.get('serviceCalendar').id;
          first = false;
        } else {
          if (trip.get('pattern').id != tripPatternId ||
            trip.get('serviceCalendar').id != serviceCalendarId) {
            throw new Error("Trip pattern or service calendars differ between trips");
          }
        }
      });

      // Handsontable needs the collection to have a splice method
      // inspired by code at http://handsontable.com/demo/backbone.html
    this.collection.splice = function (idx, size) {
      var toRemove = this.toArray().slice(idx, size + 1);
      this.remove(toRemove);
      // collections are not ordered, since this one has a comparator; thus it doesn't matter if we insert stuff in the middle
      // backbone will just move it around anyhow, and handsontable doesn't seem to care if things don't end up where it put them
      this.add(arguments.slice(2));
      return toRemove;
    };

      // event handlers
      _.bindAll(this, 'saveAll', 'newTrip');
    },

    // combination of getAttr and setAttr for handsontable
    attr: function(name) {
      var instance = this;
      var ret = {
        data: function(trip, val) {
          if (name.indexOf('stop:') === 0) {
            // we need to return a stop time, so first parse out the column header
            var sp = name.split(':');
            var stopId = sp[1];
            var stopSeq = sp[2];
            var arr = sp[3] == 'arr';

            // find the stopTime with this stop id and stop sequence
            // note that this makes an assumption about stop_sequence: when a stop is ommitted,
            // its sequence number is omitted as well
            var st = _.find(trip.get('stopTimes'), function(st) {
              return st.stop.id == stopId && st.stopSequence == stopSeq;
            });

            if (_.isUndefined(val)) {
              return new StopTimeCell({
                arr: arr,
                stopTime: st,
                trip: trip
              });
            } else {
              if (st == null) {
                // find the appropriate pattern stop
                var ps = _.find(instance.pattern.get('patternStops'), function (ps) {
                  return ps.stop.id == stopId && ps.stopSequence == stopSeq;
                });

                st = instance.makeStopTime(ps);

                // find the index of the previous stop
                var largestIndex = -1;
                _.each(trip.get('stopTimes'), function (eachSt, idx) {
                  if (eachSt.stopSequence < st.stopSequence && idx > largestIndex) {
                    largestIndex = idx;
                  }
                });

                // insert
                trip.get('stopTimes').splice(largestIndex + 1, 0, st);
                trip.set('stopTimes', trip.get('stopTimes'));
              }

              if (arr) {
                st.arrivalTime = parseTime(val);
              } else {
                st.departureTime = parseTime(val);
              }

              // keep track of modifications
              // Backbone doesn't know quite how to handle nested objects inside models, so we track state ourselves
              trip.modified = true;
            }
          } else if (name == 'tripId') {
            if (_.isUndefined(val)) {
              return trip.get('gtfsTripId');
            } else {
              trip.set('gtfsTripId', val);
              trip.modified = true;
            }
          } else if (name == 'blockId') {
            if (_.isUndefined(val)) {
              return trip.get('blockId');
            } else {
              trip.set('blockId', val);
              trip.modified = true;
            }
          } else if (name == 'tripHeadsign') {
            if (_.isUndefined(val)) {
              return trip.get('tripHeadsign');
            } else {
              trip.set('tripHeadsign', val);
              trip.modified = true;
            }
          } else if (name == 'modified') {
            return trip.modified ? '*' : '';
          }
        },
      };

      if (name.indexOf('stop:') === 0) {
        ret.validator = validTimeFormat;
        ret.renderer = stopTimeRenderer;
        ret.editor = StopTimeEditor;
        ret.allowInvalid = false;
      }

      return ret;
    },

    saveAll: function() {
      var instance = this;
      var deferreds = [];

      this.collection.each(function(trip) {
        if (trip.modified) {
          deferreds.push(
            // we assume that, if it's been modified, it is now valid
            // the invalid flag just indicates that something has occurred that requires human intervention
            // but we don't clear the flag until the save is successful
            trip.save({invalid: false}, {wait: true}).done(function() {
              trip.modified = false;
            })
          );
        }
      });

      $.when.apply($, deferreds).always(function () {
        instance.$container.handsontable('render');
      });
    },

    // create a new trip based on the pattern
    newTrip: function () {
      var trip = new G.Trip();
      trip.set('pattern', this.pattern.toJSON());
      trip.set('serviceCalendar', this.calendar.toJSON());
      trip.set('route', this.pattern.get('route'));
      trip.set('useFrequency', false);
      var stopTimes = [];

      // prepopulate stop times based on the pattern
      // TODO: midnight is a bad initial time. what is a good intial time?
      var currentTime = 0;

      var instance = this;
      _.each(this.pattern.get('patternStops'), function (patternStop) {
        var st = instance.makeStopTime(patternStop);

        currentTime += patternStop.defaultTravelTime;
        st.arrivalTime = currentTime;
        currentTime += patternStop.defaultDwellTime;
        st.departureTime = currentTime;

        stopTimes.push(st);
      });

      trip.set('stopTimes', stopTimes);

      // flag so it gets saved
      trip.modified = true;

      this.collection.add(trip);

      // trigger redraw, show the new trip
      this.$container.handsontable('render');
    },

    /** Make a stop time from a pattern stop */
    makeStopTime: function (patternStop) {
      var st = {};

      st.stop = patternStop.stop;
      st.patternStop = patternStop;
      st.stopSequence = patternStop.stopSequence;

      st.arrivalTime = st.departureTime = null;

      return st;
    },

    render: function() {
      // render the template
      this.$el.html(ich['timetable-tpl']());

      // figure out what columns we're rendering
      var columns = [this.attr('modified'), this.attr('tripId'), this.attr('blockId'), this.attr('tripHeadsign')];
      // TODO: i18n
      var headers = ['', 'Trip ID', 'Block ID', 'Trip headsign'];

      var instance = this;

      // note: the merged header cells aren't actually merged, but depend on a really awful hack (in timetable.css) where the width of
      // two columns is hardcoded as the header width. As a side effect, these numbers must be exactly twice the width of the time
      // display cells
      var colWidths = [15, 150, 150, 150];

      _.each(this.pattern.get('patternStops'), function(patternStop, idx) {
        // we put stopSequence here for loop routes
        columns.push(instance.attr('stop:' + patternStop.stop.id + ':' + patternStop.stopSequence + ':arr'));
        columns.push(instance.attr('stop:' + patternStop.stop.id + ':' + patternStop.stopSequence + ':dep'));
        headers.push(patternStop.stop.stopName);
        // dummy header, will be overwritten when cells are merged
        headers.push('');

        colWidths.push(75);
        colWidths.push(75);
      });

      // make a new trip, and since it's new it's been modified (from its previous state of non-existence)
      var schema = function() {
        var t = new G.Trip();
        t.modified = true;
        return t;
      };

      this.$container = this.$('#timetable');
      this.$container.handsontable({
        data: this.collection,
        dataSchema: schema,
        // we'll be defining interaction on our own
        // also adding a column doesn't make sense
        // and adding a row (trip) causes display issues with handsontable
        contextMenu: false,
        columns: columns,
        colHeaders: headers,
        colWidths: colWidths
      });

      // add the event handlers
      this.$('.save').click(this.saveAll);
      this.$('.new-trip').click(this.newTrip);

      return this;
    }
  });
})(GtfsEditor, jQuery, ich);
<|endoftext|>