mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 02:54:11 +08:00
390 lines
9.6 KiB
TypeScript
390 lines
9.6 KiB
TypeScript
import { GridLayout as GridLayoutDefinition, ItemSpec as ItemSpecDefinition } from '.';
|
|
import { LayoutBase } from '../layout-base';
|
|
import { View, CSSType } from '../../core/view';
|
|
import { Property, makeParser, makeValidator } from '../../core/properties';
|
|
import { Observable } from '../../../data/observable';
|
|
|
|
function validateArgs(element: View): View {
|
|
if (!element) {
|
|
throw new Error('element cannot be null or undefined.');
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
View.prototype.row = 0;
|
|
View.prototype.col = 0;
|
|
View.prototype.rowSpan = 1;
|
|
View.prototype.colSpan = 1;
|
|
|
|
Object.defineProperty(View.prototype, 'column', {
|
|
get(this: View): number {
|
|
return this.col;
|
|
},
|
|
set(this: View, value: number) {
|
|
this.col = value;
|
|
},
|
|
enumerable: true,
|
|
configurable: true,
|
|
});
|
|
|
|
Object.defineProperty(View.prototype, 'columnSpan', {
|
|
get(this: View): number {
|
|
return this.colSpan;
|
|
},
|
|
set(this: View, value: number) {
|
|
this.colSpan = value;
|
|
},
|
|
enumerable: true,
|
|
configurable: true,
|
|
});
|
|
|
|
function validateItemSpec(itemSpec: ItemSpec): void {
|
|
if (!itemSpec) {
|
|
throw new Error('Value cannot be undefined.');
|
|
}
|
|
|
|
if (itemSpec.owner) {
|
|
throw new Error('itemSpec is already added to GridLayout.');
|
|
}
|
|
}
|
|
|
|
function convertGridLength(value: string): ItemSpec {
|
|
if (value === GridUnitType.AUTO) {
|
|
return ItemSpec.create(1, GridUnitType.AUTO);
|
|
} else if (value.indexOf('*') !== -1) {
|
|
const starCount = parseInt(value.replace('*', '') || '1');
|
|
|
|
return ItemSpec.create(starCount, GridUnitType.STAR);
|
|
} else if (!isNaN(parseInt(value))) {
|
|
return ItemSpec.create(parseInt(value), GridUnitType.PIXEL);
|
|
} else {
|
|
throw new Error(`Cannot parse item spec from string: ${value}`);
|
|
}
|
|
}
|
|
|
|
function parseAndAddItemSpecs(value: string, func: (itemSpec: ItemSpec) => void): void {
|
|
// ensure value is a string since view bindings could be parsed as number/int's here
|
|
const arr = `${value}`.split(/[\s,]+/);
|
|
for (let i = 0, length = arr.length; i < length; i++) {
|
|
const str = arr[i].trim();
|
|
if (str.length > 0) {
|
|
func(convertGridLength(arr[i].trim()));
|
|
}
|
|
}
|
|
}
|
|
|
|
export class ItemSpec extends Observable implements ItemSpecDefinition {
|
|
private _value: number;
|
|
private _unitType: GridUnitType;
|
|
|
|
constructor(...args) {
|
|
super();
|
|
|
|
if (args.length === 0) {
|
|
this._value = 1;
|
|
this._unitType = GridUnitType.STAR;
|
|
} else if (arguments.length === 2) {
|
|
const value = args[0];
|
|
const type = args[1];
|
|
if (typeof value === 'number' && typeof type === 'string') {
|
|
if (value < 0 || isNaN(value) || !isFinite(value)) {
|
|
throw new Error(`Value should not be negative, NaN or Infinity: ${value}`);
|
|
}
|
|
|
|
this._value = value;
|
|
this._unitType = GridUnitType.parse(type);
|
|
} else {
|
|
throw new Error('First argument should be number, second argument should be string.');
|
|
}
|
|
} else {
|
|
throw new Error('ItemSpec expects 0 or 2 arguments');
|
|
}
|
|
|
|
this.index = -1;
|
|
}
|
|
|
|
public owner: GridLayoutBase;
|
|
public index: number;
|
|
public _actualLength = 0;
|
|
|
|
public static create(value: number, type: GridUnitType): ItemSpec {
|
|
const spec = new ItemSpec();
|
|
spec._value = value;
|
|
spec._unitType = type;
|
|
|
|
return spec;
|
|
}
|
|
|
|
public get actualLength(): number {
|
|
return this._actualLength;
|
|
}
|
|
|
|
public static equals(value1: ItemSpec, value2: ItemSpec): boolean {
|
|
return value1.gridUnitType === value2.gridUnitType && value1.value === value2.value && value1.owner === value2.owner && value1.index === value2.index;
|
|
}
|
|
|
|
get gridUnitType(): GridUnitType {
|
|
return this._unitType;
|
|
}
|
|
|
|
get isAbsolute(): boolean {
|
|
return this._unitType === GridUnitType.PIXEL;
|
|
}
|
|
|
|
get isAuto(): boolean {
|
|
return this._unitType === GridUnitType.AUTO;
|
|
}
|
|
|
|
get isStar(): boolean {
|
|
return this._unitType === GridUnitType.STAR;
|
|
}
|
|
|
|
get value(): number {
|
|
return this._value;
|
|
}
|
|
}
|
|
|
|
@CSSType('GridLayout')
|
|
export class GridLayoutBase extends LayoutBase implements GridLayoutDefinition {
|
|
private _rows: Array<ItemSpec> = new Array<ItemSpec>();
|
|
private _cols: Array<ItemSpec> = new Array<ItemSpec>();
|
|
|
|
public static getColumn(element: View): number {
|
|
return validateArgs(element).col;
|
|
}
|
|
|
|
public static setColumn(element: View, value: number): void {
|
|
validateArgs(element).col = value;
|
|
}
|
|
|
|
public static getColumnSpan(element: View): number {
|
|
return validateArgs(element).colSpan;
|
|
}
|
|
|
|
public static setColumnSpan(element: View, value: number): void {
|
|
validateArgs(element).colSpan = value;
|
|
}
|
|
|
|
public static getRow(element: View): number {
|
|
return validateArgs(element).row;
|
|
}
|
|
|
|
public static setRow(element: View, value: number): void {
|
|
validateArgs(element).row = value;
|
|
}
|
|
|
|
public static getRowSpan(element: View): number {
|
|
return validateArgs(element).rowSpan;
|
|
}
|
|
|
|
public static setRowSpan(element: View, value: number): void {
|
|
validateArgs(element).rowSpan = value;
|
|
}
|
|
|
|
public addRow(itemSpec: ItemSpec) {
|
|
validateItemSpec(itemSpec);
|
|
itemSpec.owner = this;
|
|
this._rows.push(itemSpec);
|
|
this._onRowAdded(itemSpec);
|
|
this.invalidate();
|
|
}
|
|
|
|
public addColumn(itemSpec: ItemSpec) {
|
|
validateItemSpec(itemSpec);
|
|
itemSpec.owner = this;
|
|
this._cols.push(itemSpec);
|
|
this._onColumnAdded(itemSpec);
|
|
this.invalidate();
|
|
}
|
|
|
|
public addChildAtCell(view: View, row: number, column: number, rowSpan?: number, columnSpan?: number): void {
|
|
this.addChild(view);
|
|
GridLayoutBase.setRow(view, row);
|
|
GridLayoutBase.setColumn(view, column);
|
|
if (rowSpan) {
|
|
GridLayoutBase.setRowSpan(view, rowSpan);
|
|
}
|
|
if (columnSpan) {
|
|
GridLayoutBase.setColumnSpan(view, columnSpan);
|
|
}
|
|
}
|
|
|
|
public removeRow(itemSpec: ItemSpec): void {
|
|
if (!itemSpec) {
|
|
throw new Error('Value is null.');
|
|
}
|
|
|
|
const index = this._rows.indexOf(itemSpec);
|
|
if (itemSpec.owner !== this || index < 0) {
|
|
throw new Error('Row is not child of this GridLayout');
|
|
}
|
|
|
|
itemSpec.index = -1;
|
|
this._rows.splice(index, 1);
|
|
this._onRowRemoved(itemSpec, index);
|
|
this.invalidate();
|
|
}
|
|
|
|
public removeColumn(itemSpec: ItemSpec): void {
|
|
if (!itemSpec) {
|
|
throw new Error('Value is null.');
|
|
}
|
|
|
|
const index = this._cols.indexOf(itemSpec);
|
|
if (itemSpec.owner !== this || index < 0) {
|
|
throw new Error('Column is not child of this GridLayout');
|
|
}
|
|
|
|
itemSpec.index = -1;
|
|
this._cols.splice(index, 1);
|
|
this._onColumnRemoved(itemSpec, index);
|
|
this.invalidate();
|
|
}
|
|
|
|
public removeColumns() {
|
|
for (let i = this._cols.length - 1; i >= 0; i--) {
|
|
const colSpec = this._cols[i];
|
|
this._onColumnRemoved(colSpec, i);
|
|
colSpec.index = -1;
|
|
}
|
|
this._cols.length = 0;
|
|
this.invalidate();
|
|
}
|
|
|
|
public removeRows() {
|
|
for (let i = this._rows.length - 1; i >= 0; i--) {
|
|
const rowSpec = this._rows[i];
|
|
this._onRowRemoved(rowSpec, i);
|
|
rowSpec.index = -1;
|
|
}
|
|
this._rows.length = 0;
|
|
this.invalidate();
|
|
}
|
|
|
|
public onRowChanged(element: View, oldValue: number, newValue: number) {
|
|
this.invalidate();
|
|
}
|
|
|
|
public onRowSpanChanged(element: View, oldValue: number, newValue: number) {
|
|
this.invalidate();
|
|
}
|
|
|
|
public onColumnChanged(element: View, oldValue: number, newValue: number) {
|
|
this.invalidate();
|
|
}
|
|
|
|
public onColumnSpanChanged(element: View, oldValue: number, newValue: number) {
|
|
this.invalidate();
|
|
}
|
|
|
|
public _onRowAdded(itemSpec: ItemSpec) {
|
|
//
|
|
}
|
|
|
|
public _onColumnAdded(itemSpec: ItemSpec) {
|
|
//
|
|
}
|
|
|
|
public _onRowRemoved(itemSpec: ItemSpec, index: number): void {
|
|
//
|
|
}
|
|
|
|
public _onColumnRemoved(itemSpec: ItemSpec, index: number): void {
|
|
//
|
|
}
|
|
|
|
public getColumns(): Array<ItemSpec> {
|
|
return this._cols.slice();
|
|
}
|
|
|
|
public getRows(): Array<ItemSpec> {
|
|
return this._rows.slice();
|
|
}
|
|
|
|
protected get columnsInternal(): Array<ItemSpec> {
|
|
return this._cols;
|
|
}
|
|
|
|
protected get rowsInternal(): Array<ItemSpec> {
|
|
return this._rows;
|
|
}
|
|
|
|
protected invalidate(): void {
|
|
// handled natively in android and overridden in ios.
|
|
}
|
|
|
|
set rows(value: string) {
|
|
this.removeRows();
|
|
parseAndAddItemSpecs(value, (spec: ItemSpec) => this.addRow(spec));
|
|
}
|
|
|
|
set columns(value: string) {
|
|
this.removeColumns();
|
|
parseAndAddItemSpecs(value, (spec: ItemSpec) => this.addColumn(spec));
|
|
}
|
|
}
|
|
|
|
GridLayoutBase.prototype.recycleNativeView = 'auto';
|
|
|
|
export const columnProperty = new Property<View, number>({
|
|
name: 'col',
|
|
defaultValue: 0,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
const grid = target.parent;
|
|
if (grid instanceof GridLayoutBase) {
|
|
grid.onColumnChanged(target, oldValue, newValue);
|
|
}
|
|
},
|
|
valueConverter: (v) => Math.max(0, parseInt(v)),
|
|
});
|
|
columnProperty.register(View);
|
|
|
|
export const columnSpanProperty = new Property<View, number>({
|
|
name: 'colSpan',
|
|
defaultValue: 1,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
const grid = target.parent;
|
|
if (grid instanceof GridLayoutBase) {
|
|
grid.onColumnSpanChanged(target, oldValue, newValue);
|
|
}
|
|
},
|
|
valueConverter: (v) => Math.max(1, parseInt(v)),
|
|
});
|
|
columnSpanProperty.register(View);
|
|
|
|
export const rowProperty = new Property<View, number>({
|
|
name: 'row',
|
|
defaultValue: 0,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
const grid = target.parent;
|
|
if (grid instanceof GridLayoutBase) {
|
|
grid.onRowChanged(target, oldValue, newValue);
|
|
}
|
|
},
|
|
valueConverter: (v) => Math.max(0, parseInt(v)),
|
|
});
|
|
rowProperty.register(View);
|
|
|
|
export const rowSpanProperty = new Property<View, number>({
|
|
name: 'rowSpan',
|
|
defaultValue: 1,
|
|
valueChanged: (target, oldValue, newValue) => {
|
|
const grid = target.parent;
|
|
if (grid instanceof GridLayoutBase) {
|
|
grid.onRowSpanChanged(target, oldValue, newValue);
|
|
}
|
|
},
|
|
valueConverter: (v) => Math.max(1, parseInt(v)),
|
|
});
|
|
rowSpanProperty.register(View);
|
|
|
|
export type GridUnitType = 'pixel' | 'star' | 'auto';
|
|
export namespace GridUnitType {
|
|
export const PIXEL = 'pixel';
|
|
export const STAR = 'star';
|
|
export const AUTO = 'auto';
|
|
export const isValid = makeValidator<GridUnitType>(PIXEL, STAR, AUTO);
|
|
export const parse = makeParser<GridUnitType>(isValid);
|
|
}
|