<commit_before>;
(function (win, undefined) {
    "use strict";

    var axemas = win['axemas'] = (win['axemas'] || {});
    axemas._platform_cache = null;

    axemas.goto = function (data) {
        if (typeof data === 'string' || data instanceof String)
            data = {
                'url': data
            };

        if (axemas.getPlatform() == 'unsupported')
            window.location = '/' + data["url"];
        else
            axemas.call('goto', data);
    };

    axemas.gotoFromSidebar = function (data) {
        if (typeof data === 'string' || data instanceof String)
            data = {
                'url': data
            };

        if (axemas.getPlatform() == 'unsupported')
            window.location = '../' + data["url"];
        else
            axemas.call('gotoFromSidebar', data);
    };

    axemas.showProgressHUD = function () {
        axemas.call('showProgressHUD', {});
    };

    axemas.hideProgressHUD = function () {
        axemas.call('hideProgressHUD', {});
    };

    axemas.dialog = function (title, message, buttons, onButtonClickCallback) {
        axemas.call('dialog', {
                title: title,
                message: message,
                buttons: buttons
            },
            onButtonClickCallback);
    };

    axemas.alert = function (title, message) {
        axemas.dialog(title, message, ['Ok']);
    };

    axemas.getPlatform = function () {
        if (axemas._platform_cache !== null)
            return axemas._platform_cache;

        var platform = 'unsupported';
        if (navigator.userAgent.match(/Windows Phone/i))
            platform = 'winphone';
        else if (navigator.userAgent.match(/Android/i))
            platform = 'android';
        else if (navigator.userAgent.match(/iPhone|iPad|iPod/i))
            platform = 'ios';

        axemas._platform_cache = platform;
        return platform;
    };

    axemas.storeData = function (key, value) {
        if (axemas.getPlatform() == 'unsupported')
            localStorage.setItem(key, value);
        else
            axemas.call('storeData', {
                'key': key,
                'value': value
            });
    };

    axemas.fetchData = function (key, callback) {
        if (axemas.getPlatform() == 'unsupported') {
            var data = {};
            data[key] = localStorage.getItem(key);
            callback(data);
        }
        else
            axemas.call('fetchData', key, callback);
    };

    axemas.removeData = function (key) {
        if (axemas.getPlatform() == 'unsupported')
            localStorage.removeItem(key);
        else
            axemas.call('removeData', key);
    };

    axemas.call = function (handlerName, data, responseCallback) {
        WebViewJavascriptBridge.callHandler(handlerName, data, responseCallback);
    };

    axemas.register = function (handlerName, handler) {
        WebViewJavascriptBridge.registerHandler(handlerName, handler);
    };

    // Provide a fake WebViewJavascriptBridge for development in browser
    if (axemas.getPlatform() == 'unsupported') {
        if (!win.WebViewJavascriptBridge) {
            var messageHandlers = {};

            var registerHandler = function(handlerName, handler) {
                messageHandlers[handlerName] = handler;
            };

            var callHandler = function(handlerName, data, responseCallback) {
                setTimeout(function() { messageHandlers[handlerName](data, responseCallback); }, 0);
            }

            win.WebViewJavascriptBridge = {
                registerHandler: registerHandler,
                callHandler: callHandler,
            };
        }
    }

    // Support for native calls on WindowsPhone
    if (axemas.getPlatform() == 'winphone') {
        if (!win.WebViewJavascriptBridge) {
            var messageHandlers = {};
            var responseCallbacks = {};
            var uniqueId = 1;

            var callHandler = function (handlerName, data, responseCallback) {
                var message = {
                    'type': 'CallHandler',
                    'handlerName': handlerName,
                    'data': data
                };
                if (responseCallback) {
                    var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
                    responseCallbacks[callbackId] = responseCallback;
                    message['callbackId'] = callbackId;
                }

                var encodedMessage = JSON.stringify(message);
                window.external.notify(encodedMessage);
            };

            var registerHandler = function (handlerName, handler) {
                messageHandlers[handlerName] = handler;
            };

            var _callJSCallback = function (callbackId, data) {
                var responseCallback = responseCallbacks[callbackId];
                if (!responseCallback) {
                    return;
                }
                delete responseCallbacks[callbackId];
                responseCallback(JSON.parse(data));
            };

            var _callJSHandler = function (handlerName, data, callbackId) {
                var responseCallback = function (responseData) {
                    var message = {
                        'type': 'CallCallback',
                        'callbackId': callbackId,
                        'data': responseData
                    };
                    var encodedMessage = JSON.stringify(message);
                    window.external.notify(encodedMessage);
                }

                var handler = messageHandlers[handlerName];
                if (!handler) {
                    console.log('No handler in place for message', data);
                    responseCallback({});
                    return;
                }

                handler(JSON.parse(data), responseCallback);
            }

            win.WebViewJavascriptBridge = {
                registerHandler: registerHandler,
                callHandler: callHandler,
                _callJSCallback: _callJSCallback,
                _callJSHandler: _callJSHandler
            };
        }
    }

    // Support for native calls on Android
    if (axemas.getPlatform() == 'android') {
        if (!win.WebViewJavascriptBridge) {
            var messageHandlers = {};
            var responseCallbacks = {};
            var uniqueId = 1;

            var callHandler = function (handlerName, data, responseCallback) {
                var message = {
                    data: data
                };
                if (responseCallback) {
                    var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
                    responseCallbacks[callbackId] = responseCallback;
                    message['callbackId'] = callbackId;
                }

                var encodedMessage = JSON.stringify(message);
                AndroidNativeJS.callAndroid(handlerName, encodedMessage);
            };

            var registerHandler = function (handlerName, handler) {
                messageHandlers[handlerName] = handler;
            };

            var _callJSCallback = function (callbackId, data) {
                var responseCallback = responseCallbacks[callbackId];
                if (!responseCallback) {
                    return;
                }
                delete responseCallbacks[callbackId];
                responseCallback(data);
            };

            var _callJSHandler = function (handlerName, data, callbackId) {
                var responseCallback = function (responseData) {
                    AndroidNativeJS.callAndroidCallback(callbackId,
                        JSON.stringify(responseData));
                }

                var handler = messageHandlers[handlerName];
                if (!handler) {
                    console.log('No handler in place for message', data);
                    responseCallback({});
                    return;
                }

                handler(data, responseCallback);
            }

            win.WebViewJavascriptBridge = {
                registerHandler: registerHandler,
                callHandler: callHandler,
                _callJSCallback: _callJSCallback,
                _callJSHandler: _callJSHandler
            };
        }
    }

    // Support for native calls on iOS
    if (axemas.getPlatform() == 'ios') {
        if (!win.WebViewJavascriptBridge) {
            var messagingIframe
            var sendMessageQueue = []
            var receiveMessageQueue = []
            var messageHandlers = {}

            var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'
            var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

            var responseCallbacks = {}
            var uniqueId = 1

            var _createQueueReadyIframe = function (doc) {
                messagingIframe = doc.createElement('iframe')
                messagingIframe.style.display = 'none'
                doc.documentElement.appendChild(messagingIframe)
            }

            var send = function (data, responseCallback) {
                _doSend({
                    data: data
                }, responseCallback)
            }

            var registerHandler = function (handlerName, handler) {
                messageHandlers[handlerName] = handler
            }

            var callHandler = function (handlerName, data, responseCallback) {
                _doSend({
                    handlerName: handlerName,
                    data: data
                }, responseCallback)
            }

            var _doSend = function (message, responseCallback) {
                if (responseCallback) {
                    var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime()
                    responseCallbacks[callbackId] = responseCallback
                    message['callbackId'] = callbackId
                }
                sendMessageQueue.push(message)
                messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
            }

            var _fetchQueue = function () {
                var messageQueueString = JSON.stringify(sendMessageQueue)
                sendMessageQueue = []
                return messageQueueString
            }

            var _defaultHandler = function (data, responseCallback) {
                console.log('No handler in place for message', data);
                if (responseCallback)
                    responseCallback(null);
            }

            var _dispatchMessageFromObjC = function (messageJSON) {
                setTimeout(function _timeoutDispatchMessageFromObjC() {
                    var message = JSON.parse(messageJSON)
                    var messageHandler

                    if (message.responseId) {
                        var responseCallback = responseCallbacks[message.responseId]
                        if (!responseCallback) {
                            return;
                        }
                        delete responseCallbacks[message.responseId]
                        responseCallback(message.responseData)
                    } else {
                        var responseCallback
                        if (message.callbackId) {
                            var callbackResponseId = message.callbackId
                            responseCallback = function (responseData) {
                                _doSend({
                                    responseId: callbackResponseId,
                                    responseData: responseData
                                })
                            }
                        }

                        var handler = _defaultHandler
                        if (message.handlerName) {
                            handler = messageHandlers[message.handlerName]
                        }

                        try {
                            handler(message.data, responseCallback)
                        } catch (exception) {
                            if (typeof console != 'undefined') {
                                console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
                            }
                        }
                    }
                })
            }

            var _handleMessageFromObjC = function (messageJSON) {
                _dispatchMessageFromObjC(messageJSON)
            }

            win.WebViewJavascriptBridge = {
                send: send,
                registerHandler: registerHandler,
                callHandler: callHandler,
                _fetchQueue: _fetchQueue,
                _handleMessageFromObjC: _handleMessageFromObjC
            }

            var doc = win.document
            _createQueueReadyIframe(doc)
        }
    }

})(window);

/* Those are needed only for WindowsPhone */
var __axemas_WebViewJavascriptBridge_callJSCallback = function (callbackId, data) {
    WebViewJavascriptBridge._callJSCallback(callbackId, data);
}
var __axemas_WebViewJavascriptBridge_callJSHandler = function (handlerName, data, callbackId) {
    WebViewJavascriptBridge._callJSHandler(handlerName, data, callbackId);
}<commit_msg>axemas.alert for browser<commit_after>;
(function (win, undefined) {
    "use strict";

    var axemas = win['axemas'] = (win['axemas'] || {});
    axemas._platform_cache = null;

    axemas.goto = function (data) {
        if (typeof data === 'string' || data instanceof String)
            data = {
                'url': data
            };

        if (axemas.getPlatform() == 'unsupported')
            window.location = '/' + data["url"];
        else
            axemas.call('goto', data);
    };

    axemas.gotoFromSidebar = function (data) {
        if (typeof data === 'string' || data instanceof String)
            data = {
                'url': data
            };

        if (axemas.getPlatform() == 'unsupported')
            window.location = '../' + data["url"];
        else
            axemas.call('gotoFromSidebar', data);
    };

    axemas.showProgressHUD = function () {
        axemas.call('showProgressHUD', {});
    };

    axemas.hideProgressHUD = function () {
        axemas.call('hideProgressHUD', {});
    };

    axemas.dialog = function (title, message, buttons, onButtonClickCallback) {
        axemas.call('dialog', {
                title: title,
                message: message,
                buttons: buttons
            },
            onButtonClickCallback);
    };

    axemas.alert = function (title, message) {
        if (axemas.getPlatform() == 'unsupported')
            alert(title + "\n\n" + message);
        else
            axemas.dialog(title, message, ['Ok']);
    };

    axemas.getPlatform = function () {
        if (axemas._platform_cache !== null)
            return axemas._platform_cache;

        var platform = 'unsupported';
        if (navigator.userAgent.match(/Windows Phone/i))
            platform = 'winphone';
        else if (navigator.userAgent.match(/Android/i))
            platform = 'android';
        else if (navigator.userAgent.match(/iPhone|iPad|iPod/i))
            platform = 'ios';

        axemas._platform_cache = platform;
        return platform;
    };

    axemas.storeData = function (key, value) {
        if (axemas.getPlatform() == 'unsupported')
            localStorage.setItem(key, value);
        else
            axemas.call('storeData', {
                'key': key,
                'value': value
            });
    };

    axemas.fetchData = function (key, callback) {
        if (axemas.getPlatform() == 'unsupported') {
            var data = {};
            data[key] = localStorage.getItem(key);
            callback(data);
        }
        else
            axemas.call('fetchData', key, callback);
    };

    axemas.removeData = function (key) {
        if (axemas.getPlatform() == 'unsupported')
            localStorage.removeItem(key);
        else
            axemas.call('removeData', key);
    };

    axemas.call = function (handlerName, data, responseCallback) {
        WebViewJavascriptBridge.callHandler(handlerName, data, responseCallback);
    };

    axemas.register = function (handlerName, handler) {
        WebViewJavascriptBridge.registerHandler(handlerName, handler);
    };

    // Provide a fake WebViewJavascriptBridge for development in browser
    if (axemas.getPlatform() == 'unsupported') {
        if (!win.WebViewJavascriptBridge) {
            var messageHandlers = {};

            var registerHandler = function(handlerName, handler) {
                messageHandlers[handlerName] = handler;
            };

            var callHandler = function(handlerName, data, responseCallback) {
                setTimeout(function() { messageHandlers[handlerName](data, responseCallback); }, 0);
            }

            win.WebViewJavascriptBridge = {
                registerHandler: registerHandler,
                callHandler: callHandler,
            };
        }
    }

    // Support for native calls on WindowsPhone
    if (axemas.getPlatform() == 'winphone') {
        if (!win.WebViewJavascriptBridge) {
            var messageHandlers = {};
            var responseCallbacks = {};
            var uniqueId = 1;

            var callHandler = function (handlerName, data, responseCallback) {
                var message = {
                    'type': 'CallHandler',
                    'handlerName': handlerName,
                    'data': data
                };
                if (responseCallback) {
                    var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
                    responseCallbacks[callbackId] = responseCallback;
                    message['callbackId'] = callbackId;
                }

                var encodedMessage = JSON.stringify(message);
                window.external.notify(encodedMessage);
            };

            var registerHandler = function (handlerName, handler) {
                messageHandlers[handlerName] = handler;
            };

            var _callJSCallback = function (callbackId, data) {
                var responseCallback = responseCallbacks[callbackId];
                if (!responseCallback) {
                    return;
                }
                delete responseCallbacks[callbackId];
                responseCallback(JSON.parse(data));
            };

            var _callJSHandler = function (handlerName, data, callbackId) {
                var responseCallback = function (responseData) {
                    var message = {
                        'type': 'CallCallback',
                        'callbackId': callbackId,
                        'data': responseData
                    };
                    var encodedMessage = JSON.stringify(message);
                    window.external.notify(encodedMessage);
                }

                var handler = messageHandlers[handlerName];
                if (!handler) {
                    console.log('No handler in place for message', data);
                    responseCallback({});
                    return;
                }

                handler(JSON.parse(data), responseCallback);
            }

            win.WebViewJavascriptBridge = {
                registerHandler: registerHandler,
                callHandler: callHandler,
                _callJSCallback: _callJSCallback,
                _callJSHandler: _callJSHandler
            };
        }
    }

    // Support for native calls on Android
    if (axemas.getPlatform() == 'android') {
        if (!win.WebViewJavascriptBridge) {
            var messageHandlers = {};
            var responseCallbacks = {};
            var uniqueId = 1;

            var callHandler = function (handlerName, data, responseCallback) {
                var message = {
                    data: data
                };
                if (responseCallback) {
                    var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
                    responseCallbacks[callbackId] = responseCallback;
                    message['callbackId'] = callbackId;
                }

                var encodedMessage = JSON.stringify(message);
                AndroidNativeJS.callAndroid(handlerName, encodedMessage);
            };

            var registerHandler = function (handlerName, handler) {
                messageHandlers[handlerName] = handler;
            };

            var _callJSCallback = function (callbackId, data) {
                var responseCallback = responseCallbacks[callbackId];
                if (!responseCallback) {
                    return;
                }
                delete responseCallbacks[callbackId];
                responseCallback(data);
            };

            var _callJSHandler = function (handlerName, data, callbackId) {
                var responseCallback = function (responseData) {
                    AndroidNativeJS.callAndroidCallback(callbackId,
                        JSON.stringify(responseData));
                }

                var handler = messageHandlers[handlerName];
                if (!handler) {
                    console.log('No handler in place for message', data);
                    responseCallback({});
                    return;
                }

                handler(data, responseCallback);
            }

            win.WebViewJavascriptBridge = {
                registerHandler: registerHandler,
                callHandler: callHandler,
                _callJSCallback: _callJSCallback,
                _callJSHandler: _callJSHandler
            };
        }
    }

    // Support for native calls on iOS
    if (axemas.getPlatform() == 'ios') {
        if (!win.WebViewJavascriptBridge) {
            var messagingIframe
            var sendMessageQueue = []
            var receiveMessageQueue = []
            var messageHandlers = {}

            var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'
            var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

            var responseCallbacks = {}
            var uniqueId = 1

            var _createQueueReadyIframe = function (doc) {
                messagingIframe = doc.createElement('iframe')
                messagingIframe.style.display = 'none'
                doc.documentElement.appendChild(messagingIframe)
            }

            var send = function (data, responseCallback) {
                _doSend({
                    data: data
                }, responseCallback)
            }

            var registerHandler = function (handlerName, handler) {
                messageHandlers[handlerName] = handler
            }

            var callHandler = function (handlerName, data, responseCallback) {
                _doSend({
                    handlerName: handlerName,
                    data: data
                }, responseCallback)
            }

            var _doSend = function (message, responseCallback) {
                if (responseCallback) {
                    var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime()
                    responseCallbacks[callbackId] = responseCallback
                    message['callbackId'] = callbackId
                }
                sendMessageQueue.push(message)
                messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
            }

            var _fetchQueue = function () {
                var messageQueueString = JSON.stringify(sendMessageQueue)
                sendMessageQueue = []
                return messageQueueString
            }

            var _defaultHandler = function (data, responseCallback) {
                console.log('No handler in place for message', data);
                if (responseCallback)
                    responseCallback(null);
            }

            var _dispatchMessageFromObjC = function (messageJSON) {
                setTimeout(function _timeoutDispatchMessageFromObjC() {
                    var message = JSON.parse(messageJSON)
                    var messageHandler

                    if (message.responseId) {
                        var responseCallback = responseCallbacks[message.responseId]
                        if (!responseCallback) {
                            return;
                        }
                        delete responseCallbacks[message.responseId]
                        responseCallback(message.responseData)
                    } else {
                        var responseCallback
                        if (message.callbackId) {
                            var callbackResponseId = message.callbackId
                            responseCallback = function (responseData) {
                                _doSend({
                                    responseId: callbackResponseId,
                                    responseData: responseData
                                })
                            }
                        }

                        var handler = _defaultHandler
                        if (message.handlerName) {
                            handler = messageHandlers[message.handlerName]
                        }

                        try {
                            handler(message.data, responseCallback)
                        } catch (exception) {
                            if (typeof console != 'undefined') {
                                console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
                            }
                        }
                    }
                })
            }

            var _handleMessageFromObjC = function (messageJSON) {
                _dispatchMessageFromObjC(messageJSON)
            }

            win.WebViewJavascriptBridge = {
                send: send,
                registerHandler: registerHandler,
                callHandler: callHandler,
                _fetchQueue: _fetchQueue,
                _handleMessageFromObjC: _handleMessageFromObjC
            }

            var doc = win.document
            _createQueueReadyIframe(doc)
        }
    }

})(window);

/* Those are needed only for WindowsPhone */
var __axemas_WebViewJavascriptBridge_callJSCallback = function (callbackId, data) {
    WebViewJavascriptBridge._callJSCallback(callbackId, data);
}
var __axemas_WebViewJavascriptBridge_callJSHandler = function (handlerName, data, callbackId) {
    WebViewJavascriptBridge._callJSHandler(handlerName, data, callbackId);
}<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 Google Inc., authors, and contributors <see AUTHORS file>
 * Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
 * Created By: dan@reciprocitylabs.com
 * Maintained By: dan@reciprocitylabs.com
 */

(function($, CMS, GGRC) {
  var WorkflowExtension = {},
      _workflow_object_types = [
        "Program",
        "Regulation", "Standard", "Policy", "Contract",
        "Objective", "Control", "Section", "Clause",
        "System", "Process",
        "DataAsset", "Facility", "Market", "Product", "Project"
      ];

  // Register `workflows` extension with GGRC
  GGRC.extensions.push(WorkflowExtension);

  WorkflowExtension.name = "workflows";

  // Register Workflow models for use with `infer_object_type`
  WorkflowExtension.object_type_decision_tree = function() {
    return {
      "cycle": CMS.Models.Cycle,
      "cycle_task_entry": CMS.Models.CycleTaskEntry,
      "cycle_task_group": CMS.Models.CycleTaskGroup,
      "cycle_task_group_object": CMS.Models.CycleTaskGroupObject,
      "cycle_task_group_object_task": CMS.Models.CycleTaskGroupObjectTask,

      "task": CMS.Models.Task,
      "task_group": CMS.Models.TaskGroup,
      "workflow": CMS.Models.Workflow
    };
  };

  // Configure mapping extensions for ggrc_workflows
  WorkflowExtension.init_mappings = function init_mappings() {
    var Proxy = GGRC.MapperHelpers.Proxy,
        Direct = GGRC.MapperHelpers.Direct,
        Cross = GGRC.MapperHelpers.Cross,
        CustomFilter = GGRC.MapperHelpers.CustomFilter,
        Reify = GGRC.MapperHelpers.Reify;
    // Add mappings for basic workflow objects
    var mappings = {
        Task: {
          _canonical: {
            subtasks: "Task",
            task_groups: "TaskGroup"
          },
          task_groups: Proxy(
            "TaskGroup", "task_group", "TaskGroupTask", "task", "task_group_tasks"),
        },

        TaskGroup: {
          _canonical: {
            tasks: "Task",
            objects: _workflow_object_types
          },
          task_group_tasks: Direct(
            "TaskGroupTask", "task_group", "task_group_tasks"),
          tasks: Proxy(
            "Task", "task", "TaskGroupTask", "task_group", "task_group_tasks"),
          objects: Proxy(
            null, "object", "TaskGroupObject", "task_group", "task_group_objects")
        },

        Workflow: {
          _canonical: {
            objects: _workflow_object_types,
            tasks: "Task",
            task_groups: "TaskGroup",
            people: "Person"
          },
          objects: Proxy(
            null, "object", "WorkflowObject", "workflow", "workflow_objects"),
          tasks: Proxy(
            "Task", "task", "WorkflowTask", "workflow", "workflow_tasks"),
          people: Proxy(
            "Person", "person", "WorkflowPerson", "workflow", "workflow_people"),
          task_groups: Direct(
            "TaskGroup", "workflow", "task_groups"),
          cycles: Direct(
            "Cycle", "workflow", "cycles"),
          previous_cycles: CustomFilter("cycles", function(result) {
              return result.instance.status == "Finished";
            }),
          current_cycle: CustomFilter("cycles", function(result) {
              return result.instance.status != "Finished";
            }),
          current_task_groups: Cross("current_cycle", "reify_cycle_task_groups")
        },

        Cycle: {
          cycle_task_groups: Direct(
            "CycleTaskGroup", "cycle", "cycle_task_groups"),
          reify_cycle_task_groups: Reify("cycle_task_groups")
        },

        CycleTaskGroup: {
          cycle: Direct(
            "Cycle", "cycle_task_groups", "cycle"),
          //task_group: Direct(
          //  "TaskGroup", "cycle", "tasks"),
          cycle_task_group_objects: Direct(
            "CycleTaskGroupObject",
            "cycle_task_group",
            "cycle_task_group_objects")
        },

        CycleTaskGroupObject: {
          cycle_task_group: Direct(
            "CycleTaskGroup", "cycle_task_group_objects", "cycle_task_group"),
          //task_group_object: Direct(
          //  "TaskGroupObject", "cycle", "tasks")
          cycle_task_group_object_tasks: Direct(
            "CycleTaskGroupObjectTask",
            "cycle_task_group_object",
            "cycle_task_group_object_tasks")
        },

        CycleTaskGroupObjectTask: {
          cycle_task_group_object: Direct(
            "CycleTaskGroupObject",
            "cycle_task_group_object_tasks",
            "cycle_task_group_object"),
          //task_group_object: Direct(
          //  "TaskGroupObject", "cycle", "tasks")
          cycle_task_entries: Direct(
            "CycleTaskEntry",
            "cycle_task_group_object_task",
            "cycle_task_entries")
        },

        CycleTaskEntry: {
          cycle_task_group_object_task: Direct(
            "CycleTaskGroupObjectTask",
            "cycle_task_entries",
            "cycle_task_group_object_task")
        },

        People: {
          _canonical: {
            workflows: "Workflow"
          },
          workflows: Proxy(
            "Workflow", "workflow", "WorkflowPerson", "person", "workflow_people"),
        }
      };

    // Insert `workflows` mappings to all business object types
    can.each(_workflow_object_types, function(type) {
      mappings[type] = {};
      mappings[type].workflows = new GGRC.ListLoaders.ProxyListLoader(
        "WorkflowObject", "object", "workflow", "workflow_objects", null);
      mappings[type].task_groups = new GGRC.ListLoaders.ProxyListLoader(
        "TaskGroupObject", "object", "task_group", "task_group_objects", null);
      //mappings[type]._canonical = {
      //  "workflows": "Workflow",
      //  "task_groups": "TaskGroup"
      //};
    });
    new GGRC.Mappings("ggrc_workflows", mappings);
  };

  // Override GGRC.extra_widget_descriptors and GGRC.extra_default_widgets
  // Initialize widgets for workflow page
  WorkflowExtension.init_widgets = function init_widgets() {
    var page_instance = GGRC.page_instance();

    if (page_instance instanceof CMS.Models.Workflow) {
      WorkflowExtension.init_widgets_for_workflow_page();
    } else if (page_instance instanceof CMS.Models.Task) {
      WorkflowExtension.init_widgets_for_task_page();
    } else {
      WorkflowExtension.init_widgets_for_other_pages();
    }
  };

  WorkflowExtension.init_widgets_for_other_pages =
      function init_widgets_for_other_pages() {
    var descriptor = {},
        page_instance = GGRC.page_instance();

    if (page_instance && ~can.inArray(page_instance.constructor.shortName, _workflow_object_types)) {
      descriptor[page_instance.constructor.shortName] = {
        workflow: {
          widget_id: "workflow",
          widget_name: "Workflows",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            mapping: "workflows",
            parent_instance: page_instance,
            model: CMS.Models.Workflow,
            show_view: GGRC.mustache_path + "/workflows/tree.mustache",
            footer_view: GGRC.mustache_path + "/base_objects/tree_footer.mustache"
          }
        }
      };
    }
    new GGRC.WidgetList("ggrc_workflows", descriptor);
  };

  WorkflowExtension.init_widgets_for_task_page =
      function init_widgets_for_task_page() {

    var task_widget_descriptors = {},
        new_default_widgets = [
          "info"
        ];

    can.each(GGRC.WidgetList.get_current_page_widgets(), function(descriptor, name) {
      if (~new_default_widgets.indexOf(name))
        task_widget_descriptors[name] = descriptor;
    });

    $.extend(
      true,
      task_widget_descriptors,
      {
        info: {
          content_controller: GGRC.Controllers.InfoWidget,
          content_controller_options: {
            widget_view: GGRC.mustache_path + "/tasks/info.mustache"
          }
        }
      }
    );

    new GGRC.WidgetList("ggrc_workflows", { Task: task_widget_descriptors });
  };

  WorkflowExtension.init_widgets_for_workflow_page =
      function init_widgets_for_workflow_page() {

    var new_widget_descriptors = {},
        new_default_widgets = [
          "info",
          "objects", "task", "person", "task_group",
          "history", "current"
        ],
        objects_widget_descriptor,
        history_widget_descriptor,
        current_widget_descriptor,
        object = GGRC.page_instance(),
        object_descriptors = {};

    can.each(GGRC.WidgetList.get_current_page_widgets(), function(descriptor, name) {
      if (~new_default_widgets.indexOf(name))
        new_widget_descriptors[name] = descriptor;
    });

    // Initialize controller -- probably this should go in a separate
    // initialization area
    $(function() {
      $(document.body).ggrc_controllers_workflow_page();
    });

    GGRC.register_hook(
        "ObjectNav.Actions",
        GGRC.mustache_path + "/dashboard/object_nav_actions");

    $.extend(
      true,
      new_widget_descriptors,
      {
        info: {
          content_controller: GGRC.Controllers.InfoWidget,
          content_controller_options: {
            widget_view: GGRC.mustache_path + "/workflows/info.mustache"
          }
        },
        task: {
          widget_id: "task",
          widget_name: "Tasks",
          widget_icon: "task",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            parent_instance: object,
            model: CMS.Models.Task,
            show_view: GGRC.mustache_path + "/tasks/tree.mustache",
            mapping: "tasks"
          }
        },
        person: {
          widget_id: "person",
          widget_name: "People",
          widget_icon: "person",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            parent_instance: object,
            model: CMS.Models.Person,
            mapping: "people"
          }
        },
        task_group: {
          widget_id: "task_group",
          widget_name: "Task Groups",
          widget_icon: "task_group",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            parent_instance: object,
            model: CMS.Models.TaskGroup,
            show_view: GGRC.mustache_path + "/task_groups/tree.mustache",
            mapping: "task_groups",
            draw_children: true,
            //note that we are using special naming for the tree views here.
            //  also, tasks for a task group aren't directly mapping to the
            //  tasks themselves but to the join object.  This is impotant
            //  since the join objects themselves have important attributes.
            child_options: [
              {
                model: can.Model.Cacheable,
                mapping: "objects",
                show_view: GGRC.mustache_path + "/base_objects/task_group_subtree.mustache",
                footer_view: GGRC.mustache_path + "/base_objects/task_group_subtree_footer.mustache"
              },
              {
                model: CMS.Models.Task,
                mapping: "task_group_tasks",
                show_view: GGRC.mustache_path + "/tasks/task_group_subtree.mustache",
                footer_view: GGRC.mustache_path + "/tasks/task_group_subtree_footer.mustache",
                sort_property: 'sort_index'
              }
            ]
          }
        }
      }
    );

    objects_widget_descriptor = {
      content_controller: CMS.Controllers.TreeView,
      content_controller_selector: "ul",
      widget_initial_content: '

',
      widget_id: "objects",
      widget_name: "Objects",
      widget_icon: "object",
      //object_category: "objects",
      //model: can.Model.Cacheable //far_model,
      content_controller_options: {
        child_options: [],
        draw_children: false,
        parent_instance: object,
        model: can.Model.Cacheable,
        mapping: "objects",
        //show_view: GGRC.mustache_path + "/sections/tree.mustache",
        footer_view: GGRC.mustache_path + "/base_objects/tree_footer.mustache"
      }
    };
    history_widget_descriptor = {
      content_controller: CMS.Controllers.TreeView,
      content_controller_selector: "ul",
      widget_initial_content: '

',
      widget_id: "history",
      widget_name: "History",
      widget_icon: "history",
      //object_category: "history",
      content_controller_options: {
        draw_children: true,
        parent_instance: object,
        model: "Cycle",
        mapping: "current_cycle",
      }
    };
    current_widget_descriptor = {
      content_controller: CMS.Controllers.TreeView,
      content_controller_selector: "ul",
      widget_initial_content: '

',
      widget_id: "current",
      widget_name: "Current Cycle",
      widget_icon: "cycle",
      //object_category: "history",
      content_controller_options: {
        draw_children: true,
        parent_instance: object,
        model: "CycleTaskGroup",
        mapping: "current_task_groups",
        header_view: GGRC.mustache_path + "/cycle_task_groups/current_cycle_header.mustache"
      }
    };
    new_widget_descriptors.objects = objects_widget_descriptor;
    new_widget_descriptors.history = history_widget_descriptor;
    new_widget_descriptors.current = current_widget_descriptor;

    new GGRC.WidgetList("ggrc_workflows", { Workflow: new_widget_descriptors });
  }

  GGRC.register_hook(
      "LHN.Sections", GGRC.mustache_path + "/dashboard/lhn_workflows");

  WorkflowExtension.init_mappings();

})(this.can.$, this.CMS, this.GGRC);
<commit_msg>Map workflow and task group to audit objects<commit_after>/*
 * Copyright (C) 2013 Google Inc., authors, and contributors <see AUTHORS file>
 * Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
 * Created By: dan@reciprocitylabs.com
 * Maintained By: dan@reciprocitylabs.com
 */

(function($, CMS, GGRC) {
  var WorkflowExtension = {},
      _workflow_object_types = [
        "Program",
        "Regulation", "Standard", "Policy", "Contract",
        "Objective", "Control", "Section", "Clause",
        "System", "Process",
        "DataAsset", "Facility", "Market", "Product", "Project"
      ];

  // Register `workflows` extension with GGRC
  GGRC.extensions.push(WorkflowExtension);

  WorkflowExtension.name = "workflows";

  // Register Workflow models for use with `infer_object_type`
  WorkflowExtension.object_type_decision_tree = function() {
    return {
      "cycle": CMS.Models.Cycle,
      "cycle_task_entry": CMS.Models.CycleTaskEntry,
      "cycle_task_group": CMS.Models.CycleTaskGroup,
      "cycle_task_group_object": CMS.Models.CycleTaskGroupObject,
      "cycle_task_group_object_task": CMS.Models.CycleTaskGroupObjectTask,

      "task": CMS.Models.Task,
      "task_group": CMS.Models.TaskGroup,
      "workflow": CMS.Models.Workflow
    };
  };

  // Configure mapping extensions for ggrc_workflows
  WorkflowExtension.init_mappings = function init_mappings() {
    var Proxy = GGRC.MapperHelpers.Proxy,
        Direct = GGRC.MapperHelpers.Direct,
        Cross = GGRC.MapperHelpers.Cross,
        CustomFilter = GGRC.MapperHelpers.CustomFilter,
        Reify = GGRC.MapperHelpers.Reify;
    // Add mappings for basic workflow objects
    var mappings = {
        Task: {
          _canonical: {
            subtasks: "Task",
            task_groups: "TaskGroup"
          },
          task_groups: Proxy(
            "TaskGroup", "task_group", "TaskGroupTask", "task", "task_group_tasks"),
        },

        TaskGroup: {
          _canonical: {
            tasks: "Task",
            objects: _workflow_object_types
          },
          task_group_tasks: Direct(
            "TaskGroupTask", "task_group", "task_group_tasks"),
          tasks: Proxy(
            "Task", "task", "TaskGroupTask", "task_group", "task_group_tasks"),
          objects: Proxy(
            null, "object", "TaskGroupObject", "task_group", "task_group_objects")
        },

        Workflow: {
          _canonical: {
            objects: _workflow_object_types,
            tasks: "Task",
            task_groups: "TaskGroup",
            people: "Person"
          },
          objects: Proxy(
            null, "object", "WorkflowObject", "workflow", "workflow_objects"),
          tasks: Proxy(
            "Task", "task", "WorkflowTask", "workflow", "workflow_tasks"),
          people: Proxy(
            "Person", "person", "WorkflowPerson", "workflow", "workflow_people"),
          task_groups: Direct(
            "TaskGroup", "workflow", "task_groups"),
          cycles: Direct(
            "Cycle", "workflow", "cycles"),
          previous_cycles: CustomFilter("cycles", function(result) {
              return result.instance.status == "Finished";
            }),
          current_cycle: CustomFilter("cycles", function(result) {
              return result.instance.status != "Finished";
            }),
          current_task_groups: Cross("current_cycle", "reify_cycle_task_groups")
        },

        Cycle: {
          cycle_task_groups: Direct(
            "CycleTaskGroup", "cycle", "cycle_task_groups"),
          reify_cycle_task_groups: Reify("cycle_task_groups")
        },

        CycleTaskGroup: {
          cycle: Direct(
            "Cycle", "cycle_task_groups", "cycle"),
          //task_group: Direct(
          //  "TaskGroup", "cycle", "tasks"),
          cycle_task_group_objects: Direct(
            "CycleTaskGroupObject",
            "cycle_task_group",
            "cycle_task_group_objects")
        },

        CycleTaskGroupObject: {
          cycle_task_group: Direct(
            "CycleTaskGroup", "cycle_task_group_objects", "cycle_task_group"),
          //task_group_object: Direct(
          //  "TaskGroupObject", "cycle", "tasks")
          cycle_task_group_object_tasks: Direct(
            "CycleTaskGroupObjectTask",
            "cycle_task_group_object",
            "cycle_task_group_object_tasks")
        },

        CycleTaskGroupObjectTask: {
          cycle_task_group_object: Direct(
            "CycleTaskGroupObject",
            "cycle_task_group_object_tasks",
            "cycle_task_group_object"),
          //task_group_object: Direct(
          //  "TaskGroupObject", "cycle", "tasks")
          cycle_task_entries: Direct(
            "CycleTaskEntry",
            "cycle_task_group_object_task",
            "cycle_task_entries")
        },

        CycleTaskEntry: {
          cycle_task_group_object_task: Direct(
            "CycleTaskGroupObjectTask",
            "cycle_task_entries",
            "cycle_task_group_object_task")
        },

        People: {
          _canonical: {
            workflows: "Workflow"
          },
          workflows: Proxy(
            "Workflow", "workflow", "WorkflowPerson", "person", "workflow_people"),
        }
      };

    // Insert `workflows` mappings to all business object types
    can.each(_workflow_object_types, function(type) {
      mappings[type] = {};
      mappings[type].workflows = new GGRC.ListLoaders.ProxyListLoader(
        "WorkflowObject", "object", "workflow", "workflow_objects", null);
      mappings[type].task_groups = new GGRC.ListLoaders.ProxyListLoader(
        "TaskGroupObject", "object", "task_group", "task_group_objects", null);
      mappings[type]._canonical = {
       "workflows": "Workflow",
       "task_groups": "TaskGroup"
      };
    });
    new GGRC.Mappings("ggrc_workflows", mappings);
  };

  // Override GGRC.extra_widget_descriptors and GGRC.extra_default_widgets
  // Initialize widgets for workflow page
  WorkflowExtension.init_widgets = function init_widgets() {
    var page_instance = GGRC.page_instance();

    if (page_instance instanceof CMS.Models.Workflow) {
      WorkflowExtension.init_widgets_for_workflow_page();
    } else if (page_instance instanceof CMS.Models.Task) {
      WorkflowExtension.init_widgets_for_task_page();
    } else {
      WorkflowExtension.init_widgets_for_other_pages();
    }
  };

  WorkflowExtension.init_widgets_for_other_pages =
      function init_widgets_for_other_pages() {
    var descriptor = {},
        page_instance = GGRC.page_instance();

    if (page_instance && ~can.inArray(page_instance.constructor.shortName, _workflow_object_types)) {
      descriptor[page_instance.constructor.shortName] = {
        workflow: {
          widget_id: "workflow",
          widget_name: "Workflows",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            mapping: "workflows",
            parent_instance: page_instance,
            model: CMS.Models.Workflow,
            show_view: GGRC.mustache_path + "/workflows/tree.mustache",
            footer_view: GGRC.mustache_path + "/base_objects/tree_footer.mustache"
          }
        }
      };
    }
    new GGRC.WidgetList("ggrc_workflows", descriptor);
  };

  WorkflowExtension.init_widgets_for_task_page =
      function init_widgets_for_task_page() {

    var task_widget_descriptors = {},
        new_default_widgets = [
          "info"
        ];

    can.each(GGRC.WidgetList.get_current_page_widgets(), function(descriptor, name) {
      if (~new_default_widgets.indexOf(name))
        task_widget_descriptors[name] = descriptor;
    });

    $.extend(
      true,
      task_widget_descriptors,
      {
        info: {
          content_controller: GGRC.Controllers.InfoWidget,
          content_controller_options: {
            widget_view: GGRC.mustache_path + "/tasks/info.mustache"
          }
        }
      }
    );

    new GGRC.WidgetList("ggrc_workflows", { Task: task_widget_descriptors });
  };

  WorkflowExtension.init_widgets_for_workflow_page =
      function init_widgets_for_workflow_page() {

    var new_widget_descriptors = {},
        new_default_widgets = [
          "info",
          "objects", "task", "person", "task_group",
          "history", "current"
        ],
        objects_widget_descriptor,
        history_widget_descriptor,
        current_widget_descriptor,
        object = GGRC.page_instance(),
        object_descriptors = {};

    can.each(GGRC.WidgetList.get_current_page_widgets(), function(descriptor, name) {
      if (~new_default_widgets.indexOf(name))
        new_widget_descriptors[name] = descriptor;
    });

    // Initialize controller -- probably this should go in a separate
    // initialization area
    $(function() {
      $(document.body).ggrc_controllers_workflow_page();
    });

    GGRC.register_hook(
        "ObjectNav.Actions",
        GGRC.mustache_path + "/dashboard/object_nav_actions");

    $.extend(
      true,
      new_widget_descriptors,
      {
        info: {
          content_controller: GGRC.Controllers.InfoWidget,
          content_controller_options: {
            widget_view: GGRC.mustache_path + "/workflows/info.mustache"
          }
        },
        task: {
          widget_id: "task",
          widget_name: "Tasks",
          widget_icon: "task",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            parent_instance: object,
            model: CMS.Models.Task,
            show_view: GGRC.mustache_path + "/tasks/tree.mustache",
            mapping: "tasks"
          }
        },
        person: {
          widget_id: "person",
          widget_name: "People",
          widget_icon: "person",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            parent_instance: object,
            model: CMS.Models.Person,
            mapping: "people"
          }
        },
        task_group: {
          widget_id: "task_group",
          widget_name: "Task Groups",
          widget_icon: "task_group",
          content_controller: GGRC.Controllers.TreeView,
          content_controller_options: {
            parent_instance: object,
            model: CMS.Models.TaskGroup,
            show_view: GGRC.mustache_path + "/task_groups/tree.mustache",
            mapping: "task_groups",
            draw_children: true,
            //note that we are using special naming for the tree views here.
            //  also, tasks for a task group aren't directly mapping to the
            //  tasks themselves but to the join object.  This is impotant
            //  since the join objects themselves have important attributes.
            child_options: [
              {
                model: can.Model.Cacheable,
                mapping: "objects",
                show_view: GGRC.mustache_path + "/base_objects/task_group_subtree.mustache",
                footer_view: GGRC.mustache_path + "/base_objects/task_group_subtree_footer.mustache"
              },
              {
                model: CMS.Models.Task,
                mapping: "task_group_tasks",
                show_view: GGRC.mustache_path + "/tasks/task_group_subtree.mustache",
                footer_view: GGRC.mustache_path + "/tasks/task_group_subtree_footer.mustache",
                sort_property: 'sort_index'
              }
            ]
          }
        }
      }
    );

    objects_widget_descriptor = {
      content_controller: CMS.Controllers.TreeView,
      content_controller_selector: "ul",
      widget_initial_content: '

',
      widget_id: "objects",
      widget_name: "Objects",
      widget_icon: "object",
      //object_category: "objects",
      //model: can.Model.Cacheable //far_model,
      content_controller_options: {
        child_options: [],
        draw_children: false,
        parent_instance: object,
        model: can.Model.Cacheable,
        mapping: "objects",
        //show_view: GGRC.mustache_path + "/sections/tree.mustache",
        footer_view: GGRC.mustache_path + "/base_objects/tree_footer.mustache"
      }
    };
    history_widget_descriptor = {
      content_controller: CMS.Controllers.TreeView,
      content_controller_selector: "ul",
      widget_initial_content: '

',
      widget_id: "history",
      widget_name: "History",
      widget_icon: "history",
      //object_category: "history",
      content_controller_options: {
        draw_children: true,
        parent_instance: object,
        model: "Cycle",
        mapping: "current_cycle",
      }
    };
    current_widget_descriptor = {
      content_controller: CMS.Controllers.TreeView,
      content_controller_selector: "ul",
      widget_initial_content: '

',
      widget_id: "current",
      widget_name: "Current Cycle",
      widget_icon: "cycle",
      //object_category: "history",
      content_controller_options: {
        draw_children: true,
        parent_instance: object,
        model: "CycleTaskGroup",
        mapping: "current_task_groups",
        header_view: GGRC.mustache_path + "/cycle_task_groups/current_cycle_header.mustache"
      }
    };
    new_widget_descriptors.objects = objects_widget_descriptor;
    new_widget_descriptors.history = history_widget_descriptor;
    new_widget_descriptors.current = current_widget_descriptor;

    new GGRC.WidgetList("ggrc_workflows", { Workflow: new_widget_descriptors });
  }

  GGRC.register_hook(
      "LHN.Sections", GGRC.mustache_path + "/dashboard/lhn_workflows");

  WorkflowExtension.init_mappings();

})(this.can.$, this.CMS, this.GGRC);
<|endoftext|>


<commit_before>$(document).ready(function() {

    function login_user()
    {
        $.post("lib/php/auth/login.php", $("#getin").serialize(), function(data) {

            var response = $.parseJSON(data);
            if (response.login == 'true')
            {
                $("#status").html(response.message);
                window.location = response.url;
            }

            else

            {
                $("#status").html(response.message);
                $("input").addClass('error');
            }

        });
    }

    //Deal with IE
    if ($('html').hasClass('ie7') || $('html').hasClass('ie6'))
    {
        var warn = "
Sorry, your browser is out of date and cannot display ClinicCases.  Please either update your version of Internet Explorer or, even better, install Google Chrome.
";
        $('#content').html(warn);
    }

    //Round corner
    $('div.wrapper').addClass('ui-corner-all');

    //Lost password form
    $('a.lost_password').click(function(event) {
        event.preventDefault();
        var forgotForm = '
' +
        '' +
        '

<form>' +
        'Please provide your email address:' +
        '<input type="text" name="email" value="">' +
        '

<button>Cancel</button><button>Submit</button></form>
';

        $(forgotForm).dialog({
            autoOpen: true,
            height: 300,
            width: 325,
            modal: true,
            open: function()
            {
                var diag = $(this);

                //Close
                $('a.forgot_close').click(function(event) {
                    event.preventDefault();
                    $('div.bug_form').remove();
                    diag.dialog("destroy");
                });

                //Cancel
                $('div.forgot_form button:first').click(function(event) {
                    event.preventDefault();
                    $('div.forgot_form').remove();
                    diag.dialog("destroy");
                });

                //Submit
                $('div.forgot_form button:first').next().click(function(event) {
                    event.preventDefault();
                    $('#status').remove();
                    var email = $(this).siblings('input').val();
                    $.post('lib/php/auth/lost_password.php', ({'email': email}), function(data) {
                        var serverResponse = $.parseJSON(data);
                        $('div.forgot_form').remove();
                        diag.dialog("destroy");
                        notify(serverResponse.message, true);
                    });
                });
            }

        }).siblings('.ui-dialog-titlebar').remove();

    });

    $("#login_button").bind('click', function(event) {
        event.preventDefault();
        login_user();
    });

    $("#status").ajaxSend(function(){
        $(this).text('Sending...');
    });

    $("#status").ajaxError(function(){
        $(this).text('Sorry, system error.');
    });

    $("#password").keyup(function(event) {
        event.preventDefault();
        if (event.keyCode == '13') {
            login_user();
        }
    });

});
<commit_msg>Minor changes to login script<commit_after>$(document).ready(function() {

    function login_user()
    {
        $.post("lib/php/auth/login.php", $("#getin").serialize(), function(data) {

            var response = $.parseJSON(data);
            if (response.login == 'true')
            {
                $("#status").html(response.message);
                window.location = response.url;
            }

            else

            {
                $("#status").html(response.message);
                $("input").addClass('error');
            }

        });
    }

    //Deal with IE
    if ($('html').hasClass('ie7') || $('html').hasClass('ie6'))
    {
        var warn = "
Sorry, your browser is out of date and cannot display ClinicCases.  Please either update your version of Internet Explorer or, even better, install Google Chrome.

If you are not allowed to add or modify software on your system, you can use Chrome Frame.";
        $('#content').html(warn);
    }

    //Round corner
    $('div.wrapper').addClass('ui-corner-all');

    //Lost password form
    $('a.lost_password').click(function(event) {
        event.preventDefault();
        var forgotForm = '
' +
        '' +
        '

<form>' +
        'Please provide your email address:' +
        '<input type="text" name="email" value="">' +
        '

<button>Cancel</button><button>Submit</button></form>
';

        $(forgotForm).dialog({
            autoOpen: true,
            height: 300,
            width: 325,
            modal: true,
            open: function()
            {
                var diag = $(this);

                //Close
                $('a.forgot_close').click(function(event) {
                    event.preventDefault();
                    $('div.bug_form').remove();
                    diag.dialog("destroy");
                });

                //Cancel
                $('div.forgot_form button:first').click(function(event) {
                    event.preventDefault();
                    $('div.forgot_form').remove();
                    diag.dialog("destroy");
                });

                //Submit
                $('div.forgot_form button:first').next().click(function(event) {
                    event.preventDefault();
                    $('#status').remove();
                    var email = $(this).siblings('input').val();
                    $.post('lib/php/auth/lost_password.php', ({'email': email}), function(data) {
                        var serverResponse = $.parseJSON(data);
                        $('div.forgot_form').remove();
                        diag.dialog("destroy");
                        notify(serverResponse.message, true);
                    });
                });
            }

        }).siblings('.ui-dialog-titlebar').remove();

    });

    $("#login_button").bind('click', function(event) {
        event.preventDefault();
        login_user();
    });

    $("#password").keyup(function(event) {
        event.preventDefault();
        if (event.keyCode == '13') {
            login_user();
        }
    });

});
<|endoftext|>


<commit_before>(function(µ,SMOD,GMOD,HMOD,SC){

	var gui=require("MorgasGui");
	var Concat=require("concat-with-sourcemaps");

	SC=SC({
		dependencyParser:require.bind(null,"Morgas/lib/dependencyParser"),
		morgasModuleRegister:"Morgas.ModuleRegister",
		morgasModuleDependencies:"Morgas.ModuleDependencies",
		morgasGuiModuleRegister:"Morgas.gui.ModuleRegister",
		morgasGuiModuleDependencies:"Morgas.gui.ModuleDependencies",
		DependencyResolver:"DepRes",
		File:"File"
	});

	var normalizePath=function(path){return path.replace(/\\/g,"/");};
	var morgasJsFile=normalizePath(new SC.File(µ.dirname).changePath("Morgas.js").getAbsolutePath());

	module.exports=function(files,relativeTo)
	{
		relativeTo=SC.File.stringToFile(relativeTo);

		var replaceDirs=[
			[normalizePath(relativeTo.getAbsolutePath()),"."],
			[normalizePath(µ.dirname)+"/","/morgas/"],
			[normalizePath(gui.dirname)+"/","/morgas/gui/"]
		];

		var resouceMap=new Map();

		var parser=(new SC.dependencyParser()).addSources(files)
		.addModuleRegister(SC.morgasModuleRegister,µ.dirname).addModuleDependencies(SC.morgasModuleDependencies,µ.dirname)
		.addModuleRegister(SC.morgasGuiModuleRegister,gui.dirname).addModuleDependencies(SC.morgasGuiModuleDependencies,gui.dirname);

		var getFile=function(path)
		{
			if(resouceMap.has(path[0]))
			{
				path=path.slice();
				path[0]=resouceMap.get(path[0]);
			}
			return relativeTo.clone().changePath(path.join("/"));
		};

		var restService=function(param)
		{
			if(param.path.length==0||param.path[0]=="")
			{
				return parser.parse()
				.then(function(rtn)
				{
					rtn.providedModules={};
					for(let key in parser.moduleRegister) rtn.providedModules[key]=parser.moduleRegister[key].getAbsolutePath();
					rtn.providedDependencies={};
					for(let key in parser.moduleDependencies) rtn.providedDependencies[key]=parser.moduleDependencies[key];
					rtn.providedFileDependencies={};
					for(let key in parser.fileDependencies) rtn.providedFileDependencies[key]=parser.fileDependencies[key];
					if(param.query.file)
					{
						var resolver=new SC.DependencyResolver(rtn.fileDependencies);
						let files=[].concat(param.query.file).map(f=>normalizePath(getFile(f.split("/")).getAbsolutePath()));
						rtn.order=resolver.resolve([morgasJsFile,...files]);
					}
					return rtn;
				});
			}

			var file=getFile(param.path);

			return file.exists()
			.then(function()
			{
				if("raw" in param.query)  return file.read({encoding:"UTF-8"});

				return parser.parse()
				.then(function(result)
				{
					var resolver=new SC.DependencyResolver(result.fileDependencies);
					var files=resolver.resolve([morgasJsFile,normalizePath(file.getAbsolutePath())]);
					return Promise.all(files.map(f=>SC.File.stringToFile(f).read().then(data=>[f,data])))
					.then(function(fileContents)
					{
						var concat=new Concat(true,param.path.join("/"),"\n/********************/\n");
						for (var [name,data] of fileContents)
						{
							let replaced=false;
							for(let[filepath,replacement] of replaceDirs)
							{
								if(name.startsWith(filepath))
								{
									name=name.replace(filepath,replacement);
									replaced=true;
									break;
								}
							}
							if(!replaced)
							{
								for(let[replacement,filepath] of resouceMap)
								{
									if(name.startsWith(filepath))
									{
										name=name.replace(filepath,replacement);
										break;
									}
								}
							}
							name+="?raw";
							concat.add(name,data);
						}
						return concat.content+"\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + new Buffer(concat.sourceMap).toString("base64")
					});
				})
				//.then(files=>files.join("\n/********************/\n"));
			},()=>
			{
				param.status=404;
				return param.path+" NOT FOUND";
			});
		};

		let resourceCounter=0;
		restService.addResource=function(moduleRegister,moduleDependencies,directory,name)
		{
			if(!directory) throw "no directory";
			directory=SC.File.stringToFile(directory);
			if(moduleRegister) parser.addModuleRegister(moduleRegister,directory);
			parser.addModuleDependencies(moduleDependencies,directory);

			if(!name) name="resource_"+(resourceCounter++);
			if(resouceMap.has(name))
			{
				µ.logger.warn(`resource name "${name}" already in use`);
				name+="_"+(resourceCounter++);
			}
			resouceMap.set(name,normalizePath(directory.getAbsolutePath()));
		};
		return restService;
	}

})(Morgas,Morgas.setModule,Morgas.getModule,Morgas.hasModule,Morgas.shortcut);<commit_msg>dependencies<commit_after>(function(µ,SMOD,GMOD,HMOD,SC){

	var gui=require("MorgasGui");
	var Concat=require("concat-with-sourcemaps");

	SC=SC({
		dependencyParser:require.bind(null,"Morgas/lib/dependencyParser"),
		morgasModuleRegister:"Morgas.ModuleRegister",
		morgasModuleDependencies:"Morgas.ModuleDependencies",
		morgasGuiModuleRegister:"Morgas.gui.ModuleRegister",
		morgasGuiModuleDependencies:"Morgas.gui.ModuleDependencies",
		DependencyResolver:"DepRes",
		File:"File"
	});

	var normalizePath=function(path){return path.replace(/\\/g,"/");};
	var morgasJsFile=normalizePath(new SC.File(µ.dirname).changePath("Morgas.js").getAbsolutePath());

	module.exports=function(files,relativeTo)
	{
		relativeTo=SC.File.stringToFile(relativeTo);

		var replaceDirs=[
			[normalizePath(relativeTo.getAbsolutePath()),"."],
			[normalizePath(µ.dirname)+"/","/morgas/"],
			[normalizePath(gui.dirname)+"/","/morgas/gui/"]
		];

		var resouceMap=new Map();

		var parser=(new SC.dependencyParser()).addSources(files)
		.addModuleRegister(SC.morgasModuleRegister,µ.dirname).addModuleDependencies(SC.morgasModuleDependencies,µ.dirname)
		.addModuleRegister(SC.morgasGuiModuleRegister,gui.dirname).addModuleDependencies(SC.morgasGuiModuleDependencies,gui.dirname);

		var getFile=function(path)
		{
			if(resouceMap.has(path[0]))
			{
				path=path.slice();
				path[0]=resouceMap.get(path[0]);
			}
			return relativeTo.clone().changePath(path.join("/"));
		};

		var restService=function(param)
		{
			if(param.path.length==0||param.path[0]=="")
			{
				return parser.parse()
				.then(function(rtn)
				{
					rtn.providedModules={};
					for(let key in parser.moduleRegister) rtn.providedModules[key]=parser.moduleRegister[key].getAbsolutePath();
					rtn.providedDependencies={};
					for(let key in parser.moduleDependencies) rtn.providedDependencies[key]=parser.moduleDependencies[key];
					rtn.providedFileDependencies={};
					for(let key in parser.fileDependencies) rtn.providedFileDependencies[key]=parser.fileDependencies[key];
					if(param.query.file)
					{
						var resolver=new SC.DependencyResolver(rtn.fileDependencies);
						let files=[].concat(param.query.file).map(f=>normalizePath(getFile(f.split("/")).getAbsolutePath()));
						rtn.order=resolver.resolve([...files]);
						rtn.order.unshift(morgasJsFile);
					}
					return rtn;
				});
			}

			var file=getFile(param.path);

			return file.exists()
			.then(function()
			{
				if("raw" in param.query)  return file.read({encoding:"UTF-8"});

				return parser.parse()
				.then(function(result)
				{
					var resolver=new SC.DependencyResolver(result.fileDependencies);
					var files=resolver.resolve([normalizePath(file.getAbsolutePath())]);
					files.unshift(morgasJsFile);
					return Promise.all(files.map(f=>SC.File.stringToFile(f).read().then(data=>[f,data])))
					.then(function(fileContents)
					{
						var concat=new Concat(true,param.path.join("/"),"\n/********************/\n");
						for (var [name,data] of fileContents)
						{
							let replaced=false;
							for(let[filepath,replacement] of replaceDirs)
							{
								if(name.startsWith(filepath))
								{
									name=name.replace(filepath,replacement);
									replaced=true;
									break;
								}
							}
							if(!replaced)
							{
								for(let[replacement,filepath] of resouceMap)
								{
									if(name.startsWith(filepath))
									{
										name=name.replace(filepath,replacement);
										break;
									}
								}
							}
							name+="?raw";
							concat.add(name,data);
						}
						return concat.content+"\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + new Buffer(concat.sourceMap).toString("base64")
					});
				})
				//.then(files=>files.join("\n/********************/\n"));
			},()=>
			{
				param.status=404;
				return param.path+" NOT FOUND";
			});
		};

		let resourceCounter=0;
		restService.addResource=function(moduleRegister,moduleDependencies,directory,name)
		{
			if(!directory) throw "no directory";
			directory=SC.File.stringToFile(directory);
			if(moduleRegister) parser.addModuleRegister(moduleRegister,directory);
			parser.addModuleDependencies(moduleDependencies,directory);

			if(!name) name="resource_"+(resourceCounter++);
			if(resouceMap.has(name))
			{
				µ.logger.warn(`resource name "${name}" already in use`);
				name+="_"+(resourceCounter++);
			}
			resouceMap.set(name,normalizePath(directory.getAbsolutePath()));
		};
		return restService;
	}

})(Morgas,Morgas.setModule,Morgas.getModule,Morgas.hasModule,Morgas.shortcut);<|endoftext|>


<commit_before>
var	fs = require('fs-extra'),
	marked = require('marked'),
	path = require('path'),
	pkg = fs.readJsonSync(path.join(__dirname + '/../package.json')),
    	nbbId = pkg.name.replace(/nodebb-plugin-/, ''),
	Plugin = {
		admin: {
			menu: function(custom_header) {
				custom_header.plugins.push({
					"route": '/plugins/' + nbbId,
					"icon": 'icon-edit',
					"name": nbbId
				});

				return custom_header;
			},
			route: function(custom_routes, callback) {
				fs.readFile(path.join(__dirname, '../README.md'), function(err, tpl) {
					marked(tpl.toString(), function(err, content){
						if (err) throw err;

						custom_routes.routes.push({
							route: '/plugins/' + nbbId,
							method: "get",
							options: function(req, res, callback) {
								callback({
									req: req,
									res: res,
									route: '/plugins/' + nbbId,
									name: Plugin,
									content: content
								});
							}
						});

						callback(null, custom_routes);
					});
				});
			}
		}
	};

module.exports = Plugin;
<commit_msg>Update index.js<commit_after>
var	fs = require('fs-extra'),
	marked = require('marked'),
	path = require('path'),
	pkg = fs.readJsonSync(path.join(__dirname + '/../package.json')),
    	nbbId = pkg.name.replace(/nodebb-plugin-/, ''),
	Plugin = {
		admin: {
			menu: function(custom_header) {
				custom_header.plugins.push({
					"route": '/plugins/' + nbbId,
					"icon": 'icon-edit',
					"name": nbbId
				});

				return custom_header;
			},
			route: function(custom_routes, callback) {
				fs.readFile(path.join(__dirname, '/../README.md'), function(err, tpl) {
					marked(tpl.toString(), function(err, content){
						if (err) throw err;

						custom_routes.routes.push({
							route: '/plugins/' + nbbId,
							method: "get",
							options: function(req, res, callback) {
								callback({
									req: req,
									res: res,
									route: '/plugins/' + nbbId,
									name: Plugin,
									content: content
								});
							}
						});

						callback(null, custom_routes);
					});
				});
			}
		}
	};

module.exports = Plugin;
<|endoftext|>


<commit_before>'use strict';
/* eslint-disable max-len*/
require('./lib/index.js');
const _ = require('lodash');
const middleware = require('../middleware/errorMiddleware');
const utils = require('../utils');
const errors = utils.errors;
const NotFound = errors.NotFound;

class Response {
  constructor() {
    this.constructor.methods.forEach((method) => {
      this[method] = sinon.spy(() => {
        return this;
      });
    });
  }
  reset() {
    this.constructor.methods.forEach((method) => {
      this[method].reset();
    });
  }
  static get methods() {
    return ['set', 'status', 'sendStatus', 'send', 'json', 'render', 'format'];
  }
}

describe('middleware', () => {
  /* eslint no-unused-expressions:0 */
  const req = {
    method: 'GET',
    path: '/foo'
  };

  const res = new Response();
  const next = sinon.spy();

  afterEach(() => {
    next.reset();
    res.reset();
  });

  describe.skip('#notFound', () => {
    const notFoundMiddleware = middleware.notFound();
    let msg = 'Unable to find any resource ' +
      `matching the requested path ${req.path}`;
    const notFoundError = new NotFound(msg);

    it('should always abort with a NotFound error', () => {
      notFoundMiddleware(req, res, next);
      sinon.assert.calledWithExactly(next, notFoundError);
    });
  });

  describe('#error', () => {
    const error = {
      message: 'a message',
      foo: 'bar',
      stack: 'a stack trace'
    };
    const originalError = _.clone(error);

    describe('with stack trace', () => {
      const errorWithStackMiddleware = middleware.error(true);
      const errorResponse = _.assign(
        _.pick(originalError, 'message', 'stack'), {
          status: 500
        });
      let responseFormatter;
      const errorResponseForHtml = { message: 'Internal Error', status: 500 };

      beforeEach(() => {
        errorWithStackMiddleware(error, req, res, next);
        expect(res.format).to.have.been.calledOnce;
        responseFormatter = _.first(res.format.firstCall.args);
      });

      it('should not modify the error object', () => {
        expect(error).to.eql(originalError);
      });

      it('should respond with status 500 by default', () => {
        expect(res.status, 500);
        expect(next).to.not.have.been.called;
      });

      it('should respond with an error in json format', () => {
        responseFormatter.json();
        sinon.assert.calledWithExactly(res.json, errorResponse);
        expect(next).to.not.have.been.called;
      });

      it('should respond with an error in html format', () => {
        responseFormatter.html();
        sinon.assert.calledWithExactly(res.render, 'error', errorResponseForHtml);
        expect(next).to.not.have.been.called;
      });

      it('should respond with an error in text format', () => {
        responseFormatter.text();
        let responseText = _.first(res.send.firstCall.args);
        let parsedResponseText = _.chain(responseText).split('\n').map(
          (line) => {
            let [key, val] = line.split(/:\s*/);
            return [key, key !== 'status' ? val : parseInt(val)];
          }).fromPairs().value();
        expect(parsedResponseText).to.eql(errorResponse);
        expect(next).to.not.have.been.called;
      });
    });

    describe('without stack trace', () => {
      const errorMiddleware = middleware.error({
        env: 'production'
      });
      let responseFormatter;

      beforeEach(() => {
        errorMiddleware(error, req, res, next);
        expect(res.format).to.have.been.calledOnce;
        responseFormatter = _.first(res.format.firstCall.args);
      });

      it('should respond with an error that does not include a stack trace',
        () => {
          responseFormatter.json();
          expect(
            _.first(res.json.firstCall.args)).to.not.have.property('stack');
        });
    });
  });
});
<commit_msg>Do not deep equal objects in dashboard test (#82)<commit_after>'use strict';
/* eslint-disable max-len*/
require('./lib/index.js');
const _ = require('lodash');
const middleware = require('../middleware/errorMiddleware');
const utils = require('../utils');
const errors = utils.errors;
const NotFound = errors.NotFound;

class Response {
  constructor() {
    this.constructor.methods.forEach((method) => {
      this[method] = sinon.spy(() => {
        return this;
      });
    });
  }
  reset() {
    this.constructor.methods.forEach((method) => {
      this[method].reset();
    });
  }
  static get methods() {
    return ['set', 'status', 'sendStatus', 'send', 'json', 'render', 'format'];
  }
}

describe('middleware', () => {
  /* eslint no-unused-expressions:0 */
  const req = {
    method: 'GET',
    path: '/foo'
  };

  const res = new Response();
  const next = sinon.spy();

  afterEach(() => {
    next.reset();
    res.reset();
  });

  describe('#notFound', () => {
    const notFoundMiddleware = middleware.notFound();
    let msg = 'Unable to find any resource ' +
      `matching the requested path '${req.path}'`;

    it('should always abort with a NotFound error', () => {
      notFoundMiddleware(req, res, next);
      sinon.assert.calledWith(next, sinon.match.instanceOf(NotFound)
        .and(sinon.match.has('message', msg)));
    });
  });

  describe('#error', () => {
    const error = {
      message: 'a message',
      foo: 'bar',
      stack: 'a stack trace'
    };
    const originalError = _.clone(error);

    describe('with stack trace', () => {
      const errorWithStackMiddleware = middleware.error(true);
      const errorResponse = _.assign(
        _.pick(originalError, 'message', 'stack'), {
          status: 500
        });
      let responseFormatter;
      const errorResponseForHtml = { message: 'Internal Error', status: 500 };

      beforeEach(() => {
        errorWithStackMiddleware(error, req, res, next);
        expect(res.format).to.have.been.calledOnce;
        responseFormatter = _.first(res.format.firstCall.args);
      });

      it('should not modify the error object', () => {
        expect(error).to.eql(originalError);
      });

      it('should respond with status 500 by default', () => {
        expect(res.status, 500);
        expect(next).to.not.have.been.called;
      });

      it('should respond with an error in json format', () => {
        responseFormatter.json();
        sinon.assert.calledWithExactly(res.json, errorResponse);
        expect(next).to.not.have.been.called;
      });

      it('should respond with an error in html format', () => {
        responseFormatter.html();
        sinon.assert.calledWithExactly(res.render, 'error', errorResponseForHtml);
        expect(next).to.not.have.been.called;
      });

      it('should respond with an error in text format', () => {
        responseFormatter.text();
        let responseText = _.first(res.send.firstCall.args);
        let parsedResponseText = _.chain(responseText).split('\n').map(
          (line) => {
            let [key, val] = line.split(/:\s*/);
            return [key, key !== 'status' ? val : parseInt(val)];
          }).fromPairs().value();
        expect(parsedResponseText).to.eql(errorResponse);
        expect(next).to.not.have.been.called;
      });
    });

    describe('without stack trace', () => {
      const errorMiddleware = middleware.error({
        env: 'production'
      });
      let responseFormatter;

      beforeEach(() => {
        errorMiddleware(error, req, res, next);
        expect(res.format).to.have.been.calledOnce;
        responseFormatter = _.first(res.format.firstCall.args);
      });

      it('should respond with an error that does not include a stack trace',
        () => {
          responseFormatter.json();
          expect(
            _.first(res.json.firstCall.args)).to.not.have.property('stack');
        });
    });
  });
});
<|endoftext|>


<commit_before>import { autorun, observable } from 'mobx';

import { DEFAULT_FEATURES_CONFIG } from '../../config';

const debug = require('debug')('Franz:feature:spellchecker');

export const config = observable({
  isIncludedInCurrentPlan: DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan,
});

export default function init(stores) {
  debug('Initializing `spellchecker` feature');

  autorun(() => {
    const { isSpellcheckerIncludedInCurrentPlan } = stores.features.features;

    config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan;

    if (!stores.user.data.isPremium && config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) {
      debug('Override settings.spellcheckerEnabled flag to false');

      Object.assign(stores.settings.app, {
        enableSpellchecking: false,
      });
    }
  });
}
<commit_msg>fix(Spellchecker): Fix disabling spellchecker after app start<commit_after>import { autorun, observable } from 'mobx';

import { DEFAULT_FEATURES_CONFIG } from '../../config';

const debug = require('debug')('Franz:feature:spellchecker');

export const config = observable({
  isIncludedInCurrentPlan: DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan,
});

export default function init(stores) {
  debug('Initializing `spellchecker` feature');

  autorun(() => {
    const { isSpellcheckerIncludedInCurrentPlan } = stores.features.features;

    config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan;

    if (!stores.user.data.isPremium && !config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) {
      debug('Override settings.spellcheckerEnabled flag to false');

      Object.assign(stores.settings.app, {
        enableSpellchecking: false,
      });
    }
  });
}
<|endoftext|>


<commit_before>ReactiveDatatable = function (options) {
	var tableID = "datatable";
	var self = this;

	this.options = options = _.defaults(options, {
		// Any of these can be overriden by passing an options
		// object into your ReactiveDatatable template (see readme)
		stateSave: true,
		stateDuration: -1, // Store data for session only
		pageLength: 5,
		lengthMenu: [ 3, 5, 10, 50, 100 ],
		columnDefs: [{ // Global default blank value to avoid popup on missing data
			targets: "_all",
			defaultContent: "–––"
		}],

		stateLoadParams: function ( settings, data ){
			// Make it easy to change to the stored page on .update()
			self.page = data.start / data.length;
		}
	});

	// Help Blaze cleanly remove entire datatable when changing template / route by
	// wrapping table in existing element (#datatable_wrap) defined in the template.
	var table = document.createElement('table');
	table.id = tableID;
	table.className = "table dataTable";

	// Render the table element and turn it into a DataTable
	$("#datatable_wrapper").append(table);
	this.datatable = $(table).DataTable(options);

};

ReactiveDatatable.prototype.update = function (data){
	if(!data.length) return;
	var self = this;

	self.datatable
		.clear()
		.rows.add(data)
		.draw(false)
		.page(self.page || 0) // XXX: Can we avoid drawing twice?
		.draw(false);		  // I couldn't get the page drawing to work otherwise
};
<commit_msg>Fix data existence check in reactive-datatables.js<commit_after>ReactiveDatatable = function (options) {
	var tableID = "datatable";
	var self = this;

	this.options = options = _.defaults(options, {
		// Any of these can be overriden by passing an options
		// object into your ReactiveDatatable template (see readme)
		stateSave: true,
		stateDuration: -1, // Store data for session only
		pageLength: 5,
		lengthMenu: [ 3, 5, 10, 50, 100 ],
		columnDefs: [{ // Global default blank value to avoid popup on missing data
			targets: "_all",
			defaultContent: "–––"
		}],

		stateLoadParams: function ( settings, data ){
			// Make it easy to change to the stored page on .update()
			self.page = data.start / data.length;
		}
	});

	// Help Blaze cleanly remove entire datatable when changing template / route by
	// wrapping table in existing element (#datatable_wrap) defined in the template.
	var table = document.createElement('table');
	table.id = tableID;
	table.className = "table dataTable";

	// Render the table element and turn it into a DataTable
	$("#datatable_wrapper").append(table);
	this.datatable = $(table).DataTable(options);

};

ReactiveDatatable.prototype.update = function (data){
	if (!data) return;
	var self = this;

	self.datatable
		.clear()
		.rows.add(data)
		.draw(false)
		.page(self.page || 0) // XXX: Can we avoid drawing twice?
		.draw(false);		  // I couldn't get the page drawing to work otherwise
};
<|endoftext|>