Skip to content

Commit 70756fb

Browse files
ErjanGavaljihshristov
authored and
hshristov
committed
OptionsMenu XML support
1 parent 71a01a5 commit 70756fb

File tree

12 files changed

+260
-5
lines changed

12 files changed

+260
-5
lines changed

CrossPlatformModules.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,12 @@
118118
<TypeScriptCompile Include="apps\tests\layouts\layout-helper.ts" />
119119
<TypeScriptCompile Include="apps\tests\layouts\wrap-layout-tests.ts" />
120120
<TypeScriptCompile Include="apps\tests\pages\page12.ts" />
121+
<TypeScriptCompile Include="apps\tests\pages\page16.ts" />
121122
<TypeScriptCompile Include="apps\tests\pages\page15.ts" />
122123
<TypeScriptCompile Include="apps\tests\pages\page13.ts" />
124+
<TypeScriptCompile Include="apps\tests\pages\page17.ts">
125+
<DependentUpon>page17.xml</DependentUpon>
126+
</TypeScriptCompile>
123127
<TypeScriptCompile Include="apps\tests\pages\property-bindings.ts" />
124128
<TypeScriptCompile Include="apps\tests\platform-tests.ts" />
125129
<TypeScriptCompile Include="apps\tests\fps-meter-tests.ts" />
@@ -578,6 +582,10 @@
578582
<Content Include="apps\tests\file-name-resolver-tests\files\test.ios.land.xml" />
579583
<Content Include="apps\tests\file-name-resolver-tests\files\test.android.minWH600.xml" />
580584
<Content Include="apps\tests\file-name-resolver-tests\files\test.xml" />
585+
<Content Include="apps\tests\pages\page17.xml">
586+
<SubType>Designer</SubType>
587+
</Content>
588+
<Content Include="apps\tests\test-icon.png" />
581589
<Content Include="apps\ui-tests-app\pages\i86.xml" />
582590
<Content Include="apps\template-blank\app.css" />
583591
<Content Include="apps\template-hello-world\app.css" />

apps/tests/pages/page16.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import pageModule = require("ui/page");
2+
import buttonModule = require("ui/button");
3+
import stackModule = require("ui/layouts/stack-layout");
4+
import frame = require("ui/frame");
5+
6+
export function createPage() {
7+
var page = new pageModule.Page();
8+
9+
var iconItem = new pageModule.MenuItem();
10+
iconItem.text = "TEST";
11+
12+
iconItem.icon = "~/app" + "/tests" + "/test-icon.png"; // use + to stop regex replace during build
13+
iconItem.on("tap", () => {
14+
console.log("Icon item tapped");
15+
});
16+
page.optionsMenu.addItem(iconItem);
17+
18+
var textItem = new pageModule.MenuItem();
19+
textItem.text = "SAVE";
20+
textItem.on("tap", () => {
21+
console.log("Save item tapped");
22+
});
23+
page.optionsMenu.addItem(textItem);
24+
25+
var stackLayout = new stackModule.StackLayout();
26+
var count = 0;
27+
var btn1 = new buttonModule.Button();
28+
btn1.text = "add item";
29+
btn1.on("tap", () => {
30+
console.log("adding menu item");
31+
32+
var newItem = new pageModule.MenuItem();
33+
var text = "item " + count;
34+
newItem.text = text
35+
newItem.on("tap", () => {
36+
console.log("ITEM [" + text + "] tapped");
37+
});
38+
page.optionsMenu.addItem(newItem);
39+
count++;
40+
});
41+
42+
stackLayout.addChild(btn1);
43+
44+
var btn2 = new buttonModule.Button();
45+
btn2.text = "navigate";
46+
btn2.on("tap", () => {
47+
var nextPage = "app/tests/pages/page16";
48+
frame.topmost().navigate(nextPage);
49+
});
50+
51+
stackLayout.addChild(btn2);
52+
53+
page.content = stackLayout;
54+
return page;
55+
}

apps/tests/pages/page17.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import observable = require("data/observable");
2+
import pages = require("ui/page");
3+
4+
// Event handler for Page "loaded" event attached in main-page.xml
5+
export function pageLoaded(args: observable.EventData) {
6+
// Get the event sender
7+
var page = <pages.Page>args.object;
8+
9+
var textItem = new pages.MenuItem();
10+
textItem.text = "from loaded";
11+
textItem.on("tap", () => {
12+
console.log("item added in page.loaded tapped!!!");
13+
});
14+
page.optionsMenu.addItem(textItem);
15+
}
16+
17+
export function optionTap(args) {
18+
console.log("item added form XML tapped!!!");
19+
}
20+

apps/tests/pages/page17.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Page loaded="pageLoaded">
2+
<Page.optionsMenu>
3+
<MenuItem text="test" tap="optionTap"/>
4+
<MenuItem text="with icon" tap="optionTap" icon="~/app/test-icon.png"/>
5+
</Page.optionsMenu>
6+
<StackLayout>
7+
<Button text="button" />
8+
</StackLayout>
9+
</Page>

apps/tests/test-icon.png

4.8 KB
Loading

ui/builder/component-builder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var MODULES = {
3737
"TimePicker": "ui/time-picker",
3838
"DatePicker": "ui/date-picker",
3939
"ListPicker": "ui/list-picker",
40+
"MenuItem": "ui/page",
4041
};
4142

4243
var ROW = "row";

ui/frame/frame-common.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ export class Frame extends view.CustomLayoutView implements definition.Frame {
370370
public _removeViewFromNativeVisualTree(child: view.View): void {
371371
child._isAddedToNativeVisualTree = false;
372372
}
373+
374+
public _invalidateOptionsMenu() {
375+
//
376+
}
373377
}
374378

375379
var _topmost = function (): Frame {

ui/frame/frame.android.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import observable = require("data/observable");
66
import utils = require("utils/utils");
77
import view = require("ui/core/view");
88
import application = require("application");
9+
import imageSource = require("image-source");
910

1011
declare var exports;
1112
require("utils/module-merge").merge(frameCommon, exports);
@@ -37,6 +38,7 @@ class PageFragmentBody extends android.app.Fragment {
3738
onCreate(savedInstanceState: android.os.Bundle) {
3839
super.onCreate(savedInstanceState);
3940
trace.write(this.getTag() + ".onCreate(); savedInstanceState: " + savedInstanceState, trace.categories.NativeLifecycle);
41+
super.setHasOptionsMenu(true);
4042
}
4143

4244
onCreateView(inflater: android.view.LayoutInflater, container: android.view.ViewGroup, savedInstanceState: android.os.Bundle): android.view.View {
@@ -132,6 +134,38 @@ class PageFragmentBody extends android.app.Fragment {
132134
super.onDetach();
133135
trace.write(this.getTag() + ".onDetach();", trace.categories.NativeLifecycle);
134136
}
137+
138+
onCreateOptionsMenu(menu: android.view.IMenu, inflater: android.view.MenuInflater) {
139+
super.onCreateOptionsMenu(menu, inflater);
140+
141+
var page: pages.Page = this.entry.resolvedPage;
142+
var items = page.optionsMenu.getItems();
143+
144+
for (var i = 0; i < items.length; i++) {
145+
var item = items[i];
146+
var menuItem = menu.add(android.view.Menu.NONE, i, item.priority, item.text);
147+
if (item.icon) {
148+
var img = imageSource.fromFile(item.icon);
149+
var drawable = new android.graphics.drawable.BitmapDrawable(img.android);
150+
menuItem.setIcon(drawable);
151+
}
152+
153+
menuItem.setShowAsAction(android.view.MenuItem.SHOW_AS_ACTION_ALWAYS);
154+
}
155+
}
156+
157+
onOptionsItemSelected(item: android.view.IMenuItem) {
158+
var page: pages.Page = this.entry.resolvedPage;
159+
var itemId = item.getItemId();
160+
161+
var menuItem = page.optionsMenu.getItemAt(itemId);
162+
if (menuItem) {
163+
menuItem._raiseTap();
164+
return true;
165+
}
166+
167+
super.onOptionsItemSelected(item);
168+
}
135169
}
136170

137171
function onFragmentShown(fragment: PageFragmentBody) {
@@ -314,6 +348,12 @@ export class Frame extends frameCommon.Frame {
314348
public _clearAndroidReference() {
315349
// we should keep the reference to underlying native object, since frame can contain many pages.
316350
}
351+
352+
public _invalidateOptionsMenu() {
353+
if (this.android && this.android.activity) {
354+
this.android.activity.invalidateOptionsMenu();
355+
}
356+
}
317357
}
318358

319359
declare module com {

ui/frame/frame.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ declare module "ui/frame" {
8484

8585
//@private
8686
_processNavigationQueue(page: pages.Page);
87+
_invalidateOptionsMenu();
8788
//@endprivate
8889
}
8990

ui/page/page-common.ts

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,30 @@ import frame = require("ui/frame");
55
import styleScope = require("ui/styling/style-scope");
66
import fs = require("file-system");
77
import fileSystemAccess = require("file-system/file-system-access");
8+
import trace = require("trace");
9+
import observable = require("data/observable");
10+
11+
var OPTIONS_MENU = "optionsMenu";
812

913
export module knownEvents {
1014
export var navigatedTo = "navigatedTo";
15+
export var tap = "tap";
16+
}
17+
18+
export module knownCollections {
19+
export var optionsMenu = "optionsMenu";
1120
}
1221

13-
export class Page extends contentView.ContentView implements dts.Page {
22+
export class Page extends contentView.ContentView implements dts.Page, view.AddArrayFromBuilder {
1423
private _navigationContext: any;
1524

1625
private _cssApplied: boolean;
1726
private _styleScope: styleScope.StyleScope = new styleScope.StyleScope();
27+
private _optionsMenu: OptionsMenu;
1828

1929
constructor(options?: dts.Options) {
2030
super(options);
31+
this._optionsMenu = new OptionsMenu(this);
2132
}
2233

2334
public onLoaded() {
@@ -40,6 +51,13 @@ export class Page extends contentView.ContentView implements dts.Page {
4051
this._refreshCss();
4152
}
4253

54+
get optionsMenu(): OptionsMenu {
55+
return this._optionsMenu;
56+
}
57+
set optionsMenu(value: OptionsMenu) {
58+
throw new Error("optionsMenu property is read-only");
59+
}
60+
4361
private _refreshCss(): void {
4462
if (this._cssApplied) {
4563
this._resetCssValues();
@@ -131,4 +149,71 @@ export class Page extends contentView.ContentView implements dts.Page {
131149
view.eachDescendant(this, resetCssValuesFunc);
132150

133151
}
152+
153+
public _addArrayFromBuilder(name: string, value: Array<any>) {
154+
if (name === OPTIONS_MENU) {
155+
this.optionsMenu.setItems(value);
156+
}
157+
}
158+
}
159+
160+
export class OptionsMenu implements dts.OptionsMenu {
161+
private _items: Array<MenuItem> = new Array<MenuItem>();
162+
private _page: Page;
163+
164+
constructor(page: Page) {
165+
this._page = page;
166+
}
167+
168+
public addItem(item: MenuItem): void {
169+
if (!item) {
170+
throw new Error("Cannot add empty item");
171+
}
172+
173+
this._items.push(item);
174+
this.invalidate();
175+
}
176+
177+
public removeItem(item: MenuItem): void {
178+
if (!item) {
179+
throw new Error("Cannot remove empty item");
180+
}
181+
182+
var itemIndex = this._items.indexOf(item);
183+
if (itemIndex < 0) {
184+
throw new Error("Cannot find item to remove");
185+
}
186+
187+
this._items.splice(itemIndex, 1);
188+
this.invalidate();
189+
}
190+
191+
public getItems(): Array<MenuItem> {
192+
return this._items.slice();
193+
}
194+
195+
public getItemAt(index: number): MenuItem {
196+
return this._items[index];
197+
}
198+
199+
public setItems(items: Array<MenuItem>) {
200+
this._items = items;
201+
this.invalidate();
202+
}
203+
204+
private invalidate() {
205+
if (this._page.frame) {
206+
this._page.frame._invalidateOptionsMenu();
207+
}
208+
}
209+
}
210+
211+
export class MenuItem extends observable.Observable implements dts.MenuItem {
212+
public text: string = "";
213+
public icon: string;
214+
public priority: number = 0;
215+
216+
public _raiseTap() {
217+
this._emit(knownEvents.tap);
218+
}
134219
}

ui/page/page.android.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import definition = require("ui/page");
33
import trace = require("trace");
44

5-
module.exports.knownEvents = pageCommon.knownEvents;
5+
declare var exports;
6+
require("utils/module-merge").merge(pageCommon, exports);
67

78
export class Page extends pageCommon.Page {
89
private _isBackNavigation = false;

0 commit comments

Comments
 (0)