<commit_before>frappe.ui.form.Control = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.make();

		// if developer_mode=1, show fieldname as tooltip
		if(frappe.boot.user && frappe.boot.developer_mode===1 && this.$wrapper) {
			this.$wrapper.attr("title", __(this.df.fieldname));
		}

		if(this.render_input) {
			this.refresh();
		}
	},
	make: function() {
		this.make_wrapper();
		this.$wrapper
			.attr("data-fieldtype", this.df.fieldtype)
			.attr("data-fieldname", this.df.fieldname);
		this.wrapper = this.$wrapper.get(0);
		this.wrapper.fieldobj = this; // reference for event handlers
	},

	make_wrapper: function() {
		this.$wrapper = $("

").appendTo(this.parent);

		// alias
		this.wrapper = this.$wrapper;
	},

	toggle: function(show) {
		this.df.hidden = show ? 0 : 1;
		this.refresh();
	},

	// returns "Read", "Write" or "None"
	// as strings based on permissions
	get_status: function(explain) {
		if (this.df.get_status) {
			return this.df.get_status(this);
		}

		if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) {
			// like in case of a dialog box
			if (cint(this.df.hidden)) {
				// eslint-disable-next-line
				if (explain) console.log("By Hidden: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.hidden_due_to_dependency)) {
				// eslint-disable-next-line
				if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.read_only)) {
				// eslint-disable-next-line
				if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
				return "Read";

			} else if ((this.grid &&
						this.grid.display_status == 'Read') ||
						(this.layout &&
						this.layout.grid &&
						this.layout.grid.display_status == 'Read')) {
				// parent grid is read
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
				return "Read";
			}

			return "Write";
		}

		var status = frappe.perm.get_field_display_status(this.df,
			frappe.model.get_doc(this.doctype, this.docname), this.perm || (this.frm && this.frm.perm), explain);

		// Match parent grid controls read only status
		if (status === 'Write' && (this.grid || (this.layout && this.layout.grid) && !this.df.allow_on_submit)) {
			var grid = this.grid || this.layout.grid;
			if (grid.display_status == 'Read') {
				status = 'Read';
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
			}
		}

		// hide if no value
		if (this.doctype && status==="Read" && !this.only_input
			&& is_null(frappe.model.get_value(this.doctype, this.docname, this.df.fieldname))
			&& !in_list(["HTML", "Image", "Button"], this.df.fieldtype)) {

			// eslint-disable-next-line
			if (explain) console.log("By Hide Read-only, null fields: None"); // eslint-disable-line no-console
			status = "None";
		}

		return status;
	},
	refresh: function() {
		this.disp_status = this.get_status();
		this.$wrapper
			&& this.$wrapper.toggleClass("hide-control", this.disp_status=="None")
			&& this.refresh_input
			&& this.refresh_input();

		var value = this.get_value();

		this.show_translatable_button(value);
	},
	show_translatable_button(value) {
		// Disable translation non-string fields or special string fields
		if (!frappe.model
			|| !this.frm
			|| !this.doc
			|| !this.df.translatable
			|| !frappe.model.can_write('Translation')
			|| !value) return;

		// Disable translation in website
		if (!frappe.views || !frappe.views.TranslationManager) return;

		// Already attached button
		if (this.$wrapper.find('.clearfix .btn-translation').length) return;

		const translation_btn =
			`

			`;

		$(translation_btn)
			.appendTo(this.$wrapper.find('.clearfix'))
			.on('click', () => {
				if (!this.doc.__islocal) {
					new frappe.views.TranslationManager({
						'df': this.df,
						'source_text': value,
						'target_language': this.doc.language,
						'doc': this.doc
					});
				}
			});

	},
	get_doc: function() {
		return this.doctype && this.docname
			&& locals[this.doctype] && locals[this.doctype][this.docname] || {};
	},
	get_model_value: function() {
		if(this.doc) {
			return this.doc[this.df.fieldname];
		}
	},
	set_value: function(value) {
		return this.validate_and_set_in_model(value);
	},
	parse_validate_and_set_in_model: function(value, e) {
		if(this.parse) {
			value = this.parse(value);
		}
		return this.validate_and_set_in_model(value, e);
	},
	validate_and_set_in_model: function(value, e) {
		var me = this;
		if(this.inside_change_event) {
			return Promise.resolve();
		}
		this.inside_change_event = true;
		var set = function(value) {
			me.inside_change_event = false;
			return frappe.run_serially([
				() => me.set_model_value(value),
				() => {
					me.set_mandatory && me.set_mandatory(value);

					if(me.df.change || me.df.onchange) {
						// onchange event specified in df
						let set = (me.df.change || me.df.onchange).apply(me, [e]);
						me.set_invalid && me.set_invalid();
						return set;
					}
					me.set_invalid && me.set_invalid();
				}
			]);
		};

		value = this.validate(value);
		if (value && value.then) {
			// got a promise
			return value.then((value) => set(value));
		} else {
			// all clear
			return set(value);
		}
	},
	get_value: function() {
		if(this.get_status()==='Write') {
			return this.get_input_value ?
				(this.parse ? this.parse(this.get_input_value()) : this.get_input_value()) :
				undefined;
		} else {
			return this.value || undefined;
		}
	},
	set_model_value: function(value) {
		if(this.frm) {
			this.last_value = value;
			return frappe.model.set_value(this.doctype, this.docname, this.df.fieldname,
				value, this.df.fieldtype);
		} else {
			if(this.doc) {
				this.doc[this.df.fieldname] = value;
			}
			this.set_input(value);
			return Promise.resolve();
		}
	},
	set_focus: function() {
		if(this.$input) {
			this.$input.get(0).focus();
			return true;
		}
	}
});
<commit_msg>chore: Update frappe/public/js/frappe/form/controls/base_control.js<commit_after>frappe.ui.form.Control = Class.extend({
	init: function(opts) {
		$.extend(this, opts);
		this.make();

		// if developer_mode=1, show fieldname as tooltip
		if(frappe.boot.user && frappe.boot.developer_mode===1 && this.$wrapper) {
			this.$wrapper.attr("title", __(this.df.fieldname));
		}

		if(this.render_input) {
			this.refresh();
		}
	},
	make: function() {
		this.make_wrapper();
		this.$wrapper
			.attr("data-fieldtype", this.df.fieldtype)
			.attr("data-fieldname", this.df.fieldname);
		this.wrapper = this.$wrapper.get(0);
		this.wrapper.fieldobj = this; // reference for event handlers
	},

	make_wrapper: function() {
		this.$wrapper = $("

").appendTo(this.parent);

		// alias
		this.wrapper = this.$wrapper;
	},

	toggle: function(show) {
		this.df.hidden = show ? 0 : 1;
		this.refresh();
	},

	// returns "Read", "Write" or "None"
	// as strings based on permissions
	get_status: function(explain) {
		if (this.df.get_status) {
			return this.df.get_status(this);
		}

		if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) {
			// like in case of a dialog box
			if (cint(this.df.hidden)) {
				// eslint-disable-next-line
				if (explain) console.log("By Hidden: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.hidden_due_to_dependency)) {
				// eslint-disable-next-line
				if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.read_only)) {
				// eslint-disable-next-line
				if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
				return "Read";

			} else if ((this.grid &&
						this.grid.display_status == 'Read') ||
						(this.layout &&
						this.layout.grid &&
						this.layout.grid.display_status == 'Read')) {
				// parent grid is read
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
				return "Read";
			}

			return "Write";
		}

		var status = frappe.perm.get_field_display_status(this.df,
			frappe.model.get_doc(this.doctype, this.docname), this.perm || (this.frm && this.frm.perm), explain);

		// Match parent grid controls read only status
		if (status === 'Write' && (this.grid || (this.layout && this.layout.grid) && !cint(this.df.allow_on_submit))) {
			var grid = this.grid || this.layout.grid;
			if (grid.display_status == 'Read') {
				status = 'Read';
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
			}
		}

		// hide if no value
		if (this.doctype && status==="Read" && !this.only_input
			&& is_null(frappe.model.get_value(this.doctype, this.docname, this.df.fieldname))
			&& !in_list(["HTML", "Image", "Button"], this.df.fieldtype)) {

			// eslint-disable-next-line
			if (explain) console.log("By Hide Read-only, null fields: None"); // eslint-disable-line no-console
			status = "None";
		}

		return status;
	},
	refresh: function() {
		this.disp_status = this.get_status();
		this.$wrapper
			&& this.$wrapper.toggleClass("hide-control", this.disp_status=="None")
			&& this.refresh_input
			&& this.refresh_input();

		var value = this.get_value();

		this.show_translatable_button(value);
	},
	show_translatable_button(value) {
		// Disable translation non-string fields or special string fields
		if (!frappe.model
			|| !this.frm
			|| !this.doc
			|| !this.df.translatable
			|| !frappe.model.can_write('Translation')
			|| !value) return;

		// Disable translation in website
		if (!frappe.views || !frappe.views.TranslationManager) return;

		// Already attached button
		if (this.$wrapper.find('.clearfix .btn-translation').length) return;

		const translation_btn =
			`

			`;

		$(translation_btn)
			.appendTo(this.$wrapper.find('.clearfix'))
			.on('click', () => {
				if (!this.doc.__islocal) {
					new frappe.views.TranslationManager({
						'df': this.df,
						'source_text': value,
						'target_language': this.doc.language,
						'doc': this.doc
					});
				}
			});

	},
	get_doc: function() {
		return this.doctype && this.docname
			&& locals[this.doctype] && locals[this.doctype][this.docname] || {};
	},
	get_model_value: function() {
		if(this.doc) {
			return this.doc[this.df.fieldname];
		}
	},
	set_value: function(value) {
		return this.validate_and_set_in_model(value);
	},
	parse_validate_and_set_in_model: function(value, e) {
		if(this.parse) {
			value = this.parse(value);
		}
		return this.validate_and_set_in_model(value, e);
	},
	validate_and_set_in_model: function(value, e) {
		var me = this;
		if(this.inside_change_event) {
			return Promise.resolve();
		}
		this.inside_change_event = true;
		var set = function(value) {
			me.inside_change_event = false;
			return frappe.run_serially([
				() => me.set_model_value(value),
				() => {
					me.set_mandatory && me.set_mandatory(value);

					if(me.df.change || me.df.onchange) {
						// onchange event specified in df
						let set = (me.df.change || me.df.onchange).apply(me, [e]);
						me.set_invalid && me.set_invalid();
						return set;
					}
					me.set_invalid && me.set_invalid();
				}
			]);
		};

		value = this.validate(value);
		if (value && value.then) {
			// got a promise
			return value.then((value) => set(value));
		} else {
			// all clear
			return set(value);
		}
	},
	get_value: function() {
		if(this.get_status()==='Write') {
			return this.get_input_value ?
				(this.parse ? this.parse(this.get_input_value()) : this.get_input_value()) :
				undefined;
		} else {
			return this.value || undefined;
		}
	},
	set_model_value: function(value) {
		if(this.frm) {
			this.last_value = value;
			return frappe.model.set_value(this.doctype, this.docname, this.df.fieldname,
				value, this.df.fieldtype);
		} else {
			if(this.doc) {
				this.doc[this.df.fieldname] = value;
			}
			this.set_input(value);
			return Promise.resolve();
		}
	},
	set_focus: function() {
		if(this.$input) {
			this.$input.get(0).focus();
			return true;
		}
	}
});
<|endoftext|>


<commit_before>// Whoo! Welcome to spopsh's source code!! ❤ ahstro \\

// TODO:
// Re-render last command on resize
// Add config to set order of songs/index/time etc. on qls/ls, custom prompt
// Improve colors (theme vs direct)
// Make ^C clear line
// Make ^D exit
// "Random" crashses might be because telnet doesn't output JSON, e.g. 'bye' outputs 'Bye bye!'

var telnet = new require('telnet-client')();

var colors = require('colors');
colors.setTheme({
    help: ['magenta', 'bold'],
    info: ['blue', 'bold'],
    error: 'red',
    win: 'green'
});

var rl = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
});
rl.setPrompt('❤ '.red);
rl.on('line', function(cmd) {
    rl.pause();
    execute(cmd);
});

telnet.on('ready', function() {
    console.log("Welcome to spopsh!\nType 'help' for a list of commands");
    getCommand(process.argv[2]);
});

telnet.on('error', function() {
    console.log("Fatal error! Your spop daemon might not be running.");
    process.exit(5);
});

telnet.on('close', function() {
    process.exit();
});

// TODO: Add to separate file?
var customCommands = [
    {
        name: 'about',
        args: '',
        summary: 'information about spopsh and spop'
    },
    {
        name: 'clear',
        args: '',
        summary: 'clears the screen, same as ctrl+u'
    }
];

function getSpacer(leftArr, rightArr) {
    var leftLength = 0;
    for(var i = 0; i < leftArr.length; i++) {
        leftLength += leftArr[i].length;
    }
    var rightLength = 0;
    for(var i = 0; i < rightArr.length; i++) {
        rightLength += rightArr[i].length;
    }
    var spacerLength = (process.stdout.columns - (leftLength + rightLength));
    var spacer = '';
    for(var j = 0; j < spacerLength; j++) spacer += ' ';
    return spacer;
}

function getTime(duration) {
    var minutes = Math.floor((duration / 1000) / 60);
    var seconds = (duration / 1000) % 60;
        seconds = seconds < 10 ? '0' + seconds : seconds;
    return minutes + ':' + seconds;
}

function parse(cmd, json) {
    var output = '';
    switch(cmd) {
        case 'help':
            output += "Available commands:".help;
            for(var i = 0; i < json.commands.length; i++) {
                var name = json.commands[i].command;
                var args = '';
                json.commands[i].args.forEach(function(arg) {
                    args += ' [' + arg.blue + ']';
                });
                var summary = json.commands[i].summary;

                output += '\n' + name.info + args + '\n  ' + summary;
            }
            for(var i = 0; i < customCommands.length; i++) {
                output += '\n' + customCommands[i].name.info + customCommands[i].args + '\n  ' + customCommands[i].summary;
            }
            break;

        case 'ls':
            output += "Playlists:".help;
            for(var i = 0; i < json.playlists.length; i++) {
                var name = json.playlists[i].name || '★';
                var index = json.playlists[i].index || i; // Can't get json if offline; i should be same
                index = index < 10 ? '0' + index : index;
                index += ' ';
                var offline = ' [' + (json.playlists[i].offline ? '✓': '✕') + ']';
                var spacer = getSpacer([index, name], [offline]);
                // Setting color in the original declaration of 'offline'
                // messes with the .length property used by getSpacer()
                offline = offline.replace(/[✓✕]/, function(p1) {
                    return p1 === '✓' ? '✓'.win : '✕'.error;
                });
                output += '\n' + index.info + (name === '★' ? name.yellow : name) + spacer + offline;
            }
            break;

        case 'next':
        case 'prev':
        //TODO: Make sure these make sense
        case 'qclear':
        case 'stop':
        case 'status':
        case 'notify':
        case 'repeat':
        case 'shuffle':
        case 'toggle':
        case 'play':
            var stat, song;
            if(json.status === 'playing' || json.status === 'paused' ) {
                //stat = json.status === 'playing' ? '♪'.win + ' Playing: ' :  '▮▮'.yellow + ' Paused: ';
                stat = json.status === 'playing' ? '♪' + ' Playing: ' :  '▮▮' + ' Paused: ';
                song = json.artist  + ' - ' + json.title + ' - ' + json.album;
            } else {
                stat = '■' + ' Stopped';
                song = '';
            }
            var shuffle = ' Shuffle: ' + (json.shuffle ? '↝' : '→');
            var repeat = ' Repeat: ' + (json.repeat ? '↺' : '→');
            var track = ' Track: ' + (json.current_track || ' ') + '/' + json.total_tracks;
            //TODO: output += getTime(json.position * 1000) + '/' + getTime(json.duration) + '\n';
            var spacer = getSpacer([stat, song], [repeat, shuffle, track]);
            stat = stat.replace(/[♪▮■]/g, function(p1) {
                if(p1 === '♪') {
                    return '♪'.win;
                } else if (p1 === '▮') {
                    return '▮'.yellow;
                } else if (p1 === '■'){
                    return '■'.error;
                }
            });
            output += stat + song + spacer + repeat + shuffle + track + '\n';
            for(var i = 0; i < process.stdout.columns; i++) {
                output += i < (json.position / (json.duration / 1000)) * process.stdout.columns ? '='.blue : '-'.info;
            }
            break;

        // TODO
        // TODO: Currently playing?
        case 'qls':
            output += "Queue:".help;
            for(var i = 0; i < json.tracks.length; i++) {
                var index = (json.tracks[i].index < 10 ? '0' + json.tracks[i].index : json.tracks[i].index.toString());
                var song = ' ' + json.tracks[i].artist + ' - ' + json.tracks[i].title;
                var album = ' [' + json.tracks[i].album + '] ';
                var time = getTime(json.tracks[i].duration);
                var spacer = getSpacer([index, song], [album, time]);

                // Grey out song if not available (offline doesn't grey. bug in spop?)
                if(json.tracks[i].available) {
                    output += '\n' + index.info + song + spacer + album + time.yellow;
                } else {
                    output += '\n' + index.grey + song.grey + spacer + album + time.grey;
                }
            }
            break;

        case 'qrm':
        case 'add':
        case 'seek':
        case 'goto':
        case 'offline-status':
        case 'offline-toggle':
        case 'image':
        case 'uinfo':
        case 'uadd':
        case 'uplay':
        case 'uimage':
        case 'search':
        case 'bye':
        case 'quit':
        case 'idle':
            output = json;
            break;

        case 'about':
            output = "spopsh is pretty much a prettifier of spop's telnet interface.\nYou can check out the spopsh's source code at https://github.com/ahstro/spopsh\nand spop over at https://github.com/Schnouki/spop\n  ❤ ahstro";
            break;

        // TODO: Add monitor command to show status dynamically

        case '':
            // Stops 'Invalid command!' output for custom command
            return;

        default:
            output = 'Invalid command!'.error + "\nTry the 'help' command for a list of available commands";
    }

    console.log(output);
}

function alias(cmd) {
    // Aliases
    // TODO: Add to 'help' somehow
    switch(cmd) {
        case 'q':
        case 'e':
        case 'exit':
            return 'quit';

        case 'n':
            return 'next';

        case 'p':
        case 'previous':
            return 'prev';

        case 'dir':
        case 'list':
        case 'playlists':
            return 'ls';

        case 'clear':
            rl.write(null, {ctrl: true, name: 'l'});
            return '';

        default:
            return cmd;
    }
}

function execute(cmd) {
    cmd = alias(cmd);

    telnet.exec(cmd, { echoLines: -1 }, function(res) {
        // TODO: Add subcommands, e.g. 'help notify' to bring up help on just notify command
        //cmd = cmd.split(' ');

        // TODO: Fix hack
        // Might be unnecessary
        if(cmd === 'bye' || cmd === 'quit') process.exit();

        try {
            var json = JSON.parse(res);
            parse(cmd, json);
        } catch (e) {
            // TODO
            console.log(e);
        } finally {
            getCommand();
        }
    });
}

function getCommand(arg) {
    if(arg) execute(arg);
    rl.prompt();
    rl.resume();
}

telnet.connect({
    host: '127.0.0.1',
    port: 6602,
    irs: '\n',
    shellPrompt: '\n',
    timeout: 0
});
<commit_msg>Partial argument functionality, e.g., 'ls 30'<commit_after>// Whoo! Welcome to spopsh's source code!! ❤ ahstro \\

// TODO:
// Re-render last command on resize
// Add config to set order of songs/index/time etc. on qls/ls, custom prompt
// Improve colors (theme vs direct)
// Make ^C clear line
// Make ^D exit
// "Random" crashses might be because telnet doesn't output JSON, e.g. 'bye' outputs 'Bye bye!'

var telnet = new require('telnet-client')();

var colors = require('colors');
colors.setTheme({
    help: ['magenta', 'bold'],
    info: ['blue', 'bold'],
    error: 'red',
    win: 'green'
});

var rl = require('readline').createInterface({
    input: process.stdin,
    output: process.stdout
});
rl.setPrompt('❤ '.red);
rl.on('line', function(cmd) {
    rl.pause();
    execute(cmd);
});

telnet.on('ready', function() {
    console.log("Welcome to spopsh!\nType 'help' for a list of commands");
    var args = process.argv.splice(2).join(' ');
    getCommand(args);
});

telnet.on('error', function() {
    console.log("Fatal error! Your spop daemon might not be running.");
    process.exit(5);
});

telnet.on('close', function() {
    process.exit();
});

// TODO: Add to separate file?
var customCommands = [
    {
        name: 'about',
        args: '',
        summary: 'information about spopsh and spop'
    },
    {
        name: 'clear',
        args: '',
        summary: 'clears the screen, same as ctrl+u'
    }
];

function getPlaylist(json, i) {
    var name = json.name || '★';
    var index = json.index || i; // Can't get json if offline; i should be same
    index = index < 10 ? '0' + index : index;
    index += ' ';
    var offline = ' [' + (json.offline ? '✓': '✕') + ']';
    var spacer = getSpacer([index, name], [offline]);
    // Setting color in the original declaration of 'offline'
    // messes with the .length property used by getSpacer()
    offline = offline.replace(/[✓✕]/, function(p1) {
        return p1 === '✓' ? '✓'.win : '✕'.error;
    });
    return '\n' + index.info + (name === '★' ? name.yellow : name) + spacer + offline;
}

function getTrack(json) {
    var index = (json.index < 10 ? '0' + json.index : json.index.toString());
    var song = ' ' + json.artist + ' - ' + json.title;
    var album = ' [' + json.album + '] ';
    var time = getTime(json.duration);
    var spacer = getSpacer([index, song], [album, time]);

    // Grey out song if not available (offline doesn't grey. bug in spop?)
    if(json.available) {
        return '\n' + index.info + song + spacer + album + time.yellow;
    } else {
        return '\n' + index.grey + song.grey + spacer + album + time.grey;
    }
}

function getSpacer(leftArr, rightArr) {
    var leftLength = 0;
    for(var i = 0; i < leftArr.length; i++) {
        leftLength += leftArr[i].length;
    }
    var rightLength = 0;
    for(var i = 0; i < rightArr.length; i++) {
        rightLength += rightArr[i].length;
    }
    var spacerLength = (process.stdout.columns - (leftLength + rightLength));
    var spacer = '';
    for(var j = 0; j < spacerLength; j++) spacer += ' ';
    return spacer;
}

function getTime(duration) {
    var minutes = Math.floor((duration / 1000) / 60);
    var seconds = Math.floor((duration / 1000) % 60);
        seconds = seconds < 10 ? '0' + seconds : seconds;
    return minutes + ':' + seconds;
}

function parse(cmd, json) {
    cmd = cmd.split(' ');
    var output = '';
    switch(cmd[0]) {
        case 'help':
            if(cmd[1]) {
                output += "'help' doesn't take arguments";
                break;
            }
            output += "Available commands:".help;
            for(var i = 0; i < json.commands.length; i++) {
                var name = json.commands[i].command;
                var args = '';
                json.commands[i].args.forEach(function(arg) {
                    args += ' [' + arg.blue + ']';
                });
                var summary = json.commands[i].summary;

                output += '\n' + name.info + args + '\n  ' + summary;
            }
            for(var i = 0; i < customCommands.length; i++) {
                output += '\n' + customCommands[i].name.info + customCommands[i].args + '\n  ' + customCommands[i].summary;
            }
            break;

        case 'ls':
            if(!cmd[1]) {
                // TODO: Add argument support
                output += "Playlists:".help;
                for(var i = 0; i < json.playlists.length; i++) {
                    output += getPlaylist(json.playlists[i], i);
                }
            } else if (!isNaN(cmd[1]) && !cmd[2]) {
                output += "Tracks:".help;
                for(var i = 0; i < json.tracks.length; i++) {
                    output += getTrack(json.tracks[i], i);
                }
            } else {
                output += "'ls' doesn't take that many arguments";
                // TODO: Add ability to search for playlist names
            }

            break;

        case 'next':
        case 'prev':
        //TODO: Make sure these make sense
        case 'qclear':
        case 'stop':
        case 'status':
        case 'notify':
        case 'repeat':
        case 'shuffle':
        case 'toggle':
        case 'play':
            var stat, song;
            if(json.status === 'playing' || json.status === 'paused' ) {
                //stat = json.status === 'playing' ? '♪'.win + ' Playing: ' :  '▮▮'.yellow + ' Paused: ';
                stat = json.status === 'playing' ? '♪' + ' Playing: ' :  '▮▮' + ' Paused: ';
                song = json.artist  + ' - ' + json.title + ' - ' + json.album;
            } else {
                stat = '■' + ' Stopped';
                song = '';
            }
            var shuffle = ' Shuffle: ' + (json.shuffle ? '↝' : '→');
            var repeat = ' Repeat: ' + (json.repeat ? '↺' : '→');
            var track = ' Track: ' + (json.current_track || ' ') + '/' + json.total_tracks;
            //TODO: output += getTime(json.position * 1000) + '/' + getTime(json.duration) + '\n';
            var spacer = getSpacer([stat, song], [repeat, shuffle, track]);
            stat = stat.replace(/[♪▮■]/g, function(p1) {
                if(p1 === '♪') {
                    return '♪'.win;
                } else if (p1 === '▮') {
                    return '▮'.yellow;
                } else if (p1 === '■'){
                    return '■'.error;
                }
            });
            output += stat + song + spacer + repeat + shuffle + track + '\n';
            for(var i = 0; i < process.stdout.columns; i++) {
                output += i < (json.position / (json.duration / 1000)) * process.stdout.columns ? '='.blue : '-'.info;
            }
            break;

        // TODO
        // TODO: Currently playing?
        case 'qls':
            output += "Queue:".help;
            for(var i = 0; i < json.tracks.length; i++) {
                output += getTrack(json.tracks[i]);
            }
            break;

        case 'qrm':
        case 'add':
        case 'seek':
        case 'goto':
        case 'offline-status':
        case 'offline-toggle':
        case 'image':
        case 'uinfo':
        case 'uadd':
        case 'uplay':
        case 'uimage':
        case 'search':
        case 'bye':
        case 'quit':
        case 'idle':
            output = json;
            break;

        case 'about':
            output = "spopsh is pretty much a prettifier of spop's telnet interface.\nYou can check out the spopsh's source code at https://github.com/ahstro/spopsh\nand spop over at https://github.com/Schnouki/spop\n  ❤ ahstro";
            break;

        // TODO: Add monitor command to show status dynamically

        case '':
            // Stops 'Invalid command!' output for custom command
            return;

        default:
            output = 'Invalid command!'.error + "\nTry the 'help' command for a list of available commands";
    }

    console.log(output);
}

function alias(cmd) {
    // Aliases
    // TODO: Add to 'help' somehow
    switch(cmd) {
        case 'q':
        case 'e':
        case 'exit':
            return 'quit';

        case 'n':
            return 'next';

        case 'p':
        case 'previous':
            return 'prev';

        case 'dir':
        case 'list':
        case 'playlists':
            return 'ls';

        case 'clear':
            rl.write(null, {ctrl: true, name: 'l'});
            return '';

        default:
            return cmd;
    }
}

function execute(cmd) {
    cmd = alias(cmd);

    telnet.exec(cmd, { echoLines: -1 }, function(res) {
        // TODO: Add subcommands, e.g. 'help notify' to bring up help on just notify command
        //cmd = cmd.split(' ');

        // TODO: Fix hack
        // Might be unnecessary
        if(cmd === 'bye' || cmd === 'quit') process.exit();

        try {
            var json = JSON.parse(res);
            parse(cmd, json);
        } catch (e) {
            // TODO
            console.log(e);
        } finally {
            getCommand();
        }
    });
}

function getCommand(arg) {
    if(arg) execute(arg);
    rl.prompt();
    rl.resume();
}

telnet.connect({
    host: '127.0.0.1',
    port: 6602,
    irs: '\n',
    shellPrompt: '\n',
    timeout: 0
});
<|endoftext|>


<commit_before>/*
 * draggable_core.js
 */

(function( $ ) {

module( "draggable: core" );

test( "element types", function() {
	var typeNames = (
			"p,h1,h2,h3,h4,h5,h6,blockquote,ol,ul,dl,div,form" +
			",table,fieldset,address,ins,del,em,strong,q,cite,dfn,abbr" +
			",acronym,code,samp,kbd,var,img,hr" +
			",input,button,label,select,iframe"
		).split(",");

	expect( typeNames.length * 2 );

	$.each( typeNames, function( i ) {
		var offsetBefore, offsetAfter,
			typeName = typeNames[ i ],
			el = $( document.createElement( typeName ) ).appendTo("#qunit-fixture");

		if ( typeName === "table" ) {
			el.append("
content
");
		}

		// intrinsic height is incorrect in FF for buttons with no content
		// https://bugzilla.mozilla.org/show_bug.cgi?id=471763
		// Support: FF
		if ( typeName === "button" ) {
			el.text( "button" );
		}

		el.draggable({ cancel: "" });
		offsetBefore = el.offset();
		el.simulate( "drag", {
			dx: 50,
			dy: 50
		});
		offsetAfter = el.offset();

		// Support: FF, Chrome, and IE9,
		// there are some rounding errors in so we can't say equal, we have to settle for close enough
		closeEnough( offsetBefore.left, offsetAfter.left - 50, 1, "dragged[50, 50] " + "<" + typeName + "> left" );
		closeEnough( offsetBefore.top, offsetAfter.top - 50, 1, "dragged[50, 50] " + "<" + typeName + "> top" );
		el.draggable("destroy");
		el.remove();
	});
});

test( "No options, relative", function() {
	expect( 2 );
	TestHelpers.draggable.shouldMove( $( "#draggable1" ).draggable(), "no options, relative" );
});

test( "No options, absolute", function() {
	expect( 2 );
	TestHelpers.draggable.shouldMove( $( "#draggable2" ).draggable(), "no options, absolute" );
});

test( "resizable handle with complex markup (#8756 / #8757)", function() {
	expect( 2 );

	$( "#draggable1" )
		.append(
			$("
")
				.addClass("ui-resizable-handle ui-resizable-w")
				.append( $("
") )
		);

	var handle = $(".ui-resizable-w div"),
		target = $( "#draggable1" ).draggable().resizable({ handles: "all" });

	// todo: fix resizable so it doesn't require a mouseover
	handle.simulate("mouseover").simulate( "drag", { dx: -50 } );
	equal( target.width(), 250, "compare width" );

	// todo: fix resizable so it doesn't require a mouseover
	handle.simulate("mouseover").simulate( "drag", { dx: 50 } );
	equal( target.width(), 200, "compare width" );
});

test( "#8269: Removing draggable element on drop", function() {
	expect( 2 );

	var element = $( "#draggable1" ).wrap( "
" ).draggable({
			stop: function() {
				ok( true, "stop still called despite element being removed from DOM on drop" );
			}
		}),
		dropOffset = $( "#droppable" ).offset();

	$( "#droppable" ).droppable({
		drop: function() {
			$( "#wrapper" ).remove();
			ok( true, "element removed from DOM on drop" );
		}
	});

	// Support: Opera 12.10, Safari 5.1, jQuery <1.8
	if ( TestHelpers.draggable.unreliableContains ) {
		ok( true, "Opera <12.14 and Safari <6.0 report wrong values for $.contains in jQuery < 1.8" );
		ok( true, "Opera <12.14 and Safari <6.0 report wrong values for $.contains in jQuery < 1.8" );
	} else {
		element.simulate( "drag", {
			handle: "corner",
			x: dropOffset.left,
			y: dropOffset.top
		});
	}
});

// http://bugs.jqueryui.com/ticket/7778
// drag element breaks in IE8 when its content is replaced onmousedown
test( "Stray mousemove after mousedown still drags", function() {
	expect( 2 );

	var element = $( "#draggable1" ).draggable({ scroll: false });

	// In IE8, when content is placed under the mouse (e.g. when draggable content is replaced
	// on mousedown), mousemove is triggered on those elements even though the mouse hasn't moved.
	// Support: IE <9
	element.bind( "mousedown", function() {
		$( document ).simulate( "mousemove", { button: -1 });
	});

	TestHelpers.draggable.shouldMove( element, "element is draggable" );
});

test( "#6258: not following mouse when scrolled and using overflow-y: scroll", function() {
	expect( 2 );

	var element = $( "#draggable1" ).draggable({
			stop: function( event, ui ) {
				equal( ui.position.left, 1, "left position is correct despite overflow on HTML" );
				equal( ui.position.top, 1, "top position is correct despite overflow on HTML" );
				$( "html" )
					.css( "overflow-y", oldOverflowY )
					.css( "overflow-x", oldOverflowX )
					.scrollTop( 0 )
					.scrollLeft( 0 );
			}
		}),
		oldOverflowY = $( "html" ).css( "overflow-y" ),
		oldOverflowX = $( "html" ).css( "overflow-x" );

		TestHelpers.forceScrollableWindow();

		$( "html" )
			.css( "overflow-y", "scroll" )
			.css( "overflow-x", "scroll" )
			.scrollTop( 300 )
			.scrollLeft( 300 );

		element.simulate( "drag", {
			dx: 1,
			dy: 1,
			moves: 1
		});
});

test( "#9315: jumps down with offset of scrollbar", function() {
	expect( 2 );

	var element = $( "#draggable2" ).draggable({
			stop: function( event, ui ) {
				equal( ui.position.left, 11, "left position is correct when position is absolute" );
				equal( ui.position.top, 11, "top position is correct when position is absolute" );
				$( "html" ).scrollTop( 0 ).scrollLeft( 0 );
			}
		});

		TestHelpers.forceScrollableWindow();

		$( "html" ).scrollTop( 300 ).scrollLeft( 300 );

		element.simulate( "drag", {
			dx: 1,
			dy: 1,
			moves: 1
		});
});

test( "#5009: scroll not working with parent's position fixed", function() {
	expect( 2 );

	var startValue = 300,
		element = $( "#draggable1" ).wrap( "
" ).draggable({
			drag: function() {
				startValue += 100;
				$( document ).scrollTop( startValue ).scrollLeft( startValue );
			},
			stop: function( event, ui ) {
				equal( ui.position.left, 10, "left position is correct when parent position is fixed" );
				equal( ui.position.top, 10, "top position is correct when parent position is fixed" );
				$( document ).scrollTop( 0 ).scrollLeft( 0 );
			}
		});

	TestHelpers.forceScrollableWindow();

	$( "#wrapper" ).css( "position", "fixed" );

	element.simulate( "drag", {
		dx: 10,
		dy: 10,
		moves: 3
	});
});

$( [ "hidden", "auto", "scroll" ] ).each(function() {
	var overflow = this;

	// http://bugs.jqueryui.com/ticket/9379 - position bug in scrollable div
	// http://bugs.jqueryui.com/ticket/10147 - Wrong position in a parent with "overflow: hidden"
	test( "position in scrollable parent with overflow: " + overflow, function() {
		expect( 2 );

		$( "#qunit-fixture" ).html( "

a

" );
		$( "#inner" ).css({ position: "absolute", width: "500px", height: "500px" });
		$( "#outer" ).css({ position: "absolute", width: "300px", height: "300px" });
		$( "#dragged" ).css({ width: "10px", height: "10px" });

		var moves = 3,
			startValue = 0,
			dragDelta = 20,
			delta = 100,

			// we scroll after each drag event, so subtract 1 from number of moves for expected
			expected = delta + ( ( moves - 1 ) * dragDelta ),
			element = $( "#dragged" ).draggable({
				drag: function() {
					startValue += dragDelta;
					$( "#outer" ).scrollTop( startValue ).scrollLeft( startValue );
				},
				stop: function( event, ui ) {
					equal( ui.position.left, expected, "left position is correct when grandparent is scrolled" );
					equal( ui.position.top, expected, "top position is correct when grandparent is scrolled" );
				}
			});

		$( "#outer" ).css( "overflow", overflow );

		element.simulate( "drag", {
			dy: delta,
			dx: delta,
			moves: moves
		});
	});
});

test( "#5727: draggable from iframe", function() {
	expect( 1 );

	var iframeBody, draggable1,
		iframe = $( "" ).appendTo( "#qunit-fixture" ),
		iframeDoc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;

	iframeDoc.write( "<!doctype html>" );
	iframeDoc.close();

	iframeBody = $( iframeDoc.body ).append( "
" );
	draggable1 = iframeBody.find( "div" );

	draggable1.draggable();

	equal( draggable1.closest( iframeBody ).length, 1 );

	// TODO: fix draggable within an IFRAME to fire events on the element properly
	// and these TestHelpers.draggable.shouldMove relies on events for testing
	//TestHelpers.draggable.shouldMove( draggable1, "draggable from an iframe" );
});

test( "#8399: A draggable should become the active element after you are finished interacting with it, but not before.", function() {
	expect( 2 );

	var element = $( "link" ).appendTo( "#qunit-fixture" ).draggable();

	$( document ).one( "mousemove", function() {
		notStrictEqual( document.activeElement, element.get( 0 ), "moving a draggable anchor did not make it the active element" );
	});

	TestHelpers.draggable.move( element, 50, 50 );

	strictEqual( document.activeElement, element.get( 0 ), "finishing moving a draggable anchor made it the active element" );
});

asyncTest( "blur behavior", function() {
	expect( 3 );

	var element = $( "#draggable1" ).draggable(),
		focusElement = $( "

" ).appendTo( element );

	TestHelpers.onFocus( focusElement, function() {
		strictEqual( document.activeElement, focusElement.get( 0 ), "test element is focused before mousing down on a draggable" );

		TestHelpers.draggable.move( focusElement, 1, 1 );

		// http://bugs.jqueryui.com/ticket/10527
		// Draggable: Can't select option in modal dialog (IE8)
		strictEqual( document.activeElement, focusElement.get( 0 ), "test element is focused after mousing down on itself" );

		TestHelpers.draggable.move( element, 50, 50 );

		// http://bugs.jqueryui.com/ticket/4261
		// active element should blur when mousing down on a draggable
		notStrictEqual( document.activeElement, focusElement.get( 0 ), "test element is no longer focused after mousing down on a draggable" );
		start();
	});
});

test( "ui-draggable-handle assigned to appropriate element", function() {
	expect( 5 );

	var p = $( "
" ).appendTo( "#qunit-fixture" ),
		element = $( "

" ).appendTo( "#qunit-fixture" ).draggable();
	ok( element.hasClass( "ui-draggable-handle" ), "handle is element by default" );

	element.draggable( "option", "handle", "p" );
	ok( !element.hasClass( "ui-draggable-handle" ), "removed from element" );
	ok( element.find( "p" ).hasClass( "ui-draggable-handle" ), "added to handle" );

	ok( !p.hasClass( "ui-draggable-handle" ),
		"ensure handle class name is constrained within the draggble (#10212)" );

	element.draggable( "destroy" );
	ok( !element.find( "p" ).hasClass( "ui-draggable-handle" ), "removed in destroy()" );
});

test( "ui-draggable-handle managed correctly in nested draggables", function() {
	expect( 4 );
	var parent = $( "

" ).draggable().appendTo( "#qunit-fixture" ),
		child = parent.find( "div" ).draggable();

	ok( parent.hasClass( "ui-draggable-handle" ), "parent has class name on init" );
	ok( child.hasClass( "ui-draggable-handle" ), "child has class name on init" );

	parent.draggable( "destroy" );
	ok( !parent.hasClass( "ui-draggable-handle" ), "parent loses class name on destroy" );
	ok( child.hasClass( "ui-draggable-handle" ), "child retains class name on destroy" );
});

// http://bugs.jqueryui.com/ticket/7772
// when css 'right' is set, element resizes on drag
test( "setting right/bottom css shouldn't cause resize", function() {
	expect( 3 );

	var finalOffset,
		element = $( "#draggable3" ),
		origWidth = element.width(),
		origHeight = element.height(),
		origOffset = element.offset();

	element.draggable();

	TestHelpers.draggable.move( element, -50, -50 );

	finalOffset = element.offset();
	finalOffset.left += 50;
	finalOffset.top += 50;

	equal( element.width(), origWidth, "element retains width" );
	equal( element.height(), origHeight, "element retains height" );
	deepEqual( finalOffset, origOffset, "element moves the correct distance" );
});

})( jQuery );
<commit_msg>Draggable Tests: Fix failures due to fractional widths<commit_after>/*
 * draggable_core.js
 */

(function( $ ) {

module( "draggable: core" );

test( "element types", function() {
	var typeNames = (
			"p,h1,h2,h3,h4,h5,h6,blockquote,ol,ul,dl,div,form" +
			",table,fieldset,address,ins,del,em,strong,q,cite,dfn,abbr" +
			",acronym,code,samp,kbd,var,img,hr" +
			",input,button,label,select,iframe"
		).split(",");

	expect( typeNames.length * 2 );

	$.each( typeNames, function( i ) {
		var offsetBefore, offsetAfter,
			typeName = typeNames[ i ],
			el = $( document.createElement( typeName ) ).appendTo("#qunit-fixture");

		if ( typeName === "table" ) {
			el.append("
content
");
		}

		// intrinsic height is incorrect in FF for buttons with no content
		// https://bugzilla.mozilla.org/show_bug.cgi?id=471763
		// Support: FF
		if ( typeName === "button" ) {
			el.text( "button" );
		}

		el.draggable({ cancel: "" });
		offsetBefore = el.offset();
		el.simulate( "drag", {
			dx: 50,
			dy: 50
		});
		offsetAfter = el.offset();

		// Support: FF, Chrome, and IE9,
		// there are some rounding errors in so we can't say equal, we have to settle for close enough
		closeEnough( offsetBefore.left, offsetAfter.left - 50, 1, "dragged[50, 50] " + "<" + typeName + "> left" );
		closeEnough( offsetBefore.top, offsetAfter.top - 50, 1, "dragged[50, 50] " + "<" + typeName + "> top" );
		el.draggable("destroy");
		el.remove();
	});
});

test( "No options, relative", function() {
	expect( 2 );
	TestHelpers.draggable.shouldMove( $( "#draggable1" ).draggable(), "no options, relative" );
});

test( "No options, absolute", function() {
	expect( 2 );
	TestHelpers.draggable.shouldMove( $( "#draggable2" ).draggable(), "no options, absolute" );
});

test( "resizable handle with complex markup (#8756 / #8757)", function() {
	expect( 2 );

	$( "#draggable1" )
		.append(
			$("
")
				.addClass("ui-resizable-handle ui-resizable-w")
				.append( $("
") )
		);

	var handle = $(".ui-resizable-w div"),
		target = $( "#draggable1" ).draggable().resizable({ handles: "all" });

	// todo: fix resizable so it doesn't require a mouseover
	handle.simulate("mouseover").simulate( "drag", { dx: -50 } );
	equal( target.width(), 250, "compare width" );

	// todo: fix resizable so it doesn't require a mouseover
	handle.simulate("mouseover").simulate( "drag", { dx: 50 } );
	equal( target.width(), 200, "compare width" );
});

test( "#8269: Removing draggable element on drop", function() {
	expect( 2 );

	var element = $( "#draggable1" ).wrap( "
" ).draggable({
			stop: function() {
				ok( true, "stop still called despite element being removed from DOM on drop" );
			}
		}),
		dropOffset = $( "#droppable" ).offset();

	$( "#droppable" ).droppable({
		drop: function() {
			$( "#wrapper" ).remove();
			ok( true, "element removed from DOM on drop" );
		}
	});

	// Support: Opera 12.10, Safari 5.1, jQuery <1.8
	if ( TestHelpers.draggable.unreliableContains ) {
		ok( true, "Opera <12.14 and Safari <6.0 report wrong values for $.contains in jQuery < 1.8" );
		ok( true, "Opera <12.14 and Safari <6.0 report wrong values for $.contains in jQuery < 1.8" );
	} else {
		element.simulate( "drag", {
			handle: "corner",
			x: dropOffset.left,
			y: dropOffset.top
		});
	}
});

// http://bugs.jqueryui.com/ticket/7778
// drag element breaks in IE8 when its content is replaced onmousedown
test( "Stray mousemove after mousedown still drags", function() {
	expect( 2 );

	var element = $( "#draggable1" ).draggable({ scroll: false });

	// In IE8, when content is placed under the mouse (e.g. when draggable content is replaced
	// on mousedown), mousemove is triggered on those elements even though the mouse hasn't moved.
	// Support: IE <9
	element.bind( "mousedown", function() {
		$( document ).simulate( "mousemove", { button: -1 });
	});

	TestHelpers.draggable.shouldMove( element, "element is draggable" );
});

test( "#6258: not following mouse when scrolled and using overflow-y: scroll", function() {
	expect( 2 );

	var element = $( "#draggable1" ).draggable({
			stop: function( event, ui ) {
				equal( ui.position.left, 1, "left position is correct despite overflow on HTML" );
				equal( ui.position.top, 1, "top position is correct despite overflow on HTML" );
				$( "html" )
					.css( "overflow-y", oldOverflowY )
					.css( "overflow-x", oldOverflowX )
					.scrollTop( 0 )
					.scrollLeft( 0 );
			}
		}),
		oldOverflowY = $( "html" ).css( "overflow-y" ),
		oldOverflowX = $( "html" ).css( "overflow-x" );

		TestHelpers.forceScrollableWindow();

		$( "html" )
			.css( "overflow-y", "scroll" )
			.css( "overflow-x", "scroll" )
			.scrollTop( 300 )
			.scrollLeft( 300 );

		element.simulate( "drag", {
			dx: 1,
			dy: 1,
			moves: 1
		});
});

test( "#9315: jumps down with offset of scrollbar", function() {
	expect( 2 );

	var element = $( "#draggable2" ).draggable({
			stop: function( event, ui ) {
				equal( ui.position.left, 11, "left position is correct when position is absolute" );
				equal( ui.position.top, 11, "top position is correct when position is absolute" );
				$( "html" ).scrollTop( 0 ).scrollLeft( 0 );
			}
		});

		TestHelpers.forceScrollableWindow();

		$( "html" ).scrollTop( 300 ).scrollLeft( 300 );

		element.simulate( "drag", {
			dx: 1,
			dy: 1,
			moves: 1
		});
});

test( "#5009: scroll not working with parent's position fixed", function() {
	expect( 2 );

	var startValue = 300,
		element = $( "#draggable1" ).wrap( "
" ).draggable({
			drag: function() {
				startValue += 100;
				$( document ).scrollTop( startValue ).scrollLeft( startValue );
			},
			stop: function( event, ui ) {
				equal( ui.position.left, 10, "left position is correct when parent position is fixed" );
				equal( ui.position.top, 10, "top position is correct when parent position is fixed" );
				$( document ).scrollTop( 0 ).scrollLeft( 0 );
			}
		});

	TestHelpers.forceScrollableWindow();

	$( "#wrapper" ).css( "position", "fixed" );

	element.simulate( "drag", {
		dx: 10,
		dy: 10,
		moves: 3
	});
});

$( [ "hidden", "auto", "scroll" ] ).each(function() {
	var overflow = this;

	// http://bugs.jqueryui.com/ticket/9379 - position bug in scrollable div
	// http://bugs.jqueryui.com/ticket/10147 - Wrong position in a parent with "overflow: hidden"
	test( "position in scrollable parent with overflow: " + overflow, function() {
		expect( 2 );

		$( "#qunit-fixture" ).html( "

a

" );
		$( "#inner" ).css({ position: "absolute", width: "500px", height: "500px" });
		$( "#outer" ).css({ position: "absolute", width: "300px", height: "300px" });
		$( "#dragged" ).css({ width: "10px", height: "10px" });

		var moves = 3,
			startValue = 0,
			dragDelta = 20,
			delta = 100,

			// we scroll after each drag event, so subtract 1 from number of moves for expected
			expected = delta + ( ( moves - 1 ) * dragDelta ),
			element = $( "#dragged" ).draggable({
				drag: function() {
					startValue += dragDelta;
					$( "#outer" ).scrollTop( startValue ).scrollLeft( startValue );
				},
				stop: function( event, ui ) {
					equal( ui.position.left, expected, "left position is correct when grandparent is scrolled" );
					equal( ui.position.top, expected, "top position is correct when grandparent is scrolled" );
				}
			});

		$( "#outer" ).css( "overflow", overflow );

		element.simulate( "drag", {
			dy: delta,
			dx: delta,
			moves: moves
		});
	});
});

test( "#5727: draggable from iframe", function() {
	expect( 1 );

	var iframeBody, draggable1,
		iframe = $( "" ).appendTo( "#qunit-fixture" ),
		iframeDoc = ( iframe[ 0 ].contentWindow || iframe[ 0 ].contentDocument ).document;

	iframeDoc.write( "<!doctype html>" );
	iframeDoc.close();

	iframeBody = $( iframeDoc.body ).append( "
" );
	draggable1 = iframeBody.find( "div" );

	draggable1.draggable();

	equal( draggable1.closest( iframeBody ).length, 1 );

	// TODO: fix draggable within an IFRAME to fire events on the element properly
	// and these TestHelpers.draggable.shouldMove relies on events for testing
	//TestHelpers.draggable.shouldMove( draggable1, "draggable from an iframe" );
});

test( "#8399: A draggable should become the active element after you are finished interacting with it, but not before.", function() {
	expect( 2 );

	var element = $( "link" ).appendTo( "#qunit-fixture" ).draggable();

	$( document ).one( "mousemove", function() {
		notStrictEqual( document.activeElement, element.get( 0 ), "moving a draggable anchor did not make it the active element" );
	});

	TestHelpers.draggable.move( element, 50, 50 );

	strictEqual( document.activeElement, element.get( 0 ), "finishing moving a draggable anchor made it the active element" );
});

asyncTest( "blur behavior", function() {
	expect( 3 );

	var element = $( "#draggable1" ).draggable(),
		focusElement = $( "

" ).appendTo( element );

	TestHelpers.onFocus( focusElement, function() {
		strictEqual( document.activeElement, focusElement.get( 0 ), "test element is focused before mousing down on a draggable" );

		TestHelpers.draggable.move( focusElement, 1, 1 );

		// http://bugs.jqueryui.com/ticket/10527
		// Draggable: Can't select option in modal dialog (IE8)
		strictEqual( document.activeElement, focusElement.get( 0 ), "test element is focused after mousing down on itself" );

		TestHelpers.draggable.move( element, 50, 50 );

		// http://bugs.jqueryui.com/ticket/4261
		// active element should blur when mousing down on a draggable
		notStrictEqual( document.activeElement, focusElement.get( 0 ), "test element is no longer focused after mousing down on a draggable" );
		start();
	});
});

test( "ui-draggable-handle assigned to appropriate element", function() {
	expect( 5 );

	var p = $( "
" ).appendTo( "#qunit-fixture" ),
		element = $( "

" ).appendTo( "#qunit-fixture" ).draggable();
	ok( element.hasClass( "ui-draggable-handle" ), "handle is element by default" );

	element.draggable( "option", "handle", "p" );
	ok( !element.hasClass( "ui-draggable-handle" ), "removed from element" );
	ok( element.find( "p" ).hasClass( "ui-draggable-handle" ), "added to handle" );

	ok( !p.hasClass( "ui-draggable-handle" ),
		"ensure handle class name is constrained within the draggble (#10212)" );

	element.draggable( "destroy" );
	ok( !element.find( "p" ).hasClass( "ui-draggable-handle" ), "removed in destroy()" );
});

test( "ui-draggable-handle managed correctly in nested draggables", function() {
	expect( 4 );
	var parent = $( "

" ).draggable().appendTo( "#qunit-fixture" ),
		child = parent.find( "div" ).draggable();

	ok( parent.hasClass( "ui-draggable-handle" ), "parent has class name on init" );
	ok( child.hasClass( "ui-draggable-handle" ), "child has class name on init" );

	parent.draggable( "destroy" );
	ok( !parent.hasClass( "ui-draggable-handle" ), "parent loses class name on destroy" );
	ok( child.hasClass( "ui-draggable-handle" ), "child retains class name on destroy" );
});

// http://bugs.jqueryui.com/ticket/7772
// when css 'right' is set, element resizes on drag
test( "setting right/bottom css shouldn't cause resize", function() {
	expect( 3 );

	var finalOffset,
		element = $( "#draggable3" ),
		origWidth = element.width(),
		origHeight = element.height(),
		origOffset = element.offset();

	element.draggable();

	TestHelpers.draggable.move( element, -50, -50 );

	finalOffset = element.offset();
	finalOffset.left += 50;
	finalOffset.top += 50;

	closeEnough( element.width(), origWidth, 1, "element retains width" );
	closeEnough( element.height(), origHeight, 1, "element retains height" );
	deepEqual( finalOffset, origOffset, "element moves the correct distance" );
});

})( jQuery );
<|endoftext|>


<commit_before>const moment          = require('moment');
const BinaryPjax      = require('../../../../base/binary_pjax');
const Client          = require('../../../../base/client');
const Header          = require('../../../../base/header');
const BinarySocket    = require('../../../../base/socket');
const formatMoney     = require('../../../../common/currency').formatMoney;
const FormManager     = require('../../../../common/form_manager');
const CommonFunctions = require('../../../../../_common/common_functions');
const localize        = require('../../../../../_common/localize').localize;
const State           = require('../../../../../_common/storage').State;
require('select2');

const PersonalDetails = (() => {
    const form_id           = '#frmPersonalDetails';
    const real_acc_elements = '.RealAcc';

    let is_for_new_account = false;

    let editable_fields,
        is_jp,
        is_virtual,
        residence,
        get_settings_data,
        currency;

    const init = () => {
        editable_fields   = {};
        get_settings_data = {};
        is_virtual        = Client.get('is_virtual');
        residence         = Client.get('residence');
        is_jp             = residence === 'jp';
        if (is_jp && !is_virtual) {
            setVisibility('#fieldset_email_consent');
        }
        showHideTaxMessage();
    };

    const showHideTaxMessage = () => {
        if (Client.shouldCompleteTax()) {
            setVisibility('#tax_information_notice');
        } else {
            $('#tax_information_notice').setVisibility(0);
        }
    };

    const showHideLabel = (get_settings) => {
        if (!is_jp) {
            ['account_opening_reason', 'place_of_birth'].forEach((id) => {
                if (Object.prototype.hasOwnProperty.call(get_settings, id)) {
                    if (get_settings[id]) {
                        // we have to show text here instead of relying on displayGetSettingsData()
                        // since it prioritizes showing data instead of label
                        const $label = $(`#lbl_${id}`);
                        $label.text(get_settings[id]);
                        $(`#row_${id}`).setVisibility(0);
                        $(`#row_lbl_${id}`).setVisibility(1);
                    } else {
                        $(`#row_lbl_${id}`).setVisibility(0);
                        $(`#row_${id}`).setVisibility(1);
                    }
                }
            });
        }
    };

    const getDetailsResponse = (data, residence_list = State.getResponse('residence_list')) => {
        const get_settings         = $.extend({}, data);
        get_settings.date_of_birth = get_settings.date_of_birth ? moment.utc(new Date(get_settings.date_of_birth * 1000)).format('YYYY-MM-DD') : '';
        const accounts             = Client.getAllLoginids();
        // for subaccounts, back-end sends loginid of the master account as name
        const hide_name            = accounts.some(loginid => new RegExp(loginid, 'i').test(get_settings.first_name)) || is_virtual;
        if (!hide_name) {
            setVisibility('#row_name');
            get_settings.name = is_jp ? get_settings.last_name : `${(get_settings.salutation || '')} ${(get_settings.first_name || '')} ${(get_settings.last_name || '')}`;
        }

        if (get_settings.place_of_birth) {
            get_settings.place_of_birth =
                (residence_list.find(obj => obj.value === get_settings.place_of_birth) || {}).text ||
                get_settings.place_of_birth;
        }

        showHideLabel(get_settings);

        displayGetSettingsData(get_settings);

        if (is_virtual) {
            $(real_acc_elements).remove();
        } else if (is_jp) {
            const jp_settings = get_settings.jp_settings;
            switch (jp_settings.gender) {
                case 'f':
                    jp_settings.gender = localize('Female');
                    break;
                case 'm':
                    jp_settings.gender = localize('Male');
                    break;
                default:
                    break;
            }
            displayGetSettingsData(jp_settings);
            if (jp_settings.hedge_asset !== null && jp_settings.hedge_asset_amount !== null) {
                setVisibility('.hedge');
            }
            setVisibility('.JpAcc');
        } else {
            setVisibility(real_acc_elements);
        }
        setVisibility('#row_country');
        setVisibility('#row_email');
        $(form_id).setVisibility(1);
        $('#loading').remove();
        initFormManager();
        FormManager.handleSubmit({
            form_selector       : form_id,
            obj_request         : { set_settings: 1 },
            fnc_response_handler: setDetailsResponse,
            fnc_additional_check: additionalCheck,
            enable_button       : true,
        });
    };

    const displayGetSettingsData = (data, populate = true) => {
        let el_key,
            el_lbl_key,
            data_key;
        Object.keys(data).forEach((key) => {
            el_key     = document.getElementById(key);
            el_lbl_key = document.getElementById(`lbl_${key}`);
            // prioritise labels for japan account
            el_key = is_jp ? (el_lbl_key || el_key) : (el_key || el_lbl_key);
            if (el_key) {
                data_key             = /format_money/.test(el_key.className) && data[key] !== null ? formatMoney(currency, data[key]) : (data[key] || '');
                editable_fields[key] = data_key;
                if (populate) {
                    if (el_key.type === 'checkbox') {
                        el_key.checked = !!data_key;
                    } else if (/select|text/i.test(el_key.type)) {
                        $(el_key)
                            .val(data_key.split(','))
                            .trigger('change');
                    } else if (key !== 'country') {
                        CommonFunctions.elementInnerHtml(el_key, data_key ? localize(data_key) : '-');
                    }
                }
            }
        });
        if (data.country) {
            $('#residence').replaceWith($('<label/>').append($('<strong/>', { id: 'lbl_country' })));
            $('#lbl_country').text(data.country);
            if (is_virtual) $('#btn_update').setVisibility(0);
        }
    };

    const additionalCheck = (data) => {
        if (!isChanged(data) && (!data.jp_settings || !isChanged(data.jp_settings))) {
            showFormMessage('You did not change anything.', false);
            return false;
        }
        return true;
    };

    const isChanged = (data) => {
        const compare_data = $.extend({}, data);
        return Object.keys(compare_data).some(key => (
            key !== 'set_settings' && key !== 'jp_settings' && editable_fields[key] !== compare_data[key]
        ));
    };

    const getValidations = (data) => {
        let validations;
        if (is_jp) {
            validations = [
                { request_field: 'address_line_1',   value: data.address_line_1 },
                { request_field: 'address_line_2',   value: data.address_line_2 },
                { request_field: 'address_city',     value: data.address_city },
                { request_field: 'address_state',    value: data.address_state },
                { request_field: 'address_postcode', value: data.address_postcode },
                { request_field: 'phone',            value: data.phone },

                { selector: '#email_consent' },

                { selector: '#hedge_asset_amount',     validations: ['req', 'number'], parent_node: 'jp_settings' },
                { selector: '#hedge_asset',            validations: ['req'],           parent_node: 'jp_settings' },
                { selector: '#account_opening_reason', validations: ['req'] },

            ];
            $(form_id).find('select').each(function () {
                validations.push({ selector: `#${$(this).attr('id')}`, validations: ['req'], parent_node: 'jp_settings' });
            });
        } else if (is_virtual) {
            validations = [{ selector: '#residence', validations: ['req'] }];
        } else {
            validations = [
                { selector: '#address_line_1',         validations: ['req', 'address'] },
                { selector: '#address_line_2',         validations: ['address'] },
                { selector: '#address_city',           validations: ['req', 'letter_symbol'] },
                { selector: '#address_state',          validations: $('#address_state').prop('nodeName') === 'SELECT' ? '' : ['letter_symbol'] },
                { selector: '#address_postcode',       validations: [Client.get('residence') === 'gb' ? 'req' : '', 'postcode', ['length', { min: 0, max: 20 }]] },
                { selector: '#phone',                  validations: ['req', 'phone', ['length', { min: 6, max: 35, value: () => $('#phone').val().replace(/^\+/, '')  }]] },
                { selector: '#account_opening_reason', validations: ['req'] },

                { selector: '#place_of_birth', validations: Client.isAccountOfType('financial') ? ['req'] : '' },
                { selector: '#tax_residence',  validations: Client.isAccountOfType('financial') ? ['req'] : '' },
            ];
            const tax_id_validation = { selector: '#tax_identification_number', validations: ['tax_id', ['length', { min: 0, max: 20 }]] };
            if (Client.isAccountOfType('financial')) {
                tax_id_validation.validations[1][1].min = 1;
                tax_id_validation.validations.unshift('req');
            }
            validations.push(tax_id_validation);
        }
        return validations;
    };

    const setDetailsResponse = (response) => {
        // allow user to resubmit the form on error.
        const is_error = response.set_settings !== 1;
        if (!is_error) {
            // to update tax information message for financial clients
            BinarySocket.send({ get_account_status: 1 }, { forced: true }).then(() => {
                showHideTaxMessage();
                Header.displayAccountStatus();
            });
            // to update the State with latest get_settings data
            BinarySocket.send({ get_settings: 1 }, { forced: true }).then((data) => {
                getDetailsResponse(data.get_settings);
                // update notification shown for set residence etc
                Header.displayAccountStatus();
                if (is_for_new_account) {
                    is_for_new_account = false;
                    BinaryPjax.loadPreviousUrl();
                }
            });
        }
        showFormMessage(is_error ?
            'Sorry, an error occurred while processing your account.' :
            'Your settings have been updated successfully.', !is_error);
    };

    const showFormMessage = (msg, is_success) => {
        $('#formMessage')
            .attr('class', is_success ? 'success-msg' : 'errorfield')
            .html(is_success ? $('<ul/>', { class: 'checked' }).append($('<li/>', { text: localize(msg) })) : localize(msg))
            .css('display', 'block')
            .delay(5000)
            .fadeOut(1000);
    };

    const populateResidence = (response) => {
        const residence_list = response.residence_list;
        if (residence_list.length > 0) {
            const $options               = $('<div/>');
            const $options_with_disabled = $('<div/>');
            residence_list.forEach((res) => {
                $options.append(CommonFunctions.makeOption({ text: res.text, value: res.value }));
                $options_with_disabled.append(CommonFunctions.makeOption({
                    text       : res.text,
                    value      : res.value,
                    is_disabled: res.disabled,
                }));
            });

            if (residence) {
                const $tax_residence = $('#tax_residence');
                $tax_residence.html($options.html()).promise().done(() => {
                    setTimeout(() => {
                        $tax_residence.select2()
                            .val(get_settings_data.tax_residence ? get_settings_data.tax_residence.split(',') : '').trigger('change');
                        setVisibility('#tax_residence');
                    }, 500);
                });

                if (!get_settings_data.place_of_birth) {
                    $options.prepend($('<option/>', { value: '', text: localize('Please select') }));
                    $('#place_of_birth')
                        .html($options.html())
                        .val(residence);
                }
            } else {
                $('#lbl_country').parent().replaceWith($('<select/>', { id: 'residence' }));
                const $residence = $('#residence');
                $options_with_disabled.prepend($('<option/>', { text: localize('Please select a value'), value: '' }));
                $residence.html($options_with_disabled.html());
                initFormManager();
            }
        }
    };

    const populateStates = (response) => {
        const states = response.states_list;

        if (is_jp) {
            const state_text = (states.filter(state => state.value === get_settings_data.address_state)[0] || {}).text;
            $('#lbl_address_state').text(state_text || get_settings_data.address_state);
        } else {
            const address_state = '#address_state';
            let $field          = $(address_state);

            $field.empty();

            if (states && states.length > 0) {
                $field.append($('<option/>', { value: '', text: localize('Please select') }));
                states.forEach((state) => {
                    $field.append($('<option/>', { value: state.value, text: state.text }));
                });
            } else {
                $field.replaceWith($('<input/>', { id: address_state.replace('#', ''), name: 'address_state', type: 'text', maxlength: '35' }));
                $field = $(address_state);
            }
            $field.val(get_settings_data.address_state);
        }

        initFormManager();
        if (is_jp && !is_virtual) {
            // detect hedging needs to be called after FormManager.init
            // or all previously bound event listeners on form elements will be removed
            CommonFunctions.detectHedging($('#trading_purpose'), $('.hedge'));
        }
    };

    const initFormManager = () => { FormManager.init(form_id, getValidations(get_settings_data)); };

    const setVisibility = (el) => {
        if (is_for_new_account) {
            $(el).setVisibility(0);
        } else {
            $(el).setVisibility(1);
        }
    };

    const onLoad = () => {
        currency = Client.get('currency');
        BinarySocket.wait('get_account_status', 'get_settings').then(() => {
            init();
            get_settings_data = State.getResponse('get_settings');

            $('#account_opening_reason_notice').setVisibility(+is_for_new_account);

            if (is_virtual || is_jp) {
                getDetailsResponse(get_settings_data);
            }

            if (!is_virtual || !residence) {
                $('#btn_update').setVisibility(1);
                if (!is_jp) {
                    BinarySocket.send({ residence_list: 1 }).then(response => {
                        getDetailsResponse(get_settings_data, response.residence_list);
                        populateResidence(response);
                    });
                }
                if (residence) {
                    BinarySocket.send({ states_list: residence }).then(response => populateStates(response));
                }
            } else {
                $('#btn_update').setVisibility(0);
            }
        });
    };

    return {
        onLoad,

        setIsForNewAccount: (bool) => { is_for_new_account = bool; },
    };
})();

module.exports = PersonalDetails;
<commit_msg>disable restricted countries in select<commit_after>const moment          = require('moment');
const BinaryPjax      = require('../../../../base/binary_pjax');
const Client          = require('../../../../base/client');
const Header          = require('../../../../base/header');
const BinarySocket    = require('../../../../base/socket');
const formatMoney     = require('../../../../common/currency').formatMoney;
const FormManager     = require('../../../../common/form_manager');
const CommonFunctions = require('../../../../../_common/common_functions');
const localize        = require('../../../../../_common/localize').localize;
const State           = require('../../../../../_common/storage').State;
require('select2');

const PersonalDetails = (() => {
    const form_id           = '#frmPersonalDetails';
    const real_acc_elements = '.RealAcc';

    let is_for_new_account = false;

    let editable_fields,
        is_jp,
        is_virtual,
        residence,
        get_settings_data,
        currency;

    const init = () => {
        editable_fields   = {};
        get_settings_data = {};
        is_virtual        = Client.get('is_virtual');
        residence         = Client.get('residence');
        is_jp             = residence === 'jp';
        if (is_jp && !is_virtual) {
            setVisibility('#fieldset_email_consent');
        }
        showHideTaxMessage();
    };

    const showHideTaxMessage = () => {
        if (Client.shouldCompleteTax()) {
            setVisibility('#tax_information_notice');
        } else {
            $('#tax_information_notice').setVisibility(0);
        }
    };

    const showHideLabel = (get_settings) => {
        if (!is_jp) {
            ['account_opening_reason', 'place_of_birth'].forEach((id) => {
                if (Object.prototype.hasOwnProperty.call(get_settings, id)) {
                    if (get_settings[id]) {
                        // we have to show text here instead of relying on displayGetSettingsData()
                        // since it prioritizes showing data instead of label
                        const $label = $(`#lbl_${id}`);
                        $label.text(get_settings[id]);
                        $(`#row_${id}`).setVisibility(0);
                        $(`#row_lbl_${id}`).setVisibility(1);
                    } else {
                        $(`#row_lbl_${id}`).setVisibility(0);
                        $(`#row_${id}`).setVisibility(1);
                    }
                }
            });
        }
    };

    const getDetailsResponse = (data, residence_list = State.getResponse('residence_list')) => {
        const get_settings         = $.extend({}, data);
        get_settings.date_of_birth = get_settings.date_of_birth ? moment.utc(new Date(get_settings.date_of_birth * 1000)).format('YYYY-MM-DD') : '';
        const accounts             = Client.getAllLoginids();
        // for subaccounts, back-end sends loginid of the master account as name
        const hide_name            = accounts.some(loginid => new RegExp(loginid, 'i').test(get_settings.first_name)) || is_virtual;
        if (!hide_name) {
            setVisibility('#row_name');
            get_settings.name = is_jp ? get_settings.last_name : `${(get_settings.salutation || '')} ${(get_settings.first_name || '')} ${(get_settings.last_name || '')}`;
        }

        if (get_settings.place_of_birth) {
            get_settings.place_of_birth =
                (residence_list.find(obj => obj.value === get_settings.place_of_birth) || {}).text ||
                get_settings.place_of_birth;
        }

        showHideLabel(get_settings);

        displayGetSettingsData(get_settings);

        if (is_virtual) {
            $(real_acc_elements).remove();
        } else if (is_jp) {
            const jp_settings = get_settings.jp_settings;
            switch (jp_settings.gender) {
                case 'f':
                    jp_settings.gender = localize('Female');
                    break;
                case 'm':
                    jp_settings.gender = localize('Male');
                    break;
                default:
                    break;
            }
            displayGetSettingsData(jp_settings);
            if (jp_settings.hedge_asset !== null && jp_settings.hedge_asset_amount !== null) {
                setVisibility('.hedge');
            }
            setVisibility('.JpAcc');
        } else {
            setVisibility(real_acc_elements);
        }
        setVisibility('#row_country');
        setVisibility('#row_email');
        $(form_id).setVisibility(1);
        $('#loading').remove();
        initFormManager();
        FormManager.handleSubmit({
            form_selector       : form_id,
            obj_request         : { set_settings: 1 },
            fnc_response_handler: setDetailsResponse,
            fnc_additional_check: additionalCheck,
            enable_button       : true,
        });
    };

    const displayGetSettingsData = (data, populate = true) => {
        let el_key,
            el_lbl_key,
            data_key;
        Object.keys(data).forEach((key) => {
            el_key     = document.getElementById(key);
            el_lbl_key = document.getElementById(`lbl_${key}`);
            // prioritise labels for japan account
            el_key = is_jp ? (el_lbl_key || el_key) : (el_key || el_lbl_key);
            if (el_key) {
                data_key             = /format_money/.test(el_key.className) && data[key] !== null ? formatMoney(currency, data[key]) : (data[key] || '');
                editable_fields[key] = data_key;
                if (populate) {
                    if (el_key.type === 'checkbox') {
                        el_key.checked = !!data_key;
                    } else if (/select|text/i.test(el_key.type)) {
                        $(el_key)
                            .val(data_key.split(','))
                            .trigger('change');
                    } else if (key !== 'country') {
                        CommonFunctions.elementInnerHtml(el_key, data_key ? localize(data_key) : '-');
                    }
                }
            }
        });
        if (data.country) {
            $('#residence').replaceWith($('<label/>').append($('<strong/>', { id: 'lbl_country' })));
            $('#lbl_country').text(data.country);
            if (is_virtual) $('#btn_update').setVisibility(0);
        }
    };

    const additionalCheck = (data) => {
        if (!isChanged(data) && (!data.jp_settings || !isChanged(data.jp_settings))) {
            showFormMessage('You did not change anything.', false);
            return false;
        }
        return true;
    };

    const isChanged = (data) => {
        const compare_data = $.extend({}, data);
        return Object.keys(compare_data).some(key => (
            key !== 'set_settings' && key !== 'jp_settings' && editable_fields[key] !== compare_data[key]
        ));
    };

    const getValidations = (data) => {
        let validations;
        if (is_jp) {
            validations = [
                { request_field: 'address_line_1',   value: data.address_line_1 },
                { request_field: 'address_line_2',   value: data.address_line_2 },
                { request_field: 'address_city',     value: data.address_city },
                { request_field: 'address_state',    value: data.address_state },
                { request_field: 'address_postcode', value: data.address_postcode },
                { request_field: 'phone',            value: data.phone },

                { selector: '#email_consent' },

                { selector: '#hedge_asset_amount',     validations: ['req', 'number'], parent_node: 'jp_settings' },
                { selector: '#hedge_asset',            validations: ['req'],           parent_node: 'jp_settings' },
                { selector: '#account_opening_reason', validations: ['req'] },

            ];
            $(form_id).find('select').each(function () {
                validations.push({ selector: `#${$(this).attr('id')}`, validations: ['req'], parent_node: 'jp_settings' });
            });
        } else if (is_virtual) {
            validations = [{ selector: '#residence', validations: ['req'] }];
        } else {
            validations = [
                { selector: '#address_line_1',         validations: ['req', 'address'] },
                { selector: '#address_line_2',         validations: ['address'] },
                { selector: '#address_city',           validations: ['req', 'letter_symbol'] },
                { selector: '#address_state',          validations: $('#address_state').prop('nodeName') === 'SELECT' ? '' : ['letter_symbol'] },
                { selector: '#address_postcode',       validations: [Client.get('residence') === 'gb' ? 'req' : '', 'postcode', ['length', { min: 0, max: 20 }]] },
                { selector: '#phone',                  validations: ['req', 'phone', ['length', { min: 6, max: 35, value: () => $('#phone').val().replace(/^\+/, '')  }]] },
                { selector: '#account_opening_reason', validations: ['req'] },

                { selector: '#place_of_birth', validations: Client.isAccountOfType('financial') ? ['req'] : '' },
                { selector: '#tax_residence',  validations: Client.isAccountOfType('financial') ? ['req'] : '' },
            ];
            const tax_id_validation = { selector: '#tax_identification_number', validations: ['tax_id', ['length', { min: 0, max: 20 }]] };
            if (Client.isAccountOfType('financial')) {
                tax_id_validation.validations[1][1].min = 1;
                tax_id_validation.validations.unshift('req');
            }
            validations.push(tax_id_validation);
        }
        return validations;
    };

    const setDetailsResponse = (response) => {
        // allow user to resubmit the form on error.
        const is_error = response.set_settings !== 1;
        if (!is_error) {
            // to update tax information message for financial clients
            BinarySocket.send({ get_account_status: 1 }, { forced: true }).then(() => {
                showHideTaxMessage();
                Header.displayAccountStatus();
            });
            // to update the State with latest get_settings data
            BinarySocket.send({ get_settings: 1 }, { forced: true }).then((data) => {
                getDetailsResponse(data.get_settings);
                // update notification shown for set residence etc
                Header.displayAccountStatus();
                if (is_for_new_account) {
                    is_for_new_account = false;
                    BinaryPjax.loadPreviousUrl();
                }
            });
        }
        showFormMessage(is_error ?
            'Sorry, an error occurred while processing your account.' :
            'Your settings have been updated successfully.', !is_error);
    };

    const showFormMessage = (msg, is_success) => {
        $('#formMessage')
            .attr('class', is_success ? 'success-msg' : 'errorfield')
            .html(is_success ? $('<ul/>', { class: 'checked' }).append($('<li/>', { text: localize(msg) })) : localize(msg))
            .css('display', 'block')
            .delay(5000)
            .fadeOut(1000);
    };

    const populateResidence = (response) => {
        const residence_list = response.residence_list;
        if (residence_list.length > 0) {
            const $options               = $('<div/>');
            const $options_with_disabled = $('<div/>');
            residence_list.forEach((res) => {
                $options.append(CommonFunctions.makeOption({ text: res.text, value: res.value }));
                $options_with_disabled.append(CommonFunctions.makeOption({
                    text       : res.text,
                    value      : res.value,
                    is_disabled: res.disabled,
                }));
            });

            if (residence) {
                const $tax_residence = $('#tax_residence');
                $tax_residence.html($options_with_disabled.html()).promise().done(() => {
                    setTimeout(() => {
                        $tax_residence.select2()
                            .val(get_settings_data.tax_residence ? get_settings_data.tax_residence.split(',') : '').trigger('change');
                        setVisibility('#tax_residence');
                    }, 500);
                });

                if (!get_settings_data.place_of_birth) {
                    $options.prepend($('<option/>', { value: '', text: localize('Please select') }));
                    $('#place_of_birth')
                        .html($options.html())
                        .val(residence);
                }
            } else {
                $('#lbl_country').parent().replaceWith($('<select/>', { id: 'residence' }));
                const $residence = $('#residence');
                $options_with_disabled.prepend($('<option/>', { text: localize('Please select a value'), value: '' }));
                $residence.html($options_with_disabled.html());
                initFormManager();
            }
        }
    };

    const populateStates = (response) => {
        const states = response.states_list;

        if (is_jp) {
            const state_text = (states.filter(state => state.value === get_settings_data.address_state)[0] || {}).text;
            $('#lbl_address_state').text(state_text || get_settings_data.address_state);
        } else {
            const address_state = '#address_state';
            let $field          = $(address_state);

            $field.empty();

            if (states && states.length > 0) {
                $field.append($('<option/>', { value: '', text: localize('Please select') }));
                states.forEach((state) => {
                    $field.append($('<option/>', { value: state.value, text: state.text }));
                });
            } else {
                $field.replaceWith($('<input/>', { id: address_state.replace('#', ''), name: 'address_state', type: 'text', maxlength: '35' }));
                $field = $(address_state);
            }
            $field.val(get_settings_data.address_state);
        }

        initFormManager();
        if (is_jp && !is_virtual) {
            // detect hedging needs to be called after FormManager.init
            // or all previously bound event listeners on form elements will be removed
            CommonFunctions.detectHedging($('#trading_purpose'), $('.hedge'));
        }
    };

    const initFormManager = () => { FormManager.init(form_id, getValidations(get_settings_data)); };

    const setVisibility = (el) => {
        if (is_for_new_account) {
            $(el).setVisibility(0);
        } else {
            $(el).setVisibility(1);
        }
    };

    const onLoad = () => {
        currency = Client.get('currency');
        BinarySocket.wait('get_account_status', 'get_settings').then(() => {
            init();
            get_settings_data = State.getResponse('get_settings');

            $('#account_opening_reason_notice').setVisibility(+is_for_new_account);

            if (is_virtual || is_jp) {
                getDetailsResponse(get_settings_data);
            }

            if (!is_virtual || !residence) {
                $('#btn_update').setVisibility(1);
                if (!is_jp) {
                    BinarySocket.send({ residence_list: 1 }).then(response => {
                        getDetailsResponse(get_settings_data, response.residence_list);
                        populateResidence(response);
                    });
                }
                if (residence) {
                    BinarySocket.send({ states_list: residence }).then(response => populateStates(response));
                }
            } else {
                $('#btn_update').setVisibility(0);
            }
        });
    };

    return {
        onLoad,

        setIsForNewAccount: (bool) => { is_for_new_account = bool; },
    };
})();

module.exports = PersonalDetails;
<|endoftext|>


<commit_before>;(function ($) {
    $.fn.ticker = function (settings) {
        var me = this;

        var defaultSettings = {
            gapWidth: 20,
            speed: 5
        };

        settings = $.extend(defaultSettings, settings);

        me.each(function (i, outerElement) {
            var $outerElement = $(outerElement),
                totalWidth    = 0,
                $wrapper      = $('

'),
                childCount    = $outerElement.children().length;

            // initialise wrapping element
            $wrapper.insertBefore($outerElement);
            $outerElement.appendTo($wrapper);

            // initialise wrapper element styling
            $wrapper.css({
                width: $wrapper.innerWidth(),
                overflow: 'hidden'
            });

            // initialise inner element float styling
            $outerElement.children().css({
                float: 'left',
                marginRight: settings.gapWidth
            });

            // duplicate all child elements
            $outerElement.children().each(function (i, innerElement) {
                var $innerElement = $(innerElement);

                $innerElement.clone().appendTo($outerElement);
            });

            // calculate width of all child elements
            $outerElement.children().each(function (i, innerElement) {
                var $innerElement = $(innerElement);

                totalWidth += $innerElement.outerWidth() + settings.gapWidth;
            });

            // initialise outer element styling
            $outerElement.css({
                width: totalWidth
            });

            // detect CSS animation support
            function detectAnimation()
            {
                // taken from https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Using_CSS_animations/Detecting_CSS_animation_support
                var elm = $outerElement[0],
                    animation = false,
                    animationstring = 'animation',
                    keyframeprefix = '',
                    domPrefixes = ['Webkit', 'Moz', 'O', 'ms', 'Khtml'],
                    pfx  = '';

                if (elm.style.animationName) {
                    animation = true;
                }

                if (animation === false) {
                    for (var prefixIndex = 0; prefixIndex < domPrefixes.length; prefixIndex++) {
                        if (elm.style[domPrefixes[prefixIndex] + 'AnimationName' ] !== undefined) {
                            pfx = domPrefixes[prefixIndex];
                            animationstring = pfx + 'Animation';
                            keyframeprefix = '-' + pfx.toLowerCase() + '-';
                            animation = true;
                            break;
                        }
                    }
                }

                return animation;
            }

            if (!detectAnimation()) {
                function startAnimation()
                {
                    $outerElement.css({
                        marginLeft: 0
                    }).animate({
                        marginLeft: -(totalWidth / 2)
                    }, settings.speed * childCount * 1000, 'linear', function () {
                        startAnimation();
                    });
                }

                startAnimation();
            } else {
                $('').appendTo('head');
                $('').appendTo('head');
                $('').appendTo('head');
                $('').appendTo('head');
                $('').appendTo('head');

                $outerElement.css({
                    '-webkit-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite',
                    '-moz-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite',
                    '-o-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite',
                    '-msie-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite',
                    animation: 'tickerAnimation ' + (settings.speed * childCount) + 's infinite'
                });
            }
        });
    };
})(jQuery);
<commit_msg>CSS animation should have been linear<commit_after>;(function ($) {
    $.fn.ticker = function (settings) {
        var me = this;

        var defaultSettings = {
            gapWidth: 20,
            speed: 5
        };

        settings = $.extend(defaultSettings, settings);

        me.each(function (i, outerElement) {
            var $outerElement = $(outerElement),
                totalWidth    = 0,
                $wrapper      = $('

'),
                childCount    = $outerElement.children().length;

            // initialise wrapping element
            $wrapper.insertBefore($outerElement);
            $outerElement.appendTo($wrapper);

            // initialise wrapper element styling
            $wrapper.css({
                width: $wrapper.innerWidth(),
                overflow: 'hidden'
            });

            // initialise inner element float styling
            $outerElement.children().css({
                float: 'left',
                marginRight: settings.gapWidth
            });

            // duplicate all child elements
            $outerElement.children().each(function (i, innerElement) {
                var $innerElement = $(innerElement);

                $innerElement.clone().appendTo($outerElement);
            });

            // calculate width of all child elements
            $outerElement.children().each(function (i, innerElement) {
                var $innerElement = $(innerElement);

                totalWidth += $innerElement.outerWidth() + settings.gapWidth;
            });

            // initialise outer element styling
            $outerElement.css({
                width: totalWidth
            });

            // detect CSS animation support
            function detectAnimation()
            {
                // taken from https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Using_CSS_animations/Detecting_CSS_animation_support
                var elm = $outerElement[0],
                    animation = false,
                    animationstring = 'animation',
                    keyframeprefix = '',
                    domPrefixes = ['Webkit', 'Moz', 'O', 'ms', 'Khtml'],
                    pfx  = '';

                if (elm.style.animationName) {
                    animation = true;
                }

                if (animation === false) {
                    for (var prefixIndex = 0; prefixIndex < domPrefixes.length; prefixIndex++) {
                        if (elm.style[domPrefixes[prefixIndex] + 'AnimationName' ] !== undefined) {
                            pfx = domPrefixes[prefixIndex];
                            animationstring = pfx + 'Animation';
                            keyframeprefix = '-' + pfx.toLowerCase() + '-';
                            animation = true;
                            break;
                        }
                    }
                }

                return animation;
            }

            if (!detectAnimation()) {
                function startAnimation()
                {
                    $outerElement.css({
                        marginLeft: 0
                    }).animate({
                        marginLeft: -(totalWidth / 2)
                    }, settings.speed * childCount * 1000, 'linear', function () {
                        startAnimation();
                    });
                }

                startAnimation();
            } else {
                $('').appendTo('head');
                $('').appendTo('head');
                $('').appendTo('head');
                $('').appendTo('head');
                $('').appendTo('head');

                $outerElement.css({
                    '-webkit-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite linear',
                    '-moz-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite linear',
                    '-o-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite linear',
                    '-msie-animation': 'tickerAnimation ' + (settings.speed * childCount) + 's infinite linear',
                    animation: 'tickerAnimation ' + (settings.speed * childCount) + 's infinite linear'
                });
            }
        });
    };
})(jQuery);
<|endoftext|>


<commit_before>/**
 * Prototype Form component tests.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import PrototypeForm from './PrototypeForm';
import { render, createTestRegistry } from '../../../../../../tests/js/test-utils';
import { STORE_NAME } from '../../datastore/constants';
import { CORE_MODULES } from '../../../../googlesitekit/modules/datastore/constants';
import { CORE_SITE } from '../../../../googlesitekit/datastore/site/constants';
import { CORE_USER } from '../../../../googlesitekit/datastore/user/constants';

describe( 'PrototypeForm', () => {
	let registry;
	beforeEach( () => {
		registry = createTestRegistry();
		// Set settings to prevent fetch in resolver.
		registry.dispatch( STORE_NAME ).setSettings( {} );
		// Set user info.
		registry.dispatch( CORE_USER ).receiveUserInfo( { email: 'user@example.com' } );
		registry.dispatch( CORE_USER ).finishResolution( 'getUser', [] );
		// Prevent error when loading site info.
		registry.dispatch( CORE_SITE ).receiveSiteInfo( {} );
		// Receive empty modules to prevent unexpected fetch by resolver.
		registry.dispatch( CORE_MODULES ).receiveGetModules( [] );
	} );

	describe( '"Done" button', () => {
		it( 'hides when not passed a `doneCallback` prop', () => {
			const { queryByRole } = render( <PrototypeForm />, { registry } );
			const doneButton = queryByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeNull();
		} );

		it( 'shows when passed a `doneCallback` prop', () => {
			const doneCallbackMock = jest.fn();
			const { queryByRole } = render( <PrototypeForm doneCallback={ doneCallbackMock } />, { registry } );
			const doneButton = queryByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeInTheDocument();
		} );

		it( 'disables when required fields are missing', () => {
			const doneCallbackMock = jest.fn();
			const { queryByRole } = render( <PrototypeForm doneCallback={ doneCallbackMock } />, { registry } );
			const doneButton = queryByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeDisabled();
		} );

		it( 'enables when required fields are present', () => {
			registry.dispatch( STORE_NAME ).setSettings( {
				publicationID: 'hello',
				products: 'hello',
			} );
			const doneCallbackMock = jest.fn();
			const { queryByRole } = render( <PrototypeForm doneCallback={ doneCallbackMock } />, { registry } );
			const doneButton = queryByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeEnabled();
		} );
	} );
} );
<commit_msg>Simplify test.<commit_after>/**
 * Prototype Form component tests.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import PrototypeForm from './PrototypeForm';
import { render, createTestRegistry } from '../../../../../../tests/js/test-utils';
import { CORE_MODULES } from '../../../../googlesitekit/modules/datastore/constants';
import { STORE_NAME } from '../../datastore/constants';

describe( 'PrototypeForm', () => {
	let registry;
	beforeEach( () => {
		registry = createTestRegistry();
		// Prevent extra fetches during tests.
		registry.dispatch( CORE_MODULES ).receiveGetModules( [] );
		registry.dispatch( STORE_NAME ).setSettings( {} );
	} );

	describe( '"Done" button', () => {
		it( 'hides when not passed a `doneCallback` prop', () => {
			const { queryByRole } = render( <PrototypeForm />, { registry } );
			const doneButton = queryByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeNull();
		} );

		it( 'shows when passed a `doneCallback` prop', () => {
			const doneCallbackMock = jest.fn();
			const { getByRole } = render( <PrototypeForm doneCallback={ doneCallbackMock } />, { registry } );
			const doneButton = getByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeInTheDocument();
		} );

		it( 'disables when required fields are missing', () => {
			const doneCallbackMock = jest.fn();
			const { getByRole } = render( <PrototypeForm doneCallback={ doneCallbackMock } />, { registry } );
			const doneButton = getByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeDisabled();
		} );

		it( 'enables when required fields are present', () => {
			registry.dispatch( STORE_NAME ).setSettings( {
				publicationID: 'hello',
				products: 'hello',
			} );
			const doneCallbackMock = jest.fn();
			const { getByRole } = render( <PrototypeForm doneCallback={ doneCallbackMock } />, { registry } );
			const doneButton = getByRole( 'button', { name: /Done/i } );
			expect( doneButton ).toBeEnabled();
		} );
	} );
} );
<|endoftext|>