diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj
index 98b8b41c8..eca1d024d 100644
--- a/CrossPlatformModules.csproj
+++ b/CrossPlatformModules.csproj
@@ -118,8 +118,12 @@
+
+
+ page17.xml
+
@@ -578,6 +582,10 @@
+
+ Designer
+
+
diff --git a/apps/tests/pages/page16.ts b/apps/tests/pages/page16.ts
new file mode 100644
index 000000000..3d66f18b4
--- /dev/null
+++ b/apps/tests/pages/page16.ts
@@ -0,0 +1,55 @@
+import pageModule = require("ui/page");
+import buttonModule = require("ui/button");
+import stackModule = require("ui/layouts/stack-layout");
+import frame = require("ui/frame");
+
+export function createPage() {
+ var page = new pageModule.Page();
+
+ var iconItem = new pageModule.MenuItem();
+ iconItem.text = "TEST";
+
+ iconItem.icon = "~/app" + "/tests" + "/test-icon.png"; // use + to stop regex replace during build
+ iconItem.on("tap", () => {
+ console.log("Icon item tapped");
+ });
+ page.optionsMenu.addItem(iconItem);
+
+ var textItem = new pageModule.MenuItem();
+ textItem.text = "SAVE";
+ textItem.on("tap", () => {
+ console.log("Save item tapped");
+ });
+ page.optionsMenu.addItem(textItem);
+
+ var stackLayout = new stackModule.StackLayout();
+ var count = 0;
+ var btn1 = new buttonModule.Button();
+ btn1.text = "add item";
+ btn1.on("tap", () => {
+ console.log("adding menu item");
+
+ var newItem = new pageModule.MenuItem();
+ var text = "item " + count;
+ newItem.text = text
+ newItem.on("tap", () => {
+ console.log("ITEM [" + text + "] tapped");
+ });
+ page.optionsMenu.addItem(newItem);
+ count++;
+ });
+
+ stackLayout.addChild(btn1);
+
+ var btn2 = new buttonModule.Button();
+ btn2.text = "navigate";
+ btn2.on("tap", () => {
+ var nextPage = "app/tests/pages/page16";
+ frame.topmost().navigate(nextPage);
+ });
+
+ stackLayout.addChild(btn2);
+
+ page.content = stackLayout;
+ return page;
+}
\ No newline at end of file
diff --git a/apps/tests/pages/page17.ts b/apps/tests/pages/page17.ts
new file mode 100644
index 000000000..6390bf553
--- /dev/null
+++ b/apps/tests/pages/page17.ts
@@ -0,0 +1,20 @@
+import observable = require("data/observable");
+import pages = require("ui/page");
+
+// Event handler for Page "loaded" event attached in main-page.xml
+export function pageLoaded(args: observable.EventData) {
+ // Get the event sender
+ var page = args.object;
+
+ var textItem = new pages.MenuItem();
+ textItem.text = "from loaded";
+ textItem.on("tap", () => {
+ console.log("item added in page.loaded tapped!!!");
+ });
+ page.optionsMenu.addItem(textItem);
+}
+
+export function optionTap(args) {
+ console.log("item added form XML tapped!!!");
+}
+
diff --git a/apps/tests/pages/page17.xml b/apps/tests/pages/page17.xml
new file mode 100644
index 000000000..fe7e9d1f2
--- /dev/null
+++ b/apps/tests/pages/page17.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/tests/test-icon.png b/apps/tests/test-icon.png
new file mode 100644
index 000000000..add1ce15d
Binary files /dev/null and b/apps/tests/test-icon.png differ
diff --git a/ui/builder/component-builder.ts b/ui/builder/component-builder.ts
index ed1083bf0..86d8f4e6c 100644
--- a/ui/builder/component-builder.ts
+++ b/ui/builder/component-builder.ts
@@ -37,6 +37,7 @@ var MODULES = {
"TimePicker": "ui/time-picker",
"DatePicker": "ui/date-picker",
"ListPicker": "ui/list-picker",
+ "MenuItem": "ui/page",
};
var ROW = "row";
diff --git a/ui/frame/frame-common.ts b/ui/frame/frame-common.ts
index 3a07a5041..7122f7d76 100644
--- a/ui/frame/frame-common.ts
+++ b/ui/frame/frame-common.ts
@@ -370,6 +370,10 @@ export class Frame extends view.CustomLayoutView implements definition.Frame {
public _removeViewFromNativeVisualTree(child: view.View): void {
child._isAddedToNativeVisualTree = false;
}
+
+ public _invalidateOptionsMenu() {
+ //
+ }
}
var _topmost = function (): Frame {
diff --git a/ui/frame/frame.android.ts b/ui/frame/frame.android.ts
index 5c65ed5d8..dee5d7fc6 100644
--- a/ui/frame/frame.android.ts
+++ b/ui/frame/frame.android.ts
@@ -6,6 +6,7 @@ import observable = require("data/observable");
import utils = require("utils/utils");
import view = require("ui/core/view");
import application = require("application");
+import imageSource = require("image-source");
declare var exports;
require("utils/module-merge").merge(frameCommon, exports);
@@ -37,6 +38,7 @@ class PageFragmentBody extends android.app.Fragment {
onCreate(savedInstanceState: android.os.Bundle) {
super.onCreate(savedInstanceState);
trace.write(this.getTag() + ".onCreate(); savedInstanceState: " + savedInstanceState, trace.categories.NativeLifecycle);
+ super.setHasOptionsMenu(true);
}
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 {
super.onDetach();
trace.write(this.getTag() + ".onDetach();", trace.categories.NativeLifecycle);
}
+
+ onCreateOptionsMenu(menu: android.view.IMenu, inflater: android.view.MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+
+ var page: pages.Page = this.entry.resolvedPage;
+ var items = page.optionsMenu.getItems();
+
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ var menuItem = menu.add(android.view.Menu.NONE, i, item.priority, item.text);
+ if (item.icon) {
+ var img = imageSource.fromFile(item.icon);
+ var drawable = new android.graphics.drawable.BitmapDrawable(img.android);
+ menuItem.setIcon(drawable);
+ }
+
+ menuItem.setShowAsAction(android.view.MenuItem.SHOW_AS_ACTION_ALWAYS);
+ }
+ }
+
+ onOptionsItemSelected(item: android.view.IMenuItem) {
+ var page: pages.Page = this.entry.resolvedPage;
+ var itemId = item.getItemId();
+
+ var menuItem = page.optionsMenu.getItemAt(itemId);
+ if (menuItem) {
+ menuItem._raiseTap();
+ return true;
+ }
+
+ super.onOptionsItemSelected(item);
+ }
}
function onFragmentShown(fragment: PageFragmentBody) {
@@ -314,6 +348,12 @@ export class Frame extends frameCommon.Frame {
public _clearAndroidReference() {
// we should keep the reference to underlying native object, since frame can contain many pages.
}
+
+ public _invalidateOptionsMenu() {
+ if (this.android && this.android.activity) {
+ this.android.activity.invalidateOptionsMenu();
+ }
+ }
}
declare module com {
diff --git a/ui/frame/frame.d.ts b/ui/frame/frame.d.ts
index 3829c3026..880c82567 100644
--- a/ui/frame/frame.d.ts
+++ b/ui/frame/frame.d.ts
@@ -84,6 +84,7 @@ declare module "ui/frame" {
//@private
_processNavigationQueue(page: pages.Page);
+ _invalidateOptionsMenu();
//@endprivate
}
diff --git a/ui/page/page-common.ts b/ui/page/page-common.ts
index cb2cca780..3dcf09a53 100644
--- a/ui/page/page-common.ts
+++ b/ui/page/page-common.ts
@@ -5,19 +5,30 @@ import frame = require("ui/frame");
import styleScope = require("ui/styling/style-scope");
import fs = require("file-system");
import fileSystemAccess = require("file-system/file-system-access");
+import trace = require("trace");
+import observable = require("data/observable");
+
+var OPTIONS_MENU = "optionsMenu";
export module knownEvents {
export var navigatedTo = "navigatedTo";
+ export var tap = "tap";
}
-export class Page extends contentView.ContentView implements dts.Page {
+export module knownCollections {
+ export var optionsMenu = "optionsMenu";
+}
+
+export class Page extends contentView.ContentView implements dts.Page, view.AddArrayFromBuilder {
private _navigationContext: any;
private _cssApplied: boolean;
private _styleScope: styleScope.StyleScope = new styleScope.StyleScope();
+ private _optionsMenu: OptionsMenu;
constructor(options?: dts.Options) {
super(options);
+ this._optionsMenu = new OptionsMenu(this);
}
public onLoaded() {
@@ -40,6 +51,13 @@ export class Page extends contentView.ContentView implements dts.Page {
this._refreshCss();
}
+ get optionsMenu(): OptionsMenu {
+ return this._optionsMenu;
+ }
+ set optionsMenu(value: OptionsMenu) {
+ throw new Error("optionsMenu property is read-only");
+ }
+
private _refreshCss(): void {
if (this._cssApplied) {
this._resetCssValues();
@@ -131,4 +149,71 @@ export class Page extends contentView.ContentView implements dts.Page {
view.eachDescendant(this, resetCssValuesFunc);
}
+
+ public _addArrayFromBuilder(name: string, value: Array) {
+ if (name === OPTIONS_MENU) {
+ this.optionsMenu.setItems(value);
+ }
+ }
+}
+
+export class OptionsMenu implements dts.OptionsMenu {
+ private _items: Array