<commit_before>let existCookie = document.cookie.match(/^(.*;)?\s*tipCookie\s*=\s*[^;]+(.*)?$/)
var htmlNames = ["Importanttips.html","Secondarytips.html","Tertiarytips.html"];
var fileNames = ["https://raw.githubusercontent.com/umple/umple/Issue%231636_TOTD/build/reference/6600ImportantTips.txt","https://raw.githubusercontent.com/umple/umple/Issue%231636_TOTD/build/reference/6601SecondaryTips.txt", "https://raw.githubusercontent.com/umple/umple/Issue%231636_TOTD/build/reference/6602TertiaryTips.txt"];

if(existCookie===null) {
    // =========================================
    // Tip Information Gatherers
    // =========================================

    var firstTime = localStorage.getItem("first_time");
    if(!firstTime) {
        window.localStorage.setItem('tipCount',0);
        window.localStorage.setItem('priorityCount',0);
        window.localStorage.setItem('loopCount',0);
        //acquiring tipInformation
        for (let files=0; files<fileNames.length; files++){
            jQuery.ajax({
                url: fileNames[files],
                success: function(results){
                    let allInfo=results;
                    allInfo=allInfo.split("
");
                    allInfo.shift();    //removes title+descrription element
                    var descrpt=[];
                    for (let i=0; i<allInfo.length; i++){
                        var t=[];
                        t.push(allInfo[i].substring(0, allInfo[i].indexOf("
")));
                        var link="https://cruise.umple.org/umple/"+htmlNames[files];
                        var navbar='
<button class="lowerBarBtn" id="goLeft" onclick="onButtonPrevious()">&laquo Previous</button> View All <button class="lowerBarBtn" id="goRight" onclick="onButtonNext()">Next &raquo</button>
';
                        if (i==allInfo.length-1){
                            t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length)+navbar);
                        }
                        else{
                            t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length-11)+navbar);
                        }
                        descrpt.push(t);
                    }
                    var inform=[];
                    inform[0]=descrpt;
                    window.localStorage.setItem('tipInfo'+files, JSON.stringify(inform)); //adding items to local storage
                },
                error: function(response){
                    descrpt.push(["couldn't access file",error]);
                },
            });
        }
        window.localStorage.setItem("first_time","1");
    }

    // =========================================
    // Tip Pickers
    // =========================================

    jQuery(document).ajaxStop(function(){allocateMessage()});

    let currentTime=new Date();
    currentTime.setTime(currentTime.getTime());
    currentTime.setHours(24, 0, 0, 0);
    document.cookie="tipCookie=done; expires="+currentTime.toUTCString()+"; path=/";
}

function allocateMessage() {
    var priority = window.localStorage.getItem('priorityCount');
    var tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
    var num = parseInt(window.localStorage.getItem('tipCount'));
    var loop = parseInt(window.localStorage.getItem('loopCount'));

    if (tip[loop]!=null && tip[loop][num]!=null && tip[loop][num][0]!=null && tip[loop][num][1]!=null){
        document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num][0] + ' Click for more
';
        jQuery('#styleTip a').attr('target', 'helppage');
        document.getElementById('extraInfo').innerHTML = tip[loop][num][1];
        jQuery('#extraInfo a').attr('target', 'helppage');
    }
    setForNext(num, priority, tip, loop);
};

// =========================================
// Display/Hide Functions
// =========================================

function onButtonNext(){
    if (document.getElementById("goRight").style.opacity!='0'){
        hideExtra();
        setTimeout(function(){
            allocateMessage()
        }, 800);
    }
}

function onButtonPrevious(){
    if (document.getElementById("goLeft").style.opacity!='0'){
        hideExtra();
        setTimeout(function(){
            showPrevious()
        }, 800);
    }
}

var hideWithDelay;

function showExtra(){
    document.getElementById('extraInfo').classList.remove("fade-outInst");
    document.getElementById('extraInfo').classList.add("fade-in");
    hideWithDelay = setTimeout(function(){hideExtra()}, calculateDelay());
    hideWithDelay;
}

function hideExtra(){
    clearTimeout(hideWithDelay);
    document.getElementById('extraInfo').classList.remove("fade-in");
    document.getElementById('extraInfo').classList.add("fade-outInst");
}

function calculateDelay(){
    sectionHtml=document.getElementById('extraInfo').innerHTML;
    sectionText=document.getElementById('extraInfo').textContent;

    if (sectionText==null){
        return null;
    }
    if (sectionText.length==0){
        return 0;
    }

    let sum=5;

    //counting words
    var rem=sectionText;
    rem=rem.replace(/\r?\n|\r/g, "");
    var wordCount=rem.split(" ").length;

    sum += Math.ceil((wordCount-5)/3);

    if (sectionHtml.indexOf("href")>=0 && sectionHtml.indexOf("href")<sectionHtml.indexOf("lowerBarBtn")){
        sum += 5;
    }

    return sum*1000; // get Prev, Next, View All out of count, and turn into milliseconds
}

// =========================================
// Previous/Next Manipulators
// =========================================

function setForNext(num, priority, tip, loop){
    if (priority==0 && num==0 && loop==0){
        document.getElementById("goLeft").style.opacity=0;
        document.getElementById("goLeft").style.cursor="default";
    }
    else{
        document.getElementById("goLeft").style.opacity=1;
        document.getElementById("goLeft").style.cursor="pointer";
    }

    if (num>=tip[loop].length-1){
        if (parseInt(priority)==2){ //renew data
            loop++;
            let numNulls=0;
            for (let files=0; files<fileNames.length; files++){
                jQuery.ajax({
                    url: fileNames[files],
                    success: function(results){
                        let allInfo=results;
                        allInfo=allInfo.split("
");
                        allInfo.shift();    //removes title+descrription element
                        var descrpt=[];
                        for (let i=0; i<allInfo.length; i++){
                            var t=[];
                            t.push(allInfo[i].substring(0, allInfo[i].indexOf("
")));
                            var link="https://cruise.umple.org/umple/"+htmlNames[files];
                            var navbar='
<button class="lowerBarBtn" id="goLeft" onclick="onButtonPrevious()">&laquo Previous</button> View All <button class="lowerBarBtn" id="goRight" onclick="onButtonNext()">Next &raquo</button>
';
                            if (i==allInfo.length-1){
                                t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length)+navbar);
                            }
                            else{
                                t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length-11)+navbar);
                            }
                            var info = window.localStorage.getItem("tipInfo"+files);
                            let notFound = true;
                            for (let checkLoop=0; checkLoop<loop; checkloop++){ //only if none of the previous versions have that tip
                                if (info[checkLoop].includes(t)>=0){
                                    notFound = false;
                                    break;
                                }
                            }
                            if (notFound){
                                descrpt.push(t);
                            }
                        }
                        if (descrpt == null){
                            numNulls++;
                        }
                        info.push(descrpt);
                        window.localStorage.setItem('tipInfo'+files, JSON.stringify(info));
                        },
                    error: function(response){
                        descrpt.push(["couldn't access file",error]);
                    }
                });
            }
            if (numNulls == 3){ //no new additions to either files
                for (let countPriority=0; countPriority<=parseInt(priority); countPriority++){
                    var data = JSON.parse(window.localStorage.getItem('tipInfo'+countPriority));
                    data.pop();
                    window.localStorage.setItem('tipInfo'+countPriority, JSON.stringify(info));
                }
                loop--;
            }
            else{
                window.localStorage.setItem('tipCount','0');
            }
            window.localStorage.setItem('priorityCount', '0');
            window.localStorage.setItem("loopCount", loop);
        }
        else {
            window.localStorage.setItem('priorityCount', parseInt(priority)+1);
            window.localStorage.setItem('tipCount','0');
        }
    }
    else{
        window.localStorage.setItem('tipCount',num+1);
    }

    if (tip[loop]==null || tip[loop][num]==null || tip[loop][num][0]==null || tip[loop][num][1]==null){
        document.getElementById("goRight").style.opacity=0;
        document.getElementById("goRight").style.cursor="default";
    }
    else{
        document.getElementById("goRight").style.opacity=1;
        document.getElementById("goRight").style.cursor="pointer";
    }
}

function showPrevious(){
    var priority = window.localStorage.getItem('priorityCount');
    var tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
    var num = parseInt(window.localStorage.getItem('tipCount'));
    var loop = parseInt(window.localStorage.getItem('loopCount'));

    if (num-2 < 0){
        if (priority-1 >= 0 && (num === 0 || num === 1)){ //no change to tip otherwise
            priority--;
            window.localStorage.setItem('priorityCount',priority);
            tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
            if (num === 0){
                num=tip[loop].length-2;
            }
            else {
                num=tip[loop].length-1;
            }
            window.localStorage.setItem('tipCount',num);

            document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num][0] + ' Click for more
';
            jQuery('#styleTip a').attr('target', 'helppage');
            document.getElementById('extraInfo').innerHTML = tip[loop][num][1];
            jQuery('#extraInfo a').attr('target', 'helppage');

            setForNext(num, priority, tip, loop);
        }
        else if (loop-1 >= 0 && priority == 0){ //change versions
            priority=2;
            window.localStorage.setItem('priorityCount',priority);
            tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
            loop--;
            window.localStorage.setItem('loopCount',loop);

            if (num === 0){
                num=tip[loop].length-2;
            }
            else {
                num=tip[loop].length-1;
            }
            window.localStorage.setItem('tipCount',num);

            document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num][0] + ' Click for more
';
            jQuery('#styleTip a').attr('target', 'helppage');
            document.getElementById('extraInfo').innerHTML = tip[loop][num][1];
            jQuery('#extraInfo a').attr('target', 'helppage');

            setForNext(num, priority, tip, loop);
        }
    }
    else{
        document.getElementById('extraInfo').style.visibility = 'hidden';
        document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num-2][0] + ' Click for more
';
        jQuery('#styleTip a').attr('target', 'helppage');
        document.getElementById('extraInfo').innerHTML = tip[loop][num-2][1];
        jQuery('#extraInfo a').attr('target', 'helppage');

        window.localStorage.setItem('tipCount',num-2);

        setForNext(num-2, priority, tip, loop);
    }
}<commit_msg>Updated links to raw files<commit_after>let existCookie = document.cookie.match(/^(.*;)?\s*tipCookie\s*=\s*[^;]+(.*)?$/)
var htmlNames = ["Importanttips.html","Secondarytips.html","Tertiarytips.html"];
var fileNames = ["https://raw.githubusercontent.com/umple/umple/master/build/reference/6600ImportantTips.txt","https://raw.githubusercontent.com/umple/umple/master/build/reference/6601SecondaryTips.txt", "https://raw.githubusercontent.com/umple/umple/master/build/reference/6602TertiaryTips.txt"];

if(existCookie===null) {
    // =========================================
    // Tip Information Gatherers
    // =========================================

    var firstTime = localStorage.getItem("first_time");
    if(!firstTime) {
        window.localStorage.setItem('tipCount',0);
        window.localStorage.setItem('priorityCount',0);
        window.localStorage.setItem('loopCount',0);
        //acquiring tipInformation
        for (let files=0; files<fileNames.length; files++){
            jQuery.ajax({
                url: fileNames[files],
                success: function(results){
                    let allInfo=results;
                    allInfo=allInfo.split("
");
                    allInfo.shift();    //removes title+descrription element
                    var descrpt=[];
                    for (let i=0; i<allInfo.length; i++){
                        var t=[];
                        t.push(allInfo[i].substring(0, allInfo[i].indexOf("
")));
                        var link="https://cruise.umple.org/umple/"+htmlNames[files];
                        var navbar='
<button class="lowerBarBtn" id="goLeft" onclick="onButtonPrevious()">&laquo Previous</button> View All <button class="lowerBarBtn" id="goRight" onclick="onButtonNext()">Next &raquo</button>
';
                        if (i==allInfo.length-1){
                            t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length)+navbar);
                        }
                        else{
                            t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length-11)+navbar);
                        }
                        descrpt.push(t);
                    }
                    var inform=[];
                    inform[0]=descrpt;
                    window.localStorage.setItem('tipInfo'+files, JSON.stringify(inform)); //adding items to local storage
                },
                error: function(response){
                    descrpt.push(["couldn't access file",error]);
                },
            });
        }
        window.localStorage.setItem("first_time","1");
    }

    // =========================================
    // Tip Pickers
    // =========================================

    jQuery(document).ajaxStop(function(){allocateMessage()});

    let currentTime=new Date();
    currentTime.setTime(currentTime.getTime());
    currentTime.setHours(24, 0, 0, 0);
    document.cookie="tipCookie=done; expires="+currentTime.toUTCString()+"; path=/";
}

function allocateMessage() {
    var priority = window.localStorage.getItem('priorityCount');
    var tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
    var num = parseInt(window.localStorage.getItem('tipCount'));
    var loop = parseInt(window.localStorage.getItem('loopCount'));

    if (tip[loop]!=null && tip[loop][num]!=null && tip[loop][num][0]!=null && tip[loop][num][1]!=null){
        document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num][0] + ' Click for more
';
        jQuery('#styleTip a').attr('target', 'helppage');
        document.getElementById('extraInfo').innerHTML = tip[loop][num][1];
        jQuery('#extraInfo a').attr('target', 'helppage');
    }
    setForNext(num, priority, tip, loop);
};

// =========================================
// Display/Hide Functions
// =========================================

function onButtonNext(){
    if (document.getElementById("goRight").style.opacity!='0'){
        hideExtra();
        setTimeout(function(){
            allocateMessage()
        }, 800);
    }
}

function onButtonPrevious(){
    if (document.getElementById("goLeft").style.opacity!='0'){
        hideExtra();
        setTimeout(function(){
            showPrevious()
        }, 800);
    }
}

var hideWithDelay;

function showExtra(){
    document.getElementById('extraInfo').classList.remove("fade-outInst");
    document.getElementById('extraInfo').classList.add("fade-in");
    hideWithDelay = setTimeout(function(){hideExtra()}, calculateDelay());
    hideWithDelay;
}

function hideExtra(){
    clearTimeout(hideWithDelay);
    document.getElementById('extraInfo').classList.remove("fade-in");
    document.getElementById('extraInfo').classList.add("fade-outInst");
}

function calculateDelay(){
    sectionHtml=document.getElementById('extraInfo').innerHTML;
    sectionText=document.getElementById('extraInfo').textContent;

    if (sectionText==null){
        return null;
    }
    if (sectionText.length==0){
        return 0;
    }

    let sum=5;

    //counting words
    var rem=sectionText;
    rem=rem.replace(/\r?\n|\r/g, "");
    var wordCount=rem.split(" ").length;

    sum += Math.ceil((wordCount-5)/3);

    if (sectionHtml.indexOf("href")>=0 && sectionHtml.indexOf("href")<sectionHtml.indexOf("lowerBarBtn")){
        sum += 5;
    }

    return sum*1000; // get Prev, Next, View All out of count, and turn into milliseconds
}

// =========================================
// Previous/Next Manipulators
// =========================================

function setForNext(num, priority, tip, loop){
    if (priority==0 && num==0 && loop==0){
        document.getElementById("goLeft").style.opacity=0;
        document.getElementById("goLeft").style.cursor="default";
    }
    else{
        document.getElementById("goLeft").style.opacity=1;
        document.getElementById("goLeft").style.cursor="pointer";
    }

    if (num>=tip[loop].length-1){
        if (parseInt(priority)==2){ //renew data
            loop++;
            let numNulls=0;
            for (let files=0; files<fileNames.length; files++){
                jQuery.ajax({
                    url: fileNames[files],
                    success: function(results){
                        let allInfo=results;
                        allInfo=allInfo.split("
");
                        allInfo.shift();    //removes title+descrription element
                        var descrpt=[];
                        for (let i=0; i<allInfo.length; i++){
                            var t=[];
                            t.push(allInfo[i].substring(0, allInfo[i].indexOf("
")));
                            var link="https://cruise.umple.org/umple/"+htmlNames[files];
                            var navbar='
<button class="lowerBarBtn" id="goLeft" onclick="onButtonPrevious()">&laquo Previous</button> View All <button class="lowerBarBtn" id="goRight" onclick="onButtonNext()">Next &raquo</button>
';
                            if (i==allInfo.length-1){
                                t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length)+navbar);
                            }
                            else{
                                t.push(allInfo[i].substring(allInfo[i].indexOf("
")+6, allInfo[i].length-11)+navbar);
                            }
                            var info = window.localStorage.getItem("tipInfo"+files);
                            let notFound = true;
                            for (let checkLoop=0; checkLoop<loop; checkloop++){ //only if none of the previous versions have that tip
                                if (info[checkLoop].includes(t)>=0){
                                    notFound = false;
                                    break;
                                }
                            }
                            if (notFound){
                                descrpt.push(t);
                            }
                        }
                        if (descrpt == null){
                            numNulls++;
                        }
                        info.push(descrpt);
                        window.localStorage.setItem('tipInfo'+files, JSON.stringify(info));
                        },
                    error: function(response){
                        descrpt.push(["couldn't access file",error]);
                    }
                });
            }
            if (numNulls == 3){ //no new additions to either files
                for (let countPriority=0; countPriority<=parseInt(priority); countPriority++){
                    var data = JSON.parse(window.localStorage.getItem('tipInfo'+countPriority));
                    data.pop();
                    window.localStorage.setItem('tipInfo'+countPriority, JSON.stringify(info));
                }
                loop--;
            }
            else{
                window.localStorage.setItem('tipCount','0');
            }
            window.localStorage.setItem('priorityCount', '0');
            window.localStorage.setItem("loopCount", loop);
        }
        else {
            window.localStorage.setItem('priorityCount', parseInt(priority)+1);
            window.localStorage.setItem('tipCount','0');
        }
    }
    else{
        window.localStorage.setItem('tipCount',num+1);
    }

    if (tip[loop]==null || tip[loop][num]==null || tip[loop][num][0]==null || tip[loop][num][1]==null){
        document.getElementById("goRight").style.opacity=0;
        document.getElementById("goRight").style.cursor="default";
    }
    else{
        document.getElementById("goRight").style.opacity=1;
        document.getElementById("goRight").style.cursor="pointer";
    }
}

function showPrevious(){
    var priority = window.localStorage.getItem('priorityCount');
    var tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
    var num = parseInt(window.localStorage.getItem('tipCount'));
    var loop = parseInt(window.localStorage.getItem('loopCount'));

    if (num-2 < 0){
        if (priority-1 >= 0 && (num === 0 || num === 1)){ //no change to tip otherwise
            priority--;
            window.localStorage.setItem('priorityCount',priority);
            tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
            if (num === 0){
                num=tip[loop].length-2;
            }
            else {
                num=tip[loop].length-1;
            }
            window.localStorage.setItem('tipCount',num);

            document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num][0] + ' Click for more
';
            jQuery('#styleTip a').attr('target', 'helppage');
            document.getElementById('extraInfo').innerHTML = tip[loop][num][1];
            jQuery('#extraInfo a').attr('target', 'helppage');

            setForNext(num, priority, tip, loop);
        }
        else if (loop-1 >= 0 && priority == 0){ //change versions
            priority=2;
            window.localStorage.setItem('priorityCount',priority);
            tip = JSON.parse(window.localStorage.getItem('tipInfo'+priority));
            loop--;
            window.localStorage.setItem('loopCount',loop);

            if (num === 0){
                num=tip[loop].length-2;
            }
            else {
                num=tip[loop].length-1;
            }
            window.localStorage.setItem('tipCount',num);

            document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num][0] + ' Click for more
';
            jQuery('#styleTip a').attr('target', 'helppage');
            document.getElementById('extraInfo').innerHTML = tip[loop][num][1];
            jQuery('#extraInfo a').attr('target', 'helppage');

            setForNext(num, priority, tip, loop);
        }
    }
    else{
        document.getElementById('extraInfo').style.visibility = 'hidden';
        document.getElementById('styleTip').innerHTML = "Tip: "+tip[loop][num-2][0] + ' Click for more
';
        jQuery('#styleTip a').attr('target', 'helppage');
        document.getElementById('extraInfo').innerHTML = tip[loop][num-2][1];
        jQuery('#extraInfo a').attr('target', 'helppage');

        window.localStorage.setItem('tipCount',num-2);

        setForNext(num-2, priority, tip, loop);
    }
}<|endoftext|>


<commit_before>/**
 * @copyright Devexperts
 *
 * @requires DX
 * @requires DX.Dom
 * @requires DX.Tmpl
 * @requires DX.Date
 * @requires DX.Event
 * @namespace
 */
var Datepicker = (function(DX, window, document, undefined) {
	'use strict';

	var dom = DX.Dom,
		dateUtil = DX.Date,
		event = DX.Event,
		CN_DATEPICKER = 'datepicker',
		CN_DATEPICKER_INPUT = CN_DATEPICKER + '--input',
		CN_DROPDOWN = 'dropDown',
		CN_DROPDOWN_CALENDAR = CN_DROPDOWN + '--calendar',
		CN_BUTTON = 'button',
		CN_CALENDAR = 'calendar',
		M_SELECTED = 'selected',
		M_DISABLED = 'disabled',
		M_CURRENT_MONTH = 'currentMonth',
		CN_CALENDAR_SWITCHER = CN_CALENDAR + '--switcher',
		CN_CALENDAR_SWITCHER_NEXT = CN_CALENDAR_SWITCHER + '-next',
		CN_CALENDAR_SWITCHER_PREV = CN_CALENDAR_SWITCHER + '-prev',
		CN_CALENDAR_INFO = CN_CALENDAR + '--info',
		CN_CALENDAR_HEADER = CN_CALENDAR + '--header',
		CN_CALENDAR_DATES = CN_CALENDAR + '--dates',
		CN_CALENDAR_MONTH = CN_CALENDAR + '--month',
		CN_CALENDAR_MONTH_CURRENT = CN_CALENDAR_MONTH + '-current',
		CN_CALENDAR_YEAR = CN_CALENDAR + '--year',
		A_MAX_DATE = 'max',
		A_MIN_DATE = 'min',
		defaults = {
			CONTAINER_INNER_TMPL: [
				'',
				'<button type="button" class="button">{%= BUTTON_INNER_TMPL %}</button>'
			].join(''),
			BUTTON_INNER_TMPL : 'Show Calendar',
			TMPL_DROPDOWN_INNER: [
				'

'
			].join(''),
			NEXT_BUTTON_TMPL: 'next',
			PREV_BUTTON_TMPL: 'prev',
			TMPL_CALENDAR: [
				'
',
				'<button class="' + [CN_BUTTON, CN_CALENDAR_SWITCHER, CN_CALENDAR_SWITCHER_PREV].join(' ') +
				'">{%= PREV_BUTTON_TMPL %}</button>',
				'',
				'',
				'<button class="' + [CN_BUTTON, CN_CALENDAR_SWITCHER, CN_CALENDAR_SWITCHER_NEXT].join(' ') +
				'">{%= NEXT_BUTTON_TMPL %}</button>',
				'
',
				'

',
				'

'
			].join('')
		};

	function createWidget(input, config) {
		var parent = dom.getParent(input),
			container = dom.createElement('span', {
				className: CN_DATEPICKER,
				innerHTML: DX.Tmpl.process(config.CONTAINER_INNER_TMPL, config)
			}),
			inputWrapper = container.querySelector('.' + CN_DATEPICKER_INPUT);

		input.type = 'text';
		parent.insertBefore(container, input);
		inputWrapper.appendChild(input);

		return container;
	}

	function fireDateChanged(input) {
		event.trigger(input, Datepicker.E_CHANGED);
	}

	function isSelectableDate(dateObject) {
		return dateObject.modifiers.indexOf(M_DISABLED) === -1 &&
			dateObject.modifiers.indexOf(M_CURRENT_MONTH) > -1;
	}

	function isDateInRange(date, config) {
		var firstAvailableDate = new Date(config.min),
			lastAvailableDate = new Date(config.max),
			isGreaterOrEqual = true,
			isLessOrEqual = true;

		if (config.min) {
			isGreaterOrEqual = dateUtil.isGreater(date, firstAvailableDate) ||
				dateUtil.isEqual(date, firstAvailableDate);
		}

		if (config.max) {
			isLessOrEqual = dateUtil.isLess(date, lastAvailableDate) ||
				dateUtil.isEqual(date, lastAvailableDate);
		}

		return isLessOrEqual && isGreaterOrEqual;
	}

	/**
	 * Creates new datepicker
	 * @constructor Datepicker
	 * @param {Node|Element} input
	 * @param {Object} customConfig
	 */
	return function Datepicker(input, customConfig) {
		var dropdown, calendar, elements, container, selectedDate, constraints,
			config,
			dateFormatter = dateUtil.toShortISOString.bind(dateUtil),
			dateParser = function(value) {
				return new Date(value);
			};

		function init() {
			config = Object.assign({}, defaults, customConfig);
			constraints = constraints ? constraints : {};
			updateConstraints();
			initAppearance();
			initDropdown();
			initCalendar();
			initElements();
			initListeners();

			DX.Event.trigger(input, Datepicker.E_CREATED, {
				detail: {
					block: container,
					eventTarget: input
				}
			});
		}

		function updateConstraints() {
			var minDate = input.getAttribute(A_MIN_DATE),
				maxDate = input.getAttribute(A_MAX_DATE);

			constraints.min = minDate ? minDate : undefined;
			constraints.max = maxDate ? maxDate : undefined;
		}

		function initAppearance() {
			container = createWidget(input, config);
		}

		function initDropdown() {
			dropdown = new DropDown(input, {
				innerTmpl: config.TMPL_DROPDOWN_INNER,
				modifiers: CN_DATEPICKER
			});
		}

		function initCalendar() {
			var calendarBlock = dropdown.getBlock().querySelector('.' + CN_DROPDOWN_CALENDAR);

			calendar = new Calendar(calendarBlock, {
				tmpl: DX.Tmpl.process(config.TMPL_CALENDAR, config)
			});

			calendar.registerProcessor(selectedDateProcessor);
			calendar.registerProcessor(disabledDatesProcessor);
		}

		function initElements() {
			var dropdownBlock = dropdown.getBlock();

			elements = {
				input: input,
				container: container,
				dropDownOpener: container.querySelector('.button'),
				calendarContainer: dropdownBlock.querySelector('.' + CN_DROPDOWN_CALENDAR),
				nextSwitcher: dropdownBlock.querySelector('.' + CN_CALENDAR_SWITCHER_NEXT),
				prevSwitcher: dropdownBlock.querySelector('.' + CN_CALENDAR_SWITCHER_PREV)
			};
		}

		function initListeners() {
			elements.dropDownOpener.addEventListener(event.CLICK, function() {
				dropdown.show();
			});
			elements.prevSwitcher.addEventListener(event.CLICK, function() {
				calendar.drawPrevMonth();
				toggleSwitchersState();
			});
			elements.nextSwitcher.addEventListener(event.CLICK, function() {
				calendar.drawNextMonth();
				toggleSwitchersState();
			});

			calendar.getEventTarget().addEventListener(Calendar.E_DAY_SELECTED, selectDate);

			dropdown.getEventTarget().addEventListener(DropDown.E_SHOWN, setDefaultValue);

			input.addEventListener(Datepicker.E_UPDATE_CONSTRAINTS, updateConstraints);
			input.addEventListener(event.BLUR, function() {
				fireDateChanged(input);
			});
		}

		function setDefaultValue() {
			var valueDate = dateParser(input.value),
				date;

			if (dateUtil.isDate(valueDate)) {
				date = valueDate;
				selectedDate = date;
			} else {
				date = new Date();
			}

			calendar.drawMonth(date);
			toggleSwitchersState();
		}

		function toggleSwitchersState() {
			var currentMonth = calendar.getDate(),
				lastAvailableMonth = new Date(constraints.max),
				firstAvailableMonth = new Date(constraints.min);

			if (dateUtil.isDate(lastAvailableMonth)) {
				elements.nextSwitcher.disabled = dateUtil.isEqualMonth(currentMonth, lastAvailableMonth) ||
					dateUtil.isGreaterMonth(currentMonth, lastAvailableMonth);
			} else {
				elements.nextSwitcher.disabled = false;
			}

			if (dateUtil.isDate(firstAvailableMonth)) {
				elements.prevSwitcher.disabled = dateUtil.isEqualMonth(currentMonth, firstAvailableMonth) ||
					dateUtil.isLessMonth(currentMonth, firstAvailableMonth);
			} else {
				elements.prevSwitcher.disabled = false;
			}
		}

		function selectDate(e) {
			var dayModel = e.detail.dayModel;

			if (isSelectableDate(dayModel)) {
				selectedDate = dayModel.date;
				input.value = dateFormatter(selectedDate);
				calendar.update();
				dropdown.hide();
				fireDateChanged(input);
			}
		}

		function selectedDateProcessor(dateObject) {
			if (selectedDate && dateUtil.isEqual(dateObject.date, selectedDate)) {
				dateObject.modifiers.push(M_SELECTED);
			}
		}

		function disabledDatesProcessor(dateObject) {
			var date = dateObject.date;

			if (!isDateInRange(date, constraints)) {
				dateObject.modifiers.push(M_DISABLED);
			}
		}

		init();
		/**
		 * Gets HTMLNode containing dropdown
		 * @method getBlock
		 * @returns {Node}
		 */
		this.getBlock = function() {
			return container;
		};
		/**
		 * Gets element which listens to events
		 * @method getEventTarget
		 * @returns {Node}
		 */
		this.getEventTarget = function() {
			return input;
		};
		/**
		 * Sets custom date formatter
		 * @method setDateFormatter
		 * @param {Function} customDateFormatter
		 */
		this.setDateFormatter = function(customDateFormatter) {
			dateFormatter = customDateFormatter;
		};
		/**
		 * Sets custom date parser
		 * @method setDateParser
		 * @param {Function} customDateParser
		 */
		this.setDateParser = function(customDateParser) {
			dateParser = customDateParser;
		};
	};
})(DX, window, document);
/** @constant
 * @type {string}
 * @default
 * @memberof Datepicker
 */
Datepicker.E_CREATED = 'datepicker:created';
/** @constant
 * @type {string}
 * @default
 * @memberof Datepicker
 */
Datepicker.E_CHANGED = 'datepicker:changed';
/** @constant
 * @type {string}
 * @default
 * @memberof Datepicker
 */
Datepicker.E_UPDATE_CONSTRAINTS = 'datepicker:updateconstraints';
<commit_msg>remove window, document, undefined<commit_after>/**
 * @copyright Devexperts
 *
 * @requires DX
 * @requires DX.Dom
 * @requires DX.Tmpl
 * @requires DX.Date
 * @requires DX.Event
 * @namespace
 */
var Datepicker = (function(DX) {
	'use strict';

	var dom = DX.Dom,
		dateUtil = DX.Date,
		event = DX.Event,
		CN_DATEPICKER = 'datepicker',
		CN_DATEPICKER_INPUT = CN_DATEPICKER + '--input',
		CN_DROPDOWN = 'dropDown',
		CN_DROPDOWN_CALENDAR = CN_DROPDOWN + '--calendar',
		CN_BUTTON = 'button',
		CN_CALENDAR = 'calendar',
		M_SELECTED = 'selected',
		M_DISABLED = 'disabled',
		M_CURRENT_MONTH = 'currentMonth',
		CN_CALENDAR_SWITCHER = CN_CALENDAR + '--switcher',
		CN_CALENDAR_SWITCHER_NEXT = CN_CALENDAR_SWITCHER + '-next',
		CN_CALENDAR_SWITCHER_PREV = CN_CALENDAR_SWITCHER + '-prev',
		CN_CALENDAR_INFO = CN_CALENDAR + '--info',
		CN_CALENDAR_HEADER = CN_CALENDAR + '--header',
		CN_CALENDAR_DATES = CN_CALENDAR + '--dates',
		CN_CALENDAR_MONTH = CN_CALENDAR + '--month',
		CN_CALENDAR_MONTH_CURRENT = CN_CALENDAR_MONTH + '-current',
		CN_CALENDAR_YEAR = CN_CALENDAR + '--year',
		A_MAX_DATE = 'max',
		A_MIN_DATE = 'min',
		defaults = {
			CONTAINER_INNER_TMPL: [
				'',
				'<button type="button" class="button">{%= BUTTON_INNER_TMPL %}</button>'
			].join(''),
			BUTTON_INNER_TMPL : 'Show Calendar',
			TMPL_DROPDOWN_INNER: [
				'

'
			].join(''),
			NEXT_BUTTON_TMPL: 'next',
			PREV_BUTTON_TMPL: 'prev',
			TMPL_CALENDAR: [
				'
',
				'<button class="' + [CN_BUTTON, CN_CALENDAR_SWITCHER, CN_CALENDAR_SWITCHER_PREV].join(' ') +
				'">{%= PREV_BUTTON_TMPL %}</button>',
				'',
				'',
				'<button class="' + [CN_BUTTON, CN_CALENDAR_SWITCHER, CN_CALENDAR_SWITCHER_NEXT].join(' ') +
				'">{%= NEXT_BUTTON_TMPL %}</button>',
				'
',
				'

',
				'

'
			].join('')
		};

	function createWidget(input, config) {
		var parent = dom.getParent(input),
			container = dom.createElement('span', {
				className: CN_DATEPICKER,
				innerHTML: DX.Tmpl.process(config.CONTAINER_INNER_TMPL, config)
			}),
			inputWrapper = container.querySelector('.' + CN_DATEPICKER_INPUT);

		input.type = 'text';
		parent.insertBefore(container, input);
		inputWrapper.appendChild(input);

		return container;
	}

	function fireDateChanged(input) {
		event.trigger(input, Datepicker.E_CHANGED);
	}

	function isSelectableDate(dateObject) {
		return dateObject.modifiers.indexOf(M_DISABLED) === -1 &&
			dateObject.modifiers.indexOf(M_CURRENT_MONTH) > -1;
	}

	function isDateInRange(date, config) {
		var firstAvailableDate = new Date(config.min),
			lastAvailableDate = new Date(config.max),
			isGreaterOrEqual = true,
			isLessOrEqual = true;

		if (config.min) {
			isGreaterOrEqual = dateUtil.isGreater(date, firstAvailableDate) ||
				dateUtil.isEqual(date, firstAvailableDate);
		}

		if (config.max) {
			isLessOrEqual = dateUtil.isLess(date, lastAvailableDate) ||
				dateUtil.isEqual(date, lastAvailableDate);
		}

		return isLessOrEqual && isGreaterOrEqual;
	}

	/**
	 * Creates new datepicker
	 * @constructor Datepicker
	 * @param {Node|Element} input
	 * @param {Object} customConfig
	 */
	return function Datepicker(input, customConfig) {
		var dropdown, calendar, elements, container, selectedDate, constraints,
			config,
			dateFormatter = dateUtil.toShortISOString.bind(dateUtil),
			dateParser = function(value) {
				return new Date(value);
			};

		function init() {
			config = Object.assign({}, defaults, customConfig);
			constraints = constraints ? constraints : {};
			updateConstraints();
			initAppearance();
			initDropdown();
			initCalendar();
			initElements();
			initListeners();

			DX.Event.trigger(input, Datepicker.E_CREATED, {
				detail: {
					block: container,
					eventTarget: input
				}
			});
		}

		function updateConstraints() {
			var minDate = input.getAttribute(A_MIN_DATE),
				maxDate = input.getAttribute(A_MAX_DATE);

			constraints.min = minDate ? minDate : undefined;
			constraints.max = maxDate ? maxDate : undefined;
		}

		function initAppearance() {
			container = createWidget(input, config);
		}

		function initDropdown() {
			dropdown = new DropDown(input, {
				innerTmpl: config.TMPL_DROPDOWN_INNER,
				modifiers: CN_DATEPICKER
			});
		}

		function initCalendar() {
			var calendarBlock = dropdown.getBlock().querySelector('.' + CN_DROPDOWN_CALENDAR);

			calendar = new Calendar(calendarBlock, {
				tmpl: DX.Tmpl.process(config.TMPL_CALENDAR, config)
			});

			calendar.registerProcessor(selectedDateProcessor);
			calendar.registerProcessor(disabledDatesProcessor);
		}

		function initElements() {
			var dropdownBlock = dropdown.getBlock();

			elements = {
				input: input,
				container: container,
				dropDownOpener: container.querySelector('.button'),
				calendarContainer: dropdownBlock.querySelector('.' + CN_DROPDOWN_CALENDAR),
				nextSwitcher: dropdownBlock.querySelector('.' + CN_CALENDAR_SWITCHER_NEXT),
				prevSwitcher: dropdownBlock.querySelector('.' + CN_CALENDAR_SWITCHER_PREV)
			};
		}

		function initListeners() {
			elements.dropDownOpener.addEventListener(event.CLICK, function() {
				dropdown.show();
			});
			elements.prevSwitcher.addEventListener(event.CLICK, function() {
				calendar.drawPrevMonth();
				toggleSwitchersState();
			});
			elements.nextSwitcher.addEventListener(event.CLICK, function() {
				calendar.drawNextMonth();
				toggleSwitchersState();
			});

			calendar.getEventTarget().addEventListener(Calendar.E_DAY_SELECTED, selectDate);

			dropdown.getEventTarget().addEventListener(DropDown.E_SHOWN, setDefaultValue);

			input.addEventListener(Datepicker.E_UPDATE_CONSTRAINTS, updateConstraints);
			input.addEventListener(event.BLUR, function() {
				fireDateChanged(input);
			});
		}

		function setDefaultValue() {
			var valueDate = dateParser(input.value),
				date;

			if (dateUtil.isDate(valueDate)) {
				date = valueDate;
				selectedDate = date;
			} else {
				date = new Date();
			}

			calendar.drawMonth(date);
			toggleSwitchersState();
		}

		function toggleSwitchersState() {
			var currentMonth = calendar.getDate(),
				lastAvailableMonth = new Date(constraints.max),
				firstAvailableMonth = new Date(constraints.min);

			if (dateUtil.isDate(lastAvailableMonth)) {
				elements.nextSwitcher.disabled = dateUtil.isEqualMonth(currentMonth, lastAvailableMonth) ||
					dateUtil.isGreaterMonth(currentMonth, lastAvailableMonth);
			} else {
				elements.nextSwitcher.disabled = false;
			}

			if (dateUtil.isDate(firstAvailableMonth)) {
				elements.prevSwitcher.disabled = dateUtil.isEqualMonth(currentMonth, firstAvailableMonth) ||
					dateUtil.isLessMonth(currentMonth, firstAvailableMonth);
			} else {
				elements.prevSwitcher.disabled = false;
			}
		}

		function selectDate(e) {
			var dayModel = e.detail.dayModel;

			if (isSelectableDate(dayModel)) {
				selectedDate = dayModel.date;
				input.value = dateFormatter(selectedDate);
				calendar.update();
				dropdown.hide();
				fireDateChanged(input);
			}
		}

		function selectedDateProcessor(dateObject) {
			if (selectedDate && dateUtil.isEqual(dateObject.date, selectedDate)) {
				dateObject.modifiers.push(M_SELECTED);
			}
		}

		function disabledDatesProcessor(dateObject) {
			var date = dateObject.date;

			if (!isDateInRange(date, constraints)) {
				dateObject.modifiers.push(M_DISABLED);
			}
		}

		init();
		/**
		 * Gets HTMLNode containing dropdown
		 * @method getBlock
		 * @returns {Node}
		 */
		this.getBlock = function() {
			return container;
		};
		/**
		 * Gets element which listens to events
		 * @method getEventTarget
		 * @returns {Node}
		 */
		this.getEventTarget = function() {
			return input;
		};
		/**
		 * Sets custom date formatter
		 * @method setDateFormatter
		 * @param {Function} customDateFormatter
		 */
		this.setDateFormatter = function(customDateFormatter) {
			dateFormatter = customDateFormatter;
		};
		/**
		 * Sets custom date parser
		 * @method setDateParser
		 * @param {Function} customDateParser
		 */
		this.setDateParser = function(customDateParser) {
			dateParser = customDateParser;
		};
	};
})(DX);
/** @constant
 * @type {string}
 * @default
 * @memberof Datepicker
 */
Datepicker.E_CREATED = 'datepicker:created';
/** @constant
 * @type {string}
 * @default
 * @memberof Datepicker
 */
Datepicker.E_CHANGED = 'datepicker:changed';
/** @constant
 * @type {string}
 * @default
 * @memberof Datepicker
 */
Datepicker.E_UPDATE_CONSTRAINTS = 'datepicker:updateconstraints';
<|endoftext|>


<commit_before>// Copyright 2002-2012, University of Colorado

/**
 * A node for the phet-scene scene graph. Supports only tree-style graphs at the moment.
 * Handles multiple layers with assorted types (canvas, svg, DOM, etc.), and bounds
 * computation
 *
 * TODO: investigate handling DAGs (directed acyclic graphs)
 *
 * @author Jonathan Olson
 */

var phet = phet || {};
phet.scene = phet.scene || {};

(function(){
    var Bounds2 = phet.math.Bounds2;
    var Shape = phet.scene.Shape;

    // TODO: consider an args-style constructor here!
    phet.scene.Node = function() {
        this.visible = true;

        // type of layer to be created for content under this node.
        // if non-null, this node is a layer root, and layerType should be a layer constructor function
        this.layerType = null;

        // This node and all children will be clipped by this shape (in addition to any other clipping shapes).
        // The shape should be in the local coordinate frame
        this.clipShape = null;

        this.children = [];
        this.transform = new phet.math.Transform3();
        this.parent = null;

        // layer-specific data, currently updated in the rebuildLayers step
        this._layerBeforeRender = null; // layer to swap to before rendering this node
        this._layerAfterRender = null; // layer to swap to after rendering this node
        this._layerReference = null; // stores a reference to this node's corresponding layer (the one used to render it's self content)

        // bounds handling
        this._bounds = Bounds2.NOTHING; // for this node and its children, in "parent" coordinates
        this._selfBounds = Bounds2.NOTHING; // just for this node, in "local" coordinates
        this._childBounds = Bounds2.NOTHING; // just for children, in "local" coordinates
        this._boundsDirty = true;
        this._selfBoundsDirty = true;
        this._childBoundsDirty = true;

        // dirty region handling
        this._paintDirty = false;
        this._childPaintDirty = false;

        // shape used for rendering
        this._shape = null;
        // fill/stroke for shapes
        this.stroke = null;
        this.fill = null;

        this._lineDrawingStyles = new Shape.LineStyles();
    }

    var Node = phet.scene.Node;
    var Matrix3 = phet.math.Matrix3;

    Node.prototype = {
        constructor: Node,

        enterState: function( state ) {
            // switch layers if needed
            if( this._layerBeforeRender ) {
                state.switchToLayer( this._layerBeforeRender );
            }

            // apply this node's transform
            if ( !this.transform.isIdentity() ) {
                // TODO: consider a stack-based model for transforms?
                state.applyTransformationMatrix( this.transform.getMatrix() );
            }

            if( this.clipShape ) {
                state.pushClipShape( this.clipShape );
            }
        },

        exitState: function( state ) {
            if( this.clipShape ) {
                state.popClipShape();
            }

            // apply the inverse of this node's transform
            if ( !this.transform.isIdentity() ) {
                state.applyTransformationMatrix( this.transform.getInverse() );
            }

            // switch layers if needed
            if( this._layerAfterRender ) {
                state.switchToLayer( this._layerAfterRender );
            }
        },

        // Renders this node and all of its children. NOTE: this is not the only way rendering happens, but it is the clearest expression of the order of operations
        render: function( state ) {
            this.enterState( state );

            if( this.visible ) {
                this.renderSelf( state );
                this.renderChildren( state );
            }

            this.exitState( state );
        },

        // override to render typical leaf behavior (although possible to use for non-leaf nodes also)
        renderSelf: function ( state ) {

            // by default, render a shape if it exists
            if( this.hasShape() ) {
                if( state.isCanvasState() ) {
                    var layer = state.layer;
                    var context = layer.context;

                    // TODO: fill/stroke delay optimizations?
                    context.beginPath();
                    this._shape.writeToContext( context );

                    if( this.fill ) {
                        layer.setFillStyle( this.fill );
                        context.fill();
                    }
                    if( this.stroke ) {
                        layer.setStrokeStyle( this.stroke );
                        context.stroke();
                    }
                } else {
                    throw new Error( 'layer type shape rendering not implemented' );
                }
            }
        },

        // override to run before rendering of this node is done
        preRender: function ( state ) {

        },

        // override to run just after this node and its children are rendered
        postRender: function ( state ) {

        },

        renderChildren: function ( state ) {
            _.each( this.children, function( child ) {
                child.render( state );
            } );
        },

        addChild: function ( node ) {
            phet.assert( node !== null && node !== undefined );
            if ( this.isChild( node ) ) {
                return;
            }
            if ( node.parent !== null ) {
                node.parent.removeChild( node );
            }
            node.parent = this;
            this.children.push( node );

            this.invalidateBounds();
            this._childBoundsDirty = true;
        },

        removeChild: function ( node ) {
            phet.assert( this.isChild( node ) );

            this.markDirtyRegion( node.getBounds() );

            node.parent = null;
            this.children.splice( this.children.indexOf( node ), 1 );

            this.invalidateBounds();
        },

        hasParent: function () {
            return this.parent !== null && this.parent !== undefined;
        },

        // remove this node from its parent
        detach: function () {
            if ( this.hasParent() ) {
                this.parent.removeChild( this );
            }
        },

        isChild: function ( potentialChild ) {
            phet.assert( (potentialChild.parent === this ) === (this.children.indexOf( potentialChild ) != -1) );
            return potentialChild.parent === this;
        },

        // does this node have an associated layerType (are the contents of this node rendered separately from its ancestors)
        isLayerRoot: function() {
            return this.layerType != null;
        },

        // the first layer associated with this node (can be multiple layers if children of this node are also layer roots)
        getLayer: function() {
            phet.assert( this.isLayerRoot() );
            return this._layerBeforeRender;
        },

        translate: function ( x, y ) {
            this.appendMatrix( Matrix3.translation( x, y ) );
            this.invalidateBounds();
        },

        // scale( s ) is also supported
        scale: function ( x, y ) {
            this.appendMatrix( Matrix3.scaling( x, y ) );
            this.invalidateBounds();
        },

        rotate: function ( angle ) {
            this.appendMatrix( Matrix3.rotation2( angle ) );
            this.invalidateBounds();
        },

        // append a transformation matrix to our local transform
        appendMatrix: function( matrix ) {
            // invalidate paint TODO improve methods for this
            this.markDirtyRegion( this.parentToLocalBounds( this._bounds ) );

            this.transform.append( matrix );
        },

        // bounds assumed to be in the local coordinate frame, below this node's transform
        markDirtyRegion: function( bounds ) {
            var globalBounds = this.localToGlobalBounds( bounds );
            _.each( this.findDescendantLayers(), function( layer ) {
                layer.markDirtyRegion( globalBounds );
            } );
        },

        // the bounds for content in renderSelf(), in "local" coordinates
        getSelfBounds: function() {
            return this._selfBounds;
        },

        // the bounds for content in render(), in "parent" coordinates
        getBounds: function() {
            this.validateBounds();
            return this._bounds;
        },

        // ensure that cached bounds stored on this node (and all children) are accurate
        validateBounds: function() {
            if( this._selfBoundsDirty ) {
                this._selfBoundsDirty = false;

                // if our self bounds changed, make sure to paint the area where our new bounds are
                this.markDirtyRegion( this._selfBounds );
            }

            // validate bounds of children if necessary
            if( this._childBoundsDirty ) {
                // have each child validate their own bounds
                _.each( this.children, function( child ) {
                    child.validateBounds();
                } );

                // and recompute our _childBounds
                this._childBounds = Bounds2.NOTHING;
                var that = this;
                _.each( this.children, function( child ) {
                    that._childBounds = that._childBounds.union( child._bounds );
                } );

                this._childBoundsDirty = false;
            }

            // TODO: layout here?

            if( this._boundsDirty ) {
                var oldBounds = this._bounds;

                var that = this;

                var newBounds = this.localToParentBounds( this._selfBounds ).union( that.localToParentBounds( this._childBounds ) );

                if( !newBounds.equals( oldBounds ) ) {
                    this._bounds = newBounds;

                    if( this.parent != null ) {
                        this.parent.invalidateBounds();
                    }
                    // this.markDirtyRegion( this.parentToLocalBounds( oldBounds ) );

                    // TODO: fire off event listeners?
                }

                this._boundsDirty = false;
            }
        },

        // find the layer to which this node will be rendered
        findLayer: function() {
            return this._layerReference;
        },

        // find all layers for which this node (and all its descendants) will be rendered
        findDescendantLayers: function() {
            var firstLayer = this.findLayer();

            if( firstLayer == null ) {
                return [];
            }

            // run a node out to the last child until we reach a leaf, to find the last layer we will render to in this subtree
            var node = this;
            while( node.children.length != 0 ) {
                node = _.last( node.children );
            }
            var lastLayer = node.findLayer();

            // collect all layers between the first and last layers
            var layers = [firstLayer];
            var layer = firstLayer;
            while( layer != lastLayer && layer.nextLayer != null ) {
                layer = layer.nextLayer;
                layers.push( layer );
            }
            return layers;
        },

        // mark the bounds of this node as invalid, so it is recomputed before it is accessed again
        invalidateBounds: function() {
            this._boundsDirty = true;

            // and set flags for all ancestors
            var node = this.parent;
            while( node != null ) {
                // TODO: for performance, consider cutting this once we detect a node with this as true
                node._childBoundsDirty = true;
                node = node.parent;
            }
        },

        // sets the bounds of the content rendered by renderSelf()
        setSelfBounds: function( newBounds ) {
            // if these bounds are different than current self bounds
            if( !this._selfBounds.equals( newBounds ) ) {
                // mark the old region to be repainted
                this.markDirtyRegion( this._selfBounds );

                // set repaint flags
                this._selfBoundsDirty = true;
                this.invalidateBounds();

                // record the new bounds
                this._selfBounds = newBounds;
            }
        },

        // checking for whether a point (in parent coordinates) is contained in this sub-tree
        containsPoint: function( point ) {
            // update bounds for pruning
            this.validateBounds();

            // bail quickly if this doesn't hit our computed bounds
            if( !this._bounds.containsPoint( point ) ) { return false; }

            // point in the local coordinate frame. computed after the main bounds check, so we can bail out there efficiently
            var localPoint = this.transform.inversePosition2( point );

            // check children first, since they are rendered later
            if( this.children.length > 0 && this._childBounds.containsPoint( localPoint ) ) {

                // manual iteration here so we can return directly, and so we can iterate backwards (last node is in front)
                for( var i = this.children.length - 1; i >= 0; i-- ) {
                    var child = this.children[i];

                    // the child will have the point in its parent's coordinate frame (i.e. this node's frame)
                    if( child.containsPoint( localPoint ) ) {
                        return true;
                    }
                }
            }

            // didn't hit our children, so check ourself as a last resort
            if( this._selfBounds.containsPoint( point ) ) {
                return this.containsPointSelf( point );
            }
        },

        // override for computation of whether a point is inside the content rendered in renderSelf
        containsPointSelf: function( point ) {
            return false;
        },

        hasShape: function() {
            return this._shape != null;
        },

        // sets the shape drawn, or null to remove the shape
        setShape: function( shape ) {
            this._shape = shape;

            this.setSelfBounds( shape.computeBounds( ) );
        },

        getShape: function() {
            return this._shape;
        },

        // returns a list of ancestors of this node, with the root first
        ancestors: function() {
            return this.parent ? this.parent.pathToRoot() : [];
        },

        // like ancestors(), but includes the current node as well
        pathToRoot: function() {
            var result = [];
            var node = this;

            while( node != null ) {
                result.unshift( node );
                node = node.parent;
            }

            return result;
        },

        /*---------------------------------------------------------------------------*
        * Coordinate transform methods
        *----------------------------------------------------------------------------*/

        // apply this node's transform to the point
        localToParentPoint: function( point ) {
            return this.transform.transformPosition2( point );
        },

        localToParentBounds: function( bounds ) {
            return this.transform.transformBounds2( bounds );
        },

        // apply the inverse of this node's transform to the point
        parentToLocalPoint: function( point ) {
            return this.transform.inversePosition2( point );
        },

        parentToLocalBounds: function( bounds ) {
            return this.transform.inverseBounds2( bounds );
        },

        // apply this node's transform (and then all of its parents' transforms) to the point
        localToGlobalPoint: function( point ) {
            var node = this;
            while( node != null ) {
                point = node.transform.transformPosition2( point );
                node = node.parent;
            }
            return point;
        },

        localToGlobalBounds: function( bounds ) {
            var node = this;
            while( node != null ) {
                bounds = node.transform.transformBounds2( bounds );
                node = node.parent;
            }
            return bounds;
        },

        globalToLocalPoint: function( point ) {
            var node = this;

            // we need to apply the transformations in the reverse order, so we temporarily store them
            var transforms = [];
            while( node != null ) {
                transforms.push( node.transform );
                node = node.parent;
            }

            // iterate from the back forwards (from the root node to here)
            for( var i = transforms.length - 1; i >=0; i-- ) {
                point = transforms[i].inversePosition2( point );
            }
            return point;
        },

        globalToLocalBounds: function( bounds ) {
            var node = this;

            // we need to apply the transformations in the reverse order, so we temporarily store them
            var transforms = [];
            while( node != null ) {
                transforms.push( node.transform );
                node = node.parent;
            }

            // iterate from the back forwards (from the root node to here)
            for( var i = transforms.length - 1; i >=0; i-- ) {
                bounds = transforms[i].inverseBounds2( bounds );
            }
            return bounds;
        }
    };
})();<commit_msg>Adding children withing bounds checks<commit_after>// Copyright 2002-2012, University of Colorado

/**
 * A node for the phet-scene scene graph. Supports only tree-style graphs at the moment.
 * Handles multiple layers with assorted types (canvas, svg, DOM, etc.), and bounds
 * computation
 *
 * TODO: investigate handling DAGs (directed acyclic graphs)
 *
 * @author Jonathan Olson
 */

var phet = phet || {};
phet.scene = phet.scene || {};

(function(){
    var Bounds2 = phet.math.Bounds2;
    var Shape = phet.scene.Shape;

    // TODO: consider an args-style constructor here!
    phet.scene.Node = function() {
        this.visible = true;

        // type of layer to be created for content under this node.
        // if non-null, this node is a layer root, and layerType should be a layer constructor function
        this.layerType = null;

        // This node and all children will be clipped by this shape (in addition to any other clipping shapes).
        // The shape should be in the local coordinate frame
        this.clipShape = null;

        this.children = [];
        this.transform = new phet.math.Transform3();
        this.parent = null;

        // layer-specific data, currently updated in the rebuildLayers step
        this._layerBeforeRender = null; // layer to swap to before rendering this node
        this._layerAfterRender = null; // layer to swap to after rendering this node
        this._layerReference = null; // stores a reference to this node's corresponding layer (the one used to render it's self content)

        // bounds handling
        this._bounds = Bounds2.NOTHING; // for this node and its children, in "parent" coordinates
        this._selfBounds = Bounds2.NOTHING; // just for this node, in "local" coordinates
        this._childBounds = Bounds2.NOTHING; // just for children, in "local" coordinates
        this._boundsDirty = true;
        this._selfBoundsDirty = true;
        this._childBoundsDirty = true;

        // dirty region handling
        this._paintDirty = false;
        this._childPaintDirty = false;

        // shape used for rendering
        this._shape = null;
        // fill/stroke for shapes
        this.stroke = null;
        this.fill = null;

        this._lineDrawingStyles = new Shape.LineStyles();
    }

    var Node = phet.scene.Node;
    var Matrix3 = phet.math.Matrix3;

    Node.prototype = {
        constructor: Node,

        enterState: function( state ) {
            // switch layers if needed
            if( this._layerBeforeRender ) {
                state.switchToLayer( this._layerBeforeRender );
            }

            // apply this node's transform
            if ( !this.transform.isIdentity() ) {
                // TODO: consider a stack-based model for transforms?
                state.applyTransformationMatrix( this.transform.getMatrix() );
            }

            if( this.clipShape ) {
                state.pushClipShape( this.clipShape );
            }
        },

        exitState: function( state ) {
            if( this.clipShape ) {
                state.popClipShape();
            }

            // apply the inverse of this node's transform
            if ( !this.transform.isIdentity() ) {
                state.applyTransformationMatrix( this.transform.getInverse() );
            }

            // switch layers if needed
            if( this._layerAfterRender ) {
                state.switchToLayer( this._layerAfterRender );
            }
        },

        // Renders this node and all of its children. NOTE: this is not the only way rendering happens, but it is the clearest expression of the order of operations
        render: function( state ) {
            this.enterState( state );

            if( this.visible ) {
                this.renderSelf( state );
                this.renderChildren( state );
            }

            this.exitState( state );
        },

        // override to render typical leaf behavior (although possible to use for non-leaf nodes also)
        renderSelf: function ( state ) {

            // by default, render a shape if it exists
            if( this.hasShape() ) {
                if( state.isCanvasState() ) {
                    var layer = state.layer;
                    var context = layer.context;

                    // TODO: fill/stroke delay optimizations?
                    context.beginPath();
                    this._shape.writeToContext( context );

                    if( this.fill ) {
                        layer.setFillStyle( this.fill );
                        context.fill();
                    }
                    if( this.stroke ) {
                        layer.setStrokeStyle( this.stroke );
                        context.stroke();
                    }
                } else {
                    throw new Error( 'layer type shape rendering not implemented' );
                }
            }
        },

        // override to run before rendering of this node is done
        preRender: function ( state ) {

        },

        // override to run just after this node and its children are rendered
        postRender: function ( state ) {

        },

        renderChildren: function ( state ) {
            _.each( this.children, function( child ) {
                child.render( state );
            } );
        },

        addChild: function ( node ) {
            phet.assert( node !== null && node !== undefined );
            if ( this.isChild( node ) ) {
                return;
            }
            if ( node.parent !== null ) {
                node.parent.removeChild( node );
            }
            node.parent = this;
            this.children.push( node );

            this.invalidateBounds();
            this._childBoundsDirty = true;
        },

        removeChild: function ( node ) {
            phet.assert( this.isChild( node ) );

            this.markDirtyRegion( node.getBounds() );

            node.parent = null;
            this.children.splice( this.children.indexOf( node ), 1 );

            this.invalidateBounds();
        },

        hasParent: function () {
            return this.parent !== null && this.parent !== undefined;
        },

        // remove this node from its parent
        detach: function () {
            if ( this.hasParent() ) {
                this.parent.removeChild( this );
            }
        },

        isChild: function ( potentialChild ) {
            phet.assert( (potentialChild.parent === this ) === (this.children.indexOf( potentialChild ) != -1) );
            return potentialChild.parent === this;
        },

        // does this node have an associated layerType (are the contents of this node rendered separately from its ancestors)
        isLayerRoot: function() {
            return this.layerType != null;
        },

        // the first layer associated with this node (can be multiple layers if children of this node are also layer roots)
        getLayer: function() {
            phet.assert( this.isLayerRoot() );
            return this._layerBeforeRender;
        },

        translate: function ( x, y ) {
            this.appendMatrix( Matrix3.translation( x, y ) );
            this.invalidateBounds();
        },

        // scale( s ) is also supported
        scale: function ( x, y ) {
            this.appendMatrix( Matrix3.scaling( x, y ) );
            this.invalidateBounds();
        },

        rotate: function ( angle ) {
            this.appendMatrix( Matrix3.rotation2( angle ) );
            this.invalidateBounds();
        },

        // append a transformation matrix to our local transform
        appendMatrix: function( matrix ) {
            // invalidate paint TODO improve methods for this
            this.markDirtyRegion( this.parentToLocalBounds( this._bounds ) );

            this.transform.append( matrix );
        },

        // bounds assumed to be in the local coordinate frame, below this node's transform
        markDirtyRegion: function( bounds ) {
            var globalBounds = this.localToGlobalBounds( bounds );
            _.each( this.findDescendantLayers(), function( layer ) {
                layer.markDirtyRegion( globalBounds );
            } );
        },

        // the bounds for content in renderSelf(), in "local" coordinates
        getSelfBounds: function() {
            return this._selfBounds;
        },

        // the bounds for content in render(), in "parent" coordinates
        getBounds: function() {
            this.validateBounds();
            return this._bounds;
        },

        // ensure that cached bounds stored on this node (and all children) are accurate
        validateBounds: function() {
            if( this._selfBoundsDirty ) {
                this._selfBoundsDirty = false;

                // if our self bounds changed, make sure to paint the area where our new bounds are
                this.markDirtyRegion( this._selfBounds );
            }

            // validate bounds of children if necessary
            if( this._childBoundsDirty ) {
                // have each child validate their own bounds
                _.each( this.children, function( child ) {
                    child.validateBounds();
                } );

                // and recompute our _childBounds
                this._childBounds = Bounds2.NOTHING;
                var that = this;
                _.each( this.children, function( child ) {
                    that._childBounds = that._childBounds.union( child._bounds );
                } );

                this._childBoundsDirty = false;
            }

            // TODO: layout here?

            if( this._boundsDirty ) {
                var oldBounds = this._bounds;

                var that = this;

                var newBounds = this.localToParentBounds( this._selfBounds ).union( that.localToParentBounds( this._childBounds ) );

                if( !newBounds.equals( oldBounds ) ) {
                    this._bounds = newBounds;

                    if( this.parent != null ) {
                        this.parent.invalidateBounds();
                    }
                    // this.markDirtyRegion( this.parentToLocalBounds( oldBounds ) );

                    // TODO: fire off event listeners?
                }

                this._boundsDirty = false;
            }
        },

        // find the layer to which this node will be rendered
        findLayer: function() {
            return this._layerReference;
        },

        // find all layers for which this node (and all its descendants) will be rendered
        findDescendantLayers: function() {
            var firstLayer = this.findLayer();

            if( firstLayer == null ) {
                return [];
            }

            // run a node out to the last child until we reach a leaf, to find the last layer we will render to in this subtree
            var node = this;
            while( node.children.length != 0 ) {
                node = _.last( node.children );
            }
            var lastLayer = node.findLayer();

            // collect all layers between the first and last layers
            var layers = [firstLayer];
            var layer = firstLayer;
            while( layer != lastLayer && layer.nextLayer != null ) {
                layer = layer.nextLayer;
                layers.push( layer );
            }
            return layers;
        },

        // mark the bounds of this node as invalid, so it is recomputed before it is accessed again
        invalidateBounds: function() {
            this._boundsDirty = true;

            // and set flags for all ancestors
            var node = this.parent;
            while( node != null ) {
                // TODO: for performance, consider cutting this once we detect a node with this as true
                node._childBoundsDirty = true;
                node = node.parent;
            }
        },

        // sets the bounds of the content rendered by renderSelf()
        setSelfBounds: function( newBounds ) {
            // if these bounds are different than current self bounds
            if( !this._selfBounds.equals( newBounds ) ) {
                // mark the old region to be repainted
                this.markDirtyRegion( this._selfBounds );

                // set repaint flags
                this._selfBoundsDirty = true;
                this.invalidateBounds();

                // record the new bounds
                this._selfBounds = newBounds;
            }
        },

        // checking for whether a point (in parent coordinates) is contained in this sub-tree
        containsPoint: function( point ) {
            // update bounds for pruning
            this.validateBounds();

            // bail quickly if this doesn't hit our computed bounds
            if( !this._bounds.containsPoint( point ) ) { return false; }

            // point in the local coordinate frame. computed after the main bounds check, so we can bail out there efficiently
            var localPoint = this.transform.inversePosition2( point );

            // check children first, since they are rendered later
            if( this.children.length > 0 && this._childBounds.containsPoint( localPoint ) ) {

                // manual iteration here so we can return directly, and so we can iterate backwards (last node is in front)
                for( var i = this.children.length - 1; i >= 0; i-- ) {
                    var child = this.children[i];

                    // the child will have the point in its parent's coordinate frame (i.e. this node's frame)
                    if( child.containsPoint( localPoint ) ) {
                        return true;
                    }
                }
            }

            // didn't hit our children, so check ourself as a last resort
            if( this._selfBounds.containsPoint( point ) ) {
                return this.containsPointSelf( point );
            }
        },

        childrenWithinBounds: function( bounds ) {
            return _.filter( this.children, function( child ) { !child._bounds.intersection( bounds ).isEmpty(); } );
        },

        // override for computation of whether a point is inside the content rendered in renderSelf
        containsPointSelf: function( point ) {
            return false;
        },

        hasShape: function() {
            return this._shape != null;
        },

        // sets the shape drawn, or null to remove the shape
        setShape: function( shape ) {
            this._shape = shape;

            this.setSelfBounds( shape.computeBounds( ) );
        },

        getShape: function() {
            return this._shape;
        },

        // returns a list of ancestors of this node, with the root first
        ancestors: function() {
            return this.parent ? this.parent.pathToRoot() : [];
        },

        // like ancestors(), but includes the current node as well
        pathToRoot: function() {
            var result = [];
            var node = this;

            while( node != null ) {
                result.unshift( node );
                node = node.parent;
            }

            return result;
        },

        /*---------------------------------------------------------------------------*
        * Coordinate transform methods
        *----------------------------------------------------------------------------*/

        // apply this node's transform to the point
        localToParentPoint: function( point ) {
            return this.transform.transformPosition2( point );
        },

        localToParentBounds: function( bounds ) {
            return this.transform.transformBounds2( bounds );
        },

        // apply the inverse of this node's transform to the point
        parentToLocalPoint: function( point ) {
            return this.transform.inversePosition2( point );
        },

        parentToLocalBounds: function( bounds ) {
            return this.transform.inverseBounds2( bounds );
        },

        // apply this node's transform (and then all of its parents' transforms) to the point
        localToGlobalPoint: function( point ) {
            var node = this;
            while( node != null ) {
                point = node.transform.transformPosition2( point );
                node = node.parent;
            }
            return point;
        },

        localToGlobalBounds: function( bounds ) {
            var node = this;
            while( node != null ) {
                bounds = node.transform.transformBounds2( bounds );
                node = node.parent;
            }
            return bounds;
        },

        globalToLocalPoint: function( point ) {
            var node = this;

            // we need to apply the transformations in the reverse order, so we temporarily store them
            var transforms = [];
            while( node != null ) {
                transforms.push( node.transform );
                node = node.parent;
            }

            // iterate from the back forwards (from the root node to here)
            for( var i = transforms.length - 1; i >=0; i-- ) {
                point = transforms[i].inversePosition2( point );
            }
            return point;
        },

        globalToLocalBounds: function( bounds ) {
            var node = this;

            // we need to apply the transformations in the reverse order, so we temporarily store them
            var transforms = [];
            while( node != null ) {
                transforms.push( node.transform );
                node = node.parent;
            }

            // iterate from the back forwards (from the root node to here)
            for( var i = transforms.length - 1; i >=0; i-- ) {
                bounds = transforms[i].inverseBounds2( bounds );
            }
            return bounds;
        }
    };
})();<|endoftext|>


<commit_before>import { primaries2016Dates, standardize} from 'election-utils';

module.exports = function urlManager(opts) {

	const stateAbbr = opts.stateAbbr;
	const partyAbbr = opts.partyAbbr;
	const level     = opts.level;
	const test		= true;

	// get race for this state-party combination
	const { date } = primaries2016Dates.find(el => el.stateAbbr.toLowerCase() === stateAbbr.toLowerCase() && el.party === standardize.expandParty(partyAbbr));

	// construct the api url
	const baseUrl = test ? '//dev.apps.bostonglobe.com/electionapi/elections/' : '//www.bostonglobe.com/electionapi/elections/';

	const url = `${baseUrl}${date}?statePostal=${stateAbbr}&party=${partyAbbr}&level=${level}`;

	return url;

	// const baseUrl = '//qaweb.bostonglobe.com/electionapi/elections/';
	// const baseUrl = '//devweb.bostonglobe.com/electionapi/elections/';

	// const baseUrl = '//api.ap.org/v2/elections/';
	// const url = `${baseUrl}${date}?statepostal=${stateAbbr}&party=${partyAbbr}&level=${level}&test=true&apikey=2F2XwShRRLTIV1MgLIVZMDgbeWtbZqUX&officeID=P&format=json`;

};
<commit_msg>Drop module.exports syntax<commit_after>import { primaries2016Dates, standardize} from 'election-utils';

export default function urlManager(opts) {

	const stateAbbr = opts.stateAbbr;
	const partyAbbr = opts.partyAbbr;
	const level     = opts.level;
	const test		= true;

	// get race for this state-party combination
	const { date } = primaries2016Dates.find(el => el.stateAbbr.toLowerCase() === stateAbbr.toLowerCase() && el.party === standardize.expandParty(partyAbbr));

	// construct the api url
	const baseUrl = test ? '//dev.apps.bostonglobe.com/electionapi/elections/' : '//www.bostonglobe.com/electionapi/elections/';

	const url = `${baseUrl}${date}?statePostal=${stateAbbr}&party=${partyAbbr}&level=${level}`;

	return url;

	// const baseUrl = '//qaweb.bostonglobe.com/electionapi/elections/';
	// const baseUrl = '//devweb.bostonglobe.com/electionapi/elections/';

	// const baseUrl = '//api.ap.org/v2/elections/';
	// const url = `${baseUrl}${date}?statepostal=${stateAbbr}&party=${partyAbbr}&level=${level}&test=true&apikey=2F2XwShRRLTIV1MgLIVZMDgbeWtbZqUX&officeID=P&format=json`;

};
<|endoftext|>


<commit_before>/**
 * ChartReviewPanel->PortalPanel(center)->Portlet(ClinicalElementPortlet)->ItemInfo->ItemSummary/ItemList->ItemSummaryDetail/ItemListGrid&ItemListDetail
 * @class Portal.view.PortalPanel
 * @extends Ext.panel.Panel
 * A {@link Ext.panel.Panel Panel} class used for providing drag-drop-enabled portal layouts.
 */
Ext.define('CR.app.view.PortalPanel', {
    id: 'portalpanel',
    extend: 'Ext.dashboard.Dashboard',
    alias: 'widget.portalpanel',
    requires: [
        'Ext.layout.container.Border',
        'Ext.dashboard.Dashboard'
    ],
    stateful: false,
    layout: 'column',
    columnWidths: [
        0.50,
        0.50
    ],
    //bodyPadding: 3,
    parts: {
        portlet: {
            viewTemplate: {
                layout: 'fit',
                items: [{
                    xtype: 'portlet'
                }]
            }
        }
    }
});
<commit_msg>fix layout problem, remove cruft<commit_after>/**
 * ChartReviewPanel->PortalPanel(center)->Portlet(ClinicalElementPortlet)->ItemInfo->ItemSummary/ItemList->ItemSummaryDetail/ItemListGrid&ItemListDetail
 * @class Portal.view.PortalPanel
 * @extends Ext.panel.Panel
 * A {@link Ext.panel.Panel Panel} class used for providing drag-drop-enabled portal layouts.
 */
Ext.define('CR.app.view.PortalPanel', {
    id: 'portalpanel',
    extend: 'Ext.dashboard.Dashboard',
    alias: 'widget.portalpanel',
    requires: [
        'Ext.layout.container.Border',
        'Ext.dashboard.Dashboard'
    ],
    stateful: false, // MAYBE use this to save the layout by user
    layout: 'column', // only one shown
    //layout: 'card', // only one shown
    //layout: 'anchor', // pretty good - can drag to left, not right, RESIZES WITH WINDOW HORIZONTALLY!!
    //layout: 'border', // weird - all show up but overlayed in CENTER? can resize, but not move apart
    //layout: 'box', // horizontal layout - drag looses windows, add view adds to right side, way past blank spots from dragged things
    //layout: 'center', // vertical layout centered horizontally.  can resize vertically, but not horizontally; drag overlays cant separate vertically
    //layout: 'checkboxgroup', // kind of like box, but drag can go down one or more rows.  not bad but still too weird
    //layout: 'container', // kind of like column, but no better; does not resize with window horizontally; after drag resizes to window width and cannot be resized down; can still be vertically resized
    //layout: 'fit', // WORKS WELL, ONE PORTAL PER SCREEN, vertical scroll - CAN SCROLL DOWN each portal is sized to browser window size; cannot be resized horizontally; after drag can be resized vertially, not horizontally
    //layout: 'form', // Pretty good.  Initial layout is vertical, not auto expand horizontal.  drag is nice, but resizes horizontally to screen width on drop, cannot resize horizontally after; vertial resize of non dragged portals moves top of dragged portal
    //layout: 'table', // Pretty good.  Initial layout is horizontal, not auto expand horizontal.  drag is nice, but resizes horizontally to screen width on drop, cannot resize horizontally after; vertial resize of non dragged portals moves top of dragged portal
    //bodyPadding: 3,
    //padding: '5 5 5 5',
    //margin: '5 5 5 5',
    //shrinkWrap: 3,
    //draggable: true,
    //simpleDrag: true,
    parts: {
        portlet: {
            viewTemplate: {
                layout: 'fit',
                items: [{
                    xtype: 'portlet'
                }]
            }
        }
    }
});
<|endoftext|>