<commit_before>define(function () {
    'use strict';

    var SlidingRender = Backbone.Marionette.Behavior.extend({
        collectionEvents: {
            'reset': '_onCollectionReset',
            'remove': '_onCollectionRemove',
            'add': '_onCollectionAdd',
            'change:active': '_onCollectionChangeActive'
        },

        ui: {
            list: '.list'
        },

        //  Enables progressive rendering of children by keeping track of indices which are currently rendered.
        minRenderIndex: -1,
        maxRenderIndex: -1,

        //  The height of a rendered childView in px. Including padding/margin.
        childViewHeight: 40,
        viewportHeight: -1,

        //  The number of items to render outside of the viewport. Helps with flickering because if
        //  only views which would be visible are rendered then they'd be visible while loading.
        threshold: 10,

        //  Keep track of where user is scrolling from to determine direction and amount changed.
        lastScrollTop: 0,

        initialize: function () {
            //  IMPORTANT: Stub out the view's implementation of addChild with the slidingRender version.
            this.view.addChild = this._addChild.bind(this);
        },

        onShow: function () {
            //  Allow N items to be rendered initially where N is how many items need to cover the viewport.
            this.minRenderIndex = this._getMinRenderIndex(0);
            this._setViewportHeight();

            //  If the collection implements getActiveItem - scroll to the active item.
            if (this.view.collection.getActiveItem) {
                if (this.view.collection.length > 0) {
                    this._scrollToItem(this.view.collection.getActiveItem());
                }
            }

            var self = this;
            //  Throttle the scroll event because scrolls can happen a lot and don't need to re-calculate very often.
            this.ui.list.scroll(_.throttle(function () {
                self._setRenderedElements(this.scrollTop);
            }, 20));
        },

        //  jQuery UI's sortable needs to be able to know the minimum rendered index. Whenever an external
        //  event requests the min render index -- return it!
        onGetMinRenderIndex: function () {
            this.view.triggerMethod('GetMinRenderIndexReponse', {
                minRenderIndex: this.minRenderIndex
            });
        },

        onListHeightUpdated: function () {
            this._setViewportHeight();
        },

        //  Whenever the viewport height is changed -- adjust the items which are currently rendered to match
        _setViewportHeight: function () {
            this.viewportHeight = this.ui.list.height();

            //  Unload or load N items where N is the difference in viewport height.
            var currentMaxRenderIndex = this.maxRenderIndex;

            var newMaxRenderIndex = this._getMaxRenderIndex(this.lastScrollTop);
            var indexDifference = currentMaxRenderIndex - newMaxRenderIndex;

            //  Be sure to update before potentially adding items or else they won't render.
            this.maxRenderIndex = newMaxRenderIndex;
            if (indexDifference > 0) {
                //  Unload N Items.
                //  Only remove items if need be -- collection's length might be so small that the viewport's height isn't affecting rendered count.
                if (this.view.collection.length > currentMaxRenderIndex) {
                    this._removeItemsByIndex(currentMaxRenderIndex, indexDifference);
                }
            }
            else if (indexDifference < 0) {
                //  Load N items
                for (var count = 0; count < Math.abs(indexDifference) ; count++) {
                    this._renderElementAtIndex(currentMaxRenderIndex + 1 + count);
                }
            }

            this._setHeightPaddingTop();
        },

        //  When deleting an element from a list it's important to render the next element (if any) since
        //  usually this only happens during scroll, but positions change when removing.
        _renderElementAtIndex: function (index) {
            var rendered = false;

            if (this.view.collection.length > index) {
                var item = this.view.collection.at(index);
                var ChildView = this.view.getChildView(item);

                //  Adjust the childView's index to account for where it is actually being added in the list
                this._addChild(item, ChildView, index);
                rendered = true;
            }

            return rendered;
        },

        _setRenderedElements: function (scrollTop) {
            //  Figure out the range of items currently rendered:
            var currentMinRenderIndex = this.minRenderIndex;
            var currentMaxRenderIndex = this.maxRenderIndex;

            //  Figure out the range of items which need to be rendered:
            var minRenderIndex = this._getMinRenderIndex(scrollTop);
            var maxRenderIndex = this._getMaxRenderIndex(scrollTop);

            var itemsToAdd = [];
            var itemsToRemove = [];

            //  Append items in the direction being scrolled and remove items being scrolled away from.
            var direction = scrollTop > this.lastScrollTop ? 'down' : 'up';

            if (direction === 'down') {
                //  Need to remove items which are less than the new minRenderIndex
                if (minRenderIndex > currentMinRenderIndex) {
                    itemsToRemove = this.view.collection.slice(currentMinRenderIndex, minRenderIndex);
                }

                //  Need to add items which are greater than oldMaxRenderIndex and ltoe maxRenderIndex
                if (maxRenderIndex > currentMaxRenderIndex) {
                    itemsToAdd = this.view.collection.slice(currentMaxRenderIndex + 1, maxRenderIndex + 1);
                }
            } else {
                //  Need to add items which are greater than currentMinRenderIndex and ltoe minRenderIndex
                if (minRenderIndex < currentMinRenderIndex) {
                    itemsToAdd = this.view.collection.slice(minRenderIndex, currentMinRenderIndex);
                }

                //  Need to remove items which are greater than the new maxRenderIndex
                if (maxRenderIndex < currentMaxRenderIndex) {
                    itemsToRemove = this.view.collection.slice(maxRenderIndex + 1, currentMaxRenderIndex + 1);
                }
            }

            if (itemsToAdd.length > 0 || itemsToRemove.length > 0) {
                this.minRenderIndex = minRenderIndex;
                this.maxRenderIndex = maxRenderIndex;

                if (direction === 'down') {
                    //  Items will be appended after oldMaxRenderIndex.
                    this._addItems(itemsToAdd, currentMaxRenderIndex + 1, true);
                } else {
                    this._addItems(itemsToAdd, minRenderIndex, false);
                }

                this._removeItems(itemsToRemove);
                this._setHeightPaddingTop();
            }

            this.lastScrollTop = scrollTop;
        },

        _setHeightPaddingTop: function() {
            this._setPaddingTop();
            this._setHeight();
        },

        //  Adjust padding-top to properly position relative items inside of list since not all items are rendered.
        _setPaddingTop: function () {
            this.view.ui.childContainer.css('padding-top', this._getPaddingTop());
        },

        _getPaddingTop: function () {
            return this.minRenderIndex * this.childViewHeight;
        },

        //  Set the elements height calculated from the number of potential items rendered into it.
        //  Necessary because items are lazy-appended for performance, but scrollbar size changing not desired.
        _setHeight: function () {
            //  Subtracting minRenderIndex is important because of how CSS renders the element. If you don't subtract minRenderIndex
            //  then the rendered items will push up the height of the element by minRenderIndex * childViewHeight.
            var height = (this.view.collection.length - this.minRenderIndex) * this.childViewHeight;

            //  Keep height set to at least the viewport height to allow for proper drag-and-drop target - can't drop if height is too small.
            if (height < this.viewportHeight) {
                height = this.viewportHeight;
            }

            this.view.ui.childContainer.height(height);
        },

        _addItems: function (models, indexOffset, isAddingToEnd) {
            var ChildView;
            _.each(models, function (model, index) {
                ChildView = this.view.getChildView(model);

                if (isAddingToEnd) {
                    //  Adjust the childView's index to account for where it is actually being added in the list
                    this._addChild(model, ChildView, index + indexOffset);
                } else {
                    //  Adjust the childView's index to account for where it is actually being added in the list, but
                    //  also provide the unmodified index because this is the location in the rendered childViewList in which it will be added.
                    this._addChild(model, ChildView, index, index + indexOffset);
                }
            }, this);
        },

        //  Remove N items from the end of the render item list.
        _removeItemsByIndex: function (startIndex, countToRemove) {
            for (var index = 0; index < countToRemove; index++) {
                var item = this.view.collection.at(startIndex - index);
                var childView = this.view.children.findByModel(item);
                this.view.removeChildView(childView);
            }
        },

        _removeItems: function (models) {
            _.each(models, function (model) {
                var childView = this.view.children.findByModel(model);

                this.view.removeChildView(childView);
            }, this);
        },

        _addChild: function (item, ChildView, index, indexOverride) {
            //  indexOverride is necessary because the 'actual' index of an item is different from its rendered position's index.
            var shouldAdd;
            if (_.isUndefined(indexOverride)) {
                shouldAdd = this._indexWithinRenderRange(index);
            } else {
                shouldAdd = this._indexWithinRenderRange(indexOverride);
            }

            if (shouldAdd) {
                Backbone.Marionette.CompositeView.prototype.addChild.apply(this.view, arguments);
            }
        },

        _getMinRenderIndex: function (scrollTop) {
            var minRenderIndex = Math.floor(scrollTop / this.childViewHeight) - this.threshold;

            if (minRenderIndex < 0) {
                minRenderIndex = 0;
            }

            return minRenderIndex;
        },

        _getMaxRenderIndex: function (scrollTop) {
            //  Subtract 1 to make math 'inclusive' instead of 'exclusive'
            var maxRenderIndex = Math.ceil((scrollTop / this.childViewHeight) + (this.viewportHeight / this.childViewHeight)) - 1 + this.threshold;

            return maxRenderIndex;
        },

        //  Returns true if an childView at the given index would not be fully visible -- part of it rendering out of the top of the viewport.
        _indexOverflowsTop: function (index) {
            var position = index * this.childViewHeight;
            var scrollPosition = this.ui.list.scrollTop();

            var overflowsTop = position < scrollPosition;

            return overflowsTop;
        },

        _indexOverflowsBottom: function (index) {
            //  Add one to index because want to get the bottom of the element and not the top.
            var position = (index + 1) * this.childViewHeight;
            var scrollPosition = this.ui.list.scrollTop() + this.viewportHeight;

            var overflowsBottom = position > scrollPosition;

            return overflowsBottom;
        },

        _indexWithinRenderRange: function (index) {
            return index >= this.minRenderIndex && index <= this.maxRenderIndex;
        },

        //  Ensure that the active item is visible by setting the container's scrollTop to a position which allows it to be seen.
        _scrollToItem: function (item) {
            var itemIndex = this.view.collection.indexOf(item);

            var overflowsTop = this._indexOverflowsTop(itemIndex);
            var overflowsBottom = this._indexOverflowsBottom(itemIndex);

            //  Only scroll to the item if it isn't in the viewport.
            if (overflowsTop || overflowsBottom) {
                var scrollTop = 0;

                //  If the item needs to be made visible from the bottom, offset the viewport's height:
                if (overflowsBottom) {
                    //  Add 1 to index because want the bottom of the element and not the top.
                    scrollTop = (itemIndex + 1) * this.childViewHeight - this.viewportHeight;
                }
                else if (overflowsTop) {
                    scrollTop = itemIndex * this.childViewHeight;
                }

                this.ui.list.scrollTop(scrollTop);
            }
        },

        //  Reset min/max, scrollTop, paddingTop and height to their default values.
        _onCollectionReset: function () {
            this.ui.list.scrollTop(0);
            this.lastScrollTop = 0;

            this.minRenderIndex = this._getMinRenderIndex(0);
            this.maxRenderIndex = this._getMaxRenderIndex(0);

            this._setHeightPaddingTop();
        },

        _onCollectionRemove: function (item, collection, options) {
            //  When a rendered view is lost - render the next one since there's a spot in the viewport
            if (this._indexWithinRenderRange(options.index)) {
                var rendered = this._renderElementAtIndex(this.maxRenderIndex);

                //  If failed to render next item and there are previous items waiting to be rendered, slide view back 1 item
                if (!rendered && this.minRenderIndex > 0) {
                    this.ui.list.scrollTop(this.lastScrollTop - this.childViewHeight);
                }
            }

            this._setHeightPaddingTop();
        },

        _onCollectionAdd: function (item, collection) {
            var index = collection.indexOf(item);

            var indexWithinRenderRange = this._indexWithinRenderRange(index);

            //  Subtract 1 from collection.length because, for instance, if our collection has 8 items in it
            //  and min-max is 0-7, the 8th item in the collection has an index of 7.
            //  Use a > comparator not >= because we only want to run this logic when the viewport is overfilled and not just enough to be filled.
            var viewportOverfull = collection.length - 1 > this.maxRenderIndex;

            //  If a view has been rendered and it pushes another view outside of maxRenderIndex, remove that view.
            if (indexWithinRenderRange && viewportOverfull) {
                //  Adding one because I want to grab the item which is outside maxRenderIndex. maxRenderIndex is inclusive.
                this._removeItemsByIndex(this.maxRenderIndex + 1, 1);
            }

            this._setHeightPaddingTop();
        },

        _onCollectionChangeActive: function (item, active) {
            if (active) {
                this._scrollToItem(item);
            }
        }
    });

    return SlidingRender;
});<commit_msg>Fixed slidingRender logic<commit_after>define(function () {
    'use strict';

    var SlidingRender = Backbone.Marionette.Behavior.extend({
        collectionEvents: {
            'reset': '_onCollectionReset',
            'remove': '_onCollectionRemove',
            'add': '_onCollectionAdd',
            'change:active': '_onCollectionChangeActive'
        },

        ui: {
            list: '.list'
        },

        //  Enables progressive rendering of children by keeping track of indices which are currently rendered.
        minRenderIndex: -1,
        maxRenderIndex: -1,

        //  The height of a rendered childView in px. Including padding/margin.
        childViewHeight: 40,
        viewportHeight: -1,

        //  The number of items to render outside of the viewport. Helps with flickering because if
        //  only views which would be visible are rendered then they'd be visible while loading.
        threshold: 10,

        //  Keep track of where user is scrolling from to determine direction and amount changed.
        lastScrollTop: 0,

        initialize: function () {
            //  IMPORTANT: Stub out the view's implementation of addChild with the slidingRender version.
            this.view.addChild = this._addChild.bind(this);
        },

        onShow: function () {
            //  Allow N items to be rendered initially where N is how many items need to cover the viewport.
            this.minRenderIndex = this._getMinRenderIndex(0);
            this._setViewportHeight();

            //  If the collection implements getActiveItem - scroll to the active item.
            if (this.view.collection.getActiveItem) {
                if (this.view.collection.length > 0) {
                    this._scrollToItem(this.view.collection.getActiveItem());
                }
            }

            var self = this;
            //  Throttle the scroll event because scrolls can happen a lot and don't need to re-calculate very often.
            this.ui.list.scroll(_.throttle(function () {
                self._setRenderedElements(this.scrollTop);
            }, 20));
        },

        //  jQuery UI's sortable needs to be able to know the minimum rendered index. Whenever an external
        //  event requests the min render index -- return it!
        onGetMinRenderIndex: function () {
            this.view.triggerMethod('GetMinRenderIndexReponse', {
                minRenderIndex: this.minRenderIndex
            });
        },

        onListHeightUpdated: function () {
            this._setViewportHeight();
        },

        //  Whenever the viewport height is changed -- adjust the items which are currently rendered to match
        _setViewportHeight: function () {
            this.viewportHeight = this.ui.list.height();

            //  Unload or load N items where N is the difference in viewport height.
            var currentMaxRenderIndex = this.maxRenderIndex;

            var newMaxRenderIndex = this._getMaxRenderIndex(this.lastScrollTop);
            var indexDifference = currentMaxRenderIndex - newMaxRenderIndex;

            //  Be sure to update before potentially adding items or else they won't render.
            this.maxRenderIndex = newMaxRenderIndex;
            if (indexDifference > 0) {
                //  Unload N Items.
                //  Only remove items if need be -- collection's length might be so small that the viewport's height isn't affecting rendered count.
                if (this.view.collection.length > currentMaxRenderIndex) {
                    this._removeItemsByIndex(currentMaxRenderIndex, indexDifference);
                }
            }
            else if (indexDifference < 0) {
                //  Load N items
                for (var count = 0; count < Math.abs(indexDifference) ; count++) {
                    this._renderElementAtIndex(currentMaxRenderIndex + 1 + count);
                }
            }

            this._setHeightPaddingTop();
        },

        //  When deleting an element from a list it's important to render the next element (if any) since
        //  usually this only happens during scroll, but positions change when removing.
        _renderElementAtIndex: function (index) {
            var rendered = false;

            if (this.view.collection.length > index) {
                var item = this.view.collection.at(index);
                var ChildView = this.view.getChildView(item);

                //  Adjust the childView's index to account for where it is actually being added in the list
                this._addChild(item, ChildView, index);
                rendered = true;
            }

            return rendered;
        },

        _setRenderedElements: function (scrollTop) {
            //  Figure out the range of items currently rendered:
            var currentMinRenderIndex = this.minRenderIndex;
            var currentMaxRenderIndex = this.maxRenderIndex;

            //  Figure out the range of items which need to be rendered:
            var minRenderIndex = this._getMinRenderIndex(scrollTop);
            var maxRenderIndex = this._getMaxRenderIndex(scrollTop);

            var itemsToAdd = [];
            var itemsToRemove = [];

            //  Append items in the direction being scrolled and remove items being scrolled away from.
            var direction = scrollTop > this.lastScrollTop ? 'down' : 'up';

            if (direction === 'down') {
                //  Need to remove items which are less than the new minRenderIndex
                if (minRenderIndex > currentMinRenderIndex) {
                    itemsToRemove = this.view.collection.slice(currentMinRenderIndex, minRenderIndex);
                }

                //  Need to add items which are greater than oldMaxRenderIndex and ltoe maxRenderIndex
                if (maxRenderIndex > currentMaxRenderIndex) {
                    itemsToAdd = this.view.collection.slice(currentMaxRenderIndex + 1, maxRenderIndex + 1);
                }
            } else {
                //  Need to add items which are greater than currentMinRenderIndex and ltoe minRenderIndex
                if (minRenderIndex < currentMinRenderIndex) {
                    itemsToAdd = this.view.collection.slice(minRenderIndex, currentMinRenderIndex);
                }

                //  Need to remove items which are greater than the new maxRenderIndex
                if (maxRenderIndex < currentMaxRenderIndex) {
                    itemsToRemove = this.view.collection.slice(maxRenderIndex + 1, currentMaxRenderIndex + 1);
                }
            }

            if (itemsToAdd.length > 0 || itemsToRemove.length > 0) {
                this.minRenderIndex = minRenderIndex;
                this.maxRenderIndex = maxRenderIndex;

                var currentTotalRendered = (currentMaxRenderIndex - currentMinRenderIndex) + 1;

                if (direction === 'down') {
                    //  Items will be appended after oldMaxRenderIndex.
                    this._addItems(itemsToAdd, currentMaxRenderIndex + 1, currentTotalRendered, true);
                } else {
                    this._addItems(itemsToAdd, minRenderIndex, currentTotalRendered, false);
                }

                this._removeItems(itemsToRemove);
                this._setHeightPaddingTop();
            }

            this.lastScrollTop = scrollTop;
        },

        _setHeightPaddingTop: function() {
            this._setPaddingTop();
            this._setHeight();
        },

        //  Adjust padding-top to properly position relative items inside of list since not all items are rendered.
        _setPaddingTop: function () {
            this.view.ui.childContainer.css('padding-top', this._getPaddingTop());
        },

        _getPaddingTop: function () {
            return this.minRenderIndex * this.childViewHeight;
        },

        //  Set the elements height calculated from the number of potential items rendered into it.
        //  Necessary because items are lazy-appended for performance, but scrollbar size changing not desired.
        _setHeight: function () {
            //  Subtracting minRenderIndex is important because of how CSS renders the element. If you don't subtract minRenderIndex
            //  then the rendered items will push up the height of the element by minRenderIndex * childViewHeight.
            var height = (this.view.collection.length - this.minRenderIndex) * this.childViewHeight;

            //  Keep height set to at least the viewport height to allow for proper drag-and-drop target - can't drop if height is too small.
            if (height < this.viewportHeight) {
                height = this.viewportHeight;
            }

            this.view.ui.childContainer.height(height);
        },

        _addItems: function (models, indexOffset, currentTotalRendered, isAddingToEnd) {
            var skippedCount = 0;

            var ChildView;
            _.each(models, function (model, index) {
                ChildView = this.view.getChildView(model);

                var shouldAdd = this._indexWithinRenderRange(index + indexOffset);

                if (shouldAdd) {
                    if (isAddingToEnd) {
                        //  Adjust the childView's index to account for where it is actually being added in the list
                        this._addChild(model, ChildView, index + currentTotalRendered - skippedCount, true);
                    } else {
                        //  Adjust the childView's index to account for where it is actually being added in the list, but
                        //  also provide the unmodified index because this is the location in the rendered childViewList in which it will be added.
                        this._addChild(model, ChildView, index, true);
                    }
                } else {
                    skippedCount++;
                }
            }, this);
        },

        //  Remove N items from the end of the render item list.
        _removeItemsByIndex: function (startIndex, countToRemove) {
            for (var index = 0; index < countToRemove; index++) {
                var item = this.view.collection.at(startIndex - index);
                var childView = this.view.children.findByModel(item);
                this.view.removeChildView(childView);
            }
        },

        _removeItems: function (models) {
            _.each(models, function (model) {
                var childView = this.view.children.findByModel(model);

                this.view.removeChildView(childView);
            }, this);
        },

        //  The bypass flag is set when shouldAdd has already been determined elsewhere.
        //  This is necessary because sometimes the view's model's index in its collection is different than the view's index in the collectionview.
        //  In this scenario the index has already been corrected before _addChild is called so the index isn't a valid indicator of whether the view should be added.
        _addChild: function (child, ChildView, index, bypass) {
            var shouldAdd = bypass || this._indexWithinRenderRange(index);

            if (shouldAdd) {
                Backbone.Marionette.CompositeView.prototype.addChild.apply(this.view, arguments);
            }
        },

        _getMinRenderIndex: function (scrollTop) {
            var minRenderIndex = Math.floor(scrollTop / this.childViewHeight) - this.threshold;

            if (minRenderIndex < 0) {
                minRenderIndex = 0;
            }

            return minRenderIndex;
        },

        _getMaxRenderIndex: function (scrollTop) {
            //  Subtract 1 to make math 'inclusive' instead of 'exclusive'
            var maxRenderIndex = Math.ceil((scrollTop / this.childViewHeight) + (this.viewportHeight / this.childViewHeight)) - 1 + this.threshold;

            return maxRenderIndex;
        },

        //  Returns true if an childView at the given index would not be fully visible -- part of it rendering out of the top of the viewport.
        _indexOverflowsTop: function (index) {
            var position = index * this.childViewHeight;
            var scrollPosition = this.ui.list.scrollTop();

            var overflowsTop = position < scrollPosition;

            return overflowsTop;
        },

        _indexOverflowsBottom: function (index) {
            //  Add one to index because want to get the bottom of the element and not the top.
            var position = (index + 1) * this.childViewHeight;
            var scrollPosition = this.ui.list.scrollTop() + this.viewportHeight;

            var overflowsBottom = position > scrollPosition;

            return overflowsBottom;
        },

        _indexWithinRenderRange: function (index) {
            return index >= this.minRenderIndex && index <= this.maxRenderIndex;
        },

        //  Ensure that the active item is visible by setting the container's scrollTop to a position which allows it to be seen.
        _scrollToItem: function (item) {
            var itemIndex = this.view.collection.indexOf(item);

            var overflowsTop = this._indexOverflowsTop(itemIndex);
            var overflowsBottom = this._indexOverflowsBottom(itemIndex);

            //  Only scroll to the item if it isn't in the viewport.
            if (overflowsTop || overflowsBottom) {
                var scrollTop = 0;

                //  If the item needs to be made visible from the bottom, offset the viewport's height:
                if (overflowsBottom) {
                    //  Add 1 to index because want the bottom of the element and not the top.
                    scrollTop = (itemIndex + 1) * this.childViewHeight - this.viewportHeight;
                }
                else if (overflowsTop) {
                    scrollTop = itemIndex * this.childViewHeight;
                }

                this.ui.list.scrollTop(scrollTop);
            }
        },

        //  Reset min/max, scrollTop, paddingTop and height to their default values.
        _onCollectionReset: function () {
            this.ui.list.scrollTop(0);
            this.lastScrollTop = 0;

            this.minRenderIndex = this._getMinRenderIndex(0);
            this.maxRenderIndex = this._getMaxRenderIndex(0);

            this._setHeightPaddingTop();
        },

        _onCollectionRemove: function (item, collection, options) {
            //  When a rendered view is lost - render the next one since there's a spot in the viewport
            if (this._indexWithinRenderRange(options.index)) {
                var rendered = this._renderElementAtIndex(this.maxRenderIndex);

                //  If failed to render next item and there are previous items waiting to be rendered, slide view back 1 item
                if (!rendered && this.minRenderIndex > 0) {
                    this.ui.list.scrollTop(this.lastScrollTop - this.childViewHeight);
                }
            }

            this._setHeightPaddingTop();
        },

        _onCollectionAdd: function (item, collection) {
            var index = collection.indexOf(item);

            var indexWithinRenderRange = this._indexWithinRenderRange(index);

            //  Subtract 1 from collection.length because, for instance, if our collection has 8 items in it
            //  and min-max is 0-7, the 8th item in the collection has an index of 7.
            //  Use a > comparator not >= because we only want to run this logic when the viewport is overfilled and not just enough to be filled.
            var viewportOverfull = collection.length - 1 > this.maxRenderIndex;

            //  If a view has been rendered and it pushes another view outside of maxRenderIndex, remove that view.
            if (indexWithinRenderRange && viewportOverfull) {
                //  Adding one because I want to grab the item which is outside maxRenderIndex. maxRenderIndex is inclusive.
                this._removeItemsByIndex(this.maxRenderIndex + 1, 1);
            }

            this._setHeightPaddingTop();
        },

        _onCollectionChangeActive: function (item, active) {
            if (active) {
                this._scrollToItem(item);
            }
        }
    });

    return SlidingRender;
});<|endoftext|>


<commit_before>import React from "react";
import injectSheet from "react-jss";
import { Hamburger } from '../icons'

const styles = {
  SectionsButton: {
    display: "flex",
    alignItems: "center",
    width: "8vw",
    height: "6vh",
    minWidth: "103px",
    minHeight: "39px",
    paddingRight: "10px",
    borderRadius: "3px",
    border: "solid 1.5px #dddddd",
    backgroundColor: "white",
    float: "left",
    "& span": {
      position: "relative",
      top: "2px",
      transitionDuration: ".3s"
    },
    "&:hover span": {
      color: "#888"
    }
  },
  hamburger: {
    display: "inline",
    width: "3vw",
    height: "3vh",
    minWidth: "24px",
    minHeight: "23px",
    opacity: "0.48",
    marginRight: "2px",
    float: "left"
  },
  text: {
    fontFamily: "Circular Std",
    fontSize: "1.8rem",
    fontWeight: "bold",
    textAlign: "center",
    color: "#000"
  },
};

const SectionsButton = ({ onClick, classes }) => {
  return (
    <button onClick={onClick} className={classes.SectionsButton}>
      <Hamburger className={classes.hamburger} />
      Sections
    </button>
  );
};

export default injectSheet(styles)(SectionsButton);
<commit_msg>tweaked margin on SectionsButton<commit_after>import React from "react";
import injectSheet from "react-jss";
import { Hamburger } from '../icons'

const styles = {
  SectionsButton: {
    display: "flex",
    alignItems: "center",
    width: "8vw",
    height: "6vh",
    minWidth: "103px",
    minHeight: "39px",
    paddingRight: "10px",
    borderRadius: "3px",
    border: "solid 1.5px #dddddd",
    backgroundColor: "white",
    float: "left",
    "& span": {
      position: "relative",
      top: "2px",
      transitionDuration: ".3s"
    },
    "&:hover span": {
      color: "#888"
    }
  },
  hamburger: {
    display: "inline",
    width: "3vw",
    height: "3vh",
    minWidth: "24px",
    minHeight: "23px",
    opacity: "0.48",
    marginRight: "0.4vw",
    float: "left"
  },
  text: {
    fontFamily: "Circular Std",
    fontSize: "1.8rem",
    fontWeight: "bold",
    textAlign: "center",
    color: "#000"
  },
};

const SectionsButton = ({ onClick, classes }) => {
  return (
    <button onClick={onClick} className={classes.SectionsButton}>
      <Hamburger className={classes.hamburger} />
      Sections
    </button>
  );
};

export default injectSheet(styles)(SectionsButton);
<|endoftext|>


<commit_before>$(function() {
	vo.tokenizer("abc");

	var submitButtonClicked = function(){
		$("#workflow_diagram").html("");
		$("#xml_editor_pre").html("");
		$("#errorMessage").html("");

		var formData = new FormData($("#fileform")[0]);
		$.ajax({
//			url: '<s:url value="/api/upload_xml"/>'
			url: vo.contextRoot + "api/upload_xml"
			, type: 'POST'
			, data: formData
			, complete: function(jqXHR, textStatus){
				var res = $.parseJSON(jqXHR.responseText);
//				$("#xml_editor_pre").html(res.escapedXml);
				drawXmlEditor(res.escapedXml, res.lineNumber, res.columnNumber);
//				SyntaxHighlighter.autoloader( 'xml js/shBrushXml.js'); // This needs to be loaded at this point.
//				SyntaxHighlighter.all();

				if(!res.succeeded){
					$("#errorMessage").html(res.errorMessage);
					return;
				}
				drawDiagram(res);

			}
			, cache: false
			, contentType: false
			, processData: false
		});
	};

	var weightedNodes = {};

	var PAPER_WITH = 500;

	function drawDiagram(res){
		Joint.paper("workflow_diagram", PAPER_WITH, 500);
		var fsa = Joint.dia.fsa;
		var unsortedNodes = res.nodes;

		// Set weightedNodes wihtout sortOrder
		for(var nodeIndex = 0; nodeIndex < unsortedNodes.length; nodeIndex++){
			var node = unsortedNodes[nodeIndex];
			weightedNodes[node.name] = { to : node.to , type: node.type };
		}

		addSortOrderToWeightedNodes("start", 0);

		// Invert the index TODO pretty bad logic.
		var sortedNodeNames = [];
		for(var key in weightedNodes){

			var notAdded = true;
			for(var i = 0; i < sortedNodeNames.length; i++){
				if(weightedNodes[key].sortOrder < sortedNodeNames[i].sortOrder){
					sortedNodeNames.splice(i, 0, {name: key, sortOrder: weightedNodes[key].sortOrder});
					notAdded = false;
					break;
				}
			}

			if(notAdded){
				sortedNodeNames.push({name: key, sortOrder: weightedNodes[key].sortOrder});
			}
		}

		var allCircles = [];
		var yPos = 40;
		// Generates svg circles
		for(var i = 0; i < sortedNodeNames.length; i++){
			// how many same sort order there are?
			var sameSortCount = 1
			for(; i + sameSortCount < sortedNodeNames.length ;sameSortCount++){
				if(sortedNodeNames[i].sortOrder != sortedNodeNames[i + sameSortCount].sortOrder){
					break;
				}
			}

			for(var j = 0; j < sameSortCount; j++){
				var attrs;
				if(weightedNodes[sortedNodeNames[i + j].name].type == "KILL"){
					attrs= { fill : "red" };
				}else if(weightedNodes[sortedNodeNames[i + j].name].type == "START" || weightedNodes[sortedNodeNames[i + j].name].type == "END"){
					attrs= { fill : "grey" };
				}else{
					attrs= {};
				}
				var circle = fsa.State.create({
							position: {x: (PAPER_WITH/(sameSortCount + 1) * (j+1)), y: yPos}
							, label: sortedNodeNames[i + j].name
							, attrs : attrs
						});
				allCircles.push(circle);
				weightedNodes[sortedNodeNames[i + j].name].circle = circle;

				if(j > 0){
					i++;
				}
			}

			yPos += 80;

		}

		// Add svg lines
		for(var key in weightedNodes){
			var node = weightedNodes[key];
			for(var i = 0; i < node.to.length; i++){
				node.circle.joint(weightedNodes[node.to[i]].circle, fsa.arrow).registerForever(allCircles);
			}
		}

	}

	function addSortOrderToWeightedNodes(currentKey, parentSortOrder){
		if(weightedNodes[currentKey].sortOrder === undefined){
			weightedNodes[currentKey].sortOrder = (parentSortOrder + 1);
		}
		for(var i = 0; i < weightedNodes[currentKey].to.length; i++){
			addSortOrderToWeightedNodes(weightedNodes[currentKey].to[i], parentSortOrder + 1);
		}
	}

	function drawXmlEditor(xmlArray, lineNumber, columnNumber){
		var editor = "
";
		editor += "
";
		editor += "#	";
		editor += "Content	";
		editor += "
";
		for(var i = 0; i <  xmlArray.length; i++){
			var line = xmlArray[i].replace(/ /g, " ");
			editor += "
";
			if(i == lineNumber - 1){
				editor += "" + (i + 1) + "	";
				editor += "" + line + "	";
			}else{
				editor += "" + (i + 1) + "	";
				editor += "" + line + "	";
			}
			editor += "
";
		}
		editor += "
";
		$("#xml_editor_div code").html(editor);
	}

	$("#submitButton").click(submitButtonClicked);

}); // End of JQuery Initialization

<commit_msg>added tab replace<commit_after>$(function() {
	vo.tokenizer("abc");

	var submitButtonClicked = function(){
		$("#workflow_diagram").html("");
		$("#xml_editor_pre").html("");
		$("#errorMessage").html("");

		var formData = new FormData($("#fileform")[0]);
		$.ajax({
//			url: '<s:url value="/api/upload_xml"/>'
			url: vo.contextRoot + "api/upload_xml"
			, type: 'POST'
			, data: formData
			, complete: function(jqXHR, textStatus){
				var res = $.parseJSON(jqXHR.responseText);
//				$("#xml_editor_pre").html(res.escapedXml);
				drawXmlEditor(res.escapedXml, res.lineNumber, res.columnNumber);
//				SyntaxHighlighter.autoloader( 'xml js/shBrushXml.js'); // This needs to be loaded at this point.
//				SyntaxHighlighter.all();

				if(!res.succeeded){
					$("#errorMessage").html(res.errorMessage);
					return;
				}
				drawDiagram(res);

			}
			, cache: false
			, contentType: false
			, processData: false
		});
	};

	var weightedNodes = {};

	var PAPER_WITH = 500;

	function drawDiagram(res){
		Joint.paper("workflow_diagram", PAPER_WITH, 500);
		var fsa = Joint.dia.fsa;
		var unsortedNodes = res.nodes;

		// Set weightedNodes wihtout sortOrder
		for(var nodeIndex = 0; nodeIndex < unsortedNodes.length; nodeIndex++){
			var node = unsortedNodes[nodeIndex];
			weightedNodes[node.name] = { to : node.to , type: node.type };
		}

		addSortOrderToWeightedNodes("start", 0);

		// Invert the index TODO pretty bad logic.
		var sortedNodeNames = [];
		for(var key in weightedNodes){

			var notAdded = true;
			for(var i = 0; i < sortedNodeNames.length; i++){
				if(weightedNodes[key].sortOrder < sortedNodeNames[i].sortOrder){
					sortedNodeNames.splice(i, 0, {name: key, sortOrder: weightedNodes[key].sortOrder});
					notAdded = false;
					break;
				}
			}

			if(notAdded){
				sortedNodeNames.push({name: key, sortOrder: weightedNodes[key].sortOrder});
			}
		}

		var allCircles = [];
		var yPos = 40;
		// Generates svg circles
		for(var i = 0; i < sortedNodeNames.length; i++){
			// how many same sort order there are?
			var sameSortCount = 1
			for(; i + sameSortCount < sortedNodeNames.length ;sameSortCount++){
				if(sortedNodeNames[i].sortOrder != sortedNodeNames[i + sameSortCount].sortOrder){
					break;
				}
			}

			for(var j = 0; j < sameSortCount; j++){
				var attrs;
				if(weightedNodes[sortedNodeNames[i + j].name].type == "KILL"){
					attrs= { fill : "red" };
				}else if(weightedNodes[sortedNodeNames[i + j].name].type == "START" || weightedNodes[sortedNodeNames[i + j].name].type == "END"){
					attrs= { fill : "grey" };
				}else{
					attrs= {};
				}
				var circle = fsa.State.create({
							position: {x: (PAPER_WITH/(sameSortCount + 1) * (j+1)), y: yPos}
							, label: sortedNodeNames[i + j].name
							, attrs : attrs
						});
				allCircles.push(circle);
				weightedNodes[sortedNodeNames[i + j].name].circle = circle;

				if(j > 0){
					i++;
				}
			}

			yPos += 80;

		}

		// Add svg lines
		for(var key in weightedNodes){
			var node = weightedNodes[key];
			for(var i = 0; i < node.to.length; i++){
				node.circle.joint(weightedNodes[node.to[i]].circle, fsa.arrow).registerForever(allCircles);
			}
		}

	}

	function addSortOrderToWeightedNodes(currentKey, parentSortOrder){
		if(weightedNodes[currentKey].sortOrder === undefined){
			weightedNodes[currentKey].sortOrder = (parentSortOrder + 1);
		}
		for(var i = 0; i < weightedNodes[currentKey].to.length; i++){
			addSortOrderToWeightedNodes(weightedNodes[currentKey].to[i], parentSortOrder + 1);
		}
	}

	function drawXmlEditor(xmlArray, lineNumber, columnNumber){
		var editor = "
";
		editor += "
";
		editor += "#	";
		editor += "Content	";
		editor += "
";
		for(var i = 0; i <  xmlArray.length; i++){
			var line = xmlArray[i].replace(/ /g, " ").replace(/\t/g, "    ");
			editor += "
";
			if(i == lineNumber - 1){
				editor += "" + (i + 1) + "	";
				editor += "" + line + "	";
			}else{
				editor += "" + (i + 1) + "	";
				editor += "" + line + "	";
			}
			editor += "
";
		}
		editor += "
";
		$("#xml_editor_div code").html(editor);
	}

	$("#submitButton").click(submitButtonClicked);

}); // End of JQuery Initialization

<|endoftext|>


<commit_before>;(function(exports){

    var defaults = {
        monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
                     'August', 'September', 'October', 'November', 'December'],

        shortMonthNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
                          'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],

        repeatLevels: {
            'low': [0, 2],
            'medium-low': [2, 5],
            'medium': [5, 10],
            'medium-high': [10, 30],
            'high': [30, 50],
            'hot': [50, 70],
            'extra-hot': [70, 100]
        },

        labels: {
            time: 'Time',
            people: 'People',
            weekOf: 'Week of'
        },

        timeInterval: 'monthly',

        drawEmptyCells: true,

        rawNumberOnHover: true,

        classPrefix: 'cornelius-',

        formatHeaderLabel: function(i) {
            return i === 0 ? this.labels.people : i.toString();
        },

        formatDailyLabel: function(date, i) {
            date.setDate(date.getDate() + i);
            return this.monthNames[date.getMonth()] + ' ' + date.getDate() + ', ' + getYear(date);
        },

        formatWeeklyLabel: function(date, i) {
            date.setDate(date.getDate() + i * 7);
            return this.labels.weekOf + ' ' + this.shortMonthNames[date.getMonth()] + ' ' +
                    date.getDate() + ', ' + getYear(date);
        },

        formatMonthlyLabel: function(date, i) {
            date.setMonth(date.getMonth() + i - 1);
            return this.monthNames[date.getMonth()] + ' ' + getYear(date);
        },

        formatYearlyLabel: function(date, i) {
            return date.getYear() + 1900 + i;
        }
    };

    function extend() {
        var target = arguments[0];

        if (arguments.length === 1) return target;

        for (var i = 1; i < arguments.length; i++) {
            var source = arguments[i];
            for (var prop in source) target[prop] = source[prop];
        }

        return target;
    }

    function isNumber(val) {
        return Object.prototype.toString.call(val) === '[object Number]';
    }

    function isEmpty(val) {
        return val === null || val === undefined || val === "";
    }

    function getYear(date) {
        return date.getYear() + 1900;
    }

    var draw = function(cohort, container, config) {

        function create(el, options) {
            options = options || {};

            el = document.createElement(el);

            if ((className = options.className)) {
                delete options.className;
                el.className = prefixClass(className);
            }
            if (!isEmpty(textContent = options.text)) {
                delete options.text;
                el.textContent = textContent.toString();
            }

            for (var option in options) {
                if ((opt = options[option])) el[option] = opt;
            }

            return el;
        }

        function prefixClass(className) {
            var prefixedClass = [],
                classes = className.split(/\s+/);

            for (var i in classes) {
                prefixedClass.push(config.classPrefix + classes[i]);
            }
            return prefixedClass.join(" ");
        }

        function drawHeader(data) {
            var th = create('tr'),
                monthLength = data[0].length;

            th.appendChild(create('th', { text: config.labels.time, className: 'time' }));

            for (var i = 0; i < monthLength; i++) {
                if (i > config.maxColumns) break;
                th.appendChild(create('th', { text: config.formatHeaderLabel(i), className: 'people' }));
            }
            return th;
        }

        function formatTimeLabel(initial, timeInterval, i) {
            var date = new Date(initial.getTime()),
                formatFn = null;

            if (timeInterval === 'daily') {
                formatFn = 'formatDailyLabel';
            } else if (timeInterval === 'weekly') {
                formatFn = 'formatWeeklyLabel';
            } else if (timeInterval === 'monthly') {
                formatFn = 'formatMonthlyLabel';
            } else if (timeInterval === 'yearly') {
                formatFn = 'formatYearlyLabel';
            } else {
                throw new Error("Interval not supported");
            }

            return config[formatFn].call(config, date, i);
        }

        function drawCells(data) {
            var fragment = document.createDocumentFragment(),

                startMonth = config.maxRows ? data.length - config.maxRows : 0,

                formatPercentage = function(value, base) {
                    if (isNumber(value) && base > 0) {
                        return (value / base * 100).toFixed(2);
                    } else if (isNumber(value)) {
                        return 0;
                    }
                },

                classNameFor = function(value) {
                    var levels = config.repeatLevels,
                        floatValue = value && parseFloat(value),
                        highestLevel = null;

                    var classNames = ['percentage'];

                    for (var level in levels) {
                        if (floatValue >= levels[level][0] && floatValue < levels[level][1]) {
                            classNames.push(level);
                            return classNames.join(" ");
                        }
                        highestLevel = level;
                    }

                    // handle 100% case
                    classNames.push(highestLevel);
                    return classNames.join(" ");

                };

            for (var i = startMonth; i < data.length; i++) {
                var tr = create('tr'),
                    row = data[i],
                    baseValue = row[0];

                tr.appendChild(create('td', {
                    className: 'label',
                    textContent: formatTimeLabel(config.initialDate, config.timeInterval, i)
                }));

                for (var j = 0; j < data[0].length; j++) {
                    if (j > config.maxColumns) break;

                    var value = row[j],
                        cellValue = j === 0 ? value : formatPercentage(value, baseValue),
                        opts = {};

                        if (!isEmpty(cellValue)) {
                            opts = {
                                text: cellValue,
                                title: j > 0 && config.rawNumberOnHover ? value : null,
                                className: j === 0 ? 'people' : classNameFor(cellValue)
                            };
                        } else if (config.drawEmptyCells) {
                            opts = { text: '-', className: 'empty' };
                        }

                    tr.appendChild(create('td', opts));
                }
                fragment.appendChild(tr);
            }
            return fragment;
        }

        var mainContainer = create('div', { className: 'container' }),
            table = create('table', { className: 'table' });

        table.appendChild(drawHeader(cohort));
        table.appendChild(drawCells(cohort));

        if ((title = config.title)) {
            mainContainer.appendChild(create('div', { text: title, className: 'title' }));
        }
        mainContainer.appendChild(table);

        container.appendChild(mainContainer);
    };

    var Cornelius = function(opts) {
        this.config = extend({}, defaults, opts || {});
    };

    Cornelius.prototype.draw = function(cohort, container) {
        if (!cohort)    throw new Error ("Please provide the cohort data");
        if (!container) throw new Error ("Please provide the cohort container");

        draw(cohort, container, this.config);
    };

    if (typeof jQuery !== "undefined" && jQuery !== null) {
        jQuery.fn.cornelius = function(options) {
            return this.each(function() {
                return new Cornelius(options).draw(options.cohort, this);
            });
        };
    }

    exports.Cornelius = Cornelius;

})(window);
<commit_msg>ability to set defaults<commit_after>;(function(exports){

    var corneliusDefaults = {
        monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
                     'August', 'September', 'October', 'November', 'December'],

        shortMonthNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
                          'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],

        repeatLevels: {
            'low': [0, 2],
            'medium-low': [2, 5],
            'medium': [5, 10],
            'medium-high': [10, 30],
            'high': [30, 50],
            'hot': [50, 70],
            'extra-hot': [70, 100]
        },

        labels: {
            time: 'Time',
            people: 'People',
            weekOf: 'Week of'
        },

        timeInterval: 'monthly',

        drawEmptyCells: true,

        rawNumberOnHover: true,

        classPrefix: 'cornelius-',

        formatHeaderLabel: function(i) {
            return i === 0 ? this.labels.people : i.toString();
        },

        formatDailyLabel: function(date, i) {
            date.setDate(date.getDate() + i);
            return this.monthNames[date.getMonth()] + ' ' + date.getDate() + ', ' + getYear(date);
        },

        formatWeeklyLabel: function(date, i) {
            date.setDate(date.getDate() + i * 7);
            return this.labels.weekOf + ' ' + this.shortMonthNames[date.getMonth()] + ' ' +
                    date.getDate() + ', ' + getYear(date);
        },

        formatMonthlyLabel: function(date, i) {
            date.setMonth(date.getMonth() + i - 1);
            return this.monthNames[date.getMonth()] + ' ' + getYear(date);
        },

        formatYearlyLabel: function(date, i) {
            return date.getYear() + 1900 + i;
        }
    },

    defaults = corneliusDefaults;

    function extend() {
        var target = arguments[0];

        if (arguments.length === 1) return target;

        for (var i = 1; i < arguments.length; i++) {
            var source = arguments[i];
            for (var prop in source) target[prop] = source[prop];
        }

        return target;
    }

    function isNumber(val) {
        return Object.prototype.toString.call(val) === '[object Number]';
    }

    function isEmpty(val) {
        return val === null || val === undefined || val === "";
    }

    function getYear(date) {
        return date.getYear() + 1900;
    }

    var draw = function(cohort, container, config) {

        function create(el, options) {
            options = options || {};

            el = document.createElement(el);

            if ((className = options.className)) {
                delete options.className;
                el.className = prefixClass(className);
            }
            if (!isEmpty(textContent = options.text)) {
                delete options.text;
                el.textContent = textContent.toString();
            }

            for (var option in options) {
                if ((opt = options[option])) el[option] = opt;
            }

            return el;
        }

        function prefixClass(className) {
            var prefixedClass = [],
                classes = className.split(/\s+/);

            for (var i in classes) {
                prefixedClass.push(config.classPrefix + classes[i]);
            }
            return prefixedClass.join(" ");
        }

        function drawHeader(data) {
            var th = create('tr'),
                monthLength = data[0].length;

            th.appendChild(create('th', { text: config.labels.time, className: 'time' }));

            for (var i = 0; i < monthLength; i++) {
                if (i > config.maxColumns) break;
                th.appendChild(create('th', { text: config.formatHeaderLabel(i), className: 'people' }));
            }
            return th;
        }

        function formatTimeLabel(initial, timeInterval, i) {
            var date = new Date(initial.getTime()),
                formatFn = null;

            if (timeInterval === 'daily') {
                formatFn = 'formatDailyLabel';
            } else if (timeInterval === 'weekly') {
                formatFn = 'formatWeeklyLabel';
            } else if (timeInterval === 'monthly') {
                formatFn = 'formatMonthlyLabel';
            } else if (timeInterval === 'yearly') {
                formatFn = 'formatYearlyLabel';
            } else {
                throw new Error("Interval not supported");
            }

            return config[formatFn].call(config, date, i);
        }

        function drawCells(data) {
            var fragment = document.createDocumentFragment(),

                startMonth = config.maxRows ? data.length - config.maxRows : 0,

                formatPercentage = function(value, base) {
                    if (isNumber(value) && base > 0) {
                        return (value / base * 100).toFixed(2);
                    } else if (isNumber(value)) {
                        return 0;
                    }
                },

                classNameFor = function(value) {
                    var levels = config.repeatLevels,
                        floatValue = value && parseFloat(value),
                        highestLevel = null;

                    var classNames = ['percentage'];

                    for (var level in levels) {
                        if (floatValue >= levels[level][0] && floatValue < levels[level][1]) {
                            classNames.push(level);
                            return classNames.join(" ");
                        }
                        highestLevel = level;
                    }

                    // handle 100% case
                    classNames.push(highestLevel);
                    return classNames.join(" ");

                };

            for (var i = startMonth; i < data.length; i++) {
                var tr = create('tr'),
                    row = data[i],
                    baseValue = row[0];

                tr.appendChild(create('td', {
                    className: 'label',
                    textContent: formatTimeLabel(config.initialDate, config.timeInterval, i)
                }));

                for (var j = 0; j < data[0].length; j++) {
                    if (j > config.maxColumns) break;

                    var value = row[j],
                        cellValue = j === 0 ? value : formatPercentage(value, baseValue),
                        opts = {};

                        if (!isEmpty(cellValue)) {
                            opts = {
                                text: cellValue,
                                title: j > 0 && config.rawNumberOnHover ? value : null,
                                className: j === 0 ? 'people' : classNameFor(cellValue)
                            };
                        } else if (config.drawEmptyCells) {
                            opts = { text: '-', className: 'empty' };
                        }

                    tr.appendChild(create('td', opts));
                }
                fragment.appendChild(tr);
            }
            return fragment;
        }

        var mainContainer = create('div', { className: 'container' }),
            table = create('table', { className: 'table' });

        table.appendChild(drawHeader(cohort));
        table.appendChild(drawCells(cohort));

        if ((title = config.title)) {
            mainContainer.appendChild(create('div', { text: title, className: 'title' }));
        }
        mainContainer.appendChild(table);

        container.appendChild(mainContainer);
    };

    var Cornelius = function(opts) {
        this.config = extend({}, Cornelius.getDefaults(), opts || {});
    };

    Cornelius.prototype.draw = function(cohort, container) {
        if (!cohort)    throw new Error ("Please provide the cohort data");
        if (!container) throw new Error ("Please provide the cohort container");

        draw(cohort, container, this.config);
    };

    extend(Cornelius, {
        getDefaults: function() {
            return defaults;
        },

        setDefaults: function(def) {
            defaults = extend({}, corneliusDefaults, def);
        },

        resetDefaults: function() {
            defaults = corneliusDefaults;
        }
    });

    if (typeof jQuery !== "undefined" && jQuery !== null) {
        jQuery.fn.cornelius = function(options) {
            return this.each(function() {
                return new Cornelius(options).draw(options.cohort, this);
            });
        };
    }

    exports.Cornelius = Cornelius;

})(window);
<|endoftext|>


<commit_before>/* foundation modal tweaks
 */

// add a class on the body, when the modal is opened
// so we can set position: fixed on the background.
$(document).on('opened', '[data-reveal]', function () {
  $(document.body).addClass('modal-opened');
});

$(document).on('closed', '[data-reveal]', function () {
  $(document.body).removeClass('modal-opened');
});
<commit_msg>Fix jshint errors.<commit_after>/* foundation modal tweaks
 */

(function() {
  'use strict';

  // add a class on the body, when the modal is opened
  // so we can set position: fixed on the background.
  $(document).on('opened', '[data-reveal]', function () {
    $(document.body).addClass('modal-opened');
  });

  $(document).on('closed', '[data-reveal]', function () {
    $(document.body).removeClass('modal-opened');
  });
})();
<|endoftext|>


<commit_before>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import { ipcRenderer } from 'electron';
import { walkDirectory, enhanceEntry } from './services/utils-io';
import AppConfig from './config';
import { getThumbnailURLPromise } from './services/thumbsgenerator';

function init() {
  console.log('Init worker');
  ipcRenderer.on('worker', (event, arg) => {
    // console.log('worker recieved: ' + arg);
    switch (arg.action) {
    case 'createDirectoryIndex': {
      console.log('createDirectoryIndex started in worker window');
      console.time('createDirectoryIndex');
      const directoryIndex = [];
      let counter = 0;
      walkDirectory(arg.path, { recursive: true, skipMetaFolder: true }, (fileEntry) => {
        counter += 1;
        if (counter > AppConfig.indexerLimit) {
          console.warn('Walk canceled by ' + AppConfig.indexerLimit);
          window.walkCanceled = true;
        }
        directoryIndex.push(enhanceEntry(fileEntry));
      }, (directoryEntry) => {
        if (directoryEntry.name !== AppConfig.metaFolder) {
          counter += 1;
          directoryIndex.push(enhanceEntry(directoryEntry));
        }
      }).then(() => { // entries - can be used for further processing
        window.walkCanceled = false;
        console.log('Directory index created ' + arg.path + ' containing ' + directoryIndex.length);
        console.timeEnd('createDirectoryIndex');
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: directoryIndex,
          error: ''
        });
        return true;
      }).catch((err) => {
        window.walkCanceled = false;
        console.timeEnd('createDirectoryIndex');
        console.warn('Error creating index: ' + err);
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: [], // directoryIndex,
          error: err
        });
      });
      break;
    }
    case 'createThumbnails': {
      console.log('createThumbnails started in worker window');
      if (!arg.tmbGenerationList || arg.tmbGenerationList.length < 1) {
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: [],
          error: 'Empty or not available tmb list'
        });
        break;
      }
      const tmbGenerationPromises = [];
      arg.tmbGenerationList.map(entry => tmbGenerationPromises.push(getThumbnailURLPromise(entry)));
      Promise.all(tmbGenerationPromises).then(tmbResult => {
        console.log('tmb results' + JSON.stringify(tmbResult));
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: tmbResult,
          error: ''
        });
        return true;
      }).catch(error => {
        console.warn('Tmb gener: ' + error);
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: [],
          error: 'Error generating tmbs: ' + error
        });
      });
      break;
    }
    default:
      return false;
    }
  });
}

init();
<commit_msg>let the worker generate only one batch of thumbs at once<commit_after>/**
 * TagSpaces - universal file and folder organizer
 * Copyright (C) 2017-present TagSpaces UG (haftungsbeschraenkt)
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 * @flow
 */

import { ipcRenderer } from 'electron';
import { walkDirectory, enhanceEntry } from './services/utils-io';
import AppConfig from './config';
import { getThumbnailURLPromise } from './services/thumbsgenerator';

let isGeneratingThumbs = false;

function init() {
  console.log('Init worker');
  ipcRenderer.on('worker', (event, arg) => {
    // console.log('worker recieved: ' + arg);
    switch (arg.action) {
    case 'createDirectoryIndex': {
      console.log('createDirectoryIndex started in worker window');
      console.time('createDirectoryIndex');
      const directoryIndex = [];
      let counter = 0;
      walkDirectory(arg.path, { recursive: true, skipMetaFolder: true }, (fileEntry) => {
        counter += 1;
        if (counter > AppConfig.indexerLimit) {
          console.warn('Walk canceled by ' + AppConfig.indexerLimit);
          window.walkCanceled = true;
        }
        directoryIndex.push(enhanceEntry(fileEntry));
      }, (directoryEntry) => {
        if (directoryEntry.name !== AppConfig.metaFolder) {
          counter += 1;
          directoryIndex.push(enhanceEntry(directoryEntry));
        }
      }).then(() => { // entries - can be used for further processing
        window.walkCanceled = false;
        console.log('Directory index created ' + arg.path + ' containing ' + directoryIndex.length);
        console.timeEnd('createDirectoryIndex');
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: directoryIndex,
          error: ''
        });
        return true;
      }).catch((err) => {
        window.walkCanceled = false;
        console.timeEnd('createDirectoryIndex');
        console.warn('Error creating index: ' + err);
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: [], // directoryIndex,
          error: err
        });
      });
      break;
    }
    case 'createThumbnails': {
      console.log('createThumbnails started in worker window');
      if (!arg.tmbGenerationList || arg.tmbGenerationList.length < 1 || isGeneratingThumbs) {
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: [],
          error: 'Empty or not available tmb list or busy worker'
        });
        break;
      }
      isGeneratingThumbs = true;
      const tmbGenerationPromises = [];
      arg.tmbGenerationList.map(entry => tmbGenerationPromises.push(getThumbnailURLPromise(entry)));
      Promise.all(tmbGenerationPromises).then(tmbResult => {
        console.log('tmb results' + JSON.stringify(tmbResult));
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: tmbResult,
          error: ''
        });
        isGeneratingThumbs = false;
        return true;
      }).catch(error => {
        console.warn('Tmb gener: ' + error);
        ipcRenderer.send('worker', {
          id: arg.id,
          action: arg.action,
          result: [],
          error: 'Error generating tmbs: ' + error
        });
        isGeneratingThumbs = false;
      });
      break;
    }
    default:
      return false;
    }
  });
}

init();
<|endoftext|>


<commit_before>'use strict'
let component = require('omniscient')
let immutable = require('immutable')
let S = require('underscore.string.fp')
let logger = require('js-logger-aknudsen').get('explore')
let R = require('ramda')
let h = require('react-hyperscript')
let React = require('react')
let ReactDOM = require('react-dom')
let Packery = React.createFactory(require('react-packery-component')(React))

let FocusingInput = require('./focusingInput')
let ajax = require('../ajax')
let router = require('../router')

if (__IS_BROWSER__) {
  require('./explore.styl')
}

let getQualifiedId = (project) => {
  return `${project.owner}/${project.projectId}`
}

let setSearch = (cursor, text) => {
  logger.debug(`Setting search to '${text}'`)
  cursor.cursor('explore').set('search', text)
}

let createProjectElement = (cursor, i) => {
  let project = cursor.toJS()
  let thumbnail = !R.isEmpty(project.pictures || []) ? project.pictures[0].exploreUrl :
    '/images/revox-reel-to-reel-resized.jpg'
  return h('.project-item', {
    key: i,
    'data-id': getQualifiedId(project),
  }, [
    h('a', {href: `/u/${project.owner}/${project.projectId}`,}, [
      h('.project-item-header', [
        h('.project-item-title', project.title),
        h('.project-item-author', project.owner),
      ]),
      h('img.project-item-image', {src: thumbnail,}),
    ]),
  ])
}

let Results = component('Results', (cursor) => {
  let exploreCursor = cursor.cursor('explore')
  if (exploreCursor.get('isSearching')) {
    return h('p', 'Searching...')
  } else {
    let projectsCursor = exploreCursor.cursor('projects')
    logger.debug(`Got ${projectsCursor.toJS().length} search results`)
    let projectElems = projectsCursor.map(createProjectElement).toJS()
    return projectsCursor.isEmpty() ? h('p', 'No projects were found, please try again.') :
      Packery({
        className: 'projects-container',
        options: {
          itemSelector: '.project-item',
        },
      }, projectElems)
  }
})

let searchAsync = (cursor, query) => {
  return ajax.getJson('/api/search', {query: query || '',})
    .then((projects) => {
      logger.debug(`Searching succeeded`)
      return projects
    }, (reason) => {
      logger.warn('Searching failed:', reason)
      return []
    })
}

let performSearch = (cursor) => {
  let exploreCursor = cursor.cursor('explore')
  let query = cursor.getIn([`explore`, `search`,])
  let searchString = encodeURIComponent(query.replace(' ', '+'))
  if (S.isBlank(searchString)) {
    router.goTo(`/`)
  } else {
    router.goTo(`/?q=${searchString}`)
  }
}

let SearchBox = component('SearchBox', function (cursor) {
  let searchQuery = cursor.cursor('explore').get('search')
  logger.debug(`SearchBox rendering, query: '${searchQuery}'`)
  let hasSearchQuery = !S.isBlank(searchQuery)
  return h('.search-box', [
    h('span#explore-do-search.search-icon.icon-search.muted', {
      onClick: R.partial(performSearch, [cursor,]),
    }),
    FocusingInput({
      id: 'explore-search-input',
      value: searchQuery,
      placeholder: 'Search MuzHack',
      ref: 'search',
      refName: 'search',
      onChange: (event) => {
        let text = event.currentTarget.value
        logger.debug(`Search input detected`)
        setSearch(cursor, text)
      },
      onEnter: R.partial(performSearch, [cursor,]),
    }),
    hasSearchQuery ? h('span#explore-clear-search.clear-icon.icon-cross.muted', {
      onClick: () => {
        logger.debug('Clear search clicked')
        setSearch(cursor, '')
        let node = ReactDOM.findDOMNode(this.refs.search)
        logger.debug('Giving focus to search input:', node)
        node.select()
      },
    }) : null,
  ])
})

module.exports = {
  createState: () => {
    return immutable.fromJS({
      search: '',
      projects: [
      ],
    })
  },
  loadData: (cursor, params, queryParams) => {
    logger.debug(`Loading projects`)
    let searchString = queryParams.q || ''
    return searchAsync(cursor, searchString)
      .then((projects) => {
        return {
          explore: {
            isSearching: false,
            search: searchString,
            projects,
          },
        }
      })
  },
  render: (cursor) => {
    let exploreCursor = cursor.cursor('explore')
    // logger.debug(`Explore state:`, exploreCursor.toJS())
    return h('.pure-g', [
      h('.pure-u-1', [
        h('#explore-pad', [
          SearchBox(cursor),
          Results(cursor),
        ]),
      ]),
    ])
  },
}
<commit_msg>Remove unused vars<commit_after>'use strict'
let component = require('omniscient')
let immutable = require('immutable')
let S = require('underscore.string.fp')
let logger = require('js-logger-aknudsen').get('explore')
let R = require('ramda')
let h = require('react-hyperscript')
let React = require('react')
let ReactDOM = require('react-dom')
let Packery = React.createFactory(require('react-packery-component')(React))

let FocusingInput = require('./focusingInput')
let ajax = require('../ajax')
let router = require('../router')

if (__IS_BROWSER__) {
  require('./explore.styl')
}

let getQualifiedId = (project) => {
  return `${project.owner}/${project.projectId}`
}

let setSearch = (cursor, text) => {
  logger.debug(`Setting search to '${text}'`)
  cursor.cursor('explore').set('search', text)
}

let createProjectElement = (cursor, i) => {
  let project = cursor.toJS()
  let thumbnail = !R.isEmpty(project.pictures || []) ? project.pictures[0].exploreUrl :
    '/images/revox-reel-to-reel-resized.jpg'
  return h('.project-item', {
    key: i,
    'data-id': getQualifiedId(project),
  }, [
    h('a', {href: `/u/${project.owner}/${project.projectId}`,}, [
      h('.project-item-header', [
        h('.project-item-title', project.title),
        h('.project-item-author', project.owner),
      ]),
      h('img.project-item-image', {src: thumbnail,}),
    ]),
  ])
}

let Results = component('Results', (cursor) => {
  let exploreCursor = cursor.cursor('explore')
  if (exploreCursor.get('isSearching')) {
    return h('p', 'Searching...')
  } else {
    let projectsCursor = exploreCursor.cursor('projects')
    logger.debug(`Got ${projectsCursor.toJS().length} search results`)
    let projectElems = projectsCursor.map(createProjectElement).toJS()
    return projectsCursor.isEmpty() ? h('p', 'No projects were found, please try again.') :
      Packery({
        className: 'projects-container',
        options: {
          itemSelector: '.project-item',
        },
      }, projectElems)
  }
})

let searchAsync = (cursor, query) => {
  return ajax.getJson('/api/search', {query: query || '',})
    .then((projects) => {
      logger.debug(`Searching succeeded`)
      return projects
    }, (reason) => {
      logger.warn('Searching failed:', reason)
      return []
    })
}

let performSearch = (cursor) => {
  let query = cursor.getIn([`explore`, `search`,])
  let searchString = encodeURIComponent(query.replace(' ', '+'))
  if (S.isBlank(searchString)) {
    router.goTo(`/`)
  } else {
    router.goTo(`/?q=${searchString}`)
  }
}

let SearchBox = component('SearchBox', function (cursor) {
  let searchQuery = cursor.cursor('explore').get('search')
  logger.debug(`SearchBox rendering, query: '${searchQuery}'`)
  let hasSearchQuery = !S.isBlank(searchQuery)
  return h('.search-box', [
    h('span#explore-do-search.search-icon.icon-search.muted', {
      onClick: R.partial(performSearch, [cursor,]),
    }),
    FocusingInput({
      id: 'explore-search-input',
      value: searchQuery,
      placeholder: 'Search MuzHack',
      ref: 'search',
      refName: 'search',
      onChange: (event) => {
        let text = event.currentTarget.value
        logger.debug(`Search input detected`)
        setSearch(cursor, text)
      },
      onEnter: R.partial(performSearch, [cursor,]),
    }),
    hasSearchQuery ? h('span#explore-clear-search.clear-icon.icon-cross.muted', {
      onClick: () => {
        logger.debug('Clear search clicked')
        setSearch(cursor, '')
        let node = ReactDOM.findDOMNode(this.refs.search)
        logger.debug('Giving focus to search input:', node)
        node.select()
      },
    }) : null,
  ])
})

module.exports = {
  createState: () => {
    return immutable.fromJS({
      search: '',
      projects: [
      ],
    })
  },
  loadData: (cursor, params, queryParams) => {
    logger.debug(`Loading projects`)
    let searchString = queryParams.q || ''
    return searchAsync(cursor, searchString)
      .then((projects) => {
        return {
          explore: {
            isSearching: false,
            search: searchString,
            projects,
          },
        }
      })
  },
  render: (cursor) => {
    // logger.debug(`Explore state:`, exploreCursor.toJS())
    return h('.pure-g', [
      h('.pure-u-1', [
        h('#explore-pad', [
          SearchBox(cursor),
          Results(cursor),
        ]),
      ]),
    ])
  },
}
<|endoftext|>


<commit_before>
Seed({
		name : "Mold.Lib.Template",
		dna : "class",
		version : "0.0.3",
		include : [
			"Mold.Lib.Tree",
			"Mold.Lib.TreeFactory",
			"Mold.Lib.Event",
			"Mold.Lib.TemplateFilter"
		],
		browserInclude : [
			"Mold.Lib.Directive",
			"Mold.Defaults.TemplateDirectives"
		],
		nodeInclude : [
			"Mold.Lib.Document"
		]
	},
	function(content){

		var that = this,
			_templateContent = "",
			_shadowTemplate = false,
			_compiledTemplate = false,
			_container = false,
			_viewModel = {},
			_snatched = {},
			_contentType = false,
			_doc = false,
			_testMode = false,
			_target = false,
			_tid = Mold.getId(),
			undefined;

		Mold.mixin(this, new Mold.Lib.Event(this));

		if(Mold.isNodeJS || _testMode){
			_doc = new Mold.Lib.Document();
		}else{
			_doc = document;
		}

		var _hideTemplate = function(element){

			Mold.each(element, function(element){
				if(element.childs && element.childs[0]){
					_hideTemplate(element.childs[0]);
				}
				element.hide();
			});

		}

		var _parseTemplate = function(templateContent){
			_applyToDom(templateContent);
			Mold.Lib.TreeFactory.preParseTemplate(_shadowTemplate);
			var tree = _buildTree();
			if(!Mold.isNodeJS){
				that.on("after.init", function(){
					var addDirectives = function(scope){
						Mold.each(Mold.Defaults.TemplateDirectives, function(directive){
							directive._id = directive.name +"_"+that._eid;
							Mold.Lib.Directive.add(
								directive,
								scope,
								that
							);
						});

					}

					addDirectives(_shadowTemplate);
				});
			}
			return tree;
		}

		var _parseElement = function(templateElement){
			_shadowTemplate = templateElement;
			Mold.Lib.TreeFactory.preParseTemplate(_shadowTemplate);
			var tree = _buildTree();
			return tree;
		}

		var _copyNodesToElements = function(target, node){
			while (node.hasChildNodes()) {
  				target.appendChild(node.removeChild(node.firstChild))
			}
		}

		var _applyToDom = function(template){
			_shadowTemplate =  _doc.createElement("div"); //_doc.createDocumentFragment();
			_container = _doc.createElement("div");
			_container.innerHTML = template;

			while (_container.hasChildNodes()) {
				var templateElement = _container.firstChild;

  				_shadowTemplate.appendChild(_container.removeChild(_container.firstChild))
			}
			_shadowTemplate.moldTemplate = that;
			_target = _shadowTemplate;
		}

		var _firstElement = function(){
			var next = _target.firstChild;
			var selected = false;
			while (next) {
				if(next.nodeType === 1){
					selected = next;
				}
  				next = next.nextSibling;
			}
			return selected;
		}

		var _appendTo = function(target){

			var addDirectives = function(){
				Mold.each(Mold.Defaults.TemplateDirectives, function(directive){
					Mold.Lib.Directive.add(
						directive,
						target,
						that
					);
				});
			}

			while(_shadowTemplate.hasChildNodes()){
				var targetObserver = new Mold.Lib.Event(_shadowTemplate.firstChild);
				targetObserver.on("DOMNodeInserted", addDirectives);
				target.appendChild(_shadowTemplate.removeChild(_shadowTemplate.firstChild));

			}
			_target = target;
		}

		var _buildTree = function(){
			var tree = Mold.Lib.TreeFactory.parseDomTree(_shadowTemplate, new Mold.Lib.Tree("root", false, false, false, that), that);
			return tree;
		}

		var _triggerEvent = function(name, e){
			var data = _viewModel;
			if(_snatched[name]){
				var result = _snatched[name].call(this, data);
				if(result === false){
					return false;
				}
			}
			Mold.mixin(data, e);
			this.trigger(name.split("@")[1], data)
		}

		var _hasVar = function(name, tree){
			name = name.replace("{{", "").replace("}}", "");
			var output = false;
			Mold.each(tree, function(treeValue, treeName){
				if(name === treeName){
					 output = true;
					 return "break";
				}else{
					if(treeValue.childs.length){
						Mold.each(treeValue.childs, function(child){
							output = (_hasVar(name, child)) ? true : output
						})

					}
				}

			});
			return output;
		}

		var _viewModelObject = {
			set : function(model, name, value){
				_viewModel[model] = _viewModel[model] || {};
				_viewModel[model][name] = value;
				that.trigger("viewmodel.change", { model : _viewModel, modelname : model, name : name, value : value })
				return _viewModel[model][name];
			},
			get : function(model, name){
				if(!model && !name){
					return _viewModel;
				}
				if(_viewModel[model] && _viewModel[model][name]){
					return _viewModel[model][name];
				}
				return false;
			},
			remove : function(model, name){
				if(_viewModel[model] && _viewModel[model][name]){
					delete _viewModel[model][name];
					return true;
				}
				return false;
			}
		}

		var _addData = function(template, data, bind){

				Mold.each(template, function(element, name){

					if(data[name] != undefined){

						if(Mold.isArray(data[name])){
							if(bind){
								data[name].on("list.item.add", function(e){
									var filterResult = true;

									if(element.filter && element.filter.length){
										//_applyFilter(element);
									}
									//if(filterResult){
										if(!element.childs[e.data.index]){
											element.add();
										}else{
											element.show();

										}
										_addData(element.childs[e.data.index], e.data.value, bind);
									//}

								}).on("list.item.remove", function(e){
									element.remove(e.data.index);
								}).on("list.item.change", function(e){
									_addData(element.childs[e.data.index], e.data.value, bind)
								});
							}
							Mold.each(data[name], function(selectedData, index){
								if(!element.childs[index]){
									element.add();
								}else{
									element.show();
								}
								_addData(element.childs[index], selectedData, bind);
							})
						}else{
							element.setValue(data[name])
							if(bind){
								data.on("property.change."+name, function(e){
									element.setValue(e.data.value)
								});
							}
						}

					}

				});
		}

		var _each = function(element, callback){

			if(element.childs && element.childs.length){
				Mold.each(element.childs, function(childElement){

					Mold.each(childElement, function(selected, name){
						callback.call(this, selected, name);
						_each(selected, callback);
					});
				});
			}
		}

		var _visibility = function(element, callback){
			var rowcount = -1;
			if(element.childs && element.childs.length){
				var i = 0,
					len = element.childs.length;

				for(; i < len; i++){
					var selected = element.childs[i];
					rowcount++;
					if(
						Mold.some(selected, function(selectedChild, name){
							var output = callback.call(this, selectedChild, rowcount);
							if(output === "exit"){
								i = len;
								return false;
							}
							return output;
						})
					){
						element.show(false, i);
					}else{
						element.hide(i);
					}
				}
			}
		}

		var _applyFilter = function(element){

			if(element.filter && element.filter.length){
				Mold.each(element.filter, function(filterName){
					var filterParts = filterName.split(":");
					filterName =  filterParts[0];
					var filterParameter = (filterParts[1]) ? filterParts[1] : false;

					var filter = Mold.Lib.TemplateFilter.get(filterName);
					if(filter){
						switch (filter.select){
							case "child":
								switch (filter.type){
									case "visibility" :
										_visibility(element, function(child, rowcount){
											return filter.action.call(this, that, {
												child : child,
												viewModel : _viewModel,
												element : element,
												rowcount : rowcount,
												parameter : filterParameter
											});
										});
										break;
									default:
										break;
								}
								break;
							default:
								break;
						}
					}
				});
			}
		}

		var _refresh = function(){

			switch(_contentType){
				case "string":
					_compiledTemplate = _parseTemplate(_templateContent);
					break;
				case "node":
					_compiledTemplate = _parseElement(_templateContent);
					break;
				default:
					break;
			}
		}

		if(typeof content === "function"){
			_templateContent = content.toString().replace(/(^function\s*\(\)\s*\{\s*\/\*\|)([\s\S]*)(\|\*\/\s*\})/g, function(){
				return arguments[2];
			});
			_contentType = "string";
			_compiledTemplate = _parseTemplate(_templateContent);
			this.trigger("ready");
		}else if(Mold.isNode(content)){
			_templateContent = content;
			_contentType = "node"
			_compiledTemplate = _parseElement(_templateContent);
		}else{
			console.log("is nothing")
		}

		this.publics = {
			viewModel : _viewModelObject,
			triggerEvent : _triggerEvent,
			applyFilter : _applyFilter,
			visibility : _visibility,
			each : function(callback){
				_each(_compiledTemplate, callback);
			},
			refresh : function(){
				_refresh();
			},
			snatch : function(data){
				Mold.each(data, function(callback, name){
					_snatched[name] = callback;
				});
				return this;
			},
/**
* @namespace Mold.Lib.Template
* @methode bind
* @desc  bind a Mold model to the Template
* @public
* @return (Object)  returns this;
* @param (model) object - the modelt
**/
			bind : function(model){
				if(!_compiledTemplate){
					throw "Template not compiled!";
				}
				_addData(_compiledTemplate.childs[0], model.data, true);
				return that;
			},
/**
* @namespace Mold.Lib.Template
* @methode unbind
* @desc  unbind a Mold model from the Template
* @public
* @return (Object)  returns this;
* @param (model) object - the modelt
**/
			unbind : function(model){

				return this;
			},
/**
* @namespace Mold.Lib.Template
* @methode append
* @desc  append a Mold model to the Template, without bindings
* @public
* @return (Object)  returns this;
* @param (model) object - the model
**/
			append : function(model){
				if(!_compiledTemplate){
					throw "Template not compiled!";
				}
				if(model.className && model.className === "Mold.Lib.Model"){
					_addData(_compiledTemplate.childs[0], model.data, false);
				}else{
					_addData(_compiledTemplate.childs[0], model, false);
				}
				return this;
			},
/**
* @namespace Mold.Lib.Template
* @methode tree
* @desc  returns the template-tree
* @public
* @return (Object)  the template-tree;
**/
			tree : function(){
				return _compiledTemplate;
			},

			hasVar : function(name){
				return _hasVar(name, _compiledTemplate.childs[0]);
			},

/**
* @namespace Mold.Lib.Template
* @methode get
* @desc  returns the parsed templates main dom node
* @public
* @return (node)  returns the parsed Template
**/
			get : function(){
				return _shadowTemplate;
			},
			getFirst : function(){
				return _firstElement();
			},
			appendTo : function(target){
				return _appendTo(target);
			},
/**
* @namespace Mold.Lib.Template
* @methode getInner
* @desc  returns the parsed template dom nodes without main node
* @public
* @return (node)  returns the parsed Tempalte inner nodes
**/
			getInner : function(){
				return _shadowTemplate.childNodes;
			}

		}

	}
);<commit_msg>Safari Bugfix added<commit_after>
Seed({
		name : "Mold.Lib.Template",
		dna : "class",
		version : "0.0.3",
		include : [
			"Mold.Lib.Tree",
			"Mold.Lib.TreeFactory",
			"Mold.Lib.Event",
			"Mold.Lib.TemplateFilter"
		],
		browserInclude : [
			"Mold.Lib.Directive",
			"Mold.Defaults.TemplateDirectives"
		],
		nodeInclude : [
			"Mold.Lib.Document"
		]
	},
	function(content){

		var that = this,
			_templateContent = "",
			_shadowTemplate = false,
			_compiledTemplate = false,
			_container = false,
			_viewModel = {},
			_snatched = {},
			_contentType = false,
			_doc = false,
			_testMode = false,
			_target = false,
			_tid = Mold.getId(),
			undefined;

		Mold.mixin(this, new Mold.Lib.Event(this));

		if(Mold.isNodeJS || _testMode){
			_doc = new Mold.Lib.Document();
		}else{
			_doc = document;
		}

		var _hideTemplate = function(element){

			Mold.each(element, function(element){
				if(element.childs && element.childs[0]){
					_hideTemplate(element.childs[0]);
				}
				element.hide();
			});

		}

		var _parseTemplate = function(templateContent){
			_applyToDom(templateContent);
			Mold.Lib.TreeFactory.preParseTemplate(_shadowTemplate);
			var tree = _buildTree();
			if(!Mold.isNodeJS){
				that.on("after.init", function(){
					var addDirectives = function(scope){
						Mold.each(Mold.Defaults.TemplateDirectives, function(directive){
							directive._id = directive.name +"_"+that._eid;
							Mold.Lib.Directive.add(
								directive,
								scope,
								that
							);
						});

					}

					addDirectives(_shadowTemplate);
				});
			}
			return tree;
		}

		var _parseElement = function(templateElement){
			_shadowTemplate = templateElement;
			Mold.Lib.TreeFactory.preParseTemplate(_shadowTemplate);
			var tree = _buildTree();
			return tree;
		}

		var _copyNodesToElements = function(target, node){
			while (node.hasChildNodes()) {
  				target.appendChild(node.removeChild(node.firstChild))
			}
		}

		var _applyToDom = function(template){
			_shadowTemplate =  _doc.createElement("div"); //_doc.createDocumentFragment();
			_container = _doc.createElement("div");
			_container.innerHTML = template;

			while (_container.hasChildNodes()) {
				var templateElement = _container.firstChild;

  				_shadowTemplate.appendChild(_container.removeChild(_container.firstChild))
			}
			_shadowTemplate.moldTemplate = that;
			_target = _shadowTemplate;
		}

		var _firstElement = function(){
			var next = _target.firstChild;
			var selected = false;
			while (next) {
				if(next.nodeType === 1){
					selected = next;
				}
  				next = next.nextSibling;
			}
			return selected;
		}

		var _appendTo = function(target){

			var addDirectives = function(){
				Mold.each(Mold.Defaults.TemplateDirectives, function(directive){
					Mold.Lib.Directive.add(
						directive,
						target,
						that
					);
				});
			}
			target.moldTemplate = _shadowTemplate.moldTemplate;
			while(_shadowTemplate.hasChildNodes()){
				var targetObserver = new Mold.Lib.Event(_shadowTemplate.firstChild);
				targetObserver.on("DOMNodeInserted", addDirectives);
				target.appendChild(_shadowTemplate.removeChild(_shadowTemplate.firstChild));

			}
			_target = target;
		}

		var _buildTree = function(){
			var tree = Mold.Lib.TreeFactory.parseDomTree(_shadowTemplate, new Mold.Lib.Tree("root", false, false, false, that), that);
			return tree;
		}

		var _triggerEvent = function(name, e){
			var data = _viewModel;
			if(_snatched[name]){
				var result = _snatched[name].call(this, data);
				if(result === false){
					return false;
				}
			}
			Mold.mixin(data, e);
			this.trigger(name.split("@")[1], data)
		}

		var _hasVar = function(name, tree){
			name = name.replace("{{", "").replace("}}", "");
			var output = false;
			Mold.each(tree, function(treeValue, treeName){
				if(name === treeName){
					 output = true;
					 return "break";
				}else{
					if(treeValue.childs.length){
						Mold.each(treeValue.childs, function(child){
							output = (_hasVar(name, child)) ? true : output
						})

					}
				}

			});
			return output;
		}

		var _viewModelObject = {
			set : function(model, name, value){
				_viewModel[model] = _viewModel[model] || {};
				_viewModel[model][name] = value;
				that.trigger("viewmodel.change", { model : _viewModel, modelname : model, name : name, value : value })
				return _viewModel[model][name];
			},
			get : function(model, name){
				if(!model && !name){
					return _viewModel;
				}
				if(_viewModel[model] && _viewModel[model][name]){
					return _viewModel[model][name];
				}
				return false;
			},
			remove : function(model, name){
				if(_viewModel[model] && _viewModel[model][name]){
					delete _viewModel[model][name];
					return true;
				}
				return false;
			}
		}

		var _addData = function(template, data, bind){

				Mold.each(template, function(element, name){

					if(data[name] != undefined){

						if(Mold.isArray(data[name])){
							if(bind){
								data[name].on("list.item.add", function(e){
									var filterResult = true;

									if(element.filter && element.filter.length){
										//_applyFilter(element);
									}
									//if(filterResult){
										if(!element.childs[e.data.index]){
											element.add();
										}else{
											element.show();

										}
										_addData(element.childs[e.data.index], e.data.value, bind);
									//}

								}).on("list.item.remove", function(e){
									element.remove(e.data.index);
								}).on("list.item.change", function(e){
									_addData(element.childs[e.data.index], e.data.value, bind)
								});
							}
							Mold.each(data[name], function(selectedData, index){
								if(!element.childs[index]){
									element.add();
								}else{
									element.show();
								}
								_addData(element.childs[index], selectedData, bind);
							})
						}else{
							element.setValue(data[name])
							if(bind){
								data.on("property.change."+name, function(e){
									element.setValue(e.data.value)
								});
							}
						}

					}

				});
		}

		var _each = function(element, callback){

			if(element.childs && element.childs.length){
				Mold.each(element.childs, function(childElement){

					Mold.each(childElement, function(selected, name){
						callback.call(this, selected, name);
						_each(selected, callback);
					});
				});
			}
		}

		var _visibility = function(element, callback){
			var rowcount = -1;
			if(element.childs && element.childs.length){
				var i = 0,
					len = element.childs.length;

				for(; i < len; i++){
					var selected = element.childs[i];
					rowcount++;
					if(
						Mold.some(selected, function(selectedChild, name){
							var output = callback.call(this, selectedChild, rowcount);
							if(output === "exit"){
								i = len;
								return false;
							}
							return output;
						})
					){
						element.show(false, i);
					}else{
						element.hide(i);
					}
				}
			}
		}

		var _applyFilter = function(element){

			if(element.filter && element.filter.length){
				Mold.each(element.filter, function(filterName){
					var filterParts = filterName.split(":");
					filterName =  filterParts[0];
					var filterParameter = (filterParts[1]) ? filterParts[1] : false;

					var filter = Mold.Lib.TemplateFilter.get(filterName);
					if(filter){
						switch (filter.select){
							case "child":
								switch (filter.type){
									case "visibility" :
										_visibility(element, function(child, rowcount){
											return filter.action.call(this, that, {
												child : child,
												viewModel : _viewModel,
												element : element,
												rowcount : rowcount,
												parameter : filterParameter
											});
										});
										break;
									default:
										break;
								}
								break;
							default:
								break;
						}
					}
				});
			}
		}

		var _refresh = function(){

			switch(_contentType){
				case "string":
					_compiledTemplate = _parseTemplate(_templateContent);
					break;
				case "node":
					_compiledTemplate = _parseElement(_templateContent);
					break;
				default:
					break;
			}
		}

		if(typeof content === "function"){

			_templateContent = content.toString().replace(/(^function\s*\(\)\s*\{\s*\/\*\|)([\s\S]*)(\|\*\/\s*\})$/g, function(){
				return arguments[2];
			});

			//Safari Bugfix
			_templateContent = _templateContent.replace(/^function\s*\(\)\s*\{\s*\/\*\|/g, '');
			_templateContent = _templateContent.replace(/\|\*\/\s*\}$/g, '');

			_contentType = "string";
			_compiledTemplate = _parseTemplate(_templateContent);
			this.trigger("ready");
		}else if(Mold.isNode(content)){
			_templateContent = content;
			_contentType = "node"
			_compiledTemplate = _parseElement(_templateContent);
		}else{
			console.log("is nothing")
		}

		this.publics = {
			viewModel : _viewModelObject,
			triggerEvent : _triggerEvent,
			applyFilter : _applyFilter,
			visibility : _visibility,
			each : function(callback){
				_each(_compiledTemplate, callback);
			},
			refresh : function(){
				_refresh();
			},
			snatch : function(data){
				Mold.each(data, function(callback, name){
					_snatched[name] = callback;
				});
				return this;
			},
/**
* @namespace Mold.Lib.Template
* @methode bind
* @desc  bind a Mold model to the Template
* @public
* @return (Object)  returns this;
* @param (model) object - the modelt
**/
			bind : function(model){
				if(!_compiledTemplate){
					throw "Template not compiled!";
				}
				_addData(_compiledTemplate.childs[0], model.data, true);
				return that;
			},
/**
* @namespace Mold.Lib.Template
* @methode unbind
* @desc  unbind a Mold model from the Template
* @public
* @return (Object)  returns this;
* @param (model) object - the modelt
**/
			unbind : function(model){

				return this;
			},
/**
* @namespace Mold.Lib.Template
* @methode append
* @desc  append a Mold model to the Template, without bindings
* @public
* @return (Object)  returns this;
* @param (model) object - the model
**/
			append : function(model){
				if(!_compiledTemplate){
					throw "Template not compiled!";
				}
				if(model.className && model.className === "Mold.Lib.Model"){
					_addData(_compiledTemplate.childs[0], model.data, false);
				}else{
					_addData(_compiledTemplate.childs[0], model, false);
				}
				return this;
			},
/**
* @namespace Mold.Lib.Template
* @methode tree
* @desc  returns the template-tree
* @public
* @return (Object)  the template-tree;
**/
			tree : function(){
				return _compiledTemplate;
			},

			hasVar : function(name){
				return _hasVar(name, _compiledTemplate.childs[0]);
			},

/**
* @namespace Mold.Lib.Template
* @methode get
* @desc  returns the parsed templates main dom node
* @public
* @return (node)  returns the parsed Template
**/
			get : function(){
				return _shadowTemplate;
			},
			getFirst : function(){
				return _firstElement();
			},
			appendTo : function(target){
				return _appendTo(target);
			},
/**
* @namespace Mold.Lib.Template
* @methode getInner
* @desc  returns the parsed template dom nodes without main node
* @public
* @return (node)  returns the parsed Tempalte inner nodes
**/
			getInner : function(){
				return _shadowTemplate.childNodes;
			}

		}

	}
);<|endoftext|>