<commit_before>class Pager {

    /**
     * @param {HTMLElement|String} root can be a selector
     * @param {Number} itemsPerPage
     * @param {Number} activePage
     * @param {Number} totalItems
     * @param {String} urlTemplate
     * @param {Boolean} unlimited
     */
    constructor (root, itemsPerPage, activePage, totalItems, urlTemplate, unlimited) {

        this.ETC          = '...';
        this.rootElement  = $(root);
        this.itemsPerPage = parseInt(itemsPerPage);
        this.activePage   = parseInt(activePage);
        this.totalCount   = parseInt(totalItems);
        this.urlTemplate  = urlTemplate;
        this.unlimited = unlimited;
        this.maxPage      = this.calculatePageCount();
        this.tileWidth    = 48;

        this.prototypeLi   = $('<li>');
        this.prototypeA    = $('<a>');
        this.prototypeIcon = $('<as24-icon>');

        $(window).on('resize', $.proxy(this.render, this));

        this.render();
    }

    /**
     * @returns {Number}
     */
    get maxPage() {
        return this._maxPage;
    }

    /**
     * @param {Number} pages
     */
    set maxPage(pages) {
        this._maxPage = pages;
        console.log(pages);
    }

    /**
     * @returns {Object|Zepto}
     */
    get previousButton() {
        let li   = this.prototypeLi.clone(),
            a    = this.prototypeA.clone(),
            icon = this.prototypeIcon.clone();

        const previousText = $(this.rootElement).data('previous-text') || 'Previous';
        const isFirstPage = 1 === this.activePage;

        li.addClass('previous-page');
        if (!isFirstPage) {
            a.attr('href', this.getPageUrl(this.activePage - 1));
        }

        a.text(' ' + previousText);
        icon.attr('type', 'arrow');

        if (isFirstPage) {
            a.addClass('disabled');
        }

        return li.append(a.prepend(icon));
    }

    /**
     * @returns {Object|Zepto}
     */
    get nextButton() {
        let li   = this.prototypeLi.clone(),
            a    = this.prototypeA.clone(),
            icon = this.prototypeIcon.clone();

        const nextText = $(this.rootElement).data('next-text') || 'Next';
        const isLastPage = this.maxPage === this.activePage;

        li.addClass('next-page');
        if (!isLastPage) {
            a.attr('href', this.getPageUrl(this.activePage + 1));
        }

        a.text(nextText + ' ');
        icon.attr('type', 'arrow');

        if (isLastPage) {
            a.addClass('disabled');
        }

        return li.append(a.append(icon));
    }

    get infoPage() {
        return this.prototypeLi.clone().addClass('info-page').append(
            this.prototypeA.clone().addClass('disabled').attr('href', 'javascript:void(0)').text(
                this.activePage + ' / ' + this.maxPage
            )
        );
    }

    /**
     * @param {Number} pageNumber
     * @returns {String}
     */
    getPageUrl(pageNumber) {
        let template = this.urlTemplate.replace('{page}', pageNumber.toString());

        return template.replace('{size}', this.itemsPerPage.toString());
    }

    /**
     * Create a single page element
     *
     * @param {Number} pageNumber
     * @returns {Object|Zepto}
     */
    createPage(pageNumber) {
        let tile = this.prototypeLi.clone().data('page', pageNumber),
            a    = this.prototypeA.clone().attr('href', this.getPageUrl(pageNumber));

        if (this.ETC === pageNumber) {
            tile.data('page', 'etc');
            a.addClass('disabled');
        }

        if (this.activePage === pageNumber) {
            a.addClass('active');
        }

        a.text(pageNumber);

        return tile.append(a);
    }

    /**
     * Returns the maximum possible amount ot tiles between <PREV> and <NEXT>
     *
     * @returns {int}
     */
    getMaximumPossibleTiles() {
        const rootWidth = this.rootElement.width();

        // We assume that this is the minWidth for both buttons
        const prevNextWidth = 200;

        return Math.floor((rootWidth - prevNextWidth) / this.tileWidth);
    }

    /**
     * Returns an array with all page numbers in the correct order
     *
     * Example:
     * activePage = 17
     * maxPage    = 20
     * Returns [1, "...", 14, 15, 16, 17, 18, 19, 20]
     *
     * @param {Number} activePage
     * @returns {Array}
     */
    getPageTiles(activePage) {
        let leftNumber = activePage - 1,
            rightNumber = activePage + 1,
            maxPossibleTiles = this.getMaximumPossibleTiles(),
            usefulTiles = 0,
            countEtc = 0;

        // we always want to have an odd number of tiles to show
        if (maxPossibleTiles % 2 === 0 && this.maxPage > maxPossibleTiles) {
            maxPossibleTiles--;
        }

        let tiles = [activePage];

        // because we have our activePage, we have one possible tile less
        maxPossibleTiles--;

        while ((leftNumber > 0 || rightNumber <= this.maxPage) && maxPossibleTiles > 0) {

            if (leftNumber > 0) {
                tiles.unshift(leftNumber);
                usefulTiles++;
                maxPossibleTiles--;
                if (0 === maxPossibleTiles) {
                    break;
                }
            }

            if (rightNumber <= this.maxPage) {
                tiles.push(rightNumber);
                usefulTiles++;
                maxPossibleTiles--;
                if (0 === maxPossibleTiles) {
                    break;
                }
            }

            leftNumber--;
            rightNumber++;
        }

        // special case: we have enough space to show 'em all
        if (tiles.length === this.maxPage) {
            return tiles;
        }

        // special case: If we have less or equal to 7 pages/tiles in total, we show all or infopage
        if (this.maxPage <= 7 && tiles.length < this.maxPage) {
            return [];
        }

        // show dots on the left ( < 1 ... 7 8 9)
        if (1 !== tiles[0]) {
            tiles[0] = 1;
            tiles[1] = this.ETC;
            countEtc++;
            usefulTiles -= 1;
        }

        // show dots on the right ( 10 11 ... 20 >)
        if (this.maxPage !== tiles[tiles.length - 1]) {
            tiles[tiles.length - 1] = this.maxPage;
            tiles[tiles.length - 2] = this.ETC;
            countEtc++;
            usefulTiles -= 1;
        }

        // special case: show info page if less than 3 useful tiles
        if (countEtc >= 1 && usefulTiles <= 3) {
            return [];
        }

        // show only the info page tile
        if (usefulTiles <= 2 || this.maxPage <= 3) {
            return [];
        }

        return tiles;
    }

    /**
     * Render the pagination
     */
    render() {
        this.rootElement.children().remove();

        let pagination = this.getPageTiles(this.activePage),
            collection      = $();

        this.rootElement.append(this.previousButton);

        if (0 === pagination.length) {
            this.rootElement.append(this.infoPage);
        } else {
            pagination.forEach((page) => {
                collection = collection.add(this.createPage(page));
            });
            this.rootElement.append(collection);
        }

        this.rootElement.append(this.nextButton);
    }

    /**
     * @returns {Number}
     */
    calculatePageCount() {
        let numberOfPages = Math.ceil(this.totalCount / this.itemsPerPage);

        if (this.unlimited) {
            return numberOfPages;
        }

        if (numberOfPages >= 20) {
            return 20;
        }

        return numberOfPages;
    }
}
export default Pager;
<commit_msg>removed console log<commit_after>class Pager {

    /**
     * @param {HTMLElement|String} root can be a selector
     * @param {Number} itemsPerPage
     * @param {Number} activePage
     * @param {Number} totalItems
     * @param {String} urlTemplate
     * @param {Boolean} unlimited
     */
    constructor (root, itemsPerPage, activePage, totalItems, urlTemplate, unlimited) {

        this.ETC          = '...';
        this.rootElement  = $(root);
        this.itemsPerPage = parseInt(itemsPerPage);
        this.activePage   = parseInt(activePage);
        this.totalCount   = parseInt(totalItems);
        this.urlTemplate  = urlTemplate;
        this.unlimited = unlimited;
        this.maxPage      = this.calculatePageCount();
        this.tileWidth    = 48;

        this.prototypeLi   = $('<li>');
        this.prototypeA    = $('<a>');
        this.prototypeIcon = $('<as24-icon>');

        $(window).on('resize', $.proxy(this.render, this));

        this.render();
    }

    /**
     * @returns {Number}
     */
    get maxPage() {
        return this._maxPage;
    }

    /**
     * @param {Number} pages
     */
    set maxPage(pages) {
        this._maxPage = pages;
    }

    /**
     * @returns {Object|Zepto}
     */
    get previousButton() {
        let li   = this.prototypeLi.clone(),
            a    = this.prototypeA.clone(),
            icon = this.prototypeIcon.clone();

        const previousText = $(this.rootElement).data('previous-text') || 'Previous';
        const isFirstPage = 1 === this.activePage;

        li.addClass('previous-page');
        if (!isFirstPage) {
            a.attr('href', this.getPageUrl(this.activePage - 1));
        }

        a.text(' ' + previousText);
        icon.attr('type', 'arrow');

        if (isFirstPage) {
            a.addClass('disabled');
        }

        return li.append(a.prepend(icon));
    }

    /**
     * @returns {Object|Zepto}
     */
    get nextButton() {
        let li   = this.prototypeLi.clone(),
            a    = this.prototypeA.clone(),
            icon = this.prototypeIcon.clone();

        const nextText = $(this.rootElement).data('next-text') || 'Next';
        const isLastPage = this.maxPage === this.activePage;

        li.addClass('next-page');
        if (!isLastPage) {
            a.attr('href', this.getPageUrl(this.activePage + 1));
        }

        a.text(nextText + ' ');
        icon.attr('type', 'arrow');

        if (isLastPage) {
            a.addClass('disabled');
        }

        return li.append(a.append(icon));
    }

    get infoPage() {
        return this.prototypeLi.clone().addClass('info-page').append(
            this.prototypeA.clone().addClass('disabled').attr('href', 'javascript:void(0)').text(
                this.activePage + ' / ' + this.maxPage
            )
        );
    }

    /**
     * @param {Number} pageNumber
     * @returns {String}
     */
    getPageUrl(pageNumber) {
        let template = this.urlTemplate.replace('{page}', pageNumber.toString());

        return template.replace('{size}', this.itemsPerPage.toString());
    }

    /**
     * Create a single page element
     *
     * @param {Number} pageNumber
     * @returns {Object|Zepto}
     */
    createPage(pageNumber) {
        let tile = this.prototypeLi.clone().data('page', pageNumber),
            a    = this.prototypeA.clone().attr('href', this.getPageUrl(pageNumber));

        if (this.ETC === pageNumber) {
            tile.data('page', 'etc');
            a.addClass('disabled');
        }

        if (this.activePage === pageNumber) {
            a.addClass('active');
        }

        a.text(pageNumber);

        return tile.append(a);
    }

    /**
     * Returns the maximum possible amount ot tiles between <PREV> and <NEXT>
     *
     * @returns {int}
     */
    getMaximumPossibleTiles() {
        const rootWidth = this.rootElement.width();

        // We assume that this is the minWidth for both buttons
        const prevNextWidth = 200;

        return Math.floor((rootWidth - prevNextWidth) / this.tileWidth);
    }

    /**
     * Returns an array with all page numbers in the correct order
     *
     * Example:
     * activePage = 17
     * maxPage    = 20
     * Returns [1, "...", 14, 15, 16, 17, 18, 19, 20]
     *
     * @param {Number} activePage
     * @returns {Array}
     */
    getPageTiles(activePage) {
        let leftNumber = activePage - 1,
            rightNumber = activePage + 1,
            maxPossibleTiles = this.getMaximumPossibleTiles(),
            usefulTiles = 0,
            countEtc = 0;

        // we always want to have an odd number of tiles to show
        if (maxPossibleTiles % 2 === 0 && this.maxPage > maxPossibleTiles) {
            maxPossibleTiles--;
        }

        let tiles = [activePage];

        // because we have our activePage, we have one possible tile less
        maxPossibleTiles--;

        while ((leftNumber > 0 || rightNumber <= this.maxPage) && maxPossibleTiles > 0) {

            if (leftNumber > 0) {
                tiles.unshift(leftNumber);
                usefulTiles++;
                maxPossibleTiles--;
                if (0 === maxPossibleTiles) {
                    break;
                }
            }

            if (rightNumber <= this.maxPage) {
                tiles.push(rightNumber);
                usefulTiles++;
                maxPossibleTiles--;
                if (0 === maxPossibleTiles) {
                    break;
                }
            }

            leftNumber--;
            rightNumber++;
        }

        // special case: we have enough space to show 'em all
        if (tiles.length === this.maxPage) {
            return tiles;
        }

        // special case: If we have less or equal to 7 pages/tiles in total, we show all or infopage
        if (this.maxPage <= 7 && tiles.length < this.maxPage) {
            return [];
        }

        // show dots on the left ( < 1 ... 7 8 9)
        if (1 !== tiles[0]) {
            tiles[0] = 1;
            tiles[1] = this.ETC;
            countEtc++;
            usefulTiles -= 1;
        }

        // show dots on the right ( 10 11 ... 20 >)
        if (this.maxPage !== tiles[tiles.length - 1]) {
            tiles[tiles.length - 1] = this.maxPage;
            tiles[tiles.length - 2] = this.ETC;
            countEtc++;
            usefulTiles -= 1;
        }

        // special case: show info page if less than 3 useful tiles
        if (countEtc >= 1 && usefulTiles <= 3) {
            return [];
        }

        // show only the info page tile
        if (usefulTiles <= 2 || this.maxPage <= 3) {
            return [];
        }

        return tiles;
    }

    /**
     * Render the pagination
     */
    render() {
        this.rootElement.children().remove();

        let pagination = this.getPageTiles(this.activePage),
            collection      = $();

        this.rootElement.append(this.previousButton);

        if (0 === pagination.length) {
            this.rootElement.append(this.infoPage);
        } else {
            pagination.forEach((page) => {
                collection = collection.add(this.createPage(page));
            });
            this.rootElement.append(collection);
        }

        this.rootElement.append(this.nextButton);
    }

    /**
     * @returns {Number}
     */
    calculatePageCount() {
        let numberOfPages = Math.ceil(this.totalCount / this.itemsPerPage);

        if (this.unlimited) {
            return numberOfPages;
        }

        if (numberOfPages >= 20) {
            return 20;
        }

        return numberOfPages;
    }
}
export default Pager;
<|endoftext|>


<commit_before>/**
 * It displays all the available phenotypes in the system.
 *
 * @author frances
 * @version $Id$
 */
Ext.namespace('Gemma');

Gemma.PhenotypeGridPanel = Ext.extend(Ext.grid.GridPanel, {
	title: "Phenotypes",
    autoScroll: true,
    stripeRows: true,
	width: 350,
	height: 300,
	split: true,
	loadMask: true,
    viewConfig: {
        forceFit: true
    },
    initComponent: function() {
		var checkboxSelectionModel = new Ext.grid.CheckboxSelectionModel({
			dataIndex: 'isChecked',
			singleSelect: false,
			header: '', // remove the "select all" checkbox on the header
		    listeners: {
				rowdeselect: function(selectionModel, rowIndex, record) {
					record.set('isChecked', false);
				},
				rowselect: function(selectionModel, rowIndex, record) {
					record.set('isChecked', true);
				},
		        selectionchange: function(selectionModel) {
					var selectedPhenotypes;

					if (selectionModel.hasSelection()) {
						selectedPhenotypes = [];

						var selections = selectionModel.getSelections();
					    for (var i = 0; i < selections.length; i++) {
					        selectedPhenotypes.push({
								urlId: selections[i].get('urlId'),
					        	value: selections[i].get('value'),
					        	valueUri: selections[i].get('valueUri')
					        });
						}
					} else {
						selectedPhenotypes = null;
					}
					this.fireEvent('phenotypeSelectionChange', selectedPhenotypes);
		        },
				scope: this
		    }
		});

		var phenotypeSearchField = new Gemma.PhenotypePanelSearchField({
			emptyText: 'Search Phenotypes',
			listeners: {
				filterApplied: function(recordFilter) {
					var filterFields = ['value'];
					this.getStore().filterBy(
						function(record) {
							if (this.getSelectionModel().isSelected(record) || recordFilter(record, filterFields)) {
								return true;
							}
						    return false;
					    },
					    this
					);
				},
				filterRemoved: function() {
					this.getStore().clearFilter(false);
				},
				scope: this
			}
		});

		var generateGeneCountHTML = function(width, geneCountText) {
			return '' + geneCountText + '';
		}

		Ext.apply(this, {
			store: new Ext.data.Store({
				proxy: this.phenotypeStoreProxy == null ?
					new Ext.data.DWRProxy(PhenotypeController.loadAllPhenotypes) :
					this.phenotypeStoreProxy,
				reader: new Ext.data.JsonReader({
					root: 'records', // required.
					successProperty: 'success', // same as default.
					messageProperty: 'message', // optional
					totalProperty: 'totalRecords', // default is 'total'; optional unless paging.
					idProperty: "urlId",
					fields: [
						'urlId',
						'value',
						'valueUri',
//						{
//							name: 'count',
//							type: "long",
//							convert: function(value, record) {
//								return record.publicGeneCount + record.privateGeneCount;
//							}
//						},
						'publicGeneCount',
						'privateGeneCount',
						{ name: 'isChecked', sortDir: 'DESC' }
					]
				}),
				sortInfo: {	field: 'value', direction: 'ASC' },
				autoLoad: true
			}),
			columns:[
				checkboxSelectionModel,
				{
					header: "Phenotype",
					dataIndex: 'value',
					width: 285,
					renderToolTip: true,
					sortable: true
				},{
					header: "Gene Count",
					dataIndex: 'publicGeneCount',
					align: "right",
					width: 135,
		            renderer: function(value, metadata, record, rowIndex, colIndex, store) {
						return generateGeneCountHTML(50, record.data.publicGeneCount) + ' ' +
							   generateGeneCountHTML(26, (record.data.privateGeneCount > 0 ? '(' + record.data.privateGeneCount + ')' : ' '));
		            },
					sortable: true
			    }
			],
		    sm: checkboxSelectionModel,
		    listeners: {
		    	headerclick: function(gridPanel, columnIndex, event) {
		    		if (columnIndex == 0) {
		    			this.getStore().sort('isChecked');
		    		}
		    	}
		    },
			tbar: [
				phenotypeSearchField,
				{
					handler: this.createPhenotypeAssociationHandler ?
						this.createPhenotypeAssociationHandler :
						function() {
							var phenotypeAssociationFormWindow = new Gemma.PhenotypeAssociationForm.Window();

							this.relayEvents(phenotypeAssociationFormWindow, ['phenotypeAssociationChanged']);
							phenotypeAssociationFormWindow.showWindow(Gemma.PhenotypeAssociationForm.ACTION_CREATE,
								{
									gene: this.currentGene,
									phenotypes: this.currentPhenotypes
								});
						}
					,
					scope: this,
					icon: "/Gemma/images/icons/add.png",
					tooltip: "Add new phenotype association"
				}
			]
		});

		this.superclass().initComponent.call(this);

		this.getStore().on('load',
			function(store, records, options) {
				if (phenotypeSearchField.getValue() !== '') {
					phenotypeSearchField.applyCurrentFilter();
				}
			},
			this // scope
		);
    }
});
<commit_msg>I fixed the problem that more than one phenotypeSelectionChange events are fired when more than one rows are selected previously and then user selects only one row.<commit_after>/**
 * It displays all the available phenotypes in the system.
 *
 * @author frances
 * @version $Id$
 */
Ext.namespace('Gemma');

Gemma.PhenotypeGridPanel = Ext.extend(Ext.grid.GridPanel, {
	title: "Phenotypes",
    autoScroll: true,
    stripeRows: true,
	width: 350,
	height: 300,
	split: true,
	loadMask: true,
    viewConfig: {
        forceFit: true
    },
    initComponent: function() {
		var currentSelections = [];

		var checkboxSelectionModel = new Ext.grid.CheckboxSelectionModel({
			dataIndex: 'isChecked',
			singleSelect: false,
			header: '', // remove the "select all" checkbox on the header
		    listeners: {
				rowdeselect: function(selectionModel, rowIndex, record) {
					// I must defer changing the field isChecked. Otherwise,
					// other events such as cellclick will not be fired.
					Ext.defer(
						function() {
							record.set('isChecked', false);
						},
						500);
				},
				rowselect: function(selectionModel, rowIndex, record) {
					// I must defer changing the field isChecked. Otherwise,
					// other events such as cellclick will not be fired.
					Ext.defer(
						function() {
							record.set('isChecked', true);
						},
						500);
				},
				scope: this
		    }
		});

		var phenotypeSearchField = new Gemma.PhenotypePanelSearchField({
			emptyText: 'Search Phenotypes',
			listeners: {
				filterApplied: function(recordFilter) {
					var filterFields = ['value'];
					this.getStore().filterBy(
						function(record) {
							if (this.getSelectionModel().isSelected(record) || recordFilter(record, filterFields)) {
								return true;
							}
						    return false;
					    },
					    this
					);
				},
				filterRemoved: function() {
					this.getStore().clearFilter(false);
				},
				scope: this
			}
		});

		var generateGeneCountHTML = function(width, geneCountText) {
			return '' + geneCountText + '';
		}

		Ext.apply(this, {
			store: new Ext.data.Store({
				proxy: this.phenotypeStoreProxy == null ?
					new Ext.data.DWRProxy(PhenotypeController.loadAllPhenotypes) :
					this.phenotypeStoreProxy,
				reader: new Ext.data.JsonReader({
					root: 'records', // required.
					successProperty: 'success', // same as default.
					messageProperty: 'message', // optional
					totalProperty: 'totalRecords', // default is 'total'; optional unless paging.
					idProperty: "urlId",
					fields: [
						'urlId',
						'value',
						'valueUri',
						'publicGeneCount',
						'privateGeneCount',
						{ name: 'isChecked', sortDir: 'DESC' }
					]
				}),
				sortInfo: {	field: 'value', direction: 'ASC' },
				autoLoad: true
			}),
			columns:[
				checkboxSelectionModel,
				{
					header: "Phenotype",
					dataIndex: 'value',
					width: 285,
					renderToolTip: true,
					sortable: true
				},{
					header: "Gene Count",
					dataIndex: 'publicGeneCount',
					align: "right",
					width: 135,
		            renderer: function(value, metadata, record, rowIndex, colIndex, store) {
						return generateGeneCountHTML(50, record.data.publicGeneCount) + ' ' +
							   generateGeneCountHTML(26, (record.data.privateGeneCount > 0 ? '(' + record.data.privateGeneCount + ')' : ' '));
		            },
					sortable: true
			    }
			],
		    sm: checkboxSelectionModel,
		    listeners: {
		    	// cellclick instead of selection model's selectionchange event handler is implemented
		    	// for letting listeners know that phenotype selections have been changed
		    	// because selectionchange events are fired even when rows are deselected in code.
				cellclick: function(thisGrid, rowIndex, columnIndex, event) {
					var newSelections = this.getSelectionModel().getSelections();

					var hasSameSelections = (currentSelections.length === newSelections.length);

					if (hasSameSelections) {
						for (var i = 0; hasSameSelections && i < currentSelections.length; i++) {
							hasSameSelections = (currentSelections[i].get('urlId') === newSelections[i].get('urlId'));
						}
					}

					if (!hasSameSelections) {
						var selectedPhenotypes = [];

						currentSelections = newSelections;
					    for (var i = 0; i < currentSelections.length; i++) {
					        selectedPhenotypes.push({
								urlId: currentSelections[i].get('urlId'),
					        	value: currentSelections[i].get('value'),
					        	valueUri: currentSelections[i].get('valueUri')
					        });
						}
						this.fireEvent('phenotypeSelectionChange', selectedPhenotypes);
					}
				},
		    	headerclick: function(gridPanel, columnIndex, event) {
		    		if (columnIndex == 0) {
		    			this.getStore().sort('isChecked');
		    		}
		    	}
		    },
			tbar: [
				phenotypeSearchField,
				{
					handler: this.createPhenotypeAssociationHandler ?
						this.createPhenotypeAssociationHandler :
						function() {
							var phenotypeAssociationFormWindow = new Gemma.PhenotypeAssociationForm.Window();

							this.relayEvents(phenotypeAssociationFormWindow, ['phenotypeAssociationChanged']);
							phenotypeAssociationFormWindow.showWindow(Gemma.PhenotypeAssociationForm.ACTION_CREATE,
								{
									gene: this.currentGene,
									phenotypes: this.currentPhenotypes
								});
						}
					,
					scope: this,
					icon: "/Gemma/images/icons/add.png",
					tooltip: "Add new phenotype association"
				}
			]
		});

		this.superclass().initComponent.call(this);

		this.getStore().on('load',
			function(store, records, options) {
				if (phenotypeSearchField.getValue() !== '') {
					phenotypeSearchField.applyCurrentFilter();
				}
			},
			this // scope
		);
    }
});
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
(function () {
  "use strict";

  dust.loadSource(dust.compile($('#tmpl-federationhealth').html(), 'federationhealth'));
  dust.loadSource(dust.compile($('#tmpl-namenode').html(), 'namenode-info'));
  dust.loadSource(dust.compile($('#tmpl-router').html(), 'router-info'));
  dust.loadSource(dust.compile($('#tmpl-datanode').html(), 'datanode-info'));
  dust.loadSource(dust.compile($('#tmpl-mounttable').html(), 'mounttable'));

  $.fn.dataTable.ext.order['ng-value'] = function (settings, col)
  {
    return this.api().column(col, {order:'index'} ).nodes().map(function (td, i) {
      return $(td).attr('ng-value');
    });
  };

  function load_overview() {
    var BEANS = [
      {"name": "federation",  "url": "/jmx?qry=Hadoop:service=Router,name=FederationState"},
      {"name": "routerstat",  "url": "/jmx?qry=Hadoop:service=NameNode,name=NameNodeStatus"},
      {"name": "router",      "url": "/jmx?qrt=Hadoop:service=NameNode,name=NameNodeInfo"},
      {"name": "mem",         "url": "/jmx?qry=java.lang:type=Memory"}
    ];

    var HELPERS = {
      'helper_fs_max_objects': function (chunk, ctx, bodies, params) {
        var o = ctx.current();
        if (o.MaxObjects > 0) {
          chunk.write('(' + Math.round((o.FilesTotal + o.BlockTotal) / o.MaxObjects * 100) * 100 + ')%');
        }
      },

      'helper_dir_status': function (chunk, ctx, bodies, params) {
        var j = ctx.current();
        for (var i in j) {
          chunk.write('
' + i + '	' + j[i] + '	' + params.type + '
');
        }
      },

      'helper_date_tostring' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write('' + new Date(Number(value)).toLocaleString());
      }
    };

    var data = {};

    // Workarounds for the fact that JMXJsonServlet returns non-standard JSON strings
    function workaround(nn) {
      nn.NodeUsage = JSON.parse(nn.NodeUsage);
      return nn;
    }

    load_json(
      BEANS,
      guard_with_startup_progress(function(d) {
        for (var k in d) {
          data[k] = k === 'federation' ? workaround(d[k].beans[0]) : d[k].beans[0];
        }
        render();
      }),
      function (url, jqxhr, text, err) {
        show_err_msg('
Failed to retrieve data from ' + url + ', cause: ' + err + '
');
      });

    function render() {
      var base = dust.makeBase(HELPERS);
      dust.render('federationhealth', base.push(data), function(err, out) {
        $('#tab-overview').html(out);
        $('#ui-tabs a[href="#tab-overview"]').tab('show');
      });
    }
  }

  function load_namenode_info() {
    var HELPERS = {
      'helper_lastcontact_tostring' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write('' + new Date(Date.now()-1000*Number(value)));
      }
    };

    function workaround(r) {
      function node_map_to_array(nodes) {
        var res = [];
        for (var n in nodes) {
          var p = nodes[n];
          p.name = n;
          res.push(p);
        }
        return res;
      }

      function capitalise(string) {
          return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
      }

      function augment_namenodes(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          var n = nodes[i];
          n.usedPercentage = Math.round(n.used * 1.0 / n.totalSpace * 100);
          n.title = "Unavailable";
          n.iconState = "unavailable";
          if (n.isSafeMode === true) {
            n.title = capitalise(n.state) + " (safe mode)"
            n.iconState = "safemode";
          } else if (n.state === "ACTIVE") {
            n.title = capitalise(n.state);
            n.iconState = "active";
          } else if (nodes[i].state === "STANDBY") {
            n.title = capitalise(n.state);
            n.iconState = "standby";
          } else if (nodes[i].state === "UNAVAILABLE") {
            n.title = capitalise(n.state);
            n.iconState = "unavailable";
          } else if (nodes[i].state === "DISABLED") {
            n.title = capitalise(n.state);
            n.iconState = "disabled";
          }
          if (n.namenodeId === "null") {
            n.namenodeId = "";
          }
        }
      }

      r.Nameservices = node_map_to_array(JSON.parse(r.Nameservices));
      augment_namenodes(r.Nameservices);
      r.Namenodes = node_map_to_array(JSON.parse(r.Namenodes));
      augment_namenodes(r.Namenodes);
      return r;
    }

    $.get(
      '/jmx?qry=Hadoop:service=Router,name=FederationState',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('namenode-info', base.push(data), function(err, out) {
          $('#tab-namenode').html(out);
          $('#ui-tabs a[href="#tab-namenode"]').tab('show');
        });
      })).error(ajax_error_handler);
  }

  function load_router_info() {
    var HELPERS = {
      'helper_lastcontact_tostring' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write('' + new Date(Date.now()-1000*Number(value)));
      }
    };

    function workaround(r) {
      function node_map_to_array(nodes) {
        var res = [];
        for (var n in nodes) {
          var p = nodes[n];
          p.name = n;
          res.push(p);
        }
        return res;
      }

      function capitalise(string) {
          return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
      }

      function augment_routers(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          var n = nodes[i];
          n.title = "Unavailable"
          n.iconState = "unavailable";
          if (n.status === "INITIALIZING") {
            n.title = capitalise(n.status);
            n.iconState = "active";
          } else if (n.status === "RUNNING") {
            n.title = capitalise(n.status);
            n.iconState = "active";
          } else if (n.status === "SAFEMODE") {
            n.title = capitalise(n.status);
            n.iconState = "safemode";
          } else if (n.status === "STOPPING") {
            n.title = capitalise(n.status);
            n.iconState = "unavailable";
          } else if (n.status === "SHUTDOWN") {
            n.title = capitalise(n.status);
            n.iconState = "unavailable";
          }
        }
      }

      r.Routers = node_map_to_array(JSON.parse(r.Routers));
      augment_routers(r.Routers);
      return r;
    }

    $.get(
      '/jmx?qry=Hadoop:service=Router,name=FederationState',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('router-info', base.push(data), function(err, out) {
          $('#tab-router').html(out);
          $('#ui-tabs a[href="#tab-router"]').tab('show');
        });
      })).error(ajax_error_handler);
  }

  // TODO Copied directly from dfshealth.js; is there a way to import this function?
  function load_datanode_info() {

    var HELPERS = {
      'helper_relative_time' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write(moment().subtract(Number(value), 'seconds').format('YYYY-MM-DD HH:mm:ss'));
      },
      'helper_usage_bar' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        var v = Number(value);
        var r = null;
        if (v < 70) {
          r = 'progress-bar-success';
        } else if (v < 85) {
          r = 'progress-bar-warning';
        } else {
          r = "progress-bar-danger";
        }
        return chunk.write(r);
      },
    };

    function workaround(r) {
      function node_map_to_array(nodes) {
        var res = [];
        for (var n in nodes) {
          var p = nodes[n];
          p.name = n;
          res.push(p);
        }
        return res;
      }

      function augment_live_nodes(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          var n = nodes[i];
          n.usedPercentage = Math.round((n.used + n.nonDfsUsedSpace) * 1.0 / n.capacity * 100);
          if (n.adminState === "In Service") {
            n.state = "alive";
          } else if (nodes[i].adminState === "Decommission In Progress") {
            n.state = "decommissioning";
          } else if (nodes[i].adminState === "Decommissioned") {
            n.state = "decommissioned";
          }
        }
      }

      function augment_dead_nodes(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          if (nodes[i].decommissioned) {
            nodes[i].state = "down-decommissioned";
          } else {
            nodes[i].state = "down";
          }
        }
      }

      r.LiveNodes = node_map_to_array(JSON.parse(r.LiveNodes));
      augment_live_nodes(r.LiveNodes);
      r.DeadNodes = node_map_to_array(JSON.parse(r.DeadNodes));
      augment_dead_nodes(r.DeadNodes);
      r.DecomNodes = node_map_to_array(JSON.parse(r.DecomNodes));
      return r;
    }

    $.get(
      '/jmx?qry=Hadoop:service=NameNode,name=NameNodeInfo',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('datanode-info', base.push(data), function(err, out) {
          $('#tab-datanode').html(out);
          $('#table-datanodes').dataTable( {
            'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
            'columns': [
              { 'orderDataType': 'ng-value', 'searchable': true },
              { 'orderDataType': 'ng-value', 'type': 'numeric' },
              { 'orderDataType': 'ng-value', 'type': 'numeric' },
              { 'orderDataType': 'ng-value', 'type': 'numeric'}
            ]});
          $('#ui-tabs a[href="#tab-datanode"]').tab('show');
        });
      })).error(ajax_error_handler);
  }

  function load_mount_table() {
    var HELPERS = {}

    function workaround(resource) {
      function augment_read_only(mountTable) {
        for (var i = 0, e = mountTable.length; i < e; ++i) {
          if (mountTable[i].readonly == true) {
            mountTable[i].readonly = "true"
          } else {
            mountTable[i].readonly = "false"
          }
        }
      }

      resource.MountTable = JSON.parse(resource.MountTable)
      augment_read_only(resource.MountTable)
      return resource;
    }

    $.get(
      '/jmx?qry=Hadoop:service=Router,name=FederationState',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('mounttable', base.push(data), function(err, out) {
          $('#tab-mounttable').html(out);
          $('#ui-tabs a[href="#tab-mounttable"]').tab('show');
        });
      })).error(ajax_error_handler);
  }

  function toTitleCase(str) {
    return str.replace(/\w\S*/g, function(txt){
        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
      });
  }

  function show_err_msg(msg) {
    $('#alert-panel-body').html(msg);
    $('#alert-panel').show();
  }

  function ajax_error_handler(url, jqxhr, text, err) {
    show_err_msg('
Failed to retrieve data from ' + url + ', cause: ' + err + '
');
  }

  function guard_with_startup_progress(fn) {
    return function() {
      try {
        fn.apply(this, arguments);
      } catch (err) {
        if (err instanceof TypeError) {
          show_err_msg('Router error: ' + err);
        }
      }
    };
  }

  function load_page() {
    var hash = window.location.hash;
    switch(hash) {
      case "#tab-overview":
        load_overview();
        break;
      case "#tab-namenode":
        load_namenode_info();
        break;
      case "#tab-router":
        load_router_info();
        break;
      case "#tab-datanode":
        load_datanode_info();
        break;
      case "#tab-mounttable":
        load_mount_table();
        break;
      default:
        window.location.hash = "tab-overview";
        break;
    }
  }
  load_page();

  $(window).bind('hashchange', function () {
    load_page();
  });
})();
<commit_msg>HDFS-14005. RBF: Web UI update to bootstrap-3.3.7. Contributed by Inigo Goiri.<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
(function () {
  "use strict";

  dust.loadSource(dust.compile($('#tmpl-federationhealth').html(), 'federationhealth'));
  dust.loadSource(dust.compile($('#tmpl-namenode').html(), 'namenode-info'));
  dust.loadSource(dust.compile($('#tmpl-router').html(), 'router-info'));
  dust.loadSource(dust.compile($('#tmpl-datanode').html(), 'datanode-info'));
  dust.loadSource(dust.compile($('#tmpl-mounttable').html(), 'mounttable'));

  $.fn.dataTable.ext.order['ng-value'] = function (settings, col)
  {
    return this.api().column(col, {order:'index'} ).nodes().map(function (td, i) {
      return $(td).attr('ng-value');
    });
  };

  function load_overview() {
    var BEANS = [
      {"name": "federation",  "url": "/jmx?qry=Hadoop:service=Router,name=FederationState"},
      {"name": "routerstat",  "url": "/jmx?qry=Hadoop:service=NameNode,name=NameNodeStatus"},
      {"name": "router",      "url": "/jmx?qrt=Hadoop:service=NameNode,name=NameNodeInfo"},
      {"name": "mem",         "url": "/jmx?qry=java.lang:type=Memory"}
    ];

    var HELPERS = {
      'helper_fs_max_objects': function (chunk, ctx, bodies, params) {
        var o = ctx.current();
        if (o.MaxObjects > 0) {
          chunk.write('(' + Math.round((o.FilesTotal + o.BlockTotal) / o.MaxObjects * 100) * 100 + ')%');
        }
      },

      'helper_dir_status': function (chunk, ctx, bodies, params) {
        var j = ctx.current();
        for (var i in j) {
          chunk.write('
' + i + '	' + j[i] + '	' + params.type + '
');
        }
      },

      'helper_date_tostring' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write('' + new Date(Number(value)).toLocaleString());
      }
    };

    var data = {};

    // Workarounds for the fact that JMXJsonServlet returns non-standard JSON strings
    function workaround(nn) {
      nn.NodeUsage = JSON.parse(nn.NodeUsage);
      return nn;
    }

    load_json(
      BEANS,
      guard_with_startup_progress(function(d) {
        for (var k in d) {
          data[k] = k === 'federation' ? workaround(d[k].beans[0]) : d[k].beans[0];
        }
        render();
      }),
      function (url, jqxhr, text, err) {
        show_err_msg('
Failed to retrieve data from ' + url + ', cause: ' + err + '
');
      });

    function render() {
      var base = dust.makeBase(HELPERS);
      dust.render('federationhealth', base.push(data), function(err, out) {
        $('#tab-overview').html(out);
        $('#ui-tabs a[href="#tab-overview"]').tab('show');
      });
    }
  }

  function load_namenode_info() {
    var HELPERS = {
      'helper_lastcontact_tostring' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write('' + new Date(Date.now()-1000*Number(value)));
      }
    };

    function workaround(r) {
      function node_map_to_array(nodes) {
        var res = [];
        for (var n in nodes) {
          var p = nodes[n];
          p.name = n;
          res.push(p);
        }
        return res;
      }

      function capitalise(string) {
          return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
      }

      function augment_namenodes(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          var n = nodes[i];
          n.usedPercentage = Math.round(n.used * 1.0 / n.totalSpace * 100);
          n.title = "Unavailable";
          n.iconState = "unavailable";
          if (n.isSafeMode === true) {
            n.title = capitalise(n.state) + " (safe mode)"
            n.iconState = "safemode";
          } else if (n.state === "ACTIVE") {
            n.title = capitalise(n.state);
            n.iconState = "active";
          } else if (nodes[i].state === "STANDBY") {
            n.title = capitalise(n.state);
            n.iconState = "standby";
          } else if (nodes[i].state === "UNAVAILABLE") {
            n.title = capitalise(n.state);
            n.iconState = "unavailable";
          } else if (nodes[i].state === "DISABLED") {
            n.title = capitalise(n.state);
            n.iconState = "disabled";
          }
          if (n.namenodeId === "null") {
            n.namenodeId = "";
          }
        }
      }

      r.Nameservices = node_map_to_array(JSON.parse(r.Nameservices));
      augment_namenodes(r.Nameservices);
      r.Namenodes = node_map_to_array(JSON.parse(r.Namenodes));
      augment_namenodes(r.Namenodes);
      return r;
    }

    $.get(
      '/jmx?qry=Hadoop:service=Router,name=FederationState',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('namenode-info', base.push(data), function(err, out) {
          $('#tab-namenode').html(out);
          $('#ui-tabs a[href="#tab-namenode"]').tab('show');
        });
      })).fail(ajax_error_handler);
  }

  function load_router_info() {
    var HELPERS = {
      'helper_lastcontact_tostring' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write('' + new Date(Date.now()-1000*Number(value)));
      }
    };

    function workaround(r) {
      function node_map_to_array(nodes) {
        var res = [];
        for (var n in nodes) {
          var p = nodes[n];
          p.name = n;
          res.push(p);
        }
        return res;
      }

      function capitalise(string) {
          return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
      }

      function augment_routers(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          var n = nodes[i];
          n.title = "Unavailable"
          n.iconState = "unavailable";
          if (n.status === "INITIALIZING") {
            n.title = capitalise(n.status);
            n.iconState = "active";
          } else if (n.status === "RUNNING") {
            n.title = capitalise(n.status);
            n.iconState = "active";
          } else if (n.status === "SAFEMODE") {
            n.title = capitalise(n.status);
            n.iconState = "safemode";
          } else if (n.status === "STOPPING") {
            n.title = capitalise(n.status);
            n.iconState = "unavailable";
          } else if (n.status === "SHUTDOWN") {
            n.title = capitalise(n.status);
            n.iconState = "unavailable";
          }
        }
      }

      r.Routers = node_map_to_array(JSON.parse(r.Routers));
      augment_routers(r.Routers);
      return r;
    }

    $.get(
      '/jmx?qry=Hadoop:service=Router,name=FederationState',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('router-info', base.push(data), function(err, out) {
          $('#tab-router').html(out);
          $('#ui-tabs a[href="#tab-router"]').tab('show');
        });
      })).fail(ajax_error_handler);
  }

  // TODO Copied directly from dfshealth.js; is there a way to import this function?
  function load_datanode_info() {

    var HELPERS = {
      'helper_relative_time' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        return chunk.write(moment().subtract(Number(value), 'seconds').format('YYYY-MM-DD HH:mm:ss'));
      },
      'helper_usage_bar' : function (chunk, ctx, bodies, params) {
        var value = dust.helpers.tap(params.value, chunk, ctx);
        var v = Number(value);
        var r = null;
        if (v < 70) {
          r = 'progress-bar-success';
        } else if (v < 85) {
          r = 'progress-bar-warning';
        } else {
          r = "progress-bar-danger";
        }
        return chunk.write(r);
      },
    };

    function workaround(r) {
      function node_map_to_array(nodes) {
        var res = [];
        for (var n in nodes) {
          var p = nodes[n];
          p.name = n;
          res.push(p);
        }
        return res;
      }

      function augment_live_nodes(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          var n = nodes[i];
          n.usedPercentage = Math.round((n.used + n.nonDfsUsedSpace) * 1.0 / n.capacity * 100);
          if (n.adminState === "In Service") {
            n.state = "alive";
          } else if (nodes[i].adminState === "Decommission In Progress") {
            n.state = "decommissioning";
          } else if (nodes[i].adminState === "Decommissioned") {
            n.state = "decommissioned";
          }
        }
      }

      function augment_dead_nodes(nodes) {
        for (var i = 0, e = nodes.length; i < e; ++i) {
          if (nodes[i].decommissioned) {
            nodes[i].state = "down-decommissioned";
          } else {
            nodes[i].state = "down";
          }
        }
      }

      r.LiveNodes = node_map_to_array(JSON.parse(r.LiveNodes));
      augment_live_nodes(r.LiveNodes);
      r.DeadNodes = node_map_to_array(JSON.parse(r.DeadNodes));
      augment_dead_nodes(r.DeadNodes);
      r.DecomNodes = node_map_to_array(JSON.parse(r.DecomNodes));
      return r;
    }

    $.get(
      '/jmx?qry=Hadoop:service=NameNode,name=NameNodeInfo',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('datanode-info', base.push(data), function(err, out) {
          $('#tab-datanode').html(out);
          $('#table-datanodes').dataTable( {
            'lengthMenu': [ [25, 50, 100, -1], [25, 50, 100, "All"] ],
            'columns': [
              { 'orderDataType': 'ng-value', 'searchable': true },
              { 'orderDataType': 'ng-value', 'type': 'numeric' },
              { 'orderDataType': 'ng-value', 'type': 'numeric' },
              { 'orderDataType': 'ng-value', 'type': 'numeric'}
            ]});
          $('#ui-tabs a[href="#tab-datanode"]').tab('show');
        });
      })).fail(ajax_error_handler);
  }

  function load_mount_table() {
    var HELPERS = {}

    function workaround(resource) {
      function augment_read_only(mountTable) {
        for (var i = 0, e = mountTable.length; i < e; ++i) {
          if (mountTable[i].readonly == true) {
            mountTable[i].readonly = "true"
          } else {
            mountTable[i].readonly = "false"
          }
        }
      }

      resource.MountTable = JSON.parse(resource.MountTable)
      augment_read_only(resource.MountTable)
      return resource;
    }

    $.get(
      '/jmx?qry=Hadoop:service=Router,name=FederationState',
      guard_with_startup_progress(function (resp) {
        var data = workaround(resp.beans[0]);
        var base = dust.makeBase(HELPERS);
        dust.render('mounttable', base.push(data), function(err, out) {
          $('#tab-mounttable').html(out);
          $('#ui-tabs a[href="#tab-mounttable"]').tab('show');
        });
      })).fail(ajax_error_handler);
  }

  function toTitleCase(str) {
    return str.replace(/\w\S*/g, function(txt){
        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
      });
  }

  function show_err_msg(msg) {
    $('#alert-panel-body').html(msg);
    $('#alert-panel').show();
  }

  function ajax_error_handler(url, jqxhr, text, err) {
    show_err_msg('
Failed to retrieve data from ' + url + ', cause: ' + err + '
');
  }

  function guard_with_startup_progress(fn) {
    return function() {
      try {
        fn.apply(this, arguments);
      } catch (err) {
        if (err instanceof TypeError) {
          show_err_msg('Router error: ' + err);
        }
      }
    };
  }

  function load_page() {
    var hash = window.location.hash;
    switch(hash) {
      case "#tab-overview":
        load_overview();
        break;
      case "#tab-namenode":
        load_namenode_info();
        break;
      case "#tab-router":
        load_router_info();
        break;
      case "#tab-datanode":
        load_datanode_info();
        break;
      case "#tab-mounttable":
        load_mount_table();
        break;
      default:
        window.location.hash = "tab-overview";
        break;
    }
  }
  load_page();

  $(window).bind('hashchange', function () {
    load_page();
  });
})();
<|endoftext|>


<commit_before>/**
 * A text field, properties:
 *
 * - label
 * - name
 * - .. any other allowed by input
 *
 * @jsx React.DOM
 */
define([
    'react',
    'build/Components/Panels/Page/Form/label.react',
    'jquery.fileupload'
], function (React, Label) {

    var File = React.createClass({
        handleDeleteFile: function () {
            this.props.deleteFileWithRef(this.props.file.ref);
        },
        render: function () {
            var failed = '';
            if (this.props.file != null && this.props.file.failed) {
                failed = failed ;
            }
            return

                {this.props.file.name + " "}
                {failed}
                delete

;
        }
    });

    return React.createClass({
        failedRefId: 0,
        getInitialState: function () {
            return {
                files: this.props.item.getFiles()
            }
        },
        addFailedFile: function (data) {
            this.state.files.push({
                "ref": "failed_" + ++this.failedRefId,
                "failed": true,
                "name": data.files[0].name
            });
            this.setState(this.state);
        }, componentDidMount: function () {
            //Dynamically add the element since we don't want to have React to bind its events to it
            var uploadElem = $('<input type="file" name="file" data-name="file" />');

            if(this.props.item.isUploadForTinymce()) {
                uploadElem = $('<input id="uploader-tinymce" type="file" name="file-uploader-tinymce" data-name="file" />');
            }
            if (this.props.item.isMultiple()) {
                uploadElem = $('<input multiple="multiple" type="file" name="file" data-name="file" />');
            }
            var node = $(this.refs.file.getDOMNode()).append(uploadElem);

            node.fileupload({
                url: this.props.item.getTarget(),

                /**
                 * Starting an upload
                 * @param e
                 * @param data
                 */
                send: function (e, data) {
                    if (!this.props.item.isMultiple()) {
                        this.state.files = [];
                        this.setState(this.state);
                    }
                }.bind(this),

                /**
                 * Success
                 * @param e
                 * @param data
                 */
                done: function (e, data) {
                    if (data.result.type == 'file') {
                        this.state.files.push(data.result.file);
                        this.setState(this.state);
                    } else if (data.files[0].name) {
                        this.addFailedFile(data);
                    }
                }.bind(this),

                /**
                 * Failed
                 * @param e
                 * @param data
                 */
                fail: function (e, data) {
                    if (data.files[0].name) {
                        this.addFailedFile(data);
                    }
                }.bind(this)
            });
        },
        /**
         * Hidden input that is submitted
         * @returns {Array}
         */
        getHiddenInput: function () {
            var hidden = [];
            for (var i in this.state.files) {
                if (this.state.files[i] != null && !this.state.files[i].failed) {
                    if (this.props.item.isMultiple) {
                        hidden.push(<input key={this.state.files[i].ref} type="hidden" name={this.props.item.getName()} value={this.state.files[i].ref} />);
                    } else {
                        hidden = [
                            <input key={this.state.files[i].ref} type="hidden" name={this.props.item.getName()} value={this.state.files[i].ref} />
                        ];
                    }
                }
            }
            return hidden;
        },
        deleteFileWithRef: function (ref) {
            this.state.files = this.state.files.remove(function (file) {
                return file.ref == ref;
            });
            this.setState(this.state);
        },
        getFiles: function () {
            return this.state.files.map(function (file) {
                return <File deleteFileWithRef={this.deleteFileWithRef} file={file} />;
            }.bind(this));
        }, render: function () {

            console.log(this.props.item.isUploadForTinymce());

            var hidden = this.getHiddenInput();
            var files = this.getFiles();

            var style = {};
            if(this.props.item.isUploadForTinymce()) {
                style = {display: 'none'}
            }
            return (

{files}

                    {hidden}

            );
        }
    });

});<commit_msg>Removed de console.log<commit_after>/**
 * A text field, properties:
 *
 * - label
 * - name
 * - .. any other allowed by input
 *
 * @jsx React.DOM
 */
define([
    'react',
    'build/Components/Panels/Page/Form/label.react',
    'jquery.fileupload'
], function (React, Label) {

    var File = React.createClass({
        handleDeleteFile: function () {
            this.props.deleteFileWithRef(this.props.file.ref);
        },
        render: function () {
            var failed = '';
            if (this.props.file != null && this.props.file.failed) {
                failed = failed ;
            }
            return

                {this.props.file.name + " "}
                {failed}
                delete

;
        }
    });

    return React.createClass({
        failedRefId: 0,
        getInitialState: function () {
            return {
                files: this.props.item.getFiles()
            }
        },
        addFailedFile: function (data) {
            this.state.files.push({
                "ref": "failed_" + ++this.failedRefId,
                "failed": true,
                "name": data.files[0].name
            });
            this.setState(this.state);
        }, componentDidMount: function () {
            //Dynamically add the element since we don't want to have React to bind its events to it
            var uploadElem = $('<input type="file" name="file" data-name="file" />');

            if(this.props.item.isUploadForTinymce()) {
                uploadElem = $('<input id="uploader-tinymce" type="file" name="file-uploader-tinymce" data-name="file" />');
            }
            if (this.props.item.isMultiple()) {
                uploadElem = $('<input multiple="multiple" type="file" name="file" data-name="file" />');
            }
            var node = $(this.refs.file.getDOMNode()).append(uploadElem);

            node.fileupload({
                url: this.props.item.getTarget(),

                /**
                 * Starting an upload
                 * @param e
                 * @param data
                 */
                send: function (e, data) {
                    if (!this.props.item.isMultiple()) {
                        this.state.files = [];
                        this.setState(this.state);
                    }
                }.bind(this),

                /**
                 * Success
                 * @param e
                 * @param data
                 */
                done: function (e, data) {
                    if (data.result.type == 'file') {
                        this.state.files.push(data.result.file);
                        this.setState(this.state);
                    } else if (data.files[0].name) {
                        this.addFailedFile(data);
                    }
                }.bind(this),

                /**
                 * Failed
                 * @param e
                 * @param data
                 */
                fail: function (e, data) {
                    if (data.files[0].name) {
                        this.addFailedFile(data);
                    }
                }.bind(this)
            });
        },
        /**
         * Hidden input that is submitted
         * @returns {Array}
         */
        getHiddenInput: function () {
            var hidden = [];
            for (var i in this.state.files) {
                if (this.state.files[i] != null && !this.state.files[i].failed) {
                    if (this.props.item.isMultiple) {
                        hidden.push(<input key={this.state.files[i].ref} type="hidden" name={this.props.item.getName()} value={this.state.files[i].ref} />);
                    } else {
                        hidden = [
                            <input key={this.state.files[i].ref} type="hidden" name={this.props.item.getName()} value={this.state.files[i].ref} />
                        ];
                    }
                }
            }
            return hidden;
        },
        deleteFileWithRef: function (ref) {
            this.state.files = this.state.files.remove(function (file) {
                return file.ref == ref;
            });
            this.setState(this.state);
        },
        getFiles: function () {
            return this.state.files.map(function (file) {
                return <File deleteFileWithRef={this.deleteFileWithRef} file={file} />;
            }.bind(this));
        }, render: function () {

            var hidden = this.getHiddenInput();
            var files = this.getFiles();

            var style = {};
            if(this.props.item.isUploadForTinymce()) {
                style = {display: 'none'}
            }
            return (

{files}

                    {hidden}

            );
        }
    });

});<|endoftext|>


<commit_before>/* @flow */
import React, { Component } from 'react';
import Box from 'grommet/components/Box';
import Markdown from 'grommet/components/Markdown';
import Button from 'grommet/components/Button';
import Footer from 'grommet/components/Footer';
import { findDOMNode } from 'react-dom';
import type { GrommetBoxTypes$Size } from 'grommet';
import ImageBox from './ImageBox';
import WrapperBox from './WrapperBox';
import ImageWrapper from './ImageWrapper';
import ContentBox from './ContentBox';
import calculateAnimation from './animationUtils';
import {
  selectContentClassname,
  selectAlign,
  selectImage,
  selectContent,
  selectImageSize
} from './selectors';

export type Image = { path: string };
export type Justification = 'left' | 'right';
export type Color = 'White' | 'Black';
type CarouselItem = {
  image: {
    path: string
  },
  color: Color,
  justification: Justification
}

type Props = {
  carousel: CarouselItem[],
  imageSize: GrommetBoxTypes$Size,
  content: string,
  button: ?{
    label: string,
    path: string,
  }
}

type State = {
  scale: number,
  opacity: number,
  isMobile: boolean,
  currentSlide: ?CarouselItem
}

class BlockMarquee extends Component { // eslint-disable-line react/prefer-stateless-function
  constructor() {
    super();
    this.handleScroll = this.handleScroll.bind(this);
    this.handleResize = this.handleResize.bind(this);
    this.setRandomImage = this.setRandomImage.bind(this);
    this.heroRef = null;
    this.state = {
      scale: 1,
      isMobile: window.innerWidth <= 720,
      opacity: 1,
      currentSlide: null
    };
  }
  state: State;
  componentWillMount() {
    this.setRandomImage();
  }
  componentDidMount() {
    if (typeof window !== 'undefined') {
      window.addEventListener('scroll', this.handleScroll);
      window.addEventListener('resize', this.handleResize);
    }
  }
  componentWillUnmount() {
    if (typeof window !== 'undefined') {
      window.addEventListener('scroll', this.handleScroll);
      window.addEventListener('resize', this.handleResize);
    }
  }
  setRandomImage() {
    const { carousel, imageSize, content, button } = this.props;
    const { scale, opacity, isMobile } = this.state;
    const randomIndex = Math.floor(Math.random() * carousel.length);
    const currentSlide = carousel[randomIndex];
    this.setState({
      currentSlide
    });
  }
  handleScroll: () => void;
  handleResize: () => void;
  setRandomImage: () => void;
  heroRef: ?HTMLElement;
  props: Props;
  handleScroll() {
    if (this.heroRef) {
      const node = findDOMNode(this.heroRef);
      if (node) {
        const state = calculateAnimation(node);
        window.requestAnimationFrame(() => {
          this.setState({
            ...state,
          });
        });
      }
    }
  }
  handleResize() {
    const isMobile = window.innerWidth <= 720;
    this.setState({
      isMobile,
    });
  }
  render() {
    const { scale, opacity, isMobile, currentSlide } = this.state;
    if (!currentSlide) return null;

    const { carousel, imageSize, content, button } = this.props;
    const { image, color, justification } = currentSlide;
    const align = selectAlign(justification);
    const size = selectImageSize(imageSize);
    const contentClassName = selectContentClassname(color, isMobile);
    const imagePath = selectImage(image);
    const textContent = selectContent(content);

    return (
      <WrapperBox size={size} id="grommet-cms-content-blocks--marquee">
        <ImageWrapper id="grommet-cms-content-blocks--marquee__image-wrapper" size={size}>
          <ImageBox
            id="grommet-cms-content-blocks--marquee__image-box"
            ref={(ref) => {
              this.heroRef = ref;
            }}
            size={size}
            style={{ transform: `scale(${scale})` }}
            scale={scale}
            texture={imagePath}
          />
        </ImageWrapper>
        <ContentBox
          id="grommet-cms-content-blocks--marquee__content-box"
          justify="center"
          pad="large"
          align={align}
          style={!isMobile && { opacity }}
        >
          <Box
            pad="large"
            justify="center"
            className={contentClassName}
          >
            <Markdown
              content={textContent}
              components={{
                p: { props: { size: 'large', margin: 'small' } },
                h1: { props: { strong: true, margin: 'small' } },
                h2: { props: { strong: true } },
              }}
            />
            <Footer pad={{ vertical: 'medium' }} className="grommetux-background-color-index--light">
              {button && button.label && button.path &&
                <Button
                  primary
                  {...button}
                />
              }
            </Footer>
          </Box>
        </ContentBox>
      </WrapperBox>
    );
  }
}

export default BlockMarquee;
<commit_msg>Fix: remove block marquee event listeners on unmount<commit_after>/* @flow */
import React, { Component } from 'react';
import Box from 'grommet/components/Box';
import Markdown from 'grommet/components/Markdown';
import Button from 'grommet/components/Button';
import Footer from 'grommet/components/Footer';
import { findDOMNode } from 'react-dom';
import type { GrommetBoxTypes$Size } from 'grommet';
import ImageBox from './ImageBox';
import WrapperBox from './WrapperBox';
import ImageWrapper from './ImageWrapper';
import ContentBox from './ContentBox';
import calculateAnimation from './animationUtils';
import {
  selectContentClassname,
  selectAlign,
  selectImage,
  selectContent,
  selectImageSize
} from './selectors';

export type Image = { path: string };
export type Justification = 'left' | 'right';
export type Color = 'White' | 'Black';
type CarouselItem = {
  image: {
    path: string
  },
  color: Color,
  justification: Justification
}

type Props = {
  carousel: CarouselItem[],
  imageSize: GrommetBoxTypes$Size,
  content: string,
  button: ?{
    label: string,
    path: string,
  }
}

type State = {
  scale: number,
  opacity: number,
  isMobile: boolean,
  currentSlide: ?CarouselItem
}

class BlockMarquee extends Component { // eslint-disable-line react/prefer-stateless-function
  constructor() {
    super();
    this.handleScroll = this.handleScroll.bind(this);
    this.handleResize = this.handleResize.bind(this);
    this.setRandomImage = this.setRandomImage.bind(this);
    this.heroRef = null;
    this.state = {
      scale: 1,
      isMobile: window.innerWidth <= 720,
      opacity: 1,
      currentSlide: null
    };
  }
  state: State;
  componentWillMount() {
    this.setRandomImage();
  }
  componentDidMount() {
    if (typeof window !== 'undefined') {
      window.addEventListener('scroll', this.handleScroll);
      window.addEventListener('resize', this.handleResize);
    }
  }
  componentWillUnmount() {
    if (typeof window !== 'undefined') {
      window.removeEventListener('scroll', this.handleScroll);
      window.removeEventListener('resize', this.handleResize);
    }
  }
  setRandomImage() {
    const { carousel, imageSize, content, button } = this.props;
    const { scale, opacity, isMobile } = this.state;
    const randomIndex = Math.floor(Math.random() * carousel.length);
    const currentSlide = carousel[randomIndex];
    this.setState({
      currentSlide
    });
  }
  handleScroll: () => void;
  handleResize: () => void;
  setRandomImage: () => void;
  heroRef: ?HTMLElement;
  props: Props;
  handleScroll() {
    if (this.heroRef) {
      const node = findDOMNode(this.heroRef);
      if (node) {
        const state = calculateAnimation(node);
        window.requestAnimationFrame(() => {
          this.setState({
            ...state,
          });
        });
      }
    }
  }
  handleResize() {
    const isMobile = window.innerWidth <= 720;
    this.setState({
      isMobile,
    });
  }
  render() {
    const { scale, opacity, isMobile, currentSlide } = this.state;
    if (!currentSlide) return null;

    const { carousel, imageSize, content, button } = this.props;
    const { image, color, justification } = currentSlide;
    const align = selectAlign(justification);
    const size = selectImageSize(imageSize);
    const contentClassName = selectContentClassname(color, isMobile);
    const imagePath = selectImage(image);
    const textContent = selectContent(content);

    return (
      <WrapperBox size={size} id="grommet-cms-content-blocks--marquee">
        <ImageWrapper id="grommet-cms-content-blocks--marquee__image-wrapper" size={size}>
          <ImageBox
            id="grommet-cms-content-blocks--marquee__image-box"
            ref={(ref) => {
              this.heroRef = ref;
            }}
            size={size}
            style={{ transform: `scale(${scale})` }}
            scale={scale}
            texture={imagePath}
          />
        </ImageWrapper>
        <ContentBox
          id="grommet-cms-content-blocks--marquee__content-box"
          justify="center"
          pad="large"
          align={align}
          style={!isMobile && { opacity }}
        >
          <Box
            pad="large"
            justify="center"
            className={contentClassName}
          >
            <Markdown
              content={textContent}
              components={{
                p: { props: { size: 'large', margin: 'small' } },
                h1: { props: { strong: true, margin: 'small' } },
                h2: { props: { strong: true } },
              }}
            />
            <Footer pad={{ vertical: 'medium' }} className="grommetux-background-color-index--light">
              {button && button.label && button.path &&
                <Button
                  primary
                  {...button}
                />
              }
            </Footer>
          </Box>
        </ContentBox>
      </WrapperBox>
    );
  }
}

export default BlockMarquee;
<|endoftext|>


<commit_before>/*
 * This file is part of Invenio.
 * Copyright (C) 2020 CERN.
 *
 * Invenio is free software; you can redistribute it and/or modify it
 * under the terms of the MIT License; see LICENSE file for more details.
 */

import Overridable from "react-overridable";
import React, { useContext } from "react";
import {
  Count,
  LayoutSwitcher,
  Pagination,
  ResultsMultiLayout,
  Sort,
  ResultsList,
  ResultsGrid,
  ResultsPerPage,
} from "react-searchkit";
import { Grid } from "semantic-ui-react";
import { SearchConfigurationContext } from "./context";

export const Results = ({ currentResultsState = {} }) => {
  const { total } = currentResultsState.data;
  const {
    sortOptions,
    layoutOptions,
    paginationOptions,
    sortOrderDisabled,
  } = useContext(SearchConfigurationContext);
  let multipleLayouts = layoutOptions.listView && layoutOptions.gridView;
  return (
    (total || null) && (
      <Overridable
        id={"SearchApp.results"}
        {...{
          currentResultsState,
          sortOptions,
          layoutOptions,
          paginationOptions,
        }}
      >
        <Grid relaxed>
          <Grid.Row verticalAlign="middle">
            <Grid.Column width={multipleLayouts ? 5 : 8}>
              <Count label={(cmp) => <>{cmp} result(s) found</>} />

            </Grid.Column>
            <Grid.Column width={4} textAlign="right">
              <ResultsPerPage
                values={paginationOptions.resultsPerPage}
                label={(cmp) => <> {cmp} results per page</>}
                defaultValue={paginationOptions.defaultValue}
              />
            </Grid.Column>
            <Grid.Column width={4} textAlign="right">
              {sortOptions && (
                <Overridable id={"SearchApp.sort"} options={sortOptions}>
                  <Sort
                    sortOrderDisabled={sortOrderDisabled || false}
                    values={sortOptions}
                    label={(cmp) => <>sort by {cmp}</>}
                  />
                </Overridable>
              )}
            </Grid.Column>
            {multipleLayouts ? (
              <Grid.Column width={3} textAlign="right">
                <LayoutSwitcher defaultLayout="list" />
              </Grid.Column>
            ) : null}
          </Grid.Row>
          <Grid.Row>
            <Grid.Column>
              {multipleLayouts ? (
                <ResultsMultiLayout />
              ) : layoutOptions.listView ? (
                <ResultsList />
              ) : (
                <ResultsGrid />
              )}
            </Grid.Column>
          </Grid.Row>
          <Grid.Row verticalAlign="middle" textAlign="center">
            <Grid.Column>
              <Pagination />
            </Grid.Column>
          </Grid.Row>
        </Grid>
      </Overridable>
    )
  );
};
<commit_msg>search: removes 'sort by' label<commit_after>/*
 * This file is part of Invenio.
 * Copyright (C) 2020 CERN.
 *
 * Invenio is free software; you can redistribute it and/or modify it
 * under the terms of the MIT License; see LICENSE file for more details.
 */

import Overridable from "react-overridable";
import React, { useContext } from "react";
import {
  Count,
  LayoutSwitcher,
  Pagination,
  ResultsMultiLayout,
  Sort,
  ResultsList,
  ResultsGrid,
  ResultsPerPage,
} from "react-searchkit";
import { Grid } from "semantic-ui-react";
import { SearchConfigurationContext } from "./context";

export const Results = ({ currentResultsState = {} }) => {
  const { total } = currentResultsState.data;
  const {
    sortOptions,
    layoutOptions,
    paginationOptions,
    sortOrderDisabled,
  } = useContext(SearchConfigurationContext);
  let multipleLayouts = layoutOptions.listView && layoutOptions.gridView;
  return (
    (total || null) && (
      <Overridable
        id={"SearchApp.results"}
        {...{
          currentResultsState,
          sortOptions,
          layoutOptions,
          paginationOptions,
        }}
      >
        <Grid relaxed>
          <Grid.Row verticalAlign="middle">
            <Grid.Column width={multipleLayouts ? 5 : 8}>
              <Count label={(cmp) => <>{cmp} result(s) found</>} />

            </Grid.Column>
            <Grid.Column width={4} textAlign="right">
              <ResultsPerPage
                values={paginationOptions.resultsPerPage}
                label={(cmp) => <> {cmp} results per page</>}
                defaultValue={paginationOptions.defaultValue}
              />
            </Grid.Column>
            <Grid.Column width={4} textAlign="right">
              {sortOptions && (
                <Overridable id={"SearchApp.sort"} options={sortOptions}>
                  <Sort
                    sortOrderDisabled={sortOrderDisabled || false}
                    values={sortOptions}
                  />
                </Overridable>
              )}
            </Grid.Column>
            {multipleLayouts ? (
              <Grid.Column width={3} textAlign="right">
                <LayoutSwitcher defaultLayout="list" />
              </Grid.Column>
            ) : null}
          </Grid.Row>
          <Grid.Row>
            <Grid.Column>
              {multipleLayouts ? (
                <ResultsMultiLayout />
              ) : layoutOptions.listView ? (
                <ResultsList />
              ) : (
                <ResultsGrid />
              )}
            </Grid.Column>
          </Grid.Row>
          <Grid.Row verticalAlign="middle" textAlign="center">
            <Grid.Column>
              <Pagination />
            </Grid.Column>
          </Grid.Row>
        </Grid>
      </Overridable>
    )
  );
};
<|endoftext|>


<commit_before>/*jslint es5: true, nomen: true, node: true */

/*
 * #%L
 * %%
 * Copyright (C) 2011 - 2017 BMW Car IT GmbH
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
var Promise = require("../../../global/Promise");
var WebSocket = require("../../../global/WebSocketNode");
var Typing = require("../../util/Typing");
var JSONSerializer = require("../../util/JSONSerializer");
var LongTimer = require("../../util/LongTimer");
var Util = require("../../util/UtilInternal");
var LoggerFactory = require("../../system/LoggerFactory");
var log = LoggerFactory.getLogger("joynr.messaging.websocket.SharedWebSocket");
/**
 * @param address
 * @param {WebSocketAddress}
 *            address to which messages are sent on the clustercontroller.
 * @returns {String} a url
 */
function webSocketAddressToUrl(address) {
    var url = address.protocol + "://" + address.host + ":" + address.port;
    if (address.path) {
        url += address.path;
    }
    return url;
}

/**
 * @param {WebSocket}
 *            websocket to use for the initialization
 * @param {String}
 *            localAddress address used by the cluster controller to contact this
 *            libjoynr
 */
function initializeConnection(websocket, localAddress) {
    websocket.send(websocket.encodeString(JSON.stringify(localAddress)), { binary: true });
}

/**
 * @param {WebSocket}
 *            websocket to use for sending messages
 * @param {Array}
 *            queuedMessages
 */
function sendQueuedMessages(websocket, queuedMessages) {
    var queued;
    while (queuedMessages.length) {
        queued = queuedMessages.shift();
        try {
            websocket.send(websocket.marshalJoynrMessage(queued.message), { binary: true });
            queued.resolve();
            // Error is thrown if the socket is no longer open
        } catch (e) {
            // so add the message back to the front of the queue
            queuedMessages.unshift(queued);
            throw e;
        }
    }
}

function sendMessage(websocket, joynrMessage, queuedMessages) {
    var deferred = Util.createDeferred();

    if (websocket.readyState === WebSocket.OPEN) {
        try {
            websocket.send(websocket.marshalJoynrMessage(joynrMessage), { binary: true });
            deferred.resolve();
            // Error is thrown if the socket is no longer open, so requeue to the front
        } catch (e) {
            // add the message back to the front of the queue
            queuedMessages.unshift({
                message: joynrMessage,
                resolve: deferred.resolve
            });
            throw e;
        }
    } else {
        // push new messages onto the back of the queue
        queuedMessages.push({
            message: joynrMessage,
            resolve: deferred.resolve
        });
    }
    return deferred.promise;
}

/**
 * @name SharedWebSocket
 * @constructor
 * @param {Object}
 *            settings
 * @param {WebSocketClientAddress}
 *            settings.localAddress address used by the websocket server to contact this
 *            client. This address is used in in the init phase on the websocket, and
 *            then registered with the message router on the remote side
 * @param {WebSocketAddress}
 *            settings.remoteAddress to which messages are sent on the websocket server.
 * @param {Object} settings.provisioning
 * @param {Number} settings.provisioning.reconnectSleepTimeMs
 * @param {Object} settings.keychain
 */
var SharedWebSocket = function SharedWebSocket(settings) {
    Typing.checkProperty(settings, "Object", "settings");
    Typing.checkProperty(settings.localAddress, "WebSocketClientAddress", "localAddress");
    Typing.checkProperty(settings.remoteAddress, "WebSocketAddress", "remoteAddress");

    var websocket = null;
    var provisioning = settings.provisioning || {};
    var reconnectSleepTimeMs = provisioning.reconnectSleepTimeMs || 1000; // default value = 1000ms
    var useUnencryptedTls = provisioning.useUnencryptedTls || true; // default to unencrypted Tls communication
    var localAddress = settings.localAddress;
    var remoteUrl = webSocketAddressToUrl(settings.remoteAddress);
    var onmessageCallback = null;
    var queuedMessages = [];
    var onOpen;
    var onError;
    var onClose;
    var closed = false;
    var reconnectTimer;

    var resetConnection = function resetConnection() {
        reconnectTimer = undefined;
        if (closed) {
            return;
        }
        websocket = new WebSocket(remoteUrl, settings.keychain, useUnencryptedTls);
        websocket.onopen = onOpen;
        websocket.onclose = onClose;
        websocket.onerror = onError;
        if (onmessageCallback !== null) {
            websocket.onmessage = onmessageCallback;
        }
    };

    onError = function onError(event) {
        if (closed) {
            return;
        }
        log.error(
            "error in websocket: " +
                event.code +
                (event.reason !== undefined ? " reason: " + event.reason : "") +
                ". Resetting connection."
        );
        if (reconnectTimer !== undefined) {
            LongTimer.clearTimeout(reconnectTimer);
        }
        if (websocket) {
            websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
            websocket = null;
        }
        reconnectTimer = LongTimer.setTimeout(resetConnection, reconnectSleepTimeMs);
    };

    onClose = function onClose(event) {
        if (closed) {
            return;
        }
        if (event.code !== SharedWebSocket.EVENT_CODE_SHUTDOWN) {
            log.info(
                "connection closed unexpectedly. code: " +
                    event.code +
                    " reason: " +
                    event.reason +
                    ". Trying to reconnect..."
            );
            if (reconnectTimer !== undefined) {
                LongTimer.clearTimeout(reconnectTimer);
            }
            if (websocket) {
                websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
                websocket = null;
            }
            reconnectTimer = LongTimer.setTimeout(resetConnection, reconnectSleepTimeMs);
        } else {
            log.info("connection closed. reason: " + event.reason);
            if (websocket) {
                websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
                websocket = null;
            }
        }
    };

    // send all queued messages, requeuing to the front in case of a problem
    onOpen = function onOpen() {
        try {
            log.debug("connection opened.");
            initializeConnection(websocket, localAddress);
            sendQueuedMessages(websocket, queuedMessages);
        } catch (e) {
            resetConnection();
        }
    };

    resetConnection();

    /**
     * @name SharedWebSocket#send
     * @function
     * @param {JoynrMessage}
     *            joynrMessage the joynr message to transmit
     */
    this.send = function send(joynrMessage) {
        function sendMessageOnError(e1) {
            resetConnection();
        }

        log.debug(">>> OUTGOING >>> message with ID " + joynrMessage.msgId);
        return sendMessage(websocket, joynrMessage, queuedMessages).catch(sendMessageOnError);
    };

    /**
     * @name SharedWebSocket#close
     * @function
     */
    this.close = function close() {
        closed = true;
        if (reconnectTimer !== undefined) {
            LongTimer.clearTimeout(reconnectTimer);
            reconnectTimer = undefined;
        }
        if (websocket !== null) {
            websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
            websocket = null;
        }
    };

    // using the defineProperty syntax for onmessage to be able to keep
    // the same API as WebSocket but have a setter function called when
    // the attribute is set.
    Object.defineProperty(this, "onmessage", {
        set: function(newCallback) {
            if (typeof newCallback === "function") {
                onmessageCallback = function(data) {
                    websocket.unmarshalJoynrMessage(data, newCallback);
                };
                websocket.onmessage = onmessageCallback;
            } else {
                throw new Error("onmessage callback must be a function, but instead was of type " + typeof newCallback);
            }
        },
        get: function() {
            return onmessageCallback;
        },
        enumerable: false,
        configurable: false
    });
};
SharedWebSocket.EVENT_CODE_SHUTDOWN = 4000;
module.exports = SharedWebSocket;
<commit_msg>[JS] SharedWebSocket.send remove unnecessary function<commit_after>/*jslint es5: true, nomen: true, node: true */

/*
 * #%L
 * %%
 * Copyright (C) 2011 - 2017 BMW Car IT GmbH
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
var Promise = require("../../../global/Promise");
var WebSocket = require("../../../global/WebSocketNode");
var Typing = require("../../util/Typing");
var JSONSerializer = require("../../util/JSONSerializer");
var LongTimer = require("../../util/LongTimer");
var Util = require("../../util/UtilInternal");
var LoggerFactory = require("../../system/LoggerFactory");
var log = LoggerFactory.getLogger("joynr.messaging.websocket.SharedWebSocket");
/**
 * @param address
 * @param {WebSocketAddress}
 *            address to which messages are sent on the clustercontroller.
 * @returns {String} a url
 */
function webSocketAddressToUrl(address) {
    var url = address.protocol + "://" + address.host + ":" + address.port;
    if (address.path) {
        url += address.path;
    }
    return url;
}

/**
 * @param {WebSocket}
 *            websocket to use for the initialization
 * @param {String}
 *            localAddress address used by the cluster controller to contact this
 *            libjoynr
 */
function initializeConnection(websocket, localAddress) {
    websocket.send(websocket.encodeString(JSON.stringify(localAddress)), { binary: true });
}

/**
 * @param {WebSocket}
 *            websocket to use for sending messages
 * @param {Array}
 *            queuedMessages
 */
function sendQueuedMessages(websocket, queuedMessages) {
    var queued;
    while (queuedMessages.length) {
        queued = queuedMessages.shift();
        try {
            websocket.send(websocket.marshalJoynrMessage(queued.message), { binary: true });
            queued.resolve();
            // Error is thrown if the socket is no longer open
        } catch (e) {
            // so add the message back to the front of the queue
            queuedMessages.unshift(queued);
            throw e;
        }
    }
}

function sendMessage(websocket, joynrMessage, queuedMessages) {
    var deferred = Util.createDeferred();

    if (websocket.readyState === WebSocket.OPEN) {
        try {
            websocket.send(websocket.marshalJoynrMessage(joynrMessage), { binary: true });
            deferred.resolve();
            // Error is thrown if the socket is no longer open, so requeue to the front
        } catch (e) {
            // add the message back to the front of the queue
            queuedMessages.unshift({
                message: joynrMessage,
                resolve: deferred.resolve
            });
            throw e;
        }
    } else {
        // push new messages onto the back of the queue
        queuedMessages.push({
            message: joynrMessage,
            resolve: deferred.resolve
        });
    }
    return deferred.promise;
}

/**
 * @name SharedWebSocket
 * @constructor
 * @param {Object}
 *            settings
 * @param {WebSocketClientAddress}
 *            settings.localAddress address used by the websocket server to contact this
 *            client. This address is used in in the init phase on the websocket, and
 *            then registered with the message router on the remote side
 * @param {WebSocketAddress}
 *            settings.remoteAddress to which messages are sent on the websocket server.
 * @param {Object} settings.provisioning
 * @param {Number} settings.provisioning.reconnectSleepTimeMs
 * @param {Object} settings.keychain
 */
var SharedWebSocket = function SharedWebSocket(settings) {
    Typing.checkProperty(settings, "Object", "settings");
    Typing.checkProperty(settings.localAddress, "WebSocketClientAddress", "localAddress");
    Typing.checkProperty(settings.remoteAddress, "WebSocketAddress", "remoteAddress");

    var websocket = null;
    var provisioning = settings.provisioning || {};
    var reconnectSleepTimeMs = provisioning.reconnectSleepTimeMs || 1000; // default value = 1000ms
    var useUnencryptedTls = provisioning.useUnencryptedTls || true; // default to unencrypted Tls communication
    var localAddress = settings.localAddress;
    var remoteUrl = webSocketAddressToUrl(settings.remoteAddress);
    var onmessageCallback = null;
    var queuedMessages = [];
    var onOpen;
    var onError;
    var onClose;
    var closed = false;
    var reconnectTimer;

    var resetConnection = function resetConnection() {
        reconnectTimer = undefined;
        if (closed) {
            return;
        }
        websocket = new WebSocket(remoteUrl, settings.keychain, useUnencryptedTls);
        websocket.onopen = onOpen;
        websocket.onclose = onClose;
        websocket.onerror = onError;
        if (onmessageCallback !== null) {
            websocket.onmessage = onmessageCallback;
        }
    };

    onError = function onError(event) {
        if (closed) {
            return;
        }
        log.error(
            "error in websocket: " +
                event.code +
                (event.reason !== undefined ? " reason: " + event.reason : "") +
                ". Resetting connection."
        );
        if (reconnectTimer !== undefined) {
            LongTimer.clearTimeout(reconnectTimer);
        }
        if (websocket) {
            websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
            websocket = null;
        }
        reconnectTimer = LongTimer.setTimeout(resetConnection, reconnectSleepTimeMs);
    };

    onClose = function onClose(event) {
        if (closed) {
            return;
        }
        if (event.code !== SharedWebSocket.EVENT_CODE_SHUTDOWN) {
            log.info(
                "connection closed unexpectedly. code: " +
                    event.code +
                    " reason: " +
                    event.reason +
                    ". Trying to reconnect..."
            );
            if (reconnectTimer !== undefined) {
                LongTimer.clearTimeout(reconnectTimer);
            }
            if (websocket) {
                websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
                websocket = null;
            }
            reconnectTimer = LongTimer.setTimeout(resetConnection, reconnectSleepTimeMs);
        } else {
            log.info("connection closed. reason: " + event.reason);
            if (websocket) {
                websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
                websocket = null;
            }
        }
    };

    // send all queued messages, requeuing to the front in case of a problem
    onOpen = function onOpen() {
        try {
            log.debug("connection opened.");
            initializeConnection(websocket, localAddress);
            sendQueuedMessages(websocket, queuedMessages);
        } catch (e) {
            resetConnection();
        }
    };

    resetConnection();

    /**
     * @name SharedWebSocket#send
     * @function
     * @param {JoynrMessage}
     *            joynrMessage the joynr message to transmit
     */
    this.send = function send(joynrMessage) {
        log.debug(">>> OUTGOING >>> message with ID " + joynrMessage.msgId);
        return sendMessage(websocket, joynrMessage, queuedMessages).catch(resetConnection);
    };

    /**
     * @name SharedWebSocket#close
     * @function
     */
    this.close = function close() {
        closed = true;
        if (reconnectTimer !== undefined) {
            LongTimer.clearTimeout(reconnectTimer);
            reconnectTimer = undefined;
        }
        if (websocket !== null) {
            websocket.close(SharedWebSocket.EVENT_CODE_SHUTDOWN, "shutdown");
            websocket = null;
        }
    };

    // using the defineProperty syntax for onmessage to be able to keep
    // the same API as WebSocket but have a setter function called when
    // the attribute is set.
    Object.defineProperty(this, "onmessage", {
        set: function(newCallback) {
            if (typeof newCallback === "function") {
                onmessageCallback = function(data) {
                    websocket.unmarshalJoynrMessage(data, newCallback);
                };
                websocket.onmessage = onmessageCallback;
            } else {
                throw new Error("onmessage callback must be a function, but instead was of type " + typeof newCallback);
            }
        },
        get: function() {
            return onmessageCallback;
        },
        enumerable: false,
        configurable: false
    });
};
SharedWebSocket.EVENT_CODE_SHUTDOWN = 4000;
module.exports = SharedWebSocket;
<|endoftext|>