<commit_before>'use strict';
import moment from 'moment';
import * as ajax from './ajax';
import * as config from './config';

/**
 * @enum {string}
 */
export let types = {
  RECHARGE: 'RECHARGE',
  DATA: 'DATA',
  DATA_PERCENT: 'DATA_PERCENT',
  ACTIVATED: 'ACTIVATED',
  DEPLETED: 'DEPLETED',
  AD: 'AD',
};

/**
 * @enum {number}
 */
export let boxTypes = {
  INBOX: 1,
  SENT: 2,
  DRAFT: 3,
};

function _arrayMatch(message, regExpMatch, mapFunc) {
  let data = message.match(regExpMatch);
  if (data) {
    return data.map(mapFunc);
  } else {
    return [];
  }
}
/**
 * @typedef SmsDataUsage
 * @property {number} amount
 * @property {string} unit
 */
/**
 * Get's data usage strings in the message
 * @param {string} message
 * @return {SmsDataUsage[]}
 */
function _getDataUsage(message) {
  return _arrayMatch(message, /(\d*)(\.*)(\d*)( *)mb/gi, (element) => {
    return {
      amount: parseFloat(element.replace(/( *)mb/i, '')),
      unit: 'MB',
    };
  });
}
function _getExpiryDate(message) {
  return _arrayMatch(
    message, /(\d+)-(\d+)-(\d+) (\d{2}):(\d{2}):(\d{2})/g, (date) => {
      return moment(date);
    });
}
function _getMoney(message) {
  return _arrayMatch(
    message, /(\d*)(\.*)(\d*)( *)kwacha/gi, (element) => {
      return parseFloat(element.replace(/( *)kwacha/i, ''));
    });
}

function _getPercent(message) {
  return _arrayMatch(message, /\d+%/gi, (element) => {
    return parseFloat(element.replace(/%/, ''));
  });
}

/**
 *
 * @param {object} info
 * @param {string} message
 * @return {types}
 */
function _getType(info, message) {
  let adPhrases = [
    'spaka',
    'bonus',
    'congratulations',
    'songs', 'tunes', 'music',
    'subscribe',
    'enjoy',
    'watch tv', 'mtn tv plus', 'mtn tv+',
    'download',
    'call across all networks',
    'youtube',
    'borrow',
    'laugh',
    'app',
    'sport',
  ];
  let count = 0;
  for (let phrase of adPhrases) {
    if (message.toLowerCase().search(phrase) > -1) {
      count++;
    }
  }
  let ml = message.toLowerCase();
  if (info.money.length >= 2 && ml.includes('recharged') && ml.includes('balance')) {
    return types.RECHARGE;
  }
  if (info.data.length > 0) {
    if (info.expires.length > 0) {
      return types.DATA;
    }
    if (ml.search(/\d+%/) > 0) {
      return types.DATA_PERCENT;
    }
  }
  if (ml.includes('activated') && ml.includes('bundle')) {
    return types.ACTIVATED;
  }
  if (ml.includes('depleted') && ml.includes('bundle')) {
    return types.DEPLETED;
  }
  return types.AD;
}
export function parse(message) {
  let info = {
    data: _getDataUsage(message),
    expires: _getExpiryDate(message),
    money: _getMoney(message),
    percent: _getPercent(message),
  };

  return Object.assign(info, {
    type: _getType(info, message),
  });
}

// Separate

/**
 * @typedef SmsCount
 * @property {number} LocalUnread
 * @property {number} LocalInbox
 * @property {number} LocalOutbox
 * @property {number} LocalDraft
 * @property {number} LocalDeleted
 * @property {number} SimUnread
 * @property {number} SimInbox
 * @property {number} SimOutbox
 * @property {number} SimDraft
 * @property {number} LocalMax
 * @property {number} SimMax
 * @property {number} SimUsed
 * @property {number} NewMsg
 */

/**
 * Gets the number of read and unread messages
 * @return {Promise<SmsCount>}
 */
export async function getSmsCount() {
  return ajax.getAjaxData({url: 'api/sms/sms-count'});
}

/**
 * @typedef Message
 * @property {number} Smstat Whether the message is read or not
 * @property {number} Index
 * @property {string|number} Phone The phone number from which the SMS was sent
 * @property {string} Content The actual content of the SMS
 * @property {string} Date The date the SMS was received
 * @property {*} Sca
 * @property {number} SaveType
 * @property {number} Priority
 * @property {number} SmsType
 */

// TODO: Fix SmsBoxTypes JSDoc
/**
 * @typedef SmsListOptions
 * @property {number} [page=1]
 * @property {number} [perPage=20]
 * @property {boxTypes} [boxType=1] Which box to retreive. Can be Inbox(1), sent(2) or draft(3)
 * @property {('desc'|'asc')} [sortOrder=desc]
*/

/**
 * Get's the list of SMSs from the router
 * @param {SmsListOptions} options Options
 * @return {Promise<Message[]>}
 */
export function getSmsList(options) {
  options = Object.assign({
    page: 1,
    perPage: 20,
    boxType: 1,
    sortOrder: 'desc',
  }, options);
  return ajax.saveAjaxData({
    url: 'api/sms/sms-list',
    request: {
      PageIndex: options.page,
      ReadCount: options.perPage,
      BoxType: options.boxType,
      SortType: 0,
      Ascending: options.sortOrder === 'desc' ? 0 : 1,
      UnreadPreferred: 0,
    },
  }).then((data) => {
    if (data.Count > 1) {
      return data.Messages.Message;
    } else if (data.Count > 0) {
      return [data.Messages.Message];
    } else {
      return [];
    }
  });
}

/**
 *
 * @param {FullSmsListOptions} options
 * @param {Message[]} list
 * @return {Message[]}
 */
function _filterSmsList(options, list) {
  let filteredList = [];
  for (let message of list) {
    if (options.minDate) {
      if (Date.parse(message.Date) > options.minDate) {
        filteredList.push(message);
      }
    } else {
      filteredList.push(message);
    }
  }
  return filteredList;
}

/**
 *
 * @param {FullSmsListOptions} options
 * @param {SmsListOptions} smsListOptions
 * @param {number} perPage
 * @param {number} total
 * @param {number} [page=1]
 * @param {Message[]} list
 * @return {Promise<Message[]>}
 */
function _getFullSmsListRecursive(
  options, smsListOptions, perPage, total, page=1, list) {
  smsListOptions.perPage = perPage;
  smsListOptions.page = page;
  return getSmsList(smsListOptions).then((currentList) => {
    page++;
    let filteredList = _filterSmsList(options, currentList);
    // Add all messages to list
    if (!list) {
      list = filteredList;
    } else {
      list = list.concat(filteredList);
    }
    // If we have not reached the end of the messages
    if (((page - 1) * perPage) < total) {
      return _getFullSmsListRecursive(
        options, smsListOptions, perPage, total, page, list);
    } else {
      return list;
    }
  });
}

/**
 * @typedef FullSmsListOptions
 * @property {number} total
 * @property {number} [minDate]
 */

/**
 *
 * @param {FullSmsListOptions} options
 * @param {SmsListOptions} [smsListOptions]
 * @return {Promise<Message[]>}
 */
export function getFullSmsList(options, smsListOptions={}) {
  smsListOptions = Object.assign({
    sortOrder: 'desc',
  }, smsListOptions);

  options = Object.assign({
    total: 0,
  }, options);

  if (options.total > 0) {
    return config.getSmsConfig().then((smsConfig) => {
      return _getFullSmsListRecursive(
        options, smsListOptions, smsConfig.pagesize, options.total
      ).then((list) => {
        return list;
      });
    });
  } else {
    return Promise.resolve([]);
  }
}

/**
 *
 * @param {number} idx The index of the SMS
 * @return {Promise<Boolean>}
 */
export function setSmsAsRead(idx) {
  return ajax.saveAjaxData({
    url: 'api/sms/set-read',
    request: {
      Index: idx,
    },
    responseMustBeOk: true,
  });
}

export function createSmsRequest(options) {
  options = Object.assign({
    smsIndex: -1,
    numbers: [],
    content: '',
  }, options);

  return {
    Index: options.smsIndex,
    Phones: {
      Phone: options.numbers,
    },
    Sca: '',
    Content: options.content,
    Length: options.content.length,
    // TODO: Add different text modes
    // SMS_TEXT_MODE_UCS2 = 0
    // SMS_TEXT_MODE_7BIT = 1
    // SMS_TEXT_MODE_8BIT = 2
    Reserved: 1,
    Date: moment(Date.now()).format('Y-M-D H:mm:ss'),
  };
}

/**
 * @typedef SaveSmsOptions
 * @property {number} smsIndex The index of the  Only used for sending drafts
 * @property {string[]} numbers An array of numbers to send the sms to
 * @property {string} content The SMS body
 */

/**
 * Sends an sms or saves a draft
 * @param {SaveSmsOptions} options
 * @return {Promise<boolean>}
 */
export function saveSms(options) {
  return ajax.saveAjaxData({
    url: 'api/sms/save-sms',
    request: createSmsRequest(options),
    responseMustBeOk: true,
  });
}

/**
 * @typedef SmsSendStatus
 * @property {string} TotalCount
 * @property {string} CurIndex
 * @property {string} Phone
 * @property {string} SucPhone
 * @property {string} FailPhone
 */

/**
 * @return {Promise<SmsSendStatus>}
 */
export function getSmsSendStatus() {
  return ajax.getAjaxData({
    url: 'api/sms/send-status',
  });
}

/**
 * @typedef SendSmsOptions
 * @property {string[]} numbers An array of numbers to send the sms to
 * @property {string} content The SMS body
 */

/**
 * @param {SendSmsOptions} options
 * @return {Promise<SmsSendStatus>}
 */
export function sendSms(options) {
  return ajax.saveAjaxData({
    url: 'api/sms/send-sms',
    request: createSmsRequest(options),
  }).then(() => {
    return getSmsSendStatus();
  });
}

/**
 * Delete's all messages with the given indices
 * @param {number[]} indices An array of indices of messages
 * @return {Promise<any>}
 */
export function deleteSms(indices) {
  let request = indices.map((i) => {
    return {Index: i};
  });
  return ajax.saveAjaxData({
    url: 'api/sms/delete-sms',
    request: request,
    responseMustBeOk: true,
  });
}
<commit_msg>Make minDate filter smarter<commit_after>'use strict';
import moment from 'moment';
import * as ajax from './ajax';
import * as config from './config';

/**
 * @enum {string}
 */
export let types = {
  RECHARGE: 'RECHARGE',
  DATA: 'DATA',
  DATA_PERCENT: 'DATA_PERCENT',
  ACTIVATED: 'ACTIVATED',
  DEPLETED: 'DEPLETED',
  AD: 'AD',
};

/**
 * @enum {number}
 */
export let boxTypes = {
  INBOX: 1,
  SENT: 2,
  DRAFT: 3,
};

function _arrayMatch(message, regExpMatch, mapFunc) {
  let data = message.match(regExpMatch);
  if (data) {
    return data.map(mapFunc);
  } else {
    return [];
  }
}
/**
 * @typedef SmsDataUsage
 * @property {number} amount
 * @property {string} unit
 */
/**
 * Get's data usage strings in the message
 * @param {string} message
 * @return {SmsDataUsage[]}
 */
function _getDataUsage(message) {
  return _arrayMatch(message, /(\d*)(\.*)(\d*)( *)mb/gi, (element) => {
    return {
      amount: parseFloat(element.replace(/( *)mb/i, '')),
      unit: 'MB',
    };
  });
}
function _getExpiryDate(message) {
  return _arrayMatch(
    message, /(\d+)-(\d+)-(\d+) (\d{2}):(\d{2}):(\d{2})/g, (date) => {
      return moment(date);
    });
}
function _getMoney(message) {
  return _arrayMatch(
    message, /(\d*)(\.*)(\d*)( *)kwacha/gi, (element) => {
      return parseFloat(element.replace(/( *)kwacha/i, ''));
    });
}

function _getPercent(message) {
  return _arrayMatch(message, /\d+%/gi, (element) => {
    return parseFloat(element.replace(/%/, ''));
  });
}

/**
 *
 * @param {object} info
 * @param {string} message
 * @return {types}
 */
function _getType(info, message) {
  let adPhrases = [
    'spaka',
    'bonus',
    'congratulations',
    'songs', 'tunes', 'music',
    'subscribe',
    'enjoy',
    'watch tv', 'mtn tv plus', 'mtn tv+',
    'download',
    'call across all networks',
    'youtube',
    'borrow',
    'laugh',
    'app',
    'sport',
  ];
  let count = 0;
  for (let phrase of adPhrases) {
    if (message.toLowerCase().search(phrase) > -1) {
      count++;
    }
  }
  let ml = message.toLowerCase();
  if (info.money.length >= 2 && ml.includes('recharged') && ml.includes('balance')) {
    return types.RECHARGE;
  }
  if (info.data.length > 0) {
    if (info.expires.length > 0) {
      return types.DATA;
    }
    if (ml.search(/\d+%/) > 0) {
      return types.DATA_PERCENT;
    }
  }
  if (ml.includes('activated') && ml.includes('bundle')) {
    return types.ACTIVATED;
  }
  if (ml.includes('depleted') && ml.includes('bundle')) {
    return types.DEPLETED;
  }
  return types.AD;
}
export function parse(message) {
  let info = {
    data: _getDataUsage(message),
    expires: _getExpiryDate(message),
    money: _getMoney(message),
    percent: _getPercent(message),
  };

  return Object.assign(info, {
    type: _getType(info, message),
  });
}

// Separate

/**
 * @typedef SmsCount
 * @property {number} LocalUnread
 * @property {number} LocalInbox
 * @property {number} LocalOutbox
 * @property {number} LocalDraft
 * @property {number} LocalDeleted
 * @property {number} SimUnread
 * @property {number} SimInbox
 * @property {number} SimOutbox
 * @property {number} SimDraft
 * @property {number} LocalMax
 * @property {number} SimMax
 * @property {number} SimUsed
 * @property {number} NewMsg
 */

/**
 * Gets the number of read and unread messages
 * @return {Promise<SmsCount>}
 */
export async function getSmsCount() {
  return ajax.getAjaxData({url: 'api/sms/sms-count'});
}

/**
 * @typedef Message
 * @property {number} Smstat Whether the message is read or not
 * @property {number} Index
 * @property {string|number} Phone The phone number from which the SMS was sent
 * @property {string} Content The actual content of the SMS
 * @property {string} Date The date the SMS was received
 * @property {*} Sca
 * @property {number} SaveType
 * @property {number} Priority
 * @property {number} SmsType
 */

// TODO: Fix SmsBoxTypes JSDoc
/**
 * @typedef SmsListOptions
 * @property {number} [page=1]
 * @property {number} [perPage=20]
 * @property {boxTypes} [boxType=1] Which box to retreive. Can be Inbox(1), sent(2) or draft(3)
 * @property {('desc'|'asc')} [sortOrder=desc]
*/

/**
 * Get's the list of SMSs from the router
 * @param {SmsListOptions} options Options
 * @return {Promise<Message[]>}
 */
export function getSmsList(options) {
  options = Object.assign({
    page: 1,
    perPage: 20,
    boxType: 1,
    sortOrder: 'desc',
  }, options);
  return ajax.saveAjaxData({
    url: 'api/sms/sms-list',
    request: {
      PageIndex: options.page,
      ReadCount: options.perPage,
      BoxType: options.boxType,
      SortType: 0,
      Ascending: options.sortOrder === 'desc' ? 0 : 1,
      UnreadPreferred: 0,
    },
  }).then((data) => {
    if (data.Count > 1) {
      return data.Messages.Message;
    } else if (data.Count > 0) {
      return [data.Messages.Message];
    } else {
      return [];
    }
  });
}

/**
 * @typedef FilterSmsListOption
 * @property {number} minDate
 */

/**
 *
 * @param {FilterSmsListOption} options
 * @param {Message[]} list
 * @return {Message[]}
 */
function _filterSmsList(options, list) {
  let filteredList = [];
  for (let message of list) {
    if (options.minDate) {
      if (Date.parse(message.Date) > options.minDate) {
        filteredList.push(message);
      }
    } else {
      filteredList.push(message);
    }
  }
  return filteredList;
}

/**
 * @typedef FullSmsListOptions
 * @property {number} total
 * @property {number} [minDate]
 * @property {FilterSmsListOption} [filter]
 */

/**
 *
 * @param {FullSmsListOptions} options
 * @param {SmsListOptions} smsListOptions
 * @param {Message[]} list
 * @param {number} perPage
 * @param {number} total
 * @param {number} [page=1]
 * @return {Promise<Message[]>}
 */
function _getFullSmsListRecursive(
  options, smsListOptions, list, perPage, total, page=1) {
  smsListOptions.perPage = perPage;
  smsListOptions.page = page;
  return getSmsList(smsListOptions).then((currentList) => {
    page++;

    if (options.filter) {
      list = list.concat(_filterSmsList(options.filter, currentList));
    } else {
      list = list.concat(currentList);
    }

    // If a minimum date is required and the order is descending,
    if (options.minDate && smsListOptions.sortOrder === 'desc') {
      let dateFilteredList = _filterSmsList({minDate: options.minDate}, currentList);
      // If the date filtered list does not match the list then
      // this is the last page we should check as anything later
      // will be older than the minimum date
      if (dateFilteredList.length !== currentList.length) {
        return list;
      }
    }

    // If we have not reached the end of the messages
    if (((page - 1) * perPage) < total) {
      return _getFullSmsListRecursive(
        options, smsListOptions, list, perPage, total, page);
    } else {
      return list;
    }
  });
}

/**
 *
 * @param {FullSmsListOptions} options
 * @param {SmsListOptions} [smsListOptions]
 * @return {Promise<Message[]>}
 */
export function getFullSmsList(options, smsListOptions={}) {
  smsListOptions = Object.assign({
    sortOrder: 'desc',
  }, smsListOptions);

  options = Object.assign({
    total: 0,
    filter: null,
  }, options);

  if (options.minDate && smsListOptions.sortOrder !== 'desc') {
    throw new Error('Invalid arguments for getFullSmsList; If a minimum date is passed, then the sorting order must be descending.');
  }

  if (options.total > 0) {
    return config.getSmsConfig().then((smsConfig) => {
      return _getFullSmsListRecursive(
        options, smsListOptions, [], smsConfig.pagesize, options.total
      ).then((list) => {
        return list;
      });
    });
  } else {
    return Promise.resolve([]);
  }
}

/**
 *
 * @param {number} idx The index of the SMS
 * @return {Promise<Boolean>}
 */
export function setSmsAsRead(idx) {
  return ajax.saveAjaxData({
    url: 'api/sms/set-read',
    request: {
      Index: idx,
    },
    responseMustBeOk: true,
  });
}

export function createSmsRequest(options) {
  options = Object.assign({
    smsIndex: -1,
    numbers: [],
    content: '',
  }, options);

  return {
    Index: options.smsIndex,
    Phones: {
      Phone: options.numbers,
    },
    Sca: '',
    Content: options.content,
    Length: options.content.length,
    // TODO: Add different text modes
    // SMS_TEXT_MODE_UCS2 = 0
    // SMS_TEXT_MODE_7BIT = 1
    // SMS_TEXT_MODE_8BIT = 2
    Reserved: 1,
    Date: moment(Date.now()).format('Y-M-D H:mm:ss'),
  };
}

/**
 * @typedef SaveSmsOptions
 * @property {number} smsIndex The index of the  Only used for sending drafts
 * @property {string[]} numbers An array of numbers to send the sms to
 * @property {string} content The SMS body
 */

/**
 * Sends an sms or saves a draft
 * @param {SaveSmsOptions} options
 * @return {Promise<boolean>}
 */
export function saveSms(options) {
  return ajax.saveAjaxData({
    url: 'api/sms/save-sms',
    request: createSmsRequest(options),
    responseMustBeOk: true,
  });
}

/**
 * @typedef SmsSendStatus
 * @property {string} TotalCount
 * @property {string} CurIndex
 * @property {string} Phone
 * @property {string} SucPhone
 * @property {string} FailPhone
 */

/**
 * @return {Promise<SmsSendStatus>}
 */
export function getSmsSendStatus() {
  return ajax.getAjaxData({
    url: 'api/sms/send-status',
  });
}

/**
 * @typedef SendSmsOptions
 * @property {string[]} numbers An array of numbers to send the sms to
 * @property {string} content The SMS body
 */

/**
 * @param {SendSmsOptions} options
 * @return {Promise<SmsSendStatus>}
 */
export function sendSms(options) {
  return ajax.saveAjaxData({
    url: 'api/sms/send-sms',
    request: createSmsRequest(options),
  }).then(() => {
    return getSmsSendStatus();
  });
}

/**
 * Delete's all messages with the given indices
 * @param {number[]} indices An array of indices of messages
 * @return {Promise<any>}
 */
export function deleteSms(indices) {
  let request = indices.map((i) => {
    return {Index: i};
  });
  return ajax.saveAjaxData({
    url: 'api/sms/delete-sms',
    request: request,
    responseMustBeOk: true,
  });
}
<|endoftext|>


<commit_before>var componentLookup = require('../util').$__componentLookup;
var ComponentsContext = require('../ComponentsContext');
var registry = require('../registry');
var modernRenderer = require('../renderer');
var resolveComponentKey = modernRenderer.$__resolveComponentKey;
var preserveComponentEls = modernRenderer.$__preserveComponentEls;
var handleBeginAsync = modernRenderer.$__handleBeginAsync;

var WIDGETS_BEGIN_ASYNC_ADDED_KEY = '$wa';

function createRendererFunc(templateRenderFunc, componentProps) {
    var typeName = componentProps.type;
    var roots = componentProps.roots;
    var assignedId = componentProps.id;

    return function renderer(input, out, renderingLogic) {
        var outGlobal = out.global;

        if (!outGlobal[WIDGETS_BEGIN_ASYNC_ADDED_KEY]) {
            outGlobal[WIDGETS_BEGIN_ASYNC_ADDED_KEY] = true;
            out.on('beginAsync', handleBeginAsync);
        }

        var getInitialProps;
        var getTemplateData;
        var getInitialState;
        var getWidgetConfig;
        var getInitialBody;

        if (renderingLogic) {
            getInitialProps = renderingLogic.getInitialProps;
            getTemplateData = renderingLogic.getTemplateData;
            getInitialState = renderingLogic.getInitialState;
            getWidgetConfig = renderingLogic.getWidgetConfig;
            getInitialBody = renderingLogic.getInitialBody;
        }

        var widgetConfig;
        var componentBody;
        var componentState;

        var component = outGlobal.$w;
        var fakeComponent;
        var isRerender = component !== undefined;
        var id = assignedId;
        var isExisting;
        var customEvents;
        var scope;

        if (component) {
            id = component.id;
            isExisting = true;
            outGlobal.$w = null;
        } else {
            var componentArgs = out.$c;

            if (componentArgs) {
                out.$c = null;
                scope = componentArgs[0];

                if (scope) {
                    scope = scope.id;
                }

                var ref = componentArgs[1];
                if (ref != null) {
                    ref = ref.toString();
                }
                id = id || resolveComponentKey(out, ref, scope);
                customEvents = componentArgs[2];
                delete input.$w;
            }
        }

        var componentsContext = ComponentsContext.$__getComponentsContext(out);
        id = id || componentsContext.$__nextComponentId();

        if (registry.$__isServer && typeName) {
            component = { id:id, typeName:typeName };
        } else {
            if (!component) {
                if (isRerender) {
                    // Look in in the DOM to see if a component with the same ID and type already exists.
                    component = componentLookup[id];
                    if (component && component.$__type !== typeName) {
                        component = undefined;
                    }
                }

                if (component) {
                    isExisting = true;
                } else {
                    isExisting = false;
                    // We need to create a new instance of the component
                    if (typeName) {
                        component = registry.$__createComponent(typeName, id);
                    }
                }
            }
        }

        if (input) {
            if (getWidgetConfig) {
                // If getWidgetConfig() was implemented then use that to
                // get the component config. The component config will be passed
                // to the component constructor. If rendered on the server the
                // component config will be serialized to a JSON-like data
                // structure and stored in a "data-w-config" attribute.
                widgetConfig = getWidgetConfig(input, out);
            } else {
                widgetConfig = input.widgetConfig;
            }

            if (widgetConfig) {
                component.$c = widgetConfig;
            }

            if (getInitialBody) {
                // If we have component a component body then pass it to the template
                // so that it is available to the component tag and can be inserted
                // at the w-body marker
                componentBody = getInitialBody(input, out);
            }

            // If we do not have state then we need to go through the process
            // of converting the input to a component state, or simply normalizing
            // the input using getInitialProps

            if (getInitialProps) {
                // This optional method is used to normalize input state
                input = getInitialProps(input, out) || {};
            }

            if (getInitialState) {
                // This optional method is used to derive the component state
                // from the input properties
                component.state = componentState = getInitialState(input, out);
            }

            if (!componentBody) {
                // Default to using the nested content as the component body
                componentBody = input.renderBody;
            }
        }

        if (component && isExisting) {
            if (!component.$__isDirty || !component.shouldUpdate(input, component.$__state)) {
                if (customEvents) {
                    component.$__setCustomEvents(customEvents, scope);
                }

                preserveComponentEls(component, out, componentsContext);
                return;
            }
        }

        if (!component) {
            fakeComponent = {};
        } else {
            componentState = component.$__rawState;
        }

        var templateInput = getTemplateData ?
            getTemplateData(componentState, input, out) :
            componentState || input || {};

        var componentDef = componentsContext.$__beginComponent(component || fakeComponent);
        componentDef.$__roots = roots;
        componentDef.$__component = fakeComponent ? null : component;
        componentDef.$__isExisting = isExisting;
        componentDef.b = componentBody;
        componentDef.c = function(widgetConfig) {
            component.$c = widgetConfig;
        };
        componentDef.t = function(typeName) {
            if (typeName) {
                this.$__component = component = registry.$__createComponent(typeName, fakeComponent.id);
            }
        };

        if (component && isExisting) {
            component.$__emitLifecycleEvent('$__legacyRender');
        }

        // Render the template associated with the component using the final template
        // data that we constructed
        templateRenderFunc(templateInput, out, componentDef, componentDef);

        if (customEvents && componentDef.$__component) {
            if (registry.$__isServer) {
                componentDef.$__customEvents = customEvents;
                componentDef.$__scope = scope;
            } else {
                componentDef.$__component.$__setCustomEvents(customEvents, scope);
            }
        }

        componentDef.$__end();
    };
}

module.exports = createRendererFunc;
<commit_msg>fix legacy state<commit_after>var componentLookup = require('../util').$__componentLookup;
var ComponentsContext = require('../ComponentsContext');
var registry = require('../registry');
var modernRenderer = require('../renderer');
var resolveComponentKey = modernRenderer.$__resolveComponentKey;
var preserveComponentEls = modernRenderer.$__preserveComponentEls;
var handleBeginAsync = modernRenderer.$__handleBeginAsync;

var WIDGETS_BEGIN_ASYNC_ADDED_KEY = '$wa';

function createRendererFunc(templateRenderFunc, componentProps) {
    var typeName = componentProps.type;
    var roots = componentProps.roots;
    var assignedId = componentProps.id;

    return function renderer(input, out, renderingLogic) {
        var outGlobal = out.global;

        if (!outGlobal[WIDGETS_BEGIN_ASYNC_ADDED_KEY]) {
            outGlobal[WIDGETS_BEGIN_ASYNC_ADDED_KEY] = true;
            out.on('beginAsync', handleBeginAsync);
        }

        var getInitialProps;
        var getTemplateData;
        var getInitialState;
        var getWidgetConfig;
        var getInitialBody;

        if (renderingLogic) {
            getInitialProps = renderingLogic.getInitialProps;
            getTemplateData = renderingLogic.getTemplateData;
            getInitialState = renderingLogic.getInitialState;
            getWidgetConfig = renderingLogic.getWidgetConfig;
            getInitialBody = renderingLogic.getInitialBody;
        }

        var widgetConfig;
        var componentBody;
        var componentState;

        var component = outGlobal.$w;
        var fakeComponent;
        var isRerender = component !== undefined;
        var id = assignedId;
        var isExisting;
        var customEvents;
        var scope;

        if (component) {
            id = component.id;
            isExisting = true;
            outGlobal.$w = null;
        } else {
            var componentArgs = out.$c;

            if (componentArgs) {
                out.$c = null;
                scope = componentArgs[0];

                if (scope) {
                    scope = scope.id;
                }

                var ref = componentArgs[1];
                if (ref != null) {
                    ref = ref.toString();
                }
                id = id || resolveComponentKey(out, ref, scope);
                customEvents = componentArgs[2];
                delete input.$w;
            }
        }

        var componentsContext = ComponentsContext.$__getComponentsContext(out);
        id = id || componentsContext.$__nextComponentId();

        if (registry.$__isServer && typeName) {
            component = { id:id, typeName:typeName };
        } else {
            if (!component) {
                if (isRerender) {
                    // Look in in the DOM to see if a component with the same ID and type already exists.
                    component = componentLookup[id];
                    if (component && component.$__type !== typeName) {
                        component = undefined;
                    }
                }

                if (component) {
                    isExisting = true;
                } else {
                    isExisting = false;
                    // We need to create a new instance of the component
                    if (typeName) {
                        component = registry.$__createComponent(typeName, id);
                    }
                }
            }
        }

        if (input) {
            if (getWidgetConfig) {
                // If getWidgetConfig() was implemented then use that to
                // get the component config. The component config will be passed
                // to the component constructor. If rendered on the server the
                // component config will be serialized to a JSON-like data
                // structure and stored in a "data-w-config" attribute.
                widgetConfig = getWidgetConfig(input, out);
            } else {
                widgetConfig = input.widgetConfig;
            }

            if (widgetConfig) {
                component.$c = widgetConfig;
            }

            if (getInitialBody) {
                // If we have component a component body then pass it to the template
                // so that it is available to the component tag and can be inserted
                // at the w-body marker
                componentBody = getInitialBody(input, out);
            }

            // If we do not have state then we need to go through the process
            // of converting the input to a component state, or simply normalizing
            // the input using getInitialProps

            if (getInitialProps) {
                // This optional method is used to normalize input state
                input = getInitialProps(input, out) || {};
            }

            if (getInitialState) {
                // This optional method is used to derive the component state
                // from the input properties
                component.state = componentState = getInitialState(input, out);
            }

            if (!componentBody) {
                // Default to using the nested content as the component body
                componentBody = input.renderBody;
            }
        }

        if (component && isExisting) {
            if (!component.$__isDirty || !component.shouldUpdate(input, component.$__state)) {
                if (customEvents) {
                    component.$__setCustomEvents(customEvents, scope);
                }

                preserveComponentEls(component, out, componentsContext);
                return;
            }
        }

        if (!component) {
            fakeComponent = {};
        } else {
            component.$__rawState = componentState;
        }

        var templateInput = getTemplateData ?
            getTemplateData(componentState, input, out) :
            componentState || input || {};

        var componentDef = componentsContext.$__beginComponent(component || fakeComponent);
        componentDef.$__roots = roots;
        componentDef.$__component = fakeComponent ? null : component;
        componentDef.$__isExisting = isExisting;
        componentDef.b = componentBody;
        componentDef.c = function(widgetConfig) {
            component.$c = widgetConfig;
        };
        componentDef.t = function(typeName) {
            if (typeName) {
                this.$__component = component = registry.$__createComponent(typeName, fakeComponent.id);
            }
        };

        if (component && isExisting) {
            component.$__emitLifecycleEvent('$__legacyRender');
        }

        // Render the template associated with the component using the final template
        // data that we constructed
        templateRenderFunc(templateInput, out, componentDef, componentDef);

        if (customEvents && componentDef.$__component) {
            if (registry.$__isServer) {
                componentDef.$__customEvents = customEvents;
                componentDef.$__scope = scope;
            } else {
                componentDef.$__component.$__setCustomEvents(customEvents, scope);
            }
        }

        componentDef.$__end();
    };
}

module.exports = createRendererFunc;
<|endoftext|>


<commit_before>/**
 * Identicon.js 2.2.1
 * http://github.com/stewartlord/identicon.js
 *
 * PNGLib required for PNG output
 * http://www.xarg.org/download/pnglib.js
 *
 * Copyright 2017, Stewart Lord
 * Released under the BSD license
 * http://www.opensource.org/licenses/bsd-license.php
 */

(function() {
    var PNGlib;
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        PNGlib = require('./pnglib');
    } else {
        PNGlib = window.PNGlib;
    }

    var Identicon = function(hash, options){
        if (typeof(hash) !== 'string' || hash.length < 15) {
            throw 'A hash of at least 15 characters is required.';
        }

        this.defaults = {
            background: [240, 240, 240, 255],
            margin:     0.08,
            size:       64,
            format:     'png'
        };

        this.options = typeof(options) === 'object' ? options : this.defaults;

        // backward compatibility with old constructor (hash, size, margin)
        if (typeof(arguments[1]) === 'number') { this.options.size   = arguments[1]; }
        if (arguments[2])                      { this.options.margin = arguments[2]; }

        this.hash        = hash
        this.background  = this.options.background || this.defaults.background;
        this.margin      = this.options.margin     || this.defaults.margin;
        this.size        = this.options.size       || this.defaults.size;
        this.format      = this.options.format     || this.defaults.format;
        this.margin      = this.options.margin != undefined ? this.options.margin : this.defaults.margin;

        // foreground defaults to last 7 chars as hue at 50% saturation, 70% brightness
        var hue          = parseInt(this.hash.substr(-7), 16) / 0xfffffff;
        this.foreground  = this.options.foreground || this.hsl2rgb(hue, 0.5, 0.7);
    };

    Identicon.prototype = {
        background: null,
        foreground: null,
        hash:       null,
        margin:     null,
        size:       null,
        format:     null,

        image: function(){
            return this.isSvg()
                ? new Svg(this.size, this.foreground, this.background)
                : new PNGlib(this.size, this.size, 256);
        },

        render: function(){
            var image      = this.image(),
                size       = this.size,
                baseMargin = Math.floor(size * this.margin),
                cell       = Math.floor((size - (baseMargin * 2)) / 5),
                margin     = Math.floor((size - cell * 5) / 2);
                bg         = image.color.apply(image, this.background),
                fg         = image.color.apply(image, this.foreground);

            // the first 15 characters of the hash control the pixels (even/odd)
            // they are drawn down the middle first, then mirrored outwards
            var i, color;
            for (i = 0; i < 15; i++) {
                color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg;
                if (i < 5) {
                    this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image);
                } else if (i < 10) {
                    this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
                    this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
                } else if (i < 15) {
                    this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
                    this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
                }
            }

            return image;
        },

        rectangle: function(x, y, w, h, color, image){
            if (this.isSvg()) {
                image.rectangles.push({x: x, y: y, w: w, h: h, color: color});
            } else {
                var i, j;
                for (i = x; i < x + w; i++) {
                    for (j = y; j < y + h; j++) {
                        image.buffer[image.index(i, j)] = color;
                    }
                }
            }
        },

        // adapted from: https://gist.github.com/aemkei/1325937
        hsl2rgb: function(h, s, b){
            h *= 6;
            s = [
                b += s *= b < .5 ? b : 1 - b,
                b - h % 1 * s * 2,
                b -= s *= 2,
                b,
                b + h % 1 * s,
                b + s
            ];

            return[
                s[ ~~h    % 6 ] * 255, // red
                s[ (h|16) % 6 ] * 255, // green
                s[ (h|8)  % 6 ] * 255  // blue
            ];
        },

        toString: function(raw){
            // backward compatibility with old toString, default to base64
            if (raw) {
                return this.render().getDump();
            } else {
                return this.render().getBase64();
            }
        },

        isSvg: function(){
            return this.format.match(/svg/i)
        }
    };

    var Svg = function(size, foreground, background){
        this.size       = size;
        this.foreground = this.color.apply(this, foreground);
        this.background = this.color.apply(this, background);
        this.rectangles = [];
    };

    Svg.prototype = {
        size:       null,
        foreground: null,
        background: null,
        rectangles: null,

        color: function(r, g, b, a){
            var values = [r, g, b].map(Math.round);
            values.push((a >= 0) && (a <= 255) ? a/255 : 1);
            return 'rgba(' + values.join(',') + ')';
        },

        getDump: function(){
          var i,
                xml,
                rect,
                fg     = this.foreground,
                bg     = this.background,
                stroke = this.size * 0.005;

            xml = "<svg xmlns='http://www.w3.org/2000/svg'"
                + " width='" + this.size + "' height='" + this.size + "'"
                + " style='background-color:" + bg + ";'>"
                + "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>";

            for (i = 0; i < this.rectangles.length; i++) {
                rect = this.rectangles[i];
                if (rect.color == bg) continue;
                xml += "<rect "
                    + " x='"      + rect.x + "'"
                    + " y='"      + rect.y + "'"
                    + " width='"  + rect.w + "'"
                    + " height='" + rect.h + "'"
                    + "/>";
            }
            xml += "</g></svg>"

            return xml;
        },

        getBase64: function(){
            return btoa(this.getDump());
        }
    };

    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = Identicon;
    } else {
        window.Identicon = Identicon;
    }
})();
<commit_msg>Cleanup default handling for margin after my previous sloppy work.<commit_after>/**
 * Identicon.js 2.2.1
 * http://github.com/stewartlord/identicon.js
 *
 * PNGLib required for PNG output
 * http://www.xarg.org/download/pnglib.js
 *
 * Copyright 2017, Stewart Lord
 * Released under the BSD license
 * http://www.opensource.org/licenses/bsd-license.php
 */

(function() {
    var PNGlib;
    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        PNGlib = require('./pnglib');
    } else {
        PNGlib = window.PNGlib;
    }

    var Identicon = function(hash, options){
        if (typeof(hash) !== 'string' || hash.length < 15) {
            throw 'A hash of at least 15 characters is required.';
        }

        this.defaults = {
            background: [240, 240, 240, 255],
            margin:     0.08,
            size:       64,
            format:     'png'
        };

        this.options = typeof(options) === 'object' ? options : this.defaults;

        // backward compatibility with old constructor (hash, size, margin)
        if (typeof(arguments[1]) === 'number') { this.options.size   = arguments[1]; }
        if (arguments[2])                      { this.options.margin = arguments[2]; }

        this.hash        = hash
        this.background  = this.options.background || this.defaults.background;
        this.size        = this.options.size       || this.defaults.size;
        this.format      = this.options.format     || this.defaults.format;
        this.margin      = this.options.margin !== undefined ? this.options.margin : this.defaults.margin;

        // foreground defaults to last 7 chars as hue at 50% saturation, 70% brightness
        var hue          = parseInt(this.hash.substr(-7), 16) / 0xfffffff;
        this.foreground  = this.options.foreground || this.hsl2rgb(hue, 0.5, 0.7);
    };

    Identicon.prototype = {
        background: null,
        foreground: null,
        hash:       null,
        margin:     null,
        size:       null,
        format:     null,

        image: function(){
            return this.isSvg()
                ? new Svg(this.size, this.foreground, this.background)
                : new PNGlib(this.size, this.size, 256);
        },

        render: function(){
            var image      = this.image(),
                size       = this.size,
                baseMargin = Math.floor(size * this.margin),
                cell       = Math.floor((size - (baseMargin * 2)) / 5),
                margin     = Math.floor((size - cell * 5) / 2);
                bg         = image.color.apply(image, this.background),
                fg         = image.color.apply(image, this.foreground);

            // the first 15 characters of the hash control the pixels (even/odd)
            // they are drawn down the middle first, then mirrored outwards
            var i, color;
            for (i = 0; i < 15; i++) {
                color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg;
                if (i < 5) {
                    this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image);
                } else if (i < 10) {
                    this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
                    this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
                } else if (i < 15) {
                    this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
                    this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
                }
            }

            return image;
        },

        rectangle: function(x, y, w, h, color, image){
            if (this.isSvg()) {
                image.rectangles.push({x: x, y: y, w: w, h: h, color: color});
            } else {
                var i, j;
                for (i = x; i < x + w; i++) {
                    for (j = y; j < y + h; j++) {
                        image.buffer[image.index(i, j)] = color;
                    }
                }
            }
        },

        // adapted from: https://gist.github.com/aemkei/1325937
        hsl2rgb: function(h, s, b){
            h *= 6;
            s = [
                b += s *= b < .5 ? b : 1 - b,
                b - h % 1 * s * 2,
                b -= s *= 2,
                b,
                b + h % 1 * s,
                b + s
            ];

            return[
                s[ ~~h    % 6 ] * 255, // red
                s[ (h|16) % 6 ] * 255, // green
                s[ (h|8)  % 6 ] * 255  // blue
            ];
        },

        toString: function(raw){
            // backward compatibility with old toString, default to base64
            if (raw) {
                return this.render().getDump();
            } else {
                return this.render().getBase64();
            }
        },

        isSvg: function(){
            return this.format.match(/svg/i)
        }
    };

    var Svg = function(size, foreground, background){
        this.size       = size;
        this.foreground = this.color.apply(this, foreground);
        this.background = this.color.apply(this, background);
        this.rectangles = [];
    };

    Svg.prototype = {
        size:       null,
        foreground: null,
        background: null,
        rectangles: null,

        color: function(r, g, b, a){
            var values = [r, g, b].map(Math.round);
            values.push((a >= 0) && (a <= 255) ? a/255 : 1);
            return 'rgba(' + values.join(',') + ')';
        },

        getDump: function(){
          var i,
                xml,
                rect,
                fg     = this.foreground,
                bg     = this.background,
                stroke = this.size * 0.005;

            xml = "<svg xmlns='http://www.w3.org/2000/svg'"
                + " width='" + this.size + "' height='" + this.size + "'"
                + " style='background-color:" + bg + ";'>"
                + "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>";

            for (i = 0; i < this.rectangles.length; i++) {
                rect = this.rectangles[i];
                if (rect.color == bg) continue;
                xml += "<rect "
                    + " x='"      + rect.x + "'"
                    + " y='"      + rect.y + "'"
                    + " width='"  + rect.w + "'"
                    + " height='" + rect.h + "'"
                    + "/>";
            }
            xml += "</g></svg>"

            return xml;
        },

        getBase64: function(){
            return btoa(this.getDump());
        }
    };

    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = Identicon;
    } else {
        window.Identicon = Identicon;
    }
})();
<|endoftext|>


<commit_before>/**
 * 分页控件
 *
 * Created by GUY on 2015/8/31.
 * @class BI.Pager
 * @extends BI.Widget
 */
BI.Pager = BI.inherit(BI.Widget, {
    _defaultConfig: function () {
        return BI.extend(BI.Pager.superclass._defaultConfig.apply(this, arguments), {
            baseCls: "bi-pager",
            behaviors: {},
            layouts: [{
                type: "bi.horizontal",
                hgap: 10,
                vgap: 0
            }],

            dynamicShow: true, // 是否动态显示上一页、下一页、首页、尾页， 若为false，则指对其设置使能状态
            // dynamicShow为false时以下两个有用
            dynamicShowFirstLast: false, // 是否动态显示首页、尾页
            dynamicShowPrevNext: false, // 是否动态显示上一页、下一页
            pages: false, // 总页数
            curr: function () {
                return 1;
            }, // 初始化当前页
            groups: 0, // 连续显示分页数
            jump: BI.emptyFn, // 分页的回调函数

            first: false, // 是否显示首页
            last: false, // 是否显示尾页
            prev: "上一页",
            next: "下一页",

            firstPage: 1,
            lastPage: function () { // 在万不得已时才会调用这个函数获取最后一页的页码,  主要作用于setValue方法
                return 1;
            },
            hasPrev: BI.emptyFn, // pages不可用时有效
            hasNext: BI.emptyFn  // pages不可用时有效
        });
    },
    _init: function () {
        BI.Pager.superclass._init.apply(this, arguments);
        var self = this;
        this.currPage = BI.result(this.options, "curr");
        // 翻页太灵敏
        // this._lock = false;
        // this._debouce = BI.debounce(function () {
        //     self._lock = false;
        // }, 300);
        this._populate();
    },

    _populate: function () {
        var self = this, o = this.options, view = [], dict = {};
        this.empty();
        var pages = BI.result(o, "pages");
        var curr = BI.result(this, "currPage");
        var groups = BI.result(o, "groups");
        var first = BI.result(o, "first");
        var last = BI.result(o, "last");
        var prev = BI.result(o, "prev");
        var next = BI.result(o, "next");

        if (pages === false) {
            groups = 0;
            first = false;
            last = false;
        } else {
            groups > pages && (groups = pages);
        }

        // 计算当前组
        dict.index = Math.ceil((curr + ((groups > 1 && groups !== pages) ? 1 : 0)) / (groups === 0 ? 1 : groups));

        // 当前页非首页，则输出上一页
        if (((!o.dynamicShow && !o.dynamicShowPrevNext) || curr > 1) && prev !== false) {
            if (BI.isKey(prev)) {
                view.push({
                    text: prev,
                    value: "prev",
                    disabled: pages === false ? o.hasPrev(curr) === false : !(curr > 1 && prev !== false)
                });
            } else {
                view.push(BI.extend({
                    disabled: pages === false ? o.hasPrev(curr) === false : !(curr > 1 && prev !== false)
                }, prev));
            }
        }

        // 当前组非首组，则输出首页
        if (((!o.dynamicShow && !o.dynamicShowFirstLast) || (dict.index > 1 && groups !== 0)) && first) {
            view.push({
                text: first,
                value: "first",
                disabled: !(dict.index > 1 && groups !== 0)
            });
            if (dict.index > 1 && groups !== 0) {
                view.push({
                    type: "bi.label",
                    cls: "page-ellipsis",
                    text: "\u2026"
                });
            }
        }

        // 输出当前页组
        dict.poor = Math.floor((groups - 1) / 2);
        dict.start = dict.index > 1 ? curr - dict.poor : 1;
        dict.end = dict.index > 1 ? (function () {
            var max = curr + (groups - dict.poor - 1);
            return max > pages ? pages : max;
        }()) : groups;
        if (dict.end - dict.start < groups - 1) { // 最后一组状态
            dict.start = dict.end - groups + 1;
        }
        var s = dict.start, e = dict.end;
        if (first && last && (dict.index > 1 && groups !== 0) && (pages > groups && dict.end < pages && groups !== 0)) {
            s++;
            e--;
        }
        for (; s <= e; s++) {
            if (s === curr) {
                view.push({
                    text: s,
                    value: s,
                    selected: true
                });
            } else {
                view.push({
                    text: s,
                    value: s
                });
            }
        }

        // 总页数大于连续分页数，且当前组最大页小于总页，输出尾页
        if (((!o.dynamicShow && !o.dynamicShowFirstLast) || (pages > groups && dict.end < pages && groups !== 0)) && last) {
            if (pages > groups && dict.end < pages && groups !== 0) {
                view.push({
                    type: "bi.label",
                    cls: "page-ellipsis",
                    text: "\u2026"
                });
            }
            view.push({
                text: last,
                value: "last",
                disabled: !(pages > groups && dict.end < pages && groups !== 0)
            });
        }

        // 当前页不为尾页时，输出下一页
        dict.flow = !prev && groups === 0;
        if (((!o.dynamicShow && !o.dynamicShowPrevNext) && next) || (curr !== pages && next || dict.flow)) {
            view.push((function () {
                if (BI.isKey(next)) {
                    if (pages === false) {
                        return {text: next, value: "next", disabled: o.hasNext(curr) === false};
                    }
                    return (dict.flow && curr === pages)
                        ?
                        {text: next, value: "next", disabled: true}
                        :
                        {text: next, value: "next", disabled: !(curr !== pages && next || dict.flow)};
                }
                return BI.extend({
                    disabled: pages === false ? o.hasNext(curr) === false : !(curr !== pages && next || dict.flow)
                }, next);

            }()));
        }

        this.button_group = BI.createWidget({
            type: "bi.button_group",
            element: this,
            items: BI.createItems(view, {
                cls: "bi-list-item-select bi-border-radius",
                height: 23,
                hgap: 10
            }),
            behaviors: o.behaviors,
            layouts: o.layouts
        });
        this.button_group.on(BI.Controller.EVENT_CHANGE, function (type, value, obj) {
            // if (self._lock === true) {
            //     return;
            // }
            // self._lock = true;
            // self._debouce();
            if (type === BI.Events.CLICK) {
                var v = self.button_group.getValue()[0];
                switch (v) {
                    case "first":
                        self.currPage = 1;
                        break;
                    case "last":
                        self.currPage = pages;
                        break;
                    case "prev":
                        self.currPage--;
                        break;
                    case "next":
                        self.currPage++;
                        break;
                    default:
                        self.currPage = v;
                        break;
                }
                o.jump.apply(self, [{
                    pages: pages,
                    curr: self.currPage
                }]);
                self._populate();
                self.fireEvent(BI.Pager.EVENT_CHANGE, obj);
            }
            self.fireEvent(BI.Controller.EVENT_CHANGE, arguments);
        });
        this.fireEvent(BI.Pager.EVENT_AFTER_POPULATE);
    },

    getCurrentPage: function () {
        return this.currPage;
    },

    setAllPages: function (pages) {
        this.options.pages = pages;
    },

    hasPrev: function (v) {
        v || (v = 1);
        var o = this.options;
        var pages = this.options.pages;
        return pages === false ? o.hasPrev(v) : v > 1;
    },

    hasNext: function (v) {
        v || (v = 1);
        var o = this.options;
        var pages = this.options.pages;
        return pages === false ? o.hasNext(v) : v < pages;
    },

    setValue: function (v) {
        var o = this.options;
        v = v || 0;
        v = v < 1 ? 1 : v;
        if (o.pages === false) {
            var lastPage = BI.result(o, "lastPage"), firstPage = 1;
            this.currPage = v > lastPage ? lastPage : ((firstPage = BI.result(o, "firstPage")), (v < firstPage ? firstPage : v));
        } else {
            v = v > o.pages ? o.pages : v;
            this.currPage = v;
        }
        this._populate();
    },

    getValue: function () {
        var val = this.button_group.getValue()[0];
        switch (val) {
            case "prev":
                return -1;
            case "next":
                return 1;
            case "first":
                return BI.MIN;
            case "last":
                return BI.MAX;
            default :
                return val;
        }
    },

    attr: function (key, value) {
        BI.Pager.superclass.attr.apply(this, arguments);
        if (key === "curr") {
            this.currPage = BI.result(this.options, "curr");
        }
    },

    populate: function () {
        this._populate();
    }
});
BI.Pager.EVENT_CHANGE = "EVENT_CHANGE";
BI.Pager.EVENT_AFTER_POPULATE = "EVENT_AFTER_POPULATE";
BI.shortcut("bi.pager", BI.Pager);<commit_msg>Pull request #1557: QFX-2454 pager点击事件不用向上传递<commit_after>/**
 * 分页控件
 *
 * Created by GUY on 2015/8/31.
 * @class BI.Pager
 * @extends BI.Widget
 */
BI.Pager = BI.inherit(BI.Widget, {
    _defaultConfig: function () {
        return BI.extend(BI.Pager.superclass._defaultConfig.apply(this, arguments), {
            baseCls: "bi-pager",
            behaviors: {},
            layouts: [{
                type: "bi.horizontal",
                hgap: 10,
                vgap: 0
            }],

            dynamicShow: true, // 是否动态显示上一页、下一页、首页、尾页， 若为false，则指对其设置使能状态
            // dynamicShow为false时以下两个有用
            dynamicShowFirstLast: false, // 是否动态显示首页、尾页
            dynamicShowPrevNext: false, // 是否动态显示上一页、下一页
            pages: false, // 总页数
            curr: function () {
                return 1;
            }, // 初始化当前页
            groups: 0, // 连续显示分页数
            jump: BI.emptyFn, // 分页的回调函数

            first: false, // 是否显示首页
            last: false, // 是否显示尾页
            prev: "上一页",
            next: "下一页",

            firstPage: 1,
            lastPage: function () { // 在万不得已时才会调用这个函数获取最后一页的页码,  主要作用于setValue方法
                return 1;
            },
            hasPrev: BI.emptyFn, // pages不可用时有效
            hasNext: BI.emptyFn  // pages不可用时有效
        });
    },
    _init: function () {
        BI.Pager.superclass._init.apply(this, arguments);
        var self = this;
        this.currPage = BI.result(this.options, "curr");
        // 翻页太灵敏
        // this._lock = false;
        // this._debouce = BI.debounce(function () {
        //     self._lock = false;
        // }, 300);
        this._populate();
    },

    _populate: function () {
        var self = this, o = this.options, view = [], dict = {};
        this.empty();
        var pages = BI.result(o, "pages");
        var curr = BI.result(this, "currPage");
        var groups = BI.result(o, "groups");
        var first = BI.result(o, "first");
        var last = BI.result(o, "last");
        var prev = BI.result(o, "prev");
        var next = BI.result(o, "next");

        if (pages === false) {
            groups = 0;
            first = false;
            last = false;
        } else {
            groups > pages && (groups = pages);
        }

        // 计算当前组
        dict.index = Math.ceil((curr + ((groups > 1 && groups !== pages) ? 1 : 0)) / (groups === 0 ? 1 : groups));

        // 当前页非首页，则输出上一页
        if (((!o.dynamicShow && !o.dynamicShowPrevNext) || curr > 1) && prev !== false) {
            if (BI.isKey(prev)) {
                view.push({
                    text: prev,
                    value: "prev",
                    disabled: pages === false ? o.hasPrev(curr) === false : !(curr > 1 && prev !== false)
                });
            } else {
                view.push(BI.extend({
                    disabled: pages === false ? o.hasPrev(curr) === false : !(curr > 1 && prev !== false)
                }, prev));
            }
        }

        // 当前组非首组，则输出首页
        if (((!o.dynamicShow && !o.dynamicShowFirstLast) || (dict.index > 1 && groups !== 0)) && first) {
            view.push({
                text: first,
                value: "first",
                disabled: !(dict.index > 1 && groups !== 0)
            });
            if (dict.index > 1 && groups !== 0) {
                view.push({
                    type: "bi.label",
                    cls: "page-ellipsis",
                    text: "\u2026"
                });
            }
        }

        // 输出当前页组
        dict.poor = Math.floor((groups - 1) / 2);
        dict.start = dict.index > 1 ? curr - dict.poor : 1;
        dict.end = dict.index > 1 ? (function () {
            var max = curr + (groups - dict.poor - 1);
            return max > pages ? pages : max;
        }()) : groups;
        if (dict.end - dict.start < groups - 1) { // 最后一组状态
            dict.start = dict.end - groups + 1;
        }
        var s = dict.start, e = dict.end;
        if (first && last && (dict.index > 1 && groups !== 0) && (pages > groups && dict.end < pages && groups !== 0)) {
            s++;
            e--;
        }
        for (; s <= e; s++) {
            if (s === curr) {
                view.push({
                    text: s,
                    value: s,
                    selected: true
                });
            } else {
                view.push({
                    text: s,
                    value: s
                });
            }
        }

        // 总页数大于连续分页数，且当前组最大页小于总页，输出尾页
        if (((!o.dynamicShow && !o.dynamicShowFirstLast) || (pages > groups && dict.end < pages && groups !== 0)) && last) {
            if (pages > groups && dict.end < pages && groups !== 0) {
                view.push({
                    type: "bi.label",
                    cls: "page-ellipsis",
                    text: "\u2026"
                });
            }
            view.push({
                text: last,
                value: "last",
                disabled: !(pages > groups && dict.end < pages && groups !== 0)
            });
        }

        // 当前页不为尾页时，输出下一页
        dict.flow = !prev && groups === 0;
        if (((!o.dynamicShow && !o.dynamicShowPrevNext) && next) || (curr !== pages && next || dict.flow)) {
            view.push((function () {
                if (BI.isKey(next)) {
                    if (pages === false) {
                        return {text: next, value: "next", disabled: o.hasNext(curr) === false};
                    }
                    return (dict.flow && curr === pages)
                        ?
                        {text: next, value: "next", disabled: true}
                        :
                        {text: next, value: "next", disabled: !(curr !== pages && next || dict.flow)};
                }
                return BI.extend({
                    disabled: pages === false ? o.hasNext(curr) === false : !(curr !== pages && next || dict.flow)
                }, next);

            }()));
        }

        this.button_group = BI.createWidget({
            type: "bi.button_group",
            element: this,
            items: BI.createItems(view, {
                cls: "bi-list-item-select bi-border-radius",
                height: 23,
                hgap: 10,
                stopPropagation: true
            }),
            behaviors: o.behaviors,
            layouts: o.layouts
        });
        this.button_group.on(BI.Controller.EVENT_CHANGE, function (type, value, obj) {
            // if (self._lock === true) {
            //     return;
            // }
            // self._lock = true;
            // self._debouce();
            if (type === BI.Events.CLICK) {
                var v = self.button_group.getValue()[0];
                switch (v) {
                    case "first":
                        self.currPage = 1;
                        break;
                    case "last":
                        self.currPage = pages;
                        break;
                    case "prev":
                        self.currPage--;
                        break;
                    case "next":
                        self.currPage++;
                        break;
                    default:
                        self.currPage = v;
                        break;
                }
                o.jump.apply(self, [{
                    pages: pages,
                    curr: self.currPage
                }]);
                self._populate();
                self.fireEvent(BI.Pager.EVENT_CHANGE, obj);
            }
            self.fireEvent(BI.Controller.EVENT_CHANGE, arguments);
        });
        this.fireEvent(BI.Pager.EVENT_AFTER_POPULATE);
    },

    getCurrentPage: function () {
        return this.currPage;
    },

    setAllPages: function (pages) {
        this.options.pages = pages;
    },

    hasPrev: function (v) {
        v || (v = 1);
        var o = this.options;
        var pages = this.options.pages;
        return pages === false ? o.hasPrev(v) : v > 1;
    },

    hasNext: function (v) {
        v || (v = 1);
        var o = this.options;
        var pages = this.options.pages;
        return pages === false ? o.hasNext(v) : v < pages;
    },

    setValue: function (v) {
        var o = this.options;
        v = v || 0;
        v = v < 1 ? 1 : v;
        if (o.pages === false) {
            var lastPage = BI.result(o, "lastPage"), firstPage = 1;
            this.currPage = v > lastPage ? lastPage : ((firstPage = BI.result(o, "firstPage")), (v < firstPage ? firstPage : v));
        } else {
            v = v > o.pages ? o.pages : v;
            this.currPage = v;
        }
        this._populate();
    },

    getValue: function () {
        var val = this.button_group.getValue()[0];
        switch (val) {
            case "prev":
                return -1;
            case "next":
                return 1;
            case "first":
                return BI.MIN;
            case "last":
                return BI.MAX;
            default :
                return val;
        }
    },

    attr: function (key, value) {
        BI.Pager.superclass.attr.apply(this, arguments);
        if (key === "curr") {
            this.currPage = BI.result(this.options, "curr");
        }
    },

    populate: function () {
        this._populate();
    }
});
BI.Pager.EVENT_CHANGE = "EVENT_CHANGE";
BI.Pager.EVENT_AFTER_POPULATE = "EVENT_AFTER_POPULATE";
BI.shortcut("bi.pager", BI.Pager);<|endoftext|>


<commit_before>/**
 * Created by wangwy on 15-9-7.
 */
EPUBJS.Rendition = function (book, options) {
  this.settings = EPUBJS.core.extend(this.settings || {}, {
    infinite: true,
    hidden: false,
    width: false,
    height: null,
    layoutOveride: null,
    axis: "vertical"
  });

  EPUBJS.core.extend(this.settings, options);

  this.book = book;
  this.views = null;

  EPUBJS.Hooks.mixin(this);
  this.getHooks("beforeChapterDisplay");
  this.getHooks("afterChapterDisplay");
  this.registerHook("beforeChapterDisplay", EPUBJS.replace.links.bind(this), true);

  this.q = new EPUBJS.Queue(this);
  this.q.enqueue(this.book.opened);
  this.q.enqueue(this.parseLayoutProperties);
};

/**
 * 获取书籍的布局方式
 * @param _metadata
 * @returns {{layout: (null|EPUBJS.Book.settings.layoutOveride|*|EPUBJS.Book.parseLayoutProperties.layout|metadata.layout|EPUBJS.Renderer.layout), spread: (null|EPUBJS.Book.settings.layoutOveride|*|EPUBJS.Book.parseLayoutProperties.spread|metadata.spread|string), orientation: (null|EPUBJS.Book.settings.layoutOveride|*|EPUBJS.Book.settings.orientation|EPUBJS.Book.parseLayoutProperties.orientation|metadata.orientation)}|*}
 */
EPUBJS.Rendition.prototype.parseLayoutProperties = function (_metadata) {
  var metadata = _metadata || this.book.package.metadata;
  var layout = (this.layoutOveride && this.layoutOveride.layout) || metadata.layout || "reflowable";
  var spread = (this.layoutOveride && this.layoutOveride.spread) || metadata.spread || "auto";
  var orientation = (this.layoutOveride && this.layoutOveride.orientation) || metadata.orientation || "auto";
  this.globalLayoutProperties = {
    layout: layout,
    spread: spread,
    orientation: orientation
  };
  return this.globalLayoutProperties;
};

/**
 * 创建一个div，设置高度与宽度
 * @param _options
 * @returns {HTMLElement}
 */
EPUBJS.Rendition.prototype.initialize = function (_options) {
  var options = _options || {};
  var height = options.height;
  var width = options.width;
  var container;

  if (options.height && EPUBJS.core.isNumber(options.height)) {
    height = options.height + "px";
  }

  if (options.width && EPUBJS.core.isNumber(options.width)) {
    width = options.width + "px";
  }

  container = document.createElement("div");
  container.id = "epubjs-container:" + EPUBJS.core.uuid();
  container.setAttribute("class", "epub-container");

  container.style.fontSize = "0";
  container.style.wordSpacing = "0";
  container.style.lineHeight = "0";
  container.style.verticalAlign = "top";

  if (this.settings.axis === "horizontal") {
    container.style.whiteSpace = "nowrap";
  }

  if (width) {
    container.style.width = width;
  }

  if (height) {
    container.style.height = height;
  }

  container.style.overflow = this.settings.overflow;

  return container;
};

/**
 * 创建一个div用于隐藏container
 * @param container
 * @returns {HTMLElement}
 */
EPUBJS.Rendition.wrap = function (container) {
  var wrapper = document.createElement("div");

  wrapper.style.visibility = "hidden";
  wrapper.style.overflow = "hidden";
  wrapper.style.width = "0";
  wrapper.style.height = "0";

  wrapper.appendChild(container);
  return wrapper;
};

/**
 * 将新创建的elem添加到页面
 * @param _element
 */
EPUBJS.Rendition.prototype.attachTo = function (_element) {
  this.container = this.initialize({
    "width": this.settings.width,
    "height": this.settings.height
  });

  if (EPUBJS.core.isElement(_element)) {
    this.element = _element;
  } else if (typeof _element === "string") {
    this.element = document.getElementById(_element);
  }

  if (!this.element) {
    console.error("不是一个element");
    return;
  }

  if (this.settings.hidden) {
    this.wrapper = this.wrap(this.container);
    this.element.appendChild(this.wrapper);
  } else {
    this.element.appendChild(this.container);
  }

  this.views = new EPUBJS.Views(this.container);

  this.attachListeners();

  this.stageSize();

  this.applyLayoutMethod();

  this.trigger("attached");
};

EPUBJS.Rendition.prototype.display = function (target) {
  return this.q.enqueue(this._display, target);
};

/**
 * 展示页面
 * @param target
 * @returns {*}
 * @private
 */
EPUBJS.Rendition.prototype._display = function (url) {
  var split, href, eleId, section;
  if(url){
    split = url.split("#");
    href = split[0];
    eleId = split[1] || false;
  }
  section = this.book.spine.get(href);
  if (!section) {
    console.error("没有发现要展示的资源");
    return;
  }

  this.views.hide();

  //新建一个页面
  var view = new EPUBJS.View(section, this.viewSettings);

  this.views.clear();
  //只是添加div，iframe还没有下载
  this.views.append(view);
  view.onDisplayed = this.afterDisplayed.bind(this);

  return this.render(view).
      then(function () {
        this.views.show();
      }.bind(this)).
      then(function () {
        //如果跳转路径里有锚点，跳转到锚点所在的位置
        if (eleId) {
          this.gotoEleById(view, eleId);
        }
      }.bind(this)).
      then(function () {
        this.trigger("displayed", section);
      }.bind(this));
};

EPUBJS.Rendition.prototype.gotoEleById = function (view, eleId) {
  var eleScroll = view.getEleScroll(eleId);
  if(this.settings.axis === "vertical"){
    this.scrollBy(0, eleScroll.top, true);
  }else{
    var offsetLeft = Math.floor(eleScroll.left / this.layout.delta) * this.layout.delta;
    this.scrollBy(offsetLeft, 0, true);
  }
};

/**
 * 加载页面
 * @param view
 * @returns {*}
 */
EPUBJS.Rendition.prototype.render = function (view) {
  view.create();

  view.onLayout = this.layout.format.bind(this.layout);

  this.resizeView(view);

  return view.render(this.book.request)
      .then(function () {
        return this.triggerHooks("beforeChapterDisplay", view, this);
      }.bind(this))
      .then(function () {
        return view.display();
      })
      .then(function () {
        if (this.views.hidden === false) {
          this.q.enqueue(function (view) {
            view.show();
          }, view);
        }
        this.triggerHooks("afterChapterDisplay", view, this);
        this.trigger("rendered", view.section);
      }.bind(this))
};

/**
 * 展现后的操作（此方法被重写）
 * @param view
 */
EPUBJS.Rendition.prototype.afterDisplayed = function (view) {
  this.trigger("added", view.section);
};

/**
 * 添加重置事件
 */
EPUBJS.Rendition.prototype.attachListeners = function () {
  if (!EPUBJS.core.isNumber(this.settings.width) || !EPUBJS.core.isNumber(this.settings.height)) {
    window.addEventListener("resize", this.resize.bind(this), false);
  }
};

/**
 * 重置页面
 * @param width
 * @param height
 */
EPUBJS.Rendition.prototype.resize = function (width, height) {
  this.stageSize(width, height);
  this.updateLayout();
  this.trigger("resized", {
    width: this.stage.width,
    height: this.stage.height
  });
};

/**
 * 初始化layout
 */
EPUBJS.Rendition.prototype.applyLayoutMethod = function () {
  this.layout = new EPUBJS.Layout.Scroll();
  this.updateLayout();
  this.map = new EPUBJS.Map(this.layout);
};

/**
 * 更新页面布局
 */
EPUBJS.Rendition.prototype.updateLayout = function () {
  this.layout.calculate(this.stage.width, this.stage.height);
};

/**
 * 计算显示区域的高度与宽度
 * @param _width
 * @param _height
 * @returns {{width: number, height: number}|*}
 */
EPUBJS.Rendition.prototype.stageSize = function (_width, _height) {
  var bounds;
  var width = _width || this.settings.width;
  var height = _height || this.settings.height;

  if (width === false) {
    bounds = this.element.getBoundingClientRect();

    if (bounds.width) {
      width = bounds.width;
      this.container.style.width = bounds.width + "px";
    }
  }

  if (height === false) {
    bounds = bounds || this.element.getBoundingClientRect();

    if (bounds.height) {
      height = bounds.height;
      this.container.style.height = bounds.height + "px";
    }
  }

  if (width && !EPUBJS.core.isNumber(width)) {
    bounds = this.container.getBoundingClientRect();
    width = bounds.width;
  }

  if (height && !EPUBJS.core.isNumber(height)) {
    bounds = bounds || this.container.getBoundingClientRect();
    height = bounds.height;
  }

  this.containerStyles = window.getComputedStyle(this.container);
  this.containerPadding = {
    left: parseFloat(this.containerStyles["padding-left"]) || 0,
    right: parseFloat(this.containerStyles["padding-right"]) || 0,
    top: parseFloat(this.containerStyles["padding-top"]) || 0,
    bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0
  };

  this.stage = {
    width: width -
        this.containerPadding.left -
        this.containerPadding.right,
    height: height -
        this.containerPadding.top -
        this.containerPadding.bottom
  };

  return this.stage;
};

/**
 * 重置显示区域
 * @param view
 */
EPUBJS.Rendition.prototype.resizeView = function (view) {
  if (this.globalLayoutProperties.layout === "pre-paginated") {
    view.lock("both", this.stage.width, this.stage.height);
  } else {
    view.lock("width", this.stage.width, this.stage.height);
  }
};

/**
 * 判断章节是否该显示
 * @param view
 * @param offsetPrev
 * @param offsetNext
 * @param _container
 * @returns {boolean}
 */
EPUBJS.Rendition.prototype.isVisible = function (view, offsetPrev, offsetNext, _container) {
  var position = view.position();
  var container = _container || this.container.getBoundingClientRect();

  if (this.settings.axis === "horizontal" &&
      position.right > container.left - offsetPrev &&
      position.left < container.right + offsetNext) {
    return true;
  } else if (this.settings.axis === "vertical" &&
      position.bottom > container.top - offsetPrev &&
      position.top < container.bottom + offsetNext) {
    return true;
  }

  return false;
};

/**
 * 跳转多少
 * @param x
 * @param y
 * @param silent
 */
EPUBJS.Rendition.prototype.scrollBy = function (x, y, silent) {
  if (silent) {
    this.ignore = true;
  }
  if (this.settings.height) {
    if (x) this.container.scrollLeft += x;
    if (y) this.container.scrollTop += y;
  } else {
    window.scrollBy(x, y);
  }

  this.scrolled = true;
};

/**
 * 跳转到
 * @param x
 * @param y
 * @param silent
 */
EPUBJS.Rendition.prototype.scrollTo = function (x, y, silent) {
  if (silent) {
    this.ignore = true;
  }

  if (this.settings.height) {
    this.container.scrollLeft = x;
    this.container.scrollTop = y;
  } else {
    window.scrollTo(x, y);
  }

  this.scrolled = true;
};

/**
 * 获取浏览器window的高度与宽度
 * @returns {*}
 */
EPUBJS.Rendition.prototype.bounds = function () {
  var bounds;

  if (!this.settings.height) {
    bounds = EPUBJS.core.windowBounds();
  } else {
    bounds = this.container.getBoundingClientRect();
  }

  return bounds;
};

/**
 * 清楚加载的div
 */
EPUBJS.Rendition.prototype.destroy = function () {
  this.q.clear();
  this.views.clear();

  clearTimeout(this.trimTimeout);
  if (this.settings.hidden) {
    this.element.removeChild(this.wrapper);
  } else {
    this.element.removeChild(this.container);
  }
};

/**
 * 报告位置
 */
EPUBJS.Rendition.prototype.reportLocation = function () {
  return this.q.enqueue(function () {
    this.location = this.currentLocation();
    this.trigger("locationChanged", this.location);
  }.bind(this));
};

/**
 * 当前位置(会被重写)
 * @returns {*}
 */
EPUBJS.Rendition.prototype.currentLocation = function () {
  var view;
  if(this.views.length){
    view = this.views.first();
    return this.map.page(view);
  }
};

/**
 * 获取当前页的位置
 * @returns {*}
 */
EPUBJS.Rendition.prototype.getCurrentLocation = function () {
  return this.location;
};

/**
 * 获取在显示区域内的页面
 * @returns {Array}
 */
EPUBJS.Rendition.prototype.visible = function () {
  var container = this.bounds();
  var displayedViews = this.views.displayed();
  var visible = [];
  var isVisible;
  var view;

  for(var i = 0; i < displayedViews.length; i++){
    view = displayedViews[i];
    isVisible = this.isVisible(view, 0, 0, container);

    if(isVisible === true){
      visible.push(view);
    }
  }

  return visible;
};

RSVP.EventTarget.mixin(EPUBJS.Rendition.prototype);<commit_msg>去掉afterChapterDissplay<commit_after>/**
 * Created by wangwy on 15-9-7.
 */
EPUBJS.Rendition = function (book, options) {
  this.settings = EPUBJS.core.extend(this.settings || {}, {
    infinite: true,
    hidden: false,
    width: false,
    height: null,
    layoutOveride: null,
    axis: "vertical"
  });

  EPUBJS.core.extend(this.settings, options);

  this.book = book;
  this.views = null;

  EPUBJS.Hooks.mixin(this);
  this.getHooks("beforeChapterDisplay");
  this.registerHook("beforeChapterDisplay", EPUBJS.replace.links.bind(this), true);

  this.q = new EPUBJS.Queue(this);
  this.q.enqueue(this.book.opened);
  this.q.enqueue(this.parseLayoutProperties);
};

/**
 * 获取书籍的布局方式
 * @param _metadata
 * @returns {{layout: (null|EPUBJS.Book.settings.layoutOveride|*|EPUBJS.Book.parseLayoutProperties.layout|metadata.layout|EPUBJS.Renderer.layout), spread: (null|EPUBJS.Book.settings.layoutOveride|*|EPUBJS.Book.parseLayoutProperties.spread|metadata.spread|string), orientation: (null|EPUBJS.Book.settings.layoutOveride|*|EPUBJS.Book.settings.orientation|EPUBJS.Book.parseLayoutProperties.orientation|metadata.orientation)}|*}
 */
EPUBJS.Rendition.prototype.parseLayoutProperties = function (_metadata) {
  var metadata = _metadata || this.book.package.metadata;
  var layout = (this.layoutOveride && this.layoutOveride.layout) || metadata.layout || "reflowable";
  var spread = (this.layoutOveride && this.layoutOveride.spread) || metadata.spread || "auto";
  var orientation = (this.layoutOveride && this.layoutOveride.orientation) || metadata.orientation || "auto";
  this.globalLayoutProperties = {
    layout: layout,
    spread: spread,
    orientation: orientation
  };
  return this.globalLayoutProperties;
};

/**
 * 创建一个div，设置高度与宽度
 * @param _options
 * @returns {HTMLElement}
 */
EPUBJS.Rendition.prototype.initialize = function (_options) {
  var options = _options || {};
  var height = options.height;
  var width = options.width;
  var container;

  if (options.height && EPUBJS.core.isNumber(options.height)) {
    height = options.height + "px";
  }

  if (options.width && EPUBJS.core.isNumber(options.width)) {
    width = options.width + "px";
  }

  container = document.createElement("div");
  container.id = "epubjs-container:" + EPUBJS.core.uuid();
  container.setAttribute("class", "epub-container");

  container.style.fontSize = "0";
  container.style.wordSpacing = "0";
  container.style.lineHeight = "0";
  container.style.verticalAlign = "top";

  if (this.settings.axis === "horizontal") {
    container.style.whiteSpace = "nowrap";
  }

  if (width) {
    container.style.width = width;
  }

  if (height) {
    container.style.height = height;
  }

  container.style.overflow = this.settings.overflow;

  return container;
};

/**
 * 创建一个div用于隐藏container
 * @param container
 * @returns {HTMLElement}
 */
EPUBJS.Rendition.wrap = function (container) {
  var wrapper = document.createElement("div");

  wrapper.style.visibility = "hidden";
  wrapper.style.overflow = "hidden";
  wrapper.style.width = "0";
  wrapper.style.height = "0";

  wrapper.appendChild(container);
  return wrapper;
};

/**
 * 将新创建的elem添加到页面
 * @param _element
 */
EPUBJS.Rendition.prototype.attachTo = function (_element) {
  this.container = this.initialize({
    "width": this.settings.width,
    "height": this.settings.height
  });

  if (EPUBJS.core.isElement(_element)) {
    this.element = _element;
  } else if (typeof _element === "string") {
    this.element = document.getElementById(_element);
  }

  if (!this.element) {
    console.error("不是一个element");
    return;
  }

  if (this.settings.hidden) {
    this.wrapper = this.wrap(this.container);
    this.element.appendChild(this.wrapper);
  } else {
    this.element.appendChild(this.container);
  }

  this.views = new EPUBJS.Views(this.container);

  this.attachListeners();

  this.stageSize();

  this.applyLayoutMethod();

  this.trigger("attached");
};

EPUBJS.Rendition.prototype.display = function (target) {
  return this.q.enqueue(this._display, target);
};

/**
 * 展示页面
 * @param target
 * @returns {*}
 * @private
 */
EPUBJS.Rendition.prototype._display = function (url) {
  var split, href, eleId, section;
  if(url){
    split = url.split("#");
    href = split[0];
    eleId = split[1] || false;
  }
  section = this.book.spine.get(href);
  if (!section) {
    console.error("没有发现要展示的资源");
    return;
  }

  this.views.hide();

  //新建一个页面
  var view = new EPUBJS.View(section, this.viewSettings);

  this.views.clear();
  //只是添加div，iframe还没有下载
  this.views.append(view);
  view.onDisplayed = this.afterDisplayed.bind(this);

  return this.render(view).
      then(function () {
        this.views.show();
      }.bind(this)).
      then(function () {
        //如果跳转路径里有锚点，跳转到锚点所在的位置
        if (eleId) {
          this.gotoEleById(view, eleId);
        }
      }.bind(this)).
      then(function () {
        this.trigger("displayed", section);
      }.bind(this));
};

EPUBJS.Rendition.prototype.gotoEleById = function (view, eleId) {
  var eleScroll = view.getEleScroll(eleId);
  if(this.settings.axis === "vertical"){
    this.scrollBy(0, eleScroll.top, true);
  }else{
    var offsetLeft = Math.floor(eleScroll.left / this.layout.delta) * this.layout.delta;
    this.scrollBy(offsetLeft, 0, true);
  }
};

/**
 * 加载页面
 * @param view
 * @returns {*}
 */
EPUBJS.Rendition.prototype.render = function (view) {
  view.create();

  view.onLayout = this.layout.format.bind(this.layout);

  this.resizeView(view);

  return view.render(this.book.request)
      .then(function () {
        return this.triggerHooks("beforeChapterDisplay", view, this);
      }.bind(this))
      .then(function () {
        return view.display();
      })
      .then(function () {
        if (this.views.hidden === false) {
          this.q.enqueue(function (view) {
            view.show();
          }, view);
        }
        this.trigger("rendered", view.section);
      }.bind(this))
};

/**
 * 展现后的操作（此方法被重写）
 * @param view
 */
EPUBJS.Rendition.prototype.afterDisplayed = function (view) {
  this.trigger("added", view.section);
};

/**
 * 添加重置事件
 */
EPUBJS.Rendition.prototype.attachListeners = function () {
  if (!EPUBJS.core.isNumber(this.settings.width) || !EPUBJS.core.isNumber(this.settings.height)) {
    window.addEventListener("resize", this.resize.bind(this), false);
  }
};

/**
 * 重置页面
 * @param width
 * @param height
 */
EPUBJS.Rendition.prototype.resize = function (width, height) {
  this.stageSize(width, height);
  this.updateLayout();
  this.trigger("resized", {
    width: this.stage.width,
    height: this.stage.height
  });
};

/**
 * 初始化layout
 */
EPUBJS.Rendition.prototype.applyLayoutMethod = function () {
  this.layout = new EPUBJS.Layout.Scroll();
  this.updateLayout();
  this.map = new EPUBJS.Map(this.layout);
};

/**
 * 更新页面布局
 */
EPUBJS.Rendition.prototype.updateLayout = function () {
  this.layout.calculate(this.stage.width, this.stage.height);
};

/**
 * 计算显示区域的高度与宽度
 * @param _width
 * @param _height
 * @returns {{width: number, height: number}|*}
 */
EPUBJS.Rendition.prototype.stageSize = function (_width, _height) {
  var bounds;
  var width = _width || this.settings.width;
  var height = _height || this.settings.height;

  if (width === false) {
    bounds = this.element.getBoundingClientRect();

    if (bounds.width) {
      width = bounds.width;
      this.container.style.width = bounds.width + "px";
    }
  }

  if (height === false) {
    bounds = bounds || this.element.getBoundingClientRect();

    if (bounds.height) {
      height = bounds.height;
      this.container.style.height = bounds.height + "px";
    }
  }

  if (width && !EPUBJS.core.isNumber(width)) {
    bounds = this.container.getBoundingClientRect();
    width = bounds.width;
  }

  if (height && !EPUBJS.core.isNumber(height)) {
    bounds = bounds || this.container.getBoundingClientRect();
    height = bounds.height;
  }

  this.containerStyles = window.getComputedStyle(this.container);
  this.containerPadding = {
    left: parseFloat(this.containerStyles["padding-left"]) || 0,
    right: parseFloat(this.containerStyles["padding-right"]) || 0,
    top: parseFloat(this.containerStyles["padding-top"]) || 0,
    bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0
  };

  this.stage = {
    width: width -
        this.containerPadding.left -
        this.containerPadding.right,
    height: height -
        this.containerPadding.top -
        this.containerPadding.bottom
  };

  return this.stage;
};

/**
 * 重置显示区域
 * @param view
 */
EPUBJS.Rendition.prototype.resizeView = function (view) {
  if (this.globalLayoutProperties.layout === "pre-paginated") {
    view.lock("both", this.stage.width, this.stage.height);
  } else {
    view.lock("width", this.stage.width, this.stage.height);
  }
};

/**
 * 判断章节是否该显示
 * @param view
 * @param offsetPrev
 * @param offsetNext
 * @param _container
 * @returns {boolean}
 */
EPUBJS.Rendition.prototype.isVisible = function (view, offsetPrev, offsetNext, _container) {
  var position = view.position();
  var container = _container || this.container.getBoundingClientRect();

  if (this.settings.axis === "horizontal" &&
      position.right > container.left - offsetPrev &&
      position.left < container.right + offsetNext) {
    return true;
  } else if (this.settings.axis === "vertical" &&
      position.bottom > container.top - offsetPrev &&
      position.top < container.bottom + offsetNext) {
    return true;
  }

  return false;
};

/**
 * 跳转多少
 * @param x
 * @param y
 * @param silent
 */
EPUBJS.Rendition.prototype.scrollBy = function (x, y, silent) {
  if (silent) {
    this.ignore = true;
  }
  if (this.settings.height) {
    if (x) this.container.scrollLeft += x;
    if (y) this.container.scrollTop += y;
  } else {
    window.scrollBy(x, y);
  }

  this.scrolled = true;
};

/**
 * 跳转到
 * @param x
 * @param y
 * @param silent
 */
EPUBJS.Rendition.prototype.scrollTo = function (x, y, silent) {
  if (silent) {
    this.ignore = true;
  }

  if (this.settings.height) {
    this.container.scrollLeft = x;
    this.container.scrollTop = y;
  } else {
    window.scrollTo(x, y);
  }

  this.scrolled = true;
};

/**
 * 获取浏览器window的高度与宽度
 * @returns {*}
 */
EPUBJS.Rendition.prototype.bounds = function () {
  var bounds;

  if (!this.settings.height) {
    bounds = EPUBJS.core.windowBounds();
  } else {
    bounds = this.container.getBoundingClientRect();
  }

  return bounds;
};

/**
 * 清楚加载的div
 */
EPUBJS.Rendition.prototype.destroy = function () {
  this.q.clear();
  this.views.clear();

  clearTimeout(this.trimTimeout);
  if (this.settings.hidden) {
    this.element.removeChild(this.wrapper);
  } else {
    this.element.removeChild(this.container);
  }
};

/**
 * 报告位置
 */
EPUBJS.Rendition.prototype.reportLocation = function () {
  return this.q.enqueue(function () {
    this.location = this.currentLocation();
    this.trigger("locationChanged", this.location);
  }.bind(this));
};

/**
 * 当前位置(会被重写)
 * @returns {*}
 */
EPUBJS.Rendition.prototype.currentLocation = function () {
  var view;
  if(this.views.length){
    view = this.views.first();
    return this.map.page(view);
  }
};

/**
 * 获取当前页的位置
 * @returns {*}
 */
EPUBJS.Rendition.prototype.getCurrentLocation = function () {
  if(this.location){
    return this.location;
  }
  return this.currentLocation();
};

/**
 * 获取在显示区域内的页面
 * @returns {Array}
 */
EPUBJS.Rendition.prototype.visible = function () {
  var container = this.bounds();
  var displayedViews = this.views.displayed();
  var visible = [];
  var isVisible;
  var view;

  for(var i = 0; i < displayedViews.length; i++){
    view = displayedViews[i];
    isVisible = this.isVisible(view, 0, 0, container);

    if(isVisible === true){
      visible.push(view);
    }
  }

  return visible;
};

RSVP.EventTarget.mixin(EPUBJS.Rendition.prototype);<|endoftext|>


<commit_before>var guitar = new Chordictionary.Instrument('EADGBE', 24, 5, 4);

QUnit.test("Chordictionary.isValidTab()", function(assert) {
	assert.ok(Chordictionary.isValidTab("x32010") === true, "x32010 is a valid tab.");
	assert.ok(Chordictionary.isValidTab("911111099") === true, "911111099 is a valid tab (['9','11','11','10','9','9']).");
});

QUnit.test("Chordictionary.isValidTuning()", function(assert) {
	assert.ok(Chordictionary.isValidTuning("EADGBE") === true, "EADGBE is a valid tuning.");
	assert.ok(Chordictionary.isValidTuning("E#A#D#G#B#E#") === true, "E#A#D#G#B#E# is a valid tuning.");
	assert.ok(Chordictionary.isValidTuning("DA#DGA#D") === true, "DADGA#D is a valid tuning.");
});

QUnit.test("guitar.getChordInfo()", function(assert) {

	// Add chords to be tested below. [tab, name, notes, formula]
	var chords = [
		// Major
		['x02220', 'A', 'xAEAC#E', '1-3-5'],
		['x24442', 'B', 'xBF#BD#F#', '1-3-5'],
		['x32010', 'C', 'xCEGCE', '1-3-5'],
		['xx0232', 'D', 'xxDADF#', '1-3-5'],
		['022100', 'E', 'EBEG#BE', '1-3-5'],
		['133211', 'F', 'FCFACF', '1-3-5'],
		['320033', 'G', 'GBDGDG', '1-3-5'],
		// Minor
		['x02210', ['Amin', 'C6'], 'xAEACE', ['1-b3-5', '1-3-6']],
		['x24432', ["Bmin", "D6"], 'xBF#BDF#', ['1-b3-5', '1-3-6']],
		['x35543', ["Cmin", "D#6"], 'xCGCD#G', ['1-b3-5', '1-3-6']],
		['xx0231', ["Dmin", "F6"], 'xxDADF', ['1-b3-5', '1-3-6']],
		['022000', ["Emin", "G6"], 'EBEGBE', ['1-b3-5', '1-3-6']],
		['133111', ["Fmin", "G#6"], 'FCFG#CF', ['1-b3-5', '1-3-6']],
		['355333', ["Gmin", "A#6"], 'GDGA#DG', ['1-b3-5', '1-3-6']],
		// 7
		['131211', 'F7', 'FCD#ACF', '1-3-5-b7'],
		// Minor 7 or 6
		['133231', ["F6", "Dm7"], 'FCFADF', ["1-3-5-6", "1-b3-5-b7"]],
		// Minor 6 or Minor 7b5
		['xx5545', ["Cm6", "Am7b5"], 'xxGCD#A', ["1-b3-5-6", "1-b3-b5-b7"]],
		['8988xx', ["D#m6", "Cm7b5"], 'CF#A#D#xx', ["1-b3-5-6", "1-b3-b5-b7"]]
	];

	for (var i = 0; i < chords.length; i++) {

		// Check chord name(s)
		if (chords[i][1].constructor === Array) {
			assert.deepEqual(guitar.getChordInfo(chords[i][0]).name, chords[i][1], chords[i][0] + " is an " + chords[i][1] + " chord");
		} else {
			assert.equal(guitar.getChordInfo(chords[i][0]).name, chords[i][1], chords[i][0] + " is an " + chords[i][1] + " chord");
		}

		// Check chord formula(s)
		if (chords[i][3].constructor === Array) {
			assert.deepEqual(guitar.getChordInfo(chords[i][0]).formula, chords[i][3], chords[i][0] + " formula is " + chords[i][3]);
		} else {
			assert.equal(guitar.getChordInfo(chords[i][0]).formula, chords[i][3], chords[i][0] + " formula is " + chords[i][3]);
		}

		// Check chord notes
		assert.equal(guitar.getChordInfo(chords[i][0]).notes, chords[i][2], chords[i][0] + " contains the following notes " + chords[i][2]);

		// Check chord tab
		assert.equal(guitar.getChordInfo(chords[i][0]).tab, chords[i][0], chords[i][0] + " tab is " + chords[i][0]);
	}
});<commit_msg>refactor tests<commit_after>var guitar = new Chordictionary.Instrument('EADGBE', 24, 5, 4);

QUnit.test("Chordictionary.isValidTab()", function(assert) {
	assert.ok(Chordictionary.isValidTab("x32010") === true, "x32010 is a valid tab.");
	assert.ok(Chordictionary.isValidTab("911111099") === true, "911111099 is a valid tab (['9','11','11','10','9','9']).");
});

QUnit.test("Chordictionary.isValidTuning()", function(assert) {
	assert.ok(Chordictionary.isValidTuning("EADGBE") === true, "EADGBE is a valid tuning.");
	assert.ok(Chordictionary.isValidTuning("E#A#D#G#B#E#") === true, "E#A#D#G#B#E# is a valid tuning.");
	assert.ok(Chordictionary.isValidTuning("DA#DGA#D") === true, "DADGA#D is a valid tuning.");
});

QUnit.test("guitar.getChordInfo()", function(assert) {

	// Add chords to be tested below. [tab, name, notes, formula]
	var
		formula = {
			"min": "1-b3-5",
			"maj": "1-3-5",
			"6": "1-3-6",
			"maj6": "1-3-5-6",
			"7": "1-3-5-b7",
			"m7": "1-b3-5-b7",
			"m6": "1-b3-5-6",
			"m7b5": "1-b3-b5-b7"
		},
		chords = [
		// Major
		['x02220', 'A', 'xAEAC#E', formula.maj],
		['x24442', 'B', 'xBF#BD#F#', formula.maj],
		['x32010', 'C', 'xCEGCE', formula.maj],
		['xx0232', 'D', 'xxDADF#', formula.maj],
		['022100', 'E', 'EBEG#BE', formula.maj],
		['133211', 'F', 'FCFACF', formula.maj],
		['320033', 'G', 'GBDGDG', formula.maj],
		// Minor
		['x02210', ['Amin', 'C6'], 'xAEACE', [formula.min, formula['6']]],
		['x24432', ["Bmin", "D6"], 'xBF#BDF#', [formula.min, formula['6']]],
		['x35543', ["Cmin", "D#6"], 'xCGCD#G', [formula.min, formula['6']]],
		['xx0231', ["Dmin", "F6"], 'xxDADF', [formula.min, formula['6']]],
		['022000', ["Emin", "G6"], 'EBEGBE', [formula.min, formula['6']]],
		['133111', ["Fmin", "G#6"], 'FCFG#CF', [formula.min, formula['6']]],
		['355333', ["Gmin", "A#6"], 'GDGA#DG', [formula.min, formula['6']]],
		// 7
		['131211', 'F7', 'FCD#ACF', formula['7']],
		// Minor 7 or 6
		['133231', ["F6", "Dm7"], 'FCFADF', [formula.maj6, formula.m7]],
		// Minor 6 or Minor 7b5
		['xx5545', ["Cm6", "Am7b5"], 'xxGCD#A', [formula.m6, formula.m7b5]],
		['8988xx', ["D#m6", "Cm7b5"], 'CF#A#D#xx', [formula.m6, formula.m7b5]]
	];

	for (var i = 0; i < chords.length; i++) {

		// Check chord name(s)
		if (chords[i][1].constructor === Array) {
			assert.deepEqual(guitar.getChordInfo(chords[i][0]).name, chords[i][1], chords[i][0] + " is an " + chords[i][1] + " chord");
		} else {
			assert.equal(guitar.getChordInfo(chords[i][0]).name, chords[i][1], chords[i][0] + " is an " + chords[i][1] + " chord");
		}

		// Check chord formula(s)
		if (chords[i][3].constructor === Array) {
			assert.deepEqual(guitar.getChordInfo(chords[i][0]).formula, chords[i][3], chords[i][0] + " formula is " + chords[i][3]);
		} else {
			assert.equal(guitar.getChordInfo(chords[i][0]).formula, chords[i][3], chords[i][0] + " formula is " + chords[i][3]);
		}

		// Check chord notes
		assert.equal(guitar.getChordInfo(chords[i][0]).notes, chords[i][2], chords[i][0] + " contains the following notes " + chords[i][2]);

		// Check chord tab
		assert.equal(guitar.getChordInfo(chords[i][0]).tab, chords[i][0], chords[i][0] + " tab is " + chords[i][0]);
	}
});<|endoftext|>


<commit_before>
// options (object)
// options.onTick (function) -  Runs on every tick
// options.onComplete (function) - Runs when pomodoro completed

var Pomodoro = function(options) {
  var _setIntervalId = null;
  var _defaultDuration = 20; // Default duration: 20 Min
  var _durationRemaining = 0; // In seconds

  var _onTick = function() {
    if (_durationRemaining < 1) {
      clearInterval(_setIntervalId);
      _durationRemaining = 0;
      _setIntervalId = null;
      return options.onComplete && options.onComplete();
    }
    _durationRemaining -=  1;
    if (options.onTick) options.onTick(_durationRemaining);
  }

  var exports = {
    start: function() {
      if (_setIntervalId !== null) clearInterval(_setIntervalId);
      _durationRemaining = _defaultDuration * 60 ;
      _setIntervalId = setInterval(_onTick, 1000)
    },

    stop: function() {
      clearInterval(_setIntervalId)
      _durationRemaining = 0;
      _setIntervalId = null;
    },

    updateDuration() {
      if (_defaultDuration === 60) _defaultDuration = 5;
      else _defaultDuration += 5;
    },

    getDefaultDuration() {
      return _defaultDuration;
    },
  }

  return exports;
}

module.exports = Pomodoro;
<commit_msg>Returns human readable time<commit_after>
// options (object)
// options.onTick (function) -  Runs on every tick
// options.onComplete (function) - Runs when pomodoro completed

var Pomodoro = function(options) {
  var _setIntervalId = null;
  var _defaultDuration = 20; // Default duration: 20 Min
  var _durationRemaining = 0; // In seconds

  var _onTick = function() {
    if (_durationRemaining < 1) {
      clearInterval(_setIntervalId);
      _durationRemaining = 0;
      _setIntervalId = null;
      return options.onComplete && options.onComplete();
    }
    _durationRemaining -=  1;
    if (options.onTick) {
      var remainingTime = ('0' + Math.floor(_durationRemaining/60)).slice(-2) + ':' + ('0' + _durationRemaining % 60).slice(-2);
      options.onTick(remainingTime);
    }
  }

  var exports = {
    start: function() {
      if (_setIntervalId !== null) clearInterval(_setIntervalId);
      _durationRemaining = _defaultDuration * 60 ;
      _setIntervalId = setInterval(_onTick, 1000)
    },

    stop: function() {
      clearInterval(_setIntervalId)
      _durationRemaining = 0;
      _setIntervalId = null;
    },

    updateDuration() {
      if (_defaultDuration === 60) _defaultDuration = 5;
      else _defaultDuration += 5;
    },

    getDefaultDuration() {
      return _defaultDuration;
    },
  }

  return exports;
}

module.exports = Pomodoro;
<|endoftext|>