add Ox.ArrayInput, more Ox.ListMap UI

This commit is contained in:
rolux 2011-05-21 19:56:15 +02:00
parent ce3bdb46d6
commit 6a33b9cb97
8 changed files with 381 additions and 101 deletions

View file

@ -581,14 +581,17 @@ Ox.load('Geo', function() {
},
{
options: {
min: 0,
max: 100,
type: "float"
},
title: "Float Input (0.0 - 100.0)"
},
{
options: {
min: 0,
max: 255,
type: "integer"
type: "int"
},
title: "Integer Input (0 - 255)"
},
@ -596,8 +599,7 @@ Ox.load('Geo', function() {
options: {
clear: true,
id: "integerClear",
max: 255,
type: "integer"
type: "int"
},
title: "Integer Input with Clear Button"
},
@ -605,8 +607,7 @@ Ox.load('Geo', function() {
options: {
arrows: true,
id: "integerArrows",
max: 255,
type: "integer"
type: "int"
},
title: "Integer Input with Arrows"
},

View file

@ -1,16 +1,17 @@
Ox.load('UI', {
debug: true,
hideScreen: true,
showScreen: true,
theme: 'modern'
}, function() {
Ox.getJSON('json/cities100000.json', function(data) {
Ox.load('Geo', function() {
Ox.getJSON('json/cities1000000.json', function(data) {
var listmap = new Ox.ListMap({
height: window.innerHeight,
places: data.map(function(city) {
var marker = city.population > 20000000 ? {size: 24, color: [255, 0, 0]} :
var countryCode = city.country_code == 'XK' ? 'RS-KO' : city.country_code,
marker = city.population > 20000000 ? {size: 24, color: [255, 0, 0]} :
city.population > 10000000 ? {size: 22, color: [255, 32, 0]} :
city.population > 5000000 ? {size: 20, color: [255, 64, 0]} :
city.population > 2000000 ? {size: 18, color: [255, 96, 0]} :
@ -23,10 +24,10 @@ Ox.load('UI', {
latSize = size / Ox.EARTH_CIRCUMFERENCE * 360,
lngSize = size * Ox.getDegreesPerMeter(city.latitude);
return {
countryCode: city.country_code == 'XK' ? 'RS-KO' : city.country_code,
countryCode: countryCode,
editable: true,
flag: city.country_code,
geoname: city.name,
flag: countryCode,
geoname: city.name + ', ' + Ox.getCountryByCode(countryCode).name,
markerColor: marker.color,
markerSize: marker.size,
name: city.name,
@ -49,6 +50,7 @@ Ox.load('UI', {
}
})
.appendTo(Ox.UI.$body);
$(window).resize(function() {
Ox.print('RESIZE', window.innerHeight)
listmap.options({
@ -58,5 +60,8 @@ Ox.load('UI', {
});
window.listmap = listmap;
});
});
});

View file

@ -1088,6 +1088,9 @@ Maps
position: absolute;
}
.OxMap .OxRange .OxArrow {
border-radius: 0;
}
.OxMapButton {
position: absolute;

View file

@ -0,0 +1,151 @@
Ox.ArrayInput = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
label: '',
max: 0,
sort: false,
value: [],
width: 256
})
.options(options || {});
if (self.options.value.length == 0) {
self.options.value = [''];
}
if (self.options.label) {
self.$label = Ox.Label({
title: self.options.label,
width: self.options.width
})
.appendTo(that);
}
self.$element = [];
self.$input = [];
self.$removeButton = [];
self.$addButton = [];
self.options.value.forEach(function(value, i) {
addInput(i, value);
});
function addInput(i, value, focus) {
Ox.print('add', i)
self.$element.splice(i, 0, Ox.Element()
.css({height: '16px', marginTop: self.options.label || i > 0 ? '8px' : 0})
.data({index: i}));
if (i == 0) {
self.$element[i].appendTo(that);
} else {
Ox.print(i, self.$element)
self.$element[i].insertAfter(self.$element[i - 1]);
}
self.$input.splice(i, 0, Ox.Input({
value: value,
width: self.options.width - 48
})
.css({float: 'left'})
.bindEvent({
change: function(data) {
self.options.sort && data.value !== '' && sortInputs();
}
})
.appendTo(self.$element[i]));
focus && self.$input[i].focusInput();
self.$removeButton.splice(i, 0, Ox.Button({
disabled: self.$input.length == 1,
title: 'remove',
type: 'image'
})
.css({float: 'left', marginLeft: '8px'})
.bind({
click: function() {
removeInput($(this).parent().data('index'));
}
})
.appendTo(self.$element[i]));
self.$addButton.splice(i, 0, Ox.Button({
disabled: i == self.options.max - 1,
title: 'add',
type: 'image'
})
.css({float: 'left', marginLeft: '8px'})
.bind({
click: function() {
addInput($(this).parent().data('index') + 1, '', true);
}
})
.appendTo(self.$element[i]));
self.$input.length > 1 && self.$removeButton[0].options({disabled: false});
self.$input.length == self.options.max && self.$addButton.forEach(function($button) {
$button.options({disabled: true});
});
updateIndices();
}
function removeInput(i) {
Ox.print('remove', i);
['input', 'removeButton', 'addButton', 'element'].forEach(function(element) {
var key = '$' + element;
self[key][i].removeElement();
self[key].splice(i, 1);
});
self.$input.length == 1 && self.$removeButton[0].options({disabled: true});
self.$input.length == self.options.max - 1 && self.$addButton.forEach(function($button) {
$button.options({disabled: false});
});
updateIndices();
}
function setWidths() {
self.$label && self.$label.options({width: self.options.width})
self.$element.forEach(function($element, i) {
$element.css({width: self.options.width + 'px'});
self.$input[i].options({width: self.options.width - 48});
});
}
function sortInputs() {
Ox.sort(self.$element, function($element) {
return self.$input[$element.data('index')].value();
}).forEach(function($element) {
$element.detach();
});
self.$element.forEach(function($element, i) {
$element.data({index: i}).appendTo(that);
});
}
function updateIndices() {
self.$element.forEach(function($element, i) {
Ox.print(i, $element)
$element.data({index: i});
});
/*
self.$input.forEach(function($input, i) {
$input.options({value: i});
});
*/
}
self.setOption = function(key, value) {
if (key == 'width') {
setWidths();
}
}
// fixme: can't we generally use options.value for this?
that.value = function() {
if (arguments.length == 0) {
return Ox.map(self.$input, function($input) {
var value = $input.value();
return value === '' ? null : value;
});
}
};
return that;
};

View file

@ -5,7 +5,7 @@ Ox.Input <f:Ox.Element> Input Element
(options) -> <f> Input Element
(options, self) -> <f> Input Element
options <o> Options object
arrows <b> if true, and type is 'float' or 'integer', display arrows
arrows <b> if true, and type is 'float' or 'int', display arrows
arrowStep <n> step when clicking arrows
autocomplete <a> array of possible values, or
<f> function(key, value, callback), returns one or more values
@ -14,7 +14,7 @@ Ox.Input <f:Ox.Element> Input Element
autocompleteSelect <b> if true, menu is displayed
autocompleteSelectHighlight <b> if true, value in menu is highlighted
autocompleteSelectSubmit <b> if true, submit input on menu selection
autocorrect <s|r|f|null> ('email', 'float', 'integer', 'phone', 'url'), or
autocorrect <s|r|f|null> ('email', 'float', 'int', 'phone', 'url'), or
<r> regexp(value), or
<f> function(key, value, blur, callback), returns value
autovalidate <f> --remote validation--
@ -24,8 +24,8 @@ Ox.Input <f:Ox.Element> Input Element
height <n> px (for type='textarea' and type='range' with orientation='horizontal')
id <s> element id
key <s> to be passed to autocomplete and autovalidate functions
max <n> max value if type is 'integer' or 'float'
min <n> min value if type is 'integer' or 'float'
max <n> max value if type is 'int' or 'float'
min <n> min value if type is 'int' or 'float'
name <s> will be displayed by autovalidate function ('invalid ' + name)
overlap <s> '', 'left' or 'right', will cause padding and negative margin
picker <o> picker object
@ -48,7 +48,7 @@ Ox.Input <f:Ox.Element> Input Element
trackValues <b> boolean
serialize <f> function used to serialize value in submit
textAlign <s> 'left', 'center' or 'right'
type <s> 'float', 'integer', 'password', 'text', 'textarea'
type <s> 'float', 'int', 'password', 'text', 'textarea'
value <s> string
validate <f> remote validation
width <n> px
@ -74,10 +74,11 @@ Ox.Input = function(options, self) {
autovalidate: null,
changeOnKeypress: false,
clear: false,
decimals: 0,
disabled: false,
key: '',
min: 0,
max: 100,
min: -Infinity,
max: Infinity,
label: '',
labelWidth: 64,
overlap: 'none',
@ -97,6 +98,7 @@ Ox.Input = function(options, self) {
' OxOverlap' + Ox.toTitleCase(self.options.overlap) : ''
)*/
)
.css({width: self.options.width + 'px'})
.bindEvent($.extend(self.options.type == 'textarea' ? {} : {
key_enter: submit
}, {
@ -114,15 +116,17 @@ Ox.Input = function(options, self) {
}
// fixme: set to min, not 0
// fixme: validate self.options.value !
if (self.options.type == 'float') {
self.decimals = Ox.repeat('0', self.options.decimals || 1)
$.extend(self.options, {
autovalidate: 'float',
textAlign: 'right',
value: self.options.value || '0.0'
value: self.options.value || '0.' + self.decimals
});
} else if (self.options.type == 'integer') {
} else if (self.options.type == 'int') {
$.extend(self.options, {
autovalidate: 'integer',
autovalidate: 'int',
textAlign: 'right',
value: self.options.value || '0'
});
@ -176,7 +180,8 @@ Ox.Input = function(options, self) {
}
$.extend(self, {
bindKeyboard: self.options.autocomplete || self.options.autovalidate || self.options.changeOnKeypress,
bindKeyboard: self.options.autocomplete || self.options.autovalidate ||
self.options.changeOnKeypress,
hasPasswordPlaceholder: self.options.type == 'password' && self.options.placeholder,
inputWidth: getInputWidth()
});
@ -449,48 +454,57 @@ Ox.Input = function(options, self) {
function autovalidateTypeFunction(type, value) {
// fixme: remove trailing zeroes on blur
// /(^\-?\d+\.?\d{0,8}$)/('-13000.12345678')
var cursor,
regexp = type == 'float' ? /[\d\.]/ : /\d/;
length,
regexp = type == 'float' ? new RegExp(
'(^' + (self.options.min < 0 ? '\\-?' : '') + '\\d+\\.?\\d' +
(self.options.decimals ? '{0,' + self.options.decimals + '}' : '*')
+ '$)'
) : new RegExp('(^' + (self.options.min < 0 ? '\\-?' : '') + '\\d+)');
if (type == 'float') {
if (value.indexOf('.') != value.lastIndexOf('.')) {
value = oldValue;
} else {
if (self.autovalidateFloatFlag) {
if (Ox.endsWith(value, '.')) {
value = value.substr(0, value.length - 1);
}
self.autovalidateFloatFlag = false;
}
while (value[0] == '0' && value[1] != '.') {
value = value.substr(1);
}
while (Ox.startsWith(value, '.')) {
if (Ox.startsWith(value, '..')) {
value = value.substr(1);
} else {
value = '0' + value;
}
}
if (Ox.endsWith(value, '.')) {
value += '0';
cursor = [value.length - 1, value.length];
self.autovalidateFloatFlag = true;
}
}
}
value = $.map(value.split(''), function(v) {
return regexp(v) ? v : null;
}).join('');
if (type == 'integer') {
while (value.length > 1 && Ox.startsWith(value, '0')) {
value = value.substr(1);
}
}
//Ox.print('--float--', value)
if (value === '') {
value = type == 'float' ? '0.0' : '0';
value = '0.' + self.decimals;
cursor = [0, value.length];
} else if (value > self.options.max) {
} else if (value == '-') {
value = '-0.' + self.decimals;
cursor = [1, value.length];
} else if (value == '.') {
value = '0.' + self.decimals;
cursor = [2, value.length];
} else if (!/\./(value)) {
value += '.' + self.decimals
cursor = [value.indexOf('.'), value.length];
} else if (/^\./(value)) {
value = '0' + value;
cursor = [2, value.length];
} else if (/\.$/(value)) {
//Ox.print('$$$$$$$$$$$')
value += self.decimals;
cursor = [value.indexOf('.') + 1, value.length];
} else if (/\./(value) && self.options.decimals) {
length = value.split('.')[1].length;
if (length > self.options.decimals) {
value = value.substr(0, value.indexOf('.') + 1 + self.options.decimals);
cursor = [oldCursor[0] + 1, oldCursor[1] + 1];
} else if (length < self.options.decimals) {
value += Ox.repeat('0', self.options.decimals - length);
cursor = [value.indexOf('.') + 1 + length, value.length];
}
}
} else {
if (value === '') {
value = '0';
cursor = [0, 1];
}
}
while (/^0\d/(value)) {
value = value.substr(1, value.length);
}
if (!regexp.test(value) || value < self.options.min || value > self.options.max) {
value = oldValue;
cursor = oldCursor;
}
autovalidateCallback(value, cursor);
}
@ -549,8 +563,8 @@ Ox.Input = function(options, self) {
self.options.placeholder && setPlaceholder();
self.options.validate && validate();
if (self.bindKeyboard) {
Ox.UI.$document.unbind('keydown', keypress);
Ox.UI.$document.unbind('keypress', keypress);
Ox.UI.$document.unbind('keydown', keydown);
//Ox.UI.$document.unbind('keypress', keypress);
}
that.triggerEvent('blur');
}
@ -572,7 +586,7 @@ Ox.Input = function(options, self) {
var value = '';
if (self.options.type == 'float') {
value = '0.0';
} else if (self.options.type == 'integer') {
} else if (self.options.type == 'int') {
value = '0'
}
self.$input.val(value);
@ -615,7 +629,7 @@ Ox.Input = function(options, self) {
function deselectMenu() {
return;
Ox.print('deselectMenu')
//Ox.print('deselectMenu')
self.options.value = self.oldValue;
self.$input.val(self.options.value);
cursor(self.oldCursor);
@ -636,8 +650,8 @@ Ox.Input = function(options, self) {
if (self.bindKeyboard) {
//Ox.print('binding...')
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
Ox.UI.$document.keydown(keypress);
Ox.UI.$document.keypress(keypress);
Ox.UI.$document.keydown(keydown);
//Ox.UI.$document.keypress(keypress);
self.options.autocompleteSelect && setTimeout(autocomplete, 0); // fixme: why is the timeout needed?
}
that.triggerEvent('focus');
@ -651,7 +665,7 @@ Ox.Input = function(options, self) {
(self.options.style == 'rounded' ? 14 : 6);
}
function keypress(event) {
function keydown(event) {
var oldCursor = cursor(),
oldValue = self.options.value,
newValue = oldValue.substr(0, oldCursor[0] - 1),
@ -664,7 +678,8 @@ Ox.Input = function(options, self) {
) { // fixme: can't 13 and 27 return false?
setTimeout(function() { // wait for val to be set
var value = self.$input.val();
if (self.options.autocompleteReplace && hasDeletedSelectedEnd) {
if ((self.options.autocompleteReplace || self.options.decimals) && hasDeletedSelectedEnd) {
//Ox.print('HAS DELETED SELECTED END', event.keyCode)
value = newValue;
self.$input.val(value);
}
@ -697,7 +712,7 @@ Ox.Input = function(options, self) {
function selectMenu(event, data) {
var pos = cursor();
//if (self.options.value) {
Ox.print('selectMenu', pos, data.title)
//Ox.print('selectMenu', pos, data.title)
self.options.value = data.title
self.$input.val(self.options.value);
cursor(pos[0], self.options.value.length);
@ -778,6 +793,7 @@ Ox.Input = function(options, self) {
self.$input.val(value);
setPlaceholder();
} else if (key == 'width') {
that.css({width: self.options.width + 'px'});
inputWidth = getInputWidth();
self.$input.css({
width: inputWidth + 'px'
@ -1535,8 +1551,8 @@ Ox.InputElement_ = function(options, self) {
}
}
if (self.bindKeyboard) {
Ox.UI.$document.unbind('keydown', keypress);
Ox.UI.$document.unbind('keypress', keypress);
Ox.UI.$document.unbind('keydown', keydown);
//Ox.UI.$document.unbind('keypress', keypress);
}
}
@ -1585,14 +1601,13 @@ Ox.InputElement_ = function(options, self) {
}
if (self.bindKeyboard) {
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
Ox.UI.$document.keydown(keypress);
Ox.UI.$document.keypress(keypress);
Ox.UI.$document.keydown(keydown);
//Ox.print('calling autosuggest...')
self.options.autosuggest && setTimeout(autosuggestCall, 0); // fixme: why is the timeout needed?
}
}
function keypress(event) {
function keydown(event) {
//Ox.print('keyCode', event.keyCode)
if (event.keyCode != 9 && event.keyCode != 13 && event.keyCode != 27) { // fixme: can't 13 and 27 return false?
setTimeout(function() { // fixme: document what this timeout is for

View file

@ -35,6 +35,8 @@ Ox.Label = function(options, self) {
self.setOption = function(key, value) {
if (key == 'title') {
that.html(value);
} else if (key == 'width') {
that.css({width: self.options.width - 14 + 'px'})
}
}

View file

@ -18,7 +18,7 @@ Ox.ListMap <f:Ox.Element> ListMap Object
Ox.ListMap = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
that = Ox.Element({}, self)
.defaults({
addPlace: null,
height: 256,
@ -187,13 +187,13 @@ Ox.ListMap = function(options, self) {
}
];
self.$toolbar = new Ox.Bar({
self.$listToolbar = Ox.Bar({
size: 24
});
self.$findElement = new Ox.FormElementGroup({
self.$findElement = Ox.FormElementGroup({
elements: [
self.$findSelect = new Ox.Select({
self.$findSelect = Ox.Select({
items: [
{id: 'all', title: 'Find: All'},
{id: 'name', title: 'Find: Name'},
@ -203,16 +203,16 @@ Ox.ListMap = function(options, self) {
overlap: 'right',
width: 128
}),
self.$findInput = new Ox.Input({
self.$findInput = Ox.Input({
clear: true,
width: 192
})
]
})
.css({float: 'right', margin: '4px'})
.appendTo(self.$toolbar)
.appendTo(self.$listToolbar)
self.$list = new Ox.TextList({
self.$list = Ox.TextList({
columns: self.columns,
columnsRemovable: true,
columnsVisible: true,
@ -233,19 +233,95 @@ Ox.ListMap = function(options, self) {
select: selectItem
});
self.$statusbar = new Ox.Bar({
self.$listStatusbar = Ox.Bar({
size: 16
});
self.$status = Ox.Element()
.css({paddingTop: '2px', margin: 'auto', fontSize: '9px', textAlign: 'center'})
.html(self.options.places.length + ' Place' + (self.options.places.length == 1 ? '' : 's'))
.appendTo(self.$listStatusbar);
self.$placeToolbar = Ox.Bar({
size: 24
});
self.$status = new Ox.Element()
.css({paddingTop: '4px', margin: 'auto', textAlign: 'center'})
.appendTo(self.$statusbar);
self.$newPlaceButton = Ox.Button({
id: 'newPlace',
title: 'New Place',
width: 80
})
.css({float: 'left', margin: '4px'})
.appendTo(self.$placeToolbar);
self.$placeFormItems = Ox.merge([
Ox.Input({
id: 'name',
label: 'Name',
labelWidth: 64,
width: 240
}),
Ox.Input({
id: 'geoname',
label: 'Geoname',
labelWidth: 64,
width: 240
}),
Ox.ArrayInput({
id: 'alternativeNames',
label: 'Alternative Names',
max: 10,
//sort: true,
values: [],
width: 240
}),
], ['Latitude', 'Longitude', 'South', 'West', 'North', 'East'].map(function(v) {
var key = (
v == 'Latitude' ? 'lat' : v == 'Longitude' ? 'lng' : v
).toLowerCase(),
max = ['Latitude', 'South', 'North'].indexOf(v) > -1 ? Ox.MAX_LATITUDE : 180;
return Ox.Input({
decimals: 8,
label: v,
labelWidth: 80,
min: -max,
max: max,
type: 'float',
value: self.options.places[0][key].toFixed(8),
width: 240
});
}));
self.$placeForm = Ox.Form({
items: self.$placeFormItems,
width: 240
})
.css({margin: '8px'});
self.$placeStatusbar = Ox.Bar({
size: 16
})
.addClass('OxVideoPlayer'); // fixme!
self.$savePlaceButton = Ox.Button({
disabled: true,
id: 'savePlace',
title: 'check',
style: 'symbol',
tooltip: 'Save Place',
type: 'image',
//width: 80
})
.css({float: 'right'})
.appendTo(self.$placeStatusbar);
/*
self.mapResize = [
Math.round(self.options.width * 0.25),
Math.round(self.options.width * 0.5),
Math.round(self.options.width * 0.75)
];
*/
if (Ox.isArray(self.options.places)) {
init(self.options.places)
@ -266,15 +342,15 @@ Ox.ListMap = function(options, self) {
function init(places) {
//Ox.print('PLACES', places)
self.$map = new Ox.Map({
self.$map = Ox.Map({
clickable: true,
editable: true,
findPlaceholder: 'Find on Map',
height: self.options.height,
places: places,
statusbar: true,
//statusbar: true,
toolbar: true,
width: self.mapResize[1],
width: self.options.width - 514,//self.mapResize[1],
zoombar: true
})
.bindEvent({
@ -290,31 +366,51 @@ Ox.ListMap = function(options, self) {
selectplace: selectPlace
});
that.$element.replaceWith(
that.$element = new Ox.SplitPanel({
that.$element = Ox.SplitPanel({
elements: [
{
element: new Ox.SplitPanel({
element: Ox.SplitPanel({
elements: [
{
element: self.$toolbar,
element: self.$listToolbar,
size: 24
},
{
element: self.$list
},
{
element: self.$statusbar,
size: 24
element: self.$listStatusbar,
size: 16
}
],
orientation: 'vertical'
})
}),
resizable: true,
resize: [256, 384, 512],
size: 256
},
{
element: self.$map,
resizable: true,
resize: self.mapResize,
size: self.mapResize[1]
},
{
collapsible: true,
element: Ox.SplitPanel({
elements: [
{
element: self.$placeToolbar,
size: 24
},
{
element: self.$placeForm
},
{
element: self.$placeStatusbar,
size: 16
}
],
orientation: 'vertical'
}),
size: 256
}
],
orientation: 'horizontal'

View file

@ -1,6 +1,6 @@
// vim: et:ts=4:sw=4:sts=4:ft=js
// OxJS (c) 2011 Ox2620, dual-licensed GPL/MIT, see http://oxjs.org for details
// OxJS (c) 2011 0x2620, dual-licensed GPL/MIT, see http://oxjs.org for details
/*
Some conventions:
@ -194,13 +194,18 @@ Ox.merge = function(arr) {
/*@
Ox.sort <f> Sorts an array, handling leading digits and ignoring capitalization
arr <a> Array
fn <f|u> Optional map function that returns the value for the array element
> Ox.sort(['10', '9', 'B', 'a'])
['9', '10', 'a', 'B']
> Ox.sort([{id: 0, name: '80 Days'}, {id: 1, name: '8 Women'}], function(v) {return v.name});
[{id: 1, name: '8 Women'}, {id: 0, name: '80 Days'}]
@*/
Ox.sort = function(arr) {
var len, matches = {}, sort = {};
Ox.sort = function(arr, fn) {
var len, matches = {}, sort = {},
values = fn ? arr.map(fn) : arr;
// find leading numbers
arr.forEach(function(val, i) {
values.forEach(function(val, i) {
var match = /^\d+/(val);
matches[val] = match ? match[0] : '';
});
@ -209,7 +214,7 @@ Ox.sort = function(arr) {
return val.length;
}));
// pad leading numbers, and make lower case
arr.forEach(function(val) {
values.forEach(function(val) {
sort[val] = (
matches[val] ?
Ox.pad(matches[val], len) + val.toString().substr(matches[val].length) :
@ -217,6 +222,8 @@ Ox.sort = function(arr) {
).toLowerCase();
});
return arr.sort(function(a, b) {
a = fn ? fn(a) : a;
b = fn ? fn(b) : b;
var ret = 0;
if (sort[a] < sort[b]) {
ret = -1;