こんにちは、私の名前はDmitry Karlovskyと私です...私は数学が大好きです。 眠れなくなって、私と同じように切り離された同じサービス( ユーザー定義の数式、共有、ダウンロードを備えた軽量のスプレッドシート)のサービスを洗い流しました。
クレジット計算を使用した実例:
そして、夕方に$ molフレームワークを使用して同じものを作成する方法を説明します...
それはどんなポケモンですか?
$ molは、クロスプラットフォームのレスポンシブWebアプリケーションを迅速に作成するための最新のフレームワークです。 これは、すべてのモジュールに対して次のルールを確立するMAMアーキテクチャに基づいています。
- モジュールは、ソースコードを含むディレクトリです。
- ソースコードは多くの異なる言語で作成できます。
- モジュール内のすべての言語は同等です。
- モジュールは階層を形成できます。
- モジュール名は、ファイルシステム内のモジュールへのパスに厳密に対応しています。
- モジュール間に依存関係がある可能性があります。
- モジュールの依存関係に関する情報は、そのソースコードの静的分析によって取得されます。
- モジュールは、異なる言語(js、css、ツリーなど)の独立したバンドルのセットとしてアセンブルできます。
- 実際に使用されるモジュールのみがバンドルに含まれます。
- モジュールのすべてのソースコードがバンドルに入ります。
- モジュールにはバージョンがありません-現在のコードが常に使用されます。
- モジュールインターフェイスは拡張のために開いている必要がありますが、変更のために閉じている必要があります。
- 別のインターフェイスが必要な場合は、新しいモジュールを作成する必要があります。 たとえば、
/my/file/
および/my/file2/
です。 これにより、両方のインターフェイスを混同することなく使用できます。
作業環境
$ molでの開発の開始は非常に簡単です。 一度作業環境をデプロイしてから、パイなどのアプリケーション/ライブラリをリベットします。
最初にインストールする必要があります:
- Git
- NodeJS
- 任意のエディター。 推奨: VSCode
- ツリー構文を強調表示するためのエディタープラグイン。
- EditorConfigサポート用のエディタープラグイン。
Windowsで作業している場合は、ソースの行の終わりが変更されないようにGITを構成する必要があります。
git config --global core.autocrlf input
ここで、開発サーバーを自動的に作成するMAMプロジェクトをデプロイする必要があります。
git clone https://github.com/eigenmethod/mam.git cd mam npm install npm start
開発者サーバーが実行されているすべてのものは、エディターを開くことができます。 エディターでは、特定のアプリケーションまたは会社のプロジェクトではなく、プロジェクトのMAMディレクトリを開く必要があることに注意してください。
ご覧のとおり、$ molでの開発は非常に簡単です。 MAMアーキテクチャの基本原則は、すべての機能がそのままの状態で機能し、長時間の退屈なセットアップを必要としないことです。
アプリケーションワイヤーフレーム
陰謀のために、アプリケーションにはコールサイン$mol_app_calc
ます。 MAMルールに従って、それはそれぞれ/mol/app/calc/
ディレクトリになければなりません。 将来的には、そこにすべてのファイルを作成します。
まず、エントリポイント-簡単なindex.html
作成します。
<!doctype html> <html style="height:100%"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1"> <link href="-/web.css" rel="stylesheet"/> </head> <body mol_view_root="$mol_app_calc"> <script src="-/web.js" charset="utf-8"></script> </body> </html>
アプリケーションのマウントポイントを、アプリケーションをマウントすることを示す特別な属性mol_view_root
で指定したことを除いて、特別なことはありません。 $ molのアーキテクチャは、どのコンポーネントもアプリケーションのルートとして機能できるようになっています。 逆に、$ molアプリケーションは通常のコンポーネントにすぎず、別のアプリケーション内で簡単に使用できます。 たとえば、アプリケーションギャラリーで 。
スクリプトとスタイルへのパスをすぐに規定したことに注意してください-これらのバンドルはアプリケーション用に自動的に収集され、本当に必要なソースコードのみが含まれます。 将来を見据えて、アプリケーションの総容量は縮小せずに36 KBになりますが、圧縮すると次のようになります。
そのため、アプリケーションとなるコンポーネントを宣言するには、 calc.view.tree
ファイルを作成する必要があります。 calc.view.tree
ファイルの最も単純なコンテンツは1行のみで構成されています。
$mol_app_calc $mol_page
2番目の単語はベースコンポーネントの名前で、1番目の単語はベースのコンポーネントの名前で、ベースから継承されます。 したがって、各コンポーネントは他のコンポーネントの後継です。 他のすべてのコンポーネントの最も基本的なコンポーネントは、 $ mol_viewです。 すべてのコンポーネントに最も基本的なスタイルと動作のみを提供します。 この場合、ベースコンポーネントは$ mol_pageになります。これは、ヘッダー、本文、および地下室があるページです。
TypeScriptコンポーネントクラスはcalc.view.tree
から自動的に生成され、 -view.tree/calc.view.tree.ts
配置されるため、開発環境でキャッチできます。
namespace $ { export class $mol_app_calc extends $mol_page { } }
実際、アプリケーションはすでにhttp://localhost:8080/mol/app/calc/
で開くことができ、コールサインがタイトルとして空のページが表示されます。
view.treeの構文はかなり普通ではありませんが、単純で簡潔です。 それについてのレビューの一つを引用させてください:
ツリー構文は非常に読みやすいですが、少し慣れて、事前に終了しないようにする必要があります。 私の脳は約1週間消化してinしていましたが、啓発を受けて、このフレームワークが開発プロセスをどれだけ簡素化しているかを理解できます。 ©Vitaliy Makeev
だから怖がらないで、突っ込みましょう! そして、ページの一般的なレイアウトから始めましょう。ヘッダー、現在のセルの編集パネル、および実際のデータテーブルで構成されます。
各コンポーネントには、コンポーネント内で直接レンダリングされるもののリストを返すsub()
プロパティがあります。 $ mol_pageの場合、 Head()
、 Body()
Foot()
プロパティの値がそこでレンダリングされ、対応するサブコンポーネントを返します。
$mol_page $mol_view sub / <= Head $mol_view <= Body $mol_scroll <= Foot $mol_view
このコードでは、要点が見えるようにサブコンポーネントの実装の詳細を省略しています。 サブコンポーネント(BEM用語では「要素」とも呼ばれます)を宣言することにより、コンポーネントのコンテキストでその名前と、インスタンス化する必要があるクラスの名前を示します。 この方法で作成されたコンポーネントインスタンスはキャッシュされ、同じ名前のプロパティを介してアクセスできます。 たとえば、アプリケーションのコンテキストでthis.Body()
は、カスタマイズされた$ mol_scrollのインスタンスを返します。 パターンといえば、 Body()
プロパティはローカルレイジーファクトリーとして機能します。
sub()
プロパティをオーバーライドして、必要なコンポーネントを返しましょう:
$mol_app_calc $mol_page sub / <= Head - <= Current $mol_bar <= Body $mol_grid
ここでは、$ mol_pageからキャップを外し、現在のセルを編集するためのパネルとして$ mol_barを追加し、ページボディとして$ mol_gridを使用し、仮想テーブルを描画するためのコンポーネントを使用しました。
生成されたクラスがどのように変更されたかを見てみましょう。
namespace $ { export class $mol_app_calc extends $mol_page { /// sub / /// <= Head - /// <= Current - /// <= Body - sub() { return [].concat( this.Head() , this.Current() , this.Body() ) } /// Current $mol_bar @ $mol_mem Current() { return new this.$.$mol_bar } /// Body $mol_grid @ $mol_mem Body() { return new this.$.$mol_grid } } }
$ mol名刺は非常に読みやすいコードです。 これは、生成されたコードだけでなく、$ mol自体のモジュールコード、およびそれに基づいて作成されたアプリケーションのアプリケーションコードにも適用されます。
おそらく、オブジェクトがクラス名new $mol_grid
による直接インスタンス化ではnew $mol_grid
、これを介して作成されることに気づいたでしょう。 コンポーネントには$
フィールドがあり、パターンを話すグローバルコンテキストまたはレジストリを返します。 $
フィールドを介してグローバル値にアクセスする際立った機能は、任意の深さでネストされたすべてのコンポーネントのコンテキストを再定義するコンポーネントの機能です。 したがって、$ molは非常に実用的で控えめな方法で、 制御の反転を実装します 。これにより、再利用されたコンポーネントの深部のどこかで使用されている実装を置き換えることができます。
テーブル形成
さて、少し肉を作り、ネストされたコンポーネントを自分用に構成しましょう:グリッドは、どの列識別子を持つか、どの行識別子、そしてテーブルのヘッダーと本体のセルのリストを説明する必要があります。
Body $mol_grid col_ids <= col_ids / row_ids <= row_ids / head_cells <= head_cells / cells!row <= cells!row /
生成されたクラスは、次の説明で展開されます。
/// Body $mol_grid /// col_ids <= col_ids - /// row_ids <= row_ids - /// head_cells <= head_cells - /// cells!row <= cells!row - @ $mol_mem Body() { const obj = new this.$.$mol_grid obj.col_ids = () => this.col_ids() obj.row_ids = () => this.row_ids() obj.head_cells = () => this.head_cells() obj.cells = ( row ) => this.cells( row ) return obj }
ご覧のとおり、組み込みコンポーネントの対応するプロパティを実装に再定義するだけです。 これは非常に簡単ですが、同時にコンポーネントを互いにリアクティブにリンクできる強力な手法です。 view.tree
構文は、3種類のバインディングをサポートしています。
- 上記のコードのように、ネストされたコンポーネントにそのプロパティが返す値を伝えるときの左利き。
- 右側、ネストされたコンポーネントのプロパティのエイリアスとして機能するプロパティを作成するとき。
- 双方向、埋め込みコンポーネントにプロパティの読み取りと書き込みを行うように指示すると、それは独自のコンポーネントで機能すると考えます。
双方向バインディングを説明するために、現在のセルの編集パネルを詳しく見てみましょう。
Current $mol_bar sub / <= Pos $mol_string enabled false value <= pos \ <= Edit $mol_string hint \= value?val <=> formula_current?val \
ご覧のとおり、2つの入力フィールドで構成されています。
- セル座標。 現時点では、
enabled
プロパティをenabled
して変更することは禁止されています。この機能は将来のために残しておきます。 - 数式入力フィールド。 ここでは、入力フィールドの
value
プロパティとformula_current
プロパティをすでに双方向でバインドしています。これは、デフォルト値(空の文字列)を指定してすぐに宣言します。
Edit
formula_current
とformula_current
のコードは、ほぼ次のように生成されます。
/// Edit $mol_string /// hint \= /// value?val <=> formula_current?val - @ $mol_mem Edit() { const obj = new this.$.$mol_string obj.hint = () => "=" obj.value = ( val? ) => this.formula_current( val ) return obj } /// formula_current?val \ @ $mol_mem formula_current( val? : string , force? : $mol_atom_force ) { return ( val !== undefined ) ? val : "" }
反応型メモデコレーター$ mol_memのおかげで、 formula_current
メソッドによって返される値は、他の誰かが必要とするまでキャッシュされます。
これまで、コンポーネントの構成の宣言的な記述しかありませんでした。 作業のロジックを説明する前に、セルがどのように見えるかをすぐに宣言しましょう。
Col_head!id $mol_float dom_name \th horizontal false sub / <= col_title!id \ - Row_head!id $mol_float dom_name \th vertical false sub / <= row_title!id \ - Cell!id $mol_app_calc_cell value <= result!id \ selected?val <=> selected!id?val false
行と列の見出しは浮動するため、 $ mol_floatコンポーネントを使用します。これは、コンテキストを介して$ mol_scrollコンポーネントによって提供されるスクロール位置を追跡し、常に表示領域にあるようにコンポーネントをシフトします。 そして、セルに対して、別のコンポーネント$mol_app_calc_cell
を開始します。
$mol_app_calc_cell $mol_button dom_name \td sub / <= value \ attr * ^ mol_app_calc_cell_selected <= selected?val false mol_app_calc_cell_type <= type?val \ event_click?event <=> select?event null
このコンポーネントはクリック可能になるため、 $ mol_buttonから継承します。 クリックイベントをselect
プロパティに送信します。これにより、セルエディターがクリックされたものに切り替えられます。 さらに、選択したセルを特別な方法で様式化し、数値型のセルに右揃えを行うために、ここにいくつかの属性を追加します。 将来的には、セルのスタイルはシンプルになります。
[mol_app_calc_cell] { user-select: text; /* $mol_button */ background: var(--mol_skin_card); /* css-variables post-css */ } [mol_app_calc_cell_selected] { box-shadow: var(--mol_skin_focus_outline); z-index: 1; } [mol_app_calc_cell_type="number"] { text-align: right; }
同じ名前の[mol_app_calc_cell]
セレクターに注意して[mol_app_calc_cell]
-対応する属性が自動的にdomノードに追加され、cssクラスの配置における手作業からプログラマーを完全に救います。 これにより、開発が簡素化され、命名の一貫性が確保されます。
最後に、独自のロジックを追加するために、名前空間$.$$
にクラスを作成するcalc.view.ts
を作成します$.$$
これは、名前空間$
から同じ名前の自己生成クラスから継承します。
namespace $.$$ { export class $mol_app_calc_cell extends $.$mol_app_calc_cell { // } }
実行時に、両方の名前空間は同じオブジェクトを指します。つまり、ロジックを持つクラスは、自動生成されたクラスから継承した後、単純にその場所に置き換わります。 このような巧妙な操作のおかげで、ロジックを持つクラスの追加はオプションのままであり、十分な宣言的記述がない場合にのみ適用されます。 たとえば、 select()
プロパティを再定義して、イベントオブジェクトを書き込もうとすると、 selected()
プロパティがtrue
変更selected()
ようにしtrue
。
select( event? : Event ) { if( event ) this.selected( true ) }
そして、 type()
プロパティはvalue()
プロパティを分析することでセルタイプを返します:
type() { const value = this.value() return isNaN( Number( value ) ) ? 'string' : 'number' }
しかし、テーブルに戻りましょう。 同様に、ロジックを$mol_app_calc
コンポーネントに追加します。
export class $mol_app_calc extends $.$mol_app_calc { }
まず、 row_ids()
行識別子とrow_ids()
列のリストを作成する必要があります。
@ $mol_mem col_ids() { return Array( this.dimensions().cols ).join(' ').split(' ').map( ( _ , i )=> this.number2string( i ) ) } @ $mol_mem row_ids() { return Array( this.dimensions().rows ).join(' ').split(' ').map( ( _ , i )=> i + 1 ) }
これらは、セル占有率に基づいて計算するdimensions()
プロパティに依存するため、塗りつぶされたセルには、少なくとも2つの空のセルが右と下にあります。
@ $mol_mem dimensions() { const dims = { rows : 2 , cols : 3 , } for( let key of Object.keys( this.formulas() ) ) { const parsed = /^([AZ]+)(\d+)$/.exec( key ) const rows = Number( parsed[2] ) + 2 const cols = this.string2number( parsed[1] ) + 3 if( rows > dims.rows ) dims.rows = rows if( cols > dims.cols ) dims.cols = cols } return dims }
string2number()
およびnumber2string()
メソッドは、列のアルファベット座標を数値に、またはその逆に単純に変換します。
number2string( numb : number ) { const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' let str = '' do { str = letters[ numb % 26 ] + str numb = Math.floor( numb / 26 ) } while ( numb ) return str } string2number( str : string ) { let numb = 0 for( let symb of str.split( '' ) ) { numb = numb * 26 numb += symb.charCodeAt( 0 ) - 65 } return numb }
数式のレジストリに基づいてテーブルの次元を計算します。これは、 formulas()
プロパティから取得します。 次の形式のJSONを返す必要があります。
{ "A1" : "12" , "B1" : "=A1*2" }
そして、式#A1=12/B1=%3DA1*2
の式自体と住所行を使用します。
@ $mol_mem formulas( next? : { [ key : string ] : string } ) { const formulas : typeof next = {} let args = this.$.$mol_state_arg.dict() if( next ) args = this.$.$mol_state_arg.dict({ ... args , ... next }) const ids = Object.keys( args ).filter( param => /^[AZ]+\d+$/.test( param ) ) for( let id of ids ) formulas[ id ] = args[ id ] return formulas }
ご覧のように、 formulas()
プロパティは変更可能です。つまり、セルの数式を読み取り、それを介してアドレスバーに更新を書き込むことができます。 たとえば、 this.formulas({ 'B1' : '24' })
を実行すると、アドレスバーに#A1=12/B1=24
ます。
アドレスバー
クロスプラットフォームモジュール$ mol_state_argを使用すると、辞書などのアプリケーションパラメーターを操作できますが、原則として、名前で特定のパラメーターを取得および書き込む方が便利です。 たとえば、ユーザーがテーブルの名前を変更できるようにします。名前は再びアドレスバーに保存します。
title( next? : string ) { const title = this.$.$mol_state_arg.value( `title` , next ) return title == undefined ? super.title() : title }
ご覧のとおり、アドレスバーにテーブル名が指定されていない場合、親クラスで指定された名前が使用されます。これは、 calc.view.tree
から生成され、ヘッダーを単に表示する代わりにヘッダー入力/出力フィールドをヘッダーに追加して更新します:
head / <= Title_edit $mol_string value?val <=> title?val @ \Spreedsheet <= Tools -
head()
は$ mol_pageのプロパティで、 Head()
サブコンポーネント内でレンダリングされるもののリストを返します。 これは、$ molの典型的なパターンです。ネストされたコンポーネントとそのコンテンツに同じ単語を付けますが、唯一の違いはコンポーネントの名前が大文字になっていることです。
Tools()
-ヘッダーの右側に表示される$ mol_pageのツールバー。 テーブルのダウンロードボタンをCSVファイルとして配置して、すぐに入力しましょう。
tools / <= Download $mol_link hint <= download_hint @ \Download file_name <= download_file \ uri <= download_uri?val \ click?event <=> download_generate?event null sub / <= Download_icon $mol_icon_load
$ mol_link-リンク構築用のコンポーネント。 file_name()
を指定した場合、それをクリックすると、指定された名前で保存して、参照によりファイルをダウンロードすることを提案します。 テーブル名に基づいてこの名前をすぐに作成しましょう:
download_file() { return `${ this.title() }.csv` }
ローカリゼーション
英語のデフォルト値の前の犬のシンボルに注意してください。
download_hint @ \Download
このシンボルを挿入するだけで、アプリケーションにローカライズサポートを追加できます。 生成されたクラスには「ダウンロード」行はありません-ローカライズされたテキストのリクエストのみがあります。
/// download_hint @ \Download download_hint() { return $mol_locale.text( "$mol_app_calc_download_hint" ) }
また、英語のテキスト自体は自動的に別のファイル-view.tree/calc.view.tree.locale=en.json
配置されます。
{ "$mol_app_calc_title": "Spreedsheet", "$mol_app_calc_download_hint": "Download" }
ご覧のとおり、テキストには人間が読み取れる一意のキーが形成されています。 このファイルを翻訳者に渡し、翻訳者からの翻訳を*.locale=*.json
の形式のファイルに*.locale=*.json
ことができます。 たとえば、コンポーネントのロシア語への翻訳をファイルcalc.locale=ru.json
に追加します。
{ "$mol_app_calc_title" : " " , "$mol_app_calc_download_hint" : "" }
ブラウザのメイン言語としてロシア語が設定されている場合、アプリケーションを起動すると、ロシア語のテキストを含むバンドルが非同期にロードされます-/web.locale=ru.json
その間、ダウンロードが進行中です。翻訳依存のコンポーネントは、ダウンロードインジケータを自動的に表示します。
セルを埋める
したがって、行と列の識別子があります。 セルのリストを作成しましょう。 最初の列見出し:
@ $mol_mem head_cells() { return [ this.Col_head( '' ) , ... this.col_ids().map( colId => this.Col_head( colId ) ) ] }
行ヘッダーがあるため、最初に追加の列を追加したことに注意してください。 行のセルは次のとおりです。
cells( row_id : number ) { return [ this.Row_head( row_id ) , ... this.col_ids().map( col_id => this.Cell({ row : row_id , col : col_id }) ) ] }
次に、セル用に編み上げたプロパティについて思い出します。
Cell!id $mol_app_calc_cell value <= result!id \ selected?val <=> selected!id?val false
セルの場合、これらは単なる普通のプロパティですが、私たちにとってはキー-セルの識別子を取ります。
現在のセルの識別子を格納するcurrent()
プロパティを導入します:
current?val * row 1 col \A
selected()
実装では、渡された識別子と現在の識別子でセルを単純に比較します。
@ $mol_mem_key selected( id : { row : number , col : string } , next? : boolean ) { return this.Cell( this.current( next ? id : undefined ) ) === this.Cell( id ) }
もちろん、 true
selected()
渡される場合、新しい識別子が現在の識別子として設定され、セルの比較もtrue
。
最後の仕上げ-セルを選択するときに、そのセルから値エディター自体にフォーカスを移すとよいでしょう。
@ $mol_mem current( next? : { row : number , col : string } ) { new $mol_defer( ()=> this.Edit().focused( true ) ) return next || super.current() }
ここでは、 $ mol_deferを使用して、現在のセルの識別子が変更されるたびにエディターにフォーカスを移動する遅延タスクを設定します。 保留中のタスクは同じアニメーションフレームで実行されるため、ユーザーはリフォーカスによるちらつきを見ることができません。 フォーカスをすぐに移動すると、エディターのフォーカス状態にサブスクライブし、フォーカスを移動すると、現在のセルの識別子もリセットされますが、これはもちろん必要ありません。
キーボードナビゲーション
マウスをセルに突っ込んでセル間を移動するのはあまり便利ではありません。 キーボードの矢印の方が速くなります。 従来、スプレッドシートにはナビゲーションモードと編集モードの2つのモードがあります。 それらを絶えず切り替えることも厄介です。 したがって、ナイトと一緒に動き、編集とナビゲーションを組み合わせます。 フォーカスは常にセル編集パネルに残りますが、Altキーが押されたときに矢印を押すと、編集されたセルが隣のセルに変更されます。 このようなトリックには、プラグインコンポーネントである特別なコンポーネント$ mol_navがあります。
$ molには3種類のコンポーネントがあります。
- DOMノードを作成し、その状態を制御する通常のコンポーネント。
- domノードを作成せず、渡されたコンポーネントのdomノードを使用して動作/表示を追加するファントムコンポーネント 。
- domノードも作成せず、所有者コンポーネントのdomノードを使用して動作/表示を追加するプラグインコンポーネント 。
plugins()
. , :
plugins / <= Nav $mol_nav mod_alt true keys_x <= col_ids / keys_y <= row_ids / current_x?val <=> current_col?val \A current_y?val <=> current_row?val 1
, , , . current_col()
current_row()
, current()
:
current_row( next? : number ) { return this.current( next === undefined ? undefined : { ... this.current() , row : next } ).row } current_col( next? : number ) { return this.current( next === undefined ? undefined : { ... this.current() , col : next } ).col }
, Alt+Right
, , , .
コピーアンドペースト
, td
dom-, . ctrl
, . , Tab Separated Values , . :
event * paste?event <=> paste?event null
:
paste( event? : ClipboardEvent ) { const table = event.clipboardData.getData( 'text/plain' ).trim().split( '\n' ).map( row => row.split( '\t' ) ) as string[][] if( table.length === 1 && table[0].length === 1 ) return const anchor = this.current() const row_start = anchor.row const col_start = this.string2number( anchor.col ) const patch = {} for( let row in table ) { for( let col in table[ row ] ) { const id = `${ this.number2string( col_start + Number( col ) ) }${ row_start + Number( row ) }` patch[ id ] = table[ row ][ col ] } } this.formulas( patch ) event.preventDefault() }
, — , Microsoft Excel LibreOffice Calc.
— . . . data-uri data:text/csv;charset=utf-8,{'url- }
. CSV Microsoft Excel :
- .
- .
download_generate( event? : Event ) { const table : string[][] = [] const dims = this.dimensions() for( let row = 1 ; row < dims.rows ; ++ row ) { const row_data = [] as any[] table.push( row_data ) for( let col = 0 ; col < dims.cols ; ++ col ) { row_data[ col ] = String( this.result({ row , col : this.number2string( col ) }) ) } } const content = table.map( row => row.map( val => `"${ val.replace( /"/g , '""' ) }"` ).join( ',' ) ).join( '\n' ) this.download_uri( `data:text/csv;charset=utf-8,${ encodeURIComponent( content ) }` ) $mol_defer.run() }
, , dom- . , , .
— , , . , .
. — , . =
, , , .
— , , JavaScript JS . , , - , $mol_func_sandbox , JavaScript :
@ $mol_mem sandbox() { return new $mol_func_sandbox( Math , { 'formula' : this.formula.bind( this ) , 'result' : this.result.bind( this ) , } ) }
, , : .
, .
@ $mol_mem_key func( id : { row : number , col : string } ) { const formula = this.formula( id ) if( formula[0] !== '=' ) return ()=> formula const code = 'return ' + formula.slice( 1 ) .replace( /@([AZ]+)([0-9]+)\b/g , 'formula({ row : $2 , col : "$1" })' ) .replace( /\b([AZ]+)([0-9]+)\b/g , 'result({ row : $2 , col : "$1" })' ) return this.sandbox().eval( code ) }
result
— . , , AB34
, result
. , , , : @AB34
. — , , , .
— result()
:
@ $mol_mem_key result( id : { row : number , col : string } ) { const res = this.func( id ).call() if( res === undefined ) return '' if( res === '' ) return '' if( isNaN( res ) ) return res return Number( res ) }
undefined
, .
. $mol_app_calc . . - . . だから...