<commit_before>#!/usr/bin/env node

'use strict';

var fs = require('fs');
var glob = require('glob');
var nconf = require('nconf');
var path = require('path');
var program = require('commander');
var prompt = require('prompt');
var qs = require('querystring');
var request = require('request');
var unzip = require('unzip');
var url = require('url');

var config = 'chatbot.json';

var sep = function (str) {
    return str ? '/' + str : '';
}

var composeUri = function (mode, botname, kind, filename) {
    var uri = {
	protocol: nconf.get('protocol'),
	hostname: nconf.get('hostname')
    }
    var path = mode;
    path += sep(conf_app_id());
    path += sep(botname);
    path += sep(kind);
    path += sep(filename);
    uri.pathname = path;
    return uri;
}

var composeParams = function (params) {
    params.user_key = conf_user_key();
    return params;
}

var listUri = function () {
    var uri = composeUri('bot', '', '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var botUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var fileUri = function (filename) {
    var ext = path.extname(filename);
    var base = path.basename(filename, ext);
    var kind = ext.slice(1);
    var botname = conf_botname();
    var uri;
    if (kind === 'pdefaults' || kind === 'properties')
	uri = composeUri('bot', botname, kind, '');
    else if (kind === 'map' || kind === 'set' || kind === 'substitution')
	uri = composeUri('bot', botname, kind, base);
    else if (kind === 'aiml')
	uri = composeUri('bot', botname, 'file', base + ext);
    else {
	console.log('illegal file name: ' + filename);
	return;
    }
    uri.query = composeParams({});
    return url.format(uri);
}

var zipUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({return: 'zip'});
    return url.format(uri);
}

var verifyUri = function () {
    var uri = composeUri('bot', conf_botname(), 'verify', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var talkUri = function () {
    var uri = composeUri('talk', conf_botname(), '', '');
    return url.format(uri);
}

var okResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message)
	if (jObj.results !== undefined)
	    console.log(JSON.stringify(jObj.results[0], null, 2))
    }
}

var deleteResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message);
	var prop  = {
	    message: 'Re-enter the name of the bot to delete:',
	    name: 'botname',
	    required: true
	};
	prompt.get(prop, function (error, result) {
	    if (error) {
		console.log("aborted.");
		process.exit(2);
	    }
	    else {
		nconf.set('botname', result.botname);
		request.del(botUri(), deleteResp);
	    }
	});
    }
}

var listBotResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	jObj.forEach (function (entry) {
	    console.log(entry.botname);
	});
    }
    else
	console.log(jObj.message)
}

var fileList = function (jObj) {
    var files = [];
    if (jObj.properties.length) files.push(jObj.botname + ".properties");
    if (jObj.pdefaults.length) files.push(jObj.botname + ".pdefaults");
    jObj.files.forEach (function (entry) { files.push(entry.name); });
    jObj.sets.forEach (function (entry) { files.push(entry.name + ".set"); });
    jObj.maps.forEach (function (entry) { files.push(entry.name + ".map"); });
    jObj.substitutions.forEach (function (entry) { files.push(entry.name + ".substitution"); });
    return files;
}

var listFileResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200)
	fileList(jObj).forEach (function (file) { console.log(file); });
    else
	console.log(jObj.message)
}

var pullResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	fileList(jObj).forEach (function (file) {
	    request.get(fileUri(file)).pipe(fs.createWriteStream(file));
	});
    }
    else
	console.log(jObj.message)
}

var talkResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok') {
	nconf.set('sessionid', jObj.sessionid);
	if (nconf.get('extra') || nconf.get('trace'))
	    console.log(jObj);
	else {
	    jObj.responses.forEach (function (entry) {
		console.log(entry);
	    });
	}
    }
    else
	console.log(jObj.message)
}

var conf_app_id = function () {
    var app_id = nconf.get('app_id');
    if (app_id === undefined) {
	console.log('app_id required. use --app_id <app_id> or do init');
	process.exit(1);
    }
    return app_id;
}

var conf_user_key = function () {
    var user_key = nconf.get('user_key');
    if (user_key === undefined) {
	console.log('user_key required. use --user_key <user_key> or do init');
	process.exit(1);
    }
    return user_key;
}

var conf_botname = function () {
    var botname = nconf.get('botname');
    if (botname === undefined) {
	console.log('botname required. use --botname <botname> or do init');
	process.exit(1);
    }
    return botname;
}

prompt.message = "";
prompt.delimiter = "";

nconf.env();
nconf.file({file: config});
nconf.defaults({
    protocol: 'https',
    hostname: 'aiaas.pandorabots.com',
    app_id: undefined,
    user_key: undefined,
    botname: undefined,
    client_name: undefined,
    sessionid: undefined,
    all: undefined,
    extra: undefined,
    reset: undefined,
    trace: undefined,
    reload: undefined,
    recent: undefined
});

program
    .version('0.0.1')
    .usage('command [options] <file ...>')
    .option('-p, --protocol <protocol>', 'protocol')
    .option('-h, --hostname <hostname>', 'hostname')
    .option('-i, --app_id <app_id>', 'app_id')
    .option('-k, --user_key <user_key>', 'user_key')
    .option('-b, --botname <botname>', 'name of bot')
    .option('-c, --client_name <client_name>', 'name of client')
    .option('-s, --sessionid <sessionid>', 'session id of conversation')
    .option('-e, --extra', 'reset status of bot')
    .option('-t, --trace', 'adds trace data into response')
    .option('-r, --reload', 'force system to realod bot')
    .option('-R, --recent', 'use recent pod even if it is older than files')
    .option('-a, --all', 'downloads all files')
    .parse(process.argv);

if (program.protocol)
    nconf.set('protocol', program.protocol);
if (program.hostname)
    nconf.set('hostname', program.hostname);
if (program.app_id)
    nconf.set('app_id', program.app_id);
if (program.user_key)
    nconf.set('user_key', program.user_key);
if (program.botname)
    nconf.set('botname', program.botname);
if (program.client_name)
    nconf.set('client_name', program.client_name);
if (program.sessionid)
    nconf.set('sessionid', program.sessionid);
if (program.extra)
    nconf.set('extra', program.extra);
if (program.trace)
    nconf.set('trace', program.trace);
if (program.reload)
    nconf.set('reload', program.reload);
if (program.recent)
    nconf.set('recent', program.recent);
if (program.all)
    nconf.set('all', program.all);

// Initialize
if (program.args[0] === 'init') {
    console.log('Enter app_id (required), user_key (required), botname (recommended), hostname (optional).');
    var props = [
	{message: 'app_id?', name: 'app_id',
	    required: true, validator: /^[0-9]+$/,
	    warning: 'app_id must consist of numeric characters'},
	{message: 'user_key?', name: 'user_key',
	    required: true, validator: /^[0-9a-f]+$/,
	    warning: 'user_key must consist of numeric and/or a-f characters'},
	{message: 'botname?', name: 'botname'},
	{message: 'hostname?', name: 'hostname'}
    ];
    prompt.get(props, function (error, result) {
	if (error) {
	    console.log("aborted.");
	    process.exit(2);
	}
	else {
	    var prop = {};
	    for (var key in result) {
		if (result[key]) {
		    prop[key] = result[key];
		}
	    }
	    fs.writeFileSync(config, JSON.stringify(prop, null, 4));
	}
    });
}

// List names of bots
else if (program.args[0] === 'list') {
    request.get(listUri(), listBotResp);
}

// Create a bot
else if (program.args[0] === 'create') {
    request.put(botUri(), okResp);
}

// Delete a bot
else if (program.args[0] === 'delete') {
    request.del(botUri(), deleteResp);
}

// Upload a file
else if (program.args[0] === 'upload') {
    if (program.args[1]) {
	fs.createReadStream(program.args[1]).pipe(request.put(fileUri(program.args[1]), okResp))
    }
    else {
	console.log('usage: upload <filename>');
    }
}

// Upload all files at once
else if (program.args[0] === 'push') {
    var files = glob.sync('*.{pdefaults,properties,map,set,substitution,aiml}', {});
    if (files.length) {
	files.forEach (function (entry) {
	    console.log('uploading: ' + entry);
	    fs.createReadStream(entry).pipe(request.put(fileUri(entry), okResp))
	});
    }
    else {
	console.log('no bot files found');
    }
}

// Remove a file
else if (program.args[0] === 'remove') {
    if (program.args[1]) {
	request.del(fileUri(program.args[1]), okResp);
    }
    else {
	console.log('usage: remove <filename>');
    }
}

// Download a file
else if (program.args[0] === 'download') {
    if (program.args[1]) {
	request.get(fileUri(program.args[1])).pipe(fs.createWriteStream(program.args[1]));;
    }
    else {
	console.log('usage: download <filename>');
    }
}

// List files of bot
else if (program.args[0] === 'get' && !program.all) {
    request.get(botUri(), listFileResp);
}

// Retrieve all files as ZIP
else if (program.args[0] === 'get' && program.all) {
    request.get(zipUri()).pipe(fs.createWriteStream(conf_botname() + '.zip'));
}

// Retrieve all files at once
else if (program.args[0] === 'pull') {
    //request.get(zipUri()).pipe(unzip.Extract({path: conf_botname()}));
    //it should work however gets freeze after zip have been downloaded; ugly workaround below
    request.get(botUri(), pullResp);
}

// Compile a bot
else if (program.args[0] === 'compile') {
    request.get(verifyUri(), okResp);
}

// Talk to a bot
else if (program.args[0] === 'talk') {
    if (program.args[1]) {
	var param = {input: program.args.slice(1).join(' ')};
	if (nconf.get('client_name')) param.client_name = nconf.get('client_name');
	if (nconf.get('sessionid')) param.sessionid = nconf.get('sessionid');
	if (nconf.get('extra')) param.extra = true;
	if (nconf.get('reset')) param.reset = true;
	if (nconf.get('trace')) param.trace = true;
	if (nconf.get('reload')) param.reload = true;
	if (nconf.get('recent')) param.recent = true;
	request.post(talkUri(), talkResp).form(composeParams(param));
    }
    else {
	console.log('usage: talk <text...>');
    }
}

else if (program.args[0] === undefined)
    console.log('usage: <command> [--options...]');
else {
    console.log('invalid command: ' + program.args[0]);
    process.exit(1);
}

<commit_msg>changed init validation and help text<commit_after>#!/usr/bin/env node

'use strict';

var fs = require('fs');
var glob = require('glob');
var nconf = require('nconf');
var path = require('path');
var program = require('commander');
var prompt = require('prompt');
var qs = require('querystring');
var request = require('request');
var unzip = require('unzip');
var url = require('url');

var config = 'chatbot.json';

var sep = function (str) {
    return str ? '/' + str : '';
}

var composeUri = function (mode, botname, kind, filename) {
    var uri = {
	protocol: nconf.get('protocol'),
	hostname: nconf.get('hostname')
    }
    var path = mode;
    path += sep(conf_app_id());
    path += sep(botname);
    path += sep(kind);
    path += sep(filename);
    uri.pathname = path;
    return uri;
}

var composeParams = function (params) {
    params.user_key = conf_user_key();
    return params;
}

var listUri = function () {
    var uri = composeUri('bot', '', '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var botUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var fileUri = function (filename) {
    var ext = path.extname(filename);
    var base = path.basename(filename, ext);
    var kind = ext.slice(1);
    var botname = conf_botname();
    var uri;
    if (kind === 'pdefaults' || kind === 'properties')
	uri = composeUri('bot', botname, kind, '');
    else if (kind === 'map' || kind === 'set' || kind === 'substitution')
	uri = composeUri('bot', botname, kind, base);
    else if (kind === 'aiml')
	uri = composeUri('bot', botname, 'file', base + ext);
    else {
	console.log('illegal file name: ' + filename);
	return;
    }
    uri.query = composeParams({});
    return url.format(uri);
}

var zipUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({return: 'zip'});
    return url.format(uri);
}

var verifyUri = function () {
    var uri = composeUri('bot', conf_botname(), 'verify', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var talkUri = function () {
    var uri = composeUri('talk', conf_botname(), '', '');
    return url.format(uri);
}

var okResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message)
	if (jObj.results !== undefined)
	    console.log(JSON.stringify(jObj.results[0], null, 2))
    }
}

var deleteResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message);
	var prop  = {
	    message: 'Re-enter the name of the bot to delete:',
	    name: 'botname',
	    required: true
	};
	prompt.get(prop, function (error, result) {
	    if (error) {
		console.log("aborted.");
		process.exit(2);
	    }
	    else {
		nconf.set('botname', result.botname);
		request.del(botUri(), deleteResp);
	    }
	});
    }
}

var listBotResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	jObj.forEach (function (entry) {
	    console.log(entry.botname);
	});
    }
    else
	console.log(jObj.message)
}

var fileList = function (jObj) {
    var files = [];
    if (jObj.properties.length) files.push(jObj.botname + ".properties");
    if (jObj.pdefaults.length) files.push(jObj.botname + ".pdefaults");
    jObj.files.forEach (function (entry) { files.push(entry.name); });
    jObj.sets.forEach (function (entry) { files.push(entry.name + ".set"); });
    jObj.maps.forEach (function (entry) { files.push(entry.name + ".map"); });
    jObj.substitutions.forEach (function (entry) { files.push(entry.name + ".substitution"); });
    return files;
}

var listFileResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200)
	fileList(jObj).forEach (function (file) { console.log(file); });
    else
	console.log(jObj.message)
}

var pullResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	fileList(jObj).forEach (function (file) {
	    request.get(fileUri(file)).pipe(fs.createWriteStream(file));
	});
    }
    else
	console.log(jObj.message)
}

var talkResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok') {
	nconf.set('sessionid', jObj.sessionid);
	if (nconf.get('extra') || nconf.get('trace'))
	    console.log(jObj);
	else {
	    jObj.responses.forEach (function (entry) {
		console.log(entry);
	    });
	}
    }
    else
	console.log(jObj.message)
}

var conf_app_id = function () {
    var app_id = nconf.get('app_id');
    if (app_id === undefined) {
	console.log('app_id required. use --app_id <app_id> or do init');
	process.exit(1);
    }
    return app_id;
}

var conf_user_key = function () {
    var user_key = nconf.get('user_key');
    if (user_key === undefined) {
	console.log('user_key required. use --user_key <user_key> or do init');
	process.exit(1);
    }
    return user_key;
}

var conf_botname = function () {
    var botname = nconf.get('botname');
    if (botname === undefined) {
	console.log('botname required. use --botname <botname> or do init');
	process.exit(1);
    }
    return botname;
}

prompt.message = "";
prompt.delimiter = "";

nconf.env();
nconf.file({file: config});
nconf.defaults({
    protocol: 'https',
    hostname: 'aiaas.pandorabots.com',
    app_id: undefined,
    user_key: undefined,
    botname: undefined,
    client_name: undefined,
    sessionid: undefined,
    all: undefined,
    extra: undefined,
    reset: undefined,
    trace: undefined,
    reload: undefined,
    recent: undefined
});

program
    .version('0.0.1')
    .usage('command [options] <file ...>')
    .option('-p, --protocol <protocol>', 'protocol')
    .option('-h, --hostname <hostname>', 'hostname')
    .option('-i, --app_id <app_id>', 'app_id')
    .option('-k, --user_key <user_key>', 'user_key')
    .option('-b, --botname <botname>', 'name of bot')
    .option('-c, --client_name <client_name>', 'name of client')
    .option('-s, --sessionid <sessionid>', 'session id of conversation')
    .option('-e, --extra', 'reset status of bot')
    .option('-t, --trace', 'adds trace data into response')
    .option('-r, --reload', 'force system to realod bot')
    .option('-R, --recent', 'use recent pod even if it is older than files')
    .option('-a, --all', 'downloads all files')
    .parse(process.argv);

if (program.protocol)
    nconf.set('protocol', program.protocol);
if (program.hostname)
    nconf.set('hostname', program.hostname);
if (program.app_id)
    nconf.set('app_id', program.app_id);
if (program.user_key)
    nconf.set('user_key', program.user_key);
if (program.botname)
    nconf.set('botname', program.botname);
if (program.client_name)
    nconf.set('client_name', program.client_name);
if (program.sessionid)
    nconf.set('sessionid', program.sessionid);
if (program.extra)
    nconf.set('extra', program.extra);
if (program.trace)
    nconf.set('trace', program.trace);
if (program.reload)
    nconf.set('reload', program.reload);
if (program.recent)
    nconf.set('recent', program.recent);
if (program.all)
    nconf.set('all', program.all);

// Initialize
if (program.args[0] === 'init') {
    var props = [
	{message: 'app_id? (required)', name: 'app_id',
	    required: true, validator: /^[0-9a-z]+$/,
	    warning: 'app_id must consist of alphanumeric, lowercase characters'},
	{message: 'user_key? (required)', name: 'user_key',
	    required: true, validator: /^[0-9a-z]+$/,
	    warning: 'user_key must consist of alphanumeric, lowercase characters'},
	{message: 'botname? (recommended', name: 'botname'},
	{message: 'hostname? (optional)', name: 'hostname'}
    ];
    prompt.get(props, function (error, result) {
	if (error) {
	    console.log("aborted.");
	    process.exit(2);
	}
	else {
	    var prop = {};
	    for (var key in result) {
		if (result[key]) {
		    prop[key] = result[key];
		}
	    }
	    fs.writeFileSync(config, JSON.stringify(prop, null, 4));
	}
    });
}

// List names of bots
else if (program.args[0] === 'list') {
    request.get(listUri(), listBotResp);
}

// Create a bot
else if (program.args[0] === 'create') {
    request.put(botUri(), okResp);
}

// Delete a bot
else if (program.args[0] === 'delete') {
    request.del(botUri(), deleteResp);
}

// Upload a file
else if (program.args[0] === 'upload') {
    if (program.args[1]) {
	fs.createReadStream(program.args[1]).pipe(request.put(fileUri(program.args[1]), okResp))
    }
    else {
	console.log('usage: upload <filename>');
    }
}

// Upload all files at once
else if (program.args[0] === 'push') {
    var files = glob.sync('*.{pdefaults,properties,map,set,substitution,aiml}', {});
    if (files.length) {
	files.forEach (function (entry) {
	    console.log('uploading: ' + entry);
	    fs.createReadStream(entry).pipe(request.put(fileUri(entry), okResp))
	});
    }
    else {
	console.log('no bot files found');
    }
}

// Remove a file
else if (program.args[0] === 'remove') {
    if (program.args[1]) {
	request.del(fileUri(program.args[1]), okResp);
    }
    else {
	console.log('usage: remove <filename>');
    }
}

// Download a file
else if (program.args[0] === 'download') {
    if (program.args[1]) {
	request.get(fileUri(program.args[1])).pipe(fs.createWriteStream(program.args[1]));;
    }
    else {
	console.log('usage: download <filename>');
    }
}

// List files of bot
else if (program.args[0] === 'get' && !program.all) {
    request.get(botUri(), listFileResp);
}

// Retrieve all files as ZIP
else if (program.args[0] === 'get' && program.all) {
    request.get(zipUri()).pipe(fs.createWriteStream(conf_botname() + '.zip'));
}

// Retrieve all files at once
else if (program.args[0] === 'pull') {
    //request.get(zipUri()).pipe(unzip.Extract({path: conf_botname()}));
    //it should work however gets freeze after zip have been downloaded; ugly workaround below
    request.get(botUri(), pullResp);
}

// Compile a bot
else if (program.args[0] === 'compile') {
    request.get(verifyUri(), okResp);
}

// Talk to a bot
else if (program.args[0] === 'talk') {
    if (program.args[1]) {
	var param = {input: program.args.slice(1).join(' ')};
	if (nconf.get('client_name')) param.client_name = nconf.get('client_name');
	if (nconf.get('sessionid')) param.sessionid = nconf.get('sessionid');
	if (nconf.get('extra')) param.extra = true;
	if (nconf.get('reset')) param.reset = true;
	if (nconf.get('trace')) param.trace = true;
	if (nconf.get('reload')) param.reload = true;
	if (nconf.get('recent')) param.recent = true;
	request.post(talkUri(), talkResp).form(composeParams(param));
    }
    else {
	console.log('usage: talk <text...>');
    }
}

else if (program.args[0] === undefined)
    console.log('usage: <command> [--options...]');
else {
    console.log('invalid command: ' + program.args[0]);
    process.exit(1);
}

<|endoftext|>


<commit_before>#!/usr/bin/env node

'use strict';

var fs = require('fs');
var glob = require('glob');
var nconf = require('nconf');
var path = require('path');
var program = require('commander');
var prompt = require('prompt');
var qs = require('querystring');
var request = require('request');
var unzip = require('unzip');
var url = require('url');

var config = 'chatbot.json';

var retryOnFile = function (file, msg, cb) {
    fs.exists(file, function (doesExist) {
	if (doesExist)
	    cb(file);
	else {
	    console.log('file not found');
	    reenter(msg, cb);
	}
    });
}

var reenter = function (msg, cb) {
    var prop  = {
	message: msg,
	name: 'input',
	required: true
    };
    prompt.get(prop, function (error, result) {
	if (error) {
	    console.log("aborted.");
	    process.exit(2);
	}
	else
	    retryOnFile(result.input, msg, cb);
    });
}

var sep = function (str) {
    return str ? '/' + str : '';
}

var composeUri = function (mode, botname, kind, filename) {
    var uri = {
	protocol: nconf.get('protocol'),
	hostname: nconf.get('hostname')
    }
    var path = mode;
    path += sep(conf_app_id());
    path += sep(botname);
    path += sep(kind);
    path += sep(filename);
    uri.pathname = path;
    return uri;
}

var composeParams = function (params) {
    params.user_key = conf_user_key();
    return params;
}

var listUri = function () {
    var uri = composeUri('bot', '', '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var botUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var fileUri = function (filename) {
    var ext = path.extname(filename);
    var base = path.basename(filename, ext);
    var kind = ext.slice(1);
    var botname = conf_botname();
    var uri;
    if (kind === 'pdefaults' || kind === 'properties')
	uri = composeUri('bot', botname, kind, '');
    else if (kind === 'map' || kind === 'set' || kind === 'substitution')
	uri = composeUri('bot', botname, kind, base);
    else if (kind === 'aiml')
	uri = composeUri('bot', botname, 'file', base + ext);
    else {
	console.log('illegal file name: ' + filename);
	return;
    }
    uri.query = composeParams({});
    return url.format(uri);
}

var zipUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({return: 'zip'});
    return url.format(uri);
}

var verifyUri = function () {
    var uri = composeUri('bot', conf_botname(), 'verify', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var talkUri = function () {
    var uri = composeUri('talk', conf_botname(), '', '');
    return url.format(uri);
}

var okResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message)
	if (jObj.results !== undefined)
	    console.log(JSON.stringify(jObj.results[0], null, 2))
    }
}

var deleteResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message);
	var prop  = {
	    message: 'Re-enter the name of the bot to delete:',
	    name: 'botname',
	    required: true
	};
	prompt.get(prop, function (error, result) {
	    if (error) {
		console.log("aborted.");
		process.exit(2);
	    }
	    else {
		nconf.set('botname', result.botname);
		request.del(botUri(), deleteResp);
	    }
	});
    }
}

var removeResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log("file not found");
	var prop  = {
	    message: 'Re-enter the name of file to remove:',
	    name: 'filename',
	    required: true
	};
	prompt.get(prop, function (error, result) {
	    if (error) {
		console.log("aborted.");
		process.exit(2);
	    }
	    else {
		request.del(fileUri(result.filename), removeResp);
	    }
	});
    }
}

var listBotResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	jObj.forEach (function (entry) {
	    console.log(entry.botname);
	});
    }
    else
	console.log(jObj.message)
}

var fileList = function (jObj) {
    var files = [];
    if (jObj.properties.length) files.push(jObj.botname + ".properties");
    if (jObj.pdefaults.length) files.push(jObj.botname + ".pdefaults");
    jObj.files.forEach (function (entry) { files.push(entry.name); });
    jObj.sets.forEach (function (entry) { files.push(entry.name + ".set"); });
    jObj.maps.forEach (function (entry) { files.push(entry.name + ".map"); });
    jObj.substitutions.forEach (function (entry) { files.push(entry.name + ".substitution"); });
    return files;
}

var listFileResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200)
	fileList(jObj).forEach (function (file) { console.log(file); });
    else
	console.log(jObj.message)
}

var pullResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	fileList(jObj).forEach (function (file) {
	    request.get(fileUri(file)).pipe(fs.createWriteStream(file));
	});
    }
    else
	console.log(jObj.message)
}

var talkResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok') {
	nconf.set('sessionid', jObj.sessionid);
	if (nconf.get('extra') || nconf.get('trace'))
	    console.log(jObj);
	else {
	    jObj.responses.forEach (function (entry) {
		console.log(entry);
	    });
	}
    }
    else
	console.log(jObj.message)
}

var conf_app_id = function () {
    var app_id = nconf.get('app_id');
    if (app_id === undefined) {
	console.log('app_id required. use --app_id <app_id> or do init');
	process.exit(1);
    }
    return app_id;
}

var conf_user_key = function () {
    var user_key = nconf.get('user_key');
    if (user_key === undefined) {
	console.log('user_key required. use --user_key <user_key> or do init');
	process.exit(1);
    }
    return user_key;
}

var conf_botname = function () {
    var botname = nconf.get('botname');
    if (botname === undefined) {
	console.log('botname required. use --botname <botname> or do init');
	process.exit(1);
    }
    return botname;
}

var options = {
    protocol: 'https',
    hostname: 'aiaas.pandorabots.com',
    app_id: undefined,
    user_key: undefined,
    botname: undefined,
    client_name: undefined,
    sessionid: undefined,
    extra: undefined,
    reset: undefined,
    trace: undefined,
    reload: undefined,
    recent: undefined,
    all: undefined
}

prompt.message = "";
prompt.delimiter = "";

nconf.env();
nconf.file({file: config});
nconf.defaults(options);

program
    .version('0.0.1')
    .usage('command [options] <file ...>')
    .option('-p, --protocol <protocol>', 'protocol')
    .option('-h, --hostname <hostname>', 'hostname')
    .option('-i, --app_id <app_id>', 'app_id')
    .option('-k, --user_key <user_key>', 'user_key')
    .option('-b, --botname <botname>', 'name of bot')
    .option('-c, --client_name <client_name>', 'name of client')
    .option('-s, --sessionid <sessionid>', 'session id of conversation')
    .option('-e, --extra', 'provides additional information with bot response')
    .option('-M, --reset', 'reset the bot memory')
    .option('-t, --trace', 'adds trace data into response')
    .option('-r, --reload', 'force system to reload bot')
    .option('-R, --recent', 'use most recently compiled version of bot')
    .option('-a, --all', 'downloads all files')
    .parse(process.argv);

for (var key in options) {
    if (program[key])
	nconf.set(key, program[key]);
}

// Initialize
if (program.args[0] === 'init') {
    var props = [
	{message: 'app_id? (required)', name: 'app_id',
	    required: true, validator: /^[0-9a-z]+$/,
	    warning: 'app_id must consist of alphanumeric, lowercase characters'},
	{message: 'user_key? (required)', name: 'user_key',
	    required: true, validator: /^[0-9a-z]+$/,
	    warning: 'user_key must consist of alphanumeric, lowercase characters'},
	{message: 'botname? (recommended)', name: 'botname'},
	{message: 'hostname? (optional)', name: 'hostname'}
    ];
    prompt.get(props, function (error, result) {
	if (error) {
	    console.log("aborted.");
	    process.exit(2);
	}
	else {
	    var prop = {};
	    for (var key in result) {
		if (result[key]) {
		    prop[key] = result[key];
		}
	    }
	    fs.writeFileSync(config, JSON.stringify(prop, null, 4));
	}
    });
}

// List names of bots
else if (program.args[0] === 'list') {
    request.get(listUri(), listBotResp);
}

// Create a bot
else if (program.args[0] === 'create') {
    request.put(botUri(), okResp);
}

// Delete a bot
else if (program.args[0] === 'delete') {
    request.del(botUri(), deleteResp);
}

// Upload a file
else if (program.args[0] === 'upload') {
    if (program.args[1]) {
	retryOnFile(program.args[1], 'Re-enter the name of file to upload:', function (entry) {
	    fs.createReadStream(entry).pipe(request.put(fileUri(entry), okResp));
	});
    }
    else {
	console.log('usage: upload <filename>');
    }
}

// Upload all files at once
else if (program.args[0] === 'push') {
    var files = glob.sync('*.{pdefaults,properties,map,set,substitution,aiml}', {});
    if (files.length) {
	files.forEach (function (entry) {
	    console.log('uploading: ' + entry);
	    fs.createReadStream(entry).pipe(request.put(fileUri(entry), okResp))
	});
    }
    else {
	console.log('no bot files found');
    }
}

// Remove a file
else if (program.args[0] === 'remove') {
    if (program.args[1]) {
	request.del(fileUri(program.args[1]), removeResp);
    }
    else {
	console.log('usage: remove <filename>');
    }
}

// Download a file
else if (program.args[0] === 'download') {
    if (program.args[1]) {
	request.get(fileUri(program.args[1])).pipe(fs.createWriteStream(program.args[1]));;
    }
    else {
	console.log('usage: download <filename>');
    }
}

// List files of bot
else if (program.args[0] === 'get' && !program.all) {
    request.get(botUri(), listFileResp);
}

// Retrieve all files as ZIP
else if (program.args[0] === 'get' && program.all) {
    request.get(zipUri()).pipe(fs.createWriteStream(conf_botname() + '.zip'));
}

// Retrieve all files at once
else if (program.args[0] === 'pull') {
    //request.get(zipUri()).pipe(unzip.Extract({path: conf_botname()}));
    //it should work however gets freeze after zip have been downloaded; ugly workaround below
    request.get(botUri(), pullResp);
}

// Compile a bot
else if (program.args[0] === 'compile') {
    request.get(verifyUri(), okResp);
}

// Talk to a bot
else if (program.args[0] === 'talk') {
    if (program.args[1]) {
	var param = {input: program.args.slice(1).join(' ')};
	if (nconf.get('client_name')) param.client_name = nconf.get('client_name');
	if (nconf.get('sessionid')) param.sessionid = nconf.get('sessionid');
	if (nconf.get('extra')) param.extra = true;
	if (nconf.get('reset')) param.reset = true;
	if (nconf.get('trace')) param.trace = true;
	if (nconf.get('reload')) param.reload = true;
	if (nconf.get('recent')) param.recent = true;
	request.post(talkUri(), talkResp).form(composeParams(param));
    }
    else {
	console.log('usage: talk <text...>');
    }
}

else if (program.args[0] === undefined)
    console.log('usage: <command> [--options...]');
else {
    console.log('invalid command: ' + program.args[0]);
    process.exit(1);
}

<commit_msg>simplifies code in pb-cli<commit_after>#!/usr/bin/env node

'use strict';

var fs = require('fs');
var glob = require('glob');
var nconf = require('nconf');
var path = require('path');
var program = require('commander');
var prompt = require('prompt');
var qs = require('querystring');
var request = require('request');
var unzip = require('unzip');
var url = require('url');

var config = 'chatbot.json';

var sep = function (str) {
    return str ? '/' + str : '';
}

var composeUri = function (mode, botname, kind, filename) {
    var uri = {
	protocol: nconf.get('protocol'),
	hostname: nconf.get('hostname')
    }
    var path = mode;
    path += sep(conf_app_id());
    path += sep(botname);
    path += sep(kind);
    path += sep(filename);
    uri.pathname = path;
    return uri;
}

var composeParams = function (params) {
    params.user_key = conf_user_key();
    return params;
}

var listUri = function () {
    var uri = composeUri('bot', '', '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var botUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var fileUri = function (filename) {
    var ext = path.extname(filename);
    var base = path.basename(filename, ext);
    var kind = ext.slice(1);
    var botname = conf_botname();
    var uri;
    if (kind === 'pdefaults' || kind === 'properties')
	uri = composeUri('bot', botname, kind, '');
    else if (kind === 'map' || kind === 'set' || kind === 'substitution')
	uri = composeUri('bot', botname, kind, base);
    else if (kind === 'aiml')
	uri = composeUri('bot', botname, 'file', base + ext);
    else {
	console.log('illegal file name: ' + filename);
	return;
    }
    uri.query = composeParams({});
    return url.format(uri);
}

var zipUri = function () {
    var uri = composeUri('bot', conf_botname(), '', '');
    uri.query = composeParams({return: 'zip'});
    return url.format(uri);
}

var verifyUri = function () {
    var uri = composeUri('bot', conf_botname(), 'verify', '');
    uri.query = composeParams({});
    return url.format(uri);
}

var talkUri = function () {
    var uri = composeUri('talk', conf_botname(), '', '');
    return url.format(uri);
}

var okResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message)
	if (jObj.results !== undefined)
	    console.log(JSON.stringify(jObj.results[0], null, 2))
    }
}

var deleteResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log(jObj.message);
	var prop  = {
	    message: 'Re-enter the name of the bot to delete:',
	    name: 'botname',
	    required: true
	};
	prompt.get(prop, function (error, result) {
	    if (error) {
		console.log("aborted.");
		process.exit(2);
	    }
	    else {
		nconf.set('botname', result.botname);
		request.del(botUri(), deleteResp);
	    }
	});
    }
}

var retryOnFile = function (file, cb) {
    fs.exists(file, function (doesExist) {
	if (doesExist)
	    cb(file);
	else {
	    console.log('file not found');
	    var prop  = {
		message: 'Re-enter the name of file to upload:',
		name: 'input',
		required: true
	    };
	    prompt.get(prop, function (error, result) {
		if (error) {
		    console.log("aborted.");
		    process.exit(2);
		}
		else
		    retryOnFile(result.input, cb);
	    });
	}
    });
}

var removeResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok')
	console.log(jObj.status)
    else {
	console.log("file not found");
	var prop  = {
	    message: 'Re-enter the name of file to remove:',
	    name: 'filename',
	    required: true
	};
	prompt.get(prop, function (error, result) {
	    if (error) {
		console.log("aborted.");
		process.exit(2);
	    }
	    else {
		request.del(fileUri(result.filename), removeResp);
	    }
	});
    }
}

var listBotResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	jObj.forEach (function (entry) {
	    console.log(entry.botname);
	});
    }
    else
	console.log(jObj.message)
}

var fileList = function (jObj) {
    var files = [];
    if (jObj.properties.length) files.push(jObj.botname + ".properties");
    if (jObj.pdefaults.length) files.push(jObj.botname + ".pdefaults");
    jObj.files.forEach (function (entry) { files.push(entry.name); });
    jObj.sets.forEach (function (entry) { files.push(entry.name + ".set"); });
    jObj.maps.forEach (function (entry) { files.push(entry.name + ".map"); });
    jObj.substitutions.forEach (function (entry) { files.push(entry.name + ".substitution"); });
    return files;
}

var listFileResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200)
	fileList(jObj).forEach (function (file) { console.log(file); });
    else
	console.log(jObj.message)
}

var pullResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (response.statusCode === 200) {
	fileList(jObj).forEach (function (file) {
	    request.get(fileUri(file)).pipe(fs.createWriteStream(file));
	});
    }
    else
	console.log(jObj.message)
}

var talkResp = function (error, response, body) {
    var jObj = JSON.parse(body);
    if (jObj.status === 'ok') {
	nconf.set('sessionid', jObj.sessionid);
	if (nconf.get('extra') || nconf.get('trace'))
	    console.log(jObj);
	else {
	    jObj.responses.forEach (function (entry) {
		console.log(entry);
	    });
	}
    }
    else
	console.log(jObj.message)
}

var conf_app_id = function () {
    var app_id = nconf.get('app_id');
    if (app_id === undefined) {
	console.log('app_id required. use --app_id <app_id> or do init');
	process.exit(1);
    }
    return app_id;
}

var conf_user_key = function () {
    var user_key = nconf.get('user_key');
    if (user_key === undefined) {
	console.log('user_key required. use --user_key <user_key> or do init');
	process.exit(1);
    }
    return user_key;
}

var conf_botname = function () {
    var botname = nconf.get('botname');
    if (botname === undefined) {
	console.log('botname required. use --botname <botname> or do init');
	process.exit(1);
    }
    return botname;
}

var options = {
    protocol: 'https',
    hostname: 'aiaas.pandorabots.com',
    app_id: undefined,
    user_key: undefined,
    botname: undefined,
    client_name: undefined,
    sessionid: undefined,
    extra: undefined,
    reset: undefined,
    trace: undefined,
    reload: undefined,
    recent: undefined,
    all: undefined
}

prompt.message = "";
prompt.delimiter = "";

nconf.env();
nconf.file({file: config});
nconf.defaults(options);

program
    .version('0.0.1')
    .usage('command [options] <file ...>')
    .option('-p, --protocol <protocol>', 'protocol')
    .option('-h, --hostname <hostname>', 'hostname')
    .option('-i, --app_id <app_id>', 'app_id')
    .option('-k, --user_key <user_key>', 'user_key')
    .option('-b, --botname <botname>', 'name of bot')
    .option('-c, --client_name <client_name>', 'name of client')
    .option('-s, --sessionid <sessionid>', 'session id of conversation')
    .option('-e, --extra', 'provides additional information with bot response')
    .option('-M, --reset', 'reset the bot memory')
    .option('-t, --trace', 'adds trace data into response')
    .option('-r, --reload', 'force system to reload bot')
    .option('-R, --recent', 'use most recently compiled version of bot')
    .option('-a, --all', 'downloads all files')
    .parse(process.argv);

for (var key in options) {
    if (program[key])
	nconf.set(key, program[key]);
}

// Initialize
if (program.args[0] === 'init') {
    var props = [
	{message: 'app_id? (required)', name: 'app_id',
	    required: true, validator: /^[0-9a-z]+$/,
	    warning: 'app_id must consist of alphanumeric, lowercase characters'},
	{message: 'user_key? (required)', name: 'user_key',
	    required: true, validator: /^[0-9a-z]+$/,
	    warning: 'user_key must consist of alphanumeric, lowercase characters'},
	{message: 'botname? (recommended)', name: 'botname'},
	{message: 'hostname? (optional)', name: 'hostname'}
    ];
    prompt.get(props, function (error, result) {
	if (error) {
	    console.log("aborted.");
	    process.exit(2);
	}
	else {
	    var prop = {};
	    for (var key in result) {
		if (result[key]) {
		    prop[key] = result[key];
		}
	    }
	    fs.writeFileSync(config, JSON.stringify(prop, null, 4));
	}
    });
}

// List names of bots
else if (program.args[0] === 'list') {
    request.get(listUri(), listBotResp);
}

// Create a bot
else if (program.args[0] === 'create') {
    request.put(botUri(), okResp);
}

// Delete a bot
else if (program.args[0] === 'delete') {
    request.del(botUri(), deleteResp);
}

// Upload a file
else if (program.args[0] === 'upload') {
    if (program.args[1]) {
	retryOnFile(program.args[1], function (entry) {
	    fs.createReadStream(entry).pipe(request.put(fileUri(entry), okResp));
	});
    }
    else {
	console.log('usage: upload <filename>');
    }
}

// Upload all files at once
else if (program.args[0] === 'push') {
    var files = glob.sync('*.{pdefaults,properties,map,set,substitution,aiml}', {});
    if (files.length) {
	files.forEach (function (entry) {
	    console.log('uploading: ' + entry);
	    fs.createReadStream(entry).pipe(request.put(fileUri(entry), okResp))
	});
    }
    else {
	console.log('no bot files found');
    }
}

// Remove a file
else if (program.args[0] === 'remove') {
    if (program.args[1]) {
	request.del(fileUri(program.args[1]), removeResp);
    }
    else {
	console.log('usage: remove <filename>');
    }
}

// Download a file
else if (program.args[0] === 'download') {
    if (program.args[1]) {
	request.get(fileUri(program.args[1])).pipe(fs.createWriteStream(program.args[1]));;
    }
    else {
	console.log('usage: download <filename>');
    }
}

// List files of bot
else if (program.args[0] === 'get' && !program.all) {
    request.get(botUri(), listFileResp);
}

// Retrieve all files as ZIP
else if (program.args[0] === 'get' && program.all) {
    request.get(zipUri()).pipe(fs.createWriteStream(conf_botname() + '.zip'));
}

// Retrieve all files at once
else if (program.args[0] === 'pull') {
    //request.get(zipUri()).pipe(unzip.Extract({path: conf_botname()}));
    //it should work however gets freeze after zip have been downloaded; ugly workaround below
    request.get(botUri(), pullResp);
}

// Compile a bot
else if (program.args[0] === 'compile') {
    request.get(verifyUri(), okResp);
}

// Talk to a bot
else if (program.args[0] === 'talk') {
    if (program.args[1]) {
	var param = {input: program.args.slice(1).join(' ')};
	if (nconf.get('client_name')) param.client_name = nconf.get('client_name');
	if (nconf.get('sessionid')) param.sessionid = nconf.get('sessionid');
	if (nconf.get('extra')) param.extra = true;
	if (nconf.get('reset')) param.reset = true;
	if (nconf.get('trace')) param.trace = true;
	if (nconf.get('reload')) param.reload = true;
	if (nconf.get('recent')) param.recent = true;
	request.post(talkUri(), talkResp).form(composeParams(param));
    }
    else {
	console.log('usage: talk <text...>');
    }
}

else if (program.args[0] === undefined)
    console.log('usage: <command> [--options...]');
else {
    console.log('invalid command: ' + program.args[0]);
    process.exit(1);
}

<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { connect } from 'dva'
import CSSModules from 'react-css-modules'
import styles from './page.less'
import { Link } from 'react-router'
import classNames from 'classnames'
import { priceFormat } from 'libs/util'
import ReactSwipe from 'react-swipe';
import Swipe from "components/swipe/swipe";

import { Tabs } from 'ui'
import { ProductList } from 'ui'

class GatherGoods extends Component {
  //Tabs and List component config
  tabsConfig = {
    names : [
      {
        key:'云好货',
        action: 'getCloudList',
        temp: 'similar'                 //带找相似
      },
      {
        key:'热搜好货',
        action: 'getHotSearchList'
    },
      {
        key:'我的定制',
        action: 'getHotSearchList',
      }
    ], //required
    model : 'gatherGoods',
    statusKey : 'tabActive'             //the statusKey in the model, default 'tabActive'
  }

  constructor(props) {
    super(props)
    this.state = {
    }
    props.getBannerList(20)

  }
  getData = () => {
    let model = this.tabsConfig.model
    let { loading, page, isEnd, tabActive } = this.props
    let action = this.tabsConfig.names[tabActive].action

    if ((page !== 0 && loading === true) || (isEnd)) {
      return;
    }
    this.props.action({type:`${model}/${action}`,page:++page})
  }

  touchMove = (that, args) => {
    console.log(that, args);
    if (that.min - args[0] > -500) {
      this.getData();
    }
  }
  render() {
    let { loading, page, isEnd, tabActive } = this.props;
    let action = this.tabsConfig.names[tabActive]
    let i = 0, j = this.props.productList.length, totalPrice = 0, totalSb = 0;
    let props = {
      property: "translateY",
      styleName: "product-list",
      tag: "div",
      min: "auto",
      stopPro: false,
      vertical: true,
      touchMove: this.touchMove,
      intervals: 500
      //step:200
    }
    if (j === 0 && page === 0 && loading === false) {
      return (

)
    } else if (j === 0 && page === 0) {
      return (

)
    }

    return (
      <Swipe {...props}>

          {
            this.props.bannerList.length > 0 ?
              <ReactSwipe styleName="banner-container" swipeOptions={{ continuous: false }}>
                {
                  this.props.bannerList.map((item, index) =>

                  )
                }
              </ReactSwipe>
              : ''
          }
          {/*

*/}
          <Tabs tabsConfig={this.tabsConfig} />
          <ProductList listConfig={{
            temp: this.tabsConfig.names[this.props.tabActive]['temp'],
            isNoMore: isEnd
          }} listData={this.props.productList}/>

      </Swipe>

    )
  }

};

function mapStateToProps(state) {
  return state.gatherGoods;
}

function mapDispatchToProps(dispatch) {
  return {
    action(type) {
      dispatch(type);
    },
    getBannerList(id) {
      dispatch({ type: 'gatherGoods/getBannerList', id });
    }
  }
}

GatherGoods.PropTypes = {
  enterAnimation: {
    duration: 2000,
    animation: 'slideDown'
  },
  leaveAnimation: {
    duration: 2000,
    animation: 'slideUp'
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(CSSModules(GatherGoods, styles, {allowMultiple: true}));
<commit_msg>GatherGoods tabs update<commit_after>import React, { Component } from 'react'
import { connect } from 'dva'
import CSSModules from 'react-css-modules'
import styles from './page.less'
import { Link } from 'react-router'
import classNames from 'classnames'
import { priceFormat } from 'libs/util'
import ReactSwipe from 'react-swipe';
import Swipe from "components/swipe/swipe";

import { Tabs } from 'ui'
import { ProductList } from 'ui'

class GatherGoods extends Component {
  //Tabs and List component config
  tabsConfig = {
    names : [
      {
        key:'云好货',
        action: 'getCloudList',
        temp: 'similar'                 //带找相似
      },
      {
        key:'热搜好货',
        action: 'getHotSearchList'
      }

    ], //required
    model : 'gatherGoods',
    statusKey : 'tabActive'             //the statusKey in the model, default 'tabActive'
  }

  constructor(props) {
    super(props)
    this.state = {
    }
    props.getBannerList(20)

  }
  getData = () => {
    let model = this.tabsConfig.model
    let { loading, page, isEnd, tabActive } = this.props
    let action = this.tabsConfig.names[tabActive].action

    if ((page !== 0 && loading === true) || (isEnd)) {
      return;
    }
    this.props.action({type:`${model}/${action}`,page:++page})
  }

  touchMove = (that, args) => {
    console.log(that, args);
    if (that.min - args[0] > -500) {
      this.getData();
    }
  }
  render() {
    let { loading, page, isEnd, tabActive } = this.props;
    let action = this.tabsConfig.names[tabActive]
    let i = 0, j = this.props.productList.length, totalPrice = 0, totalSb = 0;
    let props = {
      property: "translateY",
      styleName: "product-list",
      tag: "div",
      min: "auto",
      stopPro: false,
      vertical: true,
      touchMove: this.touchMove,
      intervals: 500
      //step:200
    }
    if (j === 0 && page === 0 && loading === false) {
      return (

)
    } else if (j === 0 && page === 0) {
      return (

)
    }

    return (
      <Swipe {...props}>

          {
            this.props.bannerList.length > 0 ?
              <ReactSwipe styleName="banner-container" swipeOptions={{ continuous: false }}>
                {
                  this.props.bannerList.map((item, index) =>

                  )
                }
              </ReactSwipe>
              : ''
          }
          {/*

*/}
          <Tabs tabsConfig={this.tabsConfig} />
          <ProductList listConfig={{
            temp: this.tabsConfig.names[this.props.tabActive]['temp'],
            isNoMore: isEnd
          }} listData={this.props.productList}/>

      </Swipe>

    )
  }

};

function mapStateToProps(state) {
  return state.gatherGoods;
}

function mapDispatchToProps(dispatch) {
  return {
    action(type) {
      dispatch(type);
    },
    getBannerList(id) {
      dispatch({ type: 'gatherGoods/getBannerList', id });
    }
  }
}

GatherGoods.PropTypes = {
  enterAnimation: {
    duration: 2000,
    animation: 'slideDown'
  },
  leaveAnimation: {
    duration: 2000,
    animation: 'slideUp'
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(CSSModules(GatherGoods, styles, {allowMultiple: true}));
<|endoftext|>


<commit_before>/**
 * Multiselect search
 *
 * Copyright (c) 2011 Raul Raat
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
function multiselect_search(ob, settings) {
	if (!ob || !ob.nodeName || ob.nodeName.toString().toLowerCase() !== 'select' || !ob.multiple) {
		// do nothing if given element isn't multiple select
		return;
	}

	settings = settings || {};
	settings.match = settings.match || function (searchterm, item) {return !!item.match(new RegExp(searchterm, "ig"));};
	/**
	 * @see http://www.webmasterworld.com/javascript/3304115.htm
	 */
	function insertafter(newChild, refChild) {
		refChild.parentNode.insertBefore(newChild, refChild.nextSibling);
	}
	/**
	 * @see http://www.quirksmode.org/js/eventSimple.html
	 */
	function addEventSimple(obj, evt, fn) {
		if (obj.addEventListener) {
			obj.addEventListener(evt, fn, false);
		} else if (obj.attachEvent) {
			obj.attachEvent('on' + evt, fn);
		}
	}

	function get_list(ob) {
		var i,
			cnodes = ob.childNodes,
			list = [];
		function new_list_element(node) {
			var text = cnodes[i].innerHTML,
				new_node = document.createElement('option');

			new_node.innerHTML = text;
			new_node.selected = node.selected;

			return {
				text: text,
				o_node: node,
				n_node: new_node,
				visible: true
			};
		}
		for (i in cnodes) {
			// NOTE: For IE there is no hasOwnProperty method for childNodes
			if (!cnodes.hasOwnProperty || cnodes.hasOwnProperty(i)) {
				if (cnodes[i].nodeName && cnodes[i].nodeName.toString().toLowerCase() === 'option') {
					list[list.length] = new_list_element(cnodes[i]);
				}
			}
		}
		return list;
	}

	var list = get_list(ob),
		// container div for multiselect_search
		div = document.createElement('div'),
		// the input area to search select options
		searchbox = document.createElement('input'),
		// new select where we add/remove options form
		select = document.createElement('select'),
		last_search = '',
		ctrl_key_down = false,
		i;

	// mark ctrl key as either up or down
	// TODO: this is broken in IE atm (but workarounded in select-onchange event)
	addEventSimple(window, 'keydown', function (e) {
		if (e.ctrlKey) {
			ctrl_key_down = true;
		}
	});
	addEventSimple(window, 'keyup', function (e) {
		if (!e.ctrlKey) {
			ctrl_key_down = false;
		}
	});

	function clearSelect() {
		while (select.hasChildNodes()) {
			select.removeChild(select.firstChild);
		}
	}

	function search() {
		// NOTE: don't use "this" as for IE this is window for others input element
		var term = searchbox.value,
			i;
		if (term === last_search) {
			return;
		}
		last_search = term;
		clearSelect();
		for (i in list) {
			if (list.hasOwnProperty(i)) {
				if (settings.match(term, list[i].text)) {
					select.appendChild(list[i].n_node);
					list[i].visible = true;
				} else {
					list[i].visible = false;
				}
			}
		}
	}

	searchbox.type = 'text';
	if (settings.searchbox_class) {
		searchbox.className = settings.searchbox_class;
	}
	addEventSimple(searchbox, 'keyup', search);

	select.multiple = true;
	if (ob.className) {
		select.className = ob.className;
	}
	for (i in list) {
		if (list.hasOwnProperty(i)) {
			select.appendChild(list[i].n_node);
		}
	}

	addEventSimple(select, 'change', function (e) {
		// only IE has propery ctrlKey for change event
		var i;
		if (e.ctrlKey === true || e.ctrlKey === false) {
			ctrl_key_down = e.ctrlKey;
		}
		for (i in list) {
			if (list.hasOwnProperty(i)) {
				if (!ctrl_key_down && list[i].visible == false) {
					list[i].o_node.selected = list[i].n_node.selected = false;
				} else {
					list[i].o_node.selected = list[i].n_node.selected;
				}
			}
		}
	});

	if (settings.container_id) {
		div.id = settings.container_id;
	}
	if (settings.container_class) {
		div.className = settings.container_class;
	}
	div.appendChild(searchbox);
	div.appendChild(document.createElement('br'));
	div.appendChild(select);

	ob.style.display = 'none';
	insertafter(div, ob);
}<commit_msg>use keycode<commit_after>/**
 * Multiselect search
 *
 * Copyright (c) 2011 Raul Raat
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 */
function multiselect_search(ob, settings) {
	if (!ob || !ob.nodeName || ob.nodeName.toString().toLowerCase() !== 'select' || !ob.multiple) {
		// do nothing if given element isn't multiple select
		return;
	}

	settings = settings || {};
	settings.match = settings.match || function (searchterm, item) {return !!item.match(new RegExp(searchterm, "ig"));};
	/**
	 * @see http://www.webmasterworld.com/javascript/3304115.htm
	 */
	function insertafter(newChild, refChild) {
		refChild.parentNode.insertBefore(newChild, refChild.nextSibling);
	}
	/**
	 * @see http://www.quirksmode.org/js/eventSimple.html
	 */
	function addEventSimple(obj, evt, fn) {
		if (obj.addEventListener) {
			obj.addEventListener(evt, fn, false);
		} else if (obj.attachEvent) {
			obj.attachEvent('on' + evt, fn);
		}
	}

	function get_list(ob) {
		var i,
			cnodes = ob.childNodes,
			list = [];
		function new_list_element(node) {
			var text = cnodes[i].innerHTML,
				new_node = document.createElement('option');

			new_node.innerHTML = text;
			new_node.selected = node.selected;

			return {
				text: text,
				o_node: node,
				n_node: new_node,
				visible: true
			};
		}
		for (i in cnodes) {
			// NOTE: For IE there is no hasOwnProperty method for childNodes
			if (!cnodes.hasOwnProperty || cnodes.hasOwnProperty(i)) {
				if (cnodes[i].nodeName && cnodes[i].nodeName.toString().toLowerCase() === 'option') {
					list[list.length] = new_list_element(cnodes[i]);
				}
			}
		}
		return list;
	}

	var list = get_list(ob),
		// container div for multiselect_search
		div = document.createElement('div'),
		// the input area to search select options
		searchbox = document.createElement('input'),
		// new select where we add/remove options form
		select = document.createElement('select'),
		last_search = '',
		ctrl_key_down = false,
		i;

	// mark ctrl key as either up or down
	// TODO: this is broken in IE atm (but workarounded in select-onchange event)
	addEventSimple(window, 'keydown', function (e) {
		var k = e.keyCode || e.which;
		if (k === 17) {
			ctrl_key_down = true;
		}
	});
	addEventSimple(window, 'keyup', function (e) {
		var k = e.keyCode || e.which;
		if (k === 17) {
			ctrl_key_down = false;
		}
	});

	function clearSelect() {
		while (select.hasChildNodes()) {
			select.removeChild(select.firstChild);
		}
	}

	function search() {
		// NOTE: don't use "this" as for IE this is window for others input element
		var term = searchbox.value,
			i;
		if (term === last_search) {
			return;
		}
		last_search = term;
		clearSelect();
		for (i in list) {
			if (list.hasOwnProperty(i)) {
				if (settings.match(term, list[i].text)) {
					select.appendChild(list[i].n_node);
					list[i].visible = true;
				} else {
					list[i].visible = false;
				}
			}
		}
	}

	searchbox.type = 'text';
	if (settings.searchbox_class) {
		searchbox.className = settings.searchbox_class;
	}
	addEventSimple(searchbox, 'keyup', search);

	select.multiple = true;
	if (ob.className) {
		select.className = ob.className;
	}
	for (i in list) {
		if (list.hasOwnProperty(i)) {
			select.appendChild(list[i].n_node);
		}
	}

	addEventSimple(select, 'change', function (e) {
		// only IE has propery ctrlKey for change event
		var i;
		if (e.ctrlKey === true || e.ctrlKey === false) {
			ctrl_key_down = e.ctrlKey;
		}
		for (i in list) {
			if (list.hasOwnProperty(i)) {
				if (!ctrl_key_down && list[i].visible == false) {
					list[i].o_node.selected = list[i].n_node.selected = false;
				} else {
					list[i].o_node.selected = list[i].n_node.selected;
				}
			}
		}
	});

	if (settings.container_id) {
		div.id = settings.container_id;
	}
	if (settings.container_class) {
		div.className = settings.container_class;
	}
	div.appendChild(searchbox);
	div.appendChild(document.createElement('br'));
	div.appendChild(select);

	ob.style.display = 'none';
	insertafter(div, ob);
}<|endoftext|>


<commit_before>/// <reference path="../../../../../argos-sdk/libraries/ext/ext-core-debug.js"/>
/// <reference path="../../../../../argos-sdk/libraries/sdata/sdata-client-debug"/>
/// <reference path="../../../../../argos-sdk/libraries/Simplate.js"/>
/// <reference path="../../../../../argos-sdk/src/View.js"/>
/// <reference path="../../../../../argos-sdk/src/Edit.js"/>
/// <reference path="../../Format.js"/>

Ext.namespace("Mobile.SalesLogix.History");

(function() {
    Mobile.SalesLogix.History.EditBase = Ext.extend(Sage.Platform.Mobile.Edit, {
        //Localization
        activityCategoryTitleText: 'Activity Category',
        activityDescriptionTitleText: 'Activity Description',
        categoryText: 'category',
        completedText: 'completed',
        durationText: 'duration',
        fbarHomeTitleText: 'home',
        fbarScheduleTitleText: 'schedule',
        leaderText: 'leader',
        longNotesText: 'notes',
        longNotesTitleText: 'Notes',
        moreDetailsText: 'More Details',
        priorityText: 'priority',
        priorityTitleText: 'Priority',
        regardingText: 'regarding',
        scheduledText: 'scheduled',
        timelessText: 'timeless',
        titleText: 'History',
        typeText: 'type',

        //View Properties
        entityName: 'History',
        picklistsByType: {
            'atAppointment': {
                'Category': 'Meeting Category Codes',
                'Description': 'Meeting Regarding'
            },
            'atLiterature': {
                'Description': 'Lit Request Regarding'
            },
            'atPersonal': {
                'Category': 'Meeting Category Codes',
                'Description': 'Personal Activity Regarding'
            },
            'atPhoneCall': {
                'Category': 'Phone Call Category Codes',
                'Description': 'Phone Call Regarding'
            },
            'atToDo': {
                'Category': 'To Do Category Codes',
                'Description': 'To Do Regarding'
            },
            'atEMail': {
                'Category': 'E-mail Category Codes',
                'Description': 'E-Mail Regarding'
            }
        },
        durationValueText: {
            0: 'none',
            15: '15 minutes',
            30: '30 minutes',
            60: '1 hour',
            90: '1.5 hours',
            120: '2 hours'
        },
        resourceKind: 'history',

        init: function() {
            Mobile.SalesLogix.History.EditBase.superclass.init.apply(this, arguments);

            this.fields['Timeless'].on('change', this.onTimelessChange, this);
        },
        onTimelessChange: function(value, field) {
            var field = this.fields['Duration'];

            value === true ? field.disable() : field.enable();
        },
        formatPicklistForType: function(type, which) {
            return this.picklistsByType[type] && this.picklistsByType[type][which];
        },
        formatDurationText: function(val, key, text) {
            return this.durationValueText[key] || text;
        },
        createDurationData: function() {
            var list = [];

            for (var duration in this.durationValueText)
            {
                list.push({
                    '$key': duration,
                    '$descriptor': this.durationValueText[duration]
                });
            }

            return {'$resources': list};
        },
        createLayout: function() {
            return this.layout || (this.layout = [
                {
                    name: 'Type',
                    type: 'hidden'
                },
                {
                    dependsOn: 'Type',
                    label: this.regardingText,
                    name: 'Description',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Description'], true
                    ),
                    orderBy: 'text asc',
                    title: this.activityDescriptionTitleText,
                    type: 'picklist'
                },
                {
                    name: 'LongNotes',
                    label: this.longNotesText,
                    noteProperty: false,
                    title: this.longNotesTitleText,
                    type: 'note',
                    view: 'text_edit'
                },
                {
                    options: {
                        title: this.moreDetailsText,
                        collapsed: true
                    },
                    as: [{
                            label: this.priorityText,
                            name: 'Priority',
                            picklist: 'Priorities',
                            title: this.priorityTitleText,
                            type: 'picklist',
                            maxTextLength: 64,
                            validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                        },
                        {
                            dependsOn: 'Type',
                            label: this.categoryText,
                            name: 'Category',
                            picklist: this.formatPicklistForType.createDelegate(
                                this, ['Category'], true
                            ),
                            title: this.activityCategoryTitleText,
                            type: 'picklist',
                            maxTextLength: 64,
                            validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                        },
                        {
                            label: this.completedText,
                            name: 'CompletedDate',
                            type: 'date',
                            showTimePicker: true,
                            formatString: 'M/d/yyyy h:mm tt',
                            minValue: Date.parse("01 Jan 1900"),
                            validator: Mobile.SalesLogix.Validator.isDateInRange
                        },
                        {
                            label: this.scheduledText,
                            name: 'StartDate',
                            type: 'date',
                            showTimePicker: true,
                            formatString: 'M/d/yyyy h:mm tt',
                            minValue: Date.parse("01 Jan 1900"),
                            validator: Mobile.SalesLogix.Validator.isDateInRange
                        },
                        {
                            label: this.timelessText,
                            name: 'Timeless',
                            type: 'boolean'
                        },
                        {
                            label: this.durationText,
                            name: 'Duration',
                            type: 'select',
                            view: 'select_list',
                            textRenderer: this.formatDurationText.createDelegate(this),
                            requireSelection: true,
                            valueKeyProperty: false,
                            valueTextProperty: false,
                            data: this.createDurationData()
                        },
                        {
                            label: this.leaderText,
                            name: 'User',
                            emptyText: '',
                            applyTo: '.',
                            valueKeyProperty: 'UserId',
                            valueTextProperty: 'UserName',
                            type: 'lookup',
                            view: 'user_list'
                        }
                    ]
                }
           ]);
        }
    });
})();<commit_msg>Set minValue of date as "new Date(1900, 0, 1)" as 01 Jan 1900 is not a valid date string for all locales.<commit_after>/// <reference path="../../../../../argos-sdk/libraries/ext/ext-core-debug.js"/>
/// <reference path="../../../../../argos-sdk/libraries/sdata/sdata-client-debug"/>
/// <reference path="../../../../../argos-sdk/libraries/Simplate.js"/>
/// <reference path="../../../../../argos-sdk/src/View.js"/>
/// <reference path="../../../../../argos-sdk/src/Edit.js"/>
/// <reference path="../../Format.js"/>

Ext.namespace("Mobile.SalesLogix.History");

(function() {
    Mobile.SalesLogix.History.EditBase = Ext.extend(Sage.Platform.Mobile.Edit, {
        //Localization
        activityCategoryTitleText: 'Activity Category',
        activityDescriptionTitleText: 'Activity Description',
        categoryText: 'category',
        completedText: 'completed',
        durationText: 'duration',
        fbarHomeTitleText: 'home',
        fbarScheduleTitleText: 'schedule',
        leaderText: 'leader',
        longNotesText: 'notes',
        longNotesTitleText: 'Notes',
        moreDetailsText: 'More Details',
        priorityText: 'priority',
        priorityTitleText: 'Priority',
        regardingText: 'regarding',
        scheduledText: 'scheduled',
        timelessText: 'timeless',
        titleText: 'History',
        typeText: 'type',

        //View Properties
        entityName: 'History',
        picklistsByType: {
            'atAppointment': {
                'Category': 'Meeting Category Codes',
                'Description': 'Meeting Regarding'
            },
            'atLiterature': {
                'Description': 'Lit Request Regarding'
            },
            'atPersonal': {
                'Category': 'Meeting Category Codes',
                'Description': 'Personal Activity Regarding'
            },
            'atPhoneCall': {
                'Category': 'Phone Call Category Codes',
                'Description': 'Phone Call Regarding'
            },
            'atToDo': {
                'Category': 'To Do Category Codes',
                'Description': 'To Do Regarding'
            },
            'atEMail': {
                'Category': 'E-mail Category Codes',
                'Description': 'E-Mail Regarding'
            }
        },
        durationValueText: {
            0: 'none',
            15: '15 minutes',
            30: '30 minutes',
            60: '1 hour',
            90: '1.5 hours',
            120: '2 hours'
        },
        resourceKind: 'history',

        init: function() {
            Mobile.SalesLogix.History.EditBase.superclass.init.apply(this, arguments);

            this.fields['Timeless'].on('change', this.onTimelessChange, this);
        },
        onTimelessChange: function(value, field) {
            var field = this.fields['Duration'];

            value === true ? field.disable() : field.enable();
        },
        formatPicklistForType: function(type, which) {
            return this.picklistsByType[type] && this.picklistsByType[type][which];
        },
        formatDurationText: function(val, key, text) {
            return this.durationValueText[key] || text;
        },
        createDurationData: function() {
            var list = [];

            for (var duration in this.durationValueText)
            {
                list.push({
                    '$key': duration,
                    '$descriptor': this.durationValueText[duration]
                });
            }

            return {'$resources': list};
        },
        createLayout: function() {
            return this.layout || (this.layout = [
                {
                    name: 'Type',
                    type: 'hidden'
                },
                {
                    dependsOn: 'Type',
                    label: this.regardingText,
                    name: 'Description',
                    picklist: this.formatPicklistForType.createDelegate(
                        this, ['Description'], true
                    ),
                    orderBy: 'text asc',
                    title: this.activityDescriptionTitleText,
                    type: 'picklist'
                },
                {
                    name: 'LongNotes',
                    label: this.longNotesText,
                    noteProperty: false,
                    title: this.longNotesTitleText,
                    type: 'note',
                    view: 'text_edit'
                },
                {
                    options: {
                        title: this.moreDetailsText,
                        collapsed: true
                    },
                    as: [{
                            label: this.priorityText,
                            name: 'Priority',
                            picklist: 'Priorities',
                            title: this.priorityTitleText,
                            type: 'picklist',
                            maxTextLength: 64,
                            validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                        },
                        {
                            dependsOn: 'Type',
                            label: this.categoryText,
                            name: 'Category',
                            picklist: this.formatPicklistForType.createDelegate(
                                this, ['Category'], true
                            ),
                            title: this.activityCategoryTitleText,
                            type: 'picklist',
                            maxTextLength: 64,
                            validator: Mobile.SalesLogix.Validator.exceedsMaxTextLength
                        },
                        {
                            label: this.completedText,
                            name: 'CompletedDate',
                            type: 'date',
                            showTimePicker: true,
                            formatString: 'M/d/yyyy h:mm tt',
                            minValue: (new Date(1900, 0, 1)),
                            validator: Mobile.SalesLogix.Validator.isDateInRange
                        },
                        {
                            label: this.scheduledText,
                            name: 'StartDate',
                            type: 'date',
                            showTimePicker: true,
                            formatString: 'M/d/yyyy h:mm tt',
                            minValue: (new Date(1900, 0, 1)),
                            validator: Mobile.SalesLogix.Validator.isDateInRange
                        },
                        {
                            label: this.timelessText,
                            name: 'Timeless',
                            type: 'boolean'
                        },
                        {
                            label: this.durationText,
                            name: 'Duration',
                            type: 'select',
                            view: 'select_list',
                            textRenderer: this.formatDurationText.createDelegate(this),
                            requireSelection: true,
                            valueKeyProperty: false,
                            valueTextProperty: false,
                            data: this.createDurationData()
                        },
                        {
                            label: this.leaderText,
                            name: 'User',
                            emptyText: '',
                            applyTo: '.',
                            valueKeyProperty: 'UserId',
                            valueTextProperty: 'UserName',
                            type: 'lookup',
                            view: 'user_list'
                        }
                    ]
                }
           ]);
        }
    });
})();
<|endoftext|>


<commit_before>/**
 * ComboBoxWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
 * can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which
 * a value can be chosen instead). Users can choose options from the combo box in one of two ways:
 *
 * - by typing a value in the text input field. If the value exactly matches the value of a menu
 *   option, that option will appear to be selected.
 * - by choosing a value from the menu. The value of the chosen option will then appear in the text
 *   input field.
 *
 * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
 *
 *     @example
 *     // Example: A ComboBoxWidget.
 *     var comboBox = new OO.ui.ComboBoxWidget( {
 *         label: 'ComboBoxWidget',
 *         input: { value: 'Option One' },
 *         menu: {
 *             items: [
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 1',
 *                     label: 'Option One'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 2',
 *                     label: 'Option Two'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 3',
 *                     label: 'Option Three'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 4',
 *                     label: 'Option Four'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 5',
 *                     label: 'Option Five'
 *                 } )
 *             ]
 *         }
 *     } );
 *     $( 'body' ).append( comboBox.$element );
 *
 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
 *
 * @class
 * @extends OO.ui.Widget
 * @mixins OO.ui.mixin.TabIndexedElement
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}.
 * @cfg {Object} [input] Configuration options to pass to the {@link OO.ui.TextInputWidget text input widget}.
 * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
 *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
 *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
 */
OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) {
	// Configuration initialization
	config = config || {};

	// Parent constructor
	OO.ui.ComboBoxWidget.super.call( this, config );

	// Properties (must be set before TabIndexedElement constructor call)
	this.$indicator = this.$( '<span>' );

	// Mixin constructors
	OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$indicator } ) );

	// Properties
	this.$overlay = config.$overlay || this.$element;
	this.input = new OO.ui.TextInputWidget( $.extend(
		{
			indicator: 'down',
			$indicator: this.$indicator,
			disabled: this.isDisabled()
		},
		config.input
	) );
	this.input.$input.eq( 0 ).attr( {
		role: 'combobox',
		'aria-autocomplete': 'list'
	} );
	this.menu = new OO.ui.TextInputMenuSelectWidget( this.input, $.extend(
		{
			widget: this,
			input: this.input,
			disabled: this.isDisabled()
		},
		config.menu
	) );

	// Events
	this.$indicator.on( {
		click: this.onClick.bind( this ),
		keypress: this.onKeyPress.bind( this )
	} );
	this.input.connect( this, {
		change: 'onInputChange',
		enter: 'onInputEnter'
	} );
	this.menu.connect( this, {
		choose: 'onMenuChoose',
		add: 'onMenuItemsChange',
		remove: 'onMenuItemsChange'
	} );

	// Initialization
	this.$element.addClass( 'oo-ui-comboBoxWidget' ).append( this.input.$element );
	this.$overlay.append( this.menu.$element );
	this.onMenuItemsChange();
};

/* Setup */

OO.inheritClass( OO.ui.ComboBoxWidget, OO.ui.Widget );
OO.mixinClass( OO.ui.ComboBoxWidget, OO.ui.mixin.TabIndexedElement );

/* Methods */

/**
 * Get the combobox's menu.
 * @return {OO.ui.TextInputMenuSelectWidget} Menu widget
 */
OO.ui.ComboBoxWidget.prototype.getMenu = function () {
	return this.menu;
};

/**
 * Handle input change events.
 *
 * @private
 * @param {string} value New value
 */
OO.ui.ComboBoxWidget.prototype.onInputChange = function ( value ) {
	var match = this.menu.getItemFromData( value );

	this.menu.selectItem( match );
	if ( this.menu.getHighlightedItem() ) {
		this.menu.highlightItem( match );
	}

	if ( !this.isDisabled() ) {
		this.menu.toggle( true );
	}
};

/**
 * Handle mouse click events.
 *
 *
 * @private
 * @param {jQuery.Event} e Mouse click event
 */
OO.ui.ComboBoxWidget.prototype.onClick = function ( e ) {
	if ( !this.isDisabled() && e.which === 1 ) {
		this.menu.toggle();
		this.input.$input[ 0 ].focus();
	}
	return false;
};

/**
 * Handle key press events.
 *
 *
 * @private
 * @param {jQuery.Event} e Key press event
 */
OO.ui.ComboBoxWidget.prototype.onKeyPress = function ( e ) {
	if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
		this.menu.toggle();
		this.input.$input[ 0 ].focus();
		return false;
	}
};

/**
 * Handle input enter events.
 *
 * @private
 */
OO.ui.ComboBoxWidget.prototype.onInputEnter = function () {
	if ( !this.isDisabled() ) {
		this.menu.toggle( false );
	}
};

/**
 * Handle menu choose events.
 *
 * @private
 * @param {OO.ui.OptionWidget} item Chosen item
 */
OO.ui.ComboBoxWidget.prototype.onMenuChoose = function ( item ) {
	this.input.setValue( item.getData() );
};

/**
 * Handle menu item change events.
 *
 * @private
 */
OO.ui.ComboBoxWidget.prototype.onMenuItemsChange = function () {
	var match = this.menu.getItemFromData( this.input.getValue() );
	this.menu.selectItem( match );
	if ( this.menu.getHighlightedItem() ) {
		this.menu.highlightItem( match );
	}
	this.$element.toggleClass( 'oo-ui-comboBoxWidget-empty', this.menu.isEmpty() );
};

/**
 * @inheritdoc
 */
OO.ui.ComboBoxWidget.prototype.setDisabled = function ( disabled ) {
	// Parent method
	OO.ui.ComboBoxWidget.super.prototype.setDisabled.call( this, disabled );

	if ( this.input ) {
		this.input.setDisabled( this.isDisabled() );
	}
	if ( this.menu ) {
		this.menu.setDisabled( this.isDisabled() );
	}

	return this;
};
<commit_msg>Add a getter method for ComboBoxWidget text inputs<commit_after>/**
 * ComboBoxWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
 * can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which
 * a value can be chosen instead). Users can choose options from the combo box in one of two ways:
 *
 * - by typing a value in the text input field. If the value exactly matches the value of a menu
 *   option, that option will appear to be selected.
 * - by choosing a value from the menu. The value of the chosen option will then appear in the text
 *   input field.
 *
 * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
 *
 *     @example
 *     // Example: A ComboBoxWidget.
 *     var comboBox = new OO.ui.ComboBoxWidget( {
 *         label: 'ComboBoxWidget',
 *         input: { value: 'Option One' },
 *         menu: {
 *             items: [
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 1',
 *                     label: 'Option One'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 2',
 *                     label: 'Option Two'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 3',
 *                     label: 'Option Three'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 4',
 *                     label: 'Option Four'
 *                 } ),
 *                 new OO.ui.MenuOptionWidget( {
 *                     data: 'Option 5',
 *                     label: 'Option Five'
 *                 } )
 *             ]
 *         }
 *     } );
 *     $( 'body' ).append( comboBox.$element );
 *
 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
 *
 * @class
 * @extends OO.ui.Widget
 * @mixins OO.ui.mixin.TabIndexedElement
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}.
 * @cfg {Object} [input] Configuration options to pass to the {@link OO.ui.TextInputWidget text input widget}.
 * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
 *  the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
 *  containing `<div>` and has a larger area. By default, the menu uses relative positioning.
 */
OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) {
	// Configuration initialization
	config = config || {};

	// Parent constructor
	OO.ui.ComboBoxWidget.super.call( this, config );

	// Properties (must be set before TabIndexedElement constructor call)
	this.$indicator = this.$( '<span>' );

	// Mixin constructors
	OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$indicator } ) );

	// Properties
	this.$overlay = config.$overlay || this.$element;
	this.input = new OO.ui.TextInputWidget( $.extend(
		{
			indicator: 'down',
			$indicator: this.$indicator,
			disabled: this.isDisabled()
		},
		config.input
	) );
	this.input.$input.eq( 0 ).attr( {
		role: 'combobox',
		'aria-autocomplete': 'list'
	} );
	this.menu = new OO.ui.TextInputMenuSelectWidget( this.input, $.extend(
		{
			widget: this,
			input: this.input,
			disabled: this.isDisabled()
		},
		config.menu
	) );

	// Events
	this.$indicator.on( {
		click: this.onClick.bind( this ),
		keypress: this.onKeyPress.bind( this )
	} );
	this.input.connect( this, {
		change: 'onInputChange',
		enter: 'onInputEnter'
	} );
	this.menu.connect( this, {
		choose: 'onMenuChoose',
		add: 'onMenuItemsChange',
		remove: 'onMenuItemsChange'
	} );

	// Initialization
	this.$element.addClass( 'oo-ui-comboBoxWidget' ).append( this.input.$element );
	this.$overlay.append( this.menu.$element );
	this.onMenuItemsChange();
};

/* Setup */

OO.inheritClass( OO.ui.ComboBoxWidget, OO.ui.Widget );
OO.mixinClass( OO.ui.ComboBoxWidget, OO.ui.mixin.TabIndexedElement );

/* Methods */

/**
 * Get the combobox's menu.
 * @return {OO.ui.TextInputMenuSelectWidget} Menu widget
 */
OO.ui.ComboBoxWidget.prototype.getMenu = function () {
	return this.menu;
};

/**
 * Get the combobox's text input widget.
 * @return {OO.ui.TextInputWidget} Text input widget
 */
OO.ui.ComboBoxWidget.prototype.getInput = function () {
	return this.input;
};

/**
 * Handle input change events.
 *
 * @private
 * @param {string} value New value
 */
OO.ui.ComboBoxWidget.prototype.onInputChange = function ( value ) {
	var match = this.menu.getItemFromData( value );

	this.menu.selectItem( match );
	if ( this.menu.getHighlightedItem() ) {
		this.menu.highlightItem( match );
	}

	if ( !this.isDisabled() ) {
		this.menu.toggle( true );
	}
};

/**
 * Handle mouse click events.
 *
 *
 * @private
 * @param {jQuery.Event} e Mouse click event
 */
OO.ui.ComboBoxWidget.prototype.onClick = function ( e ) {
	if ( !this.isDisabled() && e.which === 1 ) {
		this.menu.toggle();
		this.input.$input[ 0 ].focus();
	}
	return false;
};

/**
 * Handle key press events.
 *
 *
 * @private
 * @param {jQuery.Event} e Key press event
 */
OO.ui.ComboBoxWidget.prototype.onKeyPress = function ( e ) {
	if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
		this.menu.toggle();
		this.input.$input[ 0 ].focus();
		return false;
	}
};

/**
 * Handle input enter events.
 *
 * @private
 */
OO.ui.ComboBoxWidget.prototype.onInputEnter = function () {
	if ( !this.isDisabled() ) {
		this.menu.toggle( false );
	}
};

/**
 * Handle menu choose events.
 *
 * @private
 * @param {OO.ui.OptionWidget} item Chosen item
 */
OO.ui.ComboBoxWidget.prototype.onMenuChoose = function ( item ) {
	this.input.setValue( item.getData() );
};

/**
 * Handle menu item change events.
 *
 * @private
 */
OO.ui.ComboBoxWidget.prototype.onMenuItemsChange = function () {
	var match = this.menu.getItemFromData( this.input.getValue() );
	this.menu.selectItem( match );
	if ( this.menu.getHighlightedItem() ) {
		this.menu.highlightItem( match );
	}
	this.$element.toggleClass( 'oo-ui-comboBoxWidget-empty', this.menu.isEmpty() );
};

/**
 * @inheritdoc
 */
OO.ui.ComboBoxWidget.prototype.setDisabled = function ( disabled ) {
	// Parent method
	OO.ui.ComboBoxWidget.super.prototype.setDisabled.call( this, disabled );

	if ( this.input ) {
		this.input.setDisabled( this.isDisabled() );
	}
	if ( this.menu ) {
		this.menu.setDisabled( this.isDisabled() );
	}

	return this;
};
<|endoftext|>


<commit_before>/**
 * KBase widget to display a table of gene information.
 *
 * Expected input data is JSON with one key, 'table', whose value is a list.
 * Each list item is a row, with column values. The first row has column names.
 * For example :
 {'table':
    [
        [
            "Chromosome ID",
            "Source gene ID",
            "Gene ID",
            "Gene function"
        ],
        [
            "kb|g.3899.c.3",
            "AT2G02480",
            "kb|g.3899.locus.8705",
            "AAA-type ATPase-like protein [Source:EMBL;Acc:AEC05585.1]"
        ],
        [
            "kb|g.3899.c.3",
            "AT2G16485",
            "kb|g.3899.locus.10216",
            "DNA binding / nucleic acid binding / protein binding / zinc ion binding protein [Source:EMBL;Acc:AEC06501.1]"
        ],
        ... etc ...
    ]
}
*/kb_define('kbaseGeneTable',
    [
        'jquery',
        'kbaseTable',
	'kbwidget'
    ],
    function ($) {

    $.KBWidget({
        name: "GeneTableWidget",
        parent : 'kbaseTable',
        version: "0.1.0",
        options: {
            table: null,
            maxVisibleRowIndex : 20,

            row_callback : function (cell, header, row, $kb) {

                var def = $kb.default_row_callback(cell, header, row, $kb);

                if (def.length < 12) {
                    return def;
                }

                var max = '1.5em';

                var $div = $.jqElem('div')
                    .css({'max-height' : max, 'overflow' : 'hidden', display : 'inline-block'})
                    .attr('class', 'truncated')
                    .append(def);

                return $.jqElem('div').append($div).append($.jqElem('div').attr('class', 'dots').css({'font-style' : 'italic', 'text-align' : 'right'}).append('...more'));

            },

        },

        wsUrl: "https://kbase.us/services/ws/",

        init: function(options) {

            if (options.table) {
                var headers = options.table.shift();
                var rows = [];

                var i;
                for (i = 0; i < options.table.length; i++) {
                    var row = {};
                    var j;
                    for (j = 0; j < headers.length; j++) {
                        row[headers[j]] = options.table[i][j];
                    }
                    rows.push(row);
                }

                options.structure = {
                    header : headers,
                    rows   : rows,
                }
            }

            options.sortable = true;
            options.navControls = true;
            options.headerOptions = {
                style : 'background-color : #EEEEEE; color : #0D7876;',
                sortable : true,
            };

            return this._super(options);
        },

        appendUI : function($elem, struct) {
            this._super($elem, struct);

            $elem.find('.truncated').each(function(i,v) {
                if ($(v).height() == v.scrollHeight) {
                    $(v).next().remove();
                    $(v).toggleClass('truncated');
                }

            });

            var max = '1.5em';
            $elem.find('tr')
                .on('mouseover', function(e) {
                    $(this).find('.truncated').css('max-height', '');
                    $(this).find('.dots').css('display', 'none');
                })
                .on('mouseout', function(e) {
                    $(this).find('.truncated').css('max-height', max);
                    $(this).find('.dots').css('display', '');
                })
            ;

            return $elem;
        },

    });

});
<commit_msg>removes max-height constraint on singleton rows<commit_after>/**
 * KBase widget to display a table of gene information.
 *
 * Expected input data is JSON with one key, 'table', whose value is a list.
 * Each list item is a row, with column values. The first row has column names.
 * For example :
 {'table':
    [
        [
            "Chromosome ID",
            "Source gene ID",
            "Gene ID",
            "Gene function"
        ],
        [
            "kb|g.3899.c.3",
            "AT2G02480",
            "kb|g.3899.locus.8705",
            "AAA-type ATPase-like protein [Source:EMBL;Acc:AEC05585.1]"
        ],
        [
            "kb|g.3899.c.3",
            "AT2G16485",
            "kb|g.3899.locus.10216",
            "DNA binding / nucleic acid binding / protein binding / zinc ion binding protein [Source:EMBL;Acc:AEC06501.1]"
        ],
        ... etc ...
    ]
}
*/kb_define('kbaseGeneTable',
    [
        'jquery',
        'kbaseTable',
	'kbwidget'
    ],
    function ($) {

    $.KBWidget({
        name: "GeneTableWidget",
        parent : 'kbaseTable',
        version: "0.1.0",
        options: {
            table: null,
            maxVisibleRowIndex : 20,

            row_callback : function (cell, header, row, $kb) {

                var def = $kb.default_row_callback(cell, header, row, $kb);

                if (def.length < 12) {
                    return def;
                }

                var max = '1.5em';

                var $div = $.jqElem('div')
                    .css({'max-height' : max, 'overflow' : 'hidden', display : 'inline-block'})
                    .attr('class', 'truncated')
                    .append(def);

                return $.jqElem('div').append($div).append($.jqElem('div').attr('class', 'dots').css({'font-style' : 'italic', 'text-align' : 'right'}).append('...more'));

            },

        },

        wsUrl: "https://kbase.us/services/ws/",

        init: function(options) {

            if (options.table) {
                var headers = options.table.shift();
                var rows = [];

                var i;
                for (i = 0; i < options.table.length; i++) {
                    var row = {};
                    var j;
                    for (j = 0; j < headers.length; j++) {
                        row[headers[j]] = options.table[i][j];
                    }
                    rows.push(row);
                }

                options.structure = {
                    header : headers,
                    rows   : rows,
                }
            }

            options.sortable = true;
            options.navControls = true;
            options.headerOptions = {
                style : 'background-color : #EEEEEE; color : #0D7876;',
                sortable : true,
            };

            return this._super(options);
        },

        appendUI : function($elem, struct) {
            this._super($elem, struct);

            $elem.find('.truncated').each(function(i,v) {
                if ($(v).height() == v.scrollHeight) {
                    $(v).css('max-height', '');
                    $(v).next().remove();
                    $(v).toggleClass('truncated');
                }

            });

            var max = '1.5em';
            $elem.find('tr')
                .on('mouseover', function(e) {
                    $(this).find('.truncated').css('max-height', '');
                    $(this).find('.dots').css('display', 'none');
                })
                .on('mouseout', function(e) {
                    $(this).find('.truncated').css('max-height', max);
                    $(this).find('.dots').css('display', '');
                })
            ;

            return $elem;
        },

    });

});
<|endoftext|>