<commit_before>// @flow
import PhenylState from 'phenyl-state/jsnext'
import { createErrorResult } from 'phenyl-utils/jsnext'
import type { PhenylStateParams } from 'phenyl-state/jsnext'

import type {
  EntityClient,
  CommandResultOrError,
  DeleteCommand,
  MultiValuesCommandResultOrError,
  GetCommandResultOrError,
  Id,
  IdQuery,
  IdsQuery,
  InsertCommand,
  SingleInsertCommand,
  MultiInsertCommand,
  RequestData,
  ResponseData,
  QueryResultOrError,
  QueryStringParams,
  SingleQueryResultOrError,
  UpdateCommand,
  IdUpdateCommand,
  MultiUpdateCommand,
  WhereQuery,
} from 'phenyl-interfaces'

type MemoryClientParams = {
  phenylState?: PhenylStateParams,
}

// TODO: implement cooler one externally
function generateRandomStr(): string {
  return 'p' + Math.random().toString().split('.')[1] + 't' + new Date().getTime().toString()
}

export default class PhenylMemoryClient implements EntityClient {
  phenylState: PhenylState

  constructor(params: MemoryClientParams) {
    this.phenylState = new PhenylState(params.phenylState || {})
  }

  /**
   *
   */
  async find(query: WhereQuery): Promise<QueryResultOrError> {
    const entities = this.phenylState.find(query)
    return {
      ok: 1,
      values: entities
    }
  }

  /**
   *
   */
  async findOne(query: WhereQuery): Promise<SingleQueryResultOrError> {
    const entity = this.phenylState.findOne(query)
    if (entity == null) {
      return {
        ok: 0,
        type: 'NotFound',
        message: '"PhenylMemoryClient#findOne()" failed. Could not find any entity with the given query.',
      }
    }
    return {
      ok: 1,
      value: entity
    }
  }

  /**
   *
   */
  async get(query: IdQuery): Promise<SingleQueryResultOrError> {
    try {
      const entity = this.phenylState.get(query)
      return {
        ok: 1,
        value: entity
      }
    }
    catch (e) {
      if (e.constructor.name === 'Error') { // Error from PhenylState
        return {
          ok: 0,
          type: 'NotFound',
          message: `"PhenylMemoryClient#get()" failed. Could not find any entity with the given id: "${query.id}"`,
        }
      }
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async getByIds(query: IdsQuery): Promise<QueryResultOrError> {
    try {
      const entities = this.phenylState.getByIds(query)
      return {
        ok: 1,
        values: entities
      }
    }
    catch (e) {
      if (e.constructor.name === 'Error') { // Error from PhenylState
        return {
          ok: 0,
          type: 'NotFound',
          message: `"PhenylMemoryClient#getByIds()" failed. Some ids are not found. ids: "${query.ids.join(', ')}"`, // TODO: prevent from showing existing ids
        }
      }
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async insert(command: InsertCommand): Promise<CommandResultOrError> {
    if (command.values) {
      const result = await this.insertAndGetMulti(command)
      return result.ok
        ? { ok: 1, n: result.n }
        : result
    }
    const result = await this.insertAndGet(command)
    return result.ok
      ? { ok: 1, n: 1 }
      : result
  }

  /**
   *
   */
  async insertAndGet(command: SingleInsertCommand): Promise<GetCommandResultOrError> {
    const { entityName, value } = command
    const newValue = Object.assign({}, value, { id: generateRandomStr() })
    this.phenylState = this.phenylState.$register(entityName, newValue)
    return {
      ok: 1,
      n: 1,
      value: newValue,
    }
  }

  /**
   *
   */
  async insertAndGetMulti(command: MultiInsertCommand): Promise<MultiValuesCommandResultOrError> {
    const { entityName, values} = command
    const newValues = []
    for (const value of values) {
      const newValue = Object.assign({}, value, { id: generateRandomStr() })
      this.phenylState = this.phenylState.$register(entityName, newValue)
      newValues.push(newValue)
    }
    return {
      ok: 1,
      n: newValues.length,
      values: newValues,
    }
  }

  /**
   *
   */
  async update(command: UpdateCommand): Promise<CommandResultOrError> {
    try {
      this.phenylState = this.phenylState.$update(command)
      return { ok: 1, n: 1 }
    }
    catch (e) {
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async updateAndGet(command: IdUpdateCommand): Promise<GetCommandResultOrError> {
    const { entityName, id } = command
    try {
      this.phenylState = this.phenylState.$update(command)
      return { ok: 1, n: 1, value: this.phenylState.get({ entityName, id }) }
    }
    catch (e) {
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async updateAndFetch(command: MultiUpdateCommand): Promise<MultiValuesCommandResultOrError> {
    throw new Error(`Invalid response data: property name "updateAndFetch" is not found in response.`)
  }

  /**
   *
   */
  async delete(command: DeleteCommand): Promise<CommandResultOrError> {
    throw new Error(`Invalid response data: property name "delete" is not found in response.`)
  }
}
<commit_msg>[phenyl-memory-client] implements EntityClient<commit_after>// @flow
import PhenylState from 'phenyl-state/jsnext'
import { createErrorResult } from 'phenyl-utils/jsnext'
import type { PhenylStateParams } from 'phenyl-state/jsnext'

import type {
  EntityClient,
  CommandResultOrError,
  DeleteCommand,
  MultiValuesCommandResultOrError,
  GetCommandResultOrError,
  Id,
  IdQuery,
  IdsQuery,
  InsertCommand,
  SingleInsertCommand,
  MultiInsertCommand,
  RequestData,
  ResponseData,
  QueryResultOrError,
  QueryStringParams,
  SingleQueryResultOrError,
  UpdateCommand,
  IdUpdateCommand,
  MultiUpdateCommand,
  WhereQuery,
} from 'phenyl-interfaces'

type MemoryClientParams = {
  phenylState?: PhenylStateParams,
}

// TODO: implement cooler one externally
function generateRandomStr(): string {
  return 'p' + Math.random().toString().split('.')[1] + 't' + new Date().getTime().toString()
}

export default class PhenylMemoryClient implements EntityClient {
  phenylState: PhenylState

  constructor(params: MemoryClientParams) {
    this.phenylState = new PhenylState(params.phenylState || {})
  }

  /**
   *
   */
  async find(query: WhereQuery): Promise<QueryResultOrError> {
    const entities = this.phenylState.find(query)
    return {
      ok: 1,
      values: entities
    }
  }

  /**
   *
   */
  async findOne(query: WhereQuery): Promise<SingleQueryResultOrError> {
    const entity = this.phenylState.findOne(query)
    if (entity == null) {
      return {
        ok: 0,
        type: 'NotFound',
        message: '"PhenylMemoryClient#findOne()" failed. Could not find any entity with the given query.',
      }
    }
    return {
      ok: 1,
      value: entity
    }
  }

  /**
   *
   */
  async get(query: IdQuery): Promise<SingleQueryResultOrError> {
    try {
      const entity = this.phenylState.get(query)
      return {
        ok: 1,
        value: entity
      }
    }
    catch (e) {
      if (e.constructor.name === 'Error') { // Error from PhenylState
        return {
          ok: 0,
          type: 'NotFound',
          message: `"PhenylMemoryClient#get()" failed. Could not find any entity with the given id: "${query.id}"`,
        }
      }
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async getByIds(query: IdsQuery): Promise<QueryResultOrError> {
    try {
      const entities = this.phenylState.getByIds(query)
      return {
        ok: 1,
        values: entities
      }
    }
    catch (e) {
      if (e.constructor.name === 'Error') { // Error from PhenylState
        return {
          ok: 0,
          type: 'NotFound',
          message: `"PhenylMemoryClient#getByIds()" failed. Some ids are not found. ids: "${query.ids.join(', ')}"`, // TODO: prevent from showing existing ids
        }
      }
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async insert(command: InsertCommand): Promise<CommandResultOrError> {
    if (command.values) {
      const result = await this.insertAndGetMulti(command)
      return result.ok
        ? { ok: 1, n: result.n }
        : result
    }
    const result = await this.insertAndGet(command)
    return result.ok
      ? { ok: 1, n: 1 }
      : result
  }

  /**
   *
   */
  async insertAndGet(command: SingleInsertCommand): Promise<GetCommandResultOrError> {
    const { entityName, value } = command
    const newValue = Object.assign({}, value, { id: generateRandomStr() })
    this.phenylState = this.phenylState.$register(entityName, newValue)
    return {
      ok: 1,
      n: 1,
      value: newValue,
    }
  }

  /**
   *
   */
  async insertAndGetMulti(command: MultiInsertCommand): Promise<MultiValuesCommandResultOrError> {
    const { entityName, values} = command
    const newValues = []
    for (const value of values) {
      const newValue = Object.assign({}, value, { id: generateRandomStr() })
      this.phenylState = this.phenylState.$register(entityName, newValue)
      newValues.push(newValue)
    }
    return {
      ok: 1,
      n: newValues.length,
      values: newValues,
    }
  }

  /**
   *
   */
  async update(command: UpdateCommand): Promise<CommandResultOrError> {
    try {
      this.phenylState = this.phenylState.$update(command)
      return { ok: 1, n: 1 }
    }
    catch (e) {
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async updateAndGet(command: IdUpdateCommand): Promise<GetCommandResultOrError> {
    const { entityName, id } = command
    try {
      this.phenylState = this.phenylState.$update(command)
      return { ok: 1, n: 1, value: this.phenylState.get({ entityName, id }) }
    }
    catch (e) {
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async updateAndFetch(command: MultiUpdateCommand): Promise<MultiValuesCommandResultOrError> {
    const { entityName, where } = command
    try {
      // TODO Performance issue: find() runs twice for just getting N
      const values = this.phenylState.find({ entityName, where })
      const ids = values.map(value => value.id)
      this.phenylState = this.phenylState.$update(command)
      return { ok: 1, n: values.length, values: this.phenylState.getByIds({ ids, entityName }) }
    }
    catch (e) {
      return createErrorResult(e)
    }
  }

  /**
   *
   */
  async delete(command: DeleteCommand): Promise<CommandResultOrError> {
    const { entityName } = command
    try {
      // TODO Performance issue: find() runs twice for just getting N
      const n = command.where ? this.phenylState.find({ where: command.where, entityName }).length : 1
      this.phenylState = this.phenylState.$delete(command)
      return { ok: 1, n }
    }
    catch (e) {
      return createErrorResult(e)
    }
  }
}
<|endoftext|>


<commit_before>// Register UI javascript functionality here

console.log("including main.js");

let txn_types = ['data_txn', 'req_txn', 'ans_txn'];
let modal_ids= ['#addModal', '#verifyModal', '#answerModal', 'txnsModal', '#linkGenerator'];

/* Register Ajax calls per txn creation form ... */
for (let i = 0; i <  txn_types.length; i++) {
	let txn_type = txn_types[i];
	let form_id = '#'+txn_type+"_form";

	$(form_id).submit(function(e) {
		e.preventDefault(); // avoid to execute the actual submit of the form.

	    var url = "/new_txn";

	    $.ajax({
	           type: "POST",
	           url: url,
	           data: $(form_id).serialize(), // serializes the form's elements.
	           success: function(data)
	           {
	           	    if (data != undefined && data['success'] == true) {
	           	    	alert("Nice! We saved your data successfully. Please checkout your dashboard.");
	           	    } else {
	           	    	if ('message' in data) {
	           	    		alert(data['message']);
	           	    	} else {
	           	    		alert("Error while saving your data. Please try again.");
	           	    	}
	           	    }
	               toggle_progress(txn_type);
	           }
	    });

	    toggle_progress(txn_type)
	});
}

let form_id = '#link_generator_form';
$(form_id).submit(function(e) {
    e.preventDefault(); // avoid to execute the actual submit of the form.

    var url = "/link_generator_req_txn";

    $.ajax({
        type: "POST",
        url: url,
        data: $(form_id).serialize(), // serializes the form's elements.
        success: function(data)
        {
        	if (data != undefined && data['success'] == true) {
                alert("Nice! We saved your data successfully. Please checkout your dashboard.");
            } else {
                alert("Error while saving your data. Please try again.");
            }
            toggle_progress('link_generator');
        }
    });

    toggle_progress('link_generator');
});

/* Register modal related callbacks */
for (let i = 0; i < modal_ids.length; i++) {
	let modal_id = modal_ids[i];

	$(modal_id).on('hidden.bs.modal', function () {
    	location.reload();
	})
}

/* For coloring data txns in dashboard */
var sig_texts = $('.sig');
for (let i = 0; i < sig_texts.length; i++) {
	let sig = sig_texts[i].value;
	let block_num = $('#block_for_'+sig)[0].value;
	let panel = $('#panel_heading_'+sig);

	if (block_num == 'Pending') {
		panel.addClass('pending');
	}
}

/* Register stuff to do when the dom is ready */
$( document ).ready(function() {
	reload_pending_txns('#pending_txns');
});

const accept_request = function(req_txn_sig) {

	if (!confirm("Do you want to accept this request?")) {
		return;
	}

	let url = '/accept_request';

    $.ajax({
        type: "POST",
        url: url,
        data: {"sig" : req_txn_sig}, // serializes the form's elements.
        success: function(data)
        {
        	if (data != undefined && data['success'] == true) {
                alert("Answered the Id verification request");
            } else {
                alert("Error while accepting the verification request");
            }

            reload_pending_txns("#pending_txns");
        }
    });
};

/* For refreshing the pending (the ones that I'm being requested for) txns list */
const reload_pending_txns = function(ul_id) {

	// talk to the server to retrieve all pending txns

	let url = "/pending_txns";

	$.ajax({
		type: "GET",
	    url: url,
	    success: function(txn_list) {

	    	// First remove all list elements
	    	$('.req_txn_item').remove();

	    	let li_start = "
";
	    	let li_end = "
";
	    	let ul = $(ul_id);

	    	if (txn_list == undefined || txn_list == null || txn_list.length == 0) {
	    		let li_elem = li_start + "No pending requests"+li_end;
	    		ul.append(li_elem);
	    	} else {
	    		for (let i = 0; i < txn_list.length; i++) {
	    			let req = txn_list[i];

	    			let req_txn_item_id = "\'"+req.sig+"\'";

	    			let container_start = "
";
	    			let container_end = "
";
	    			let a_start = ""
	    			let a_end = ""
	    			let row_start = "
"
	    			let row_end = "
"
	    			let col_start = "
"
	    			let col_end = "
"

	    			let li_elem = li_start + container_start + a_start + row_start + col_start + req.requester + col_end + row_end + row_start + col_start + "requests your " + req.key + col_end + row_end + a_end + container_end + li_end;

	    			ul.append(li_elem);
	    		}
	    	}
	    }
	});
};

const get_history = function() {
	let url = "/history";

	$.ajax({
		type: "GET",
		url: url,
		success: function(txn_list) {

			console.log(txn_list);
			alert(txn_list);

		}
	});
}

const toggle_progress = function(txn_type) {
	let modal_id = '#'+txn_type;
	let progress_id = '#'+txn_type+'_progress';

	if ($(modal_id).hasClass('hidden')) {
		$(modal_id).removeClass('hidden');
		$(progress_id).addClass('hidden');
	} else {
		$(modal_id).addClass('hidden');
		$(progress_id).removeClass('hidden');

		let percentage = 0;

		setTimeout(function() {
			percentage++;
			$('#progress_data').style.width = parseInt(percentage) + "%";
		}, 200);
	}
};

const show_result = function(txn_type, result) {
	let div_id = '#'+txn_type+"_result";
	$(div_id).text(result);
};
<commit_msg>progressbar bugfix<commit_after>// Register UI javascript functionality here

console.log("including main.js");

let txn_types = ['data_txn', 'req_txn', 'ans_txn'];
let modal_ids= ['#addModal', '#verifyModal', '#answerModal', 'txnsModal', '#linkGenerator'];

/* Register Ajax calls per txn creation form ... */
for (let i = 0; i <  txn_types.length; i++) {
	let txn_type = txn_types[i];
	let form_id = '#'+txn_type+"_form";

	$(form_id).submit(function(e) {
		e.preventDefault(); // avoid to execute the actual submit of the form.

	    var url = "/new_txn";

	    $.ajax({
	           type: "POST",
	           url: url,
	           data: $(form_id).serialize(), // serializes the form's elements.
	           success: function(data)
	           {
	           	    if (data != undefined && data['success'] == true) {
	           	    	alert("Nice! We saved your data successfully. Please checkout your dashboard.");
	           	    } else {
	           	    	if ('message' in data) {
	           	    		alert(data['message']);
	           	    	} else {
	           	    		alert("Error while saving your data. Please try again.");
	           	    	}
	           	    }
	               toggle_progress(txn_type);
	           }
	    });

	    toggle_progress(txn_type)
	});
}

let form_id = '#link_generator_form';
$(form_id).submit(function(e) {
    e.preventDefault(); // avoid to execute the actual submit of the form.

    var url = "/link_generator_req_txn";

    $.ajax({
        type: "POST",
        url: url,
        data: $(form_id).serialize(), // serializes the form's elements.
        success: function(data)
        {
        	if (data != undefined && data['success'] == true) {
                alert("Nice! We saved your data successfully. Please checkout your dashboard.");
            } else {
                alert("Error while saving your data. Please try again.");
            }
            toggle_progress('link_generator');
        }
    });

    toggle_progress('link_generator');
});

/* Register modal related callbacks */
for (let i = 0; i < modal_ids.length; i++) {
	let modal_id = modal_ids[i];

	$(modal_id).on('hidden.bs.modal', function () {
    	location.reload();
	})
}

/* For coloring data txns in dashboard */
var sig_texts = $('.sig');
for (let i = 0; i < sig_texts.length; i++) {
	let sig = sig_texts[i].value;
	let block_num = $('#block_for_'+sig)[0].value;
	let panel = $('#panel_heading_'+sig);

	if (block_num == 'Pending') {
		panel.addClass('pending');
	}
}

/* Register stuff to do when the dom is ready */
$( document ).ready(function() {
	reload_pending_txns('#pending_txns');
});

const accept_request = function(req_txn_sig) {

	if (!confirm("Do you want to accept this request?")) {
		return;
	}

	let url = '/accept_request';

    $.ajax({
        type: "POST",
        url: url,
        data: {"sig" : req_txn_sig}, // serializes the form's elements.
        success: function(data)
        {
        	if (data != undefined && data['success'] == true) {
                alert("Answered the Id verification request");
            } else {
                alert("Error while accepting the verification request");
            }

            reload_pending_txns("#pending_txns");
        }
    });
};

/* For refreshing the pending (the ones that I'm being requested for) txns list */
const reload_pending_txns = function(ul_id) {

	// talk to the server to retrieve all pending txns

	let url = "/pending_txns";

	$.ajax({
		type: "GET",
	    url: url,
	    success: function(txn_list) {

	    	// First remove all list elements
	    	$('.req_txn_item').remove();

	    	let li_start = "
";
	    	let li_end = "
";
	    	let ul = $(ul_id);

	    	if (txn_list == undefined || txn_list == null || txn_list.length == 0) {
	    		let li_elem = li_start + "No pending requests"+li_end;
	    		ul.append(li_elem);
	    	} else {
	    		for (let i = 0; i < txn_list.length; i++) {
	    			let req = txn_list[i];

	    			let req_txn_item_id = "\'"+req.sig+"\'";

	    			let container_start = "
";
	    			let container_end = "
";
	    			let a_start = ""
	    			let a_end = ""
	    			let row_start = "
"
	    			let row_end = "
"
	    			let col_start = "
"
	    			let col_end = "
"

	    			let li_elem = li_start + container_start + a_start + row_start + col_start + req.requester + col_end + row_end + row_start + col_start + "requests your " + req.key + col_end + row_end + a_end + container_end + li_end;

	    			ul.append(li_elem);
	    		}
	    	}
	    }
	});
};

const get_history = function() {
	let url = "/history";

	$.ajax({
		type: "GET",
		url: url,
		success: function(txn_list) {

			console.log(txn_list);
			alert(txn_list);

		}
	});
}

const toggle_progress = function(txn_type) {
	let modal_id = '#'+txn_type;
	let progress_id = '#'+txn_type+'_progress';

	if ($(modal_id).hasClass('hidden')) {
		$(modal_id).removeClass('hidden');
		$(progress_id).addClass('hidden');
	} else {
		$(modal_id).addClass('hidden');
		$(progress_id).removeClass('hidden');

		let percentage = 0;

		setTimeout(function() {
			percentage++;
			$('#progress_data')[0].style.width = parseInt(percentage) + "%";
		}, 200);
	}
};

const show_result = function(txn_type, result) {
	let div_id = '#'+txn_type+"_result";
	$(div_id).text(result);
};
<|endoftext|>


<commit_before>/* eslint no-console: ["error", {allow: ["warn"]}] */
/* global console */

// Match the opening angle bracket (<) in HTML tags, and HTML entities like
// &, &, &.
const reOverlay = /<|&#?\w+;/;

/**
 * Elements allowed in translations even if they are not present in the source
 * HTML. They are text-level elements as defined by the HTML5 spec:
 * https://www.w3.org/TR/html5/text-level-semantics.html with the exception of:
 *
 *   - a - because we don't allow href on it anyways,
 *   - ruby, rt, rp - because we don't allow nested elements to be inserted.
 */
const TEXT_LEVEL_ELEMENTS = {
  "http://www.w3.org/1999/xhtml": [
    "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
    "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u",
    "mark", "bdi", "bdo", "span", "br", "wbr"
  ],
};

const LOCALIZABLE_ATTRIBUTES = {
  "http://www.w3.org/1999/xhtml": {
    global: ["title", "aria-label", "aria-valuetext", "aria-moz-hint"],
    a: ["download"],
    area: ["download", "alt"],
    // value is special-cased in isAttrNameLocalizable
    input: ["alt", "placeholder"],
    menuitem: ["label"],
    menu: ["label"],
    optgroup: ["label"],
    option: ["label"],
    track: ["label"],
    img: ["alt"],
    textarea: ["placeholder"],
    th: ["abbr"]
  },
  "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul": {
    global: [
      "accesskey", "aria-label", "aria-valuetext", "aria-moz-hint", "label"
    ],
    key: ["key", "keycode"],
    textbox: ["placeholder"],
    toolbarbutton: ["tooltiptext"],
  }
};

/**
 * Translate an element.
 *
 * Translate the element's text content and attributes. Some HTML markup is
 * allowed in the translation. The element's children with the data-l10n-name
 * attribute will be treated as arguments to the translation. If the
 * translation defines the same children, their attributes and text contents
 * will be used for translating the matching source child.
 *
 * @param   {Element} element
 * @param   {Object} translation
 * @private
 */
export default function translateElement(element, translation) {
  const {value} = translation;

  if (typeof value === "string") {
    if (!reOverlay.test(value)) {
      // If the translation doesn't contain any markup skip the overlay logic.
      element.textContent = value;
    } else {
      // Else parse the translation's HTML using an inert template element,
      // sanitize it and replace the element's content.
      const templateElement = element.ownerDocument.createElementNS(
        "http://www.w3.org/1999/xhtml", "template"
      );
      templateElement.innerHTML = value;
      overlayChildNodes(templateElement.content, element);
    }
  }

  // Even if the translation doesn't define any localizable attributes, run
  // overlayAttributes to remove any localizable attributes set by previous
  // translations.
  overlayAttributes(translation, element);
}

/**
 * Replace child nodes of an element with child nodes of another element.
 *
 * The contents of the target element will be cleared and fully replaced with
 * sanitized contents of the source element.
 *
 * @param {DocumentFragment} fromElement - The source of children to overlay.
 * @param {Element} toElement - The target of the overlay.
 * @private
 */
function overlayChildNodes(fromElement, toElement) {
  const content = toElement.ownerDocument.createDocumentFragment();

  for (const childNode of fromElement.childNodes) {
    if (childNode.nodeType === childNode.TEXT_NODE) {
      content.appendChild(childNode.cloneNode(false));
      continue;
    }

    if (childNode.hasAttribute("data-l10n-name")) {
      content.appendChild(namedChildFrom(toElement, childNode));
      continue;
    }

    if (isElementAllowed(childNode)) {
      content.appendChild(allowedChild(childNode));
      continue;
    }

    console.warn(
      `An element of forbidden type "${childNode.localName}" was found in ` +
      "the translation. Only elements with data-l10n-name can be overlaid " +
      "onto source elements of the same data-l10n-name."
    );

    // If all else fails, convert the element to its text content.
    content.appendChild(textNode(childNode));
  }

  toElement.textContent = "";
  toElement.appendChild(content);
}

/**
 * Transplant localizable attributes of an element to another element.
 *
 * Any localizable attributes already set on the target element will be
 * cleared.
 *
 * @param   {Element|Object} fromElement - The source of child nodes to overlay.
 * @param   {Element} toElement - The target of the overlay.
 * @private
 */
function overlayAttributes(fromElement, toElement) {
  const explicitlyAllowed = toElement.hasAttribute("data-l10n-attrs")
    ? toElement.getAttribute("data-l10n-attrs")
      .split(",").map(i => i.trim())
    : null;

  // Remove existing localizable attributes.
  for (const attr of Array.from(toElement.attributes)) {
    if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
      toElement.removeAttribute(attr.name);
    }
  }

  // fromElement might be a {value, attributes} object as returned by
  // Localization.messageFromContext. In which case attributes may be null to
  // save GC cycles.
  if (!fromElement.attributes) {
    return;
  }

  // Set localizable attributes.
  for (const attr of Array.from(fromElement.attributes)) {
    if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
      toElement.setAttribute(attr.name, attr.value);
    }
  }
}

/**
 * Sanitize a child element created by the translation.
 *
 * Try to find a corresponding child in sourceElement and use it as the base
 * for the sanitization. This will preserve functional attribtues defined on
 * the child element in the source HTML.
 *
 * @param   {Element} sourceElement - The source for data-l10n-name lookups.
 * @param   {Element} translatedChild - The translated child to be sanitized.
 * @returns {Element}
 * @private
 */
function namedChildFrom(sourceElement, translatedChild) {
  const childName = translatedChild.getAttribute("data-l10n-name");
  const sourceChild = sourceElement.querySelector(
    `[data-l10n-name="${childName}"]`
  );

  if (!sourceChild) {
    console.warn(
      `An element named "${childName}" wasn't found in the source.`
    );
    return textNode(translatedChild);
  }

  if (sourceChild.localName !== translatedChild.localName) {
    console.warn(
      `An element named "${childName}" was found in the translation ` +
      `but its type ${translatedChild.localName} didn't match the ` +
      `element found in the source (${sourceChild.localName}).`
    );
    return textNode(translatedChild);
  }

  // Remove it from sourceElement so that the translation cannot use
  // the same reference name again.
  sourceElement.removeChild(sourceChild);
  // We can't currently guarantee that a translation won't remove
  // sourceChild from the element completely, which could break the app if
  // it relies on an event handler attached to the sourceChild. Let's make
  // this limitation explicit for now by breaking the identitiy of the
  // sourceChild by cloning it. This will destroy all event handlers
  // attached to sourceChild via addEventListener and via on<name>
  // properties.
  const clone = sourceChild.cloneNode(false);
  return shallowPopulateUsing(translatedChild, clone);
}

/**
 * Sanitize an allowed element.
 *
 * Text-level elements allowed in translations may only use safe attributes
 * and will have any nested markup stripped to text content.
 *
 * @param   {Element} element - The element to be sanitized.
 * @returns {Element}
 * @private
 */
function allowedChild(element) {
  // Start with an empty element of the same type to remove nested children
  // and non-localizable attributes defined by the translation.
  const clone = element.ownerDocument.createElement(element.localName);
  return shallowPopulateUsing(element, clone);
}

/**
 * Convert an element to a text node.
 *
 * @param   {Element} element - The element to be sanitized.
 * @returns {Node}
 * @private
 */
function textNode(element) {
  return element.ownerDocument.createTextNode(element.textContent);
}

/**
 * Check if element is allowed in the translation.
 *
 * This method is used by the sanitizer when the translation markup contains
 * an element which is not present in the source code.
 *
 * @param   {Element} element
 * @returns {boolean}
 * @private
 */
function isElementAllowed(element) {
  const allowed = TEXT_LEVEL_ELEMENTS[element.namespaceURI];
  return allowed && allowed.includes(element.localName);
}

/**
 * Check if attribute is allowed for the given element.
 *
 * This method is used by the sanitizer when the translation markup contains
 * DOM attributes, or when the translation has traits which map to DOM
 * attributes.
 *
 * `explicitlyAllowed` can be passed as a list of attributes explicitly
 * allowed on this element.
 *
 * @param   {string}         name
 * @param   {Element}        element
 * @param   {Array}          explicitlyAllowed
 * @returns {boolean}
 * @private
 */
function isAttrNameLocalizable(name, element, explicitlyAllowed = null) {
  if (explicitlyAllowed && explicitlyAllowed.includes(name)) {
    return true;
  }

  const allowed = LOCALIZABLE_ATTRIBUTES[element.namespaceURI];
  if (!allowed) {
    return false;
  }

  const attrName = name.toLowerCase();
  const elemName = element.localName;

  // Is it a globally safe attribute?
  if (allowed.global.includes(attrName)) {
    return true;
  }

  // Are there no allowed attributes for this element?
  if (!allowed[elemName]) {
    return false;
  }

  // Is it allowed on this element?
  if (allowed[elemName].includes(attrName)) {
    return true;
  }

  // Special case for value on HTML inputs with type button, reset, submit
  if (element.namespaceURI === "http://www.w3.org/1999/xhtml" &&
      elemName === "input" && attrName === "value") {
    const type = element.type.toLowerCase();
    if (type === "submit" || type === "button" || type === "reset") {
      return true;
    }
  }

  return false;
}

/**
 * Helper to set textContent and localizable attributes on an element.
 *
 * @param   {Element} fromElement
 * @param   {Element} toElement
 * @returns {Element}
 * @private
 */
function shallowPopulateUsing(fromElement, toElement) {
  toElement.textContent = fromElement.textContent;
  overlayAttributes(fromElement, toElement);
  return toElement;
}
<commit_msg>Skip the intermediate DocumentFragment<commit_after>/* eslint no-console: ["error", {allow: ["warn"]}] */
/* global console */

// Match the opening angle bracket (<) in HTML tags, and HTML entities like
// &, &, &.
const reOverlay = /<|&#?\w+;/;

/**
 * Elements allowed in translations even if they are not present in the source
 * HTML. They are text-level elements as defined by the HTML5 spec:
 * https://www.w3.org/TR/html5/text-level-semantics.html with the exception of:
 *
 *   - a - because we don't allow href on it anyways,
 *   - ruby, rt, rp - because we don't allow nested elements to be inserted.
 */
const TEXT_LEVEL_ELEMENTS = {
  "http://www.w3.org/1999/xhtml": [
    "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data",
    "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u",
    "mark", "bdi", "bdo", "span", "br", "wbr"
  ],
};

const LOCALIZABLE_ATTRIBUTES = {
  "http://www.w3.org/1999/xhtml": {
    global: ["title", "aria-label", "aria-valuetext", "aria-moz-hint"],
    a: ["download"],
    area: ["download", "alt"],
    // value is special-cased in isAttrNameLocalizable
    input: ["alt", "placeholder"],
    menuitem: ["label"],
    menu: ["label"],
    optgroup: ["label"],
    option: ["label"],
    track: ["label"],
    img: ["alt"],
    textarea: ["placeholder"],
    th: ["abbr"]
  },
  "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul": {
    global: [
      "accesskey", "aria-label", "aria-valuetext", "aria-moz-hint", "label"
    ],
    key: ["key", "keycode"],
    textbox: ["placeholder"],
    toolbarbutton: ["tooltiptext"],
  }
};

/**
 * Translate an element.
 *
 * Translate the element's text content and attributes. Some HTML markup is
 * allowed in the translation. The element's children with the data-l10n-name
 * attribute will be treated as arguments to the translation. If the
 * translation defines the same children, their attributes and text contents
 * will be used for translating the matching source child.
 *
 * @param   {Element} element
 * @param   {Object} translation
 * @private
 */
export default function translateElement(element, translation) {
  const {value} = translation;

  if (typeof value === "string") {
    if (!reOverlay.test(value)) {
      // If the translation doesn't contain any markup skip the overlay logic.
      element.textContent = value;
    } else {
      // Else parse the translation's HTML using an inert template element,
      // sanitize it and replace the element's content.
      const templateElement = element.ownerDocument.createElementNS(
        "http://www.w3.org/1999/xhtml", "template"
      );
      templateElement.innerHTML = value;
      overlayChildNodes(templateElement.content, element);
    }
  }

  // Even if the translation doesn't define any localizable attributes, run
  // overlayAttributes to remove any localizable attributes set by previous
  // translations.
  overlayAttributes(translation, element);
}

/**
 * Replace child nodes of an element with child nodes of another element.
 *
 * The contents of the target element will be cleared and fully replaced with
 * sanitized contents of the source element.
 *
 * @param {DocumentFragment} fromFragment - The source of children to overlay.
 * @param {Element} toElement - The target of the overlay.
 * @private
 */
function overlayChildNodes(fromFragment, toElement) {
  for (const childNode of fromFragment.childNodes) {
    if (childNode.nodeType === childNode.TEXT_NODE) {
      // Keep the translated text node.
      continue;
    }

    if (childNode.hasAttribute("data-l10n-name")) {
      const sanitized = namedChildFrom(toElement, childNode);
      fromFragment.replaceChild(sanitized, childNode);
      continue;
    }

    if (isElementAllowed(childNode)) {
      const sanitized = allowedChild(childNode);
      fromFragment.replaceChild(sanitized, childNode);
      continue;
    }

    console.warn(
      `An element of forbidden type "${childNode.localName}" was found in ` +
      "the translation. Only safe text-level elements and elements with " +
      "data-l10n-name are allowed."
    );

    // If all else fails, replace the element with its text content.
    fromFragment.replaceChild(textNode(childNode), childNode);
  }

  toElement.textContent = "";
  toElement.appendChild(fromFragment);
}

/**
 * Transplant localizable attributes of an element to another element.
 *
 * Any localizable attributes already set on the target element will be
 * cleared.
 *
 * @param   {Element|Object} fromElement - The source of child nodes to overlay.
 * @param   {Element} toElement - The target of the overlay.
 * @private
 */
function overlayAttributes(fromElement, toElement) {
  const explicitlyAllowed = toElement.hasAttribute("data-l10n-attrs")
    ? toElement.getAttribute("data-l10n-attrs")
      .split(",").map(i => i.trim())
    : null;

  // Remove existing localizable attributes.
  for (const attr of Array.from(toElement.attributes)) {
    if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
      toElement.removeAttribute(attr.name);
    }
  }

  // fromElement might be a {value, attributes} object as returned by
  // Localization.messageFromContext. In which case attributes may be null to
  // save GC cycles.
  if (!fromElement.attributes) {
    return;
  }

  // Set localizable attributes.
  for (const attr of Array.from(fromElement.attributes)) {
    if (isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed)) {
      toElement.setAttribute(attr.name, attr.value);
    }
  }
}

/**
 * Sanitize a child element created by the translation.
 *
 * Try to find a corresponding child in sourceElement and use it as the base
 * for the sanitization. This will preserve functional attribtues defined on
 * the child element in the source HTML.
 *
 * @param   {Element} sourceElement - The source for data-l10n-name lookups.
 * @param   {Element} translatedChild - The translated child to be sanitized.
 * @returns {Element}
 * @private
 */
function namedChildFrom(sourceElement, translatedChild) {
  const childName = translatedChild.getAttribute("data-l10n-name");
  const sourceChild = sourceElement.querySelector(
    `[data-l10n-name="${childName}"]`
  );

  if (!sourceChild) {
    console.warn(
      `An element named "${childName}" wasn't found in the source.`
    );
    return textNode(translatedChild);
  }

  if (sourceChild.localName !== translatedChild.localName) {
    console.warn(
      `An element named "${childName}" was found in the translation ` +
      `but its type ${translatedChild.localName} didn't match the ` +
      `element found in the source (${sourceChild.localName}).`
    );
    return textNode(translatedChild);
  }

  // Remove it from sourceElement so that the translation cannot use
  // the same reference name again.
  sourceElement.removeChild(sourceChild);
  // We can't currently guarantee that a translation won't remove
  // sourceChild from the element completely, which could break the app if
  // it relies on an event handler attached to the sourceChild. Let's make
  // this limitation explicit for now by breaking the identitiy of the
  // sourceChild by cloning it. This will destroy all event handlers
  // attached to sourceChild via addEventListener and via on<name>
  // properties.
  const clone = sourceChild.cloneNode(false);
  return shallowPopulateUsing(translatedChild, clone);
}

/**
 * Sanitize an allowed element.
 *
 * Text-level elements allowed in translations may only use safe attributes
 * and will have any nested markup stripped to text content.
 *
 * @param   {Element} element - The element to be sanitized.
 * @returns {Element}
 * @private
 */
function allowedChild(element) {
  // Start with an empty element of the same type to remove nested children
  // and non-localizable attributes defined by the translation.
  const clone = element.ownerDocument.createElement(element.localName);
  return shallowPopulateUsing(element, clone);
}

/**
 * Convert an element to a text node.
 *
 * @param   {Element} element - The element to be sanitized.
 * @returns {Node}
 * @private
 */
function textNode(element) {
  return element.ownerDocument.createTextNode(element.textContent);
}

/**
 * Check if element is allowed in the translation.
 *
 * This method is used by the sanitizer when the translation markup contains
 * an element which is not present in the source code.
 *
 * @param   {Element} element
 * @returns {boolean}
 * @private
 */
function isElementAllowed(element) {
  const allowed = TEXT_LEVEL_ELEMENTS[element.namespaceURI];
  return allowed && allowed.includes(element.localName);
}

/**
 * Check if attribute is allowed for the given element.
 *
 * This method is used by the sanitizer when the translation markup contains
 * DOM attributes, or when the translation has traits which map to DOM
 * attributes.
 *
 * `explicitlyAllowed` can be passed as a list of attributes explicitly
 * allowed on this element.
 *
 * @param   {string}         name
 * @param   {Element}        element
 * @param   {Array}          explicitlyAllowed
 * @returns {boolean}
 * @private
 */
function isAttrNameLocalizable(name, element, explicitlyAllowed = null) {
  if (explicitlyAllowed && explicitlyAllowed.includes(name)) {
    return true;
  }

  const allowed = LOCALIZABLE_ATTRIBUTES[element.namespaceURI];
  if (!allowed) {
    return false;
  }

  const attrName = name.toLowerCase();
  const elemName = element.localName;

  // Is it a globally safe attribute?
  if (allowed.global.includes(attrName)) {
    return true;
  }

  // Are there no allowed attributes for this element?
  if (!allowed[elemName]) {
    return false;
  }

  // Is it allowed on this element?
  if (allowed[elemName].includes(attrName)) {
    return true;
  }

  // Special case for value on HTML inputs with type button, reset, submit
  if (element.namespaceURI === "http://www.w3.org/1999/xhtml" &&
      elemName === "input" && attrName === "value") {
    const type = element.type.toLowerCase();
    if (type === "submit" || type === "button" || type === "reset") {
      return true;
    }
  }

  return false;
}

/**
 * Helper to set textContent and localizable attributes on an element.
 *
 * @param   {Element} fromElement
 * @param   {Element} toElement
 * @returns {Element}
 * @private
 */
function shallowPopulateUsing(fromElement, toElement) {
  toElement.textContent = fromElement.textContent;
  overlayAttributes(fromElement, toElement);
  return toElement;
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

/**
 * Copyright 2004-present Facebook. All Rights Reserved.
 */

var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var utils = require('../local-cli/generator-utils');

var CLI_MODULE_PATH = function() {
  return path.resolve(
    process.cwd(),
    'node_modules',
    'react-native',
    'cli'
  );
};

var cli;
try {
  cli = require(CLI_MODULE_PATH());
} catch(e) {}

if (cli) {
  cli.run();
} else {
  var args = process.argv.slice(2);
  if (args.length === 0) {
    console.error(
      'You did not pass any commands, did you mean to run `react-native init`?'
    );
    process.exit(1);
  }

  switch (args[0]) {
  case 'init':
    if (args[1]) {
      init(args[1]);
    } else {
      console.error(
        'Usage: react-native init <ProjectName>'
      );
      process.exit(1);
    }
    break;
  default:
    console.error(
      'Command `%s` unrecognized. ' +
      'Did you mean to run this inside a react-native project?',
      args[0]
    );
    process.exit(1);
    break;
  }
}

function init(name) {
  utils.validatePackageName(name);

  var root = path.resolve(name);
  var projectName = path.basename(root);

  console.log(
    'This will walk you through creating a new React Native project in',
    root
  );

  if (!fs.existsSync(root)) {
    fs.mkdirSync(root);
  }

  var packageJson = {
    name: projectName,
    version: '0.0.1',
    private: true,
    scripts: {
      start: 'node_modules/react-native/packager/packager.sh'
    }
  };
  fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson));
  process.chdir(root);

  run('npm install --save react-native', function(e) {
    if (e) {
      console.error('`npm install --save react-native` failed');
      process.exit(1);
    }

    var cli = require(CLI_MODULE_PATH());
    cli.init(root, projectName);
  });
}

function run(command, cb) {
  var parts = command.split(/\s+/);
  var cmd = parts[0];
  var args = parts.slice(1);
  var proc = spawn(cmd, args, {stdio: 'inherit'});
  proc.on('close', function(code) {
    if (code !== 0) {
      cb(new Error('Command exited with a non-zero status'));
    } else {
      cb(null);
    }
  });
}
<commit_msg>Duplicate the name validation for now<commit_after>#!/usr/bin/env node

/**
 * Copyright 2004-present Facebook. All Rights Reserved.
 */

var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;

var CLI_MODULE_PATH = function() {
  return path.resolve(
    process.cwd(),
    'node_modules',
    'react-native',
    'cli'
  );
};

var cli;
try {
  cli = require(CLI_MODULE_PATH());
} catch(e) {}

if (cli) {
  cli.run();
} else {
  var args = process.argv.slice(2);
  if (args.length === 0) {
    console.error(
      'You did not pass any commands, did you mean to run `react-native init`?'
    );
    process.exit(1);
  }

  switch (args[0]) {
  case 'init':
    if (args[1]) {
      init(args[1]);
    } else {
      console.error(
        'Usage: react-native init <ProjectName>'
      );
      process.exit(1);
    }
    break;
  default:
    console.error(
      'Command `%s` unrecognized. ' +
      'Did you mean to run this inside a react-native project?',
      args[0]
    );
    process.exit(1);
    break;
  }
}

function validatePackageName(name) {
  if (!name.match(/^[$A-Z_][0-9A-Z_$]*$/i)) {
    console.error(
      '"%s" is not a valid name for a project. Please use a valid identifier ' +
        'name (alphanumeric).',
      name
    );
    process.exit(1);
  }
}

function init(name) {
  validatePackageName(name);

  var root = path.resolve(name);
  var projectName = path.basename(root);

  console.log(
    'This will walk you through creating a new React Native project in',
    root
  );

  if (!fs.existsSync(root)) {
    fs.mkdirSync(root);
  }

  var packageJson = {
    name: projectName,
    version: '0.0.1',
    private: true,
    scripts: {
      start: 'node_modules/react-native/packager/packager.sh'
    }
  };
  fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson));
  process.chdir(root);

  run('npm install --save react-native', function(e) {
    if (e) {
      console.error('`npm install --save react-native` failed');
      process.exit(1);
    }

    var cli = require(CLI_MODULE_PATH());
    cli.init(root, projectName);
  });
}

function run(command, cb) {
  var parts = command.split(/\s+/);
  var cmd = parts[0];
  var args = parts.slice(1);
  var proc = spawn(cmd, args, {stdio: 'inherit'});
  proc.on('close', function(code) {
    if (code !== 0) {
      cb(new Error('Command exited with a non-zero status'));
    } else {
      cb(null);
    }
  });
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2008-2015 The Open Source Geospatial Foundation
 *
 * Published under the BSD license.
 * See https://github.com/geoext/geoext2/blob/master/license.txt for the full
 * text of the license.
 */

/*
 * @requires GeoExt/Version.js
 */

/**
 * The LayerNode plugin. This is used to create a node that is connected to
 * a layer, so the checkbox and the layer's visibility are in sync. A basic
 * layer node would be configured like this:
 *
 *     {
 *         plugins: ['gx_layernode'],
 *         layer: myLayer
 *     }
 *
 * When the layer associated with this GeoExt.tree.LayerNode is no longer in
 * range (invisible due to resolution constraints), the layer will be visually
 * marked as disabled.
 *
 * If you do not want this behaviour, include the following CSS code:
 *
 *     .gx-tree-row-disabled span.x-tree-node-text {
 *         opacity: inherit;
 *         font-style: inherit;
 *     }
 *
 * If additionally you want to make the nodes checkbox unusable when the node is
 * disabled, you could use the following CSS-snippet:
 *
 *     .gx-tree-row-disabled input {
 *         visibility: hidden;
 *     }
 *
 * See GeoExt.data.LayerTreeModel for more details on GeoExt extensions to the
 * node configuration.
 *
 * @class GeoExt.tree.LayerNode
 */
Ext.define('GeoExt.tree.LayerNode', {
    extend: 'Ext.AbstractPlugin',
    alias: 'plugin.gx_layer',
    requires: [
        'GeoExt.Version',
        'GeoExt.tree.Util'
    ],
    /**
     * The init method is invoked after initComponent method has been run for
     * the client Component. It performs plugin initialization.
     *
     * @param {Ext.Component} target The client Component which owns this
     *     plugin.
     * @private
     */
    init: function(target) {
        this.target = target;
        var layer = target.get('layer');

        target.set('checked', layer.getVisibility());
        if (!target.get('checkedGroup') && layer.isBaseLayer) {
            target.set('checkedGroup', 'gx_baselayer');
        }
        target.set('fixedText', !!target.text);

        target.set('leaf', true);

        if(!target.get('iconCls')) {
            target.set('iconCls', "gx-tree-layer-icon");
        }

        target.on('afteredit', this.onAfterEdit, this);

        layer.events.on({
            'visibilitychanged': this.onLayerVisibilityChanged,
            scope: this
        });

        if (!layer.alwaysInRange && layer.map) {
            layer.map.events.register('moveend', this, this.onMapMoveend);
        }

        GeoExt.tree.Util.enforceOneLayerVisible(this.target);
    },

    /**
     * Handler for the node's afteredit event.
     *
     * @param {GeoExt.data.LayerTreeModel} node
     * @param {String[]} modifiedFields
     * @private
     */
    onAfterEdit: function(node, modifiedFields) {
        var me = this;

        if(~Ext.Array.indexOf(modifiedFields, 'checked')) {
            GeoExt.tree.Util.updateLayerVisibilityByNode(this.target, this.target.get('checked'));
        }
    },

    /**
     * Handler for visibilitychanged events on the layer.
     *
     * @private
     */
    onLayerVisibilityChanged: function() {
        if(!this._visibilityChanging) {
            this.target.set('checked', this.target.get('layer').getVisibility());
        }
    },

    /**
     *  handler for map moveend events to determine if node should be
     *  disabled or enabled
     *
     * @private
     */
    onMapMoveend: function(e) {
        this.target.set('disabled', !this.target.get('layer').inRange);
    },

    /**
     * Updates the visibility of the layer that is connected to the target
     * node.
     *
     * @private
     */
    onCheckChange: function() {
        var node = this.target,
            checked = this.target.get('checked');

        if(checked != node.get('layer').getVisibility()) {
            node._visibilityChanging = true;
            var layer = node.get('layer');
            if(checked && layer.isBaseLayer && layer.map) {
                layer.map.setBaseLayer(layer);
            } else if(!checked && layer.isBaseLayer && layer.map &&
                      layer.map.baseLayer && layer.id == layer.map.baseLayer.id) {
                // Must prevent the unchecking of radio buttons
                node.set('checked', layer.getVisibility());
            } else {
                layer.setVisibility(checked);
            }
            delete node._visibilityChanging;
        }
        GeoExt.tree.Util.enforceOneLayerVisible(node);
    }

});
<commit_msg>Dispose layer tree node event listeners.<commit_after>/*
 * Copyright (c) 2008-2015 The Open Source Geospatial Foundation
 *
 * Published under the BSD license.
 * See https://github.com/geoext/geoext2/blob/master/license.txt for the full
 * text of the license.
 */

/*
 * @requires GeoExt/Version.js
 */

/**
 * The LayerNode plugin. This is used to create a node that is connected to
 * a layer, so the checkbox and the layer's visibility are in sync. A basic
 * layer node would be configured like this:
 *
 *     {
 *         plugins: ['gx_layernode'],
 *         layer: myLayer
 *     }
 *
 * When the layer associated with this GeoExt.tree.LayerNode is no longer in
 * range (invisible due to resolution constraints), the layer will be visually
 * marked as disabled.
 *
 * If you do not want this behaviour, include the following CSS code:
 *
 *     .gx-tree-row-disabled span.x-tree-node-text {
 *         opacity: inherit;
 *         font-style: inherit;
 *     }
 *
 * If additionally you want to make the nodes checkbox unusable when the node is
 * disabled, you could use the following CSS-snippet:
 *
 *     .gx-tree-row-disabled input {
 *         visibility: hidden;
 *     }
 *
 * See GeoExt.data.LayerTreeModel for more details on GeoExt extensions to the
 * node configuration.
 *
 * @class GeoExt.tree.LayerNode
 */
Ext.define('GeoExt.tree.LayerNode', {
    extend: 'Ext.AbstractPlugin',
    alias: 'plugin.gx_layer',
    requires: [
        'GeoExt.Version',
        'GeoExt.tree.Util'
    ],

    /**
     * Cached map this layer node's layer is associated with.
     * @type {OpenLayers.Map}
     *
     * @private
     */
    map: null,

    /**
     * The init method is invoked after initComponent method has been run for
     * the client Component. It performs plugin initialization.
     *
     * @param {Ext.Component} target The client Component which owns this
     *     plugin.
     * @private
     */
    init: function(target) {
        this.target = target;
        var layer = target.get('layer');

        target.set('checked', layer.getVisibility());
        if (!target.get('checkedGroup') && layer.isBaseLayer) {
            target.set('checkedGroup', 'gx_baselayer');
        }
        target.set('fixedText', !!target.text);

        target.set('leaf', true);

        if(!target.get('iconCls')) {
            target.set('iconCls', "gx-tree-layer-icon");
        }

        target.on('afteredit', this.onAfterEdit, this);

        layer.events.on({
            'visibilitychanged': this.onLayerVisibilityChanged,
            scope: this
        });

        if (layer.map) {
            this.map = layer.map;

            // Triggers disposal of event listeners if the removed layer maps
            // to this plugins layer node.
            // TODO: Find a better way to link into lifecycle of the layer node
            //       to dispose event listeners. See:
            //       https://github.com/geoext/geoext2/pull/357
            this.map.events.on({
                'removelayer': this.onMapRemovelayer,
                scope: this
            });
        }

        if (!layer.alwaysInRange && this.map) {
            this.map.events.on({
                'moveend': this.onMapMoveend,
                scope: this
            });
        }

        GeoExt.tree.Util.enforceOneLayerVisible(this.target);
    },

    /**
     * Handler for the node's afteredit event.
     *
     * @param {GeoExt.data.LayerTreeModel} node
     * @param {String[]} modifiedFields
     * @private
     */
    onAfterEdit: function(node, modifiedFields) {
        var me = this;

        if(~Ext.Array.indexOf(modifiedFields, 'checked')) {
            GeoExt.tree.Util.updateLayerVisibilityByNode(this.target, this.target.get('checked'));
        }
    },

    /**
     * Handler for visibilitychanged events on the layer.
     *
     * @private
     */
    onLayerVisibilityChanged: function() {
        if(!this._visibilityChanging) {
            this.target.set('checked', this.target.get('layer').getVisibility());
        }
    },

    /**
     * Disposes event handlers that have been added during initialization of plugin.
     * TODO: Add tests to make sure this works as expected.
     *
     * @private
     */
    onMapRemovelayer: function(evt) {
        var target = this.target,
            layer = target.get('layer');

        if (evt.layer !== layer) {
            return;
        }

        target.un('afteredit', this.onAfterEdit, this);

        layer.events.un({
            'visibilitychanged': this.onLayerVisibilityChanged,
            scope: this
        });

        this.map.events.un({
            'removelayer': this.onMapRemovelayer,
            'moveend': this.onMapMoveend,
            scope: this
        });

        this.map = null;

        return true;
    },

    /**
     *  handler for map moveend events to determine if node should be
     *  disabled or enabled
     *
     * @private
     */
    onMapMoveend: function(e) {
        this.target.set('disabled', !this.target.get('layer').inRange);
    },

    /**
     * Updates the visibility of the layer that is connected to the target
     * node.
     *
     * @private
     */
    onCheckChange: function() {
        var node = this.target,
            checked = this.target.get('checked');

        if(checked != node.get('layer').getVisibility()) {
            node._visibilityChanging = true;
            var layer = node.get('layer');
            if(checked && layer.isBaseLayer && layer.map) {
                layer.map.setBaseLayer(layer);
            } else if(!checked && layer.isBaseLayer && layer.map &&
                      layer.map.baseLayer && layer.id == layer.map.baseLayer.id) {
                // Must prevent the unchecking of radio buttons
                node.set('checked', layer.getVisibility());
            } else {
                layer.setVisibility(checked);
            }
            delete node._visibilityChanging;
        }
        GeoExt.tree.Util.enforceOneLayerVisible(node);
    }

});
<|endoftext|>