<commit_before>/**
 *
 */

VK.init({
	apiId : 1755175
});

var WRONG_TEXT = 'Something goes wrong. Enter valid post address.';

var grp = '';
var us = '';
var grpAvatar = '';

var obj = [];
var parsed = [0, 0];

var grCount = 0;
var grOffset = 0;

var sexArray;
var manpos = [];
var sexCountArray = [0,0,0];
var wompos = [];
var onopos = [];
var wArray = [];
var mArray = [];
var m = 0;
var w = 0;

//boys girls
var sexArray = [];
var sexCountArray[0] = 0, sexCountArray[1] = 0, sexCountArray[2] = 0;
var menpos = [], wompos = [], onopos = [];
//

//for requests
var massoff = 0;
//

function getGroupInfo(groupId) {// ??? 2nd main funtion

	VK.api("groups.getById", {
		'group_id' : Math.abs(groupId),
		'fields' : 'members_count,photo_100'
	}, function(data) {
		if (data.error) {
			errorMSG('Error in data. Wrong group data.')
		} else {
			for (var i = 0; i < data.response.length; i++) {
				grp = data.response[i].name;
				us = data.response[i].members_count;
				grpAvatar = data.response[i].photo_100;

				obj[0] = grp;
				obj[1] = us;
				obj[2] = grpAvatar;

			}
			groupViewChanges(getGroupData());
			//UI changes
			getGroupMembers(getParsedInfo(),massoff);

			sexArray = [];
		}
	});

}

function getGroupMembers(objTargets, offset) {
	//clear some vars
	var sexCountArray = new Array[2];
	var menpos = [];
	var wompos = [];
	var onopos = [];

	var cMembers = 0;
	var aboutPost = objTargets;
	// 0-group, 1-item_post
	var code = '';
	code = 'var od='+aboutPost[0]+';'
           +'var id='+aboutPost[1]+';'
           +'var i=0;var off='+offset+';var c=1000;'
           +'var mc=API.likes.getList({"type":"post","owner_id":od,"item_id":id,"count":c,"offset":off}).count;'
           +'var lusers=[]; var like;'
           +'while(i<10 && off<mc){'
           +'like=API.likes.getList({"type":"post","owner_id":od,"item_id":id,"count":c,"offset":off}).users;'
           +'lusers=lusers+API.users.get({"user_ids":like,"fields":"sex"})@.sex;'
           +'off=off+c;i=i+1;};'
           +'return {"count":mc,"sex":lusers};';

	VK.api('execute', {'code' : code,}, function(data) {
		if (data.response.error) {
			errorMSG('Wrong: Group Member');
			console.log("error");
		} else {

			document.getElementById("m_members").innerHTML = 'Count: ' + data.response.count + ' members.' + " Length sex: " + data.response.sex.length;
			document.getElementById("pCaption").innerHTML += '

'+data.response.sex;
			var dtd = data.response.sex;
			var cM = data.response.count;
			cMembers = cM;
			sexArray = sexArray.concat(dtd);
			//put different in array.
			for (var i = 0; i < sexArray.length; i++) {// 1 man 2 woman
				if (parseInt(sexArray[i],10) == 1) {
					sexCountArray[1]++;
					manpos.push(i);
				}
				if (parseInt(sexArray[i],10) == 2){
					sexCountArray[2]++;
					wompos.push(i);
				}
				if (parseInt(sexArray[i],10) == 0){
					sexCountArray[0]++;
					onopos.push(i);
				}
			}

			console.log("0 ono: "+sexCountArray[0]);//ono
			console.log("1 he : "+sexCountArray[1]);//man
			console.log("2 she: "+sexCountArray[2]);//woman

			console.log("sArray: "+sexArray.length);
		}
	});

}

function getGroupData() {//data for groupName,groupMembersCount, avatar.
	return obj;
}

function getParsedInfo() {//getting obj {groupID, postID}
	return parsed;
}

function waiting() {//maybe wait some time
	var se = 0;
	var t = setTimeout(function() {
		if (se > 1) {
			waiting();
			console.log('waits ----------------------------------');
			se = 0;
		}
		se++;
	}, 500);
}

function groupViewChanges(obj) {//changes UI view
	var a = obj;
	document.getElementById('groupName').innerHTML = 'Name: ' + a[0];
	document.getElementById('members_count').innerHTML = 'Count: ' + a[1];
	document.getElementById('av').src = a[2];
}

function parseLink() {// main function for parse link from text area.
	var link = String(document.getElementById('post').value);

	if (link != '' && (link.match('vk.com') == 'vk.com') && (link.match('wall-') == 'wall-')) {

		if (link.indexOf('=wall') != -1) {
			var adr = link.substring(link.indexOf('=wall') + 5, link.length);
			var p = adr.split('_');
			if ((p[0] === undefined) && (p[1] === undefined)) {
				errorMSG('Wrong: group or post or both');
			} else {
				if ((/\d+/.test(p[0])) && (/\d+/.test(p[1]))) {
					parsed[0] = p[0];
					parsed[1] = p[1];
					getGroupInfo(parsed[0]);
				}

			}
		} else {
			errorMSG('Wrong: not wall post');
		}
	} else {
		errorMSG('Wrong: not vk.com');
	}
}

function clearadress() {// clear text area
	document.getElementById('post').value = '';
}

function errorMSG(msg) {// shows error messages in main text area
	document.getElementById('post').value = msg;
	var t = setTimeout(clearadress, 1000);
}<commit_msg>new 2<commit_after>/**
 *
 */

VK.init({
	apiId : 1755175
});

var WRONG_TEXT = 'Something goes wrong. Enter valid post address.';

var grp = '';
var us = '';
var grpAvatar = '';

var obj = [];
var parsed = [0, 0];

var grCount = 0;
var grOffset = 0;

var sexArray;
var manpos = [];
var sexCountArray = [0,0,0];
var wompos = [];
var onopos = [];
var wArray = [];
var mArray = [];
var m = 0;
var w = 0;

//boys girls
var sexArray = [];
var sexCountArray = new Array[2];
var menpos = [];
var wompos = [];
var onopos = [];
//

//for requests
var massoff = 0;
//

function getGroupInfo(groupId) {// ??? 2nd main funtion

	VK.api("groups.getById", {
		'group_id' : Math.abs(groupId),
		'fields' : 'members_count,photo_100'
	}, function(data) {
		if (data.error) {
			errorMSG('Error in data. Wrong group data.')
		} else {
			for (var i = 0; i < data.response.length; i++) {
				grp = data.response[i].name;
				us = data.response[i].members_count;
				grpAvatar = data.response[i].photo_100;

				obj[0] = grp;
				obj[1] = us;
				obj[2] = grpAvatar;

			}
			groupViewChanges(getGroupData());
			//UI changes
			getGroupMembers(getParsedInfo(),massoff);

			sexArray = [];
		}
	});

}

function getGroupMembers(objTargets, offset) {

	var cMembers = 0;
	var aboutPost = objTargets;
	// 0-group, 1-item_post
	var code = '';
	code = 'var od='+aboutPost[0]+';'
           +'var id='+aboutPost[1]+';'
           +'var i=0;var off='+offset+';var c=1000;'
           +'var mc=API.likes.getList({"type":"post","owner_id":od,"item_id":id,"count":c,"offset":off}).count;'
           +'var lusers=[]; var like;'
           +'while(i<10 && off<mc){'
           +'like=API.likes.getList({"type":"post","owner_id":od,"item_id":id,"count":c,"offset":off}).users;'
           +'lusers=lusers+API.users.get({"user_ids":like,"fields":"sex"})@.sex;'
           +'off=off+c;i=i+1;};'
           +'return {"count":mc,"sex":lusers};';

	VK.api('execute', {'code' : code,}, function(data) {
		if (data.response.error) {
			errorMSG('Wrong: Group Member');
			console.log("error");
		} else {

			document.getElementById("m_members").innerHTML = 'Count: ' + data.response.count + ' members.' + " Length sex: " + data.response.sex.length;
			document.getElementById("pCaption").innerHTML += '

'+data.response.sex;
			var dtd = data.response.sex;
			var cM = data.response.count;
			cMembers = cM;
			sexArray = sexArray.concat(dtd);
			//put different in array.
			for (var i = 0; i < sexArray.length; i++) {// 1 man 2 woman
				if (parseInt(sexArray[i],10) == 1) {
					sexCountArray[1]++;
					manpos.push(i);
				}
				if (parseInt(sexArray[i],10) == 2){
					sexCountArray[2]++;
					wompos.push(i);
				}
				if (parseInt(sexArray[i],10) == 0){
					sexCountArray[0]++;
					onopos.push(i);
				}
			}

			console.log("0 ono: "+sexCountArray[0]);//ono
			console.log("1 he : "+sexCountArray[1]);//man
			console.log("2 she: "+sexCountArray[2]);//woman

			console.log("sArray: "+sexArray.length);
		}
	});

}

function getGroupData() {//data for groupName,groupMembersCount, avatar.
	return obj;
}

function getParsedInfo() {//getting obj {groupID, postID}
	return parsed;
}

function waiting() {//maybe wait some time
	var se = 0;
	var t = setTimeout(function() {
		if (se > 1) {
			waiting();
			console.log('waits ----------------------------------');
			se = 0;
		}
		se++;
	}, 500);
}

function groupViewChanges(obj) {//changes UI view
	var a = obj;
	document.getElementById('groupName').innerHTML = 'Name: ' + a[0];
	document.getElementById('members_count').innerHTML = 'Count: ' + a[1];
	document.getElementById('av').src = a[2];
}

function parseLink() {// main function for parse link from text area.
	var link = String(document.getElementById('post').value);

	if (link != '' && (link.match('vk.com') == 'vk.com') && (link.match('wall-') == 'wall-')) {

		if (link.indexOf('=wall') != -1) {
			var adr = link.substring(link.indexOf('=wall') + 5, link.length);
			var p = adr.split('_');
			if ((p[0] === undefined) && (p[1] === undefined)) {
				errorMSG('Wrong: group or post or both');
			} else {
				if ((/\d+/.test(p[0])) && (/\d+/.test(p[1]))) {
					parsed[0] = p[0];
					parsed[1] = p[1];
					getGroupInfo(parsed[0]);
				}

			}
		} else {
			errorMSG('Wrong: not wall post');
		}
	} else {
		errorMSG('Wrong: not vk.com');
	}
}

function clearadress() {// clear text area
	document.getElementById('post').value = '';
}

function errorMSG(msg) {// shows error messages in main text area
	document.getElementById('post').value = msg;
	var t = setTimeout(clearadress, 1000);
}<|endoftext|>


<commit_before>/**
 * `modules/analytics` data store: setup-flow.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import {
	MODULES_ANALYTICS,
	SETUP_FLOW_MODE_LEGACY,
	SETUP_FLOW_MODE_UA,
	SETUP_FLOW_MODE_GA4,
	SETUP_FLOW_MODE_GA4_TRANSITIONAL,
	ACCOUNT_CREATE,
} from './constants';
import { MODULES_ANALYTICS_4 } from '../../analytics-4/datastore/constants';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';

const { createRegistrySelector } = Data;

const baseSelectors = {
	/**
	 * Gets the setup flow mode based on different conditions.
	 *
	 * @since 1.37.0
	 *
	 * @return {string} Setup flow mode.
	 */
	getSetupFlowMode: createRegistrySelector( ( select ) => () => {
		// Check to see if the Admin API is working—if it's `false` we should also use
		// the legacy analytics because the API isn't working properly.
		const isAdminAPIWorking = select(
			MODULES_ANALYTICS_4
		).isAdminAPIWorking();
		if ( isAdminAPIWorking === false ) {
			return SETUP_FLOW_MODE_LEGACY;
		}

		// Ensure the Analytics settings have loaded. If we check
		// `select( MODULES_ANALYTICS ).getAccountID();` directly, it
		// could return `undefined` because the settings are loading OR
		// because accountID is not set. Ensuring the settings are loaded
		// means an `undefined` accountID is legitimate.
		// See: https://github.com/google/site-kit-wp/pull/3260#discussion_r623924928
		if ( select( MODULES_ANALYTICS ).getSettings() === undefined ) {
			return undefined;
		}

		const accountID = select( MODULES_ANALYTICS ).getAccountID();

		// If no accountID exists then no account is selected. This means we should
		// use the UA setup flow.
		if ( ! accountID || accountID === ACCOUNT_CREATE ) {
			return SETUP_FLOW_MODE_UA;
		}

		const ga4Properties = select( MODULES_ANALYTICS_4 ).getProperties(
			accountID
		);

		if ( ga4Properties === undefined ) {
			return undefined;
		}

		// If there are no GA4 properties available for this account, don't use
		// GA4 and use the UA version.
		if ( ga4Properties.length === 0 ) {
			return SETUP_FLOW_MODE_UA;
		}

		const uaProperties = select( MODULES_ANALYTICS ).getProperties(
			accountID
		);

		if ( uaProperties === undefined ) {
			return undefined;
		}

		// If no UA properties exist and there are GA4 properties, use GA4-only.
		if ( uaProperties.length === 0 ) {
			return SETUP_FLOW_MODE_GA4;
		}

		// There are UA and GA4 properties, so use the transitional mode.
		return SETUP_FLOW_MODE_GA4_TRANSITIONAL;
	} ),

	/**
	 * Determines whether GA4 controls should be displayed or not.
	 *
	 * @since 1.37.0
	 *
	 * @return {boolean} TRUE if we can use GA4 controls, otherwise FALSE.
	 */
	canUseGA4Controls: createRegistrySelector( ( select ) => () => {
		const uaConnected = select( CORE_MODULES ).isModuleConnected(
			'analytics'
		);
		const ga4Connected = select( CORE_MODULES ).isModuleConnected(
			'analytics-4'
		);

		return uaConnected === ga4Connected;
	} ),
};

const store = Data.combineStores( {
	selectors: baseSelectors,
} );

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export default store;
<commit_msg>Enable GA4 controls if enableGA4 form value is on.<commit_after>/**
 * `modules/analytics` data store: setup-flow.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import {
	MODULES_ANALYTICS,
	SETUP_FLOW_MODE_LEGACY,
	SETUP_FLOW_MODE_UA,
	SETUP_FLOW_MODE_GA4,
	SETUP_FLOW_MODE_GA4_TRANSITIONAL,
	ACCOUNT_CREATE,
	FORM_SETUP,
} from './constants';
import { MODULES_ANALYTICS_4 } from '../../analytics-4/datastore/constants';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants';

const { createRegistrySelector } = Data;

const baseSelectors = {
	/**
	 * Gets the setup flow mode based on different conditions.
	 *
	 * @since 1.37.0
	 *
	 * @return {string} Setup flow mode.
	 */
	getSetupFlowMode: createRegistrySelector( ( select ) => () => {
		// Check to see if the Admin API is working—if it's `false` we should also use
		// the legacy analytics because the API isn't working properly.
		const isAdminAPIWorking = select(
			MODULES_ANALYTICS_4
		).isAdminAPIWorking();
		if ( isAdminAPIWorking === false ) {
			return SETUP_FLOW_MODE_LEGACY;
		}

		// Ensure the Analytics settings have loaded. If we check
		// `select( MODULES_ANALYTICS ).getAccountID();` directly, it
		// could return `undefined` because the settings are loading OR
		// because accountID is not set. Ensuring the settings are loaded
		// means an `undefined` accountID is legitimate.
		// See: https://github.com/google/site-kit-wp/pull/3260#discussion_r623924928
		if ( select( MODULES_ANALYTICS ).getSettings() === undefined ) {
			return undefined;
		}

		const accountID = select( MODULES_ANALYTICS ).getAccountID();

		// If no accountID exists then no account is selected. This means we should
		// use the UA setup flow.
		if ( ! accountID || accountID === ACCOUNT_CREATE ) {
			return SETUP_FLOW_MODE_UA;
		}

		const ga4Properties = select( MODULES_ANALYTICS_4 ).getProperties(
			accountID
		);

		if ( ga4Properties === undefined ) {
			return undefined;
		}

		// If there are no GA4 properties available for this account, don't use
		// GA4 and use the UA version.
		if ( ga4Properties.length === 0 ) {
			return SETUP_FLOW_MODE_UA;
		}

		const uaProperties = select( MODULES_ANALYTICS ).getProperties(
			accountID
		);

		if ( uaProperties === undefined ) {
			return undefined;
		}

		// If no UA properties exist and there are GA4 properties, use GA4-only.
		if ( uaProperties.length === 0 ) {
			return SETUP_FLOW_MODE_GA4;
		}

		// There are UA and GA4 properties, so use the transitional mode.
		return SETUP_FLOW_MODE_GA4_TRANSITIONAL;
	} ),

	/**
	 * Determines whether GA4 controls should be displayed or not.
	 *
	 * @since 1.37.0
	 *
	 * @return {boolean} TRUE if we can use GA4 controls, otherwise FALSE.
	 */
	canUseGA4Controls: createRegistrySelector( ( select ) => () => {
		const uaConnected = select( CORE_MODULES ).isModuleConnected(
			'analytics'
		);
		const ga4Connected = select( CORE_MODULES ).isModuleConnected(
			'analytics-4'
		);
		const enableGA4 = select( CORE_FORMS ).getValue(
			FORM_SETUP,
			'enableGA4'
		);

		return uaConnected === ga4Connected || enableGA4;
	} ),
};

const store = Data.combineStores( {
	selectors: baseSelectors,
} );

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export default store;
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
/**
 * Internal dependencies
 */
import { KEYS } from '../../constants';
import { DROPDOWN_ITEM_PROP_TYPE } from '../types';
import { TypographyPresets } from '../typography';

export const MenuContainer = styled.ul`
  align-items: flex-start;
  background-color: ${({ theme }) => theme.colors.white};
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  margin: 0;
  min-width: 210px;
  overflow: hidden;
  padding: 5px 0;
  pointer-events: auto;

  & > a {
    background-color: none;
    text-decoration: none;
    &:focus {
      box-shadow: none; /*override common js */
    }
  }
`;
MenuContainer.propTypes = {
  isOpen: PropTypes.bool,
};

export const MenuItem = styled.li`
  ${TypographyPresets.Small};
  ${({ theme, isDisabled, isHovering, separator }) => `
    margin-bottom: 0; /* override common js */
    padding: 5px 25px;
    background: ${isHovering && !isDisabled ? theme.colors.gray25 : 'none'};
    color: ${isDisabled ? theme.colors.gray400 : theme.colors.gray700};
    cursor: ${isDisabled ? 'default' : 'pointer'};
    display: flex;
    width: 100%;

    &:focus, &:active, &:hover {
      color: ${isDisabled ? theme.colors.gray400 : theme.colors.gray700};
    }

    ${
      separator === 'top'
        ? `
        border-top: 1px solid ${theme.colors.gray50};
      `
        : ''
    }

    ${
      separator === 'bottom'
        ? `
        border-bottom: 1px solid ${theme.colors.gray50};
      `
        : ''
    }
  `}
`;

MenuItem.propTypes = {
  isDisabled: PropTypes.bool,
  isHovering: PropTypes.bool,
};

const MenuItemContent = styled.span`
  align-self: flex-start;
  height: 100%;
  margin: auto 0;
`;

const Menu = ({ isOpen, currentValueIndex = 0, items, onSelect }) => {
  const [hoveredIndex, setHoveredIndex] = useState(currentValueIndex);
  const listRef = useRef(null);

  // eslint-disable-next-line consistent-return
  useEffect(() => {
    if (isOpen) {
      const handleKeyDown = (event) => {
        switch (event.key) {
          case KEYS.UP:
            event.preventDefault();
            if (hoveredIndex > 0) {
              setHoveredIndex(hoveredIndex - 1);
              if (listRef.current) {
                listRef.current.children[hoveredIndex - 1].focus();
              }
            }
            break;

          case KEYS.DOWN:
            event.preventDefault();
            if (hoveredIndex < items.length - 1) {
              setHoveredIndex(hoveredIndex + 1);
              if (listRef.current) {
                listRef.current.children[hoveredIndex + 1].focus();
              }
            }
            break;

          case KEYS.ENTER:
            event.preventDefault();
            if (onSelect) {
              onSelect(items[hoveredIndex]);
            }
            break;

          default:
            break;
        }
      };

      document.addEventListener('keydown', handleKeyDown);
      return () => document.removeEventListener('keydown', handleKeyDown);
    }
  }, [hoveredIndex, items, onSelect, isOpen]);

  useEffect(() => {
    if (listRef.current && isOpen) {
      listRef.current[currentValueIndex]?.focus();
    }
    setHoveredIndex(currentValueIndex);
  }, [currentValueIndex, isOpen]);

  const renderMenuItem = useCallback(
    (item, index) => {
      const itemIsDisabled = !item.value && item.value !== 0;
      const MenuItemPropsAsLink = item.url
        ? {
            target: '_blank',
            rel: 'noreferrer',
            href: item.url,
            as: 'a',
          }
        : {};

      return (
        <MenuItem
          key={`${item.value}_${index}`}
          isHovering={index === hoveredIndex}
          onClick={() => !itemIsDisabled && onSelect && onSelect(item)}
          onMouseEnter={() => setHoveredIndex(index)}
          isDisabled={itemIsDisabled}
          separator={item.separator}
          {...MenuItemPropsAsLink}
        >
          <MenuItemContent>{item.label}</MenuItemContent>
        </MenuItem>
      );
    },
    [hoveredIndex, onSelect]
  );

  return (
    <MenuContainer ref={listRef}>
      {items.map((item, index) => renderMenuItem(item, index))}
    </MenuContainer>
  );
};

export const MenuProps = {
  items: PropTypes.arrayOf(DROPDOWN_ITEM_PROP_TYPE).isRequired,
  currentValueIndex: PropTypes.number,
  isOpen: PropTypes.bool,
  onSelect: PropTypes.func,
};

Menu.propTypes = MenuProps;

export default Menu;
<commit_msg>use class names for adding borders to story menus instead of conditionalyl rendering in style to avoid menu items that do not use borders from having styles of `false:false`.<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
/**
 * Internal dependencies
 */
import { KEYS } from '../../constants';
import { DROPDOWN_ITEM_PROP_TYPE } from '../types';
import { TypographyPresets } from '../typography';

export const MenuContainer = styled.ul`
  align-items: flex-start;
  background-color: ${({ theme }) => theme.colors.white};
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  margin: 0;
  min-width: 210px;
  overflow: hidden;
  padding: 5px 0;
  pointer-events: auto;

  & > a {
    background-color: none;
    text-decoration: none;
    &:focus {
      box-shadow: none; /*override common js */
    }
  }
`;
MenuContainer.propTypes = {
  isOpen: PropTypes.bool,
};

export const MenuItem = styled.li`
  ${TypographyPresets.Small};
  ${({ theme, isDisabled, isHovering }) => `
    margin-bottom: 0; /* override common js */
    padding: 5px 25px;
    background: ${isHovering && !isDisabled ? theme.colors.gray25 : 'none'};
    color: ${isDisabled ? theme.colors.gray400 : theme.colors.gray700};
    cursor: ${isDisabled ? 'default' : 'pointer'};
    display: flex;
    width: 100%;

    &.separatorTop {
      border-top: 1px solid ${theme.colors.gray50};
    }

    &.separatorBottom {
      border-bottom: 1px solid ${theme.colors.gray50};
    }

    &:focus, &:active, &:hover {
      color: ${isDisabled ? theme.colors.gray400 : theme.colors.gray700};
    }

  `}
`;

MenuItem.propTypes = {
  isDisabled: PropTypes.bool,
  isHovering: PropTypes.bool,
};

const MenuItemContent = styled.span`
  align-self: flex-start;
  height: 100%;
  margin: auto 0;
`;

const Menu = ({ isOpen, currentValueIndex = 0, items, onSelect }) => {
  const [hoveredIndex, setHoveredIndex] = useState(currentValueIndex);
  const listRef = useRef(null);

  // eslint-disable-next-line consistent-return
  useEffect(() => {
    if (isOpen) {
      const handleKeyDown = (event) => {
        switch (event.key) {
          case KEYS.UP:
            event.preventDefault();
            if (hoveredIndex > 0) {
              setHoveredIndex(hoveredIndex - 1);
              if (listRef.current) {
                listRef.current.children[hoveredIndex - 1].focus();
              }
            }
            break;

          case KEYS.DOWN:
            event.preventDefault();
            if (hoveredIndex < items.length - 1) {
              setHoveredIndex(hoveredIndex + 1);
              if (listRef.current) {
                listRef.current.children[hoveredIndex + 1].focus();
              }
            }
            break;

          case KEYS.ENTER:
            event.preventDefault();
            if (onSelect) {
              onSelect(items[hoveredIndex]);
            }
            break;

          default:
            break;
        }
      };

      document.addEventListener('keydown', handleKeyDown);
      return () => document.removeEventListener('keydown', handleKeyDown);
    }
  }, [hoveredIndex, items, onSelect, isOpen]);

  useEffect(() => {
    if (listRef.current && isOpen) {
      listRef.current[currentValueIndex]?.focus();
    }
    setHoveredIndex(currentValueIndex);
  }, [currentValueIndex, isOpen]);

  const renderMenuItem = useCallback(
    (item, index) => {
      const itemIsDisabled = !item.value && item.value !== 0;
      const MenuItemPropsAsLink = item.url
        ? {
            target: '_blank',
            rel: 'noreferrer',
            href: item.url,
            as: 'a',
          }
        : {};

      return (
        <MenuItem
          key={`${item.value}_${index}`}
          isHovering={index === hoveredIndex}
          onClick={() => !itemIsDisabled && onSelect && onSelect(item)}
          onMouseEnter={() => setHoveredIndex(index)}
          isDisabled={itemIsDisabled}
          className={
            (item.separator === 'top' && 'separatorTop') ||
            (item.separator === 'bottom' && 'separatorBottom')
          }
          {...MenuItemPropsAsLink}
        >
          <MenuItemContent>{item.label}</MenuItemContent>
        </MenuItem>
      );
    },
    [hoveredIndex, onSelect]
  );

  return (
    <MenuContainer ref={listRef}>
      {items.map((item, index) => renderMenuItem(item, index))}
    </MenuContainer>
  );
};

export const MenuProps = {
  items: PropTypes.arrayOf(DROPDOWN_ITEM_PROP_TYPE).isRequired,
  currentValueIndex: PropTypes.number,
  isOpen: PropTypes.bool,
  onSelect: PropTypes.func,
};

Menu.propTypes = MenuProps;

export default Menu;
<|endoftext|>


<commit_before>/**
    * @ngdoc service
    * @name umbraco.resources.mediaResource
    * @description Loads in data for media
    **/
function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {

    /** internal method process the saving of data and post processing the result */
    function saveMediaItem(content, action, files) {
        return umbRequestHelper.postSaveContent({
            restApiUrl: umbRequestHelper.getApiUrl(
                   "mediaApiBaseUrl",
                   "PostSave"),
            content: content,
            action: action,
            files: files,
            dataFormatter: function (c, a) {
                return umbDataFormatter.formatMediaPostData(c, a);
            }
        });
    }

    return {

        getRecycleBin: function () {
            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetRecycleBin")),
                  'Failed to retrieve data for media recycle bin');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#sort
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Sorts all children below a given parent node id, based on a collection of node-ids
          *
          * ##usage
          *

          * var ids = [123,34533,2334,23434];
          * mediaResource.sort({ sortedIds: ids })
          *    .then(function() {
          *        $scope.complete = true;
          *    });
          *

          * @param {Object} args arguments object
          * @param {Int} args.parentId the ID of the parent node
          * @param {Array} options.sortedIds array of node IDs as they should be sorted
          * @returns {Promise} resourcePromise object.
          *
          */
        sort: function (args) {
            if (!args) {
                throw "args cannot be null";
            }
            if (!args.parentId) {
                throw "args.parentId cannot be null";
            }
            if (!args.sortedIds) {
                throw "args.sortedIds cannot be null";
            }

            return umbRequestHelper.resourcePromise(
                   $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"),
                         {
                             parentId: args.parentId,
                             idSortOrder: args.sortedIds
                         }),
                   'Failed to sort media');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#move
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Moves a node underneath a new parentId
          *
          * ##usage
          *

          * mediaResource.move({ parentId: 1244, id: 123 })
          *    .then(function() {
          *        alert("node was moved");
          *    }, function(err){
          *      alert("node didnt move:" + err.data.Message);
          *    });
          *

          * @param {Object} args arguments object
          * @param {Int} args.idd the ID of the node to move
          * @param {Int} args.parentId the ID of the parent node to move to
          * @returns {Promise} resourcePromise object.
          *
          */
        move: function (args) {
            if (!args) {
                throw "args cannot be null";
            }
            if (!args.parentId) {
                throw "args.parentId cannot be null";
            }
            if (!args.id) {
                throw "args.id cannot be null";
            }

            return umbRequestHelper.resourcePromise(
                   $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"),
                         {
                             parentId: args.parentId,
                             id: args.id
                         }),
                   'Failed to move media');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getById
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Gets a media item with a given id
          *
          * ##usage
          *

          * mediaResource.getById(1234)
          *    .then(function(media) {
          *        var myMedia = media;
          *        alert('its here!');
          *    });
          *

          *
          * @param {Int} id id of media item to return
          * @returns {Promise} resourcePromise object containing the media item.
          *
          */
        getById: function (id) {

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetById",
                              [{ id: id }])),
                  'Failed to retrieve data for media id ' + id);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#deleteById
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Deletes a media item with a given id
          *
          * ##usage
          *

          * mediaResource.deleteById(1234)
          *    .then(function() {
          *        alert('its gone!');
          *    });
          *

          *
          * @param {Int} id id of media item to delete
          * @returns {Promise} resourcePromise object.
          *
          */
        deleteById: function (id) {
            return umbRequestHelper.resourcePromise(
                   $http.post(
                         umbRequestHelper.getApiUrl(
                               "mediaApiBaseUrl",
                               "DeleteById",
                               [{ id: id }])),
                   'Failed to delete item ' + id);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getByIds
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Gets an array of media items, given a collection of ids
          *
          * ##usage
          *

          * mediaResource.getByIds( [1234,2526,28262])
          *    .then(function(mediaArray) {
          *        var myDoc = contentArray;
          *        alert('they are here!');
          *    });
          *

          *
          * @param {Array} ids ids of media items to return as an array
          * @returns {Promise} resourcePromise object containing the media items array.
          *
          */
        getByIds: function (ids) {

            var idQuery = "";
            _.each(ids, function (item) {
                idQuery += "ids=" + item + "&";
            });

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetByIds",
                              idQuery)),
                  'Failed to retrieve data for media ids ' + ids);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getScaffold
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias.
          *
          * - Parent Id must be provided so umbraco knows where to store the media
          * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold
          *
          * The scaffold is used to build editors for media that has not yet been populated with data.
          *
          * ##usage
          *

          * mediaResource.getScaffold(1234, 'folder')
          *    .then(function(scaffold) {
          *        var myDoc = scaffold;
          *        myDoc.name = "My new media item";
          *
          *        mediaResource.save(myDoc, true)
          *            .then(function(media){
          *                alert("Retrieved, updated and saved again");
          *            });
          *    });
          *

          *
          * @param {Int} parentId id of media item to return
          * @param {String} alias mediatype alias to base the scaffold on
          * @returns {Promise} resourcePromise object containing the media scaffold.
          *
          */
        getScaffold: function (parentId, alias) {

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetEmpty",
                              [{ contentTypeAlias: alias }, { parentId: parentId }])),
                  'Failed to retrieve data for empty media item type ' + alias);

        },

        rootMedia: function () {

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetRootMedia")),
                  'Failed to retrieve data for root media');

        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getChildren
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Gets children of a media item with a given id
          *
          * ##usage
          *

          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
          *    .then(function(contentArray) {
          *        var children = contentArray;
          *        alert('they are here!');
          *    });
          *

          *
          * @param {Int} parentid id of content item to return children of
          * @param {Object} options optional options object
          * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0
          * @param {Int} options.pageNumber if paging data, current page index, default = 0
          * @param {String} options.filter if provided, query will only return those with names matching the filter
          * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending`
          * @param {String} options.orderBy property to order items by, default: `SortOrder`
          * @returns {Promise} resourcePromise object containing an array of content items.
          *
          */
        getChildren: function (parentId, options) {

            var defaults = {
                pageSize: 0,
                pageNumber: 0,
                filter: '',
                orderDirection: "Ascending",
                orderBy: "SortOrder",
                orderBySystemField: true
            };
            if (options === undefined) {
                options = {};
            }
            //overwrite the defaults if there are any specified
            angular.extend(defaults, options);
            //now copy back to the options we will use
            options = defaults;
            //change asc/desct
            if (options.orderDirection === "asc") {
                options.orderDirection = "Ascending";
            }
            else if (options.orderDirection === "desc") {
                options.orderDirection = "Descending";
            }

            //converts the value to a js bool
            function toBool(v) {
                if (angular.isNumber(v)) {
                    return v > 0;
                }
                if (angular.isString(v)) {
                    return v === "true";
                }
                if (typeof v === "boolean") {
                    return v;
                }
                return false;
            }

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetChildren",
                              [
                                    { id: parentId },
                                    { pageNumber: options.pageNumber },
                                    { pageSize: options.pageSize },
                                    { orderBy: options.orderBy },
                                    { orderDirection: options.orderDirection },
                                    { orderBySystemField: toBool(options.orderBySystemField) },
                                    { filter: options.filter }
                              ])),
                  'Failed to retrieve children for media item ' + parentId);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#save
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation
          * if the media item needs to have files attached, they must be provided as the files param and passed separately
          *
          *
          * ##usage
          *

          * mediaResource.getById(1234)
          *    .then(function(media) {
          *          media.name = "I want a new name!";
          *          mediaResource.save(media, false)
          *            .then(function(media){
          *                alert("Retrieved, updated and saved again");
          *            });
          *    });
          *

          *
          * @param {Object} media The media item object with changes applied
          * @param {Bool} isNew set to true to create a new item or to update an existing
          * @param {Array} files collection of files for the media item
          * @returns {Promise} resourcePromise object containing the saved media item.
          *
          */
        save: function (media, isNew, files) {
            return saveMediaItem(media, "save" + (isNew ? "New" : ""), files);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#addFolder
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Shorthand for adding a media item of the type "Folder" under a given parent ID
          *
          * ##usage
          *

          * mediaResource.addFolder("My gallery", 1234)
          *    .then(function(folder) {
          *        alert('New folder');
          *    });
          *

          *
          * @param {string} name Name of the folder to create
          * @param {int} parentId Id of the media item to create the folder underneath
          * @returns {Promise} resourcePromise object.
          *
          */
        addFolder: function (name, parentId) {
            return umbRequestHelper.resourcePromise(
                   $http.post(umbRequestHelper
                         .getApiUrl("mediaApiBaseUrl", "PostAddFolder"),
                         {
                             name: name,
                             parentId: parentId
                         }),
                   'Failed to add folder');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getChildFolders
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Retrieves all media children with types used as folders.
          * Uses the convention of looking for media items with mediaTypes ending in
          * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc,
          *
          * NOTE: This will return a max of 500 folders, if more is required it needs to be paged
          *
          * ##usage
          *

          * mediaResource.getChildFolders(1234)
          *    .then(function(data) {
          *        alert('folders');
          *    });
          *

          *
          * @param {int} parentId Id of the media item to query for child folders
          * @returns {Promise} resourcePromise object.
          *
          */
        getChildFolders: function (parentId) {
            if (!parentId) {
                parentId = -1;
            }

            //NOTE: This will return a max of 500 folders, if more is required it needs to be paged
            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetChildFolders",
                            {
                                id: parentId
                            })),
                  'Failed to retrieve child folders for media item ' + parentId);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#emptyRecycleBin
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Empties the media recycle bin
          *
          * ##usage
          *

          * mediaResource.emptyRecycleBin()
          *    .then(function() {
          *        alert('its empty!');
          *    });
          *

          *
          * @returns {Promise} resourcePromise object.
          *
          */
        emptyRecycleBin: function () {
            return umbRequestHelper.resourcePromise(
                   $http.post(
                         umbRequestHelper.getApiUrl(
                               "mediaApiBaseUrl",
                               "EmptyRecycleBin")),
                   'Failed to empty the recycle bin');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#search
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Empties the media recycle bin
          *
          * ##usage
          *

          * mediaResource.search("my search", 1, 100, -1)
          *    .then(function(searchResult) {
          *        alert('it's here!');
          *    });
          *

          *
          * @param {string} query The search query
          * @param {int} pageNumber The page number
          * @param {int} pageSize The number of media items on a page
          * @param {int} searchFrom Id to search from
          * @returns {Promise} resourcePromise object.
          *
          */
        search: function (query, pageNumber, pageSize, searchFrom) {

            var args = [
                { "query": query },
                { "pageNumber": pageNumber },
                { "pageSize": pageSize },
                { "searchFrom": searchFrom }
            ];

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "Search",
                              args)),
                  'Failed to retrieve media items for search: ' + query);
        }

    };
}

angular.module('umbraco.resources').factory('mediaResource', mediaResource);
<commit_msg>U4-10999 Fixed wrong description on mediaresource.search<commit_after>/**
    * @ngdoc service
    * @name umbraco.resources.mediaResource
    * @description Loads in data for media
    **/
function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {

    /** internal method process the saving of data and post processing the result */
    function saveMediaItem(content, action, files) {
        return umbRequestHelper.postSaveContent({
            restApiUrl: umbRequestHelper.getApiUrl(
                   "mediaApiBaseUrl",
                   "PostSave"),
            content: content,
            action: action,
            files: files,
            dataFormatter: function (c, a) {
                return umbDataFormatter.formatMediaPostData(c, a);
            }
        });
    }

    return {

        getRecycleBin: function () {
            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetRecycleBin")),
                  'Failed to retrieve data for media recycle bin');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#sort
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Sorts all children below a given parent node id, based on a collection of node-ids
          *
          * ##usage
          *

          * var ids = [123,34533,2334,23434];
          * mediaResource.sort({ sortedIds: ids })
          *    .then(function() {
          *        $scope.complete = true;
          *    });
          *

          * @param {Object} args arguments object
          * @param {Int} args.parentId the ID of the parent node
          * @param {Array} options.sortedIds array of node IDs as they should be sorted
          * @returns {Promise} resourcePromise object.
          *
          */
        sort: function (args) {
            if (!args) {
                throw "args cannot be null";
            }
            if (!args.parentId) {
                throw "args.parentId cannot be null";
            }
            if (!args.sortedIds) {
                throw "args.sortedIds cannot be null";
            }

            return umbRequestHelper.resourcePromise(
                   $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostSort"),
                         {
                             parentId: args.parentId,
                             idSortOrder: args.sortedIds
                         }),
                   'Failed to sort media');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#move
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Moves a node underneath a new parentId
          *
          * ##usage
          *

          * mediaResource.move({ parentId: 1244, id: 123 })
          *    .then(function() {
          *        alert("node was moved");
          *    }, function(err){
          *      alert("node didnt move:" + err.data.Message);
          *    });
          *

          * @param {Object} args arguments object
          * @param {Int} args.idd the ID of the node to move
          * @param {Int} args.parentId the ID of the parent node to move to
          * @returns {Promise} resourcePromise object.
          *
          */
        move: function (args) {
            if (!args) {
                throw "args cannot be null";
            }
            if (!args.parentId) {
                throw "args.parentId cannot be null";
            }
            if (!args.id) {
                throw "args.id cannot be null";
            }

            return umbRequestHelper.resourcePromise(
                   $http.post(umbRequestHelper.getApiUrl("mediaApiBaseUrl", "PostMove"),
                         {
                             parentId: args.parentId,
                             id: args.id
                         }),
                   'Failed to move media');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getById
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Gets a media item with a given id
          *
          * ##usage
          *

          * mediaResource.getById(1234)
          *    .then(function(media) {
          *        var myMedia = media;
          *        alert('its here!');
          *    });
          *

          *
          * @param {Int} id id of media item to return
          * @returns {Promise} resourcePromise object containing the media item.
          *
          */
        getById: function (id) {

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetById",
                              [{ id: id }])),
                  'Failed to retrieve data for media id ' + id);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#deleteById
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Deletes a media item with a given id
          *
          * ##usage
          *

          * mediaResource.deleteById(1234)
          *    .then(function() {
          *        alert('its gone!');
          *    });
          *

          *
          * @param {Int} id id of media item to delete
          * @returns {Promise} resourcePromise object.
          *
          */
        deleteById: function (id) {
            return umbRequestHelper.resourcePromise(
                   $http.post(
                         umbRequestHelper.getApiUrl(
                               "mediaApiBaseUrl",
                               "DeleteById",
                               [{ id: id }])),
                   'Failed to delete item ' + id);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getByIds
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Gets an array of media items, given a collection of ids
          *
          * ##usage
          *

          * mediaResource.getByIds( [1234,2526,28262])
          *    .then(function(mediaArray) {
          *        var myDoc = contentArray;
          *        alert('they are here!');
          *    });
          *

          *
          * @param {Array} ids ids of media items to return as an array
          * @returns {Promise} resourcePromise object containing the media items array.
          *
          */
        getByIds: function (ids) {

            var idQuery = "";
            _.each(ids, function (item) {
                idQuery += "ids=" + item + "&";
            });

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetByIds",
                              idQuery)),
                  'Failed to retrieve data for media ids ' + ids);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getScaffold
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Returns a scaffold of an empty media item, given the id of the media item to place it underneath and the media type alias.
          *
          * - Parent Id must be provided so umbraco knows where to store the media
          * - Media Type alias must be provided so umbraco knows which properties to put on the media scaffold
          *
          * The scaffold is used to build editors for media that has not yet been populated with data.
          *
          * ##usage
          *

          * mediaResource.getScaffold(1234, 'folder')
          *    .then(function(scaffold) {
          *        var myDoc = scaffold;
          *        myDoc.name = "My new media item";
          *
          *        mediaResource.save(myDoc, true)
          *            .then(function(media){
          *                alert("Retrieved, updated and saved again");
          *            });
          *    });
          *

          *
          * @param {Int} parentId id of media item to return
          * @param {String} alias mediatype alias to base the scaffold on
          * @returns {Promise} resourcePromise object containing the media scaffold.
          *
          */
        getScaffold: function (parentId, alias) {

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetEmpty",
                              [{ contentTypeAlias: alias }, { parentId: parentId }])),
                  'Failed to retrieve data for empty media item type ' + alias);

        },

        rootMedia: function () {

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetRootMedia")),
                  'Failed to retrieve data for root media');

        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getChildren
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Gets children of a media item with a given id
          *
          * ##usage
          *

          * mediaResource.getChildren(1234, {pageSize: 10, pageNumber: 2})
          *    .then(function(contentArray) {
          *        var children = contentArray;
          *        alert('they are here!');
          *    });
          *

          *
          * @param {Int} parentid id of content item to return children of
          * @param {Object} options optional options object
          * @param {Int} options.pageSize if paging data, number of nodes per page, default = 0
          * @param {Int} options.pageNumber if paging data, current page index, default = 0
          * @param {String} options.filter if provided, query will only return those with names matching the filter
          * @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Ascending`
          * @param {String} options.orderBy property to order items by, default: `SortOrder`
          * @returns {Promise} resourcePromise object containing an array of content items.
          *
          */
        getChildren: function (parentId, options) {

            var defaults = {
                pageSize: 0,
                pageNumber: 0,
                filter: '',
                orderDirection: "Ascending",
                orderBy: "SortOrder",
                orderBySystemField: true
            };
            if (options === undefined) {
                options = {};
            }
            //overwrite the defaults if there are any specified
            angular.extend(defaults, options);
            //now copy back to the options we will use
            options = defaults;
            //change asc/desct
            if (options.orderDirection === "asc") {
                options.orderDirection = "Ascending";
            }
            else if (options.orderDirection === "desc") {
                options.orderDirection = "Descending";
            }

            //converts the value to a js bool
            function toBool(v) {
                if (angular.isNumber(v)) {
                    return v > 0;
                }
                if (angular.isString(v)) {
                    return v === "true";
                }
                if (typeof v === "boolean") {
                    return v;
                }
                return false;
            }

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetChildren",
                              [
                                    { id: parentId },
                                    { pageNumber: options.pageNumber },
                                    { pageSize: options.pageSize },
                                    { orderBy: options.orderBy },
                                    { orderDirection: options.orderDirection },
                                    { orderBySystemField: toBool(options.orderBySystemField) },
                                    { filter: options.filter }
                              ])),
                  'Failed to retrieve children for media item ' + parentId);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#save
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Saves changes made to a media item, if the media item is new, the isNew paramater must be passed to force creation
          * if the media item needs to have files attached, they must be provided as the files param and passed separately
          *
          *
          * ##usage
          *

          * mediaResource.getById(1234)
          *    .then(function(media) {
          *          media.name = "I want a new name!";
          *          mediaResource.save(media, false)
          *            .then(function(media){
          *                alert("Retrieved, updated and saved again");
          *            });
          *    });
          *

          *
          * @param {Object} media The media item object with changes applied
          * @param {Bool} isNew set to true to create a new item or to update an existing
          * @param {Array} files collection of files for the media item
          * @returns {Promise} resourcePromise object containing the saved media item.
          *
          */
        save: function (media, isNew, files) {
            return saveMediaItem(media, "save" + (isNew ? "New" : ""), files);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#addFolder
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Shorthand for adding a media item of the type "Folder" under a given parent ID
          *
          * ##usage
          *

          * mediaResource.addFolder("My gallery", 1234)
          *    .then(function(folder) {
          *        alert('New folder');
          *    });
          *

          *
          * @param {string} name Name of the folder to create
          * @param {int} parentId Id of the media item to create the folder underneath
          * @returns {Promise} resourcePromise object.
          *
          */
        addFolder: function (name, parentId) {
            return umbRequestHelper.resourcePromise(
                   $http.post(umbRequestHelper
                         .getApiUrl("mediaApiBaseUrl", "PostAddFolder"),
                         {
                             name: name,
                             parentId: parentId
                         }),
                   'Failed to add folder');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#getChildFolders
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Retrieves all media children with types used as folders.
          * Uses the convention of looking for media items with mediaTypes ending in
          * *Folder so will match "Folder", "bannerFolder", "secureFolder" etc,
          *
          * NOTE: This will return a max of 500 folders, if more is required it needs to be paged
          *
          * ##usage
          *

          * mediaResource.getChildFolders(1234)
          *    .then(function(data) {
          *        alert('folders');
          *    });
          *

          *
          * @param {int} parentId Id of the media item to query for child folders
          * @returns {Promise} resourcePromise object.
          *
          */
        getChildFolders: function (parentId) {
            if (!parentId) {
                parentId = -1;
            }

            //NOTE: This will return a max of 500 folders, if more is required it needs to be paged
            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "GetChildFolders",
                            {
                                id: parentId
                            })),
                  'Failed to retrieve child folders for media item ' + parentId);
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#emptyRecycleBin
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Empties the media recycle bin
          *
          * ##usage
          *

          * mediaResource.emptyRecycleBin()
          *    .then(function() {
          *        alert('its empty!');
          *    });
          *

          *
          * @returns {Promise} resourcePromise object.
          *
          */
        emptyRecycleBin: function () {
            return umbRequestHelper.resourcePromise(
                   $http.post(
                         umbRequestHelper.getApiUrl(
                               "mediaApiBaseUrl",
                               "EmptyRecycleBin")),
                   'Failed to empty the recycle bin');
        },

        /**
          * @ngdoc method
          * @name umbraco.resources.mediaResource#search
          * @methodOf umbraco.resources.mediaResource
          *
          * @description
          * Paginated search for media items starting on the supplied nodeId
          *
          * ##usage
          *

          * mediaResource.search("my search", 1, 100, -1)
          *    .then(function(searchResult) {
          *        alert('it's here!');
          *    });
          *

          *
          * @param {string} query The search query
          * @param {int} pageNumber The page number
          * @param {int} pageSize The number of media items on a page
          * @param {int} searchFrom NodeId to search from (-1 for root)
          * @returns {Promise} resourcePromise object.
          *
          */
        search: function (query, pageNumber, pageSize, searchFrom) {

            var args = [
                { "query": query },
                { "pageNumber": pageNumber },
                { "pageSize": pageSize },
                { "searchFrom": searchFrom }
            ];

            return umbRequestHelper.resourcePromise(
                  $http.get(
                        umbRequestHelper.getApiUrl(
                              "mediaApiBaseUrl",
                              "Search",
                              args)),
                  'Failed to retrieve media items for search: ' + query);
        }

    };
}

angular.module('umbraco.resources').factory('mediaResource', mediaResource);
<|endoftext|>


<commit_before>/**
 * Copyright 2013-2014 Facebook, Inc.
 *
 * 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.
 *
 * @jsx React.DOM
 * @emails react-core
 */

"use strict";

var emptyFunction = require('emptyFunction');
var mocks = require('mocks');

describe('ReactDOMTextarea', function() {
  var React;
  var ReactLink;
  var ReactTestUtils;

  var renderTextarea;

  beforeEach(function() {
    React = require('React');
    ReactLink = require('ReactLink');
    ReactTestUtils = require('ReactTestUtils');

    renderTextarea = function(component) {
      var stub = ReactTestUtils.renderIntoDocument(component);
      var node = stub.getDOMNode();
      // Polyfilling the browser's quirky behavior.
      node.value = node.innerHTML;
      return stub;
    };
  });

  it('should allow setting `defaultValue`', function() {
    var stub = <textarea defaultValue="giraffe" />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    // Changing `defaultValue` should do nothing.
    stub.replaceProps({defaultValue: 'gorilla'});
    expect(node.value).toEqual('giraffe');
  });

  it('should display `defaultValue` of number 0', function() {
    var stub = <textarea defaultValue={0} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('0');
  });

  it('should display "false" for `defaultValue` of `false`', function() {
    var stub = <textarea type="text" defaultValue={false} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('false');
  });

  it('should display "foobar" for `defaultValue` of `objToString`', function() {
    var objToString = {
      toString: function() {
        return "foobar";
      }
    };

    var stub = <textarea type="text" defaultValue={objToString} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('foobar');
  });

  it('should not render value as an attribute', function() {
    var stub = <textarea value="giraffe" />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.getAttribute('value')).toBe(null);
  });

  it('should display `value` of number 0', function() {
    var stub = <textarea value={0} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('0');
  });

  it('should allow setting `value` to `giraffe`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    stub.replaceProps({value: 'gorilla', onChange: emptyFunction});
    expect(node.value).toEqual('gorilla');
  });

  it('should allow setting `value` to `true`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    stub.replaceProps({value: true, onChange: emptyFunction});
    expect(node.value).toEqual('true');
  });

  it('should allow setting `value` to `false`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    stub.replaceProps({value: false});
    expect(node.value).toEqual('false');
  });

  it('should allow setting `value` to `objToString`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    var objToString = {
      toString: function() {
        return "foo";
      }
    };
    stub.replaceProps({value: objToString, onChange: emptyFunction});
    expect(node.value).toEqual('foo');
  });

  it('should properly control a value of number `0`', function() {
    var stub = <textarea value={0} onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    node.value = 'giraffe';
    ReactTestUtils.Simulate.change(node);
    expect(node.value).toBe('0');
  });

  it('should treat children like `defaultValue`', function() {
    spyOn(console, 'warn');

    var stub = <textarea>giraffe</textarea>;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('giraffe');

    // Changing children should do nothing, it functions like `defaultValue`.
    stub.replaceProps({children: 'gorilla'});
    expect(node.value).toEqual('giraffe');
  });

  it('should allow numbers as children', function() {
    spyOn(console, 'warn');
    var node = renderTextarea(<textarea>{17}</textarea>).getDOMNode();
    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('17');
  });

  it('should allow booleans as children', function() {
    spyOn(console, 'warn');
    var node = renderTextarea(<textarea>{false}</textarea>).getDOMNode();
    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('false');
  });

  it('should allow objects as children', function() {
    spyOn(console, 'warn');
    var obj = {
      toString: function() {
        return "sharkswithlasers";
      }
    };
    var node = renderTextarea(<textarea>{obj}</textarea>).getDOMNode();
    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('sharkswithlasers');
  });

  it('should throw with multiple or invalid children', function() {
    spyOn(console, 'warn');

    expect(function() {
      ReactTestUtils.renderIntoDocument(
        <textarea>{'hello'}{'there'}</textarea>
      );
    }).toThrow();

    expect(console.warn.argsForCall.length).toBe(1);

    var node;
    expect(function() {
      node = renderTextarea(<textarea></textarea>).getDOMNode();
    }).not.toThrow();

    expect(node.value).toBe('[object Object]');

    expect(console.warn.argsForCall.length).toBe(2);
  });

  it('should support ReactLink', function() {
    var container = document.createElement('div');
    var link = new ReactLink('yolo', mocks.getMockFunction());
    var instance = <textarea valueLink={link} />;

    instance = React.renderComponent(instance, container);

    expect(instance.getDOMNode().value).toBe('yolo');
    expect(link.value).toBe('yolo');
    expect(link.requestChange.mock.calls.length).toBe(0);

    instance.getDOMNode().value = 'test';
    ReactTestUtils.Simulate.change(instance.getDOMNode());

    expect(link.requestChange.mock.calls.length).toBe(1);
    expect(link.requestChange.mock.calls[0][0]).toEqual('test');
  });
});
<commit_msg>Adds empty onChange handler to textarea test<commit_after>/**
 * Copyright 2013-2014 Facebook, Inc.
 *
 * 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.
 *
 * @jsx React.DOM
 * @emails react-core
 */

"use strict";

var emptyFunction = require('emptyFunction');
var mocks = require('mocks');

describe('ReactDOMTextarea', function() {
  var React;
  var ReactLink;
  var ReactTestUtils;

  var renderTextarea;

  beforeEach(function() {
    React = require('React');
    ReactLink = require('ReactLink');
    ReactTestUtils = require('ReactTestUtils');

    renderTextarea = function(component) {
      var stub = ReactTestUtils.renderIntoDocument(component);
      var node = stub.getDOMNode();
      // Polyfilling the browser's quirky behavior.
      node.value = node.innerHTML;
      return stub;
    };
  });

  it('should allow setting `defaultValue`', function() {
    var stub = <textarea defaultValue="giraffe" />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    // Changing `defaultValue` should do nothing.
    stub.replaceProps({defaultValue: 'gorilla'});
    expect(node.value).toEqual('giraffe');
  });

  it('should display `defaultValue` of number 0', function() {
    var stub = <textarea defaultValue={0} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('0');
  });

  it('should display "false" for `defaultValue` of `false`', function() {
    var stub = <textarea type="text" defaultValue={false} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('false');
  });

  it('should display "foobar" for `defaultValue` of `objToString`', function() {
    var objToString = {
      toString: function() {
        return "foobar";
      }
    };

    var stub = <textarea type="text" defaultValue={objToString} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('foobar');
  });

  it('should not render value as an attribute', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.getAttribute('value')).toBe(null);
  });

  it('should display `value` of number 0', function() {
    var stub = <textarea value={0} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('0');
  });

  it('should allow setting `value` to `giraffe`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    stub.replaceProps({value: 'gorilla', onChange: emptyFunction});
    expect(node.value).toEqual('gorilla');
  });

  it('should allow setting `value` to `true`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    stub.replaceProps({value: true, onChange: emptyFunction});
    expect(node.value).toEqual('true');
  });

  it('should allow setting `value` to `false`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    stub.replaceProps({value: false});
    expect(node.value).toEqual('false');
  });

  it('should allow setting `value` to `objToString`', function() {
    var stub = <textarea value="giraffe" onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(node.value).toBe('giraffe');

    var objToString = {
      toString: function() {
        return "foo";
      }
    };
    stub.replaceProps({value: objToString, onChange: emptyFunction});
    expect(node.value).toEqual('foo');
  });

  it('should properly control a value of number `0`', function() {
    var stub = <textarea value={0} onChange={emptyFunction} />;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    node.value = 'giraffe';
    ReactTestUtils.Simulate.change(node);
    expect(node.value).toBe('0');
  });

  it('should treat children like `defaultValue`', function() {
    spyOn(console, 'warn');

    var stub = <textarea>giraffe</textarea>;
    stub = renderTextarea(stub);
    var node = stub.getDOMNode();

    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('giraffe');

    // Changing children should do nothing, it functions like `defaultValue`.
    stub.replaceProps({children: 'gorilla'});
    expect(node.value).toEqual('giraffe');
  });

  it('should allow numbers as children', function() {
    spyOn(console, 'warn');
    var node = renderTextarea(<textarea>{17}</textarea>).getDOMNode();
    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('17');
  });

  it('should allow booleans as children', function() {
    spyOn(console, 'warn');
    var node = renderTextarea(<textarea>{false}</textarea>).getDOMNode();
    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('false');
  });

  it('should allow objects as children', function() {
    spyOn(console, 'warn');
    var obj = {
      toString: function() {
        return "sharkswithlasers";
      }
    };
    var node = renderTextarea(<textarea>{obj}</textarea>).getDOMNode();
    expect(console.warn.argsForCall.length).toBe(1);
    expect(node.value).toBe('sharkswithlasers');
  });

  it('should throw with multiple or invalid children', function() {
    spyOn(console, 'warn');

    expect(function() {
      ReactTestUtils.renderIntoDocument(
        <textarea>{'hello'}{'there'}</textarea>
      );
    }).toThrow();

    expect(console.warn.argsForCall.length).toBe(1);

    var node;
    expect(function() {
      node = renderTextarea(<textarea></textarea>).getDOMNode();
    }).not.toThrow();

    expect(node.value).toBe('[object Object]');

    expect(console.warn.argsForCall.length).toBe(2);
  });

  it('should support ReactLink', function() {
    var container = document.createElement('div');
    var link = new ReactLink('yolo', mocks.getMockFunction());
    var instance = <textarea valueLink={link} />;

    instance = React.renderComponent(instance, container);

    expect(instance.getDOMNode().value).toBe('yolo');
    expect(link.value).toBe('yolo');
    expect(link.requestChange.mock.calls.length).toBe(0);

    instance.getDOMNode().value = 'test';
    ReactTestUtils.Simulate.change(instance.getDOMNode());

    expect(link.requestChange.mock.calls.length).toBe(1);
    expect(link.requestChange.mock.calls[0][0]).toEqual('test');
  });
});
<|endoftext|>


<commit_before>/*
 * Copyright 2008-2011 Microarray Informatics Team, EMBL-European Bioinformatics Institute
 *
 * 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.
 *
 *
 * For further details of the Gene Expression Atlas project, including source code,
 * downloads and documentation, please see:
 *
 * http://gxa.github.com/gxa
 */

(function(A, $) {
    /**
     * Ads the hash symbol at the beginning of the string if it is not there yet.
     *
     * @param s - a string with-or-without hash symbol
     */
    A.hsh = function(s) {
        return s ? (s.charAt(0) === "#" ? s : "#" + s) : s;
    };

    A.$ = function(s) {
        var el = $(A.hsh(s));
        return el.length ? el : null;
    };

    /**
     * Removes any content of the DOM element(s).
     * Accepts an Id (one or more) of DOM elements to clean up.
     */
    A.clearContent = function() {
        for (var i = 0, len = arguments.length; i < len; i++) {
            var elem = A.$(arguments[i]);
            if (elem) {
                elem.empty();
            }
        }
    };

    /**
     * Wraps a function into event handler. Any error happened during the function execution
     * is caught and logged in the console.
     *
     * @param func - a user defined function to wrap as event handler
     * @param context - a context of a user defined function execution (optional)
     */
    A.safeFunctionCall = function(func, context) {
        return function() {
            if (func && $.isFunction(func)) {
                try {
                    func.apply(context, arguments);
                } catch (e) {
                    A.logError(e);
                }
            }
            return false;
        }
    };

    /**
     * Object property path getter/setter.
     * If the value (the third argument) is given then it behaves as setter: sets value according the given property
     * path and returns the updated object; otherwise it is getter and returns the corresponding object property value
     * or 'undefined' if the property path doesn't exist.
     *
     * An empty property path does nothing in setter mode.
     *
     * @param obj - an object to update or get properties from
     * @param propPath - dot separated object property path
     * @param value - a new value to set for the given property path
     */
    A.objProperty = function(obj, propPath, value) {
        obj = obj || {};
        var setter = arguments.length > 2;
        var parts = propPath.split(".");
        var s = obj;
        for (var i = 0, len = parts.length; i < len - 1; i++) {
            var prop = parts[i];
            if (!s.hasOwnProperty(prop)) {
                if (!setter) {
                    return undefined;
                }
                s[prop] = {};
            }

            s = s[prop];
        }

        var lastProp = parts[parts.length - 1];

        if (setter && lastProp != "") {
            var copy;
            if ($.isPlainObject(value)) {
                copy = $.extend(true, {}, value);
            } else if ($.isArray(value)) {
                copy = $.extend(true, [], value);
            } else {
                copy = value;
            }
            s[lastProp] = copy;
        }

        return lastProp === "" ? s : s[lastProp];
    };

    A.extendIfUndefined = function(obj, defaults) {
        function _extend(obj1, obj2) {
            if ($.isArray(obj2)) {
                if (!obj1) {
                    obj1 = [];
                    for (var i = 0, len = v.length; i < len; i++) {
                        obj1[i] = _extend(obj1[i], obj2[i]);
                    }
                }
            } else if ($.isPlainObject(obj2)) {
                for (var p in obj2) {
                    if (obj2.hasOwnProperty(p)) {
                        obj1[p] = _extend(obj1[p], obj2[p]);
                    }
                }
            } else if (obj1 === undefined || obj1 === null) {
                obj1 = obj2;
            }
            return obj1;
        }

        return _extend(obj, defaults);
    };

    A.difference = function(obj, defaults) {
        function _difference(obj1, obj2) {
            if ($.isArray(obj2)) {
                if (obj1 && $.isArray(obj1) && obj1.length == obj2.length) {
                    for (var len = v.length, i = len - 1; i > 0; i--) {
                        if (_difference(obj1[i], obj2[i])) {
                            return obj1;
                        }
                    }
                    return null;
                }
            } else if ($.isPlainObject(obj2)) {
                for (var p in obj2) {
                    if (obj2.hasOwnProperty(p)) {
                        obj1[p] = _difference(obj1[p], obj2[p]);
                        if (obj1[p] == null) {
                            delete obj1[p];
                        }
                    }
                }
                return obj1;
            }
            return (obj1 === obj2) ? null : obj1;
        }

        return _difference(obj, defaults);
    };

    /**
     *
     * @param opts {
     *     * url -
     *     * onSuccess -
     *     * onFailure - (optional, by default error message is logged in the js console)
     *     * defaultParams - (optional)
     *     * type - (optional, default is "json")
     * }
     */
    A.ajaxLoader = function(opts) {
        opts = opts || {};
        var url = opts.url;
        var type = opts.type || "json";
        var defaultParams = opts.defaultParams || {};
        var _this = {};

        function successHandler() {
            var context = opts.context || _this;
            if (opts.onSuccess) {
                opts.onSuccess.apply(context, arguments);
            }
        }

        function failureHandler(request, errorType, errorMessage) {
            var context = opts.context || _this;
            if (opts.onFailure()) {
                opts.onFailure.apply(context, arguments);
            } else {
                A.logError({
                    errorType:errorType,
                    errorMessage:errorMessage
                });
            }
        }

        function filter(params) {
            function empty(o) {
                return o === undefined || o === null || o.length == 0 ||
                    ($.isPlainObject(o) && $.isEmptyObject(o));

            }
            if ($.isPlainObject(params)) {
                for(var p in params) {
                    if (params.hasOwnProperty(p)) {
                        if (empty(params[p]) || empty(filter(params[p]))) {
                            delete params[p];
                        }
                    }
                }
            } else if ($.isArray(params)) {
                for (var len = params.length, i = len - 1; i >= 0; i--) {
                    if (empty(params[i]) || empty(filter(params[i]))) {
                        params.splice(i, 1);
                    }
                }
            }
            return params;
        }

        return $.extend(true, _this, {
            load: function(params) {
                params = $.extend(true, {}, defaultParams, params);
                $.ajax({
                    url: A.fullPathFor(url),
                    data: filter(params),
                    dataType: type,
                    success: successHandler,
                    error: failureHandler
                });
            }
        });
    };

    /**
     * JQuery plugin: Checks if element ("<a>") has attribute "atlas-uri"; and if yes it adds application
     * context path to its "href" attribute value.
     */
    $.fn.atlasRelativeHref = function() {
        var url = $(this).attr("atlas-uri");
        if (url) {
            $(this).attr("href", A.fullPathFor(url));
        }
    }
})(atlas || {}, jQuery);
<commit_msg>a typo<commit_after>/*
 * Copyright 2008-2011 Microarray Informatics Team, EMBL-European Bioinformatics Institute
 *
 * 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.
 *
 *
 * For further details of the Gene Expression Atlas project, including source code,
 * downloads and documentation, please see:
 *
 * http://gxa.github.com/gxa
 */

(function(A, $) {
    /**
     * Ads the hash symbol at the beginning of the string if it is not there yet.
     *
     * @param s - a string with-or-without hash symbol
     */
    A.hsh = function(s) {
        return s ? (s.charAt(0) === "#" ? s : "#" + s) : s;
    };

    A.$ = function(s) {
        var el = $(A.hsh(s));
        return el.length ? el : null;
    };

    /**
     * Removes any content of the DOM element(s).
     * Accepts an Id (one or more) of DOM elements to clean up.
     */
    A.clearContent = function() {
        for (var i = 0, len = arguments.length; i < len; i++) {
            var elem = A.$(arguments[i]);
            if (elem) {
                elem.empty();
            }
        }
    };

    /**
     * Wraps a function into event handler. Any error happened during the function execution
     * is caught and logged in the console.
     *
     * @param func - a user defined function to wrap as event handler
     * @param context - a context of a user defined function execution (optional)
     */
    A.safeFunctionCall = function(func, context) {
        return function() {
            if (func && $.isFunction(func)) {
                try {
                    func.apply(context, arguments);
                } catch (e) {
                    A.logError(e);
                }
            }
            return false;
        }
    };

    /**
     * Object property path getter/setter.
     * If the value (the third argument) is given then it behaves as setter: sets value according the given property
     * path and returns the updated object; otherwise it is getter and returns the corresponding object property value
     * or 'undefined' if the property path doesn't exist.
     *
     * An empty property path does nothing in setter mode.
     *
     * @param obj - an object to update or get properties from
     * @param propPath - dot separated object property path
     * @param value - a new value to set for the given property path
     */
    A.objProperty = function(obj, propPath, value) {
        obj = obj || {};
        var setter = arguments.length > 2;
        var parts = propPath.split(".");
        var s = obj;
        for (var i = 0, len = parts.length; i < len - 1; i++) {
            var prop = parts[i];
            if (!s.hasOwnProperty(prop)) {
                if (!setter) {
                    return undefined;
                }
                s[prop] = {};
            }

            s = s[prop];
        }

        var lastProp = parts[parts.length - 1];

        if (setter && lastProp != "") {
            var copy;
            if ($.isPlainObject(value)) {
                copy = $.extend(true, {}, value);
            } else if ($.isArray(value)) {
                copy = $.extend(true, [], value);
            } else {
                copy = value;
            }
            s[lastProp] = copy;
        }

        return lastProp === "" ? s : s[lastProp];
    };

    A.extendIfUndefined = function(obj, defaults) {
        function _extend(obj1, obj2) {
            if ($.isArray(obj2)) {
                if (!obj1) {
                    obj1 = [];
                    for (var i = 0, len = v.length; i < len; i++) {
                        obj1[i] = _extend(obj1[i], obj2[i]);
                    }
                }
            } else if ($.isPlainObject(obj2)) {
                for (var p in obj2) {
                    if (obj2.hasOwnProperty(p)) {
                        obj1[p] = _extend(obj1[p], obj2[p]);
                    }
                }
            } else if (obj1 === undefined || obj1 === null) {
                obj1 = obj2;
            }
            return obj1;
        }

        return _extend(obj, defaults);
    };

    A.difference = function(obj, defaults) {
        function _difference(obj1, obj2) {
            if ($.isArray(obj2)) {
                if (obj1 && $.isArray(obj1) && obj1.length == obj2.length) {
                    for (var len = v.length, i = len - 1; i > 0; i--) {
                        if (_difference(obj1[i], obj2[i])) {
                            return obj1;
                        }
                    }
                    return null;
                }
            } else if ($.isPlainObject(obj2)) {
                for (var p in obj2) {
                    if (obj2.hasOwnProperty(p)) {
                        obj1[p] = _difference(obj1[p], obj2[p]);
                        if (obj1[p] == null) {
                            delete obj1[p];
                        }
                    }
                }
                return obj1;
            }
            return (obj1 === obj2) ? null : obj1;
        }

        return _difference(obj, defaults);
    };

    /**
     *
     * @param opts {
     *     * url -
     *     * onSuccess -
     *     * onFailure - (optional, by default error message is logged in the js console)
     *     * defaultParams - (optional)
     *     * type - (optional, default is "json")
     * }
     */
    A.ajaxLoader = function(opts) {
        opts = opts || {};
        var url = opts.url;
        var type = opts.type || "json";
        var defaultParams = opts.defaultParams || {};
        var _this = {};

        function successHandler() {
            var context = opts.context || _this;
            if (opts.onSuccess) {
                opts.onSuccess.apply(context, arguments);
            }
        }

        function failureHandler(request, errorType, errorMessage) {
            var context = opts.context || _this;
            if (opts.onFailure) {
                opts.onFailure.apply(context, arguments);
            } else {
                A.logError({
                    errorType:errorType,
                    errorMessage:errorMessage
                });
            }
        }

        function filter(params) {
            function empty(o) {
                return o === undefined || o === null || o.length == 0 ||
                    ($.isPlainObject(o) && $.isEmptyObject(o));

            }
            if ($.isPlainObject(params)) {
                for(var p in params) {
                    if (params.hasOwnProperty(p)) {
                        if (empty(params[p]) || empty(filter(params[p]))) {
                            delete params[p];
                        }
                    }
                }
            } else if ($.isArray(params)) {
                for (var len = params.length, i = len - 1; i >= 0; i--) {
                    if (empty(params[i]) || empty(filter(params[i]))) {
                        params.splice(i, 1);
                    }
                }
            }
            return params;
        }

        return $.extend(true, _this, {
            load: function(params) {
                params = $.extend(true, {}, defaultParams, params);
                $.ajax({
                    url: A.fullPathFor(url),
                    data: filter(params),
                    dataType: type,
                    success: successHandler,
                    error: failureHandler
                });
            }
        });
    };

    /**
     * JQuery plugin: Checks if element ("<a>") has attribute "atlas-uri"; and if yes it adds application
     * context path to its "href" attribute value.
     */
    $.fn.atlasRelativeHref = function() {
        var url = $(this).attr("atlas-uri");
        if (url) {
            $(this).attr("href", A.fullPathFor(url));
        }
    }
})(atlas || {}, jQuery);
<|endoftext|>