<commit_before>/*
 * gui-builder - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

function validValue(value, type) {
    var ret = null;
    switch (type) {
        case 'boolean':
            ret = Boolean(value);
            break;
        case 'float':
            ret = parseFloat(value);
            break;
        case 'integer':
            ret = parseInt(value, 10);
            break;
        case 'number':
            ret = Number(value);
            break;
        case 'object':
            ret = Object(value);
            break;
        case 'string':
            ret = String(value);
            break;
        default:
            ret = value;
            break;
    }
    return ret;
};

function copyProperties(dest, src) {
    var text, value;

    text = src.getProperty("text");
    dest.setProperty("text", text);

    value = src.getProperty("value");
    dest.setProperty("value", value);
};

// Property view widget

(function($, undefined) {

    $.widget('gb.propertyView', {

        options: {
            model: null,
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            o.designReset = this._designResetHandler;
            o.selectionChanged = this._selectionChangedHandler;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            $('
').addClass(this.widgetName)
                .appendTo(this.element);

            $('

').appendTo(this.element);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this.refresh(null, this);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.primaryTools.remove();
            this.options.secondaryTools.remove();
        },

        refresh: function(event, widget) {
            widget = widget || this;
            if (event && event.node) {
                if (BWidget.isSelectable(event.node.getType())) {
                    widget._showProperties(event);
                }
                else if (event.name === "modelUpdated" &&
                         (event.type === "nodeRemoved" ||
                          event.type === "nodeAdded") &&
                         event.node.getType() === "Option") {
                    event.node = event.parent;
                    widget._showProperties(event);
                }
            }
        },

        // Private functions
        _createPrimaryTools: function() {
            return $(null);
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _selectionChangedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh(event,widget);
        },

        _activePageChangedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh(event,widget);
        },

        _modelUpdatedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh(event,widget);
        },

        _showProperties: function(event) {
            var node = event.node,
                showUid = false,
                labelId, labelVal, valueId, valueVal, count,
                widget = this;

            // Clear the properties pane when nothing is selected
            if (node === null || node === undefined) {
                node = ADM.getActivePage();
                if (node === null || node === undefined) {
                    $('#property_content').empty()
                        .append('Nothing Selected');
                    return;
                }
            }

            $('#property_content').empty()
                .append('

' +
                        '' +
                        BWidget.getDisplayLabel(node.getType()) +
                        '' +
                        (showUid ? ' (uid=' + node.getUid() + ')' : '') +
                        '

');

            var props = node.getProperties();
            var options = node.getPropertyOptions();
            for (var p in props) {
                if (node.getType() === "SelectMenu" && p === "options") {
                    continue;
                }
                labelVal = p.replace(/_/g,'-');
                labelId = p+'-label';
                valueId = p+'-value';
                valueVal = props[p];
                if (options[p]) {
                    var code = '
' + labelVal +
                               '	' + '' +
                               '<select id="' + valueId + '">';
                    for (var o in options[p]) {
                        if (options[p][o] == props[p]) {
                            code += '<option value="' + options[p][o] +
                                    '" selected=true>' + options[p][o] +
                                    '</option>';
                        } else {
                            code += '<option value="' + options[p][o] + '">' +
                                    options[p][o] + '</option>';
                        }
                    }
                    code += '</select>' + '
';
                    $('#property_content').children().last().append(code);
                } else {
                    $('#property_content').children().last()
                        .append('
' + labelVal +
                                '	' +
                                '' +
                                '<input class="full" id="' + valueId + '" />' +
                                '
');
                    $('#'+valueId).val(valueVal);
                }
                $('#' + valueId).change(node, function (event) {
                    var updated, node, element, type, value;
                    updated = event.srcElement.id.replace(/-value/,''),
                    node = event.data;

                    if (node === null || node === undefined) {
                        throw new Error("Missing node, prop change failed!");
                        return;
                    }

                    value = validValue(element.val(),
                        BWidget.getPropertyType(node.getType(), updated));
                    ADM.setProperty(node, updated, value);
                });
            }

            if (node.getType() === "SelectMenu") {
                widget.currentNode = node;
                var children = node.getChildren();
                $('#property_content')
                    .append('
' +
                            '
');
                $('#sortable').sortable({
                    items: 'tr:not(.sortable-title)',
                    start: function(event, ui){
                        widget.originalRowIndex = ui.item[0].rowIndex - 1;
                    },
                    stop: function(event, ui){
                        var original = widget.originalRowIndex;
                        widget.originalRowIndex = ui.item[0].rowIndex - 1;
                        var current = widget.originalRowIndex;
                        var node = widget.currentNode;
                        var children = node.getChildren();
                        var tmp = new ADMNode('Option');
                        var i;
                        if (current !== original) {
                            if (current < original) {
                                copyProperties(tmp, children[original]);
                                for (i = original; i > current; i--) {
                                    copyProperties(children[i], children[i-1]);
                                }
                                copyProperties(children[current], tmp);
                            }
                            else {
                                copyProperties(tmp, children[original]);
                                for (i = original + 1; i <= current; i++) {
                                    copyProperties(children[i-1], children[i]);
                                }
                                copyProperties(children[current], tmp);
                            }
                        }
                    },
                });
                $('#property_content').children().last()
                    .append('
Options: 	' +
                        'Text	Value
');
                for (var i=0; i<children.length; i++) {
                    var subnode = children[i];
                    var subprops = subnode.getProperties();
                    $('#property_content').children().last()
                        .append('
' +
                                '' +
                                '' +
                                '	' +
                                '' +
                                '<input id="option-text-' + i +
                                '" size="5" />'+
                                '	' +
                                '' +
                                '<input id="option-value-' +i+ '" size="5" />' +
                                '	<button id="option-delete-' +
                                i + '">-</button>
');
                    $('#option-text-'+i).val(subprops["text"]);
                    $('#option-value-'+i).val(subprops["value"]);
                    $('#option-text-'+i).change(subnode, function(event) {
                        var updated = event.srcElement.id,
                            node = event.data,
                            value,
                            element = $('#' + updated);
                        value = validValue(element.val(),
                            BWidget.getPropertyType(node.getType(), "text"));
                        ADM.setProperty(node, "text", value);
                    });
                    $('#option-value-'+i).change(subnode, function(event) {
                        var updated = event.srcElement.id,
                            node = event.data,
                            value,
                            element = $('#' + updated);
                        value = validValue(element.val(),
                            BWidget.getPropertyType(node.getType(), "value"));
                        ADM.setProperty(node, "value", value);
                    });
                    $('#option-delete-'+i).click(subnode, function(event){
                        var node = event.data;
                        ADM.removeChild(node.getUid(), false);
                    });
                }
                $('#property_content').children().last()
                    .append('<button id="option-add">Add</button>

');
                $('#option-add').click(node, function(event){
                    var node = event.data;
                    var child = new ADMNode('Option');
                    ADM.addChild(node, child);
                });
            }

            $('#property_content').children().last()
                .append('<button id="delete">Delete</button>');
            $('#delete').click(function() {
                window.parent.ADM.removeChild(node.getUid());
            });

            $('table','#property_content').attr({ 'cellspacing': '0' });
            $('th:odd, td:odd','#property_content')
                .css({ 'border-top': '1px solid black',
                       'border-bottom': '1px solid black' });
            $('td','#property_content').attr('align','left');
            $('th','#property_content').attr('align','left')
                .addClass('ui-helper-reset ui-state-default');
        },
    });
})(jQuery);
<commit_msg>[Properties] Fix regressions introduced in b8715ef7<commit_after>/*
 * gui-builder - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

function validValue(value, type) {
    var ret = null;
    switch (type) {
        case 'boolean':
            ret = Boolean(value);
            break;
        case 'float':
            ret = parseFloat(value);
            break;
        case 'integer':
            ret = parseInt(value, 10);
            break;
        case 'number':
            ret = Number(value);
            break;
        case 'object':
            ret = Object(value);
            break;
        case 'string':
            ret = String(value);
            break;
        default:
            ret = value;
            break;
    }
    return ret;
};

function copyProperties(dest, src) {
    var text, value;

    text = src.getProperty("text");
    dest.setProperty("text", text);

    value = src.getProperty("value");
    dest.setProperty("value", value);
};

// Property view widget

(function($, undefined) {

    $.widget('gb.propertyView', {

        options: {
            model: null,
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            o.designReset = this._designResetHandler;
            o.selectionChanged = this._selectionChangedHandler;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            $('
').addClass(this.widgetName)
                .appendTo(this.element);

            $('

').appendTo(this.element);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this.refresh(null, this);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.primaryTools.remove();
            this.options.secondaryTools.remove();
        },

        refresh: function(event, widget) {
            widget = widget || this;
            if (event) {
                if (!event.node || event.node.isSelectable() ||
                    event.node.instanceOf('Page')) {
                    widget._showProperties(event);
                }
                else if (event.name === "modelUpdated" &&
                         (event.type === "nodeRemoved" ||
                          event.type === "nodeAdded") &&
                         event.node.getType() === "Option") {
                    event.node = event.parent;
                    widget._showProperties(event);
                }
            }
        },

        // Private functions
        _createPrimaryTools: function() {
            return $(null);
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _selectionChangedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh(event,widget);
        },

        _activePageChangedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh(event,widget);
        },

        _modelUpdatedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh(event,widget);
        },

        _showProperties: function(event) {
            var node = event.node,
                showUid = false,
                labelId, labelVal, valueId, valueVal, count,
                widget = this;

            // Clear the properties pane when nothing is selected
            if (node === null || node === undefined) {
                node = ADM.getActivePage();
                if (node === null || node === undefined) {
                    $('#property_content').empty()
                        .append('Nothing Selected');
                    return;
                }
            }

            $('#property_content').empty()
                .append('

' +
                        '' +
                        BWidget.getDisplayLabel(node.getType()) +
                        '' +
                        (showUid ? ' (uid=' + node.getUid() + ')' : '') +
                        '

');

            var props = node.getProperties();
            var options = node.getPropertyOptions();
            for (var p in props) {
                if (node.getType() === "SelectMenu" && p === "options") {
                    continue;
                }
                labelVal = p.replace(/_/g,'-');
                labelId = p+'-label';
                valueId = p+'-value';
                valueVal = props[p];
                if (options[p]) {
                    var code = '
' + labelVal +
                               '	' + '' +
                               '<select id="' + valueId + '">';
                    for (var o in options[p]) {
                        if (options[p][o] == props[p]) {
                            code += '<option value="' + options[p][o] +
                                    '" selected=true>' + options[p][o] +
                                    '</option>';
                        } else {
                            code += '<option value="' + options[p][o] + '">' +
                                    options[p][o] + '</option>';
                        }
                    }
                    code += '</select>' + '
';
                    $('#property_content').children().last().append(code);
                } else {
                    $('#property_content').children().last()
                        .append('
' + labelVal +
                                '	' +
                                '' +
                                '<input class="full" id="' + valueId + '" />' +
                                '
');
                    $('#'+valueId).val(valueVal);
                }
                $('#' + valueId).change(node, function (event) {
                    var updated, node, element, type, value;
                    updated = event.srcElement.id.replace(/-value/,''),
                    node = event.data;

                    if (node === null || node === undefined) {
                        throw new Error("Missing node, prop change failed!");
                        return;
                    }

                    element = $('#' + event.srcElement.id);
                    type = BWidget.getPropertyType(node.getType(), updated);
                    value = validValue(element.val(), type);
                    ADM.setProperty(node, updated, value);
                });
            }

            if (node.getType() === "SelectMenu") {
                widget.currentNode = node;
                var children = node.getChildren();
                $('#property_content')
                    .append('
' +
                            '
');
                $('#sortable').sortable({
                    items: 'tr:not(.sortable-title)',
                    start: function(event, ui){
                        widget.originalRowIndex = ui.item[0].rowIndex - 1;
                    },
                    stop: function(event, ui){
                        var original = widget.originalRowIndex;
                        widget.originalRowIndex = ui.item[0].rowIndex - 1;
                        var current = widget.originalRowIndex;
                        var node = widget.currentNode;
                        var children = node.getChildren();
                        var tmp = new ADMNode('Option');
                        var i;
                        if (current !== original) {
                            if (current < original) {
                                copyProperties(tmp, children[original]);
                                for (i = original; i > current; i--) {
                                    copyProperties(children[i], children[i-1]);
                                }
                                copyProperties(children[current], tmp);
                            }
                            else {
                                copyProperties(tmp, children[original]);
                                for (i = original + 1; i <= current; i++) {
                                    copyProperties(children[i-1], children[i]);
                                }
                                copyProperties(children[current], tmp);
                            }
                        }
                    },
                });
                $('#property_content').children().last()
                    .append('
Options: 	' +
                        'Text	Value
');
                for (var i=0; i<children.length; i++) {
                    var subnode = children[i];
                    var subprops = subnode.getProperties();
                    $('#property_content').children().last()
                        .append('
' +
                                '' +
                                '' +
                                '	' +
                                '' +
                                '<input id="option-text-' + i +
                                '" size="5" />'+
                                '	' +
                                '' +
                                '<input id="option-value-' +i+ '" size="5" />' +
                                '	<button id="option-delete-' +
                                i + '">-</button>
');
                    $('#option-text-'+i).val(subprops["text"]);
                    $('#option-value-'+i).val(subprops["value"]);
                    $('#option-text-'+i).change(subnode, function(event) {
                        var updated = event.srcElement.id,
                            node = event.data,
                            value,
                            element = $('#' + updated);
                        value = validValue(element.val(),
                            BWidget.getPropertyType(node.getType(), "text"));
                        ADM.setProperty(node, "text", value);
                    });
                    $('#option-value-'+i).change(subnode, function(event) {
                        var updated = event.srcElement.id,
                            node = event.data,
                            value,
                            element = $('#' + updated);
                        value = validValue(element.val(),
                            BWidget.getPropertyType(node.getType(), "value"));
                        ADM.setProperty(node, "value", value);
                    });
                    $('#option-delete-'+i).click(subnode, function(event){
                        var node = event.data;
                        ADM.removeChild(node.getUid(), false);
                    });
                }
                $('#property_content').children().last()
                    .append('<button id="option-add">Add</button>

');
                $('#option-add').click(node, function(event){
                    var node = event.data;
                    var child = new ADMNode('Option');
                    ADM.addChild(node, child);
                });
            }

            $('#property_content').children().last()
                .append('<button id="delete">Delete</button>');
            $('#delete').click(function() {
                window.parent.ADM.removeChild(node.getUid());
            });

            $('table','#property_content').attr({ 'cellspacing': '0' });
            $('th:odd, td:odd','#property_content')
                .css({ 'border-top': '1px solid black',
                       'border-bottom': '1px solid black' });
            $('td','#property_content').attr('align','left');
            $('th','#property_content').attr('align','left')
                .addClass('ui-helper-reset ui-state-default');
        },
    });
})(jQuery);
<|endoftext|>


<commit_before>Kwf.onContentReady(function() {
    Ext.select('.kwcForm', true).each(function(form) {
        form.kwcForm = new Kwc.Form.Component(form);
    });
});
Ext.ns('Kwc.Form');
Kwc.Form.Component = function(form)
{
    this.addEvents('submitSuccess');
    this.el = form;
    var config = form.parent().down('.config', true);
    if (!config) return;
    config = Ext.decode(config.value);
    if (!config) return;
    this.config = config;

    this.fields = [];
    form.select('.kwfField', true).each(function(fieldEl) {
        var classes = fieldEl.dom.className.split(' ');
        var fieldConstructor = false;
        classes.each(function (c) {
            if (Kwf.FrontendForm.fields[c]) {
                fieldConstructor = Kwf.FrontendForm.fields[c];
            }
        }, this);
        if (fieldConstructor) {
            var field = new fieldConstructor(fieldEl);
            this.fields.push(field);
        }
    }, this);

    if (this.config.hideForValue) {
        this.config.hideForValue.each(function(hideForValue) {
            var field = this.findField(hideForValue.field);
            field.on('change', function(v, f) {
                if (v == hideForValue.value) {
                    this.findField(hideForValue.hide).hide();
                } else {
                    this.findField(hideForValue.hide).show();
                }
            }, this);

            //initialize on startup
            if (field.getValue() == hideForValue.value) {
                this.findField(hideForValue.hide).hide();
            }
        }, this);
    }

    var button = form.child('form button.submit');
    if (button) {
        button.on('click', this.onSubmit, this);
    }
};
Ext.extend(Kwc.Form.Component, Ext.util.Observable, {
    findField: function(fieldName) {
        var ret = null;
        this.fields.each(function(f) {
            if (f.getFieldName() == fieldName) {
                ret = f;
                return true;
            }
        }, this);
        return ret;
    },
    onSubmit: function(e) {
        if (this.dontUseAjaxRequest) return;

        var button = this.el.child('.button');
        button.down('.saving').show();
        button.down('.submit').hide();

        Ext.Ajax.request({
            url: this.config.controllerUrl + '/json-save',
            ignoreErrors: true,
            params: {
                componentId: this.config.componentId
            },
            form: this.el.down('form'),
            failure: function() {
                //on failure try a plain old post of the form
                this.dontUseAjaxRequest = true; //avoid endless recursion
                button.down('.submit').dom.click();
            },
            success: function(response, options, r) {

                var hasErrors = false;

                // remove and set error classes for fields
                this.fields.each(function(field) {
                    field.hideError();
                }, this);
                for(var fieldName in r.errorFields) {
                    var field = this.findField(fieldName);
                    field.showError(r.errorFields[fieldName]);
                    hasErrors= true;
                }

                // remove and add error messages
                var error = this.el.parent().down('.webFormError');
                if (error) error.remove();

                if (r.errorMessages && r.errorMessages.length) {
                    hasErrors= true;
                    var html = '
';
                    html += '
' + r.errorPlaceholder + ':
';
                    html += '
';
                    for (var i=0; i<r.errorMessages.length; i++) {
                        html += '
' + r.errorMessages[i] + '
';
                    }
                    html += '
';
                    html += '
';
                    this.el.parent().createChild(html, form);
                }

                // show success content
                if (r.successContent) {
                    this.el.parent().createChild(r.successContent);
                    this.el.remove();
                } else if (r.successUrl) {
                    document.location.href = r.successUrl;
                }

                if (!r.successUrl) {
                    button.down('.saving').hide();
                    button.down('.submit').show();
                }

                if (!hasErrors) {
                    this.fireEvent('submitSuccess', this, r);
                }
            },
            scope: this
        });

        e.stopEvent();
    }
});<commit_msg>initialize form only once<commit_after>Kwf.onContentReady(function(el, param) {
    if (!param.newRender) return false;
    Ext.select('.kwcForm', true, el).each(function(form) {
        if (!form.kwcForm) {
            form.kwcForm = new Kwc.Form.Component(form);
        }
    });
});
Ext.ns('Kwc.Form');
Kwc.Form.Component = function(form)
{
    this.addEvents('submitSuccess');
    this.el = form;
    var config = form.parent().down('.config', true);
    if (!config) return;
    config = Ext.decode(config.value);
    if (!config) return;
    this.config = config;

    this.fields = [];
    form.select('.kwfField', true).each(function(fieldEl) {
        var classes = fieldEl.dom.className.split(' ');
        var fieldConstructor = false;
        classes.each(function (c) {
            if (Kwf.FrontendForm.fields[c]) {
                fieldConstructor = Kwf.FrontendForm.fields[c];
            }
        }, this);
        if (fieldConstructor) {
            var field = new fieldConstructor(fieldEl);
            this.fields.push(field);
        }
    }, this);

    if (this.config.hideForValue) {
        this.config.hideForValue.each(function(hideForValue) {
            var field = this.findField(hideForValue.field);
            field.on('change', function(v, f) {
                if (v == hideForValue.value) {
                    this.findField(hideForValue.hide).hide();
                } else {
                    this.findField(hideForValue.hide).show();
                }
            }, this);

            //initialize on startup
            if (field.getValue() == hideForValue.value) {
                this.findField(hideForValue.hide).hide();
            }
        }, this);
    }

    var button = form.child('form button.submit');
    if (button) {
        button.on('click', this.onSubmit, this);
    }
};
Ext.extend(Kwc.Form.Component, Ext.util.Observable, {
    findField: function(fieldName) {
        var ret = null;
        this.fields.each(function(f) {
            if (f.getFieldName() == fieldName) {
                ret = f;
                return true;
            }
        }, this);
        return ret;
    },
    onSubmit: function(e) {
        if (this.dontUseAjaxRequest) return;

        var button = this.el.child('.button');
        button.down('.saving').show();
        button.down('.submit').hide();

        Ext.Ajax.request({
            url: this.config.controllerUrl + '/json-save',
            ignoreErrors: true,
            params: {
                componentId: this.config.componentId
            },
            form: this.el.down('form'),
            failure: function() {
                //on failure try a plain old post of the form
                this.dontUseAjaxRequest = true; //avoid endless recursion
                button.down('.submit').dom.click();
            },
            success: function(response, options, r) {

                var hasErrors = false;

                // remove and set error classes for fields
                this.fields.each(function(field) {
                    field.hideError();
                }, this);
                for(var fieldName in r.errorFields) {
                    var field = this.findField(fieldName);
                    field.showError(r.errorFields[fieldName]);
                    hasErrors= true;
                }

                // remove and add error messages
                var error = this.el.parent().down('.webFormError');
                if (error) error.remove();

                if (r.errorMessages && r.errorMessages.length) {
                    hasErrors= true;
                    var html = '
';
                    html += '
' + r.errorPlaceholder + ':
';
                    html += '
';
                    for (var i=0; i<r.errorMessages.length; i++) {
                        html += '
' + r.errorMessages[i] + '
';
                    }
                    html += '
';
                    html += '
';
                    this.el.parent().createChild(html, form);
                }

                // show success content
                if (r.successContent) {
                    this.el.parent().createChild(r.successContent);
                    this.el.remove();
                } else if (r.successUrl) {
                    document.location.href = r.successUrl;
                }

                if (!r.successUrl) {
                    button.down('.saving').hide();
                    button.down('.submit').show();
                }

                if (!hasErrors) {
                    this.fireEvent('submitSuccess', this, r);
                }
            },
            scope: this
        });

        e.stopEvent();
    }
});<|endoftext|>


<commit_before>import Vector from '../vector';
import Label from './label';
import OBB from '../utils/obb';
import Geo from '../geo';

const PLACEMENT = {
    MID_POINT: 0,
    CORNER: 1
};

export default class LabelLine extends Label {

    constructor (size, lines, options) {
        super(size, options);

        this.lines = lines;
        this.offset = this.options.offset;

        this.segment_size = options.segment_size;
        this.segment_texture_size = options.segment_texture_size;

        this.placement = (options.placement === undefined) ? PLACEMENT.MID_POINT : options.placement;

        this.position = null;
        this.pre_offset = [[0,0], [0,0]];
        this.kink_index = 0;

        // optionally limit the line segments that the label may be placed in, by specifying a segment index range
        // used as a coarse subdivide for placing multiple labels per line geometry
        this.segment_index = options.segment_index || 0;
        this.segment_max = options.segment_end || this.lines.length;

        this.throw_away = false;

        // get first good segment
        var segment = this.getNextFittingSegment(this.getCurrentSegment());

        if (!segment) this.throw_away = true;
    }

    nextLabelLine() {
        // increment segment
        var hasNext = this.getNextSegment();
        if (!hasNext) return false;

        // clone options
        var options = Object.create(this.options);
        options.segment_index = this.segment_index;
        options.placement = this.placement;

        // create new label
        var label = new LabelLine(this.size, this.lines, options);

        return (label.throw_away) ? false : label;
    }

    getNextSegment() {
        switch (this.placement) {
            case PLACEMENT.CORNER:
                this.placement = PLACEMENT.MID_POINT;
                break;
            case PLACEMENT.MID_POINT:
                if (this.segment_index >= this.lines.length - 2) return false;
                if (this.segment_size.length > 1) {
                    this.placement = PLACEMENT.CORNER;
                    this.kink_index = 0;
                }
                this.segment_index++;
                break;
        }

        return this.getCurrentSegment();
    }

    getCurrentSegment() {
        let p1, p2, segment;
        switch (this.placement) {
            case PLACEMENT.CORNER:
                p1 = this.lines[this.segment_index - 1];
                p2 = this.lines[this.segment_index];
                let p3 = this.lines[this.segment_index + 1];
                segment = [ p1, p2, p3 ];
                break;
            case PLACEMENT.MID_POINT:
                p1 = this.lines[this.segment_index];
                p2 = this.lines[this.segment_index + 1];
                segment = [ p1, p2 ];
                break;
        }

        return segment;
    }

    getNextFittingSegment(segment) {
        segment = segment || this.getNextSegment();
        if (!segment) return false;

        if (this.doesSegmentFit(segment)) {
            this.update();
            if (this.inTileBounds())
                return segment;
        }

        return this.getNextFittingSegment();
    }

    doesSegmentFit(segment) {
        let does_fit = false;

        switch (this.placement) {
            case PLACEMENT.CORNER:
                does_fit = this.fitKinkedSegment(segment);
                break;
            case PLACEMENT.MID_POINT:
                let excess = 1 + this.options.line_exceed / 100;
                let p0p1 = Vector.sub(segment[0], segment[1]);
                let line_length = Vector.length(p0p1);

                let label_length = this.size[0] * this.options.units_per_pixel;
                does_fit = (label_length < excess * line_length)
                break;
        }

        return does_fit;
    }

    fitKinkedSegment(segment) {
        let excess = 1 + this.options.line_exceed / 100;
        let opp = this.options.units_per_pixel;

        let does_fit = false;

        let p0p1 = Vector.sub(segment[0], segment[1]);
        let p1p2 = Vector.sub(segment[1], segment[2]);

        let line_length1 = Vector.length(p0p1);
        let line_length2 = Vector.length(p1p2);

        // break up multiple segments into two chunks (N-1 options)
        let label_length1 = this.size[0];
        let label_length2 = 0;
        let width;

        this.kink_index = this.segment_size.length - 1;

        while (!does_fit && this.kink_index > 0) {
            width = this.segment_size[this.kink_index];

            label_length1 -= width;
            label_length2 += width;

            does_fit = (opp * label_length1 < excess * line_length1 && opp * label_length2 < excess * line_length2);
            if (!does_fit) this.kink_index--;
        }

        if (does_fit && this.kink_index > 0) {
            var collapsed_size = [0, 0];
            for (var i = 0; i < this.kink_index; i++) {
                collapsed_size[0] += this.segment_size[i];
            }
            collapsed_size[1] = this.size[0] - collapsed_size[0] + 16;

            this.segment_size = collapsed_size;

            this.pre_offset[0][0] = -0.5 * collapsed_size[0];
            this.pre_offset[1][0] = 0.5 * collapsed_size[1];

            return true;
        }
        else return false;
    }

    update() {
        this.angle = this.getCurrentAngle();
        this.position = this.getCurrentPosition();
        this.updateBBoxes();
    }

    getCurrentAngle() {
        var segment = this.getCurrentSegment();
        var angle;

        switch (this.placement) {
            case PLACEMENT.CORNER:
                var theta1 = getAngleFromSegment(segment[0], segment[1]);
                var theta2 = getAngleFromSegment(segment[1], segment[2]);

                var orientation1 = getOrientationFromSegment(segment[0], segment[1]);
                var orientation2 = getOrientationFromSegment(segment[1], segment[2]);

                if (orientation1 !== orientation2) {
                    theta2 -= Math.PI;
                }

                angle = (orientation1) ? [theta2, theta1] : [theta1, theta2];

                break;
            case PLACEMENT.MID_POINT:
                var theta = getAngleFromSegment(segment[0], segment[1]);
                angle = [theta];
                break;
        }

        return angle;
    }

    getCurrentPosition() {
        let segment = this.getCurrentSegment();
        let position;

        switch (this.placement) {
            case PLACEMENT.CORNER:
                position = segment[1].slice();
                break;
            case PLACEMENT.MID_POINT:
                position = [
                    (segment[0][0] + segment[1][0]) / 2,
                    (segment[0][1] + segment[1][1]) / 2
                ];
                break;
        }

        return position;
    }

    updateBBoxes () {
        let upp = this.options.units_per_pixel;
        let width = (this.size[0] + this.options.buffer[0] * 2) * upp * Label.epsilon;
        let height = (this.size[1] + this.options.buffer[1] * 2) * upp * Label.epsilon;

        var angle = this.angle[1] ? this.angle[1] : this.angle[0];

        // apply offset, x positive, y pointing down
        let offset = Vector.rot(this.offset, angle);
        let p = [
            this.position[0] + (offset[0] * upp),
            this.position[1] - (offset[1] * upp)
        ];

        // the angle of the obb is negative since it's the tile system y axis is pointing down
        this.obb = new OBB(p[0], p[1], -angle, width, height);
        this.aabb = this.obb.getExtent();
    }
}

function getAngleFromSegment(pt1, pt2) {
    let PI = Math.PI;
    let PI_2 = PI / 2;
    let p1p2 = Vector.sub(pt1, pt2);
    let theta = Math.atan2(p1p2[0], p1p2[1]) + PI_2;

    if (theta > PI_2) {
        // If in 2nd quadrant, move to 4th quadrant
        theta += PI;
    }
    else if (theta < 0) {
        // If in 4th quadrant, make a positive angle
        theta += 2 * PI
    }

    return theta;
}

function getOrientationFromSegment(pt1, pt2) {
    return pt1[0] >= pt2[0];
}<commit_msg>Bounding boxes for segmented labels<commit_after>import Vector from '../vector';
import Label from './label';
import OBB from '../utils/obb';
import Geo from '../geo';

const PLACEMENT = {
    MID_POINT: 0,
    CORNER: 1
};

export default class LabelLine extends Label {

    constructor (size, lines, options) {
        super(size, options);

        this.lines = lines;
        this.offset = this.options.offset;

        this.segment_size = options.segment_size;
        this.segment_texture_size = options.segment_texture_size;

        this.placement = (options.placement === undefined) ? PLACEMENT.MID_POINT : options.placement;

        this.position = null;
        this.pre_offset = [[0,0], [0,0]];
        this.kink_index = 0;

        // optionally limit the line segments that the label may be placed in, by specifying a segment index range
        // used as a coarse subdivide for placing multiple labels per line geometry
        this.segment_index = options.segment_index || 0;
        this.segment_max = options.segment_end || this.lines.length;

        this.throw_away = false;

        // get first good segment
        var segment = this.getNextFittingSegment(this.getCurrentSegment());

        if (!segment) this.throw_away = true;
    }

    nextLabelLine() {
        // increment segment
        var hasNext = this.getNextSegment();
        if (!hasNext) return false;

        // clone options
        var options = Object.create(this.options);
        options.segment_index = this.segment_index;
        options.placement = this.placement;

        // create new label
        var label = new LabelLine(this.size, this.lines, options);

        return (label.throw_away) ? false : label;
    }

    getNextSegment() {
        switch (this.placement) {
            case PLACEMENT.CORNER:
                this.placement = PLACEMENT.MID_POINT;
                break;
            case PLACEMENT.MID_POINT:
                if (this.segment_index >= this.lines.length - 2) return false;
                if (this.segment_size.length > 1) {
                    this.placement = PLACEMENT.CORNER;
                    this.kink_index = 0;
                }
                this.segment_index++;
                break;
        }

        return this.getCurrentSegment();
    }

    getCurrentSegment() {
        let p1, p2, segment;
        switch (this.placement) {
            case PLACEMENT.CORNER:
                p1 = this.lines[this.segment_index - 1];
                p2 = this.lines[this.segment_index];
                let p3 = this.lines[this.segment_index + 1];
                segment = [ p1, p2, p3 ];
                break;
            case PLACEMENT.MID_POINT:
                p1 = this.lines[this.segment_index];
                p2 = this.lines[this.segment_index + 1];
                segment = [ p1, p2 ];
                break;
        }

        return segment;
    }

    getNextFittingSegment(segment) {
        segment = segment || this.getNextSegment();
        if (!segment) return false;

        if (this.doesSegmentFit(segment)) {
            this.update();
            if (this.inTileBounds())
                return segment;
        }

        return this.getNextFittingSegment();
    }

    doesSegmentFit(segment) {
        let does_fit = false;

        switch (this.placement) {
            case PLACEMENT.CORNER:
                does_fit = this.fitKinkedSegment(segment);
                break;
            case PLACEMENT.MID_POINT:
                let excess = 1 + this.options.line_exceed / 100;
                let p0p1 = Vector.sub(segment[0], segment[1]);
                let line_length = Vector.length(p0p1);

                let label_length = this.size[0] * this.options.units_per_pixel;
                does_fit = (label_length < excess * line_length)
                break;
        }

        return does_fit;
    }

    fitKinkedSegment(segment) {
        let excess = 1 + this.options.line_exceed / 100;
        let opp = this.options.units_per_pixel;

        let does_fit = false;

        let p0p1 = Vector.sub(segment[0], segment[1]);
        let p1p2 = Vector.sub(segment[1], segment[2]);

        let line_length1 = Vector.length(p0p1);
        let line_length2 = Vector.length(p1p2);

        // break up multiple segments into two chunks (N-1 options)
        let label_length1 = this.size[0];
        let label_length2 = 0;
        let width;

        this.kink_index = this.segment_size.length - 1;

        while (!does_fit && this.kink_index > 0) {
            width = this.segment_size[this.kink_index];

            label_length1 -= width;
            label_length2 += width;

            does_fit = (opp * label_length1 < excess * line_length1 && opp * label_length2 < excess * line_length2);
            if (!does_fit) this.kink_index--;
        }

        if (does_fit && this.kink_index > 0) {
            var collapsed_size = [0, 0];
            for (var i = 0; i < this.kink_index; i++) {
                collapsed_size[0] += this.segment_size[i];
            }
            collapsed_size[1] = this.size[0] - collapsed_size[0] + 16;

            this.segment_size = collapsed_size;

            this.pre_offset[0][0] = -0.5 * collapsed_size[0];
            this.pre_offset[1][0] = 0.5 * collapsed_size[1];

            return true;
        }
        else return false;
    }

    update() {
        this.angle = this.getCurrentAngle();
        this.position = this.getCurrentPosition();
        this.updateBBoxes();
    }

    getCurrentAngle() {
        var segment = this.getCurrentSegment();
        var angle;

        switch (this.placement) {
            case PLACEMENT.CORNER:
                var theta1 = getAngleFromSegment(segment[0], segment[1]);
                var theta2 = getAngleFromSegment(segment[1], segment[2]);

                var orientation1 = getOrientationFromSegment(segment[0], segment[1]);
                var orientation2 = getOrientationFromSegment(segment[1], segment[2]);

                if (orientation1 !== orientation2) {
                    theta2 -= Math.PI;
                }

                angle = (orientation1) ? [theta2, theta1] : [theta1, theta2];

                break;
            case PLACEMENT.MID_POINT:
                var theta = getAngleFromSegment(segment[0], segment[1]);
                angle = [theta];
                break;
        }

        return angle;
    }

    getCurrentPosition() {
        let segment = this.getCurrentSegment();
        let position;

        switch (this.placement) {
            case PLACEMENT.CORNER:
                position = segment[1].slice();
                break;
            case PLACEMENT.MID_POINT:
                position = [
                    (segment[0][0] + segment[1][0]) / 2,
                    (segment[0][1] + segment[1][1]) / 2
                ];
                break;
        }

        return position;
    }

    updateBBoxes() {
        // TODO: should really only run once the boxes are coalesced
        let upp = this.options.units_per_pixel;
        let height = (this.size[1] + this.options.buffer[1] * 2) * upp * Label.epsilon;

        this.obbs = [];
        this.aabbs = [];

        switch (this.placement) {
            case PLACEMENT.CORNER:
                for (var i = 0; i < this.segment_size.length; i++){
                    var size = this.segment_size[i];
                    var angle = this.angle[i];

                    let width = (size) * upp * Label.epsilon;

                    var direction = (i == 0) ? -1 : 1;
                    var offset = Vector.rot([direction * 0.5 * width, 0], -angle);
                    var position = Vector.add(this.position, offset);

                    var obb = getOBB(position, width, height, angle, this.offset, upp);
                    var aabb = obb.getExtent();

                    this.obbs.push(obb);
                    this.aabbs.push(aabb);
                }
                break;
            case PLACEMENT.MID_POINT:
                let width = (this.size[0] + this.options.buffer[0] * 2) * upp * Label.epsilon;

                var angle = this.angle[0];
                var obb = getOBB(this.position, width, height, angle, this.offset, upp);
                var aabb = obb.getExtent();

                this.obbs.push(obb);
                this.aabbs.push(aabb);
                break;
        }
    }

    inTileBounds() {
        for (var i = 0; i < this.aabbs.length; i++) {
            var aabb = this.aabbs[i];
            var obj = { aabb };
            var in_bounds = super.inTileBounds.call(obj);
            if (!in_bounds) return false;
        }
        return true;
    }

    add(bboxes) {
        for (var i = 0; i < this.aabbs.length; i++){
            var aabb = this.aabbs[i];
            var obb = this.obbs[i];
            var obj = { aabb, obb };
            super.add.call(obj, bboxes);
        }
    }

    discard(bboxes) {
        for (var i = 0; i < this.obbs.length; i++){
            var aabb = this.aabbs[i];
            var obb = this.obbs[i];
            var obj = { aabb, obb };
            var shouldDiscard = super.occluded.call(obj, bboxes);
            if (shouldDiscard) return true;
        }
        return false;
    }
}

function getOBB(position, width, height, angle, offset, upp) {
    // apply offset, x positive, y pointing down
    offset = Vector.rot(offset, angle);
    let p0 = position[0] + (offset[0] * upp);
    let p1 = position[1] - (offset[1] * upp);

    // the angle of the obb is negative since it's the tile system y axis is pointing down
    return new OBB(p0, p1, -angle, width, height);
}

function getAngleFromSegment(pt1, pt2) {
    let PI = Math.PI;
    let PI_2 = PI / 2;
    let p1p2 = Vector.sub(pt1, pt2);
    let theta = Math.atan2(p1p2[0], p1p2[1]) + PI_2;

    if (theta > PI_2) {
        // If in 2nd quadrant, move to 4th quadrant
        theta += PI;
    }
    else if (theta < 0) {
        // If in 4th quadrant, make a positive angle
        theta += 2 * PI
    }

    return theta;
}

function getOrientationFromSegment(pt1, pt2) {
    return pt1[0] >= pt2[0];
}<|endoftext|>


<commit_before>var Twit = require('twit'),
    ShortIdGenerator = require('./ShortIdGenerator');

module.exports = {
    T: null,
    stream: null,
    ME: null,
    PINAuth: null,
    vorpal: null,
    cache: null,
    login: function(ckey, csecret, akey, asecret, vorpal, cache) {
        this.vorpal = vorpal;
        this.cache = cache;
        this.T = new Twit({
            consumer_key:        ckey,
            consumer_secret:     csecret,
            access_token:        akey,
            access_token_secret: asecret
        });

        this.loadMyself();
    },
    startStream: function() {
        const self = this;
        this.stream = this.T.stream('user');

        this.stream.on('tweet', function(tweet) {
            self.displayStatus(tweet);
        });
    },
    loadMyself: function() {
        const self = this;
        this.T.get('account/verify_credentials', { skip_status: true })
            .catch(function(err) {
                self.vorpal.log('Error GET account/verify_credentials: ' + err);
            })
            .then(function(result) {
                self.vorpal.log("Logged in as " + result.data.screen_name);
                self.ME = result.data;

                self.loadHome();
            });
    },

    loadHome: function() {
        const self = this;
        this.T.get('statuses/home_timeline', { count: 20 })
            .catch(function(err) {
                self.vorpal.log('Error GET statuses/home_timeline: ' + err);
            })
            .then(function(result) {
                result.data.reverse().forEach(function(tweet) {
                    self.displayStatus(tweet);
                });
            });
    },

    loadReplies: function() {
        const self = this;
        this.T.get('statuses/mentions_timeline')
            .catch(function(err) {
                self.vorpal.log('Error GET statuses/mentions_timeline: ' + err);
            })
            .then(function(result) {
                result.data.reverse().forEach(function(tweet) {
                    self.displayStatus(tweet);
                });
            });
    },

    update: function(tweet) {
        const self = this;
        this.T.post('statuses/update', { status: tweet })
            .catch(function(err) {
                if (err) self.vorpal.log(err);
            });
    },

    delete: function(id) {
        const self = this;
        this.T.post('statuses/destroy/:id', { id: id })
            .catch(function(err) {
                self.vorpal.log('Error POST statuses/destroy: ' + err);
            })
            .then(function(result) {
                self.vorpal.log(('Deleted tweet with status ' + result.data.id_str).yellow);
            });
    },

    isMention: function(status) {
        for (var m in status.entities.user_mentions) {
            var mention = status.entities.user_mentions[m];
            if (mention.id == this.ME.id) return true;
        }
        return false;
    },

    displayStatus: function(status) {
        var id = ShortIdGenerator.generate();

        var doc = {
            id: id,
            status: status
        };
        this.cache.insert(doc);

        var line = id + "> ";
        line += "<@";
        line += status.user.screen_name == this.ME.screen_name
            ? status.user.screen_name.underline.yellow
            : status.user.screen_name.underline.blue;
        line += ">: ";
        line += this.isMention(status) ? status.text.red : status.text;
        line += '\n';
        this.vorpal.log(line);
    }
};
<commit_msg>parse entities<commit_after>var Twit = require('twit'),
    ShortIdGenerator = require('./ShortIdGenerator');

module.exports = {
    T: null,
    stream: null,
    ME: null,
    PINAuth: null,
    vorpal: null,
    cache: null,
    login: function(ckey, csecret, akey, asecret, vorpal, cache) {
        this.vorpal = vorpal;
        this.cache = cache;
        this.T = new Twit({
            consumer_key:        ckey,
            consumer_secret:     csecret,
            access_token:        akey,
            access_token_secret: asecret
        });

        this.loadMyself();
    },
    startStream: function() {
        const self = this;
        this.stream = this.T.stream('user');

        this.stream.on('tweet', function(tweet) {
            self.displayStatus(tweet);
        });
    },
    loadMyself: function() {
        const self = this;
        this.T.get('account/verify_credentials', { skip_status: true })
            .catch(function(err) {
                self.vorpal.log('Error GET account/verify_credentials: ' + err);
            })
            .then(function(result) {
                self.vorpal.log("Logged in as " + result.data.screen_name);
                self.ME = result.data;

                self.loadHome();
            });
    },

    loadHome: function() {
        const self = this;
        this.T.get('statuses/home_timeline', { count: 20 })
            .catch(function(err) {
                self.vorpal.log('Error GET statuses/home_timeline: ' + err);
            })
            .then(function(result) {
                result.data.reverse().forEach(function(tweet) {
                    self.displayStatus(tweet);
                });
            });
    },

    loadReplies: function() {
        const self = this;
        this.T.get('statuses/mentions_timeline')
            .catch(function(err) {
                self.vorpal.log('Error GET statuses/mentions_timeline: ' + err);
            })
            .then(function(result) {
                result.data.reverse().forEach(function(tweet) {
                    self.displayStatus(tweet);
                });
            });
    },

    update: function(tweet) {
        const self = this;
        this.T.post('statuses/update', { status: tweet })
            .catch(function(err) {
                if (err) self.vorpal.log(err);
            });
    },

    delete: function(id) {
        const self = this;
        this.T.post('statuses/destroy/:id', { id: id })
            .catch(function(err) {
                self.vorpal.log('Error POST statuses/destroy: ' + err);
            })
            .then(function(result) {
                self.vorpal.log(('Deleted tweet with status ' + result.data.id_str).yellow);
            });
    },

    isMention: function(status) {
        for (var m in status.entities.user_mentions) {
            var mention = status.entities.user_mentions[m];
            if (mention.id == this.ME.id) return true;
        }
        return false;
    },

    displayStatus: function(status) {
        var id = ShortIdGenerator.generate();

        var doc = {
            id: id,
            status: status
        };
        this.cache.insert(doc);

        var text = status.text;

        this.vorpal.log(status.entities.urls);
        for (var u in status.entities.urls) {
            var url = status.entities.urls[u];
            var _text = text.substring(0, url.indices[0]);
            _text += url.display_url;
            _text += text.substring(url.indices[1]);
            text = _text;
        }

        for (var m in status.entities.user_mentions) {
            var mention = status.entities.user_mentions[m];
            text = text.replace(
                '@' + mention.screen_name,
                ('@' + mention.screen_name).bold
            );
        }

        var line = id + "> ";
        line += "<@";
        line += status.user.screen_name == this.ME.screen_name
            ? status.user.screen_name.underline.yellow
            : status.user.screen_name.underline.blue;
        line += ">: ";
        line += this.isMention(status) ? text.red : text;
        line += '\n';
        this.vorpal.log(line);
    }
};
<|endoftext|>


<commit_before>var Lumenize = require('./lumenize')
var NOW = new Date();

Ext.define('Rally.ui.tree.PortfolioForecastTree', {
    extend: 'Rally.ui.tree.PortfolioTree',
    alias: 'widget.portfolioforecasttree',

    handleParentItemStoreLoad: function(store, records) {
        this.callParent(arguments);
    },

    handleChildItemStoreLoad: function(store, records, parentTreeItem) {
        this.callParent(arguments);
    },

    treeItemConfigForRecordFn: function(record) {
        return { xtype: 'rallytreeitem' }
    }
});

Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',

    launch: function() {
        this.add({
            xtype: 'fieldset',
            title: 'Portfolio items starting and ending between',
            items: [
                {
                    xtype: 'datefield',
                    id: 'portfolioItemsStart',
                    itemId: 'portfolioItemsStart',
                    fieldLabel: 'Start',
                    value: (new Lumenize.Time(NOW)).add(-3, Lumenize.Time.MONTH).getJSDate('UTC'), // 3 months ago
                    listeners: {
                        change: Ext.bind(this._onDateSelected, this)
                    }
                },
                {
                    xtype: 'datefield',
                    id: 'portfolioItemsEnd',
                    itemId: 'portfolioItemsEnd',
                    fieldLabel: 'End',
                    value: (new Lumenize.Time(NOW)).add(3, Lumenize.Time.MONTH).getJSDate('UTC'), // 3 months from now
                    listeners: {
                        change: Ext.bind(this._onDateSelected, this)
                    }
                }
            ]
        });

        this.add({
            xtype: 'fieldset',
            title: 'Sample historical throughput between',
            items: [
                {
                    xtype: 'datefield',
                    id: 'historicalThroughputStart',
                    itemId: 'historicalThroughputStart',
                    fieldLabel: 'Start',
                    maxValue: NOW,
                    value: (new Lumenize.Time(NOW)).add(-3, Lumenize.Time.MONTH).getJSDate('UTC')
                },
                {
                    xtype: 'datefield',
                    id: 'historicalThroughputEnd',
                    itemId: 'historicalThroughputEnd',
                    fieldLabel: 'End',
                    maxValue: NOW,
                    value: NOW
                }
            ]
        });

        this._onDateSelected();
    },

    _onDateSelected: function() {
        var app = this;
        var pisPromise = app.loadPortfolioItems();

        // Get all currently unaccepted stories associated with a feature
        var storiesPromise = app.getSnapshots({
            fetch: ['_ProjectHierarchy', '_ItemHierarchy'],
            findConfig: {
                "_TypeHierarchy": "HierarchicalRequirement",
                "__At": "current",
                "ScheduleState": {
                    "$lt": "Accepted"
                },
                "Feature": {
                    "$exists": true
                }
            }
        });

        var throughputPromise = app.throughputByProject();

        Deft.Promise.all([ pisPromise, storiesPromise, throughputPromise ]).then(function(result) {
            var store = result[0];
            var stories = result[1];
            var historicalThroughput = result[2];

            store.each(function(portfolioItem) {
                var objectId = portfolioItem.data.ObjectID;
                var startOn = Ext.Date.format(new Date(), "Y-m-d");
                var endBefore = Ext.Date.format(portfolioItem.data.PlannedEndDate, "Y-m-d");

                //If endBefore < startOn, we need Infinity throughput to finish on time, so those
                //features will still display as "at risk" (since we already blew the planned end date)
                var timeline = new Lumenize.Timeline({
                    startOn: startOn,
                    endBefore: endBefore,
                    granularity: Lumenize.Time.DAY
                });
                var workdaysRemaining = timeline.getAll().length;

                portfolioItem.data.Projects = {};

                //TODO - optimize later
                //Count stories remaining by project id
                _.each(stories, function(story) {
                    if (story._ItemHierarchy.indexOf(objectId) >= 0) {
                        portfolioItem.data.Projects[story.Project] = portfolioItem.data.Projects[story.Project] || { count: 0 }
                        portfolioItem.data.Projects[story.Project].count++
                    }
                });

                //Normalize story count by workdays remaining
                _.each(portfolioItem.data.Projects, function(project, projectOid) {
                    project.daysToCompletion = project.count * historicalThroughput[projectOid];
                    project.atRisk = project.daysToCompletion > workdaysRemaining;
                });

                //Determine if project is at risk (required throughput > historical throughput)
                portfolioItem.data.AtRisk = _.any(portfolioItem.data.Projects, function(project) {
                    return project.daysToCompletion > workdaysRemaining;
                });

                var piLink = Ext.dom.Query.select('a[href*=' + portfolioItem.data.ObjectID +']');

                if (portfolioItem.data.AtRisk) {
                    Ext.get(piLink[0]).addCls('atRisk');
                }

                app.getProjectNamebyIds(_.keys(portfolioItem.data.Projects)).then(function(names){
                    var table = '';
                    _.each(portfolioItem.data.Projects, function(project, projectOid) {
                        var actualThroughput = historicalThroughput[projectOid];
                        table += [
                            '
',
                                '', names[projectOid], '	',
                                '', workdaysRemaining, '	',
                                '', project.daysToCompletion.toFixed(2), '	',
                            '
'
                        ].join('');
                    });

                    table = [
                        '
',
                            'Project	',
                            'Days left	',
                            'Days needed	',
                        '
',
                        table
                    ].join('');

                    Ext.create('Rally.ui.tooltip.ToolTip', {
                        target : Ext.get(piLink[0]),
                        html: '
' + table + '
'
                    });
                });
            });
        });
    },

    getProjectNamebyIds: function(ids) {
        var deferred = new Deft.Deferred();
        var filter;

        _.each(ids, function(id){
            var filterItem = Ext.create('Rally.data.QueryFilter',
            {
                property: 'ObjectID',
                operator: '=',
                value: id
            });

            filter = filter ? filter.or(filterItem) : filterItem;
        });

        Ext.create('Rally.data.WsapiDataStore', {
            config: {
                autoLoad: true,
                limit: 'Infinity'
            },
            model: 'Project',
            filters: filter,
            listeners: {
                load: function(store, data, success) {
                    var names = {};
                    _.each(data, function(project){
                        names[project.data.ObjectID] = project.data.Name;
                    });
                    deferred.resolve(names);
                }
            },
            fetch: ['ObjectID,Name']
        });

        return deferred.getPromise();
    },

    getSnapshots: function(config) {
        var workspaceOid = this.context.getWorkspace().ObjectID;
        var deferred = new Deft.Deferred();
        Ext.create('Rally.data.lookback.SnapshotStore', _.merge({
            // TODO - account for > 20k results
            autoLoad: true,
            context: {
                workspace: '/workspace/' + workspaceOid
            },
            listeners: {
                load: function(store, data, success) {
                    deferred.resolve(_.pluck(data, 'raw'));
                }
            }
        }, config));

        return deferred.getPromise();
    },

    throughputByProject: function() {
        var start = Ext.Date.format(Ext.getCmp('historicalThroughputStart').getValue(), "Y-m-d");
        var end = Ext.Date.format(Ext.getCmp('historicalThroughputEnd').getValue(), "Y-m-d");

        var timeline = new Lumenize.Timeline({
            startOn: start,
            endBefore: end,
            granularity: Lumenize.Time.DAY
        });
        var workdays = timeline.getAll().length;

        //Stories transitioned from <Accepted to >=Accepted
        var forwardThroughput = this.getSnapshots({
            fetch: ['_ProjectHierarchy'],
            findConfig: {
                "_TypeHierarchy": "HierarchicalRequirement",
                "_PreviousValues.ScheduleState": {
                    "$exists": true,
                    "$lt": "Accepted"
                },
                "ScheduleState": {
                    "$gte": "Accepted"
                },
                "_ValidFrom": {
                    "$lt": end,
                    "$gte": start
                }
            }
        });

        //Stories transitioned from >=Accepted to <Accepted
        var backwardThroughput = this.getSnapshots({
            fetch: ['_ProjectHierarchy'],
            findConfig: {
                "_TypeHierarchy": "HierarchicalRequirement",
                "_PreviousValues.ScheduleState": {
                    "$exists": true,
                    "$gte": "Accepted"
                },
                "ScheduleState": {
                    "$lt": "Accepted"
                },
                "_ValidFrom": {
                    "$lt": end,
                    "$gte": start
                }
            }
        });

        //Group into lookup table by project oid
        return Deft.Promise.all([ forwardThroughput, backwardThroughput ]).then(function(results) {
            var forward = results[0];
            var backward = results[1];
            var throughput = {};

            // Add forward throughput
            _.each(forward, function(snapshot) {
                _.each(snapshot._ProjectHierarchy, function(projectOid) {
                    throughput[projectOid] = throughput[projectOid] || 0;
                    throughput[projectOid]++;
                });
            });

            //Subtract backward throughput
            _.each(backward, function(snapshot) {
                _.each(snapshot._ProjectHierarchy, function(projectOid) {
                    throughput[projectOid] = throughput[projectOid] || 0;
                    throughput[projectOid]--;
                });
            });

            //Normalize the throughput by workday
            return _.transform(throughput, function(result, num, key) {
                result[key] = num/workdays;
            });
        });
    },

    loadPortfolioItems: function() {
        var workspaceOid = this.context.getWorkspace().ObjectID;
        var portfolioItemsStart = Ext.Date.format(Ext.getCmp('portfolioItemsStart').getValue(), "Y-m-d");
        var portfolioItemsEnd = Ext.Date.format(Ext.getCmp('portfolioItemsEnd').getValue(), "Y-m-d");
        var deferred = new Deft.Deferred();

        this.portfolioTree = this.add({
            xtype: 'portfolioforecasttree',
            id: 'portfoliotree',
            itemId: 'portfoliotree',
            enableDragAndDrop: false,
            //@todo: parameterize PI type
            topLevelModel: workspaceOid == '41529001' ? 'portfolioitem/feature' : 'portfolioitem/epic',
            topLevelStoreConfig: {
                filters: [{
                    property: 'PlannedStartDate',
                    operator: '>',
                    value: portfolioItemsStart
                }, {
                    property: 'PlannedEndDate',
                    operator: '<',
                    value: portfolioItemsEnd
                }, {
                    property: 'DirectChildrenCount', //Only show PIs that have children
                    operator: '>',
                    value: 0
                }, {
                    property: 'PercentDoneByStoryCount', //Do not show completed PIs
                    operator: '<',
                    value: 1
                }],
                listeners: {
                    refresh: function(store) {
                        deferred.resolve(store);
                    }
                }
            }
        });

        return deferred.getPromise();
    }
});
<commit_msg>Refactor to show project info under feature level<commit_after>var Lumenize = require('./lumenize')
var NOW = new Date();

Ext.define('Rally.ui.tree.PortfolioForecastTreeItem', {
    extend: 'Rally.ui.tree.TreeItem',
    alias: 'widget.rallyportfolioforecasttreeitem',

    getContentTpl: function(){
        var me = this;

        return Ext.create('Ext.XTemplate',
            '
{[this.getFormattedId()]} {[this.getSeparator()]}{Name}
',
            '
({[values.DaysToCompletion.toFixed(1)]} days needed/{WorkdaysRemaining} days left)
',
            '
',
                '{[this.getPercentDone()]}',
            '
',
            {
                getPercentDone: function() {
                    var record = me.getRecord();
                    return record.getField('PercentDoneByStoryCount') ? Rally.ui.renderer.RendererFactory.renderRecordField(me.getRecord(), 'PercentDoneByStoryCount') : '';
                },
                getFormattedId: function() {
                    var record = me.getRecord();
                    return record.getField('FormattedID') ? Rally.ui.renderer.RendererFactory.renderRecordField(record, 'FormattedID') : '';
                },
                getSeparator: function() {
                    return this.getFormattedId() ? '- ' : '';
                }
            }
        );
    }
});

Ext.define('Rally.ui.tree.PortfolioForecastTree', {
    extend: 'Rally.ui.tree.PortfolioTree',
    alias: 'widget.portfolioforecasttree',

    requires: [
        'Rally.data.lookback.SnapshotStore'
    ],

    handleParentItemStoreLoad: function(store, records) {
        //Compute risk (should always be a portfolio item)
        var originalArguments = arguments;
        this.loadStoriesRemainingByFeatureAndProject(Ext.bind(function() {
            this.computePortfolioItemRisk(store);
            this.superclass.handleParentItemStoreLoad.apply(this, originalArguments);
        }, this));
    },

    handleChildItemStoreLoad: function(store, records, parentTreeItem) {
        this.computePortfolioItemRisk(store);
        this.callParent(arguments);
    },

    drawChildItems: function (parentTreeItem) {
        var record = parentTreeItem.getRecord();
        if (this._isRecordBottomPortfolioLevel(record)) {
            //If parent is the lowest level PI, fetch projects and augment with project breakdown
            this.drawChildProjects(parentTreeItem);
        } else {
            this.callParent(arguments);
        }
    },

    computePortfolioItemRisk: function(store) {
        var storiesRemainingByFeatureAndProject = this.storiesRemainingByFeatureAndProject;
        var historicalThroughput = this.config.historicalThroughputByProject;
        store.each(function(portfolioItem) {
            var objectId = portfolioItem.data.ObjectID;
            var startOn = Ext.Date.format(new Date(), "Y-m-d");
            var endBefore = Ext.Date.format(portfolioItem.data.PlannedEndDate, "Y-m-d");

            //If endBefore < startOn, we need Infinity throughput to finish on time, so those
            //features will still display as "at risk" (since we already blew the planned end date)
            var timeline = new Lumenize.Timeline({
                startOn: startOn,
                endBefore: endBefore,
                granularity: Lumenize.Time.DAY
            });
            var workdaysRemaining = timeline.getAll().length;

            portfolioItem.data.Projects = {};

            //Count stories remaining by project id
            _.each(storiesRemainingByFeatureAndProject[objectId], function(count, projectOid) {
                portfolioItem.data.Projects[projectOid] = { StoriesRemaining: count }
            });

            //Normalize story count by workdays remaining
            _.each(portfolioItem.data.Projects, function(project, projectOid) {
                project.DaysToCompletion = project.StoriesRemaining * historicalThroughput[projectOid];
                project.AtRisk = project.DaysToCompletion > workdaysRemaining;
            });

            //Determine if project is at risk (required throughput > historical throughput)
            portfolioItem.data.AtRisk = _.any(portfolioItem.data.Projects, function(project) {
                return project.AtRisk;
            });

            portfolioItem.data.WorkdaysRemaining = workdaysRemaining;
            portfolioItem.data.DaysToCompletion = _.max(portfolioItem.data.Projects, 'DaysToCompletion').DaysToCompletion;
        });
    },

    drawChildProjects: function(parentTreeItem) {
        var filter;
        var record = parentTreeItem.getRecord();
        var ids = _.keys(record.data.Projects);

        _.each(ids, function(id) {
            var filterItem = Ext.create('Rally.data.QueryFilter',
            {
                property: 'ObjectID',
                operator: '=',
                value: id
            });

            filter = filter ? filter.or(filterItem) : filterItem;
        });

        var childStore = Ext.create('Rally.data.WsapiDataStore', {
            autoLoad: true,
            limit: 'Infinity',
            model: 'Project',
            filters: filter,
            listeners: {
                load: Ext.bind(function(store, records, success) {
                    _.each(records, function(project) {
                        _.each(record.data.Projects, function(projectRisk, projectOid) {
                            if (projectOid == project.data.ObjectID) {
                                _.merge(project.data, projectRisk);
                            }
                        });
                    });
                    this.renderChildRecords(Ext.clone(records), parentTreeItem);
                }, this)
            }
        });

        parentTreeItem.store = childStore;
    },

    loadStoriesRemainingByFeatureAndProject: function(callback) {
        Ext.create('Rally.data.lookback.SnapshotStore', {
            // TODO - account for > 20k results
            autoLoad: true,
            fetch: ['_ProjectHierarchy', '_ItemHierarchy'],
            findConfig: {
                "_TypeHierarchy": "HierarchicalRequirement",
                "__At": "current",
                "ScheduleState": {
                    "$lt": "Accepted"
                },
                "Feature": {//TODO - will this always work?
                    "$exists": true
                }
            },
            listeners: {
                load: Ext.bind(function(store, data, success) {
                    var stories = _.pluck(data, 'raw');

                    //Count stories remaining by project id
                    var storiesRemainingByFeatureAndProject = {};
                    _.each(stories, function(story) {
                        _.each(story._ItemHierarchy, function(objectId) {
                            var projects = storiesRemainingByFeatureAndProject[objectId] = storiesRemainingByFeatureAndProject[objectId] || {};
                            projects[story.Project] = projects[story.Project] || 0;
                            projects[story.Project]++;
                        });
                    });

                    this.storiesRemainingByFeatureAndProject = storiesRemainingByFeatureAndProject;

                    callback();
                }, this)
            }
        });
    },

    treeItemConfigForRecordFn: function(record) {
        return { xtype: 'rallyportfolioforecasttreeitem' }
    }
});

Ext.define('CustomApp', {
    extend: 'Rally.app.App',
    componentCls: 'app',

    launch: function() {
        this.add({
            xtype: 'fieldset',
            title: 'Portfolio items starting and ending between',
            items: [
                {
                    xtype: 'datefield',
                    id: 'portfolioItemsStart',
                    itemId: 'portfolioItemsStart',
                    fieldLabel: 'Start',
                    value: (new Lumenize.Time(NOW)).add(-3, Lumenize.Time.MONTH).getJSDate('UTC'), // 3 months ago
                    listeners: {
                        change: Ext.bind(this.loadPortfolioItems, this)
                    }
                },
                {
                    xtype: 'datefield',
                    id: 'portfolioItemsEnd',
                    itemId: 'portfolioItemsEnd',
                    fieldLabel: 'End',
                    value: (new Lumenize.Time(NOW)).add(3, Lumenize.Time.MONTH).getJSDate('UTC'), // 3 months from now
                    listeners: {
                        change: Ext.bind(this.loadPortfolioItems, this)
                    }
                }
            ]
        });

        this.add({
            xtype: 'fieldset',
            title: 'Sample historical throughput between',
            items: [
                {
                    xtype: 'datefield',
                    id: 'historicalThroughputStart',
                    itemId: 'historicalThroughputStart',
                    fieldLabel: 'Start',
                    maxValue: NOW,
                    value: (new Lumenize.Time(NOW)).add(-3, Lumenize.Time.MONTH).getJSDate('UTC'),
                    listeners: {
                        change: Ext.bind(this.loadThroughputByProjectAndPortfolioItems, this)
                    }
                },
                {
                    xtype: 'datefield',
                    id: 'historicalThroughputEnd',
                    itemId: 'historicalThroughputEnd',
                    fieldLabel: 'End',
                    maxValue: NOW,
                    value: NOW,
                    listeners: {
                        change: Ext.bind(this.loadThroughputByProjectAndPortfolioItems, this)
                    }
                }
            ]
        });

        this.loadThroughputByProjectAndPortfolioItems();
    },

    getSnapshots: function(config) {
        var workspaceOid = this.context.getWorkspace().ObjectID;
        var deferred = new Deft.Deferred();
        Ext.create('Rally.data.lookback.SnapshotStore', _.merge({
            // TODO - account for > 20k results
            autoLoad: true,
            context: {
                workspace: '/workspace/' + workspaceOid
            },
            listeners: {
                load: function(store, data, success) {
                    deferred.resolve(_.pluck(data, 'raw'));
                }
            }
        }, config));

        return deferred.getPromise();
    },

    throughputByProject: function() {
        var start = Ext.Date.format(Ext.getCmp('historicalThroughputStart').getValue(), "Y-m-d");
        var end = Ext.Date.format(Ext.getCmp('historicalThroughputEnd').getValue(), "Y-m-d");

        var timeline = new Lumenize.Timeline({
            startOn: start,
            endBefore: end,
            granularity: Lumenize.Time.DAY
        });
        var workdays = timeline.getAll().length;

        //Stories transitioned from <Accepted to >=Accepted
        var forwardThroughput = this.getSnapshots({
            fetch: ['_ProjectHierarchy'],
            findConfig: {
                "_TypeHierarchy": "HierarchicalRequirement",
                "_PreviousValues.ScheduleState": {
                    "$exists": true,
                    "$lt": "Accepted"
                },
                "ScheduleState": {
                    "$gte": "Accepted"
                },
                "_ValidFrom": {
                    "$lt": end,
                    "$gte": start
                }
            }
        });

        //Stories transitioned from >=Accepted to <Accepted
        var backwardThroughput = this.getSnapshots({
            fetch: ['_ProjectHierarchy'],
            findConfig: {
                "_TypeHierarchy": "HierarchicalRequirement",
                "_PreviousValues.ScheduleState": {
                    "$exists": true,
                    "$gte": "Accepted"
                },
                "ScheduleState": {
                    "$lt": "Accepted"
                },
                "_ValidFrom": {
                    "$lt": end,
                    "$gte": start
                }
            }
        });

        //Group into lookup table by project oid
        return Deft.Promise.all([ forwardThroughput, backwardThroughput ]).then(function(results) {
            var forward = results[0];
            var backward = results[1];
            var throughput = {};

            // Add forward throughput
            _.each(forward, function(snapshot) {
                _.each(snapshot._ProjectHierarchy, function(projectOid) {
                    throughput[projectOid] = throughput[projectOid] || 0;
                    throughput[projectOid]++;
                });
            });

            //Subtract backward throughput
            _.each(backward, function(snapshot) {
                _.each(snapshot._ProjectHierarchy, function(projectOid) {
                    throughput[projectOid] = throughput[projectOid] || 0;
                    throughput[projectOid]--;
                });
            });

            //Normalize the throughput by workday
            return _.transform(throughput, function(result, num, key) {
                result[key] = num/workdays;
            });
        });
    },

    loadThroughputByProjectAndPortfolioItems: function() {
        this.historicalThroughputByProject = this.throughputByProject();
        this.loadPortfolioItems();
    },

    loadPortfolioItems: function() {
        var workspaceOid = this.context.getWorkspace().ObjectID;
        var portfolioItemsStart = Ext.Date.format(Ext.getCmp('portfolioItemsStart').getValue(), "Y-m-d");
        var portfolioItemsEnd = Ext.Date.format(Ext.getCmp('portfolioItemsEnd').getValue(), "Y-m-d");

        this.historicalThroughputByProject.then(Ext.bind(function(historicalThroughputByProject) {
            //TODO - update and refresh existing tree
            this.portfolioTree = this.add({
                xtype: 'portfolioforecasttree',
                id: 'portfoliotree',
                itemId: 'portfoliotree',
                enableDragAndDrop: false,
                historicalThroughputByProject: historicalThroughputByProject,
                //@todo: parameterize PI type
                topLevelModel: workspaceOid == '41529001' ? 'portfolioitem/feature' : 'portfolioitem/epic',
                topLevelStoreConfig: {
                    filters: [{
                        property: 'PlannedStartDate',
                        operator: '>',
                        value: portfolioItemsStart
                    }, {
                        property: 'PlannedEndDate',
                        operator: '<',
                        value: portfolioItemsEnd
                    }, {
                        property: 'DirectChildrenCount', //Only show PIs that have children
                        operator: '>',
                        value: 0
                    }, {
                        property: 'PercentDoneByStoryCount', //Do not show completed PIs
                        operator: '<',
                        value: 1
                    }]
                }
            });
        }, this));
    }
});
<|endoftext|>


<commit_before>(function ($) {
    var IgniValidation = {
        rules: {}
    };

    IgniValidation.rules.required = function (element, message) {
        var $el = $(element);
        var check = $el.val() !== '';
        if (!check) {
            if (!$el.hasClass('validate-required-failed')) {
                $el.closest('.form-group').addClass('has-error');
                $('
' + message + '
').insertAfter($el);
                $el.addClass('validate-required-failed')
            }
        } else {
            $el.removeClass('validate-required-failed');
            $el.closest('.form-group').removeClass('has-error');
            $el.next().remove();

        }
        return check;
    };

    var validationRules = {
        'validate-required': {
            callback: IgniValidation.rules.required,
            message: 'The field is required.'
        }
    };

    $(function () {
        $('body').on('submit', 'form', function (e) {
            var that = $(this);
            var hasErrors = false;

            $.each(validationRules, function (i, value) {
                // fetch element
                var elements = $('.' + i, that);
                $.each(elements, function (idx, element) {
                    console.log(value);
                    if (!value.callback(element, value.message)) {
                        hasErrors = true;
                    }
                });
            });
            if (hasErrors) {
                e.preventDefault();
                e.stopImmediatePropagation();
            }
        });

    });
})(jQuery);<commit_msg>remove console.log<commit_after>(function ($) {
    var IgniValidation = {
        rules: {}
    };

    IgniValidation.rules.required = function (element, message) {
        var $el = $(element);
        var check = $el.val() !== '';
        if (!check) {
            if (!$el.hasClass('validate-required-failed')) {
                $el.closest('.form-group').addClass('has-error');
                $('
' + message + '
').insertAfter($el);
                $el.addClass('validate-required-failed')
            }
        } else {
            $el.removeClass('validate-required-failed');
            $el.closest('.form-group').removeClass('has-error');
            $el.next().remove();

        }
        return check;
    };

    var validationRules = {
        'validate-required': {
            callback: IgniValidation.rules.required,
            message: 'The field is required.'
        }
    };

    $(function () {
        $('body').on('submit', 'form', function (e) {
            var that = $(this);
            var hasErrors = false;

            $.each(validationRules, function (i, value) {
                // fetch element
                var elements = $('.' + i, that);
                $.each(elements, function (idx, element) {
                    if (!value.callback(element, value.message)) {
                        hasErrors = true;
                    }
                });
            });
            if (hasErrors) {
                e.preventDefault();
                e.stopImmediatePropagation();
            }
        });

    });
})(jQuery);<|endoftext|>


<commit_before>// Note: You must restart bin/webpack-dev-server for changes to take effect

const merge = require('webpack-merge');
const sharedConfig = require('./shared.js');
const { settings, output } = require('./configuration.js');

module.exports = merge(sharedConfig, {
  devtool: '#eval-source-map',

  stats: {
    errorDetails: true
  },

  output: {
    pathinfo: true
  },

  devServer: {
    clientLogLevel: 'none',
    https: settings.dev_server.https,
    host: settings.dev_server.host,
    port: settings.dev_server.port,
    contentBase: output.path,
    publicPath: output.publicPath,
    compress: true,
    headers: { 'Access-Control-Allow-Origin': '*' },
    historyApiFallback: true,
    watchOptions: {
      ignored: /node_modules/
    },
    hot: true
  }
});
<commit_msg>hotonly for hot reload<commit_after>// Note: You must restart bin/webpack-dev-server for changes to take effect

const merge = require('webpack-merge');
const sharedConfig = require('./shared.js');
const { settings, output } = require('./configuration.js');

module.exports = merge(sharedConfig, {
  devtool: '#eval-source-map',

  stats: {
    errorDetails: true
  },

  output: {
    pathinfo: true
  },

  devServer: {
    clientLogLevel: 'none',
    https: settings.dev_server.https,
    host: settings.dev_server.host,
    port: settings.dev_server.port,
    contentBase: output.path,
    publicPath: output.publicPath,
    compress: true,
    headers: { 'Access-Control-Allow-Origin': '*' },
    historyApiFallback: true,
    watchOptions: {
      ignored: /node_modules/
    },
    hot: true,
    hotOnly: true
  }
});
<|endoftext|>