<commit_before>define([
    "jquery",
    "pat-parser",
    "pat-registry",
    "pat-utils",
    "pat-inject"
], function($, Parser, registry, utils, inject) {
    var parser = new Parser("modal");

    parser.add_argument("class");
    parser.add_argument("closing", ["close-button"], ["close-button", "outside"], true);

    var modal = {
        name: "modal",
        jquery_plugin: true,
        // div's are turned into modals
        // links and forms inject modals
        trigger: "div.pat-modal, a.pat-modal, form.pat-modal",
        init: function($el, opts, trigger) {
            if ($el.length > 1) {
                // We enforce a one-to-one mapping between modal objects and
                // DOM elements, so here we recurse and instantiate a new modal
                // for each $el
                $el.each(function() {
                    modal.init($(this));
                });
            } else if ($el.length === 1) {
                var cfg = parser.parse($el, opts);
                if (trigger && trigger.type==="injection")
                    $.extend(cfg, parser.parse($(trigger.element), {}, false, false));
                if ($el.is("div"))
                    modal._init_div1($el, cfg);
                else
                    modal._init_inject1($el, cfg);
            }
        },

        _init_inject1: function($el, cfg) {
            var opts = {
                target: "#pat-modal",
                "class": "pat-modal" + (cfg["class"] ? " " + cfg["class"] : "")
            };
            // if $el is already inside a modal, do not detach #pat-modal,
            // because this would unnecessarily close the modal itself
            if (!$el.closest("#pat-modal")) {
                $("#pat-modal").detach();
            }
            inject.init($el, opts);
        },

        _init_div1: function($el, cfg) {
            var $header = $("
"),
                activeElement = document.activeElement;

            if (cfg.closing.indexOf("close-button")!==-1)
                $("<button type='button' class='close-panel'>Close</button>").appendTo($header);

            // We cannot handle text nodes here
            $el.children(":last, :not(:first)")
                .wrapAll("
");
            $(".panel-content", $el).before($header);
            $el.children(":first:not(.header)").prependTo($header);

            // Restore focus in case the active element was a child of $el and
            // the focus was lost during the wrapping.
            activeElement.focus();
            modal._init_handlers($el, cfg);
            modal.setPosition($el, cfg);
        },

        _init_handlers: function($el, cfg) {
            // event handlers remove modal - first arg to bind is ``this``
            $(document).on("click.pat-modal", ".close-panel", modal.destroy.bind($el, $el));
            $(document).on("keyup.pat-modal", modal._onKeyUp.bind($el, $el));
            if (cfg.closing.indexOf("outside")!==-1)
                $(document).on("click.pat-modal", modal._onPossibleOutsideClick.bind($el, $el));

            $(window).on("resize.pat-modal-position",
                utils.debounce(modal.setPosition.bind(modal, $el, cfg), 400));
            $(document).on("pat-inject-content-loaded.pat-modal-position", "#pat-modal",
                utils.debounce(modal.setPosition.bind(modal, $el), 400));
            $(document).on("patterns-injected.pat-modal-position", "#pat-modal,div.pat-modal",
                utils.debounce(modal.setPosition.bind(modal, $el), 400));
        },

        _onPossibleOutsideClick: function($el, ev) {
            if ($el.has(ev.target))
                modal.destroy($el);
        },

        _onKeyUp: function($el, ev) {
            if (ev.which===27)
                modal.destroy($el);
        },

        setPosition: function($el, cfg) {
            var $tallest_child;
            var true_height = $el.outerHeight(); // the height of the highest element (after the function runs)
            $("*", $el).each(function () {
                if ($(this).outerHeight(true) > true_height) {
                    $tallest_child = $(this);
                    true_height = $tallest_child.outerHeight(true);
                }
            });
            if ($tallest_child) {
                // There is a child that's taller than $el. We need to make the
                // height the height of this child plus it's offset from the top
                // of $el.
                true_height += $tallest_child.offset().top - $el.offset().top;
            }
            // Maximum height is visible browser area minus modal padding
            var maxHeight = $(window).innerHeight() - ($el.outerHeight(true) - $el.outerHeight());
            if (maxHeight - true_height < 0) {
                $el.addClass("max-height").css("height", maxHeight);
            } else if (true_height !== $el.height()) {
                $el.removeClass("max-height").css("height", true_height);
            } else {
                return;
            }
            $el.css("top", ($(window).innerHeight() - $el.outerHeight(true)) / 2);

            // XXX: This is a hack. When you have a modal inside a
            // modal.max-height, the CSS of the outermost modal affects the
            // innermost .panel-body. By redrawing here, it's fixed.
            //
            // I think ideally the CSS needs to be fixed here, but I need to
            // discuss with Cornelis first.
            if ($el.parent().closest(".pat-modal").length > 0) {
                utils.redraw($el.find(".panel-body"));
            }
        },

        destroy: function($el) {
            $(document).off(".pat-modal");
            $el.remove();
        }
    };

    registry.register(modal);
    return modal;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>Fix jshint error.<commit_after>define([
    "jquery",
    "pat-parser",
    "pat-registry",
    "pat-utils",
    "pat-inject"
], function($, Parser, registry, utils, inject) {
    var parser = new Parser("modal");

    parser.add_argument("class");
    parser.add_argument("closing", ["close-button"], ["close-button", "outside"], true);

    var modal = {
        name: "modal",
        jquery_plugin: true,
        // div's are turned into modals
        // links and forms inject modals
        trigger: "div.pat-modal, a.pat-modal, form.pat-modal",
        init: function($el, opts, trigger) {
            if ($el.length > 1) {
                // We enforce a one-to-one mapping between modal objects and
                // DOM elements, so here we recurse and instantiate a new modal
                // for each $el
                $el.each(function() {
                    modal.init($(this));
                });
            } else if ($el.length === 1) {
                var cfg = parser.parse($el, opts);
                if (trigger && trigger.type==="injection")
                    $.extend(cfg, parser.parse($(trigger.element), {}, false, false));
                if ($el.is("div"))
                    modal._init_div1($el, cfg);
                else
                    modal._init_inject1($el, cfg);
            }
        },

        _init_inject1: function($el, cfg) {
            var opts = {
                target: "#pat-modal",
                "class": "pat-modal" + (cfg["class"] ? " " + cfg["class"] : "")
            };
            // if $el is already inside a modal, do not detach #pat-modal,
            // because this would unnecessarily close the modal itself
            if (!$el.closest("#pat-modal")) {
                $("#pat-modal").detach();
            }
            inject.init($el, opts);
        },

        _init_div1: function($el, cfg) {
            var $header = $("
"),
                activeElement = document.activeElement;

            if (cfg.closing.indexOf("close-button")!==-1)
                $("<button type='button' class='close-panel'>Close</button>").appendTo($header);

            // We cannot handle text nodes here
            $el.children(":last, :not(:first)")
                .wrapAll("
");
            $(".panel-content", $el).before($header);
            $el.children(":first:not(.header)").prependTo($header);

            // Restore focus in case the active element was a child of $el and
            // the focus was lost during the wrapping.
            activeElement.focus();
            modal._init_handlers($el, cfg);
            modal.setPosition($el, cfg);
        },

        _init_handlers: function($el, cfg) {
            // event handlers remove modal - first arg to bind is ``this``
            $(document).on("click.pat-modal", ".close-panel", modal.destroy.bind($el, $el));
            $(document).on("keyup.pat-modal", modal._onKeyUp.bind($el, $el));
            if (cfg.closing.indexOf("outside")!==-1)
                $(document).on("click.pat-modal", modal._onPossibleOutsideClick.bind($el, $el));

            $(window).on("resize.pat-modal-position",
                utils.debounce(modal.setPosition.bind(modal, $el, cfg), 400));
            $(document).on("pat-inject-content-loaded.pat-modal-position", "#pat-modal",
                utils.debounce(modal.setPosition.bind(modal, $el), 400));
            $(document).on("patterns-injected.pat-modal-position", "#pat-modal,div.pat-modal",
                utils.debounce(modal.setPosition.bind(modal, $el), 400));
        },

        _onPossibleOutsideClick: function($el, ev) {
            if ($el.has(ev.target))
                modal.destroy($el);
        },

        _onKeyUp: function($el, ev) {
            if (ev.which===27)
                modal.destroy($el);
        },

        setPosition: function($el/*, cfg */) {
            var $tallest_child;
            var true_height = $el.outerHeight(); // the height of the highest element (after the function runs)
            $("*", $el).each(function () {
                if ($(this).outerHeight(true) > true_height) {
                    $tallest_child = $(this);
                    true_height = $tallest_child.outerHeight(true);
                }
            });
            if ($tallest_child) {
                // There is a child that's taller than $el. We need to make the
                // height the height of this child plus it's offset from the top
                // of $el.
                true_height += $tallest_child.offset().top - $el.offset().top;
            }
            // Maximum height is visible browser area minus modal padding
            var maxHeight = $(window).innerHeight() - ($el.outerHeight(true) - $el.outerHeight());
            if (maxHeight - true_height < 0) {
                $el.addClass("max-height").css("height", maxHeight);
            } else if (true_height !== $el.height()) {
                $el.removeClass("max-height").css("height", true_height);
            } else {
                return;
            }
            $el.css("top", ($(window).innerHeight() - $el.outerHeight(true)) / 2);

            // XXX: This is a hack. When you have a modal inside a
            // modal.max-height, the CSS of the outermost modal affects the
            // innermost .panel-body. By redrawing here, it's fixed.
            //
            // I think ideally the CSS needs to be fixed here, but I need to
            // discuss with Cornelis first.
            if ($el.parent().closest(".pat-modal").length > 0) {
                utils.redraw($el.find(".panel-body"));
            }
        },

        destroy: function($el) {
            $(document).off(".pat-modal");
            $el.remove();
        }
    };

    registry.register(modal);
    return modal;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>


<commit_before>App.ApplicationAdapter = DS.ActiveModelAdapter.extend({
  host: (!localStorage.apiDomain) ? '' : '//' + localStorage.apiDomain,
  namespace: 'api/v1',
  buildURL: function(type, id) {
    return this._super(type, id) + '.json';
  },

  /**
    Override the `ajaxError` method, using the App.errorMessage() helper
    to return a DS.InvalidError for all 422 Unprocessable Entity
    responses.

    @method ajaxError
    @param jqXHR
    @returns error
  */

  ajaxError: function(jqXHR) {
    if (jqXHR && jqXHR.status === 401) {
      var currentPath = App.route.controllerFor('application').get('currentPath');
      App.log(jqXHR.status + ' error caught by ajaxError.', jqXHR);
      if (currentPath !== 'login') {
        App.notify('Please log back in.', App.ERROR, 'Unauthorized.');
        App.route.controllerFor('application').send('redirectToLogin', currentPath);
      }
    } else if (jqXHR && jqXHR.status === 422) {
      var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)['errors'];
      if (!jsonErrors) jsonErrors = [App.errorMessage(Ember.$.parseJSON(jqXHR.responseText))];
      var errors = {};
      Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
        errors[Ember.String.camelize(key)] = jsonErrors[key];
      });
      return new DS.InvalidError(errors);
    } else {
      return this._super(jqXHR);
    }
  },

  /**
   * Fix query URL.
   */
  findMany: function(store, type, ids, owner) {
    return this.ajax(this.buildURL(type.typeKey), 'GET', {data: {ids: ids.join(',')}});
  },

  /**
   * Cast individual record to array,
   * and match the root key to the route
   */
  createRecord: function(store, type, record) {
    var data = {};
    data[this.pathForType(type.typeKey)] = [
      store.serializerFor(type.typeKey).serialize(record, {includeId: true})
    ];
    // Serialize sideloaded objects to compose compound document
    record.eachRelationship(function(key, relationship) {
      if (relationship.kind === 'hasMany') {
        var sideloadedData = this.serializeSideload(record, relationship);
        if (sideloadedData) data[this.keyForAttribute(relationship.key)] = sideloadedData;
      }
    }, store.serializerFor(type.typeKey));
    return this.ajax(this.buildURL(type.typeKey), "POST", {data: data});
  },

  /**
   * Cast individual record to array,
   * and match the root key to the route
   */
  updateRecord: function(store, type, record) {
    var data = {};
    data[this.pathForType(type.typeKey)] = [
      store.serializerFor(type.typeKey).serialize(record)
    ];
    var id = Ember.get(record, 'id');
    // Serialize sideloaded objects to compose compound document
    record.eachRelationship(function(key, relationship) {
      if (relationship.kind === 'hasMany') {
        var sideloadedData = this.serializeSideload(record, relationship);
        if (sideloadedData) data[this.keyForAttribute(relationship.key)] = sideloadedData;
      }
    }, store.serializerFor(type.typeKey));
    return this.ajax(this.buildURL(type.typeKey, id), "PUT", {data: data});
  }
});

App.ApplicationSerializer = DS.ActiveModelSerializer.extend({
  serializeSideload: function(record, relationship) {
    var key = relationship.key;
    var attrs = get(this, 'attrs');
    var sideload = attrs && attrs[key] && attrs[key].sideload === 'always';

    if (sideload) {
      return get(record, key).map(function(relation) {
        var data = relation.serialize();
        var primaryKey = get(this, 'primaryKey');
        data[primaryKey] = get(relation, primaryKey);
        return data;
      }, this);
    }
  },

  /**
    Serialize has-may relationships

    @method serializeHasMany
  */
  serializeHasMany: function(record, json, relationship) {
    var key   = relationship.key,
        attrs = get(this, 'attrs'),
        embed = attrs && attrs[key] && attrs[key].embedded === 'always';
    if (embed) {
      json[this.keyForAttribute(key)] = get(record, key).map(function(relation) {
        var data = relation.serialize(),
            primaryKey = get(this, 'primaryKey');
        data[primaryKey] = get(relation, primaryKey);
        return data;
      }, this);
    } else {
      var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
      var keyName = (this.keyForRelationship) ? this.keyForRelationship(key, relationshipType) : key;
      json[keyName] = get(record, key).mapBy('id');
    }
  },

  /**
    Underscores relationship names and appends "_id" or "_ids" when serializing
    relationship keys.

    @method keyForRelationship
    @param {String} key
    @param {String} kind
    @returns String
  */
  keyForRelationship: function(key, kind) {
    key = Ember.String.decamelize(key);
    if (kind === "belongsTo") {
      return key + "_id";
    } else if (kind === "hasMany" || kind === "manyToOne") {
      return Ember.String.singularize(key) + "_ids";
    } else {
      return key;
    }
  },

  normalize: function(type, hash, property) {
    var json = hash;
    delete json.links;  // Don't use "links" yet, until JSON-API spec is implemented API-wide
    return this._super(type, json, property);
  },

  /**
   * Patch the extractSingle method, since there are no singular records, allowing additional sideloaded records of primary type
   */
  extractSingle: function(store, primaryType, payload, recordId, requestType) {
    var primaryTypeName = primaryType.typeKey;
    var json = {};
    for(var key in payload) {
      var typeName = Ember.String.singularize(key);
      if(typeName === primaryTypeName && Ember.isArray(payload[key])) {
        json[typeName] = payload[key][0];
        if (payload[key].length > 1) {
          json['_' + key] = payload[key].slice(1);
        }
      } else {
        json[key] = payload[key];
      }
    }
    return this._super(store, primaryType, json, recordId, requestType);
  },

  /**
   * Patch the extractFindAll method to unload records not present in findAll requests, and ignore "isEditing" records
   */
  extractFindAll: function (store, type, payload, id, requestType) {
    var missingRecords = [];
    var editingRecords = [];
    store.all(type).forEach(function (item, index, enumerable) {
      var payloadIds = payload[Object.keys(payload)[0]].getEach('id').map(function (item, index, enumerable) {
        return item.toString();
      });
      var isMissing = (payloadIds.indexOf(item.get('id').toString()) === -1);
      if (isMissing) missingRecords.push(item);
      if (item.get('isEditing')) editingRecords.push(item);
    });
    var extracted = this._super(store, type, payload, id, requestType);
    missingRecords.forEach(function (item, index, enumerable) {
      if (!item.get('isDirty')) item.unloadRecord();
    });
    return extracted.filter(function (item, index, enumerable) {
      return editingRecords.mapBy('id').indexOf(item.id) === -1;
    });
  }
});

// RESTConfigAdapter for nested configuration namespace
DS.RESTConfigAdapter = App.ApplicationAdapter.extend({
  namespace: 'api/v1/configuration'
});

// RESTSingletonAdapter for singleton resources in primary namespace
DS.RESTSingletonAdapter = App.ApplicationAdapter.extend({
  // Remove ID from this singleton resource
  buildURL: function (type, id) {
    return this._super(type);
  }
});

// RESTSingletonConfigAdapter for singleton resources in configuration namespace
DS.RESTSingletonConfigAdapter = DS.RESTSingletonAdapter.extend({
  namespace: 'api/v1/configuration'
});

// RESTDefinitionsAdapter
DS.RESTSingletonDefinitionsAdapter = DS.RESTSingletonAdapter.extend({
  namespace: 'definitions'
});

Ember.Inflector.inflector.uncountable('vm_instantiation_simple');
Ember.Inflector.inflector.uncountable('vm_instantiation_detailed');

// Add additional Ember Data types
App.ArrayTransform = DS.Transform.extend({
  serialize: function(value) {
    return (Em.typeOf(value) === 'array') ? value : [];
  },
  deserialize: function(value) {
    return value;
  }
});

// Add methods on the Model class for interacting with records
DS.Model.reopen({
  clearInverseRelationships: function() {
    this.eachRelationship(function(name, relationship){
      if (relationship.kind === 'belongsTo') {
        var inverse = relationship.parentType.inverseFor(name);
        var parent = this.get(name);
        if (inverse && parent) parent.get(inverse.name).removeObject(this);
      }
    }, this);
  },
  relatedRecords: function () {
    var records = [];
    this.eachRelationship(function (name, relationship) {
      var related = this.get(name);
      if (!Ember.isEmpty(related)) {
        if (Ember.isArray(related)) {
          records.addObjects(related);
        } else {
          records.addObject(related);
        }
      }
    }, this);
    return records;
  }
});
<commit_msg>Set SAA UI's API host to location.href to allow proxying SAA UI and API in an arbitrary subdirectory<commit_after>App.ApplicationAdapter = DS.ActiveModelAdapter.extend({
  host: (!localStorage.apiDomain) ? window.location.href.slice(0, -1) : '//' + localStorage.apiDomain,
  namespace: 'api/v1',
  buildURL: function(type, id) {
    return this._super(type, id) + '.json';
  },

  /**
    Override the `ajaxError` method, using the App.errorMessage() helper
    to return a DS.InvalidError for all 422 Unprocessable Entity
    responses.

    @method ajaxError
    @param jqXHR
    @returns error
  */

  ajaxError: function(jqXHR) {
    if (jqXHR && jqXHR.status === 401) {
      var currentPath = App.route.controllerFor('application').get('currentPath');
      App.log(jqXHR.status + ' error caught by ajaxError.', jqXHR);
      if (currentPath !== 'login') {
        App.notify('Please log back in.', App.ERROR, 'Unauthorized.');
        App.route.controllerFor('application').send('redirectToLogin', currentPath);
      }
    } else if (jqXHR && jqXHR.status === 422) {
      var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)['errors'];
      if (!jsonErrors) jsonErrors = [App.errorMessage(Ember.$.parseJSON(jqXHR.responseText))];
      var errors = {};
      Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
        errors[Ember.String.camelize(key)] = jsonErrors[key];
      });
      return new DS.InvalidError(errors);
    } else {
      return this._super(jqXHR);
    }
  },

  /**
   * Fix query URL.
   */
  findMany: function(store, type, ids, owner) {
    return this.ajax(this.buildURL(type.typeKey), 'GET', {data: {ids: ids.join(',')}});
  },

  /**
   * Cast individual record to array,
   * and match the root key to the route
   */
  createRecord: function(store, type, record) {
    var data = {};
    data[this.pathForType(type.typeKey)] = [
      store.serializerFor(type.typeKey).serialize(record, {includeId: true})
    ];
    // Serialize sideloaded objects to compose compound document
    record.eachRelationship(function(key, relationship) {
      if (relationship.kind === 'hasMany') {
        var sideloadedData = this.serializeSideload(record, relationship);
        if (sideloadedData) data[this.keyForAttribute(relationship.key)] = sideloadedData;
      }
    }, store.serializerFor(type.typeKey));
    return this.ajax(this.buildURL(type.typeKey), "POST", {data: data});
  },

  /**
   * Cast individual record to array,
   * and match the root key to the route
   */
  updateRecord: function(store, type, record) {
    var data = {};
    data[this.pathForType(type.typeKey)] = [
      store.serializerFor(type.typeKey).serialize(record)
    ];
    var id = Ember.get(record, 'id');
    // Serialize sideloaded objects to compose compound document
    record.eachRelationship(function(key, relationship) {
      if (relationship.kind === 'hasMany') {
        var sideloadedData = this.serializeSideload(record, relationship);
        if (sideloadedData) data[this.keyForAttribute(relationship.key)] = sideloadedData;
      }
    }, store.serializerFor(type.typeKey));
    return this.ajax(this.buildURL(type.typeKey, id), "PUT", {data: data});
  }
});

App.ApplicationSerializer = DS.ActiveModelSerializer.extend({
  serializeSideload: function(record, relationship) {
    var key = relationship.key;
    var attrs = get(this, 'attrs');
    var sideload = attrs && attrs[key] && attrs[key].sideload === 'always';

    if (sideload) {
      return get(record, key).map(function(relation) {
        var data = relation.serialize();
        var primaryKey = get(this, 'primaryKey');
        data[primaryKey] = get(relation, primaryKey);
        return data;
      }, this);
    }
  },

  /**
    Serialize has-may relationships

    @method serializeHasMany
  */
  serializeHasMany: function(record, json, relationship) {
    var key   = relationship.key,
        attrs = get(this, 'attrs'),
        embed = attrs && attrs[key] && attrs[key].embedded === 'always';
    if (embed) {
      json[this.keyForAttribute(key)] = get(record, key).map(function(relation) {
        var data = relation.serialize(),
            primaryKey = get(this, 'primaryKey');
        data[primaryKey] = get(relation, primaryKey);
        return data;
      }, this);
    } else {
      var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
      var keyName = (this.keyForRelationship) ? this.keyForRelationship(key, relationshipType) : key;
      json[keyName] = get(record, key).mapBy('id');
    }
  },

  /**
    Underscores relationship names and appends "_id" or "_ids" when serializing
    relationship keys.

    @method keyForRelationship
    @param {String} key
    @param {String} kind
    @returns String
  */
  keyForRelationship: function(key, kind) {
    key = Ember.String.decamelize(key);
    if (kind === "belongsTo") {
      return key + "_id";
    } else if (kind === "hasMany" || kind === "manyToOne") {
      return Ember.String.singularize(key) + "_ids";
    } else {
      return key;
    }
  },

  normalize: function(type, hash, property) {
    var json = hash;
    delete json.links;  // Don't use "links" yet, until JSON-API spec is implemented API-wide
    return this._super(type, json, property);
  },

  /**
   * Patch the extractSingle method, since there are no singular records, allowing additional sideloaded records of primary type
   */
  extractSingle: function(store, primaryType, payload, recordId, requestType) {
    var primaryTypeName = primaryType.typeKey;
    var json = {};
    for(var key in payload) {
      var typeName = Ember.String.singularize(key);
      if(typeName === primaryTypeName && Ember.isArray(payload[key])) {
        json[typeName] = payload[key][0];
        if (payload[key].length > 1) {
          json['_' + key] = payload[key].slice(1);
        }
      } else {
        json[key] = payload[key];
      }
    }
    return this._super(store, primaryType, json, recordId, requestType);
  },

  /**
   * Patch the extractFindAll method to unload records not present in findAll requests, and ignore "isEditing" records
   */
  extractFindAll: function (store, type, payload, id, requestType) {
    var missingRecords = [];
    var editingRecords = [];
    store.all(type).forEach(function (item, index, enumerable) {
      var payloadIds = payload[Object.keys(payload)[0]].getEach('id').map(function (item, index, enumerable) {
        return item.toString();
      });
      var isMissing = (payloadIds.indexOf(item.get('id').toString()) === -1);
      if (isMissing) missingRecords.push(item);
      if (item.get('isEditing')) editingRecords.push(item);
    });
    var extracted = this._super(store, type, payload, id, requestType);
    missingRecords.forEach(function (item, index, enumerable) {
      if (!item.get('isDirty')) item.unloadRecord();
    });
    return extracted.filter(function (item, index, enumerable) {
      return editingRecords.mapBy('id').indexOf(item.id) === -1;
    });
  }
});

// RESTConfigAdapter for nested configuration namespace
DS.RESTConfigAdapter = App.ApplicationAdapter.extend({
  namespace: 'api/v1/configuration'
});

// RESTSingletonAdapter for singleton resources in primary namespace
DS.RESTSingletonAdapter = App.ApplicationAdapter.extend({
  // Remove ID from this singleton resource
  buildURL: function (type, id) {
    return this._super(type);
  }
});

// RESTSingletonConfigAdapter for singleton resources in configuration namespace
DS.RESTSingletonConfigAdapter = DS.RESTSingletonAdapter.extend({
  namespace: 'api/v1/configuration'
});

// RESTDefinitionsAdapter
DS.RESTSingletonDefinitionsAdapter = DS.RESTSingletonAdapter.extend({
  namespace: 'definitions'
});

Ember.Inflector.inflector.uncountable('vm_instantiation_simple');
Ember.Inflector.inflector.uncountable('vm_instantiation_detailed');

// Add additional Ember Data types
App.ArrayTransform = DS.Transform.extend({
  serialize: function(value) {
    return (Em.typeOf(value) === 'array') ? value : [];
  },
  deserialize: function(value) {
    return value;
  }
});

// Add methods on the Model class for interacting with records
DS.Model.reopen({
  clearInverseRelationships: function() {
    this.eachRelationship(function(name, relationship){
      if (relationship.kind === 'belongsTo') {
        var inverse = relationship.parentType.inverseFor(name);
        var parent = this.get(name);
        if (inverse && parent) parent.get(inverse.name).removeObject(this);
      }
    }, this);
  },
  relatedRecords: function () {
    var records = [];
    this.eachRelationship(function (name, relationship) {
      var related = this.get(name);
      if (!Ember.isEmpty(related)) {
        if (Ember.isArray(related)) {
          records.addObjects(related);
        } else {
          records.addObject(related);
        }
      }
    }, this);
    return records;
  }
});
<|endoftext|>


<commit_before>/*    Copyright 2020 Firewalla INC.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

let instance = null;

const rclient = require('../../util/redis_manager.js').getRedisClient()
const log = require('../../net2/logger.js')(__filename);

const flowTool = require('../../net2/FlowTool.js')();
const intelTool = require('../../net2/IntelTool');

const _ = require('lodash');

class Tracking {
  constructor() {
    if (instance === null) {
      this.expireInterval = 3600 * 2; // two hours;
      this.bucketInterval = 5 * 60 * 1000; // every 5 mins
      this.maxBuckets = 288;
      this.maxAggrBuckets = 576;
      this.maxResultAggrBuckets = 2016; // 7 days
      this.bucketCountPerAggr = 12;
      this.maxItemsInBucket = 100;
      this.resetOffset = 0; // local timezone, starting from 0 O'Clock
      instance = this;
    }

    return instance;
  }

  // each buckets represents 5 min activities
  // this key is a set of unique IP/domains
  getDestinationKey(mac, bucket) {
    return `tracking:dest:${mac}:${bucket}`;
  }

  getTrafficKey(mac, bucket) {
    return `tracking:traffic:${mac}:${bucket}`;
  }

  getAggregateTrafficKey(mac) {
    return `tracking:aggr:traffic:${mac}`;
  }

  getAggregateDestinationCountKey(mac) {
    return `tracking:aggr:dest:${mac}`;
  }

  getAggregateResultKey(mac) {
    return `tracking:aggr:result:${mac}`;
  }

  // begin/end is js epoch time
  getBuckets(begin, end) {
    if(begin > end) {
      return [];
    }

    let buckets = [];
    let beginBucket = Math.floor(begin / this.bucketInterval);
    let endBucket = Math.floor(end / this.bucketInterval);
    if(endBucket - beginBucket > this.maxBuckets || endBucket < beginBucket) {
      log.info("Invalid bucket setup, skipped:", beginBucket, endBucket);
      return [];
    }
    return [beginBucket, endBucket];
  }

  async appendDestination(key, destination) {
    const size = await rclient.scardAsync(key);
    if (size >= this.maxItemsInBucket) {
      return;
    }

    await rclient.saddAsync(key, destination);
    await rclient.expireAsync(key, this.expireInterval);
  }

  async appendTraffic(key, traffic) {
    await rclient.incrbyAsync(key, Math.floor(traffic));
    await rclient.expireAsync(key, this.expireInterval);
  }

  // begin, end - timestamps
  async recordDestination(mac, destination, begin, end) {
    const buckets = this.getBuckets(begin, end);
    if (buckets.length !== 2) {
      return;
    }

    for (let b = buckets[0]; b <= buckets[1]; b++) {
      const key = this.getDestinationKey(mac, b);
      await this.appendDestination(key, destination);
    }
  }

  async recordTraffic(mac, flow, begin, end) {
    const buckets = this.getBuckets(begin, end);
    if (buckets.length !== 2) {
      return;
    }

    for (let b = buckets[0]; b <= buckets[1]; b++) {
      const trafficKey = this.getTrafficKey(mac, b);
      const duration = flow.du;
      const traffic = flow.ob + flow.rb;

      // FIXME: may need to be more accurate
      if (duration < this.bucketInterval / 1000) {
        await this.appendTraffic(trafficKey, traffic);
      } else {
        await this.appendTraffic(trafficKey, traffic * this.bucketInterval / 1000 / duration);
      }
    }
  }

  async recordFlows(mac, flows) {
    for(const flow of flows) {
      const destIP = flowTool.getDestIP(flow);
      const intel = await intelTool.getIntel(destIP);
      if(intel && intel.b) { // ignore background traffic
        continue;
      }
      const begin = flow.ts * 1000;
      const end = flow.ets * 1000;
      await this.recordDestination(mac, destIP, begin, end);
      await this.recordTraffic(mac, flow, begin, end);
    }
  }

  // aggr the last 24 hours data, this is only for debugging purpose.
  async aggrLast24Hours(mac) {
    return this._aggr(mac, 24 * 3600 * 1000);
  }

  async aggr(mac) {
    return this._aggr(mac, this.bucketCountPerAggr * this.bucketInterval);
  }

  async _aggr(mac, interval) {
    const buckets = this.getBuckets(new Date() - interval, new Date());
    if (buckets.length !== 2) {
      return;
    }

    let results = {};

    // traffic
    const aggrTrafficKey = this.getAggregateTrafficKey(mac);
    for(let b = buckets[0]; b <= buckets[1]; b++) {
      const key = this.getTrafficKey(mac, b);
      const x = await rclient.getAsync(key) || 0;
      await rclient.hset(aggrTrafficKey, b, x);
      if (x > 50 * 1000) { // hard code, 50k
        results[b] = 1;
      }
    }

    // count
    const aggrDestKey = this.getAggregateDestinationCountKey(mac);
    for(let b = buckets[0]; b <= buckets[1]; b++) {
      const key = this.getDestinationKey(mac, b);
      const x = await rclient.scardAsync(key) || 0;
      await rclient.hset(aggrDestKey, b, x);
      if (x > 5) { // hard code, 5 conns
        results[b] = 1;
      }
    }

    const aggrResultKey = this.getAggregateResultKey(mac);
    for(let b = buckets[0]; b <= buckets[1]; b++) {
      if (results[b]) {
        await rclient.hset(aggrResultKey, b, 1);
      } else {
        await rclient.hset(aggrResultKey, b, 0);
      }
    }
  }

  async _cleanup(key, expireBucketIndex) {
    const keys = rclient.hkeysAsync(key);
    let count = 0;
    for(const key of keys) {
      if(key < expireBucketIndex) {
        count ++;
        await rclient.hdelAsync(aggrDestinationKey, key);
      }
    }
    log.info("Cleaned up", count, "old aggr data for key", key);
  }

  async cleanup(mac) {
    const buckets = this.getBuckets(new Date() - this.maxAggrBuckets * this.bucketInterval, new Date());
    if (buckets.length !== 2) {
      return;
    }

    await this._cleanup(this.getAggregateTrafficKey(mac), buckets[0]);
    await this._cleanup(this.getAggregateDestinationCountKey(mac), buckets[0]);
    await this._cleanup(this.getAggregateResultKey(mac), buckets[0] - 2016); // 2016 = 3600*24*7/5/60 => how many 5-min time slots in last 7 days, keep the result date for 7 more days
  }

  async getDistribution(mac, time) {
    time = time || Math.floor(new Date() / 1);
    const d = new Date();
    const offset = d.getTimezoneOffset(); // in mins

    const timeWithTimezoneOffset = time - offset * 60 * 1000;
    const beginOfDate = Math.floor(timeWithTimezoneOffset / 1000 / 3600 / 24) * 3600 * 24 * 1000;
    const beginOfDateWithTimezoneOffset = beginOfDate + offset * 60 * 1000;
    const beginBucket = Math.floor(beginOfDateWithTimezoneOffset / this.bucketInterval);
    const endBucket = beginBucket + this.maxBuckets;

    const key = this.getAggregateResultKey(mac);
    const results = await rclient.hgetallAsync(key);
    let distribution = [];

    for(let i = beginBucket; i < endBucket; i++) {
      distribution.push([i * this.bucketInterval, results[i] === '1' ? 1 : 0]);
    }

    return distribution;
  }

  async getUsedTime(mac, time) {
    time = time || Math.floor(new Date() / 1);
    const d = new Date();
    const offset = d.getTimezoneOffset(); // in mins

    const timeWithTimezoneOffset = time - offset * 60 * 1000;
    const beginOfDate = Math.floor(timeWithTimezoneOffset / 1000 / 3600 / 24) * 3600 * 24 * 1000;
    const beginOfDateWithTimezoneOffset = beginOfDate + offset * 60 * 1000;
    const beginBucket = Math.floor(beginOfDateWithTimezoneOffset / this.bucketInterval);
    const endBucket = beginBucket + this.maxBuckets;

    const key = this.getAggregateResultKey(mac);
    const results = await rclient.hgetallAsync(key);
    let count = 0;
    for(let i = beginBucket; i < endBucket; i++) {
      if(results[i] === '1') {
        if(i == beginBucket || i == endBucket - 1 ) {
          count += Math.floor(this.bucketInterval / 1000 / 60); // 5 mins by default
        } else if(results[i-1] === '1' || results[i+1] === '1') {
            count += Math.floor(this.bucketInterval / 1000 / 60); // 5 mins by default
        }
      }
    }
    return count;
  }
}

module.exports = new Tracking();<commit_msg>bug fix<commit_after>/*    Copyright 2020 Firewalla INC.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

let instance = null;

const rclient = require('../../util/redis_manager.js').getRedisClient()
const log = require('../../net2/logger.js')(__filename);

const flowTool = require('../../net2/FlowTool.js')();

const IntelTool = require('../../net2/IntelTool');
const intelTool = new IntelTool();

const _ = require('lodash');

class Tracking {
  constructor() {
    if (instance === null) {
      this.expireInterval = 3600 * 2; // two hours;
      this.bucketInterval = 5 * 60 * 1000; // every 5 mins
      this.maxBuckets = 288;
      this.maxAggrBuckets = 576;
      this.maxResultAggrBuckets = 2016; // 7 days
      this.bucketCountPerAggr = 12;
      this.maxItemsInBucket = 100;
      this.resetOffset = 0; // local timezone, starting from 0 O'Clock
      instance = this;
    }

    return instance;
  }

  // each buckets represents 5 min activities
  // this key is a set of unique IP/domains
  getDestinationKey(mac, bucket) {
    return `tracking:dest:${mac}:${bucket}`;
  }

  getTrafficKey(mac, bucket) {
    return `tracking:traffic:${mac}:${bucket}`;
  }

  getAggregateTrafficKey(mac) {
    return `tracking:aggr:traffic:${mac}`;
  }

  getAggregateDestinationCountKey(mac) {
    return `tracking:aggr:dest:${mac}`;
  }

  getAggregateResultKey(mac) {
    return `tracking:aggr:result:${mac}`;
  }

  // begin/end is js epoch time
  getBuckets(begin, end) {
    if(begin > end) {
      return [];
    }

    let buckets = [];
    let beginBucket = Math.floor(begin / this.bucketInterval);
    let endBucket = Math.floor(end / this.bucketInterval);
    if(endBucket - beginBucket > this.maxBuckets || endBucket < beginBucket) {
      log.info("Invalid bucket setup, skipped:", beginBucket, endBucket);
      return [];
    }
    return [beginBucket, endBucket];
  }

  async appendDestination(key, destination) {
    const size = await rclient.scardAsync(key);
    if (size >= this.maxItemsInBucket) {
      return;
    }

    await rclient.saddAsync(key, destination);
    await rclient.expireAsync(key, this.expireInterval);
  }

  async appendTraffic(key, traffic) {
    await rclient.incrbyAsync(key, Math.floor(traffic));
    await rclient.expireAsync(key, this.expireInterval);
  }

  // begin, end - timestamps
  async recordDestination(mac, destination, begin, end) {
    const buckets = this.getBuckets(begin, end);
    if (buckets.length !== 2) {
      return;
    }

    for (let b = buckets[0]; b <= buckets[1]; b++) {
      const key = this.getDestinationKey(mac, b);
      await this.appendDestination(key, destination);
    }
  }

  async recordTraffic(mac, flow, begin, end) {
    const buckets = this.getBuckets(begin, end);
    if (buckets.length !== 2) {
      return;
    }

    for (let b = buckets[0]; b <= buckets[1]; b++) {
      const trafficKey = this.getTrafficKey(mac, b);
      const duration = flow.du;
      const traffic = flow.ob + flow.rb;

      // FIXME: may need to be more accurate
      if (duration < this.bucketInterval / 1000) {
        await this.appendTraffic(trafficKey, traffic);
      } else {
        await this.appendTraffic(trafficKey, traffic * this.bucketInterval / 1000 / duration);
      }
    }
  }

  async recordFlows(mac, flows) {
    for(const flow of flows) {
      const destIP = flowTool.getDestIP(flow);
      const intel = await intelTool.getIntel(destIP);
      if(intel && intel.b) { // ignore background traffic
        continue;
      }
      const begin = flow.ts * 1000;
      const end = flow.ets * 1000;
      await this.recordDestination(mac, destIP, begin, end);
      await this.recordTraffic(mac, flow, begin, end);
    }
  }

  // aggr the last 24 hours data, this is only for debugging purpose.
  async aggrLast24Hours(mac) {
    return this._aggr(mac, 24 * 3600 * 1000);
  }

  async aggr(mac) {
    return this._aggr(mac, this.bucketCountPerAggr * this.bucketInterval);
  }

  async _aggr(mac, interval) {
    const buckets = this.getBuckets(new Date() - interval, new Date());
    if (buckets.length !== 2) {
      return;
    }

    let results = {};

    // traffic
    const aggrTrafficKey = this.getAggregateTrafficKey(mac);
    for(let b = buckets[0]; b <= buckets[1]; b++) {
      const key = this.getTrafficKey(mac, b);
      const x = await rclient.getAsync(key) || 0;
      await rclient.hset(aggrTrafficKey, b, x);
      if (x > 50 * 1000) { // hard code, 50k
        results[b] = 1;
      }
    }

    // count
    const aggrDestKey = this.getAggregateDestinationCountKey(mac);
    for(let b = buckets[0]; b <= buckets[1]; b++) {
      const key = this.getDestinationKey(mac, b);
      const x = await rclient.scardAsync(key) || 0;
      await rclient.hset(aggrDestKey, b, x);
      if (x > 5) { // hard code, 5 conns
        results[b] = 1;
      }
    }

    const aggrResultKey = this.getAggregateResultKey(mac);
    for(let b = buckets[0]; b <= buckets[1]; b++) {
      if (results[b]) {
        await rclient.hset(aggrResultKey, b, 1);
      } else {
        await rclient.hset(aggrResultKey, b, 0);
      }
    }
  }

  async _cleanup(key, expireBucketIndex) {
    const keys = rclient.hkeysAsync(key);
    let count = 0;
    for(const key of keys) {
      if(key < expireBucketIndex) {
        count ++;
        await rclient.hdelAsync(aggrDestinationKey, key);
      }
    }
    log.info("Cleaned up", count, "old aggr data for key", key);
  }

  async cleanup(mac) {
    const buckets = this.getBuckets(new Date() - this.maxAggrBuckets * this.bucketInterval, new Date());
    if (buckets.length !== 2) {
      return;
    }

    await this._cleanup(this.getAggregateTrafficKey(mac), buckets[0]);
    await this._cleanup(this.getAggregateDestinationCountKey(mac), buckets[0]);
    await this._cleanup(this.getAggregateResultKey(mac), buckets[0] - 2016); // 2016 = 3600*24*7/5/60 => how many 5-min time slots in last 7 days, keep the result date for 7 more days
  }

  async getDistribution(mac, time) {
    time = time || Math.floor(new Date() / 1);
    const d = new Date();
    const offset = d.getTimezoneOffset(); // in mins

    const timeWithTimezoneOffset = time - offset * 60 * 1000;
    const beginOfDate = Math.floor(timeWithTimezoneOffset / 1000 / 3600 / 24) * 3600 * 24 * 1000;
    const beginOfDateWithTimezoneOffset = beginOfDate + offset * 60 * 1000;
    const beginBucket = Math.floor(beginOfDateWithTimezoneOffset / this.bucketInterval);
    const endBucket = beginBucket + this.maxBuckets;

    const key = this.getAggregateResultKey(mac);
    const results = await rclient.hgetallAsync(key);
    let distribution = [];

    for(let i = beginBucket; i < endBucket; i++) {
      distribution.push([i * this.bucketInterval, results[i] === '1' ? 1 : 0]);
    }

    return distribution;
  }

  async getUsedTime(mac, time) {
    time = time || Math.floor(new Date() / 1);
    const d = new Date();
    const offset = d.getTimezoneOffset(); // in mins

    const timeWithTimezoneOffset = time - offset * 60 * 1000;
    const beginOfDate = Math.floor(timeWithTimezoneOffset / 1000 / 3600 / 24) * 3600 * 24 * 1000;
    const beginOfDateWithTimezoneOffset = beginOfDate + offset * 60 * 1000;
    const beginBucket = Math.floor(beginOfDateWithTimezoneOffset / this.bucketInterval);
    const endBucket = beginBucket + this.maxBuckets;

    const key = this.getAggregateResultKey(mac);
    const results = await rclient.hgetallAsync(key);
    let count = 0;
    for(let i = beginBucket; i < endBucket; i++) {
      if(results[i] === '1') {
        if(i == beginBucket || i == endBucket - 1 ) {
          count += Math.floor(this.bucketInterval / 1000 / 60); // 5 mins by default
        } else if(results[i-1] === '1' || results[i+1] === '1') {
            count += Math.floor(this.bucketInterval / 1000 / 60); // 5 mins by default
        }
      }
    }
    return count;
  }
}

module.exports = new Tracking();<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

define([
       "app",

       "api",

       // Modules
       "addons/documents/views",
       "addons/databases/base"
],

function(app, FauxtonAPI, Documents, Databases) {

  var DocEditorRouteObject = FauxtonAPI.RouteObject.extend({
    layout: "one_pane",
    disableLoader: true,
    selectedHeader: "Databases",
    initialize: function(route, masterLayout, options) {
      var databaseName = options[0];
      this.docID = options[1]||'new';

      this.database = this.database || new Databases.Model({id: databaseName});
      this.doc = new Documents.Doc({
        _id: this.docID
      }, {
        database: this.database
      });

      this.tabsView = this.setView("#tabs", new Documents.Views.FieldEditorTabs({
        disableLoader: true,
        selected: "code_editor",
        model: this.doc
      }));

    },

    routes: {
      // We are hiding the field_editor for this first release
      // "database/:database/:doc/field_editor": "field_editor",
      "database/:database/:doc/code_editor": "code_editor",
      "database/:database/:doc": "code_editor"
    },

    events: {
      "route:reRenderDoc": "reRenderDoc",
      "route:duplicateDoc": "duplicateDoc"
    },

    crumbs: function() {
      return [
        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
        {"name": this.docID, "link": "#"}
      ];
    },

    code_editor: function (database, doc) {
      this.tabsView.updateSelected('code_editor');

      this.docView = this.setView("#dashboard-content", new Documents.Views.Doc({
        model: this.doc,
        database: this.database
      }));
    },

    reRenderDoc: function () {
      this.docView.forceRender();
    },

    field_editor: function(events) {
      this.tabsView.updateSelected('field_editor');
      this.docView = this.setView("#dashboard-content", new Documents.Views.DocFieldEditor({
        model: this.doc
      }));
    },

    duplicateDoc: function (newId) {
      var doc = this.doc,
      docView = this.docView,
      database = this.database;

      doc.copy(newId).then(function () {
        doc.set({_id: newId});
        docView.forceRender();
        FauxtonAPI.navigate('/database/' + database.safeID() + '/' + app.utils.safeURLName(newId), {trigger: true});
        FauxtonAPI.addNotification({
          msg: "Document has been duplicated."
        });

      }, function (error) {
        var errorMsg = "Could not duplicate document, reason: " + error.responseText + ".";
        FauxtonAPI.addNotification({
          msg: errorMsg,
          type: "error"
        });
      });
    },

    apiUrl: function() {
      return [this.doc.url("apiurl"), this.doc.documentation()];
    }
  });

  var NewDocEditorRouteObject = DocEditorRouteObject.extend({
    initialize: function (route, masterLayout, options) {
      var databaseName = options[0];

      this.database = this.database || new Databases.Model({id: databaseName});
      this.doc = new Documents.NewDoc(null,{
        database: this.database
      });

      this.tabsView = this.setView("#tabs", new Documents.Views.FieldEditorTabs({
        selected: "code_editor",
        model: this.doc
      }));

    },
    crumbs: function() {
      return [
        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
        {"name": "New", "link": "#"}
      ];
    },
    routes: {
      "database/:database/new": "code_editor"
    },
    selectedHeader: "Databases",

  });

  var DocumentsRouteObject = FauxtonAPI.RouteObject.extend({
    layout: "with_tabs_sidebar",
    selectedHeader: "Databases",
    routes: {
      "database/:database/_all_docs(:extra)": "allDocs",
      "database/:database/_design/:ddoc/_view/:view": {
        route: "viewFn",
        roles: ['_admin']
      },
      "database/:database/new_view": "newViewEditor"
    },

    events: {
      "route:updateAllDocs": "updateAllDocsFromView",
      "route:updatePreviewDocs": "updateAllDocsFromPreview",
      "route:reloadDesignDocs": "reloadDesignDocs",
      "route:paginate": "paginate"
    },

    initialize: function (route, masterLayout, options) {
      var docOptions = app.getParams();
      docOptions.include_docs = true;

      this.databaseName = options[0];

      this.data = {
        database: new Databases.Model({id:this.databaseName})
      };

      this.data.designDocs = new Documents.AllDocs(null, {
        database: this.data.database,
        params: {startkey: '"_design"',
          endkey: '"_design1"',
          include_docs: true}
      });

      this.sidebar = this.setView("#sidebar-content", new Documents.Views.Sidebar({
        collection: this.data.designDocs,
        database: this.data.database
      }));
    },

    establish: function () {
      return this.data.designDocs.fetchOnce();
    },

    allDocs: function(databaseName, options) {
      var docOptions = app.getParams(options);

      this.data.database.buildAllDocs(docOptions);

      if (docOptions.startkey && docOptions.startkey.indexOf('_design') > -1) {
        this.sidebar.setSelectedTab('design-docs');
      } else {
        this.sidebar.setSelectedTab('all-docs');
      }

      if (this.viewEditor) { this.viewEditor.remove(); }

      this.toolsView = this.setView("#dashboard-upper-menu", new Documents.Views.JumpToDoc({
        database: this.data.database,
        collection: this.data.database.allDocs
      }));

      this.setView("#dashboard-upper-content", new Documents.Views.AllDocsLayout({
        database: this.data.database,
        collection: this.data.database.allDocs,
        params: docOptions
      }));

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        collection: this.data.database.allDocs
      }));

      this.crumbs = [
        {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)}
      ];

      this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
    },

    viewFn: function (databaseName, ddoc, view) {
      var params = app.getParams(),
          decodeDdoc = decodeURIComponent(ddoc);

      view = view.replace(/\?.*$/,'');

      this.data.indexedDocs = new Documents.IndexCollection(null, {
        database: this.data.database,
        design: decodeDdoc,
        view: view,
        params: params
      });

      var ddocInfo = {
        id: "_design/" + decodeDdoc,
        currView: view,
        designDocs: this.data.designDocs
      };

      this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({
        model: this.data.database,
        ddocs: this.data.designDocs,
        viewName: view,
        params: params,
        newView: false,
        database: this.data.database,
        ddocInfo: ddocInfo
      }));

      if (this.toolsView) { this.toolsView.remove(); }

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        database: this.data.database,
        collection: this.data.indexedDocs,
        nestedView: Documents.Views.Row,
        viewList: true,
        ddocInfo: ddocInfo
      }));

      this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + '_' + app.utils.removeSpecialCharacters(view));

      this.crumbs = function () {
        return [
          {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)},
        ];
      };

      this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
    },

    newViewEditor: function () {
      var params = app.getParams();

      if (this.toolsView) {
        this.toolsView.remove();
      }

      this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({
        ddocs: this.data.designDocs,
        params: params,
        database: this.data.database,
        newView: true
      }));

      this.sidebar.setSelectedTab('new-view');
      this.crumbs = function () {
        return [
          {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)},
        ];
      };
    },

    updateAllDocsFromView: function (event) {
      var view = event.view,
          docOptions = app.getParams(),
          ddoc = event.ddoc;

      this.documentsView && this.documentsView.remove();

      if (event.allDocs) {
        this.data.database.buildAllDocs(docOptions);
        this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
          collection: this.data.database.allDocs
        }));
        //this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
        return;
      }

      this.data.indexedDocs = new Documents.IndexCollection(null, {
        database: this.data.database,
        design: ddoc,
        view: view,
        params: app.getParams()
      });

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        database: this.data.database,
        collection: this.data.indexedDocs,
        nestedView: Documents.Views.Row,
        viewList: true
      }));

      this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
    },

    updateAllDocsFromPreview: function (event) {
      var view = event.view,
      rows = event.rows,
      ddoc = event.ddoc;

      this.data.indexedDocs = new Documents.PouchIndexCollection(null, {
        database: this.data.database,
        design: ddoc,
        view: view,
        rows: rows
      });

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        database: this.data.database,
        collection: this.data.indexedDocs,
        nestedView: Documents.Views.Row,
        viewList: true
      }));
    },

    paginate: function (direction) {
      _.extend(this.documentsView.collection.params, app.getParams());
      this.documentsView.forceRender();
      if (direction === 'next') {
        this.documentsView.collection.skipFirstItem = true;
      } else {
        this.documentsView.collection.skipFirstItem = false;
      }
    },

    reloadDesignDocs: function (event) {
      this.sidebar.forceRender();

      if (event && event.selectedTab) {
        this.sidebar.setSelectedTab(event.selectedTab);
      }
    }

  });

  var ChangesRouteObject = FauxtonAPI.RouteObject.extend({
    layout: "with_tabs",
    selectedHeader: "Databases",
    crumbs: function () {
      return [
        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
        {"name": "_changes", "link": "/_changes"}
      ];
    },

    routes: {
      "database/:database/_changes(:params)": "changes"
    },

    initialize: function (route, masterLayout, options) {
      this.databaseName = options[0];
      this.database = new Databases.Model({id: this.databaseName});

      var docOptions = app.getParams();

      this.database.buildChanges(docOptions);

      this.setView("#tabs", new Documents.Views.Tabs({
        collection: this.designDocs,
        database: this.database,
        active_id: 'changes'
      }));
    },

    changes: function (event) {
      this.setView("#dashboard-content", new Documents.Views.Changes({
        model: this.database
      }));
    },

    apiUrl: function() {
      return [this.database.url("apiurl"), this.database.documentation()];
    }

  });

  Documents.RouteObjects = [DocEditorRouteObject, NewDocEditorRouteObject, DocumentsRouteObject, ChangesRouteObject];

  return Documents;
});
<commit_msg>Remove caching from design docs, so that edit revisions are updated<commit_after>// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

define([
       "app",

       "api",

       // Modules
       "addons/documents/views",
       "addons/databases/base"
],

function(app, FauxtonAPI, Documents, Databases) {

  var DocEditorRouteObject = FauxtonAPI.RouteObject.extend({
    layout: "one_pane",
    disableLoader: true,
    selectedHeader: "Databases",
    initialize: function(route, masterLayout, options) {
      var databaseName = options[0];
      this.docID = options[1]||'new';

      this.database = this.database || new Databases.Model({id: databaseName});
      this.doc = new Documents.Doc({
        _id: this.docID
      }, {
        database: this.database
      });

      this.tabsView = this.setView("#tabs", new Documents.Views.FieldEditorTabs({
        disableLoader: true,
        selected: "code_editor",
        model: this.doc
      }));

    },

    routes: {
      // We are hiding the field_editor for this first release
      // "database/:database/:doc/field_editor": "field_editor",
      "database/:database/:doc/code_editor": "code_editor",
      "database/:database/:doc": "code_editor"
    },

    events: {
      "route:reRenderDoc": "reRenderDoc",
      "route:duplicateDoc": "duplicateDoc"
    },

    crumbs: function() {
      return [
        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
        {"name": this.docID, "link": "#"}
      ];
    },

    code_editor: function (database, doc) {
      this.tabsView.updateSelected('code_editor');

      this.docView = this.setView("#dashboard-content", new Documents.Views.Doc({
        model: this.doc,
        database: this.database
      }));
    },

    reRenderDoc: function () {
      this.docView.forceRender();
    },

    field_editor: function(events) {
      this.tabsView.updateSelected('field_editor');
      this.docView = this.setView("#dashboard-content", new Documents.Views.DocFieldEditor({
        model: this.doc
      }));
    },

    duplicateDoc: function (newId) {
      var doc = this.doc,
      docView = this.docView,
      database = this.database;

      doc.copy(newId).then(function () {
        doc.set({_id: newId});
        docView.forceRender();
        FauxtonAPI.navigate('/database/' + database.safeID() + '/' + app.utils.safeURLName(newId), {trigger: true});
        FauxtonAPI.addNotification({
          msg: "Document has been duplicated."
        });

      }, function (error) {
        var errorMsg = "Could not duplicate document, reason: " + error.responseText + ".";
        FauxtonAPI.addNotification({
          msg: errorMsg,
          type: "error"
        });
      });
    },

    apiUrl: function() {
      return [this.doc.url("apiurl"), this.doc.documentation()];
    }
  });

  var NewDocEditorRouteObject = DocEditorRouteObject.extend({
    initialize: function (route, masterLayout, options) {
      var databaseName = options[0];

      this.database = this.database || new Databases.Model({id: databaseName});
      this.doc = new Documents.NewDoc(null,{
        database: this.database
      });

      this.tabsView = this.setView("#tabs", new Documents.Views.FieldEditorTabs({
        selected: "code_editor",
        model: this.doc
      }));

    },
    crumbs: function() {
      return [
        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
        {"name": "New", "link": "#"}
      ];
    },
    routes: {
      "database/:database/new": "code_editor"
    },
    selectedHeader: "Databases",

  });

  var DocumentsRouteObject = FauxtonAPI.RouteObject.extend({
    layout: "with_tabs_sidebar",
    selectedHeader: "Databases",
    routes: {
      "database/:database/_all_docs(:extra)": "allDocs",
      "database/:database/_design/:ddoc/_view/:view": {
        route: "viewFn",
        roles: ['_admin']
      },
      "database/:database/new_view": "newViewEditor"
    },

    events: {
      "route:updateAllDocs": "updateAllDocsFromView",
      "route:updatePreviewDocs": "updateAllDocsFromPreview",
      "route:reloadDesignDocs": "reloadDesignDocs",
      "route:paginate": "paginate"
    },

    initialize: function (route, masterLayout, options) {
      var docOptions = app.getParams();
      docOptions.include_docs = true;

      this.databaseName = options[0];

      this.data = {
        database: new Databases.Model({id:this.databaseName})
      };

      this.data.designDocs = new Documents.AllDocs(null, {
        database: this.data.database,
        params: {startkey: '"_design"',
          endkey: '"_design1"',
          include_docs: true}
      });

      this.sidebar = this.setView("#sidebar-content", new Documents.Views.Sidebar({
        collection: this.data.designDocs,
        database: this.data.database
      }));
    },

    establish: function () {
      return this.data.designDocs.fetch();
    },

    allDocs: function(databaseName, options) {
      var docOptions = app.getParams(options);

      this.data.database.buildAllDocs(docOptions);

      if (docOptions.startkey && docOptions.startkey.indexOf('_design') > -1) {
        this.sidebar.setSelectedTab('design-docs');
      } else {
        this.sidebar.setSelectedTab('all-docs');
      }

      if (this.viewEditor) { this.viewEditor.remove(); }

      this.toolsView = this.setView("#dashboard-upper-menu", new Documents.Views.JumpToDoc({
        database: this.data.database,
        collection: this.data.database.allDocs
      }));

      this.setView("#dashboard-upper-content", new Documents.Views.AllDocsLayout({
        database: this.data.database,
        collection: this.data.database.allDocs,
        params: docOptions
      }));

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        collection: this.data.database.allDocs
      }));

      this.crumbs = [
        {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)}
      ];

      this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
    },

    viewFn: function (databaseName, ddoc, view) {
      var params = app.getParams(),
          decodeDdoc = decodeURIComponent(ddoc);

      view = view.replace(/\?.*$/,'');

      this.data.indexedDocs = new Documents.IndexCollection(null, {
        database: this.data.database,
        design: decodeDdoc,
        view: view,
        params: params
      });

      var ddocInfo = {
        id: "_design/" + decodeDdoc,
        currView: view,
        designDocs: this.data.designDocs
      };

      this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({
        model: this.data.database,
        ddocs: this.data.designDocs,
        viewName: view,
        params: params,
        newView: false,
        database: this.data.database,
        ddocInfo: ddocInfo
      }));

      if (this.toolsView) { this.toolsView.remove(); }

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        database: this.data.database,
        collection: this.data.indexedDocs,
        nestedView: Documents.Views.Row,
        viewList: true,
        ddocInfo: ddocInfo
      }));

      this.sidebar.setSelectedTab(app.utils.removeSpecialCharacters(ddoc) + '_' + app.utils.removeSpecialCharacters(view));

      this.crumbs = function () {
        return [
          {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)},
        ];
      };

      this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
    },

    newViewEditor: function () {
      var params = app.getParams();

      if (this.toolsView) {
        this.toolsView.remove();
      }

      this.viewEditor = this.setView("#dashboard-upper-content", new Documents.Views.ViewEditor({
        ddocs: this.data.designDocs,
        params: params,
        database: this.data.database,
        newView: true
      }));

      this.sidebar.setSelectedTab('new-view');
      this.crumbs = function () {
        return [
          {"name": this.data.database.id, "link": Databases.databaseUrl(this.data.database)},
        ];
      };
    },

    updateAllDocsFromView: function (event) {
      var view = event.view,
          docOptions = app.getParams(),
          ddoc = event.ddoc;

      this.documentsView && this.documentsView.remove();

      if (event.allDocs) {
        this.data.database.buildAllDocs(docOptions);
        this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
          collection: this.data.database.allDocs
        }));
        //this.apiUrl = [this.data.database.allDocs.url("apiurl"), this.data.database.allDocs.documentation() ];
        return;
      }

      this.data.indexedDocs = new Documents.IndexCollection(null, {
        database: this.data.database,
        design: ddoc,
        view: view,
        params: app.getParams()
      });

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        database: this.data.database,
        collection: this.data.indexedDocs,
        nestedView: Documents.Views.Row,
        viewList: true
      }));

      this.apiUrl = [this.data.indexedDocs.url("apiurl"), "docs"];
    },

    updateAllDocsFromPreview: function (event) {
      var view = event.view,
      rows = event.rows,
      ddoc = event.ddoc;

      this.data.indexedDocs = new Documents.PouchIndexCollection(null, {
        database: this.data.database,
        design: ddoc,
        view: view,
        rows: rows
      });

      this.documentsView = this.setView("#dashboard-lower-content", new Documents.Views.AllDocsList({
        database: this.data.database,
        collection: this.data.indexedDocs,
        nestedView: Documents.Views.Row,
        viewList: true
      }));
    },

    paginate: function (direction) {
      _.extend(this.documentsView.collection.params, app.getParams());
      this.documentsView.forceRender();
      if (direction === 'next') {
        this.documentsView.collection.skipFirstItem = true;
      } else {
        this.documentsView.collection.skipFirstItem = false;
      }
    },

    reloadDesignDocs: function (event) {
      this.sidebar.forceRender();

      if (event && event.selectedTab) {
        this.sidebar.setSelectedTab(event.selectedTab);
      }
    }

  });

  var ChangesRouteObject = FauxtonAPI.RouteObject.extend({
    layout: "with_tabs",
    selectedHeader: "Databases",
    crumbs: function () {
      return [
        {"name": this.database.id, "link": Databases.databaseUrl(this.database)},
        {"name": "_changes", "link": "/_changes"}
      ];
    },

    routes: {
      "database/:database/_changes(:params)": "changes"
    },

    initialize: function (route, masterLayout, options) {
      this.databaseName = options[0];
      this.database = new Databases.Model({id: this.databaseName});

      var docOptions = app.getParams();

      this.database.buildChanges(docOptions);

      this.setView("#tabs", new Documents.Views.Tabs({
        collection: this.designDocs,
        database: this.database,
        active_id: 'changes'
      }));
    },

    changes: function (event) {
      this.setView("#dashboard-content", new Documents.Views.Changes({
        model: this.database
      }));
    },

    apiUrl: function() {
      return [this.database.url("apiurl"), this.database.documentation()];
    }

  });

  Documents.RouteObjects = [DocEditorRouteObject, NewDocEditorRouteObject, DocumentsRouteObject, ChangesRouteObject];

  return Documents;
});
<|endoftext|>


<commit_before>define([
  "app",

  "api"
],

function(app, FauxtonAPI) {
  var Views = {};

  Views.Item = FauxtonAPI.View.extend({
    template: "templates/databases/item",
    tagName: "tr",

    serialize: function() {
      return {
        database: this.model
      };
    }
  });

  Views.List = FauxtonAPI.View.extend({
    dbLimit: 10,
    template: "templates/databases/list",
    events: {
      "click button.all": "selectAll",
      "submit form.database-search": "switchDatabase"
    },

    initialize: function(options) {
      this.collection.on("add", this.render, this);
    },

    serialize: function() {
      return {
        databases: this.collection
      };
    },

    switchDatabase: function(event) {
      event.preventDefault();
      var dbname = this.$el.find("input.search-query").val();

      if (dbname) {
        // TODO: switch to using a model, or Databases.databaseUrl()
        // Neither of which are in scope right now
        // var db = new Database.Model({id: dbname});
        var url = ["/database/", dbname, "/_all_docs?limit=10"].join('');
        FauxtonAPI.navigate(url);
      }
    },

    beforeRender: function() {
      this.collection.each(function(database) {
        this.insertView("table.databases tbody", new Views.Item({
          model: database
        }));
      }, this);
    },

    afterRender: function() {
      var dbLimit = this.dbLimit;
      var ajaxReq;

      this.$el.find("input.search-query").typeahead({
        source: function(query, process) {
          var url = [
            app.host,
            "/_all_dbs?startkey=%22",
            query,
            "%22&endkey=%22",
            query,
            "\u9999%22&limit=",
            dbLimit
          ].join('');
          if (ajaxReq) ajaxReq.abort();
          ajaxReq = $.ajax({
            url: url,
            dataType: 'json',
            success: function(data) {
              process(data);
            }
          });
        }
      });
    },

    selectAll: function(evt){
      $("input:checkbox").attr('checked', !$(evt.target).hasClass('active'));
    }
  });

  Views.Sidebar = FauxtonAPI.View.extend({
    template: "templates/databases/sidebar",
    events: {
      "click a#new": "newDatabase",
      "click a#owned": "showMine",
      "click a#shared": "showShared"
    },

    newDatabase: function() {
      // TODO: use a modal here instead of the prompt
      var name = prompt('Name of database', 'newdatabase');
      var db = new this.collection.model({
        id: encodeURIComponent(name),
        name: name
      });
      db.save().done(function() {
        var route = "#/database/" +  name + "/_all_docs?limit=100";
        app.router.navigate(route, { trigger: true });
      }
      );
    },

    showMine: function(){
      console.log('will show users databases and hide shared');
    },

    showShared: function(){
      console.log('will show shared databases and hide the users');
      alert('Support for shared databases coming soon');
    }
  });

  return Views;
});
<commit_msg>Display notifications when creating a new database<commit_after>define([
  "app",

  "api"
],

function(app, FauxtonAPI) {
  var Views = {};

  Views.Item = FauxtonAPI.View.extend({
    template: "templates/databases/item",
    tagName: "tr",

    serialize: function() {
      return {
        database: this.model
      };
    }
  });

  Views.List = FauxtonAPI.View.extend({
    dbLimit: 10,
    template: "templates/databases/list",
    events: {
      "click button.all": "selectAll",
      "submit form.database-search": "switchDatabase"
    },

    initialize: function(options) {
      this.collection.on("add", this.render, this);
    },

    serialize: function() {
      return {
        databases: this.collection
      };
    },

    switchDatabase: function(event) {
      event.preventDefault();
      var dbname = this.$el.find("input.search-query").val();

      if (dbname) {
        // TODO: switch to using a model, or Databases.databaseUrl()
        // Neither of which are in scope right now
        // var db = new Database.Model({id: dbname});
        var url = ["/database/", dbname, "/_all_docs?limit=10"].join('');
        FauxtonAPI.navigate(url);
      }
    },

    beforeRender: function() {
      this.collection.each(function(database) {
        this.insertView("table.databases tbody", new Views.Item({
          model: database
        }));
      }, this);
    },

    afterRender: function() {
      var dbLimit = this.dbLimit;
      var ajaxReq;

      this.$el.find("input.search-query").typeahead({
        source: function(query, process) {
          var url = [
            app.host,
            "/_all_dbs?startkey=%22",
            query,
            "%22&endkey=%22",
            query,
            "\u9999%22&limit=",
            dbLimit
          ].join('');
          if (ajaxReq) ajaxReq.abort();
          ajaxReq = $.ajax({
            url: url,
            dataType: 'json',
            success: function(data) {
              process(data);
            }
          });
        }
      });
    },

    selectAll: function(evt){
      $("input:checkbox").attr('checked', !$(evt.target).hasClass('active'));
    }
  });

  Views.Sidebar = FauxtonAPI.View.extend({
    template: "templates/databases/sidebar",
    events: {
      "click a#new": "newDatabase",
      "click a#owned": "showMine",
      "click a#shared": "showShared"
    },

    newDatabase: function() {
      // TODO: use a modal here instead of the prompt
      var name = prompt('Name of database', 'newdatabase');
      var db = new this.collection.model({
        id: encodeURIComponent(name),
        name: name
      });
      var notification = FauxtonAPI.addNotification({msg: "Creating database."});
      db.save().done(function() {
        notification = FauxtonAPI.addNotification({
          msg: "Database created successfully",
          type: "success",
          clear: true
        });
        var route = "#/database/" +  name + "/_all_docs?limit=100";
        app.router.navigate(route, { trigger: true });
      }
      ).error(function(xhr) {
        var responseText = JSON.parse(xhr.responseText).reason;
        notification = FauxtonAPI.addNotification({
          msg: "Create database failed: " + responseText,
          type: "error",
          clear: true
        });
      }
      );
    },

    showMine: function(){
      console.log('will show users databases and hide shared');
    },

    showShared: function(){
      console.log('will show shared databases and hide the users');
      alert('Support for shared databases coming soon');
    }
  });

  return Views;
});
<|endoftext|>


<commit_before>// um flackern zu unterbinden
document.write('<style type="text/css"> div.vpsSwitchOverlay div.switchContent { display: none; } </style>');

Vps.onContentReady(function() {
    var els = Ext.query('div.vpsSwitchOverlay');
    els.forEach(function(el) {
        el = Ext.get(el);
        el.switchOverlayObject = new Vps.Switch.Overlay(el);
    });
});

Vps.Switch.Overlay = function(el) {
    this.addEvents({
        'beforeOpen': true,
        'beforeClose': true,
        'opened': true,
        'closed': true
    });
    this._lockAnimation = false;

    this.el = el;
    this.switchLink = Ext.get(Ext.query('.switchLink', this.el.dom)[0]);
    this.switchContent = Ext.get(Ext.query('.switchContent', this.el.dom)[0]);

    // if it is important, show on startup
    if (this.switchContent.child('.vpsImportant')) {
        this.switchContent.setStyle('display', 'block');
        this.switchLink.addClass('switchLinkOpened');
    }

    if (this.switchLink && this.switchContent) {
        Ext.EventManager.addListener(this.switchLink, 'click', function(e) {
            if (this.switchLink.hasClass('switchLinkOpened')) {
                this.doClose();
            } else {
                this.doOpen();
            }
        }, this, { stopEvent: true });
    }
};

Ext.extend(Vps.Switch.Overlay, Ext.util.Observable, {
    doClose: function() {
        if (this._lockAnimation) return;
        this._lockAnimation = true;

        this.fireEvent('beforeClose', this);

        this.switchContent.fadeOut({
            endOpacity: .0,
            easing: 'easeIn',
            duration: .35,
            useDisplay: true,
            callback: function() {
                this.fireEvent('closed', this);
                this._lockAnimation = false;
            },
            scope: this
        });
        this.switchLink.removeClass('switchLinkOpened');
    },

    doOpen: function() {
        if (this._lockAnimation) return;
        this._lockAnimation = true;

        this.fireEvent('beforeOpen', this);

        this.switchContent.fadeIn({
            endOpacity: 1.0,
            easing: 'easeIn',
            duration: .35,
            useDisplay: true,
            callback: function() {
                this.fireEvent('opened', this);
                this._lockAnimation = false;
            },
            scope: this
        });
        this.switchLink.addClass('switchLinkOpened');
    }
});<commit_msg>markus: switch overlay kann mehrere switch links haben<commit_after>// um flackern zu unterbinden
document.write('<style type="text/css"> div.vpsSwitchOverlay div.switchContent { display: none; } </style>');

Vps.onContentReady(function() {
    var els = Ext.query('div.vpsSwitchOverlay');
    els.forEach(function(el) {
        el = Ext.get(el);
        el.switchOverlayObject = new Vps.Switch.Overlay(el);
    });
});

Vps.Switch.Overlay = function(el) {
    this.addEvents({
        'beforeOpen': true,
        'beforeClose': true,
        'opened': true,
        'closed': true
    });
    this._lockAnimation = false;

    this.el = el;
    this.switchLinks = Ext.query('.switchLink', this.el.dom);
    this.switchContent = Ext.get(Ext.query('.switchContent', this.el.dom)[0]);

    // if it is important, show on startup
    if (this.switchContent.child('.vpsImportant')) {
        this.switchContent.setStyle('display', 'block');
        this.switchLinks.each(function(sl) {
        	Ext.get(sl).addClass('switchLinkOpened');
        }, this);
    }

    if (this.switchLinks.length && this.switchContent) {
    	this.switchLinks.each(function(sl) {
	        Ext.EventManager.addListener(sl, 'click', function(e) {
	        	this.switchLinks.each(function(sl) {
		            if (Ext.get(sl).hasClass('switchLinkOpened')) {
		                this.doClose();
		            } else {
		                this.doOpen();
		            }
	        	}, this);
	        }, this, { stopEvent: true });
    	}, this);
    }
};

Ext.extend(Vps.Switch.Overlay, Ext.util.Observable, {
    doClose: function() {
        if (this._lockAnimation) return;
        this._lockAnimation = true;

        this.fireEvent('beforeClose', this);

        this.switchContent.fadeOut({
            endOpacity: .0,
            easing: 'easeIn',
            duration: .35,
            useDisplay: true,
            callback: function() {
                this.fireEvent('closed', this);
                this._lockAnimation = false;
            },
            scope: this
        });
        this.switchLinks.each(function(sl) {
        	Ext.get(sl).removeClass('switchLinkOpened');
        }, this);
    },

    doOpen: function() {
        if (this._lockAnimation) return;
        this._lockAnimation = true;

        this.fireEvent('beforeOpen', this);

        this.switchContent.fadeIn({
            endOpacity: 1.0,
            easing: 'easeIn',
            duration: .35,
            useDisplay: true,
            callback: function() {
                this.fireEvent('opened', this);
                this._lockAnimation = false;
            },
            scope: this
        });
        this.switchLinks.each(function(sl) {
        	Ext.get(sl).addClass('switchLinkOpened');
        }, this);
    }
});<|endoftext|>