<commit_before>"use strict";

const {assertIconClasses, open, replaceText, setup, wait} = require("./utils");
const FuzzyFinder = require("./utils/fuzzy-finder.js");
const TreeView    = require("./utils/tree-view.js");
const Tabs        = require("./utils/tabs.js");
const Options     = require("../lib/options.js");

describe("Interpreter directives", () => {
	const base = "name icon ";

	before(async () => {
		await setup("4.5-hashbang", {
			symlinks: [
				["astral1", "test-1"],
				["node",    "test-2"],
				["perl",    "test-3"],
				["crystal", "test-4"],
				["ruby",    "test-5"],
			],
			chmod: [
				["astral1",            0o755],
				["astral2",            0o755],
				["crystal",            0o755],
				["lambda.scm",         0o755],
				["nada4",              0o755],
				["node",               0o755],
				["not-python.py",      0o755],
				["perl",               0o755],
				["python",             0o755],
				["python2",            0o755],
				["rscript",            0o755],
				["ruby",               0o755],
				["ruby2",              0o755],
				["ruby3",              0o755],
				["sbcl",               0o755],
				["shell",              0o755],
				["shell.d",            0o755],
				["shell2",             0o755],
				["subdir/erlang.tho",  0o755],
				["subdir/haskell.tho", 0o755],
				["unknown1",           0o755],
			],
		});

		TreeView.refresh();
		TreeView.entries.should.not.be.empty;
		TreeView.entries.should.have.lengthOf.at.least(23);
		assertIconClasses(TreeView.entries, defaults);
		await wait(1500);
	});

	after(() => {
		FuzzyFinder.close();
		return Tabs.closeAll();
	});

	beforeEach(() => TreeView.refresh());

	const defaults = [
		["astral1",        "default-icon"],
		["astral2",        "default-icon"],
		["crystal",        "default-icon"],
		["lambda.scm",     "scheme-icon medium-red"],
		["nada",           "default-icon"],
		["nada2",          "default-icon"],
		["nada3",          "default-icon"],
		["nada4",          "default-icon"],
		["node",           "default-icon"],
		["not-python.py",  "python-icon dark-blue"],
		["perl",           "default-icon"],
		["python",         "default-icon"],
		["python2",        "default-icon"],
		["rscript",        "default-icon"],
		["ruby",           "default-icon"],
		["ruby2",          "default-icon"],
		["ruby3",          "default-icon"],
		["sbcl",           "default-icon"],
		["shell",          "default-icon"],
		["shell.d",        "dlang-icon medium-red"],
		["shell2",         "default-icon"],
		["unknown1",       "default-icon"],
		["unknown2",       "default-icon"],
	];

	const shebangedIcons = [
		["crystal",        base + "crystal-icon  medium-cyan"],
		["lambda.scm",     base + "cl-icon       medium-orange"],
		["node",           base + "js-icon       medium-yellow"],
		["not-python.py",  base + "perl-icon     medium-blue"],
		["perl",           base + "perl-icon     medium-blue"],
		["python",         base + "python-icon   dark-blue"],
		["python2",        base + "python-icon   dark-blue"],
		["rscript",        base + "r-icon        medium-blue"],
		["ruby",           base + "ruby-icon     medium-red"],
		["ruby2",          base + "ruby-icon     medium-red"],
		["ruby3",          base + "ruby-icon     medium-red"],
		["sbcl",           base + "cl-icon       medium-orange"],
		["shell",          base + "terminal-icon medium-purple"],
		["shell.d",        base + "terminal-icon medium-purple"],
		["shell2",         base + "terminal-icon medium-purple"],
	];

	when("a file contains a hashbang pattern", () => {
		it("displays the icon if pattern is valid", async () => {
			TreeView.expand("symlinks");
			await wait(500);
			assertIconClasses(TreeView.entries, shebangedIcons);
			assertIconClasses(TreeView.entries, [
				["symlinks/test-1", "icon-file-symlink-file medium-purple"],
				["symlinks/test-2", "icon-file-symlink-file medium-yellow"],
				["symlinks/test-3", "icon-file-symlink-file medium-blue"],
				["symlinks/test-4", "icon-file-symlink-file medium-cyan"],
				["symlinks/test-5", "icon-file-symlink-file medium-red"],
			]);
		});

		it("does nothing if the pattern is invalid", () => {
			assertIconClasses(TreeView.entries, [
				["nada",  base + "default-icon"],
				["nada2", base + "default-icon"],
				["nada3", base + "default-icon"],
				["nada4", base + "default-icon"],
			]);
		});

		it("identifies hashbangs in files containing multibyte characters", () => {
			assertIconClasses(TreeView.entries, [
				["astral1",  base + "emacs-icon    medium-purple"],
				["astral2",  base + "terminal-icon medium-purple"],
			]);
		});
	});

	unlessOnWindows(() => when("the hashbang is valid but matches nothing", () => {
		it("shows the terminal-icon if the file is executable", () => {
			TreeView.entries["unknown1"].should.have.classes(base + "terminal-icon medium-purple");
		});

		it("uses no icon if the file is not executable", () => {
			TreeView.entries["unknown2"].should.have.classes(base + "default-icon");
			TreeView.entries["unknown2"].should.not.have.classes("terminal-icon medium-purple");
		});
	}));

	when("the file's hashbang is modified", () => {
		let editor, checkpoint, crystal, crystalLink;

		const relink = async (delay = 100) => {
			await wait(delay);
			TreeView.refresh();
			crystal     = TreeView.entries["crystal"];
			crystalLink = TreeView.entries["symlinks/test-4"];
		};

		beforeEach(async () => {
			editor = await open("crystal");
			checkpoint = editor.createCheckpoint();
			await relink(10);
			crystal.should.have.classes("crystal-icon medium-cyan");
			crystalLink.should.have.classes("medium-cyan");
		});

		afterEach(async () => {
			editor = await open("crystal");
			if(editor){
				editor.revertToCheckpoint(checkpoint);
				await editor.save();
				await atom.commands.dispatch(editor.editorElement, "core:close");
			}
		});

		when("the new hashbang is valid", () => {
			it("updates its icon", async () => {
				await replaceText(/crystal$/m, "ruby");
				await relink();
				crystal.should.have.classes("ruby-icon medium-red");
				crystalLink.should.have.classes("medium-red");
				await replaceText(/ruby$/m, "mruby");
				await relink();
				crystal.should.have.class("mruby-icon").and.not.have.class("ruby-icon");
				crystalLink.should.have.classes("medium-red");
			});
		});

		when("a valid hashbang is invalidated by an edit", () => {
			it("removes the icon that was being displayed", async () => {
				crystal.should.have.classes("crystal-icon medium-cyan");
				await replaceText(/^/m, "A".repeat(20));
				await relink(300);
				crystal.should.have.classes("default-icon");
				await replaceText(/^A+/, "#!");
				await relink();
				crystal.should.have.classes("crystal-icon medium-cyan");
			});
		});
	});

	when("the Fuzzy-Finder lists files which contain hashbangs", function(){
		this.timeout(60000);
		it("updates its icons to show the interpreter icons", async () => {
			await FuzzyFinder.close();
			await wait(600);
			await FuzzyFinder.filter(".tho", 6000);
			FuzzyFinder.entries["subdir/erlang.tho"]  .should.not  .have.classes("default-icon");
			FuzzyFinder.entries["subdir/haskell.tho"] .should.not  .have.classes("default-icon");
			FuzzyFinder.entries["subdir/erlang.tho"]  .should      .have.classes("erlang-icon medium-red");
			FuzzyFinder.entries["subdir/haskell.tho"] .should      .have.classes("haskell-icon medium-purple");
		});

		it("shares what it finds with the Tree-View", async () => {
			await FuzzyFinder.close();
			TreeView.expand("subdir");
			TreeView.refresh();
			TreeView.entries["subdir/erlang.tho"].should.have.classes("erlang-icon medium-red");
			TreeView.entries["subdir/haskell.tho"].should.have.classes("haskell-icon medium-purple");
		});
	});

	when("the strategy is disabled", async () => {
		it("removes every icon that matched a hashbang", async () => {
			Options.set("hashbangs", false);
			assertIconClasses(FuzzyFinder.entries, defaults);
			await FuzzyFinder.filter(".tho", 500);
			FuzzyFinder.entries["subdir/erlang.tho"]   .should.have.classes("default-icon");
			FuzzyFinder.entries["subdir/haskell.tho"]  .should.have.classes("default-icon");
			FuzzyFinder.entries["subdir/erlang.tho"]   .should.not.have.classes("erlang-icon medium-red");
			FuzzyFinder.entries["subdir/haskell.tho"]  .should.not.have.classes("haskell-icon medium-purple");
		});

		when("the strategy is re-enabled", () => {
			it("shows the icons again", async () => {
				Options.set("hashbangs", true);
				await wait(100);
				TreeView.refresh();
				assertIconClasses(TreeView.entries, shebangedIcons);
				await FuzzyFinder.filter(".tho", 500);
				FuzzyFinder.entries["subdir/erlang.tho"]   .should.have.classes("erlang-icon medium-red");
				FuzzyFinder.entries["subdir/haskell.tho"]  .should.have.classes("haskell-icon medium-purple");
				FuzzyFinder.entries["subdir/erlang.tho"]   .should.not.have.classes("default-icon");
				FuzzyFinder.entries["subdir/haskell.tho"]  .should.not.have.classes("default-icon");
			});
		});
	});
});
<commit_msg>Tolerate missing nodes when running hashbang tests<commit_after>"use strict";

const {assertIconClasses, open, replaceText, setup, wait} = require("./utils");
const FuzzyFinder = require("./utils/fuzzy-finder.js");
const TreeView    = require("./utils/tree-view.js");
const Tabs        = require("./utils/tabs.js");
const Options     = require("../lib/options.js");

describe("Interpreter directives", () => {
	const base = "name icon ";

	before(async () => {
		await setup("4.5-hashbang", {
			symlinks: [
				["astral1", "test-1"],
				["node",    "test-2"],
				["perl",    "test-3"],
				["crystal", "test-4"],
				["ruby",    "test-5"],
			],
			chmod: [
				["astral1",            0o755],
				["astral2",            0o755],
				["crystal",            0o755],
				["lambda.scm",         0o755],
				["nada4",              0o755],
				["node",               0o755],
				["not-python.py",      0o755],
				["perl",               0o755],
				["python",             0o755],
				["python2",            0o755],
				["rscript",            0o755],
				["ruby",               0o755],
				["ruby2",              0o755],
				["ruby3",              0o755],
				["sbcl",               0o755],
				["shell",              0o755],
				["shell.d",            0o755],
				["shell2",             0o755],
				["subdir/erlang.tho",  0o755],
				["subdir/haskell.tho", 0o755],
				["unknown1",           0o755],
			],
		});

		TreeView.refresh();
		TreeView.entries.should.not.be.empty;
		TreeView.entries.should.have.lengthOf.at.least(23);
		assertIconClasses(TreeView.entries, defaults);
		await wait(1500);
	});

	after(() => {
		FuzzyFinder.close();
		return Tabs.closeAll();
	});

	beforeEach(() => TreeView.refresh());

	const defaults = [
		["astral1",        "default-icon"],
		["astral2",        "default-icon"],
		["crystal",        "default-icon"],
		["lambda.scm",     "scheme-icon medium-red"],
		["nada",           "default-icon"],
		["nada2",          "default-icon"],
		["nada3",          "default-icon"],
		["nada4",          "default-icon"],
		["node",           "default-icon"],
		["not-python.py",  "python-icon dark-blue"],
		["perl",           "default-icon"],
		["python",         "default-icon"],
		["python2",        "default-icon"],
		["rscript",        "default-icon"],
		["ruby",           "default-icon"],
		["ruby2",          "default-icon"],
		["ruby3",          "default-icon"],
		["sbcl",           "default-icon"],
		["shell",          "default-icon"],
		["shell.d",        "dlang-icon medium-red"],
		["shell2",         "default-icon"],
		["unknown1",       "default-icon"],
		["unknown2",       "default-icon"],
	];

	const shebangedIcons = [
		["crystal",        base + "crystal-icon  medium-cyan"],
		["lambda.scm",     base + "cl-icon       medium-orange"],
		["node",           base + "js-icon       medium-yellow"],
		["not-python.py",  base + "perl-icon     medium-blue"],
		["perl",           base + "perl-icon     medium-blue"],
		["python",         base + "python-icon   dark-blue"],
		["python2",        base + "python-icon   dark-blue"],
		["rscript",        base + "r-icon        medium-blue"],
		["ruby",           base + "ruby-icon     medium-red"],
		["ruby2",          base + "ruby-icon     medium-red"],
		["ruby3",          base + "ruby-icon     medium-red"],
		["sbcl",           base + "cl-icon       medium-orange"],
		["shell",          base + "terminal-icon medium-purple"],
		["shell.d",        base + "terminal-icon medium-purple"],
		["shell2",         base + "terminal-icon medium-purple"],
	];

	when("a file contains a hashbang pattern", () => {
		it("displays the icon if pattern is valid", async () => {
			TreeView.expand("symlinks");
			await wait(500);
			assertIconClasses(TreeView.entries, shebangedIcons);
			assertIconClasses(TreeView.entries, [
				["symlinks/test-1", "icon-file-symlink-file medium-purple"],
				["symlinks/test-2", "icon-file-symlink-file medium-yellow"],
				["symlinks/test-3", "icon-file-symlink-file medium-blue"],
				["symlinks/test-4", "icon-file-symlink-file medium-cyan"],
				["symlinks/test-5", "icon-file-symlink-file medium-red"],
			]);
		});

		it("does nothing if the pattern is invalid", () => {
			assertIconClasses(TreeView.entries, [
				["nada",  base + "default-icon"],
				["nada2", base + "default-icon"],
				["nada3", base + "default-icon"],
				["nada4", base + "default-icon"],
			]);
		});

		it("identifies hashbangs in files containing multibyte characters", () => {
			assertIconClasses(TreeView.entries, [
				["astral1",  base + "emacs-icon    medium-purple"],
				["astral2",  base + "terminal-icon medium-purple"],
			]);
		});
	});

	unlessOnWindows(() => when("the hashbang is valid but matches nothing", () => {
		it("shows the terminal-icon if the file is executable", () => {
			TreeView.entries["unknown1"].should.have.classes(base + "terminal-icon medium-purple");
		});

		it("uses no icon if the file is not executable", () => {
			TreeView.entries["unknown2"].should.have.classes(base + "default-icon");
			TreeView.entries["unknown2"].should.not.have.classes("terminal-icon medium-purple");
		});
	}));

	when("the file's hashbang is modified", () => {
		let editor, checkpoint, crystal, crystalLink;

		const relink = async (delay = 100) => {
			await wait(delay);
			TreeView.refresh();
			crystal     = TreeView.entries["crystal"];
			crystalLink = TreeView.entries["symlinks/test-4"];
		};

		beforeEach(async () => {
			editor = await open("crystal");
			checkpoint = editor.createCheckpoint();
			await relink(10);
			crystal.should.have.classes("crystal-icon medium-cyan");
			crystalLink.should.have.classes("medium-cyan");
		});

		afterEach(async () => {
			editor = await open("crystal");
			if(editor){
				editor.revertToCheckpoint(checkpoint);
				await editor.save();
				await atom.commands.dispatch(editor.editorElement, "core:close");
			}
		});

		when("the new hashbang is valid", () => {
			it("updates its icon", async () => {
				await replaceText(/crystal$/m, "ruby");
				await relink();
				crystal.should.have.classes("ruby-icon medium-red");
				crystalLink.should.have.classes("medium-red");
				await replaceText(/ruby$/m, "mruby");
				await relink();
				crystal.should.have.class("mruby-icon").and.not.have.class("ruby-icon");
				crystalLink.should.have.classes("medium-red");
			});
		});

		when("a valid hashbang is invalidated by an edit", () => {
			it("removes the icon that was being displayed", async () => {
				crystal.should.have.classes("crystal-icon medium-cyan");
				await replaceText(/^/m, "A".repeat(20));
				await relink(300);
				crystal.should.have.classes("default-icon");
				await replaceText(/^A+/, "#!");
				await relink();
				crystal.should.have.classes("crystal-icon medium-cyan");
			});
		});
	});

	when("the Fuzzy-Finder lists files which contain hashbangs", function(){
		this.timeout(60000);
		it("updates its icons to show the interpreter icons", async () => {
			await FuzzyFinder.close();
			await wait(600);
			await FuzzyFinder.filter(".tho", 6000);
			FuzzyFinder.entries["subdir/erlang.tho"]  .should.not  .have.classes("default-icon");
			FuzzyFinder.entries["subdir/haskell.tho"] .should.not  .have.classes("default-icon");
			FuzzyFinder.entries["subdir/erlang.tho"]  .should      .have.classes("erlang-icon medium-red");
			FuzzyFinder.entries["subdir/haskell.tho"] .should      .have.classes("haskell-icon medium-purple");
		});

		it("shares what it finds with the Tree-View", async () => {
			await FuzzyFinder.close();
			TreeView.expand("subdir");
			TreeView.refresh();
			TreeView.entries["subdir/erlang.tho"].should.have.classes("erlang-icon medium-red");
			TreeView.entries["subdir/haskell.tho"].should.have.classes("haskell-icon medium-purple");
		});
	});

	when("the strategy is disabled", async () => {
		it("removes every icon that matched a hashbang", async () => {
			Options.set("hashbangs", false);
			assertIconClasses(FuzzyFinder.entries, defaults, {ignoreMissing: true});
			await FuzzyFinder.filter(".tho", 500);
			FuzzyFinder.entries["subdir/erlang.tho"]   .should.have.classes("default-icon");
			FuzzyFinder.entries["subdir/haskell.tho"]  .should.have.classes("default-icon");
			FuzzyFinder.entries["subdir/erlang.tho"]   .should.not.have.classes("erlang-icon medium-red");
			FuzzyFinder.entries["subdir/haskell.tho"]  .should.not.have.classes("haskell-icon medium-purple");
		});

		when("the strategy is re-enabled", () => {
			it("shows the icons again", async () => {
				Options.set("hashbangs", true);
				await wait(100);
				TreeView.refresh();
				assertIconClasses(TreeView.entries, shebangedIcons, {ignoreMissing: true});
				await FuzzyFinder.filter(".tho", 500);
				FuzzyFinder.entries["subdir/erlang.tho"]   .should.have.classes("erlang-icon medium-red");
				FuzzyFinder.entries["subdir/haskell.tho"]  .should.have.classes("haskell-icon medium-purple");
				FuzzyFinder.entries["subdir/erlang.tho"]   .should.not.have.classes("default-icon");
				FuzzyFinder.entries["subdir/haskell.tho"]  .should.not.have.classes("default-icon");
			});
		});
	});
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule PushNotificationIOS
 * @flow
 */
'use strict';

const NativeEventEmitter = require('NativeEventEmitter');
const RCTPushNotificationManager = require('NativeModules').PushNotificationManager;
const invariant = require('fbjs/lib/invariant');

const PushNotificationEmitter = new NativeEventEmitter(RCTPushNotificationManager);

const _notifHandlers = new Map();

const DEVICE_NOTIF_EVENT = 'remoteNotificationReceived';
const NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
const DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived';

/**
 * Handle push notifications for your app, including permission handling and
 * icon badge number.
 *
 * To get up and running, [configure your notifications with Apple](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW6)
 * and your server-side system. To get an idea, [this is the Parse guide](https://parse.com/tutorials/ios-push-notifications).
 *
 * [Manually link](docs/linking-libraries-ios.html#manual-linking) the PushNotificationIOS library
 *
 * - Add the following to your Project: `node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj`
 * - Add the following to `Link Binary With Libraries`: `libRCTPushNotification.a`
 * - Add the following to your `Header Search Paths`:
 * `$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS` and set the search to `recursive`
 *
 * Finally, to enable support for `notification` and `register` events you need to augment your AppDelegate.
 *
 * At the top of your `AppDelegate.m`:
 *
 *   `#import "RCTPushNotificationManager.h"`
 *
 * And then in your AppDelegate implementation add the following:
 *
 *   ```
 *    // Required to register for notifications
 *    - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
 *    {
 *     [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
 *    }
 *    // Required for the register event.
 *    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
 *    {
 *     [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
 *    }
 *    // Required for the notification event.
 *    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
 *    {
 *     [RCTPushNotificationManager didReceiveRemoteNotification:notification];
 *    }
 *    // Required for the localNotification event.
 *    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
 *    {
 *     [RCTPushNotificationManager didReceiveLocalNotification:notification];
 *    }
 *    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
 *    {
 *      NSLog(@"%@", error);
 *    }
 *   ```
 */
class PushNotificationIOS {
  _data: Object;
  _alert: string | Object;
  _sound: string;
  _badgeCount: number;

  /**
   * Schedules the localNotification for immediate presentation.
   *
   * details is an object containing:
   *
   * - `alertBody` : The message displayed in the notification alert.
   * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view";
   * - `soundName` : The sound played when the notification is fired (optional).
   * - `category`  : The category of this notification, required for actionable notifications (optional).
   * - `userInfo`  : An optional object containing additional notification data.
   * - `applicationIconBadgeNumber` (optional) : The number to display as the app's icon badge. The default value of this property is 0, which means that no badge is displayed.
   */
  static presentLocalNotification(details: Object) {
    RCTPushNotificationManager.presentLocalNotification(details);
  }

  /**
   * Schedules the localNotification for future presentation.
   *
   * details is an object containing:
   *
   * - `fireDate` : The date and time when the system should deliver the notification.
   * - `alertBody` : The message displayed in the notification alert.
   * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view";
   * - `soundName` : The sound played when the notification is fired (optional).
   * - `category`  : The category of this notification, required for actionable notifications (optional).
   * - `userInfo` : An optional object containing additional notification data.
   * - `applicationIconBadgeNumber` (optional) : The number to display as the app's icon badge. Setting the number to 0 removes the icon badge.
   */
  static scheduleLocalNotification(details: Object) {
    RCTPushNotificationManager.scheduleLocalNotification(details);
  }

  /**
   * Cancels all scheduled localNotifications
   */
  static cancelAllLocalNotifications() {
    RCTPushNotificationManager.cancelAllLocalNotifications();
  }

  /**
   * Sets the badge number for the app icon on the home screen
   */
  static setApplicationIconBadgeNumber(number: number) {
    RCTPushNotificationManager.setApplicationIconBadgeNumber(number);
  }

  /**
   * Gets the current badge number for the app icon on the home screen
   */
  static getApplicationIconBadgeNumber(callback: Function) {
    RCTPushNotificationManager.getApplicationIconBadgeNumber(callback);
  }

  /**
   * Cancel local notifications.
   *
   * Optionally restricts the set of canceled notifications to those
   * notifications whose `userInfo` fields match the corresponding fields
   * in the `userInfo` argument.
   */
  static cancelLocalNotifications(userInfo: Object) {
    RCTPushNotificationManager.cancelLocalNotifications(userInfo);
  }

  /**
   * Gets the local notifications that are currently scheduled.
   */
  static getScheduledLocalNotifications(callback: Function) {
    RCTPushNotificationManager.getScheduledLocalNotifications(callback);
  }

  /**
   * Attaches a listener to remote or local notification events while the app is running
   * in the foreground or the background.
   *
   * Valid events are:
   *
   * - `notification` : Fired when a remote notification is received. The
   *   handler will be invoked with an instance of `PushNotificationIOS`.
   * - `localNotification` : Fired when a local notification is received. The
   *   handler will be invoked with an instance of `PushNotificationIOS`.
   * - `register`: Fired when the user registers for remote notifications. The
   *   handler will be invoked with a hex string representing the deviceToken.
   */
  static addEventListener(type: string, handler: Function) {
    invariant(
      type === 'notification' || type === 'register' || type === 'localNotification',
      'PushNotificationIOS only supports `notification`, `register` and `localNotification` events'
    );
    var listener;
    if (type === 'notification') {
      listener =  PushNotificationEmitter.addListener(
        DEVICE_NOTIF_EVENT,
        (notifData) => {
          handler(new PushNotificationIOS(notifData));
        }
      );
    } else if (type === 'localNotification') {
      listener = PushNotificationEmitter.addListener(
        DEVICE_LOCAL_NOTIF_EVENT,
        (notifData) => {
          handler(new PushNotificationIOS(notifData));
        }
      );
    } else if (type === 'register') {
      listener = PushNotificationEmitter.addListener(
        NOTIF_REGISTER_EVENT,
        (registrationInfo) => {
          handler(registrationInfo.deviceToken);
        }
      );
    }
    _notifHandlers.set(handler, listener);
  }

  /**
   * Removes the event listener. Do this in `componentWillUnmount` to prevent
   * memory leaks
   */
  static removeEventListener(type: string, handler: Function) {
    invariant(
      type === 'notification' || type === 'register' || type === 'localNotification',
      'PushNotificationIOS only supports `notification`, `register` and `localNotification` events'
    );
    var listener = _notifHandlers.get(handler);
    if (!listener) {
      return;
    }
    listener.remove();
    _notifHandlers.delete(handler);
  }

  /**
   * Requests notification permissions from iOS, prompting the user's
   * dialog box. By default, it will request all notification permissions, but
   * a subset of these can be requested by passing a map of requested
   * permissions.
   * The following permissions are supported:
   *
   *   - `alert`
   *   - `badge`
   *   - `sound`
   *
   * If a map is provided to the method, only the permissions with truthy values
   * will be requested.

   * This method returns a promise that will resolve when the user accepts,
   * rejects, or if the permissions were previously rejected. The promise
   * resolves to the current state of the permission.
   */
  static requestPermissions(permissions?: {
    alert?: boolean,
    badge?: boolean,
    sound?: boolean
  }): Promise<{
    alert: boolean,
    badge: boolean,
    sound: boolean
  }> {
    var requestedPermissions = {};
    if (permissions) {
      requestedPermissions = {
        alert: !!permissions.alert,
        badge: !!permissions.badge,
        sound: !!permissions.sound
      };
    } else {
      requestedPermissions = {
        alert: true,
        badge: true,
        sound: true
      };
    }
    return RCTPushNotificationManager.requestPermissions(requestedPermissions);
  }

  /**
   * Unregister for all remote notifications received via Apple Push Notification service.
   *
   * You should call this method in rare circumstances only, such as when a new version of
   * the app removes support for all types of remote notifications. Users can temporarily
   * prevent apps from receiving remote notifications through the Notifications section of
   * the Settings app. Apps unregistered through this method can always re-register.
   */
  static abandonPermissions() {
    RCTPushNotificationManager.abandonPermissions();
  }

  /**
   * See what push permissions are currently enabled. `callback` will be
   * invoked with a `permissions` object:
   *
   *  - `alert` :boolean
   *  - `badge` :boolean
   *  - `sound` :boolean
   */
  static checkPermissions(callback: Function) {
    invariant(
      typeof callback === 'function',
      'Must provide a valid callback'
    );
    RCTPushNotificationManager.checkPermissions(callback);
  }

  /**
   * If the app launch was triggered by a push notification,
   * it will give the notification object, otherwise it will give `null`
   */
  static getInitialNotification(): Promise<?PushNotificationIOS> {
    return RCTPushNotificationManager.getInitialNotification().then(notification => {
      return notification && new PushNotificationIOS(notification);
    });
  }

  /**
   * You will never need to instantiate `PushNotificationIOS` yourself.
   * Listening to the `notification` event and invoking
   * `getInitialNotification` is sufficient
   */
  constructor(nativeNotif: Object) {
    this._data = {};

    if (nativeNotif.remote) {
      // Extract data from Apple's `aps` dict as defined:
      // https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html
      Object.keys(nativeNotif).forEach((notifKey) => {
        var notifVal = nativeNotif[notifKey];
        if (notifKey === 'aps') {
          this._alert = notifVal.alert;
          this._sound = notifVal.sound;
          this._badgeCount = notifVal.badge;
        } else {
          this._data[notifKey] = notifVal;
        }
      });
    } else {
      // Local notifications aren't being sent down with `aps` dict.
      this._badgeCount = nativeNotif.applicationIconBadgeNumber;
      this._sound = nativeNotif.soundName;
      this._alert = nativeNotif.alertBody;
      this._data = nativeNotif.userInfo;
    }
  }

  /**
   * An alias for `getAlert` to get the notification's main message string
   */
  getMessage(): ?string | ?Object {
    // alias because "alert" is an ambiguous name
    return this._alert;
  }

  /**
   * Gets the sound string from the `aps` object
   */
  getSound(): ?string {
    return this._sound;
  }

  /**
   * Gets the notification's main message from the `aps` object
   */
  getAlert(): ?string | ?Object {
    return this._alert;
  }

  /**
   * Gets the badge count number from the `aps` object
   */
  getBadgeCount(): ?number {
    return this._badgeCount;
  }

  /**
   * Gets the data object on the notif
   */
  getData(): ?Object {
    return this._data;
  }
}

module.exports = PushNotificationIOS;
<commit_msg>check if remote notification via aps property<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule PushNotificationIOS
 * @flow
 */
'use strict';

const NativeEventEmitter = require('NativeEventEmitter');
const RCTPushNotificationManager = require('NativeModules').PushNotificationManager;
const invariant = require('fbjs/lib/invariant');

const PushNotificationEmitter = new NativeEventEmitter(RCTPushNotificationManager);

const _notifHandlers = new Map();

const DEVICE_NOTIF_EVENT = 'remoteNotificationReceived';
const NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
const DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived';

/**
 * Handle push notifications for your app, including permission handling and
 * icon badge number.
 *
 * To get up and running, [configure your notifications with Apple](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW6)
 * and your server-side system. To get an idea, [this is the Parse guide](https://parse.com/tutorials/ios-push-notifications).
 *
 * [Manually link](docs/linking-libraries-ios.html#manual-linking) the PushNotificationIOS library
 *
 * - Add the following to your Project: `node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj`
 * - Add the following to `Link Binary With Libraries`: `libRCTPushNotification.a`
 * - Add the following to your `Header Search Paths`:
 * `$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS` and set the search to `recursive`
 *
 * Finally, to enable support for `notification` and `register` events you need to augment your AppDelegate.
 *
 * At the top of your `AppDelegate.m`:
 *
 *   `#import "RCTPushNotificationManager.h"`
 *
 * And then in your AppDelegate implementation add the following:
 *
 *   ```
 *    // Required to register for notifications
 *    - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
 *    {
 *     [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
 *    }
 *    // Required for the register event.
 *    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
 *    {
 *     [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
 *    }
 *    // Required for the notification event.
 *    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
 *    {
 *     [RCTPushNotificationManager didReceiveRemoteNotification:notification];
 *    }
 *    // Required for the localNotification event.
 *    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
 *    {
 *     [RCTPushNotificationManager didReceiveLocalNotification:notification];
 *    }
 *    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
 *    {
 *      NSLog(@"%@", error);
 *    }
 *   ```
 */
class PushNotificationIOS {
  _data: Object;
  _alert: string | Object;
  _sound: string;
  _badgeCount: number;

  /**
   * Schedules the localNotification for immediate presentation.
   *
   * details is an object containing:
   *
   * - `alertBody` : The message displayed in the notification alert.
   * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view";
   * - `soundName` : The sound played when the notification is fired (optional).
   * - `category`  : The category of this notification, required for actionable notifications (optional).
   * - `userInfo`  : An optional object containing additional notification data.
   * - `applicationIconBadgeNumber` (optional) : The number to display as the app's icon badge. The default value of this property is 0, which means that no badge is displayed.
   */
  static presentLocalNotification(details: Object) {
    RCTPushNotificationManager.presentLocalNotification(details);
  }

  /**
   * Schedules the localNotification for future presentation.
   *
   * details is an object containing:
   *
   * - `fireDate` : The date and time when the system should deliver the notification.
   * - `alertBody` : The message displayed in the notification alert.
   * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view";
   * - `soundName` : The sound played when the notification is fired (optional).
   * - `category`  : The category of this notification, required for actionable notifications (optional).
   * - `userInfo` : An optional object containing additional notification data.
   * - `applicationIconBadgeNumber` (optional) : The number to display as the app's icon badge. Setting the number to 0 removes the icon badge.
   */
  static scheduleLocalNotification(details: Object) {
    RCTPushNotificationManager.scheduleLocalNotification(details);
  }

  /**
   * Cancels all scheduled localNotifications
   */
  static cancelAllLocalNotifications() {
    RCTPushNotificationManager.cancelAllLocalNotifications();
  }

  /**
   * Sets the badge number for the app icon on the home screen
   */
  static setApplicationIconBadgeNumber(number: number) {
    RCTPushNotificationManager.setApplicationIconBadgeNumber(number);
  }

  /**
   * Gets the current badge number for the app icon on the home screen
   */
  static getApplicationIconBadgeNumber(callback: Function) {
    RCTPushNotificationManager.getApplicationIconBadgeNumber(callback);
  }

  /**
   * Cancel local notifications.
   *
   * Optionally restricts the set of canceled notifications to those
   * notifications whose `userInfo` fields match the corresponding fields
   * in the `userInfo` argument.
   */
  static cancelLocalNotifications(userInfo: Object) {
    RCTPushNotificationManager.cancelLocalNotifications(userInfo);
  }

  /**
   * Gets the local notifications that are currently scheduled.
   */
  static getScheduledLocalNotifications(callback: Function) {
    RCTPushNotificationManager.getScheduledLocalNotifications(callback);
  }

  /**
   * Attaches a listener to remote or local notification events while the app is running
   * in the foreground or the background.
   *
   * Valid events are:
   *
   * - `notification` : Fired when a remote notification is received. The
   *   handler will be invoked with an instance of `PushNotificationIOS`.
   * - `localNotification` : Fired when a local notification is received. The
   *   handler will be invoked with an instance of `PushNotificationIOS`.
   * - `register`: Fired when the user registers for remote notifications. The
   *   handler will be invoked with a hex string representing the deviceToken.
   */
  static addEventListener(type: string, handler: Function) {
    invariant(
      type === 'notification' || type === 'register' || type === 'localNotification',
      'PushNotificationIOS only supports `notification`, `register` and `localNotification` events'
    );
    var listener;
    if (type === 'notification') {
      listener =  PushNotificationEmitter.addListener(
        DEVICE_NOTIF_EVENT,
        (notifData) => {
          handler(new PushNotificationIOS(notifData));
        }
      );
    } else if (type === 'localNotification') {
      listener = PushNotificationEmitter.addListener(
        DEVICE_LOCAL_NOTIF_EVENT,
        (notifData) => {
          handler(new PushNotificationIOS(notifData));
        }
      );
    } else if (type === 'register') {
      listener = PushNotificationEmitter.addListener(
        NOTIF_REGISTER_EVENT,
        (registrationInfo) => {
          handler(registrationInfo.deviceToken);
        }
      );
    }
    _notifHandlers.set(handler, listener);
  }

  /**
   * Removes the event listener. Do this in `componentWillUnmount` to prevent
   * memory leaks
   */
  static removeEventListener(type: string, handler: Function) {
    invariant(
      type === 'notification' || type === 'register' || type === 'localNotification',
      'PushNotificationIOS only supports `notification`, `register` and `localNotification` events'
    );
    var listener = _notifHandlers.get(handler);
    if (!listener) {
      return;
    }
    listener.remove();
    _notifHandlers.delete(handler);
  }

  /**
   * Requests notification permissions from iOS, prompting the user's
   * dialog box. By default, it will request all notification permissions, but
   * a subset of these can be requested by passing a map of requested
   * permissions.
   * The following permissions are supported:
   *
   *   - `alert`
   *   - `badge`
   *   - `sound`
   *
   * If a map is provided to the method, only the permissions with truthy values
   * will be requested.

   * This method returns a promise that will resolve when the user accepts,
   * rejects, or if the permissions were previously rejected. The promise
   * resolves to the current state of the permission.
   */
  static requestPermissions(permissions?: {
    alert?: boolean,
    badge?: boolean,
    sound?: boolean
  }): Promise<{
    alert: boolean,
    badge: boolean,
    sound: boolean
  }> {
    var requestedPermissions = {};
    if (permissions) {
      requestedPermissions = {
        alert: !!permissions.alert,
        badge: !!permissions.badge,
        sound: !!permissions.sound
      };
    } else {
      requestedPermissions = {
        alert: true,
        badge: true,
        sound: true
      };
    }
    return RCTPushNotificationManager.requestPermissions(requestedPermissions);
  }

  /**
   * Unregister for all remote notifications received via Apple Push Notification service.
   *
   * You should call this method in rare circumstances only, such as when a new version of
   * the app removes support for all types of remote notifications. Users can temporarily
   * prevent apps from receiving remote notifications through the Notifications section of
   * the Settings app. Apps unregistered through this method can always re-register.
   */
  static abandonPermissions() {
    RCTPushNotificationManager.abandonPermissions();
  }

  /**
   * See what push permissions are currently enabled. `callback` will be
   * invoked with a `permissions` object:
   *
   *  - `alert` :boolean
   *  - `badge` :boolean
   *  - `sound` :boolean
   */
  static checkPermissions(callback: Function) {
    invariant(
      typeof callback === 'function',
      'Must provide a valid callback'
    );
    RCTPushNotificationManager.checkPermissions(callback);
  }

  /**
   * If the app launch was triggered by a push notification,
   * it will give the notification object, otherwise it will give `null`
   */
  static getInitialNotification(): Promise<?PushNotificationIOS> {
    return RCTPushNotificationManager.getInitialNotification().then(notification => {
      return notification && new PushNotificationIOS(notification);
    });
  }

  /**
   * You will never need to instantiate `PushNotificationIOS` yourself.
   * Listening to the `notification` event and invoking
   * `getInitialNotification` is sufficient
   */
  constructor(nativeNotif: Object) {
    this._data = {};

    if (nativeNotif['aps']) {
      // Extract data from Apple's `aps` dict as defined:
      // https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html
      Object.keys(nativeNotif).forEach((notifKey) => {
        var notifVal = nativeNotif[notifKey];
        if (notifKey === 'aps') {
          this._alert = notifVal.alert;
          this._sound = notifVal.sound;
          this._badgeCount = notifVal.badge;
        } else {
          this._data[notifKey] = notifVal;
        }
      });
    } else {
      // Local notifications aren't being sent down with `aps` dict.
      this._badgeCount = nativeNotif.applicationIconBadgeNumber;
      this._sound = nativeNotif.soundName;
      this._alert = nativeNotif.alertBody;
      this._data = nativeNotif.userInfo;
    }
  }

  /**
   * An alias for `getAlert` to get the notification's main message string
   */
  getMessage(): ?string | ?Object {
    // alias because "alert" is an ambiguous name
    return this._alert;
  }

  /**
   * Gets the sound string from the `aps` object
   */
  getSound(): ?string {
    return this._sound;
  }

  /**
   * Gets the notification's main message from the `aps` object
   */
  getAlert(): ?string | ?Object {
    return this._alert;
  }

  /**
   * Gets the badge count number from the `aps` object
   */
  getBadgeCount(): ?number {
    return this._badgeCount;
  }

  /**
   * Gets the data object on the notif
   */
  getData(): ?Object {
    return this._data;
  }
}

module.exports = PushNotificationIOS;
<|endoftext|>


<commit_before>// Sample view

enyo.kind({
    name: "moon.sample.music.RelatedVideosNarrowSample",
    kind: "moon.Panel",
    titleAbove: "04",
    title: "Related Videos",
    titleBelow: "n Videos",
    components: [
        {
            name: "videoInfo",
            kind: "moon.DataList",
            scrollerOptions: { kind:"moon.Scroller", horizontal: "hidden" },
            fit: true,
            components: [
                {
                    kind: "moon.Item",
                    spotlight: true,
                    ontap: "changeName",
                    components: [
                        {
                            kind: "enyo.Table",
                            components: [
                                {
                                    components: [
                                        {
                                            components: [
                                                {
                                                    kind: "enyo.Image",
                                                    style: "width: 170px; height: 126px; padding-right: 30px;",
													bindings: [
														{from: ".model.coverUrl", to: ".src"}
													]
                                                }
                                            ],
                                            attributes: {rowspan: "2"}
                                        },
                                        {
                                            bindings: [
												{from: ".model.title", to: ".content"}
											]
                                        }
                                    ]
                                },
                                {
                                    components: [
                                        {
											bindings: [
												{from: ".model.time", to: ".content"}
											],
                                            classes: "moon-superscript"
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ],
    bindings: [
        {from: ".controller.videos", to: ".$.videoInfo.controller"}
    ]
});

// Sample model

enyo.ready(function (){
    var sampleModel = new enyo.Model({
        videos: new enyo.Collection([
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"}
        ])
    });

// Sample Application

    new enyo.Application({
        view: {
            classes: "enyo-unselectable moon",
            components: [
                {
                    kind: "moon.sample.music.RelatedVideosNarrowSample",
                    controller: ".app.controllers.videoController",
                    classes: "enyo-fit"
                }
            ]
        },
        controllers: [
            {
                name: "videoController",
                kind: "enyo.ModelController",
                model: sampleModel,
                changeVideoName: function(inSender, inEvent) {
                    inSender.parent.controller.set("title", "Good video");
                }
            }
        ]
    });
});
<commit_msg>GF-35117: Apply pilot-8 to Music.RelatedVidio sample<commit_after>// Sample view

enyo.kind({
    name: "moon.sample.music.RelatedVideosNarrowSample",
    kind: "moon.Panel",
    titleAbove: "04",
    title: "Related Videos",
    titleBelow: "n Videos",
    components: [
        {
            name: "videoInfo",
            kind: "moon.DataList",
            scrollerOptions: { kind:"moon.Scroller", horizontal: "hidden" },
            fit: true,
            components: [
                {
                    bindings: [
                        {from: ".model.coverUrl", to: ".$.enyoImage.src"},
                        {from: ".model.title", to: ".$.imageTitle.content"},
                        {from: ".model.time", to: ".$.imageTime.content"}
                    ],
                    components: [
                        {kind: "moon.Item", ontap: "changeName", layoutKind: "FittableColumnsLayout", components: [
                            {name: "enyoImage", kind: "enyo.Image", style: "width: 170px; height: 126px; padding-right: 30px;"},
                            {components: [
                                {name: "imageTitle"},
                                {name: "imageTime", classes: "moon-superscript"}
                            ]}
                        ]}
                    ]
                }
            ]
        }
    ],
    bindings: [
        {from: ".controller.videos", to: ".$.videoInfo.controller"}
    ]
});

// Sample model

enyo.ready(function (){
    var sampleModel = new enyo.Model({
        videos: new enyo.Collection([
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"},
            {coverUrl: "../assets/default-movie.png", title: "video title", time: "3:40"}
        ])
    });

// Sample Application

    new enyo.Application({
        view: {
            classes: "enyo-unselectable moon",
            components: [
                {
                    kind: "moon.sample.music.RelatedVideosNarrowSample",
                    controller: ".app.controllers.videoController",
                    classes: "enyo-fit"
                }
            ]
        },
        controllers: [
            {
                name: "videoController",
                kind: "enyo.ModelController",
                model: sampleModel,
                changeVideoName: function(inSender, inEvent) {
                    inSender.parent.controller.set("title", "Good video");
                }
            }
        ]
    });
});
<|endoftext|>


<commit_before>var mag = (function(mag, configs, document, undefined) {

  var ELEMENT_NODE = 1
  var cached = []

  // helper method to detect arrays -- silly javascript
    function _isArray(obj) {
      return Object.prototype.toString.call(obj) === '[object Array]'
    }

  var templates = {}

  // this is the entry point for this module, to fill the dom with data
    function fill(nodeList, data, key, index) {
      var node
      var parent
      var dataIsArray

      // there is nothing to do if there is nothing to fill
      if (!nodeList) return

      // remove all child nodes if there is no data
      if (data == null) data = {
        _text: ''
      }

      // CACHE
      // DIFF
      // CHANGE? then modify only the changes
      // KEYS for indentification

      // nodeList updates as the dom changes, so freeze it in an array
      var elements = nodeListToArray(nodeList)

      dataIsArray = _isArray(data)

      // match the number of nodes to the number of data elements
      if (dataIsArray) {

        if (templates[key + index] && elements.length === 0) {

          templates[key + index].parent.insertAdjacentHTML("beforeend", templates[key + index].node);
          elements = nodeListToArray(templates[key + index].parent.children)

        }

        if (elements.length === 0) {

          //=====================================================================
          // Warning: the following is a case where we could end up here: A page
          // that returns search results as the user is typing. If the results
          // are empty, then the child nodes that display the results will all
          // be removed. Then a subsequent attempt to fill in the search results
          // with data won't have any dom elements to clone.
          //=====================================================================

          // cannot fill empty nodeList with an array of data
          return
        }

        // clone the first node if more nodes are needed
        parent = elements[0].parentNode

        if (!templates[key + index]) {
          templates[key + index] = {
            node: elements[0].cloneNode(true).outerHTML,
            parent: parent
          }
          //remove from dom
          //parent.removeChild(elements[0])
        }

        while (elements.length < data.length) {
          if (templates[key + index]) {
            parent.insertAdjacentHTML("beforeend", templates[key + index].node)
            node = parent.lastChild
          } else {
            node = elements[0].cloneNode(true)
          }

          elements.push(node)
          if (parent) parent.appendChild(node)
        }

        // remove the last node until the number of nodes matches the data
        while (elements.length > data.length) {
          node = elements.pop()
          parent = node.parentNode
          if (parent) parent.removeChild(node)
        }
      }

      // now fill each node with the data
      for (var i = 0; i < elements.length; i++) {
        if (dataIsArray) {
          if (elements[i]) {
            fill(elements[i], data[i])
          }
        } else {
          fillNode(elements[i], data)
        }
      }
      return nodeList
    }

    function fillNode(node, data) {
      var attributes,
        attrValue,
        element,
        elements

        // ignore functions
      if (typeof data === 'function') {

        return
      }

      // if the value is a simple property wrap it in the attributes hash
      if (typeof data !== 'object') return fillNode(node, {
        _text: data
      })

      // find all the attributes
      for (var key in data) {
        var value = data[key]

        // null values are treated like empty strings
        if (value === undefined) value = ''

        // anything that starts with an underscore is an attribute
        if (key[0] === '_') {
          // store the properties to set them all at once
          // if (typeof value === 'string' || typeof value === 'number') {
          attributes = attributes || {}
          attributes[key.substr(1)] = value;
          // } else {
          //   throw new Error('Expected a string or number for "' + key +
          //                   '", got: "' + JSON.stringify(value) + '"');
          // }
        }
      }

      // fill in all the attributes
      if (attributes) {
        fillAttributes(node, attributes)
      }

      var index = 0
      // look for non-attribute keys and recurse into those elements
      for (var key in data) {
        var value = data[key]

        // only attributes start with an underscore
        if (key[0] !== '_') {
          elements = matchingElements(node, key);

          // function
          if (typeof value === 'function' && value.type == 'fun') {
            try {
              value = value()
            } catch (e) {}
          }

          // check for key replacement within the element
          //domElement.replaceWithin(node, key, value)

          fill(elements, value, key, index);

          index++
        }
      }
    }

    // freeze the NodeList into a real Array so it can't update as DOM changes
    function nodeListToArray(nodeList) {
      var temp;

      // wrap single item into an array for iteration
      // NOTE: can't use _isArray here, because it could be a NodeList (array-ish)
      if (nodeList.length == null) {
        nodeList = [nodeList];
      }

      // convert array-like object into a real array
      if (!_isArray(nodeList)) {
        temp = [];
        for (var i = 0; i < nodeList.length; i += 1) {
          // Note: occassionaly jsdom returns undefined elements in the NodeList
          if (nodeList[i]) {
            temp.push(nodeList[i]);
          }
        }
        nodeList = temp;
      }

      return nodeList;
    }

    // fill in the attributes on an element (setting text and html first)
    function fillAttributes(node, attributes) {

      // set html after setting text because html overrides text
      setText(node, attributes.text)
      setHtml(node, attributes.html)
      node._events = node._events || []

      // set the rest of the attributes
      for (var attrName in attributes) {

        // skip text and html, they've already been set
        if (attrName === 'text' || attrName === 'html') continue

        // events
        if (attrName.indexOf('on') == 0) {
          if (node._events.indexOf(attrName) !== -1) continue

          node.addEventListener(attrName.substr(2), attributes[attrName])
          node._events.push(attrName)

        } else {

          if (attrName == 'config') {

            // have we been here before?
            // does the element already exist in cache
            // useful to know if this is newly added
            var isNew = true

            // bind
            var callback = function(data, args) {
              return function() {
                return data.apply(data, args)
              }
            };

            configs.push(callback(attributes[attrName], [node, isNew]))
            continue;
          }

          if (attributes[attrName] === null) {
            node.removeAttribute(attrName)
          } else {
            node.setAttribute(attrName, attributes[attrName].toString())
          }
        }
      }
    }

    function setText(node, text) {
      var child
      var children

      // make sure that we have a node and text to insert
      if (!node || text == null) return

      // cache all of the child nodes
      if (!children) {
        children = [];
        for (var i = 0; i < node.childNodes.length; i += 1) {
          child = node.childNodes[i];
          if (child.nodeType === ELEMENT_NODE) {
            children.push(child)
          }
        }
      }

      // remove all of the children
      //WHY?
      while (node.firstChild) {
        node.removeChild(node.firstChild)
      }

      // SELECT|INPUT|TEXTAREA
      // now add the text
      if (node.nodeName.toLowerCase() === 'input') {
        // special case for input elements
        node.setAttribute('value', text.toString());
      } else {
        // create a new text node and stuff in the value
        node.appendChild(node.ownerDocument.createTextNode(text.toString()));
      }

      // reattach all the child nodes
      for (var i = 0; i < children.length; i += 1) {
        node.appendChild(children[i]);
      }
    }

    function setHtml(node, html) {
      if (!node || html == null) return;
      node.innerHTML = html;
      // CAN'T do below since it will append on every new call
      // node.insertAdjacentHTML("afterbegin", html)
    };

  //===========================================================================
  // TODO: Decide if the caching of element matching should be reintroduced.
  // The original implementation cached the lookup of elements, but it seems
  // like this will only be useful in cases where the same DOM elements are
  // getting filled mutliple times -- that seems like it would only happen
  // when someone is running performance benchmarks.
  //===========================================================================

  // find all of the matching elements (breadth-first)

  function matchingElements(node, key, nested) {
    var elements = childElements(node)
    var matches = []
    var keyName = key

    if (!(node.queryCache || {})[key]) {

      node.queryCache = (node.queryCache || {})[key] = [];

      var globalSearch = key[0] === '$'

      if (keyName[0] === '$') keyName = keyName.substr(1)

      // search all child elements for a match
      for (var i = 0; i < elements.length; i += 1) {
        if (elementMatcher(elements[i], keyName)) {
          matches.push(elements[i]);
        }
      }

      // if there is no match, recursively search the childNodes
      if (!matches.length || globalSearch) {
        for (var i = 0; i < elements.length; i++) {
          // NOTE: pass in a flag to prevent recursive calls from logging
          matches = matches.concat(matchingElements(elements[i], key, true))
          if (matches.length && !globalSearch) break
        }
      }

      if (!nested && !matches.length) log('FILL - Warning: no matches found for "' + key + '"');

      // return matches
      node.queryCache[key] = matches
    }
    return node.queryCache[key];
  }

  // return just the child nodes that are elements
  function childElements(node) {
    var children = node.childNodes
    var elements = []

    for (var i = 0; i < children.length; i += 1) {
      if (children[i].nodeType === ELEMENT_NODE) {
        elements.push(children[i])
      }
    }

    return elements
  }

  // match elements on tag, id, name, class name, data-bind, etc.
  function elementMatcher(element, key) {
    var paddedClass = ' ' + element.className + ' ';

    return (
      element.id === key ||
      paddedClass.indexOf(' ' + key + ' ') > -1 ||
      element.name === key ||
      element.nodeName.toLowerCase() === key.toLowerCase() ||
      element.getAttribute('data-bind') === key
    );
  }

  // attach fill to current context (in the browser this will be window.fill)
  // this.fill = fill;
  // this.configs = configs

  // the user can inject a logger function to get some feedback from fill
  function log(methodName) {
    return function() {
      if (typeof mag.logger === 'object') {
        mag.logger[methodName || 'log'].apply(mag.logger, arguments)
      }
    }
  }

  mag.fill = {
    fill: fill,
    log: log,
    configs: configs
  }
  return mag
}(window.mag || {}, [], document))
<commit_msg>Added caching removed logger<commit_after>var mag = (function(mag, configs, document, undefined) {

  var ELEMENT_NODE = 1
  var cached = []

  // helper method to detect arrays -- silly javascript
    function _isArray(obj) {
      return Object.prototype.toString.call(obj) === '[object Array]'
    }

    function getPathTo(element) {
      if (element.id !== '')
        return 'id("' + element.id + '")';
      if (element === document.body)
        return element.tagName;

      var ix = 0;
      var siblings = element.parentNode.childNodes;
      for (var i = 0; i < siblings.length; i++) {
        var sibling = siblings[i];
        if (sibling === element)
          return getPathTo(element.parentNode) + '/' + element.tagName + '[' + (ix + 1) + ']';
        if (sibling.nodeType === 1 && sibling.tagName === element.tagName)
          ix++;
      }
    }

  var templates = {}
  var cache = []
  // this is the entry point for this module, to fill the dom with data
    function fill(nodeList, data, key, index) {

      var node
      var parent
      var dataIsArray

      // there is nothing to do if there is nothing to fill
      if (!nodeList) return

      // remove all child nodes if there is no data
      if (data == null) data = {
        _text: ''
      }

      // CACHE
      // DIFF
      // CHANGE? then modify only the changes
      // KEYS for indentification

      // nodeList updates as the dom changes, so freeze it in an array
      var elements = nodeListToArray(nodeList)

      dataIsArray = _isArray(data)

      // match the number of nodes to the number of data elements
      if (dataIsArray) {

        if (templates[key + index] && elements.length === 0) {

          templates[key + index].parent.insertAdjacentHTML("beforeend", templates[key + index].node);
          elements = nodeListToArray(templates[key + index].parent.children)

        }

        if (elements.length === 0) {

          //=====================================================================
          // Warning: the following is a case where we could end up here: A page
          // that returns search results as the user is typing. If the results
          // are empty, then the child nodes that display the results will all
          // be removed. Then a subsequent attempt to fill in the search results
          // with data won't have any dom elements to clone.
          //=====================================================================

          // cannot fill empty nodeList with an array of data
          return
        }
        // clone the first node if more nodes are needed
        parent = elements[0].parentNode

        if (!templates[key + index]) {
          templates[key + index] = {
            node: elements[0].cloneNode(true).outerHTML,
            parent: parent
          }
          //remove from dom
          //parent.removeChild(elements[0])
        }

        while (elements.length < data.length) {
          if (templates[key + index]) {
            parent.insertAdjacentHTML("beforeend", templates[key + index].node)
            node = parent.lastChild
          } else {
            node = elements[0].cloneNode(true)
          }

          elements.push(node)
          if (parent) parent.appendChild(node)
        }

        // remove the last node until the number of nodes matches the data
        while (elements.length > data.length) {
          node = elements.pop()
          parent = node.parentNode
          if (parent) parent.removeChild(node)
        }
      }

      // now fill each node with the data
      for (var i = 0; i < elements.length; i++) {
        var p = getPathTo(elements[i])

        if (dataIsArray) {
          if (elements[i]) {
            if (cache[p] && cache[p] === JSON.stringify(data[i])) {
              //console.log('same a', p)
              continue
            }
            // if (cache[p]) console.log('changed a', p)

            fill(elements[i], data[i])
            cache[p] = JSON.stringify(data[i])
          }
        } else {
          if (cache[p] && cache[p] === JSON.stringify(data)) {
            // console.log('same', p)
            // TODO: fix issue when sub data
            // continue
          }
          // if (cache[p]) console.log('changed', p, JSON.stringify(data))

          fillNode(elements[i], data)
          cache[p] = JSON.stringify(data)
        }

      }
      return nodeList
    }

    function fillNode(node, data) {
      var attributes,
        attrValue,
        element,
        elements

        // ignore functions
      if (typeof data === 'function') {

        return
      }

      // if the value is a simple property wrap it in the attributes hash
      if (typeof data !== 'object') return fillNode(node, {
        _text: data
      })

      // find all the attributes
      for (var key in data) {
        var value = data[key]

        // null values are treated like empty strings
        if (value === undefined) value = ''

        // anything that starts with an underscore is an attribute
        if (key[0] === '_') {
          // store the properties to set them all at once
          // if (typeof value === 'string' || typeof value === 'number') {
          attributes = attributes || {}
          attributes[key.substr(1)] = value;
          // } else {
          //   throw new Error('Expected a string or number for "' + key +
          //                   '", got: "' + JSON.stringify(value) + '"');
          // }
        }
      }

      // fill in all the attributes
      if (attributes) {
        fillAttributes(node, attributes)
      }

      var index = 0
      // look for non-attribute keys and recurse into those elements
      for (var key in data) {
        var value = data[key]

        // only attributes start with an underscore
        if (key[0] !== '_') {
          elements = matchingElements(node, key);

          // function
          if (typeof value === 'function' && value.type == 'fun') {
            try {
              value = value()
            } catch (e) {}
          }

          fill(elements, value, key, index);

          index++
        }
      }
    }

    // freeze the NodeList into a real Array so it can't update as DOM changes
    function nodeListToArray(nodeList) {
      var temp;

      // wrap single item into an array for iteration
      // NOTE: can't use _isArray here, because it could be a NodeList (array-ish)
      if (nodeList.length == null) {
        nodeList = [nodeList];
      }

      // convert array-like object into a real array
      if (!_isArray(nodeList)) {
        temp = [];
        for (var i = 0; i < nodeList.length; i += 1) {
          // Note: occassionaly jsdom returns undefined elements in the NodeList
          if (nodeList[i]) {
            temp.push(nodeList[i]);
          }
        }
        nodeList = temp;
      }

      return nodeList;
    }

    // fill in the attributes on an element (setting text and html first)
    function fillAttributes(node, attributes) {

      // set html after setting text because html overrides text
      setText(node, attributes.text)
      setHtml(node, attributes.html)
      node._events = node._events || []

      // set the rest of the attributes
      for (var attrName in attributes) {

        // skip text and html, they've already been set
        if (attrName === 'text' || attrName === 'html') continue

        // events
        if (attrName.indexOf('on') == 0) {
          if (node._events.indexOf(attrName) !== -1) continue

          node.addEventListener(attrName.substr(2), attributes[attrName])
          node._events.push(attrName)

        } else {

          if (attrName == 'config') {

            // have we been here before?
            // does the element already exist in cache
            // useful to know if this is newly added
            var isNew = true

            // bind
            var callback = function(data, args) {
              return function() {
                return data.apply(data, args)
              }
            };

            configs.push(callback(attributes[attrName], [node, isNew]))
            continue;
          }

          if (attributes[attrName] === null) {
            node.removeAttribute(attrName)
          } else {
            node.setAttribute(attrName, attributes[attrName].toString())
          }
        }
      }
    }

    function setText(node, text) {
      var child
      var children

      // make sure that we have a node and text to insert
      if (!node || text == null) return

      // cache all of the child nodes
      if (!children) {
        children = [];
        for (var i = 0; i < node.childNodes.length; i += 1) {
          child = node.childNodes[i];
          if (child.nodeType === ELEMENT_NODE) {
            children.push(child)
          }
        }
      }

      // remove all of the children
      //WHY?
      while (node.firstChild) {
        node.removeChild(node.firstChild)
      }

      // SELECT|INPUT|TEXTAREA
      // now add the text
      if (node.nodeName.toLowerCase() === 'input') {
        // special case for input elements
        node.setAttribute('value', text.toString());
      } else {
        // create a new text node and stuff in the value
        node.appendChild(node.ownerDocument.createTextNode(text.toString()));
      }

      // reattach all the child nodes
      for (var i = 0; i < children.length; i += 1) {
        node.appendChild(children[i]);
      }
    }

    function setHtml(node, html) {
      if (!node || html == null) return;
      node.innerHTML = html;
      // CAN'T do below since it will append on every new call
      // node.insertAdjacentHTML("afterbegin", html)
    };

  //===========================================================================
  // TODO: Decide if the caching of element matching should be reintroduced.
  // The original implementation cached the lookup of elements, but it seems
  // like this will only be useful in cases where the same DOM elements are
  // getting filled mutliple times -- that seems like it would only happen
  // when someone is running performance benchmarks.
  //===========================================================================

  // find all of the matching elements (breadth-first)

  function matchingElements(node, key, nested) {
    var elements = childElements(node)
    var matches = []
    var keyName = key

    if (!(node.queryCache || {})[key]) {

      node.queryCache = (node.queryCache || {})[key] = [];

      var globalSearch = key[0] === '$'

      if (keyName[0] === '$') keyName = keyName.substr(1)

      // search all child elements for a match
      for (var i = 0; i < elements.length; i += 1) {
        if (elementMatcher(elements[i], keyName)) {
          matches.push(elements[i]);
        }
      }

      // if there is no match, recursively search the childNodes
      if (!matches.length || globalSearch) {
        for (var i = 0; i < elements.length; i++) {
          // NOTE: pass in a flag to prevent recursive calls from logging
          matches = matches.concat(matchingElements(elements[i], key, true))
          if (matches.length && !globalSearch) break
        }
      }

      if (!nested && !matches.length) {
        //'FILL - Warning: no matches found for "' + key + '"'
      }

      // return matches
      node.queryCache[key] = matches
    }
    return node.queryCache[key];
  }

  // return just the child nodes that are elements
  function childElements(node) {
    var children = node.childNodes
    var elements = []

    for (var i = 0; i < children.length; i += 1) {
      if (children[i].nodeType === ELEMENT_NODE) {
        elements.push(children[i])
      }
    }

    return elements
  }

  // match elements on tag, id, name, class name, data-bind, etc.
  function elementMatcher(element, key) {
    var paddedClass = ' ' + element.className + ' ';

    return (
      element.id === key ||
      paddedClass.indexOf(' ' + key + ' ') > -1 ||
      element.name === key ||
      element.nodeName.toLowerCase() === key.toLowerCase() ||
      element.getAttribute('data-bind') === key
    );
  }

  // attach fill to current context (in the browser this will be window.fill)
  // this.fill = fill;
  // this.configs = configs

  mag.fill = {
    fill: fill,
    configs: configs
  }
  return mag
}(window.mag || {}, [], document))
<|endoftext|>


<commit_before>/* global angular, console, _, Backbone, $ */
angular
	.module('boxie', ['ui','indx'])
	.config(['$routeProvider', function ($routeProvider) {

		console.log('CONFIG')
		$routeProvider
			.when('/', { templateUrl: 'partials/root.html', controller: 'RootCtrl' })
			.when('/obj/:obj_id', { templateUrl: 'partials/obj-detail.html', controller: 'ObjDetailCtrl' })
			.otherwise({ redirectTo: '/obj/1' });
	}])
	.controller('RootCtrl', function ($scope, client, utils, collection) {
		'use strict';

		var box,
			u = utils;

		var TreeObj = collection.Model.extend({
				initialize: function () {
					collection.Model.prototype.initialize.apply(this, arguments);
					var that = this;

					that.update();

					this.on('update change', function () {
						that.update();
					});
					this.icon();
					this.flatCollection = this.collection;
				},
				// identify and dereference links to objs within the attributes
				links_to: function (obj, links_to) {
					console.log('links to')
					var that = this,
						cache = false;
					links_to = links_to || [];
					if (typeof obj === "undefined") {
						obj = this.attributes;
						cache = true;
					}
					_(obj).each(function (v, k) {
						if (typeof v === "object") {
							if (v instanceof client.Obj) {
								obj[k] = that.flatCollection.get(v.id);
								links_to.push(v);
							} else {
								that.links_to(v, links_to);
							}
						}
					});
					if (cache) {
						this._links_to = links_to;
					}
					return links_to;
				},
				links_from: function () {
					var that = this;
					return this.flatCollection.filter(function (obj) {
						return obj.links_to().indexOf(that) > -1;
					});
				},
				is_root: function () {
					return this.links_from().length === 0;
				},
				update: function () {
					this.val_string = JSON.stringify(this.toJSON(), null, ' ');
					this._generate_attribute_array();
				},
				analyse: function () {
					$scope.curr_obj = this;
				},
				icon: function () {
					var keys = _(this.attributes).keys().sort(),
						cols = [[], [], [], [], []],
						colours = ['#F30021', '#FF8300', '#06799F', '#34D800', '#4F10AD', '#FFE500'],
						hash = function (key) {
							return {
								col: (_(key.split("")).reduce(function(memo, letter){return memo+letter.charCodeAt(0);},0))%5,
								colour: colours[-(_(key.split("").slice(1)).reduce(function(memo, letter){return memo-letter.charCodeAt(0);},0))%colours.length]
							}
						};
					console.log(cols)
					_.each(keys, function (key) {
						var h = hash(key);
						console.log(key, h)
						cols[h.col].push({ key: key, colour: h.colour });
					});
					_.each(cols, function (col) {
						console.log(col)
						var k = _.reduce(col, function (i, cell) { console.log(cell); return cell.key.length; }, 0) % 2;
						if (col.length > 0 && col.length % 2 === 0) {
							if (k) {
								col.push({ colour: 'transparent' });
							} else {
								col.unshift({ colour: 'transparent' });
							}
						}
						if (col.length > 4) {
							col = col.slice(0, 4);
						}
					})
					this._icon = cols;
					return cols;
				},
				_generate_attribute_array: function () {
					this.attribute_array = _.map(this.attributes, function (value, key) {
						var type = typeof value,
							is_array = false;
						if (_.isArray(value)) {
							is_array = true;
							type = 'array';
							/*value = _.map(value, function (value, i) {
								var type = typeof value;
								return { index: i, value: value, type: type };
							});*/
						}
						return { type: type, key: key, value: value, is_array: is_array };
					});
				}
			}),

			Objs = collection.Collection.extend({
				model: TreeObj,
				fetch: function () {
					var that = this,
						promise = $.Deferred(),
						ids = this.box.get_obj_ids(),
						objs = [],
						promises = _.map(ids, function (id) {
							var promise = $.Deferred();
							that.box.get_obj(id).then(function (obj) {
								objs.push(obj);
								promise.resolve();
							});
							return promise;
						});
					$.when.apply($, promises).then(function () {
						console.log('objs', objs);
						that.reset(objs);
						promise.resolve();
					});
					return promise;
				}
			}),

			ObjsTree = collection.Collection.extend({
				initialize: function (models, options) {
					this.flatCollection = new Objs(undefined, options);
					collection.Collection.prototype.initialize.apply(this, arguments);
				},
				fetch: function () {
					var that = this;
					return this.flatCollection.fetch().then(function () {
						that.buildTree();
					});
				},
				buildTree: function () {
					this.reset(this.flatCollection.select(function (obj) {
						return obj.is_root();
					}));
				}
			});

		var initialize = function () {
			console.log('init');
			$scope.objs = new ObjsTree(undefined, { box: box });
			$scope.objs.fetch();
			$scope.objs.on('update change', function () {
				console.log('fetched');
				u.safe_apply($scope);
			});
		};

		var pop = function (arr) {
			if (!_.isArray(arr)) { return arr; }
			return _.first(arr);
		};

		$scope.pop = pop;
		window.$scope = $scope;

		// watches the login stts for changes
		$scope.$watch('selected_box + selected_user', function() {
			if ($scope.selected_user && $scope.selected_box) {
				console.log('selected ', $scope.selected_user, $scope.selected_box);
				client.store.get_box($scope.selected_box).then(function(b) {
					box = b;
					window.box = box;
					initialize();
				}).fail(function(e) { u.error('error ', e); });
			}
		});

	});
<commit_msg>tweaks<commit_after>/* global angular, console, _, Backbone, $ */
angular
	.module('boxie', ['ui','indx'])
	.config(['$routeProvider', function ($routeProvider) {

		console.log('CONFIG')
		$routeProvider
			.when('/', { templateUrl: 'partials/root.html', controller: 'RootCtrl' })
			.when('/obj/:obj_id', { templateUrl: 'partials/obj-detail.html', controller: 'ObjDetailCtrl' })
			.otherwise({ redirectTo: '/obj/1' });
	}])
	.controller('RootCtrl', function ($scope, client, utils, collection) {
		'use strict';

		var box,
			u = utils;

		var TreeObj = collection.Model.extend({
				initialize: function () {
					collection.Model.prototype.initialize.apply(this, arguments);
					var that = this;

					that.update();

					this.on('update change', function () {
						that.update();
					});
					this.icon();
					this.flatCollection = this.collection;
				},
				// identify and dereference links to objs within the attributes
				links_to: function (obj, links_to) {
					//console.log('links to')
					var that = this,
						cache = false;
					links_to = links_to || [];
					if (typeof obj === "undefined") {
						obj = this.attributes;
						cache = true;
					}
					_(obj).each(function (v, k) {
						if (typeof v === "object") {
							if (v instanceof client.Obj) {
								obj[k] = that.flatCollection.get(v.id);
								links_to.push(v);
							} else {
								that.links_to(v, links_to);
							}
						}
					});
					if (cache) {
						this._links_to = links_to;
					}
					return links_to;
				},
				links_from: function () {
					var that = this;
					return this.flatCollection.filter(function (obj) {
						return obj.links_to().indexOf(that) > -1;
					});
				},
				is_root: function () {
					return this.links_from().length === 0;
				},
				update: function () {
					this.val_string = JSON.stringify(this.toJSON(), null, ' ');
					this._generate_attribute_array();
				},
				analyse: function () {
					$scope.curr_obj = this;
				},
				icon: function () {
					var keys = _(this.attributes).keys().sort(),
						cols = [[], [], [], [], []],
						colours = ['#F30021', '#FF8300', '#06799F', '#34D800', '#4F10AD', '#FFE500'],
						hash = function (key) {
							return {
								col: (_(key.split("")).reduce(function(memo, letter){return memo+letter.charCodeAt(0);},0))%5,
								colour: colours[-(_(key.split("").slice(1)).reduce(function(memo, letter){return memo-letter.charCodeAt(0);},0))%colours.length]
							}
						};
					_.each(keys, function (key) {
						var h = hash(key);
						console.log(key, h)
						cols[h.col].push({ key: key, colour: h.colour });
					});
					_.each(cols, function (col) {
						var k = _.reduce(col, function (i, cell) { console.log(cell); return cell.key.length; }, 0) % 2;
						if (col.length > 0 && col.length % 2 === 0) {
							if (k) {
								col.push({ colour: 'transparent' });
							} else {
								col.unshift({ colour: 'transparent' });
							}
						}
						if (col.length > 4) {
							col = col.slice(0, 4);
						}
					})
					this._icon = cols;
					return cols;
				},
				_generate_attribute_array: function () {
					this.attribute_array = _.map(this.attributes, function (value, key) {
						var type = typeof value,
							is_array = false;
						if (_.isArray(value)) {
							is_array = true;
							type = 'array';
							/*value = _.map(value, function (value, i) {
								var type = typeof value;
								return { index: i, value: value, type: type };
							});*/
						}
						return { type: type, key: key, value: value, is_array: is_array };
					});
				}
			}),

			Objs = collection.Collection.extend({
				model: TreeObj,
				fetch: function () {
					var that = this,
						promise = $.Deferred(),
						ids = this.box.get_obj_ids();
					that.box.get_obj(ids).then(function (objs) {
						console.log(objs);
						that.reset(objs);
						promise.resolve();
					});
					return promise;
				}
			}),

			ObjsTree = collection.Collection.extend({
				initialize: function (models, options) {
					this.flatCollection = new Objs(undefined, options);
					collection.Collection.prototype.initialize.apply(this, arguments);
				},
				fetch: function () {
					var that = this;
					return this.flatCollection.fetch().then(function () {
						that.buildTree();
					});
				},
				buildTree: function () {
					this.reset(this.flatCollection.select(function (obj) {
						return obj.is_root();
					}));
				}
			});

		var initialize = function () {
			console.log('init');
			$scope.objs = new ObjsTree(undefined, { box: box });
			$scope.objs.fetch();
			$scope.objs.on('update change', function () {
				console.log('fetched');
				u.safe_apply($scope);
			});
		};

		var pop = function (arr) {
			if (!_.isArray(arr)) { return arr; }
			return _.first(arr);
		};

		$scope.pop = pop;
		window.$scope = $scope;

		// watches the login stts for changes
		$scope.$watch('selected_box + selected_user', function() {
			if ($scope.selected_user && $scope.selected_box) {
				console.log('selected ', $scope.selected_user, $scope.selected_box);
				client.store.get_box($scope.selected_box).then(function(b) {
					box = b;
					window.box = box;
					initialize();
				}).fail(function(e) { u.error('error ', e); });
			}
		});

	});
<|endoftext|>


<commit_before>import { testAsync } from 'substance-test'
import { uuid } from 'substance'
import { DarFileStorage } from '../index'
import { promisify } from './shared/testHelpers'

// ATTENTION: these tests can not be run in the browser
// because implementation uses filesystem etc.

const path = require('path')
const fs = require('fs')
const fsExtra = require('fs-extra')

testAsync('Storage: reading a .dar file', async t => {
  let storageDir = _getTmpFolder()
  let darPath = path.join(process.cwd(), 'app-dist', 'templates', 'blank.dar')
  let storage = new DarFileStorage(storageDir)
  let rawArchive = await promisify(cb => {
    storage.read(darPath, cb)
  })
  t.deepEqual(Object.keys(rawArchive.resources), ['manifest.xml', 'manuscript.xml'], 'archive should contain correct resources')
  t.end()
})

testAsync('Storage: cloning a .dar file', async t => {
  let storageDir = _getTmpFolder()
  let storage = new DarFileStorage(storageDir)
  let darPath = path.join(process.cwd(), 'app-dist', 'templates', 'blank.dar')
  await promisify(cb => {
    storage.read(darPath, cb)
  })
  let tmpDir = _getTmpFolder()
  let newDarPath = path.join(tmpDir, 'blank_2.dar')
  await promisify(cb => {
    storage.clone(darPath, newDarPath, cb)
  })
  t.ok(fs.existsSync(newDarPath))
  let rawArchive = await promisify(cb => {
    storage._getRawArchive(newDarPath, cb)
  })
  let resourceNames = Object.keys(rawArchive.resources).sort()
  t.deepEqual(resourceNames, ['manifest.xml', 'manuscript.xml'], 'archive should contain correct resources')
  t.end()
})

testAsync('Storage: saving a .dar file', async t => {
  let storageDir = _getTmpFolder()
  let storage = new DarFileStorage(storageDir)
  let darPath = path.join(process.cwd(), 'app-dist', 'templates', 'blank.dar')
  await promisify(cb => {
    storage.read(darPath, cb)
  })
  // Note: creating a clone, so that the original dar does not get affected
  let tmpDir = _getTmpFolder()
  let newDarPath = path.join(tmpDir, 'blank_2.dar')
  await promisify(cb => {
    storage.clone(darPath, newDarPath, cb)
  })
  // TODO: ATM the write API does not validate the format of the given
  // rawArchive. It could do some checking and produce more understandable
  // errors.
  let update = {
    resources: {
      // TODO: storage does not do anything when encoding is not given
      // we should throw an error
      'manuscript.xml': { encoding: 'utf8', data: 'TEST' }
    }
  }
  await promisify(cb => {
    storage.write(newDarPath, update, cb)
  })
  let rawArchive = await promisify(cb => {
    storage._getRawArchive(newDarPath, cb)
  })
  t.equal(rawArchive.resources['manuscript.xml'].data, 'TEST', 'content should have been updated')
  t.end()
})

function _getTmpFolder () {
  let folder = path.join(process.cwd(), 'tmp', uuid())
  fsExtra.ensureDirSync(folder)
  return folder
}
<commit_msg>Add more Storage tests.<commit_after>import { testAsync } from 'substance-test'
import { uuid } from 'substance'
import { DarFileStorage } from '../index'
import { promisify } from './shared/testHelpers'

// ATTENTION: these tests can not be run in the browser
// because implementation uses filesystem etc.

/*
  TODO:
  - test loading and writing a dar with binaries
  - test loading and saving from and to a folder
  - test HttpStorageClient
  - test PersistendDocumentArchive
    - addDocument()
    - load(): 'version' handling
    - removeDocument()
    - saveAs()
    - blob related (missing in nodejs)
      - resolveUrl(): for blobs
      - _save()
*/

const path = require('path')
const fs = require('fs')
const fsExtra = require('fs-extra')

testAsync('Storage: reading a .dar file', async t => {
  let storageDir = _getTmpFolder()
  let darPath = path.join(process.cwd(), 'app-dist', 'templates', 'blank.dar')
  let storage = new DarFileStorage(storageDir)
  let rawArchive = await promisify(cb => {
    storage.read(darPath, cb)
  })
  t.deepEqual(Object.keys(rawArchive.resources), ['manifest.xml', 'manuscript.xml'], 'archive should contain correct resources')
  t.end()
})

testAsync('Storage: reading the kitchen-sink.dar', async t => {
  let storageDir = _getTmpFolder()
  let darPath = path.join(process.cwd(), 'app-dist', 'examples', 'kitchen-sink.dar')
  let storage = new DarFileStorage(storageDir)
  let rawArchive = await promisify(cb => {
    storage.read(darPath, cb)
  })
  t.ok(Boolean(rawArchive.resources['manifest.xml']), 'dar should contain manifest')
  t.ok(Boolean(rawArchive.resources['manuscript.xml']), 'dar should contain manuscript')
  t.end()
})

testAsync('Storage: cloning a .dar file', async t => {
  let storageDir = _getTmpFolder()
  let storage = new DarFileStorage(storageDir)
  let darPath = path.join(process.cwd(), 'app-dist', 'templates', 'blank.dar')
  await promisify(cb => {
    storage.read(darPath, cb)
  })
  let tmpDir = _getTmpFolder()
  let newDarPath = path.join(tmpDir, 'blank_2.dar')
  await promisify(cb => {
    storage.clone(darPath, newDarPath, cb)
  })
  t.ok(fs.existsSync(newDarPath))
  let rawArchive = await promisify(cb => {
    storage._getRawArchive(newDarPath, cb)
  })
  let resourceNames = Object.keys(rawArchive.resources).sort()
  t.deepEqual(resourceNames, ['manifest.xml', 'manuscript.xml'], 'archive should contain correct resources')
  t.end()
})

testAsync('Storage: cloning the kitchen-sink.dar', async t => {
  let storageDir = _getTmpFolder()
  let storage = new DarFileStorage(storageDir)
  let darPath = path.join(process.cwd(), 'app-dist', 'examples', 'kitchen-sink.dar')
  await promisify(cb => {
    storage.read(darPath, cb)
  })
  let tmpDir = _getTmpFolder()
  let newDarPath = path.join(tmpDir, 'kitchen-sink_2.dar')
  await promisify(cb => {
    storage.clone(darPath, newDarPath, cb)
  })
  t.ok(fs.existsSync(newDarPath))
  let rawArchive = await promisify(cb => {
    storage._getRawArchive(newDarPath, cb)
  })
  t.ok(Boolean(rawArchive.resources['manifest.xml']), 'dar should contain manifest')
  t.ok(Boolean(rawArchive.resources['manuscript.xml']), 'dar should contain manuscript')
  t.end()
})

testAsync('Storage: saving a .dar file', async t => {
  let storageDir = _getTmpFolder()
  let storage = new DarFileStorage(storageDir)
  let darPath = path.join(process.cwd(), 'app-dist', 'templates', 'blank.dar')
  await promisify(cb => {
    storage.read(darPath, cb)
  })
  // Note: creating a clone, so that the original dar does not get affected
  let tmpDir = _getTmpFolder()
  let newDarPath = path.join(tmpDir, 'blank_2.dar')
  await promisify(cb => {
    storage.clone(darPath, newDarPath, cb)
  })
  // TODO: ATM the write API does not validate the format of the given
  // rawArchive. It could do some checking and produce more understandable
  // errors.
  let update = {
    resources: {
      // TODO: storage does not do anything when encoding is not given
      // we should throw an error
      'manuscript.xml': { encoding: 'utf8', data: 'TEST' }
    }
  }
  await promisify(cb => {
    storage.write(newDarPath, update, cb)
  })
  let rawArchive = await promisify(cb => {
    storage._getRawArchive(newDarPath, cb)
  })
  t.equal(rawArchive.resources['manuscript.xml'].data, 'TEST', 'content should have been updated')
  t.end()
})

function _getTmpFolder () {
  let folder = path.join(process.cwd(), 'tmp', uuid())
  fsExtra.ensureDirSync(folder)
  return folder
}
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2019 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var resolve = require( 'resolve' ).sync;
var resolveParentPath = require( '@stdlib/fs/resolve-parent-path' ).sync;
var dirname = require( '@stdlib/utils/dirname' );

// MAIN //

/**
* Synchronously resolves the currently installed `@stdlib/stdlib` path.
*
* @private
* @throws {Error} unexpected error
* @returns {string} package path
*/
function path() {
	var opts;
	var main;
	var pdir;

	// Resolve the main `@stdlib/stdlib` entry point:
	opts = {
		'basedir': __dirname
	};
	main = resolve( '@stdlib/stdlib' );

	// Resolve the `package.json`:
	opts = {
		'dir': dirname( main )
	};
	pdir = resolveParentPath( 'package.json', opts );
	if ( pdir instanceof Error ) {
		throw pdir;
	}
	// Return the package path:
	return pdir;
}

// EXPORTS //

module.exports = path;
<commit_msg>Resolve from the root project directory<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2019 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

// MODULES //

var resolve = require( 'resolve' ).sync;
var resolveParentPath = require( '@stdlib/fs/resolve-parent-path' ).sync;
var dirname = require( '@stdlib/utils/dirname' );
var root = require( './root.js' );

// MAIN //

/**
* Synchronously resolves the currently installed `@stdlib/stdlib` path.
*
* @private
* @throws {Error} unexpected error
* @returns {string} package path
*/
function path() {
	var opts;
	var main;
	var pdir;

	// Resolve the main `@stdlib/stdlib` entry point:
	opts = {
		'basedir': root()
	};
	main = resolve( '@stdlib/stdlib' );

	// Resolve the `package.json`:
	opts = {
		'dir': dirname( main )
	};
	pdir = resolveParentPath( 'package.json', opts );
	if ( pdir instanceof Error ) {
		throw pdir;
	}
	// Return the package path:
	return pdir;
}

// EXPORTS //

module.exports = path;
<|endoftext|>