<commit_before>'use strict';

const error = require('./utils/error');

module.exports = function(opts) {

	opts = Object.assign(this.options, opts);

	opts.root = true;

	// Limit
	const sql_limit = (opts.start ? `${opts.start},` : '') + opts.limit;

	// Set the table_response_handlers
	opts.response_handlers = [];

	// Get the root tableID
	const sql_alias = opts.alias;
	const sql_table = opts.table;

	// Filters populate the filter and values (prepared statements)
	const sql_filter = [];
	let sql_values = [];

	// Fields
	const sql_fields = [];

	// Joins
	const sql_joins = [];

	// Traverse the formatted opts
	traverse(opts, (item, parent) => {

		// Should this be nested?
		const group_concat = item.nested_query && item.many ? this.group_concat : null;

		// Build up the SQL conditions
		// e.g. filter= {category: asset, action: open, created_time: 2016-04-12T13:29:23Z..]
		if (item.filter) {
			for (const key in item.filter) {

				const val = item.filter[key];

				prepCondition(key, val).forEach(([field, operator, value]) => {
					if (Array.isArray(value)) {
						sql_values = sql_values.concat(value);
						operator = 'IN';
						value = `(${  value.map(() => '?')  })`;
					}
					else {
						sql_values.push(value);
						value = '?';
					}
					sql_filter.push(`${item.alias}.${field} ${operator} ${value}`);
				});
			}
		}

		// Fields
		// e.g. fields = [action, category, count, ...]
		if (item.fields) {

			// yes, believe it or not but some queries do have them....
			item.fields.map(prepField).forEach(([def, as]) => {

				// Have we got a generated field?
				if (typeof def === 'function') {
					// Add this to the list
					opts.response_handlers.push(
						setField.bind(this, !item.root && item.alias, as, def)
					);
					return;
				}

				// Is this a GROUP_CONCAT
				if (group_concat) {
					as = `${item.alias}[${this.group_concat}].${as || def}`;
					def = `GROUP_CONCAT(CONCAT('"', IFNULL(${item.alias}.${def}, ''), '"') SEPARATOR '${group_concat}')`;
				}
				else if (!as && def.indexOf('.') === -1) {
					def = `${item.alias}.${def}`;
				}

				as = as || (!item.root ? def : '');

				if (as) {
					as = ` AS '${as}'`;
				}

				sql_fields.push(def + as);
			});

		}

		// Dont continue if this does not have a parent
		if (!parent) {
			return;
		}

		// Update the values with the alias of the parent
		for (const x in item.conditions) {
			item.conditions[x] = `${parent.alias}.${item.conditions[x]}`;
		}

		// Custom formatting of join conditions
		this.table_handler(item);

		// Prefix keys and return
		const cond_map = prefixKeys(item.conditions, `${item.alias}.`);

		// Append to the sql_join
		sql_joins.push(`LEFT JOIN ${item.table} ${item.table === item.alias ? '' : item.alias} ON (${serialize(cond_map, '=', 'AND')})`);

		// Ensure that the parent has opts.groupby
		if (group_concat && !opts.groupby) {
			opts.groupby = 'id';
		}

	});

	{
		// Count is a special field, find it ...
		const i = sql_fields.indexOf(`${opts.alias}._count`);

		if (i > -1) {
			// ... and replace it.
			sql_fields[i] = 'COUNT(*) AS _count';
		}
	}

	// Groupby
	// If the content is grouped

	let sql_groupby = '';

	if (opts.groupby) {

		// Find the special _group column...
		const i = sql_fields.indexOf(`${opts.alias}._group`);

		if (i > -1) {
			// ... and replace it.
			sql_fields[i] = `${opts.groupby} as _group`;
		}

		// Add the grouping
		sql_groupby = `GROUP BY ${opts.groupby}`;
	}

	// Orderby
	// If the content is ordered

	const sql_orderby = opts.orderby ? `ORDER BY ${opts.orderby}` : '';

	// Put it all together
	const sql = `SELECT ${sql_fields.toString()}
						 FROM ${sql_table} ${sql_alias !== sql_table ? sql_alias : ''}
								${sql_joins.join('\n')}
						 WHERE
							 ${sql_filter.join(' AND ')}
						 ${sql_groupby}
						 ${sql_orderby}
						 LIMIT ${sql_limit}`;

	return this
	.sql(sql, sql_values)
	.then(this.response_handler.bind(this))
	.then(resp => {

		// If limit was not defined we should return the first result only.
		if (opts.single) {
			if (resp.length) {
				return resp[0];
			}
			else {
				throw error.NOT_FOUND;
			}
		}
		return resp;
	});
};

function traverse(opts, handler, parent) {
	handler(opts, parent);

	if (opts.joins) {
		opts.joins.forEach(item => traverse(item, handler, opts));
	}
}

function prepField(field) {

	if (typeof field === 'string') {
		return [field];
	}

	for (const as in field) {
		const def = field[as];
		return [def, as];
	}
}

function prepCondition(field, value) {

	const a = [];

	// Range
	// A range is denoted by two dots, e.g 1..10
	const range = (typeof value === 'string') && value.split('..');

	if (range.length === 2) {
		range.forEach((value, index) => {
			if (value !== '') {
				a.push([field, (index ? '<' : '>'), value]);
			}
		});
	}

	// Is this an array match?
	else if (typeof value === 'string' && value.match('%')) {
		a.push([field, 'LIKE', value]);
	}

	// Add to the array of items
	else {
		a.push([field, '=', value]);
	}

	return a;
}

function serialize(obj, separator, delimiter) {
	const r = [];
	for (const x in obj) {
		r.push(`${x} ${separator} ${obj[x]}`);
	}
	return r.join(` ${delimiter} `);
}

function setField(table, field, handler, obj) {

	if (table) {
		obj = obj[table];
	}
	if (!Array.isArray(obj)) {
		obj = [obj];
	}

	obj.forEach(item => item[field] = handler.call(this, item));
}

function prefixKeys(obj, prefix = '') {
	const r = {};
	for (const x in obj) {
		r[prefix + x] = obj[x];
	}
	return r;
}
<commit_msg>Fix group_concat defined on parent object<commit_after>'use strict';

const error = require('./utils/error');

module.exports = function(opts) {

	opts = Object.assign(this.options, opts);

	opts.root = true;

	// Limit
	const sql_limit = (opts.start ? `${opts.start},` : '') + opts.limit;

	// Set the table_response_handlers
	opts.response_handlers = [];

	// Get the root tableID
	const sql_alias = opts.alias;
	const sql_table = opts.table;

	// Filters populate the filter and values (prepared statements)
	const sql_filter = [];
	let sql_values = [];

	// Fields
	const sql_fields = [];

	// Joins
	const sql_joins = [];

	// Traverse the formatted opts
	traverse(opts, (item, parent) => {

		if (parent) {
			// Adopt the parents settings
			item.nested_query = parent.nested_query || item.nested_query;
			item.many = parent.many || item.many;
		}

		// Should this be nested?
		const group_concat = item.nested_query && item.many ? this.group_concat : null;

		// Build up the SQL conditions
		// e.g. filter= {category: asset, action: open, created_time: 2016-04-12T13:29:23Z..]
		if (item.filter) {
			for (const key in item.filter) {

				const val = item.filter[key];

				prepCondition(key, val).forEach(([field, operator, value]) => {
					if (Array.isArray(value)) {
						sql_values = sql_values.concat(value);
						operator = 'IN';
						value = `(${  value.map(() => '?')  })`;
					}
					else {
						sql_values.push(value);
						value = '?';
					}
					sql_filter.push(`${item.alias}.${field} ${operator} ${value}`);
				});
			}
		}

		// Fields
		// e.g. fields = [action, category, count, ...]
		if (item.fields) {

			// yes, believe it or not but some queries do have them....
			item.fields.map(prepField).forEach(([def, as]) => {

				// Have we got a generated field?
				if (typeof def === 'function') {
					// Add this to the list
					opts.response_handlers.push(
						setField.bind(this, !item.root && item.alias, as, def)
					);
					return;
				}

				// Is this a GROUP_CONCAT
				if (group_concat) {
					as = `${item.alias}[${group_concat}].${as || def}`;
					def = `GROUP_CONCAT(CONCAT('"', IFNULL(${item.alias}.${def}, ''), '"') SEPARATOR '${group_concat}')`;
				}
				else if (!as && def.indexOf('.') === -1) {
					def = `${item.alias}.${def}`;
				}

				as = as || (!item.root ? def : '');

				if (as) {
					as = ` AS '${as}'`;
				}

				sql_fields.push(def + as);
			});

		}

		// Dont continue if this does not have a parent
		if (!parent) {
			return;
		}

		// Update the values with the alias of the parent
		for (const x in item.conditions) {
			item.conditions[x] = `${parent.alias}.${item.conditions[x]}`;
		}

		// Custom formatting of join conditions
		this.table_handler(item);

		// Prefix keys and return
		const cond_map = prefixKeys(item.conditions, `${item.alias}.`);

		// Append to the sql_join
		sql_joins.push(`LEFT JOIN ${item.table} ${item.table === item.alias ? '' : item.alias} ON (${serialize(cond_map, '=', 'AND')})`);

		// Ensure that the parent has opts.groupby
		if (group_concat && !opts.groupby) {
			opts.groupby = 'id';
		}

	});

	{
		// Count is a special field, find it ...
		const i = sql_fields.indexOf(`${opts.alias}._count`);

		if (i > -1) {
			// ... and replace it.
			sql_fields[i] = 'COUNT(*) AS _count';
		}
	}

	// Groupby
	// If the content is grouped

	let sql_groupby = '';

	if (opts.groupby) {

		// Find the special _group column...
		const i = sql_fields.indexOf(`${opts.alias}._group`);

		if (i > -1) {
			// ... and replace it.
			sql_fields[i] = `${opts.groupby} as _group`;
		}

		// Add the grouping
		sql_groupby = `GROUP BY ${opts.groupby}`;
	}

	// Orderby
	// If the content is ordered

	const sql_orderby = opts.orderby ? `ORDER BY ${opts.orderby}` : '';

	// Put it all together
	const sql = `SELECT ${sql_fields.toString()}
						 FROM ${sql_table} ${sql_alias !== sql_table ? sql_alias : ''}
								${sql_joins.join('\n')}
						 WHERE
							 ${sql_filter.join(' AND ')}
						 ${sql_groupby}
						 ${sql_orderby}
						 LIMIT ${sql_limit}`;

	return this
	.sql(sql, sql_values)
	.then(this.response_handler.bind(this))
	.then(resp => {

		// If limit was not defined we should return the first result only.
		if (opts.single) {
			if (resp.length) {
				return resp[0];
			}
			else {
				throw error.NOT_FOUND;
			}
		}
		return resp;
	});
};

function traverse(opts, handler, parent) {
	handler(opts, parent);

	if (opts.joins) {
		opts.joins.forEach(item => traverse(item, handler, opts));
	}
}

function prepField(field) {

	if (typeof field === 'string') {
		return [field];
	}

	for (const as in field) {
		const def = field[as];
		return [def, as];
	}
}

function prepCondition(field, value) {

	const a = [];

	// Range
	// A range is denoted by two dots, e.g 1..10
	const range = (typeof value === 'string') && value.split('..');

	if (range.length === 2) {
		range.forEach((value, index) => {
			if (value !== '') {
				a.push([field, (index ? '<' : '>'), value]);
			}
		});
	}

	// Is this an array match?
	else if (typeof value === 'string' && value.match('%')) {
		a.push([field, 'LIKE', value]);
	}

	// Add to the array of items
	else {
		a.push([field, '=', value]);
	}

	return a;
}

function serialize(obj, separator, delimiter) {
	const r = [];
	for (const x in obj) {
		r.push(`${x} ${separator} ${obj[x]}`);
	}
	return r.join(` ${delimiter} `);
}

function setField(table, field, handler, obj) {

	if (table) {
		obj = obj[table];
	}
	if (!Array.isArray(obj)) {
		obj = [obj];
	}

	obj.forEach(item => item[field] = handler.call(this, item));
}

function prefixKeys(obj, prefix = '') {
	const r = {};
	for (const x in obj) {
		r[prefix + x] = obj[x];
	}
	return r;
}
<|endoftext|>


<commit_before>var getGPA = function () {
    var dataTable=Array.prototype.slice.call(document.getElementsByClassName("linkDescList grid"))

    var dataArray = [];

// Convert table to array
$(dataTable).each(function() {
    var arrayOfThisRow = [];
    var tableData = $(this).find('td');
    if (tableData.length > 0) {
        tableData.each(function() { arrayOfThisRow.push($(this).text()); });
        dataArray.push(arrayOfThisRow);
    }
});

Object.defineProperty(Array.prototype, 'chunk', {
    value: function(chunkSize) {
        var array=this;
        return [].concat.apply([],
            array.map(function(elem,i) {
                return i%chunkSize ? [] : [array.slice(i,i+chunkSize)];
            })
            );
    }
});
dataArray=dataArray[0].chunk(16)

console.log(dataArray);
console.log("Number of rows: "+dataArray.length)

var numclasses = 0;
for (var i=0;
    !(isNaN(parseInt(dataArray[i][12][0])));
    i++,numclasses++){}

    console.log("Number of classes: "+numclasses)

for (var gpa = 0, i = 0; numclasses > i; i++) {
    gpa += parseInt(dataArray[i][12][0]);
}

return gpa / numclasses;
}

var main = function ()
{
    var change = document.getElementsByTagName("tbody")[2];
    var gpa_display = "
(S1) GPA: " + getGPA().toFixed(3)+"

";
    change.innerHTML = gpa_display;
}
main();

<commit_msg>Added traditional GPA calculation to gpa.js<commit_after>Object.defineProperty(Array.prototype, "chunk", {value:function(chunkSize) {
  var array = this;
  return [].concat.apply([], array.map(function(elem, i) {
    return i % chunkSize ? [] : [array.slice(i, i + chunkSize)];
  }));
}});

var getGPA = function() {
  var dataTable = Array.prototype.slice.call(document.getElementsByClassName("linkDescList grid"));
  var dataArray = [];
  $(dataTable).each(function() {
    var arrayOfThisRow = [];
    var tableData = $(this).find("td");
    if (tableData.length > 0) {
      tableData.each(function() {
        arrayOfThisRow.push($(this).text());
      });
      dataArray.push(arrayOfThisRow);
    }
  });

  dataArray = dataArray[0].chunk(16);
  console.log(dataArray);
  console.log("Number of rows: " + dataArray.length);
  var numclasses = 0;
  var gpa2 = 0;
  for (var i = 0; !isNaN(parseInt(dataArray[i][12][0])); i++, numclasses++) {}

  var dictionary = {7:4.3, 6:4, 5:3.3, 4:2.3, 3:1.3, 2:0, 1:0};
  console.log("Number of classes: " + numclasses);
  for (var gpa = 0, i = 0, current = 0; i < numclasses; i++) {
    current = parseInt(dataArray[i][12][0]);
    gpa2 += dictionary[current];
    if ((dataArray[i][11].substring(0, 2) == "IB" || dataArray[i][11].substring(0, 2) == "AP") && dataArray[i][11].substring(0, 15) != "IB Math Studies") {
      gpa2 += 0.5;
    }
    gpa += current;
  }

  return [gpa / numclasses, gpa2 / numclasses];
};

var main = function() {
  var change = document.getElementsByTagName("tbody")[2];
  var GPAs = getGPA();
  var gpa_display = "
(S1) GPA: " + GPAs[0].toFixed(3) + "
 Traditional GPA: " + GPAs[1].toFixed(3) + "
";
  change.innerHTML = gpa_display;
};
main();<|endoftext|>


<commit_before>/**
 * Copyright 2015-2021 OpenCB
 *
 * 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.
 */

import { LitElement, html } from "/web_modules/lit-element.js";
import UtilsNew from "./../../utilsNew.js";
import "../commons/tool-header.js";
import "../phenotype/phenotype-manager.js";
import "../individual/disorder-manager.js";

export default class IndividualCreate extends LitElement {

    static CREATE_MODE = "create";
    static UPDATE_MODE = "update";

    constructor() {
        super();
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            individual: {
                type: Object
            },
            study: {
                type: Object
            },
            opencgaSession: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = UtilsNew.randomString(8);
        this.individual = {
            phenotypes: [],
            disorders:[]
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = { ...this.getDefaultConfig(), ...this.config };
    }

    onFieldChange(e) {
        let param;
        switch (e.detail.param) {
            case "id":
            case "name":
            case "father":
            case "mother":
            case "sex":
            case "ethnicity":
            case "parentalConsanguinity":
            case "karyotypicSex":
            case "lifeStatus":
                this.individual[e.detail.param] = e.detail.value;
                break;
            case "location.address":
            case "location.postalCode":
            case "location.city":
            case "location.state":
            case "location.country":
                let cat = e.detail.param.split(".")[0];
                param = e.detail.param.split(".")[1];
                if (!this.individual[cat]) {
                    this.individual[cat] = {};
                }
                this.individual[cat][param] = e.detail.value;
                break;
            case "population.name":
            case "population.subpopulation":
            case "population.description":
                param = e.detail.param.split(".")[1];
                if (!this.individual.population) {
                    this.individual.population = {};
                }
                this.individual.population[param] = e.detail.value;
                break;
            case "status.name":
            case "status.description":
                param = e.detail.param.split(".")[1];
                if (!this.individual.status) {
                    this.individual.status = {};
                }
                this.individual.status[param] = e.detail.value;
                break;

        }
    }

    saveIndividual() {
        // this.opencgaSession.opencgaClient.projects().create(this.project)
        //     .then(res => {
        //         this.sample = {};
        //         this.requestUpdate();

        //         this.dispatchSessionUpdateRequest();

        //         Swal.fire(
        //             "New Sample",
        //             "New Sample created correctly.",
        //             "success"
        //         );
        //     })
        //     .catch(err => {
        //         console.error(err);
        //         params.error(err);
        //     });
    }

    updateIndividual() {
        // this.opencgaSession.opencgaClient.projects().update(this.Sample?.fqn,this.Sample)
        //     .then(res => {
        //         this.Sample = {};
        //         this.requestUpdate();

        //         this.dispatchSessionUpdateRequest();

        //         Swal.fire(
        //             "Edit Sample",
        //             "Sample updated correctly.",
        //             "success"
        //         );
        //     })
        //     .catch(err => {
        //         console.error(err);
        //         params.error(err);
        //     });
    }

    onSave(e) {
        // if (mode === SampleForm.CREATE_MODE) {
        //     this.saveIndividual()
        // } else {
        //     this.updateIndividual()
        // }
    }

    onClear(e) {
        console.log("onClear individual form")
    }

    getDefaultConfig() {
        return {
            title: "Edit",
            icon: "fas fa-edit",
            type: "form",
            buttons: {
                show: true,
                cancelText: "Cancel",
                okText: "Save"
            },
            display: {

                // mode: {
                //     type: "modal",
                //     title: "Review Variant",
                //     buttonClass: "btn-link"
                // },
                labelWidth: 3,
                with: "8",
                labelAlign: "right",
                defaultLayout: "horizontal",
            },
            sections: [
                {
                    title: "Individual General Information",
                    elements: [
                        {
                            name: "Individual id",
                            field: "id",
                            type: "input-text",
                            display: {
                                placeholder: "Add a short ID...",
                                help: {
                                    text: "short individual id for..."
                                },
                            }
                        },
                        {
                            name: "Name",
                            field: "name",
                            type: "input-text",
                            display: {
                                placeholder: "individual name..."
                            }
                        },
                        {
                            name: "Father id",
                            field: "father",
                            type: "input-text",
                            display: {
                                placeholder: "individual name..."
                            }
                        },
                        {
                            name: "Mother id",
                            field: "mother",
                            type: "input-text",
                            display: {
                                placeholder: "individual name..."
                            }
                        },
                        {
                            name: "Sex",
                            field: "sex",
                            type: "select",
                            allowedValues: ["MALE", "FEMALE", "UNKNOWN", "UNDETERMINED"],
                            display: {}
                        },
                        {
                            name: "Birth",
                            field: "dateOfBirth",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Ethnicity",
                            field: "ethnicity",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Parental Consanguinity",
                            field: "parentalConsanguinity",
                            type: "checkbox",
                            display: {}
                        },
                        {
                            name: "Karyotypic Sex",
                            field: "karyotypicSex",
                            type: "select",
                            allowedValues: ["UNKNOWN", "XX", "XY", "XO", "XXY", "XXX", "XXYY", "XXXY", "XXXX", "XYY", "OTHER"],
                            display: {}
                        },
                        {
                            name: "Life Status",
                            field: "lifeStatus",
                            type: "select",
                            allowedValues: ["ALIVE", "ABORTED", "DECEASED", "UNBORN", "STILLBORN", "MISCARRIAGE", "UNKNOWN"],
                            display: {}
                        },
                    ]
                },
                {
                    title: "Location Info",
                    elements: [
                        {
                            name: "Address",
                            field: "location.address",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Portal code",
                            field: "location.postalCode",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "City",
                            field: "location.city",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "State",
                            field: "location.state",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Country",
                            field: "location.country",
                            type: "input-text",
                            display: {}
                        }
                    ]
                },
                {
                    title: "Population Info",
                    elements: [
                        {
                            name: "Population name",
                            field: "population.name",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Subpopulation",
                            field: "population.subpopulation",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "populaton description",
                            field: "population.description",
                            type: "input-text",
                            display: {
                                rows: 3,
                                placeholder: "add a description...",
                            }
                        }
                    ]
                },
                {
                    elements: [
                        {
                            field: "phenotype",
                            type: "custom",
                            display: {
                                layout: "vertical",
                                defaultLayout: "vertical",
                                width: 12,
                                style: "padding-left: 0px",
                                render: () => html`
                                        <phenotype-manager
                                            .phenotypes="${this.individual?.phenotypes}"
                                            .opencgaSession="${this.opencgaSession}" >
                                        </phenotype-manager>`
                            }
                        },
                        {
                            field: "disorder",
                            type: "custom",
                            display: {
                                layout: "vertical",
                                defaultLayout: "vertical",
                                width: 12,
                                style: "padding-left: 0px",
                                render: () => html`
                                        <disorder-manager
                                            .disorders="${this.individual?.disorders}"
                                            .opencgaSession="${this.opencgaSession}" >
                                        </disorder-manager>`
                            }
                        },
                    ]
                },
            ]
        }
    }

    render() {
        return html`
            <data-form
                .data=${this.individual}
                .config="${this._config}"
                @fieldChange="${e => this.onFieldChange(e)}"
                @clear="${this.onHide}"
                @submit="${this.onSave}">
            </data-form>
        `;
    }

}

customElements.define("individual-create", IndividualCreate);
<commit_msg>wc: added feature to create individual<commit_after>/**
 * Copyright 2015-2021 OpenCB
 *
 * 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.
 */

import { LitElement, html } from "/web_modules/lit-element.js";
import UtilsNew from "./../../utilsNew.js";
import "../commons/tool-header.js";
import "../phenotype/phenotype-manager.js";
import "../individual/disorder-manager.js";

export default class IndividualCreate extends LitElement {

    constructor() {
        super();
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = UtilsNew.randomString(8);
        this.individual = {
            phenotypes: [],
            disorders: []
        };
    }

    connectedCallback() {
        super.connectedCallback();
        this._config = { ...this.getDefaultConfig(), ...this.config };
    }

    onFieldChange(e) {
        const [field, prop] = e.detail.param.split(".");
        switch (e.detail.param) {
            case "id":
            case "name":
            case "father":
            case "mother":
            case "sex":
            case "ethnicity":
            case "parentalConsanguinity":
            case "karyotypicSex":
            case "lifeStatus":
                this.individual[field] = e.detail.value;
                break;
            case "location.address":
            case "location.postalCode":
            case "location.city":
            case "location.state":
            case "location.country":
            case "population.name":
            case "population.subpopulation":
            case "population.description":
            case "status.name":
            case "status.description":
                this.individual[field] = {
                    ...this.individual[field],
                    [prop]: e.detail.value
                };
            // if (!this.individual[field]) {
            //     this.individual[field] = {};
            // }
            // this.individual[field][prop] = e.detail.value;
            break;
        }
        console.log("changeValue: ",this.individual)
    }

    onRemovePhenotype(e) {
        this.individual = {
            ...this.individual,
            phenotypes: this.individual.phenotypes
                .filter(item => item !== e.detail.value)
        }
        this.requestUpdate()
    }

    onAddPhenotype(e) {
        this.individual.phenotypes.push(e.detail.value)
        this.requestUpdate()
    }

    onClear(e) {
        console.log("onClear individual form")
    }

    onSubmit() {
        this.opencgaSession.opencgaClient.individuals().create(this.individual, { study: this.opencgaSession.study.fqn })
            .then(res => {
                this.individual = {};
                this.requestUpdate();

                // this.dispatchSessionUpdateRequest();
                Swal.fire(
                    "New Individual",
                    "New Individual created correctly.",
                    "success"
                );
            })
            .catch(err => {
                console.error(err);
            });
    }

    getDefaultConfig() {
        return {
            title: "Edit",
            icon: "fas fa-edit",
            type: "form",
            buttons: {
                show: true,
                cancelText: "Cancel",
                okText: "Save"
            },
            display: {
                labelWidth: 3,
                with: "8",
                labelAlign: "right",
                defaultLayout: "horizontal",
                defaultValue: "",
            },
            sections: [
                {
                    title: "Individual General Information",
                    elements: [
                        {
                            name: "Individual id",
                            field: "id",
                            type: "input-text",
                            display: {
                                placeholder: "Add a short ID...",
                                help: {
                                    text: "short individual id for..."
                                },
                            }
                        },
                        {
                            name: "Name",
                            field: "name",
                            type: "input-text",
                            display: {
                                placeholder: "individual name..."
                            }
                        },
                        {
                            name: "Father id",
                            field: "father",
                            type: "input-text",
                            display: {
                                placeholder: "individual name..."
                            }
                        },
                        {
                            name: "Mother id",
                            field: "mother",
                            type: "input-text",
                            display: {
                                placeholder: "individual name..."
                            }
                        },
                        {
                            name: "Sex",
                            field: "sex",
                            type: "select",
                            allowedValues: ["MALE", "FEMALE", "UNKNOWN", "UNDETERMINED"],
                            display: {}
                        },
                        {
                            name: "Birth",
                            field: "dateOfBirth",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Ethnicity",
                            field: "ethnicity",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Parental Consanguinity",
                            field: "parentalConsanguinity",
                            type: "checkbox",
                            checked: false,
                            display: {}
                        },
                        {
                            name: "Karyotypic Sex",
                            field: "karyotypicSex",
                            type: "select",
                            allowedValues: ["UNKNOWN", "XX", "XY", "XO", "XXY", "XXX", "XXYY", "XXXY", "XXXX", "XYY", "OTHER"],
                            display: {}
                        },
                        {
                            name: "Life Status",
                            field: "lifeStatus",
                            type: "select",
                            allowedValues: ["ALIVE", "ABORTED", "DECEASED", "UNBORN", "STILLBORN", "MISCARRIAGE", "UNKNOWN"],
                            display: {}
                        },
                    ]
                },
                {
                    title: "Location Info",
                    elements: [
                        {
                            name: "Address",
                            field: "location.address",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Portal code",
                            field: "location.postalCode",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "City",
                            field: "location.city",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "State",
                            field: "location.state",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Country",
                            field: "location.country",
                            type: "input-text",
                            display: {}
                        }
                    ]
                },
                {
                    title: "Population Info",
                    elements: [
                        {
                            name: "Population name",
                            field: "population.name",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "Subpopulation",
                            field: "population.subpopulation",
                            type: "input-text",
                            display: {}
                        },
                        {
                            name: "populaton description",
                            field: "population.description",
                            type: "input-text",
                            display: {
                                rows: 3,
                                placeholder: "add a description...",
                            }
                        }
                    ]
                },
                {
                    elements: [
                        {
                            field: "phenotype",
                            type: "custom",
                            display: {
                                layout: "vertical",
                                defaultLayout: "vertical",
                                width: 12,
                                style: "padding-left: 0px",
                                render: () => html`
                                        <phenotype-manager
                                            .phenotypes="${this.individual?.phenotypes}"
                                            .opencgaSession="${this.opencgaSession}"
                                            @addItem="${e => this.onAddPhenotype(e)}"
                                            @removeItem="${e => this.onRemovePhenotype(e)}">
                                        </phenotype-manager>`
                            }
                        },
                        {
                            field: "disorder",
                            type: "custom",
                            display: {
                                layout: "vertical",
                                defaultLayout: "vertical",
                                width: 12,
                                style: "padding-left: 0px",
                                render: () => html`
                                        <disorder-manager
                                            .disorders="${this.individual?.disorders}"
                                            .opencgaSession="${this.opencgaSession}" >
                                        </disorder-manager>`
                            }
                        },
                    ]
                },
            ]
        }
    }

    render() {
        return html`
            <data-form
                .data=${this.individual}
                .config="${this._config}"
                @fieldChange="${e => this.onFieldChange(e)}"
                @clear="${this.onClear}"
                @submit="${this.onSubmit}">
            </data-form>
        `;
    }

}

customElements.define("individual-create", IndividualCreate);
<|endoftext|>


<commit_before>import csjs from 'csjs-inject';
import React, { createElement as r } from 'react';
import Frame from 'react-frame-component';

import { Button, IFrame, Link, Spacer, Step } from '../components/index.js';
import renderBack from '../lib/render-back';
import renderFront from '../lib/render-front';

export default class PreviewStep extends React.Component {

  render () {
    const { address, image, message, preview  } = this.props.postcard;
    const size = this.props.postcard.size.sizes[this.props.postcard.size.selectedIndex];
    const fromAddress = address.addresses[address.selectedFromIndex];
    const toAddress = address.addresses[address.selectedToIndex];

    const scale = 300 / 100 / size.width;
    const topBottom = -(size.height * 100 - size.height * 100 * scale) / 2;
    const leftRight = -(size.width  * 100 - size.width  * 100 * scale) / 2;
    const styles = csjs`
      .frame {
        border: 1px solid black;
        margin: ${topBottom}px ${leftRight}px;
        transform: scale(${scale}, ${scale});
      }

      .sideLabel {
        font-style: italic;
      }
    `;

    const render = preview.side === 'front'
      ? renderFront({ image, size, isPreview: true })
      : renderBack({ size, message, fromAddress, toAddress, isPreview: true });

    return r(Step, { title: 'preview postcard' },
      r(Spacer, { height: '5px' }),
      r(Frame, { className: styles.frame, width: size.width * 100, height: size.height * 100 }, render),
      r(Spacer, { height: '5px' }),
      r('div', null,
        r('p', { className: styles.sideLabel }, preview.side),
        r(Spacer, { height: '5px' }),
        r(Button, { onClick: this.handleFlipClick.bind(this) }, 'flip')
      ),
      r(Spacer),
      r(Link, { to: '/to' }, 'back'),
      r(Link, { to: '/send' }, 'next')
    );
  }

  handleFlipClick () {
    const newSide = this.props.postcard.preview.side === 'front' ? 'back' : 'front';
    this.props.changeSelectedPreviewSide(newSide);
  }

}
<commit_msg>Change label on button that sends postcard from "next" to "send" to communicate what happens when you push the button<commit_after>import csjs from 'csjs-inject';
import React, { createElement as r } from 'react';
import Frame from 'react-frame-component';

import { Button, IFrame, Link, Spacer, Step } from '../components/index.js';
import renderBack from '../lib/render-back';
import renderFront from '../lib/render-front';

export default class PreviewStep extends React.Component {

  render () {
    const { address, image, message, preview  } = this.props.postcard;
    const size = this.props.postcard.size.sizes[this.props.postcard.size.selectedIndex];
    const fromAddress = address.addresses[address.selectedFromIndex];
    const toAddress = address.addresses[address.selectedToIndex];

    const scale = 300 / 100 / size.width;
    const topBottom = -(size.height * 100 - size.height * 100 * scale) / 2;
    const leftRight = -(size.width  * 100 - size.width  * 100 * scale) / 2;
    const styles = csjs`
      .frame {
        border: 1px solid black;
        margin: ${topBottom}px ${leftRight}px;
        transform: scale(${scale}, ${scale});
      }

      .sideLabel {
        font-style: italic;
      }
    `;

    const render = preview.side === 'front'
      ? renderFront({ image, size, isPreview: true })
      : renderBack({ size, message, fromAddress, toAddress, isPreview: true });

    return r(Step, { title: 'preview postcard' },
      r(Spacer, { height: '5px' }),
      r(Frame, { className: styles.frame, width: size.width * 100, height: size.height * 100 }, render),
      r(Spacer, { height: '5px' }),
      r('div', null,
        r('p', { className: styles.sideLabel }, preview.side),
        r(Spacer, { height: '5px' }),
        r(Button, { onClick: this.handleFlipClick.bind(this) }, 'flip')
      ),
      r(Spacer),
      r(Link, { to: '/to' }, 'back'),
      r(Link, { to: '/send' }, 'send')
    );
  }

  handleFlipClick () {
    const newSide = this.props.postcard.preview.side === 'front' ? 'back' : 'front';
    this.props.changeSelectedPreviewSide(newSide);
  }

}
<|endoftext|>


<commit_before>go("Tool.Blog", function() {

dom("#editArticle").addEventListener("click", Tool.Blog.edit);

});

/////

namespace("Tool.Blog");
Tool.Blog.edit = function() {
	var tools = tool("Blog").createEditTools(),
		editText = this.textContent,
		editCallback = arguments.callee;

	dom("article header h1 a").setAttribute("contenteditable", true);
	dom(".content").setAttribute("contenteditable", true);

	this.textContent = "Save changes";
	// Change edit button to save button.
	this.addEventListener("click", function(e) {
		e.preventDefault();
		tools.remove();
		this.textContent = editText;
		this.removeEventListener("click", arguments.callee);
		this.addEventListener("click", editCallback);
		alert("Saved!");
	});

	this.removeEventListener("click", editCallback);
	document.body.appendChild(tools);
};

Tool.Blog.createEditTools = function() {
	var container = document.createElement("div"),
		buttons = [
			{"title": "Save",
				"action": "save", "icon": "icon-ok"},
			{"title": "Cancel",
				"action": "cancel", "icon": "icon-remove"},
			{"spacer": true},
			{"title": "Bold",
				"cmd": "bold",	"icon": "icon-bold"},
			{"title": "Italic",
				"cmd": "italic", "icon": "icon-italic"},
			{"title": "Underline",
				"cmd": "underline", "icon": "icon-underline"},
			{"title": "Strikethrough",
				"cmd": "strikeThrough", "icon": "icon-strikethrough"},
			{"spacer": true},
			{"title": "Heading 1",
				"cmd": "formatBlock", "value": "H1"},
			{"title": "Heading 2",
				"cmd": "formatBlock", "value": "H2"},
			{"title": "Heading 3",
				"cmd": "formatBlock", "value": "H3"},
			{"title": "Paragraph",
				"cmd": "formatBlock", "value": "P"},
			{"spacer": true},
			{"title": "Bullet list",
				"cmd": "insertUnorderedList", "icon": "icon-list-ul"},
			{"title": "Numeric list",
				"cmd": "insertOrderedList", "icon": "icon-list-ol"},
			{"title": "Link", "prompt": "Link URL?",
				"cmd": "createLink", "icon": "icon-link"},
			{"title": "Image", "prompt": "Image URL?",
				"cmd": "insertImage","icon":"icon-picture"},
			{"spacer": true},
			{"title": "Preview break",
				"cmd": "insertHorizontalRule", "icon":"icon-minus"},
			{"title": "Remove all formatting",
				"cmd": "removeFormat", "icon":"icon-remove-sign"},

		], i, btn, icon;
	container.id = "tool_blog_edit";

	for(i = 0; i < buttons.length; i++) {
		if(buttons[i].spacer) {
			container.appendChild(document.createElement("hr"));
			continue;
		}
		btn = document.createElement("button");
		icon = document.createElement("i");
		if(buttons[i].icon) {
			icon.className = buttons[i].icon;
		}
		else {
			icon.textContent = buttons[i].value;
		}
		(function(c_button, c_btn) {
			c_btn.addEventListener("click", function(e) {
				var cmd = c_button.cmd || null,
					arg = c_button.value || null;
				if(c_button.prompt) {
					arg = prompt(c_button.prompt);
				}
				if(cmd) {
					document.execCommand(cmd, false, arg);
				}
				else {
					Tool.Blog.editAction[c_button.action]();
				}
			});
		})(buttons[i], btn);

		btn.setAttribute("title", buttons[i].title);
		btn.appendChild(icon);
		container.appendChild(btn);
	}

	return container;
};

Tool.Blog.editAction = {
	"save": function() {
		api("Blog", "save", {
			"title": dom("article > header h1 a").textContent,
			"content": dom("article > div.content").innerHTML,
		}, function() {
			alert("Save complete.");
		});
	},
	"cancel": function(e) {
		alert("Cancelled");
	},
};<commit_msg>Remove editability after save<commit_after>go("Tool.Blog", function() {

dom("#editArticle").addEventListener("click", Tool.Blog.edit);

});

/////

namespace("Tool.Blog");
Tool.Blog.edit = function() {
	var tools = tool("Blog").createEditTools(),
		editText = this.textContent,
		editCallback = arguments.callee;

	dom("article header h1 a").setAttribute("contenteditable", true);
	dom(".content").setAttribute("contenteditable", true);

	this.textContent = "Save changes";
	// Change edit button to save button.
	this.addEventListener("click", function(e) {
		e.preventDefault();
		tools.remove();
		this.textContent = editText;
		this.removeEventListener("click", arguments.callee);
		this.addEventListener("click", editCallback);
		dom("[contenteditable]").removeAttribute("contenteditable");
		alert("Saved!");
	});

	this.removeEventListener("click", editCallback);
	document.body.appendChild(tools);
};

Tool.Blog.createEditTools = function() {
	var container = document.createElement("div"),
		buttons = [
			{"title": "Save",
				"action": "save", "icon": "icon-ok"},
			{"title": "Cancel",
				"action": "cancel", "icon": "icon-remove"},
			{"spacer": true},
			{"title": "Bold",
				"cmd": "bold",	"icon": "icon-bold"},
			{"title": "Italic",
				"cmd": "italic", "icon": "icon-italic"},
			{"title": "Underline",
				"cmd": "underline", "icon": "icon-underline"},
			{"title": "Strikethrough",
				"cmd": "strikeThrough", "icon": "icon-strikethrough"},
			{"spacer": true},
			{"title": "Heading 1",
				"cmd": "formatBlock", "value": "H1"},
			{"title": "Heading 2",
				"cmd": "formatBlock", "value": "H2"},
			{"title": "Heading 3",
				"cmd": "formatBlock", "value": "H3"},
			{"title": "Paragraph",
				"cmd": "formatBlock", "value": "P"},
			{"spacer": true},
			{"title": "Bullet list",
				"cmd": "insertUnorderedList", "icon": "icon-list-ul"},
			{"title": "Numeric list",
				"cmd": "insertOrderedList", "icon": "icon-list-ol"},
			{"title": "Link", "prompt": "Link URL?",
				"cmd": "createLink", "icon": "icon-link"},
			{"title": "Image", "prompt": "Image URL?",
				"cmd": "insertImage","icon":"icon-picture"},
			{"spacer": true},
			{"title": "Preview break",
				"cmd": "insertHorizontalRule", "icon":"icon-minus"},
			{"title": "Remove all formatting",
				"cmd": "removeFormat", "icon":"icon-remove-sign"},

		], i, btn, icon;
	container.id = "tool_blog_edit";

	for(i = 0; i < buttons.length; i++) {
		if(buttons[i].spacer) {
			container.appendChild(document.createElement("hr"));
			continue;
		}
		btn = document.createElement("button");
		icon = document.createElement("i");
		if(buttons[i].icon) {
			icon.className = buttons[i].icon;
		}
		else {
			icon.textContent = buttons[i].value;
		}
		(function(c_button, c_btn) {
			c_btn.addEventListener("click", function(e) {
				var cmd = c_button.cmd || null,
					arg = c_button.value || null;
				if(c_button.prompt) {
					arg = prompt(c_button.prompt);
				}
				if(cmd) {
					document.execCommand(cmd, false, arg);
				}
				else {
					Tool.Blog.editAction[c_button.action]();
				}
			});
		})(buttons[i], btn);

		btn.setAttribute("title", buttons[i].title);
		btn.appendChild(icon);
		container.appendChild(btn);
	}

	return container;
};

Tool.Blog.editAction = {
	"save": function() {
		api("Blog", "save", {
			"title": dom("article > header h1 a").textContent,
			"content": dom("article > div.content").innerHTML,
		}, function() {
			alert("Save complete.");
		});
	},
	"cancel": function(e) {
		alert("Cancelled");
	},
};<|endoftext|>


<commit_before>var savedModel;

function actionFormatter(value, row, index) {
    return [
        '',
        '',
        ''
    ].join('');
}

function CandidateCreateModel(){
    ko.bindingHandlers.valueWithInit = {
        init: function(element, valueAccessor, allBindingsAccessor, data) {
            var property = valueAccessor(),
                value = element.value;

            //create the observable, if it doesn't exist
            if (!ko.isWriteableObservable(data[property])) {
                data[property] = ko.observable();
            }

            data[property](value);

            ko.applyBindingsToNode(element, { value: data[property] });
        }
    };

    ko.bindingHandlers.initDisplay = {
        init: function(element, valueAccessor, allBindingsAccessor, data) {
            self.modify(true);
        }
    };

    var self = this;
    self.errorResponse = ko.observable({});
    self.showError = ko.observable(false);

    self.ajaxCall = function() {
        var form = $("#candidate-create-form");
        $.ajax({
            url: form.attr('action'),
            method: form.attr('method'),
            contentType: 'application/json',
            data: JSON.stringify({
                id: self.id(),
                name: self.name(),
                referer: self.referer(),
                email: self.email(),
                languageSkill: self.languageSkill(),
                phone: self.phone(),
                description: self.description()
            })
        }).done(function (xhr) {
            window.location = form.attr('action')+ '/' + xhr;
        }).error(function (xhr) {
            self.errorResponse(JSON.parse(xhr.responseText));

            self.showError(true);
        });
    }

    self.errorMessages = ko.pureComputed(function() {
        return Object.keys(self.errorResponse()).map(function(key) {
            return self.errorResponse()[key];
        });
    });

    self.modify = ko.observable(false);
    modify_display_true = function() {
        self.modify(true);
        self.showError(false);
        $("#candidate-create-form").validator('destroy');
        candidateModel.id(savedModel.id);
        candidateModel.name(savedModel.name);
        candidateModel.referer(savedModel.referer);
        candidateModel.email(savedModel.email);
        candidateModel.languageSkill(savedModel.languageSkill);
        candidateModel.phone(savedModel.phone);
        candidateModel.referer(savedModel.referer);
    };
    modify_display_false = function() {
        self.modify(false);
        savedModel = ko.toJS(candidateModel);
    };
    save_button = function() {
        self.ajaxCall();
    };

    self.cssclass = ko.computed(function() {
        if (self.modify == true) {
            return "display";
        } else {
            return "";
        }
    });
}

var candidateModel = new CandidateCreateModel();

$('#candidate-create-form').validator().on('submit', function (e) {
  if (e.isDefaultPrevented()) {
    // handle the invalid form...
  } else {
    event.preventDefault();
    candidateModel.ajaxCall();
  }
});

ko.applyBindings(candidateModel);<commit_msg>Preventing event handler execution with return false instead of event.preventDefault()<commit_after>var savedModel;

function actionFormatter(value, row, index) {
    return [
        '',
        '',
        ''
    ].join('');
}

function CandidateCreateModel(){
    ko.bindingHandlers.valueWithInit = {
        init: function(element, valueAccessor, allBindingsAccessor, data) {
            var property = valueAccessor(),
                value = element.value;

            //create the observable, if it doesn't exist
            if (!ko.isWriteableObservable(data[property])) {
                data[property] = ko.observable();
            }

            data[property](value);

            ko.applyBindingsToNode(element, { value: data[property] });
        }
    };

    ko.bindingHandlers.initDisplay = {
        init: function(element, valueAccessor, allBindingsAccessor, data) {
            self.modify(true);
        }
    };

    var self = this;
    self.errorResponse = ko.observable({});
    self.showError = ko.observable(false);

    self.ajaxCall = function() {
        var form = $("#candidate-create-form");
        $.ajax({
            url: form.attr('action'),
            method: form.attr('method'),
            contentType: 'application/json',
            data: JSON.stringify({
                id: self.id(),
                name: self.name(),
                referer: self.referer(),
                email: self.email(),
                languageSkill: self.languageSkill(),
                phone: self.phone(),
                description: self.description()
            })
        }).done(function (xhr) {
            window.location = form.attr('action')+ '/' + xhr;
        }).error(function (xhr) {
            self.errorResponse(JSON.parse(xhr.responseText));

            self.showError(true);
        });
    }

    self.errorMessages = ko.pureComputed(function() {
        return Object.keys(self.errorResponse()).map(function(key) {
            return self.errorResponse()[key];
        });
    });

    self.modify = ko.observable(false);
    modify_display_true = function() {
        self.modify(true);
        self.showError(false);
        $("#candidate-create-form").validator('destroy');
        candidateModel.id(savedModel.id);
        candidateModel.name(savedModel.name);
        candidateModel.referer(savedModel.referer);
        candidateModel.email(savedModel.email);
        candidateModel.languageSkill(savedModel.languageSkill);
        candidateModel.phone(savedModel.phone);
        candidateModel.referer(savedModel.referer);
    };
    modify_display_false = function() {
        self.modify(false);
        savedModel = ko.toJS(candidateModel);
    };
    save_button = function() {
        self.ajaxCall();
    };

    self.cssclass = ko.computed(function() {
        if (self.modify == true) {
            return "display";
        } else {
            return "";
        }
    });
}

var candidateModel = new CandidateCreateModel();

$('#candidate-create-form').validator().on('submit', function (e) {
  if (e.isDefaultPrevented()) {
    // handle the invalid form...
  } else {
    candidateModel.ajaxCall();
    return false;
  }
});

ko.applyBindings(candidateModel);
<|endoftext|>


<commit_before>function DateTimeInput(input) {
    this.input = input;
    this.x_pos = $(input).offset().left;
    this.cursor_pos = function(evt) {
        var input = this.input;
        var input_style;
        try {
            input_style = getComputedStyle(input, '');
        } catch(e) {
            input_style = input.currentStyle;
        }
        if (input_style == undefined) input_style = {};

        var padding = input_style.paddingLeft;
        if (/^(\d+)(?:px)?$/.test(padding)) padding = new Number(RegExp.$1);
        else padding = 0;
        var border = input_style.borderLeftWidth;
        if (/^(\d+)(?:px)?$/.test(border )) border  = new Number(RegExp.$1);
        else border = 0;

        var x = evt.clientX - this.x_pos - padding - border;

        var $tempspan = $('').css({
            position: 'absolute',
            top: '-100px'
        });
        for (var property in input_style) {
            if (property.substr(0, 4) == 'font') {
                $tempspan.css(property, input_style[property]);
            }
        }
        $tempspan.insertAfter($(input));

        $tempspan.text('m');
        var dotwidth = -$tempspan.width();
        $tempspan.text('m.');
        dotwidth += $tempspan.width();
        $tempspan.text( $(input).val() + '.' );

        var text, last = '';
        while ($tempspan.width() - dotwidth >= x) {
            text = $tempspan.text();
            if (text == '.') break;
            text = text.substr(0,text.length-1);
            last = text.substr(text.length-1);
            $tempspan.text(
                text.substr(0, text.length-1) + '.'
            );
        }
        text = $tempspan.text();
        text = text.substr(0, text.length-1) + last;
        $tempspan.remove();
//        ;;; $('#pos').text( text );
        return text.length;
    };
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec( $(this.input).val() );
        var year, month, day, hour, minute, dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            hour   = fields[4];
            minute = fields[5];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year  ) d.setFullYear(year  );
        if (preserve.month ) d.setMonth   (month );
        if (preserve.day   ) d.setDate    (day   );
        if (preserve.hour  ) d.setHours   (hour  );
        if (preserve.minute) d.setMinutes (minute);

        year = d.getFullYear();
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        hour = d.getHours();
        minute = d.getMinutes();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = ''
        + pad(  year,4) + '-'
        + pad( month,2) + '-'
        + pad(   day,2) + ' '
        + pad(  hour,2) + ':'
        + pad(minute,2) + ' '
        + dow;
        $(this.input).val( nval );
    };
    this.scroll = function(pos, delta) {
        var input = this.input;
        var fields = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec( $(input).val() );
        if (!fields) {
            carp('Invalid date:', $(input).val());
            return;
        }

        var i = ([
            undefined,
            1,1,1,1,
            undefined,
            2,2,
            undefined,
            3,3,
            undefined,
            4,4,
            undefined,
            5,5,
            undefined,
            3,3
        ])[pos];
        if (i == undefined) return;
        fields[i] = new Number(fields[i]) + delta;

        var year   = fields[1];
        var month  = fields[2];
        var day    = fields[3];
        var hour   = fields[4];
        var minute = fields[5];
        month = new Number(month) - 1;

        var d = new Date();
        d.setFullYear(year);
        d.setMonth(month);
        d.setDate(day);
        d.setHours(hour);
        d.setMinutes(minute);
        d.setSeconds(0);
        d.setMilliseconds(0);

        this.set_date(d);

        input.selectionStart = input.selectionEnd = pos;
    };
}

function DateInput(input) {
    this.input = input;
    this.x_pos = $(input).offset().left;
    this.cursor_pos = function(evt) {
        var input = this.input;
        var input_style;
        try {
            input_style = getComputedStyle(input, '');
        } catch(e) {
            input_style = input.currentStyle;
        }
        if (input_style == undefined) input_style = {};

        var padding = input_style.paddingLeft;
        if (/^(\d+)(?:px)?$/.test(padding)) padding = new Number(RegExp.$1);
        else padding = 0;
        var border = input_style.borderLeftWidth;
        if (/^(\d+)(?:px)?$/.test(border )) border  = new Number(RegExp.$1);
        else border = 0;

        var x = evt.clientX - this.x_pos - padding - border;

        var $tempspan = $('').css({
            position: 'absolute',
            top: '-100px'
        });
        for (var property in input_style) {
            if (property.substr(0, 4) == 'font') {
                $tempspan.css(property, input_style[property]);
            }
        }
        $tempspan.insertAfter($(input));

        $tempspan.text('m');
        var dotwidth = -$tempspan.width();
        $tempspan.text('m.');
        dotwidth += $tempspan.width();
        $tempspan.text( $(input).val() + '.' );

        var text, last = '';
        while ($tempspan.width() - dotwidth >= x) {
            text = $tempspan.text();
            if (text == '.') break;
            text = text.substr(0,text.length-1);
            last = text.substr(text.length-1);
            $tempspan.text(
                text.substr(0, text.length-1) + '.'
            );
        }
        text = $tempspan.text();
        text = text.substr(0, text.length-1) + last;
        $tempspan.remove();
//        ;;; $('#pos').text( text );
        return text.length;
    };
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2})/.exec( $(this.input).val() );
        var year, month, day,  dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year ) d.setFullYear(year );
        if (preserve.month) d.setMonth   (month);
        if (preserve.day  ) d.setDate    (day  );

        year = d.getFullYear();
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = ''
        + pad(  year,4) + '-'
        + pad( month,2) + '-'
        + pad(   day,2) + ' '
        + dow;
        $(this.input).val( nval );
    };
    this.scroll = function(pos, delta) {
        var input = this.input;
        var fields = /^(\d{4})-(\d{2})-(\d{2})/.exec( $(input).val() );
        if (!fields) {
            carp('Invalid date:', $(input).val());
            return;
        }

        var i = ([
            undefined,
            1,1,1,1,
            undefined,
            2,2,
            undefined,
            3,3,
            undefined,
            3,3
        ])[pos];
        if (i == undefined) return;
        fields[i] = new Number(fields[i]) + delta;

        var year   = fields[1];
        var month  = fields[2];
        var day    = fields[3];
        month = new Number(month) - 1;

        var d = new Date();
        d.setFullYear(year);
        d.setMonth(month);
        d.setDate(day);
        d.setHours(0);
        d.setMinutes(0);
        d.setSeconds(0);
        d.setMilliseconds(0);

        this.set_date(d);

        input.selectionStart = input.selectionEnd = pos;
    };
}

(function($) {
    function datetime_init() {
        $('.vDateTimeInput,.vDateInput').each( function() {
            var $input = $(this);
            var is_datetime = $input.hasClass('vDateTimeInput');
            if (! $input.data('dti')) {
                if (is_datetime) {
                    $input.data('dti', new DateTimeInput(this));
                }
                else {
                    $input.data('dti', new DateInput(this));
                }

                $(  ''
                ).click( function() {
                    $('.datetimepicker').css({
                        top: $(this).offset().top + 'px',
                        left: ( $(this).offset().left + $(this).width() ) + 'px'
                    }).toggle().data( 'input', $input );
                }).insertAfter(this);

                $input.keypress(function(evt) {
                    var delta = 0;
                    if (evt.keyCode == 38) delta =  1;
                    if (evt.keyCode == 40) delta = -1;
                    if (!delta) return true;
                    var pos = this.selectionEnd;
                    $input.data('dti').scroll(pos, delta);
                });
            }
        });
    }
    datetime_init();

    function media_dependent_datetime_init(evt) {
        // create the datetimepicker div
        if ($('.datetimepicker').length) { }    // but only if there is none yet
        else if (
               evt.type == 'content_added'  // and we added something that uses a datepicker
            && $(evt.target).find('.dtpicker-trigger').length == 0    // if anything
        ) { }
        else {
            // Container creation
            var $dtpicker = $('
');

            // Date picker
            var $datepicker = $('
');
            $datepicker.datepicker(new DATEPICKER_OPTIONS({
                onSelect: function(dtext, dpick) {
                    var $dtpicker = $(this).closest('.datetimepicker');
                    var dti = $( $dtpicker.data('input') ).data('dti');
                    var d = new Date();
                    d.setFullYear(dpick.selectedYear);
                    d.setMonth(dpick.selectedMonth);
                    d.setDate(dpick.selectedDay);
                    d.setHours(0);
                    d.setMinutes(2);
                    d.setSeconds(0);
                    d.setMilliseconds(0);
                    dti.set_date(d, {/*preserve*/hour:true,minute:true});
                },
                onClose: function() {
                    $(this).closest('.datetimepicker').hide();
                }
            }));
            $datepicker.appendTo($dtpicker);

            // Time picker
            var $timepicker = $('
')
            .html(
                '<button type="button" class="js-dtpicker-close"></button>' +
                '<button type="button" class="js-timepick js-time-0000">'+gettext('Midnight')+'</button>' +
                '<button type="button" class="js-timepick js-time-0600">'+gettext('6am'     )+'</button>' +
                '<button type="button" class="js-timepick js-time-1200">'+gettext('Noon'    )+'</button>'
            ).appendTo($dtpicker);

            // Container placement
            $dtpicker.appendTo(
                   $('.change-form').get(0)
                || $('#content').get(0)
                || $('body').get(0)
            );
        }

        // mousewheel datetime scrolling
        function mousewheel_handler(evt, delta) {
            var dti = $(this).data('dti');
            var pos = dti.cursor_pos(evt);
            dti.scroll(pos, delta / Math.abs(delta||1));
        };
        if (DEBUG)  // FIXME: mousewheel scrolling broken!
        $('.vDateTimeInput,.vDateInput').not('.mwheel-enhanced').focus( function() {
            $(this)  .bind('mousewheel', mousewheel_handler);
        }).blur( function() {
            $(this).unbind('mousewheel', mousewheel_handler);
        }).addClass('mwheel-enhanced');
    }

    $('.js-dtpicker-close').live('click', function(evt) {
        if (evt.button != 0) return;
        $(this).closest('.datetimepicker').hide();
    });
    $('.js-timepick').live('click', function(evt) {
        if (evt.button != 0) return;
        var dti = $( $(this).closest('.datetimepicker').data('input') ).data('dti');
        var selected_time = /js-time-(\d\d)(\d\d)/.exec(this.className);
        if ( ! selected_time ) return;
        var selected_hours   = selected_time[1];
        var selected_minutes = selected_time[2];
        var d = new Date();
        d.setHours  (selected_hours  );
        d.setMinutes(selected_minutes);
        d.setSeconds(0);
        d.setMilliseconds(0);
        // let default date be the following 24 hours
        if (d.getTime() < new Date().getTime()) {
            d.setDate( d.getDate() + 1 );
        }
        dti.set_date(d, {/*preserve*/year:true,month:true,day:true});
    });

    $( document ).bind('content_added', datetime_init);
    $( document ).bind('content_added', media_dependent_datetime_init);
    $( document ).one ('media_loaded' , media_dependent_datetime_init);

    // Close the datetimepicker when something else is clicked
    $('body').live('click', function(evt) {
        // ignore clicking on the datepicker triggering icon
        if ($(evt.target).closest('.dtpicker-trigger').length) return true;
        // ignore clicking on the datepicker itself
        if ($(evt.target).closest('.datetimepicker').length)     return true;
        // if there's no datepicker shown, don't attempt to hide it
        if ($('.datetimepicker').not(':hidden').length == 0)     return true;
        // all else failed, hide all present datepickers
        $('.datetimepicker').hide();
        // and let the click be processed as usual
        return true;
    });

})(jQuery);
<commit_msg>Datetimepicker keeps its distance from page bottom. Fixes #2658<commit_after>function DateTimeInput(input) {
    this.input = input;
    this.x_pos = $(input).offset().left;
    this.cursor_pos = function(evt) {
        var input = this.input;
        var input_style;
        try {
            input_style = getComputedStyle(input, '');
        } catch(e) {
            input_style = input.currentStyle;
        }
        if (input_style == undefined) input_style = {};

        var padding = input_style.paddingLeft;
        if (/^(\d+)(?:px)?$/.test(padding)) padding = new Number(RegExp.$1);
        else padding = 0;
        var border = input_style.borderLeftWidth;
        if (/^(\d+)(?:px)?$/.test(border )) border  = new Number(RegExp.$1);
        else border = 0;

        var x = evt.clientX - this.x_pos - padding - border;

        var $tempspan = $('').css({
            position: 'absolute',
            top: '-100px'
        });
        for (var property in input_style) {
            if (property.substr(0, 4) == 'font') {
                $tempspan.css(property, input_style[property]);
            }
        }
        $tempspan.insertAfter($(input));

        $tempspan.text('m');
        var dotwidth = -$tempspan.width();
        $tempspan.text('m.');
        dotwidth += $tempspan.width();
        $tempspan.text( $(input).val() + '.' );

        var text, last = '';
        while ($tempspan.width() - dotwidth >= x) {
            text = $tempspan.text();
            if (text == '.') break;
            text = text.substr(0,text.length-1);
            last = text.substr(text.length-1);
            $tempspan.text(
                text.substr(0, text.length-1) + '.'
            );
        }
        text = $tempspan.text();
        text = text.substr(0, text.length-1) + last;
        $tempspan.remove();
//        ;;; $('#pos').text( text );
        return text.length;
    };
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec( $(this.input).val() );
        var year, month, day, hour, minute, dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            hour   = fields[4];
            minute = fields[5];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year  ) d.setFullYear(year  );
        if (preserve.month ) d.setMonth   (month );
        if (preserve.day   ) d.setDate    (day   );
        if (preserve.hour  ) d.setHours   (hour  );
        if (preserve.minute) d.setMinutes (minute);

        year = d.getFullYear();
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        hour = d.getHours();
        minute = d.getMinutes();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = ''
        + pad(  year,4) + '-'
        + pad( month,2) + '-'
        + pad(   day,2) + ' '
        + pad(  hour,2) + ':'
        + pad(minute,2) + ' '
        + dow;
        $(this.input).val( nval );
    };
    this.scroll = function(pos, delta) {
        var input = this.input;
        var fields = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2})/.exec( $(input).val() );
        if (!fields) {
            carp('Invalid date:', $(input).val());
            return;
        }

        var i = ([
            undefined,
            1,1,1,1,
            undefined,
            2,2,
            undefined,
            3,3,
            undefined,
            4,4,
            undefined,
            5,5,
            undefined,
            3,3
        ])[pos];
        if (i == undefined) return;
        fields[i] = new Number(fields[i]) + delta;

        var year   = fields[1];
        var month  = fields[2];
        var day    = fields[3];
        var hour   = fields[4];
        var minute = fields[5];
        month = new Number(month) - 1;

        var d = new Date();
        d.setFullYear(year);
        d.setMonth(month);
        d.setDate(day);
        d.setHours(hour);
        d.setMinutes(minute);
        d.setSeconds(0);
        d.setMilliseconds(0);

        this.set_date(d);

        input.selectionStart = input.selectionEnd = pos;
    };
}

function DateInput(input) {
    this.input = input;
    this.x_pos = $(input).offset().left;
    this.cursor_pos = function(evt) {
        var input = this.input;
        var input_style;
        try {
            input_style = getComputedStyle(input, '');
        } catch(e) {
            input_style = input.currentStyle;
        }
        if (input_style == undefined) input_style = {};

        var padding = input_style.paddingLeft;
        if (/^(\d+)(?:px)?$/.test(padding)) padding = new Number(RegExp.$1);
        else padding = 0;
        var border = input_style.borderLeftWidth;
        if (/^(\d+)(?:px)?$/.test(border )) border  = new Number(RegExp.$1);
        else border = 0;

        var x = evt.clientX - this.x_pos - padding - border;

        var $tempspan = $('').css({
            position: 'absolute',
            top: '-100px'
        });
        for (var property in input_style) {
            if (property.substr(0, 4) == 'font') {
                $tempspan.css(property, input_style[property]);
            }
        }
        $tempspan.insertAfter($(input));

        $tempspan.text('m');
        var dotwidth = -$tempspan.width();
        $tempspan.text('m.');
        dotwidth += $tempspan.width();
        $tempspan.text( $(input).val() + '.' );

        var text, last = '';
        while ($tempspan.width() - dotwidth >= x) {
            text = $tempspan.text();
            if (text == '.') break;
            text = text.substr(0,text.length-1);
            last = text.substr(text.length-1);
            $tempspan.text(
                text.substr(0, text.length-1) + '.'
            );
        }
        text = $tempspan.text();
        text = text.substr(0, text.length-1) + last;
        $tempspan.remove();
//        ;;; $('#pos').text( text );
        return text.length;
    };
    this.set_date = function(d, preserve) {
        var fields = /^(\d{4})-(\d{2})-(\d{2})/.exec( $(this.input).val() );
        var year, month, day,  dow;
        if (!fields) preserve = {};
        else {
            year   = fields[1];
            month  = fields[2];
            day    = fields[3];
            month = new Number(month) - 1;
        }
        if (!preserve) preserve = { };

        if (preserve.year ) d.setFullYear(year );
        if (preserve.month) d.setMonth   (month);
        if (preserve.day  ) d.setDate    (day  );

        year = d.getFullYear();
        month = new Number(d.getMonth()) + 1;
        day = d.getDate();
        dow = ([
            gettext('Su'), gettext('Mo'), gettext('Tu'), gettext('We'), gettext('Th'), gettext('Fr'), gettext('Sa')
        ])[ d.getDay() ];

        function pad(str,n) {
            var s = new String(str);
            while (s.length < n) s = '0'+s;
            return s;
        }

        var nval = ''
        + pad(  year,4) + '-'
        + pad( month,2) + '-'
        + pad(   day,2) + ' '
        + dow;
        $(this.input).val( nval );
    };
    this.scroll = function(pos, delta) {
        var input = this.input;
        var fields = /^(\d{4})-(\d{2})-(\d{2})/.exec( $(input).val() );
        if (!fields) {
            carp('Invalid date:', $(input).val());
            return;
        }

        var i = ([
            undefined,
            1,1,1,1,
            undefined,
            2,2,
            undefined,
            3,3,
            undefined,
            3,3
        ])[pos];
        if (i == undefined) return;
        fields[i] = new Number(fields[i]) + delta;

        var year   = fields[1];
        var month  = fields[2];
        var day    = fields[3];
        month = new Number(month) - 1;

        var d = new Date();
        d.setFullYear(year);
        d.setMonth(month);
        d.setDate(day);
        d.setHours(0);
        d.setMinutes(0);
        d.setSeconds(0);
        d.setMilliseconds(0);

        this.set_date(d);

        input.selectionStart = input.selectionEnd = pos;
    };
}

(function($) {
    function datetime_init() {
        $('.vDateTimeInput,.vDateInput').each( function() {
            var $input = $(this);
            var is_datetime = $input.hasClass('vDateTimeInput');
            if (! $input.data('dti')) {
                if (is_datetime) {
                    $input.data('dti', new DateTimeInput(this));
                }
                else {
                    $input.data('dti', new DateInput(this));
                }

                $(  ''
                ).click( function() {
                    var $dtp = $('.datetimepicker');
                    var x = $(this).offset().left + $(this).width();
                    var y = $(this).offset().top;

                    // check if Y doesn't reach below the current screen height
                    var d = y + $dtp.outerHeight()  -  $('body').outerHeight();
                    if (d > 0) y -= d;
                    if (y < 0) y = 0;   // But rather reach below bottom than above top

                    $('.datetimepicker').css({
                        top : y + 'px',
                        left: x + 'px'
                    }).toggle().data( 'input', $input );
                }).insertAfter(this);

                $input.keypress(function(evt) {
                    var delta = 0;
                    if (evt.keyCode == 38) delta =  1;
                    if (evt.keyCode == 40) delta = -1;
                    if (!delta) return true;
                    var pos = this.selectionEnd;
                    $input.data('dti').scroll(pos, delta);
                });
            }
        });
    }
    datetime_init();

    function media_dependent_datetime_init(evt) {
        // create the datetimepicker div
        if ($('.datetimepicker').length) { }    // but only if there is none yet
        else if (
               evt.type == 'content_added'  // and we added something that uses a datepicker
            && $(evt.target).find('.dtpicker-trigger').length == 0    // if anything
        ) { }
        else {
            // Container creation
            var $dtpicker = $('
');

            // Date picker
            var $datepicker = $('
');
            $datepicker.datepicker(new DATEPICKER_OPTIONS({
                onSelect: function(dtext, dpick) {
                    var $dtpicker = $(this).closest('.datetimepicker');
                    var dti = $( $dtpicker.data('input') ).data('dti');
                    var d = new Date();
                    d.setFullYear(dpick.selectedYear);
                    d.setMonth(dpick.selectedMonth);
                    d.setDate(dpick.selectedDay);
                    d.setHours(0);
                    d.setMinutes(2);
                    d.setSeconds(0);
                    d.setMilliseconds(0);
                    dti.set_date(d, {/*preserve*/hour:true,minute:true});
                },
                onClose: function() {
                    $(this).closest('.datetimepicker').hide();
                }
            }));
            $datepicker.appendTo($dtpicker);

            // Time picker
            var $timepicker = $('
')
            .html(
                '<button type="button" class="js-dtpicker-close"></button>' +
                '<button type="button" class="js-timepick js-time-0000">'+gettext('Midnight')+'</button>' +
                '<button type="button" class="js-timepick js-time-0600">'+gettext('6am'     )+'</button>' +
                '<button type="button" class="js-timepick js-time-1200">'+gettext('Noon'    )+'</button>'
            ).appendTo($dtpicker);

            // Container placement
            $dtpicker.appendTo(
                   $('.change-form').get(0)
                || $('#content').get(0)
                || $('body').get(0)
            );
        }

        // mousewheel datetime scrolling
        function mousewheel_handler(evt, delta) {
            var dti = $(this).data('dti');
            var pos = dti.cursor_pos(evt);
            dti.scroll(pos, delta / Math.abs(delta||1));
        };
        if (DEBUG)  // FIXME: mousewheel scrolling broken!
        $('.vDateTimeInput,.vDateInput').not('.mwheel-enhanced').focus( function() {
            $(this)  .bind('mousewheel', mousewheel_handler);
        }).blur( function() {
            $(this).unbind('mousewheel', mousewheel_handler);
        }).addClass('mwheel-enhanced');
    }

    $('.js-dtpicker-close').live('click', function(evt) {
        if (evt.button != 0) return;
        $(this).closest('.datetimepicker').hide();
    });
    $('.js-timepick').live('click', function(evt) {
        if (evt.button != 0) return;
        var dti = $( $(this).closest('.datetimepicker').data('input') ).data('dti');
        var selected_time = /js-time-(\d\d)(\d\d)/.exec(this.className);
        if ( ! selected_time ) return;
        var selected_hours   = selected_time[1];
        var selected_minutes = selected_time[2];
        var d = new Date();
        d.setHours  (selected_hours  );
        d.setMinutes(selected_minutes);
        d.setSeconds(0);
        d.setMilliseconds(0);
        // let default date be the following 24 hours
        if (d.getTime() < new Date().getTime()) {
            d.setDate( d.getDate() + 1 );
        }
        dti.set_date(d, {/*preserve*/year:true,month:true,day:true});
    });

    $( document ).bind('content_added', datetime_init);
    $( document ).bind('content_added', media_dependent_datetime_init);
    $( document ).one ('media_loaded' , media_dependent_datetime_init);

    // Close the datetimepicker when something else is clicked
    $('body').live('click', function(evt) {
        // ignore clicking on the datepicker triggering icon
        if ($(evt.target).closest('.dtpicker-trigger').length) return true;
        // ignore clicking on the datepicker itself
        if ($(evt.target).closest('.datetimepicker').length)   return true;
        // if there's no datepicker shown, don't attempt to hide it
        if ($('.datetimepicker').not(':hidden').length == 0)   return true;
        // all else failed, hide all present datepickers
        $('.datetimepicker').hide();
        // and let the click be processed as usual
        return true;
    });

})(jQuery);
<|endoftext|>