<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const validateOptions = require("schema-utils");
const schema = require("../schemas/plugins/ProgressPlugin.json");

/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */

const createDefaultHandler = profile => {
	let lineCaretPosition = 0;
	let lastMessage = "";
	let lastState;
	let lastStateTime;

	const defaultHandler = (percentage, msg, ...args) => {
		let state = msg;
		const details = args;
		if (percentage < 1) {
			percentage = Math.floor(percentage * 100);
			msg = `${percentage}% ${msg}`;
			if (percentage < 100) {
				msg = ` ${msg}`;
			}
			if (percentage < 10) {
				msg = ` ${msg}`;
			}
			for (let detail of details) {
				if (!detail) continue;
				if (detail.length > 40) {
					detail = `...${detail.substr(detail.length - 39)}`;
				}
				msg += ` ${detail}`;
			}
		}
		if (profile) {
			state = state.replace(/^\d+\/\d+\s+/, "");
			if (percentage === 0) {
				lastState = null;
				lastStateTime = Date.now();
			} else if (state !== lastState || percentage === 1) {
				const now = Date.now();
				if (lastState) {
					const stateMsg = `${now - lastStateTime}ms ${lastState}`;
					goToLineStart(stateMsg);
					process.stderr.write(stateMsg + "\n");
					lineCaretPosition = 0;
				}
				lastState = state;
				lastStateTime = now;
			}
		}
		if (lastMessage !== msg) {
			goToLineStart(msg);
			process.stderr.write(msg);
			lastMessage = msg;
		}
	};

	const goToLineStart = nextMessage => {
		let str = "";
		for (; lineCaretPosition > nextMessage.length; lineCaretPosition--) {
			str += "\b \b";
		}
		for (var i = 0; i < lineCaretPosition; i++) {
			str += "\b";
		}
		lineCaretPosition = nextMessage.length;
		if (str) process.stderr.write(str);
	};

	return defaultHandler;
};

class ProgressPlugin {
	/**
	 * @param {ProgressPluginArgument} options options
	 */
	constructor(options) {
		if (typeof options === "function") {
			options = {
				handler: options
			};
		}

		options = options || {};
		validateOptions(schema, options, "Progress Plugin");
		options = Object.assign({}, ProgressPlugin.defaultOptions, options);

		this.profile = options.profile;
		this.handler = options.handler;
		this.modulesCount = options.modulesCount;
		this.showEntries = options.entries;
		this.showModules = options.modules;
		this.showActiveModules = options.activeModules;
	}

	apply(compiler) {
		const { modulesCount } = this;
		const handler = this.handler || createDefaultHandler(this.profile);
		const showEntries = this.showEntries;
		const showModules = this.showModules;
		const showActiveModules = this.showActiveModules;
		if (compiler.compilers) {
			const states = new Array(compiler.compilers.length);
			compiler.compilers.forEach((compiler, idx) => {
				new ProgressPlugin((p, msg, ...args) => {
					states[idx] = [p, msg, ...args];
					handler(
						states
							.map(state => (state && state[0]) || 0)
							.reduce((a, b) => a + b) / states.length,
						`[${idx}] ${msg}`,
						...args
					);
				}).apply(compiler);
			});
		} else {
			let lastModulesCount = 0;
			let lastEntriesCount = 0;
			let moduleCount = modulesCount;
			let entriesCount = 1;
			let doneModules = 0;
			let doneEntries = 0;
			const activeModules = new Set();
			let lastActiveModule = "";

			const update = () => {
				const percentByModules =
					doneModules / Math.max(lastModulesCount, moduleCount);
				const percentByEntries =
					doneEntries / Math.max(lastEntriesCount, entriesCount);

				const items = [
					0.1 + Math.max(percentByModules, percentByEntries) * 0.6,
					"building"
				];
				if (showEntries) {
					items.push(`${doneEntries}/${entriesCount} entries`);
				}
				if (showModules) {
					items.push(`${doneModules}/${moduleCount} modules`);
				}
				if (showActiveModules) {
					items.push(`${activeModules.size} active`);
					items.push(lastActiveModule);
				}
				handler(...items);
			};

			const moduleAdd = module => {
				moduleCount++;
				if (showActiveModules) {
					const ident = module.identifier();
					if (ident) {
						activeModules.add(ident);
						lastActiveModule = ident;
					}
				}
				update();
			};

			const entryAdd = (entry, name) => {
				entriesCount++;
				update();
			};

			const moduleDone = module => {
				doneModules++;
				if (showActiveModules) {
					const ident = module.identifier();
					if (ident) {
						activeModules.delete(ident);
						if (lastActiveModule === ident) {
							lastActiveModule = "";
							for (const m of activeModules) {
								lastActiveModule = m;
							}
						}
					}
				}
				update();
			};

			const entryDone = (entry, name) => {
				doneEntries++;
				update();
			};

			compiler.hooks.compilation.tap("ProgressPlugin", compilation => {
				if (compilation.compiler.isChild()) return;
				lastModulesCount = moduleCount;
				lastEntriesCount = entriesCount;
				moduleCount = entriesCount = 0;
				doneModules = doneEntries = 0;
				handler(0, "compiling");

				compilation.hooks.buildModule.tap("ProgressPlugin", moduleAdd);
				compilation.hooks.failedModule.tap("ProgressPlugin", moduleDone);
				compilation.hooks.succeedModule.tap("ProgressPlugin", moduleDone);

				compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd);
				compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone);
				compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone);

				const hooks = {
					finishModules: "finish module graph",
					seal: "sealing",
					beforeChunks: "chunk graph",
					afterChunks: "after chunk graph",
					optimizeDependencies: "dependencies optimization",
					afterOptimizeDependencies: "after dependencies optimization",
					optimize: "optimizing",
					optimizeModules: "module optimization",
					afterOptimizeModules: "after module optimization",
					optimizeChunks: "chunk optimization",
					afterOptimizeChunks: "after chunk optimization",
					optimizeTree: "module and chunk tree optimization",
					afterOptimizeTree: "after module and chunk tree optimization",
					optimizeChunkModules: "chunk modules optimization",
					afterOptimizeChunkModules: "after chunk modules optimization",
					reviveModules: "module reviving",
					beforeModuleIds: "before module ids",
					moduleIds: "module ids",
					optimizeModuleIds: "module id optimization",
					afterOptimizeModuleIds: "module id optimization",
					reviveChunks: "chunk reviving",
					beforeChunkIds: "before chunk ids",
					chunkIds: "chunk ids",
					optimizeChunkIds: "chunk id optimization",
					afterOptimizeChunkIds: "after chunk id optimization",
					recordModules: "record modules",
					recordChunks: "record chunks",
					beforeHash: "hashing",
					contentHash: "content hashing",
					afterHash: "after hashing",
					recordHash: "record hash",
					beforeModuleAssets: "module assets processing",
					beforeChunkAssets: "chunk assets processing",
					additionalChunkAssets: "additional chunk assets processing",
					record: "recording",
					additionalAssets: "additional asset processing",
					optimizeChunkAssets: "chunk asset optimization",
					afterOptimizeChunkAssets: "after chunk asset optimization",
					optimizeAssets: "asset optimization",
					afterOptimizeAssets: "after asset optimization",
					afterSeal: "after seal"
				};
				const numberOfHooks = Object.keys(hooks).length;
				Object.keys(hooks).forEach((name, idx) => {
					const title = hooks[name];
					const percentage = (idx / numberOfHooks) * 0.25 + 0.7;
					compilation.hooks[name].intercept({
						name: "ProgressPlugin",
						context: true,
						call: () => {
							handler(percentage, title);
						},
						tap: (context, tap) => {
							if (context) {
								// p is percentage from 0 to 1
								// args is any number of messages in a hierarchical matter
								context.reportProgress = (p, ...args) => {
									handler(percentage, title, tap.name, ...args);
								};
							}
							handler(percentage, title, tap.name);
						}
					});
				});
			});
			compiler.hooks.emit.intercept({
				name: "ProgressPlugin",
				context: true,
				call: () => {
					handler(0.95, "emitting");
				},
				tap: (context, tap) => {
					if (context) {
						context.reportProgress = (p, ...args) => {
							handler(0.95, "emitting", tap.name, ...args);
						};
					}
					handler(0.95, "emitting", tap.name);
				}
			});
			compiler.hooks.afterEmit.intercept({
				name: "ProgressPlugin",
				context: true,
				call: () => {
					handler(0.98, "after emitting");
				},
				tap: (context, tap) => {
					if (context) {
						context.reportProgress = (p, ...args) => {
							handler(0.98, "after emitting", tap.name, ...args);
						};
					}
					handler(0.98, "after emitting", tap.name);
				}
			});
			compiler.hooks.done.tap("ProgressPlugin", () => {
				handler(1, "");
			});
		}
	}
}

ProgressPlugin.defaultOptions = {
	profile: false,
	modulesCount: 500,
	modules: true,
	activeModules: true,
	entries: true
};

module.exports = ProgressPlugin;
<commit_msg>show module count earlier when they are added to the queue<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const validateOptions = require("schema-utils");
const schema = require("../schemas/plugins/ProgressPlugin.json");

/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
/** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */

const createDefaultHandler = profile => {
	let lineCaretPosition = 0;
	let lastMessage = "";
	let lastState;
	let lastStateTime;

	const defaultHandler = (percentage, msg, ...args) => {
		let state = msg;
		const details = args;
		if (percentage < 1) {
			percentage = Math.floor(percentage * 100);
			msg = `${percentage}% ${msg}`;
			if (percentage < 100) {
				msg = ` ${msg}`;
			}
			if (percentage < 10) {
				msg = ` ${msg}`;
			}
			for (let detail of details) {
				if (!detail) continue;
				if (detail.length > 40) {
					detail = `...${detail.substr(detail.length - 39)}`;
				}
				msg += ` ${detail}`;
			}
		}
		if (profile) {
			state = state.replace(/^\d+\/\d+\s+/, "");
			if (percentage === 0) {
				lastState = null;
				lastStateTime = Date.now();
			} else if (state !== lastState || percentage === 1) {
				const now = Date.now();
				if (lastState) {
					const stateMsg = `${now - lastStateTime}ms ${lastState}`;
					goToLineStart(stateMsg);
					process.stderr.write(stateMsg + "\n");
					lineCaretPosition = 0;
				}
				lastState = state;
				lastStateTime = now;
			}
		}
		if (lastMessage !== msg) {
			goToLineStart(msg);
			process.stderr.write(msg);
			lastMessage = msg;
		}
	};

	const goToLineStart = nextMessage => {
		let str = "";
		for (; lineCaretPosition > nextMessage.length; lineCaretPosition--) {
			str += "\b \b";
		}
		for (var i = 0; i < lineCaretPosition; i++) {
			str += "\b";
		}
		lineCaretPosition = nextMessage.length;
		if (str) process.stderr.write(str);
	};

	return defaultHandler;
};

class ProgressPlugin {
	/**
	 * @param {ProgressPluginArgument} options options
	 */
	constructor(options) {
		if (typeof options === "function") {
			options = {
				handler: options
			};
		}

		options = options || {};
		validateOptions(schema, options, "Progress Plugin");
		options = Object.assign({}, ProgressPlugin.defaultOptions, options);

		this.profile = options.profile;
		this.handler = options.handler;
		this.modulesCount = options.modulesCount;
		this.showEntries = options.entries;
		this.showModules = options.modules;
		this.showActiveModules = options.activeModules;
	}

	apply(compiler) {
		const { modulesCount } = this;
		const handler = this.handler || createDefaultHandler(this.profile);
		const showEntries = this.showEntries;
		const showModules = this.showModules;
		const showActiveModules = this.showActiveModules;
		if (compiler.compilers) {
			const states = new Array(compiler.compilers.length);
			compiler.compilers.forEach((compiler, idx) => {
				new ProgressPlugin((p, msg, ...args) => {
					states[idx] = [p, msg, ...args];
					handler(
						states
							.map(state => (state && state[0]) || 0)
							.reduce((a, b) => a + b) / states.length,
						`[${idx}] ${msg}`,
						...args
					);
				}).apply(compiler);
			});
		} else {
			let lastModulesCount = 0;
			let lastEntriesCount = 0;
			let moduleCount = modulesCount;
			let entriesCount = 1;
			let doneModules = 0;
			let doneEntries = 0;
			const activeModules = new Set();
			let lastActiveModule = "";

			const update = () => {
				const percentByModules =
					doneModules / Math.max(lastModulesCount, moduleCount);
				const percentByEntries =
					doneEntries / Math.max(lastEntriesCount, entriesCount);

				const items = [
					0.1 + Math.max(percentByModules, percentByEntries) * 0.6,
					"building"
				];
				if (showEntries) {
					items.push(`${doneEntries}/${entriesCount} entries`);
				}
				if (showModules) {
					items.push(`${doneModules}/${moduleCount} modules`);
				}
				if (showActiveModules) {
					items.push(`${activeModules.size} active`);
					items.push(lastActiveModule);
				}
				handler(...items);
			};

			const moduleAdd = module => {
				moduleCount++;
				update();
			};

			const moduleBuild = module => {
				if (showActiveModules) {
					const ident = module.identifier();
					if (ident) {
						activeModules.add(ident);
						lastActiveModule = ident;
					}
				}
				update();
			};

			const entryAdd = (entry, name) => {
				entriesCount++;
				update();
			};

			const moduleDone = module => {
				doneModules++;
				if (showActiveModules) {
					const ident = module.identifier();
					if (ident) {
						activeModules.delete(ident);
						if (lastActiveModule === ident) {
							lastActiveModule = "";
							for (const m of activeModules) {
								lastActiveModule = m;
							}
						}
					}
				}
				update();
			};

			const entryDone = (entry, name) => {
				doneEntries++;
				update();
			};

			compiler.hooks.compilation.tap("ProgressPlugin", compilation => {
				if (compilation.compiler.isChild()) return;
				lastModulesCount = moduleCount;
				lastEntriesCount = entriesCount;
				moduleCount = entriesCount = 0;
				doneModules = doneEntries = 0;
				handler(0, "compiling");

				compilation.buildQueue.hooks.added.tap("ProgressPlugin", moduleAdd);
				compilation.hooks.buildModule.tap("ProgressPlugin", moduleBuild);
				compilation.hooks.failedModule.tap("ProgressPlugin", moduleDone);
				compilation.hooks.succeedModule.tap("ProgressPlugin", moduleDone);

				compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd);
				compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone);
				compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone);

				const hooks = {
					finishModules: "finish module graph",
					seal: "sealing",
					beforeChunks: "chunk graph",
					afterChunks: "after chunk graph",
					optimizeDependencies: "dependencies optimization",
					afterOptimizeDependencies: "after dependencies optimization",
					optimize: "optimizing",
					optimizeModules: "module optimization",
					afterOptimizeModules: "after module optimization",
					optimizeChunks: "chunk optimization",
					afterOptimizeChunks: "after chunk optimization",
					optimizeTree: "module and chunk tree optimization",
					afterOptimizeTree: "after module and chunk tree optimization",
					optimizeChunkModules: "chunk modules optimization",
					afterOptimizeChunkModules: "after chunk modules optimization",
					reviveModules: "module reviving",
					beforeModuleIds: "before module ids",
					moduleIds: "module ids",
					optimizeModuleIds: "module id optimization",
					afterOptimizeModuleIds: "module id optimization",
					reviveChunks: "chunk reviving",
					beforeChunkIds: "before chunk ids",
					chunkIds: "chunk ids",
					optimizeChunkIds: "chunk id optimization",
					afterOptimizeChunkIds: "after chunk id optimization",
					recordModules: "record modules",
					recordChunks: "record chunks",
					beforeHash: "hashing",
					contentHash: "content hashing",
					afterHash: "after hashing",
					recordHash: "record hash",
					beforeModuleAssets: "module assets processing",
					beforeChunkAssets: "chunk assets processing",
					additionalChunkAssets: "additional chunk assets processing",
					record: "recording",
					additionalAssets: "additional asset processing",
					optimizeChunkAssets: "chunk asset optimization",
					afterOptimizeChunkAssets: "after chunk asset optimization",
					optimizeAssets: "asset optimization",
					afterOptimizeAssets: "after asset optimization",
					afterSeal: "after seal"
				};
				const numberOfHooks = Object.keys(hooks).length;
				Object.keys(hooks).forEach((name, idx) => {
					const title = hooks[name];
					const percentage = (idx / numberOfHooks) * 0.25 + 0.7;
					compilation.hooks[name].intercept({
						name: "ProgressPlugin",
						context: true,
						call: () => {
							handler(percentage, title);
						},
						tap: (context, tap) => {
							if (context) {
								// p is percentage from 0 to 1
								// args is any number of messages in a hierarchical matter
								context.reportProgress = (p, ...args) => {
									handler(percentage, title, tap.name, ...args);
								};
							}
							handler(percentage, title, tap.name);
						}
					});
				});
			});
			compiler.hooks.emit.intercept({
				name: "ProgressPlugin",
				context: true,
				call: () => {
					handler(0.95, "emitting");
				},
				tap: (context, tap) => {
					if (context) {
						context.reportProgress = (p, ...args) => {
							handler(0.95, "emitting", tap.name, ...args);
						};
					}
					handler(0.95, "emitting", tap.name);
				}
			});
			compiler.hooks.afterEmit.intercept({
				name: "ProgressPlugin",
				context: true,
				call: () => {
					handler(0.98, "after emitting");
				},
				tap: (context, tap) => {
					if (context) {
						context.reportProgress = (p, ...args) => {
							handler(0.98, "after emitting", tap.name, ...args);
						};
					}
					handler(0.98, "after emitting", tap.name);
				}
			});
			compiler.hooks.done.tap("ProgressPlugin", () => {
				handler(1, "");
			});
		}
	}
}

ProgressPlugin.defaultOptions = {
	profile: false,
	modulesCount: 500,
	modules: true,
	activeModules: true,
	entries: true
};

module.exports = ProgressPlugin;
<|endoftext|>


<commit_before>var util = require('util');
var pluralize = require('pluralize');
var pathToRegex = require('path-to-regexp');
var procure = require('procure');
var _ = require('lodash');

var Resource = function( name , options ) {
  var self = this;

  if (!name)  throw new Error('Some configuration is required to create a Resource');
  if (!options) var options = name;

  if (!options.paths) options.paths = {};
  if (!options.routes) options.routes = {};
  if (!options.methods) options.methods = {};
  if (!options.templates) options.templates = {};
  if (!options.plugins) options.plugins = [];
  if (!options.requires) options.requires = [];
  if (!options.handlers) options.handlers = {};

  self.name = name;
  self.plural = pluralize( self.name );
  self.internal = options.internal || false;
  self.static = options.static || false;
  self.source = options.source || null; // TODO: switch to a source type, maybe?
  self.options = options;

  self.attributes = {};
  self.keys = { '_id': 0 };

  var nameLower      = self.name.toLowerCase();
  var collectionName = self.plural.toLowerCase();
  self.collection    = collectionName;

  self.synopsis    = options.synopsis    || 'Resource.';
  self.description = options.description || 'A Resource available on this server.';
  self.template    = options.template    || nameLower;

  self.routes = {};
  self.paths = { query: new RegExp( self.routes.query ) };
  self.methods = {};
  self.verbs = {};
  self.names = {
    query:  self.collection,
    get:    nameLower,
    create: nameLower
  };
  self.templates = {
    query: options.templates.query || options.template || (options.singular) ? nameLower : collectionName,
    get:   options.templates.get   || nameLower
  };

  if (self.options.routes) self.routes = _.merge( self.routes , self.options.routes );

  self.fields = {
    id: '_id',
    name: '_id',
    slug: '_id'
  };

  self.requires = options.requires;
  self.handlers = options.handlers;

  // internal resources should now be complete! return!
  if (options.internal || options.static) return;

  // public resources (non-internal) must have some attributes.
  // or not.
  // if (!options.attributes) throw new Error('Resources must have attributes.');
  if (!options.attributes) options.attributes = {};

  // determine which field to use in the path (defaults to _id)
  // TODO: recurse
  Object.keys( options.attributes ).forEach(function( f ) {
    var attribute = options.attributes[ f ];

    self.attributes[ f ] = attribute;

    if ( attribute.id === true ) {
      self.fields.id = f;
      delete self.keys._id; //Remove the _id field from white-list
    }
    if ( attribute.name === true ) {
      self.fields.name = f;
    }

  });

  // build the routes for this resource (passed to generate regexes)
  self.routes = {
    query:   options.routes.query   || '/' + collectionName ,
    get:     options.routes.get     || '/' + collectionName + '/:id',
    create:  options.routes.create  || '/' + collectionName ,
    update:  options.routes.update  || '/' + collectionName + '/:id',
    destroy: options.routes.destroy || '/' + collectionName + '/:id'
  };

  // generate paths for each action, to be used with RegExp.test
  self.paths = {
    query:   options.paths.query   || pathToRegex( self.routes.query ),
    get:     options.paths.get     || pathToRegex( self.routes.get ),
    create:  options.paths.create  || pathToRegex( self.routes.create ),
    update:  options.paths.update  || pathToRegex( self.routes.update ),
    destroy: options.paths.destroy || pathToRegex( self.routes.destroy )
  };

  // set the templates for each action
  self.templates = {
    query:  options.template || options.templates.query  || collectionName,
    get:    options.templates.get    || nameLower,
    create: options.templates.create || nameLower + '-create',
    update: options.templates.update || nameLower + '-update'
  };

}

util.inherits( Resource , require('events').EventEmitter );

Resource.prototype.attach = function( maki ) {
  var self = this;

  self.Schema = new maki.mongoose.Schema( self.options.attributes );
  self.options.attributes.__v = { restricted: true };

  self.Schema.plugin( require('maki-mongoose-hooks') , {
    maki: maki, // <- le sigh.  can we do this another way?
    resource: self
  });

  Object.keys( self.options.methods ).forEach(function( name ) {
    self.Schema.methods[ name ] = self.options.methods[ name ];
  });

  Object.keys( self.options.attributes ).forEach(function( f ) {
    var field = self.options.attributes[ f ];

    if ( field.slug === true ) {
      self.fields.id = f;
      self.Schema.plugin( require('mongoose-slug')( f , {
        unique: true
      }) );

      if (self.fields.name === '_id') {
        self.fields.name = f;
      }
    }

    if ( field.validator ) {
      self.path( f ).validate( field.validator );
    }

    // Add unrestricted keys to an easily accessible array
    if (!field.restricted) {
      self.keys[f] = 1;
    }

  });

  self.options.plugins.forEach(function(plugin) {
    self.Schema.plugin( plugin );
  });

  self.Model = maki.mongoose.model( self.name , self.Schema );

  if (self.source) {
    procure( self.source , function( err , content ) {
      if (err || !content) console.log( self.name , 'issue with collecting sourced content', err );
      var data = [];

      try {
        data = JSON.parse( file );
      } catch (e) {
        console.log( e );
      }

      self.Model.find = function( q ) {
        return {
          exec: function( cb ) {
            cb( null , self.data );
          },
          sort: function() { return this; }
        };
      };

    });
  }

  if (self.internal || self.static) return;
  maki.resources[ self.name ] = self;

}

// TODO: dry with post
Resource.prototype.pre = function( event , binding ) {
  var self = this;

  // TODO: use maki-specific bindings
  var supportedEvents = ['init', 'validate', 'save', 'remove'];
  if ( supportedEvents.indexOf( event ) < 0 ) throw new Error('not a valid event');

  self.Schema.pre( event , true , function( next , done ) {
    var self = this;
    binding.apply( self, [ function() {
      next();
      done();
    } ] );
  });
}

// TODO: dry with pre
Resource.prototype.post = function( event , binding ) {
  var self = this;

  // TODO: use maki-specific bindings
  var supportedEvents = ['init', 'validate', 'save', 'remove'];
  if ( supportedEvents.indexOf( event ) < 0 ) throw new Error('not a valid event');

  self.Schema.post( event , function( doc ) {
    var self = this;
    binding.apply( self, [ doc , function(err) {
      if (err) console.log(err);
    } ] );
  });
}

Resource.prototype.path = function( name ) {
  var self = this;

  return {
    validate: function( validator , errorString ) {
      self.Schema.path( name ).validate( validator , errorString );
    }
  }
}

Resource.prototype.query = function( q , complete ) {
  var self = this;
  if (self.options.static) return complete();

  var Model = self.Model;

  Model.find( q ).sort('-_id').exec(function(err, collection) {
    if (err) return complete(err);
    return complete( err , collection );
  });

}

// get a SINGLE instance of this resource, based on req.param('id')
Resource.prototype.get = function( q , complete ) {
  var self = this;
  if (self.options.static) return complete();

  var Model = self.Model;

  if (!complete) var complete = new Function();
  Model.findOne( q ).exec(function(err, doc) {
    if (err) return complete(err);
    return complete( err , doc );
  });

}

Resource.prototype.create = function( doc , complete ) {
  var self = this;
  if (self.options.static) return complete();

  var Model = self.Model;

  if (!complete) var complete = new Function();
  Model.findOne( doc ).exec(function(err, instance) {
    if (err) return complete(err);
    if (instance) return complete( err , instance );
    var instance = new Model( doc );
    instance.save(function(err) {
      self.emit('create', instance );
      complete( err , instance );
    });
  });
}

Resource.prototype.update = function( query , params , complete ) {
  var self = this;
  //console.log('update()', self.name , self.options.static , query , params );
  if (self.options.static) return complete();

  var Model = self.Model;

  if (!complete) var complete = new Function();
  if (!query) return complete('no query');

  Model.findOne( query ).exec(function(err, doc) {
    if (err) return complete( err );
    if (!doc) return complete( 404 );

    // TODO: deep object merge
    for (var prop in params) {
      doc[ prop ] = params[ prop ];
    }

    doc.save(function(err) {
      // TODO: consider offering a changeset.
      self.emit('update', doc );
      complete( err , doc );
    });

  });
}

Resource.prototype.destroy = function( id , complete ) {
  if (self.options.static) return complete();

  var self = this;
  var Model = self.Model;

  return;

}

module.exports = Resource;
<commit_msg>Tidy implementation.<commit_after>var util = require('util');
var pluralize = require('pluralize');
var pathToRegex = require('path-to-regexp');
var procure = require('procure');
var _ = require('lodash');

var Resource = function( name , options ) {
  var self = this;

  if (!name)  throw new Error('Some configuration is required to create a Resource');
  if (!options) var options = name;

  if (!options.paths) options.paths = {};
  if (!options.routes) options.routes = {};
  if (!options.methods) options.methods = {};
  if (!options.templates) options.templates = {};
  if (!options.plugins) options.plugins = [];
  if (!options.requires) options.requires = [];
  if (!options.handlers) options.handlers = {};

  self.name = name;
  self.plural = pluralize( self.name );
  self.internal = options.internal || false;
  self.static = options.static || false;
  self.source = options.source || null; // TODO: switch to a source type, maybe?
  self.options = options;

  self.attributes = {};
  self.keys = { '_id': 0 };

  var nameLower      = self.name.toLowerCase();
  var collectionName = self.plural.toLowerCase();
  self.collection    = collectionName;

  self.synopsis    = options.synopsis    || 'Resource.';
  self.description = options.description || 'A Resource available on this server.';
  self.template    = options.template    || nameLower;

  self.routes = {};
  self.paths = { query: new RegExp( self.routes.query ) };
  self.methods = {};
  self.verbs = {};
  self.names = {
    query:  self.collection,
    get:    nameLower,
    create: nameLower
  };
  self.templates = {
    query: options.templates.query || options.template || (options.singular) ? nameLower : collectionName,
    get:   options.templates.get   || nameLower
  };

  if (self.options.routes) self.routes = _.merge( self.routes , self.options.routes );

  self.fields = {
    id: '_id',
    name: '_id',
    slug: '_id'
  };

  self.requires = options.requires;
  self.handlers = options.handlers;

  // internal resources should now be complete! return!
  if (options.internal || options.static) return;

  // public resources (non-internal) must have some attributes.
  // or not.
  // if (!options.attributes) throw new Error('Resources must have attributes.');
  if (!options.attributes) options.attributes = {};

  // determine which field to use in the path (defaults to _id)
  // TODO: recurse
  Object.keys( options.attributes ).forEach(function( f ) {
    var attribute = options.attributes[ f ];

    self.attributes[ f ] = attribute;

    if ( attribute.id === true ) {
      self.fields.id = f;
      delete self.keys._id; //Remove the _id field from white-list
    }
    if ( attribute.name === true ) {
      self.fields.name = f;
    }

  });

  // build the routes for this resource (passed to generate regexes)
  self.routes = {
    query:   options.routes.query   || '/' + collectionName ,
    get:     options.routes.get     || '/' + collectionName + '/:id',
    create:  options.routes.create  || '/' + collectionName ,
    update:  options.routes.update  || '/' + collectionName + '/:id',
    destroy: options.routes.destroy || '/' + collectionName + '/:id'
  };

  // generate paths for each action, to be used with RegExp.test
  self.paths = {
    query:   options.paths.query   || pathToRegex( self.routes.query ),
    get:     options.paths.get     || pathToRegex( self.routes.get ),
    create:  options.paths.create  || pathToRegex( self.routes.create ),
    update:  options.paths.update  || pathToRegex( self.routes.update ),
    destroy: options.paths.destroy || pathToRegex( self.routes.destroy )
  };

  // set the templates for each action
  self.templates = {
    query:  options.template || options.templates.query  || collectionName,
    get:    options.templates.get    || nameLower,
    create: options.templates.create || nameLower + '-create',
    update: options.templates.update || nameLower + '-update'
  };

}

util.inherits( Resource , require('events').EventEmitter );

Resource.prototype.attach = function( maki ) {
  var self = this;

  self.Schema = new maki.mongoose.Schema( self.options.attributes );
  self.options.attributes.__v = { restricted: true };

  self.Schema.plugin( require('maki-mongoose-hooks') , {
    maki: maki, // <- le sigh.  can we do this another way?
    resource: self
  });

  Object.keys( self.options.methods ).forEach(function( name ) {
    self.Schema.methods[ name ] = self.options.methods[ name ];
  });

  Object.keys( self.options.attributes ).forEach(function( f ) {
    var field = self.options.attributes[ f ];

    if ( field.slug === true ) {
      self.fields.id = f;
      self.Schema.plugin( require('mongoose-slug')( f , {
        unique: true
      }) );

      if (self.fields.name === '_id') {
        self.fields.name = f;
      }
    }

    if ( field.validator ) {
      self.path( f ).validate( field.validator );
    }

    // Add unrestricted keys to an easily accessible array
    if (!field.restricted) {
      self.keys[f] = 1;
    }

  });

  self.options.plugins.forEach(function(plugin) {
    self.Schema.plugin( plugin );
  });

  self.Model = maki.mongoose.model( self.name , self.Schema );

  if (self.source) {
    procure( self.source , function( err , content ) {
      if (err || !content) console.log( self.name , 'issue with collecting sourced content', err );
      self.data = [];

      try {
        self.data = JSON.parse( content );
      } catch (e) {
        console.log( self.name , 'sourced content', 'json parse error', e );
      }

      self.Model.find = function( q ) {
        return {
          exec: function( cb ) {
            cb( null , self.data );
          },
          sort: function() { return this; }
        };
      };

    });
  }

  if (self.internal || self.static) return;
  maki.resources[ self.name ] = self;

}

// TODO: dry with post
Resource.prototype.pre = function( event , binding ) {
  var self = this;

  // TODO: use maki-specific bindings
  var supportedEvents = ['init', 'validate', 'save', 'remove'];
  if ( supportedEvents.indexOf( event ) < 0 ) throw new Error('not a valid event');

  self.Schema.pre( event , true , function( next , done ) {
    var self = this;
    binding.apply( self, [ function() {
      next();
      done();
    } ] );
  });
}

// TODO: dry with pre
Resource.prototype.post = function( event , binding ) {
  var self = this;

  // TODO: use maki-specific bindings
  var supportedEvents = ['init', 'validate', 'save', 'remove'];
  if ( supportedEvents.indexOf( event ) < 0 ) throw new Error('not a valid event');

  self.Schema.post( event , function( doc ) {
    var self = this;
    binding.apply( self, [ doc , function(err) {
      if (err) console.log(err);
    } ] );
  });
}

Resource.prototype.path = function( name ) {
  var self = this;

  return {
    validate: function( validator , errorString ) {
      self.Schema.path( name ).validate( validator , errorString );
    }
  }
}

Resource.prototype.query = function( q , complete ) {
  var self = this;
  if (self.options.static) return complete();

  var Model = self.Model;

  Model.find( q ).sort('-_id').exec(function(err, collection) {
    if (err) return complete(err);
    return complete( err , collection );
  });

}

// get a SINGLE instance of this resource, based on req.param('id')
Resource.prototype.get = function( q , complete ) {
  var self = this;
  if (self.options.static) return complete();

  var Model = self.Model;

  if (!complete) var complete = new Function();
  Model.findOne( q ).exec(function(err, doc) {
    if (err) return complete(err);
    return complete( err , doc );
  });

}

Resource.prototype.create = function( doc , complete ) {
  var self = this;
  if (self.options.static) return complete();

  var Model = self.Model;

  if (!complete) var complete = new Function();
  Model.findOne( doc ).exec(function(err, instance) {
    if (err) return complete(err);
    if (instance) return complete( err , instance );
    var instance = new Model( doc );
    instance.save(function(err) {
      self.emit('create', instance );
      complete( err , instance );
    });
  });
}

Resource.prototype.update = function( query , params , complete ) {
  var self = this;
  //console.log('update()', self.name , self.options.static , query , params );
  if (self.options.static) return complete();

  var Model = self.Model;

  if (!complete) var complete = new Function();
  if (!query) return complete('no query');

  Model.findOne( query ).exec(function(err, doc) {
    if (err) return complete( err );
    if (!doc) return complete( 404 );

    // TODO: deep object merge
    for (var prop in params) {
      doc[ prop ] = params[ prop ];
    }

    doc.save(function(err) {
      // TODO: consider offering a changeset.
      self.emit('update', doc );
      complete( err , doc );
    });

  });
}

Resource.prototype.destroy = function( id , complete ) {
  if (self.options.static) return complete();

  var self = this;
  var Model = self.Model;

  return;

}

module.exports = Resource;
<|endoftext|>


<commit_before>Kwf.EyeCandy.List.Plugins.ActiveListener.LargeContentAjax = Ext.extend(Kwf.EyeCandy.List.Plugins.Abstract, {
    init: function() {
        this.activeItem = null;
        this.list.on('activeChanged', function(item) {
            this._activate(item);
        }, this);

        this.largeContainer = this.list.el.child(this.largeContainerSelector);
        this.largeContent = {};
    },

    _loadItem: function(item, options)
    {
        if (this.largeContent[item.id]) return; //already loaded/loading

        this.largeContent[item.id] = this.largeContainer.createChild({
            html: '

'
        });
        this.largeContent[item.id].enableDisplayMode('block');

        var url = '/kwf/util/kwc/render';
        if (Kwf.Debug.rootFilename) url = Kwf.Debug.rootFilename + url;

        Ext.Ajax.request({
            params: { url: item.el.child('a').dom.href },
            url: url,
            success: function(response) {
                var contentEl = this.largeContent[item.id].createChild();
                contentEl.update(response.responseText);

                var showContent = function() {
                    this.largeContent[item.id].child('.loading').hide();
                    contentEl.fadeIn();
                    if (options && options.success) {
                        options.success.call(options.scope || this);
                    }
                };

                var imagesToLoad = 0;
                contentEl.query('img').each(function(imgEl) {
                    imagesToLoad++;
                    imgEl.onload = (function() {
                        imagesToLoad--;
                        if (imagesToLoad <= 0) showContent.call(this)
                    }).createDelegate(this);
                }, this);

                Kwf.callOnContentReady();
                contentEl.hide(); //after callOnContentReady else cufon won't work inside contentEl
            },
            scope: this
        });
    },
    _activate: function(item)
    {
        if (!this.activeItem) {
            //the first one activated must be already shown in largeContainer
            this.largeContent[item.id] = this.largeContainer.child('div');
            this.largeContent[item.id].enableDisplayMode('block');
            this.activeItem = item;
            return;
        }

        if (!this.largeContent[item.id]) {
            this._loadItem(item);
        }
        var activeEl = this.largeContent[this.activeItem.id];
        var nextEl = this.largeContent[item.id];

        if (this.transition == 'fade') {
            activeEl.dom.style.zIndex = 2;

            nextEl.dom.style.zIndex = 1;
            nextEl.show();

            activeEl.fadeOut(Ext.applyIf({
                useDisplay: true,
            }, this.transitionConfig));
        } else if (this.transition == 'slide') {
            activeEl.slideOut(
                this.activeItem.listIndex < item.listIndex ? 'l' : 'r',
                Ext.applyIf({
                    remove: false,
                    useDisplay: true
                }, this.transitionConfig)
            );
            nextEl.slideIn(
                this.activeItem.listIndex < item.listIndex ? 'r' : 'l',
                Ext.applyIf({
                    remove: false,
                    useDisplay: true
                }, this.transitionConfig)
            );
        } else {
            //default, no fx; switch hard
            activeEl.hide();
            nextEl.show();
        }

        this.activeItem = item;
    }
});
<commit_msg>ListSwitch: preload next large content after a delay of 1s<commit_after>Kwf.EyeCandy.List.Plugins.ActiveListener.LargeContentAjax = Ext.extend(Kwf.EyeCandy.List.Plugins.Abstract, {
    init: function() {
        this.activeItem = null;
        this.list.on('activeChanged', function(item) {
            this._activate(item);
        }, this);

        this.largeContainer = this.list.el.child(this.largeContainerSelector);
        this.largeContent = {};
    },

    _loadItem: function(item, options)
    {
        if (this.largeContent[item.id]) return; //already loaded/loading

        this.largeContent[item.id] = this.largeContainer.createChild({
            html: '

'
        });
        this.largeContent[item.id].enableDisplayMode('block');
        if (!options.visible) {
            this.largeContent[item.id].hide();
        }

        var url = '/kwf/util/kwc/render';
        if (Kwf.Debug.rootFilename) url = Kwf.Debug.rootFilename + url;

        Ext.Ajax.request({
            params: { url: item.el.child('a').dom.href },
            url: url,
            success: function(response) {
                var contentEl = this.largeContent[item.id].createChild();
                contentEl.update(response.responseText);

                if (this.largeContent[item.id].isVisible()) {

                    var showContent = function() {
                        this.largeContent[item.id].child('.loading').remove();
                        contentEl.fadeIn();
                        if (options && options.success) {
                            options.success.call(options.scope || this);
                        }
                    };

                    var imagesToLoad = 0;
                    contentEl.query('img').each(function(imgEl) {
                        imagesToLoad++;
                        imgEl.onload = (function() {
                            imagesToLoad--;
                            if (imagesToLoad <= 0) showContent.call(this)
                        }).createDelegate(this);
                    }, this);

                    Kwf.callOnContentReady();
                    contentEl.hide(); //after callOnContentReady else cufon won't work inside contentEl
                    if (imagesToLoad == 0) showContent.call(this)

                } else {
                    this.largeContent[item.id].child('.loading').remove();
                    this.largeContent[item.id].show();
                    Kwf.callOnContentReady();
                    this.largeContent[item.id].hide();
                }
            },
            scope: this
        });

    },
    _activate: function(item)
    {
        var nextItem = this.list.getItem(item.listIndex+1);
        if (nextItem && !this.largeContent[nextItem.id]) {
            //preload
            this._loadItem.defer(1000, this, [nextItem, {visible: false}]);
        }

        if (!this.activeItem) {
            //the first one activated must be already shown in largeContainer
            this.largeContent[item.id] = this.largeContainer.child('div');
            this.largeContent[item.id].enableDisplayMode('block');
            this.activeItem = item;
            return;
        }

        this._loadItem(item, {visible: true});

        var activeEl = this.largeContent[this.activeItem.id];
        var nextEl = this.largeContent[item.id];

        if (this.transition == 'fade') {
            activeEl.dom.style.zIndex = 2;

            nextEl.dom.style.zIndex = 1;
            nextEl.show();

            activeEl.fadeOut(Ext.applyIf({
                useDisplay: true,
            }, this.transitionConfig));
        } else if (this.transition == 'slide') {
            activeEl.slideOut(
                this.activeItem.listIndex < item.listIndex ? 'l' : 'r',
                Ext.applyIf({
                    remove: false,
                    useDisplay: true
                }, this.transitionConfig)
            );
            nextEl.slideIn(
                this.activeItem.listIndex < item.listIndex ? 'r' : 'l',
                Ext.applyIf({
                    remove: false,
                    useDisplay: true
                }, this.transitionConfig)
            );
        } else {
            //default, no fx; switch hard
            activeEl.hide();
            nextEl.show();
        }

        this.activeItem = item;
    }
});
<|endoftext|>


<commit_before>/**
 * Minimalistic Implement for Minimal.View Class
 * @author Jerome Vial, Bruno Santos
 */
define(function(require, exports, module) {

	var _log = __debug('view-core-loader').defineLevel();

	var Loader = new Class({

		options: {
			loader: {
				text: 'Loading...',
				delay: 200
			}
		},

		/**
		 * init loader
		 * @return {void}
		 */
		_initLoader: function() {
			_log.debug('_initLoader');

			this.addEvents({
				getData: this.showLoader.bind(this),
				noData: this.hideLoader.bind(this),
				setList: this.hideLoader.bind(this),
				progress: this.setLoaderText.bind(this)
			});
		},

		/**
		 * Show Loader
		 * @return {void}
		 */
		showLoader: function() {
			_log.debug('showLoader');

			var self = this;
			var opts = this.options.loader;

			this.setStatus(opts.text);
			this._createLoader();

			/*display loader after 200ms*/
			clearTimeout(this.loaderTimeout);
			this.loaderTimeout = setTimeout(function() {
				self.loader.setStyle('display', 'initial');
			}, opts.delay);
		},

		/**
		 * Hide Loader
		 * @return {void}
		 */
		hideLoader: function() {
			_log.debug('hideLoader', this.loader);

			if (!this.loader) {
				return;
			}

			clearTimeout(this.loaderTimeout);
			this.loader.destroy();
			this.loader = undefined;
			this.loaderText = undefined;
		},

		/**
		 * create loader element
		 * @return {void}
		 */
		_createLoader: function() {
			_log.debug('_createLoader', this.element);

			if (!this.element) {
				return;
			}

			var opts = this.options.loader;

			var loader = this.loader = new Element('div', {
				class: 'ui-loader',
				styles: {
					display: 'none'
				}
			}).inject(this.element);

			if (opts.text) {
				this.loaderText = new Element('span', {
					html: opts.text,
					class: 'loader-text'
				}).inject(loader, 'top');
			}

			new Element('div', {
				class: 'loader-bar'
			}).inject(loader, 'bottom');
		},

		/**
		 * set loader text
		 * @return {void}
		 */
		setLoaderText: function(text) {
			if (!this.loaderText) {
				return;
			}

			this.loaderText.set('html', text);
		}

	});

	module.exports = Loader;

});
<commit_msg>enhance text options in loader module<commit_after>/**
 * Loader Module
 * @author Jerome Vial, Bruno Santos
 */
define(function(require, exports, module) {

	var _log = __debug('view-core-loader').defineLevel();

	var Loader = new Class({

		options: {
			loader: {
				text: {
					default: 'Loading...',
					statusBar: 'Loading ',
				},
				delay: 200
			}
		},

		/**
		 * init loader
		 * @return {void}
		 */
		_initLoader: function() {
			_log.debug('_initLoader');

			this.statusBarText = this.options.loader.text.statusBar;

			this.addEvents({
				getData: this.showLoader.bind(this),
				noData: this.hideLoader.bind(this),
				setList: this.hideLoader.bind(this),
				progress: this.setLoaderText.bind(this)
			});
		},

		/**
		 * Show Loader
		 * @return {void}
		 */
		showLoader: function() {
			_log.debug('showLoader');

			var self = this;
			var opts = this.options.loader;

			this.setStatus(this.statusBarText);

			if (!this.loader) {
				this._createLoader();
			}

			/*display loader after 200ms*/
			clearTimeout(this.loaderTimeout);
			this.loaderTimeout = setTimeout(function() {
				self.loader.setStyle('display', 'initial');
			}, opts.delay);
		},

		/**
		 * Hide Loader
		 * @return {void}
		 */
		hideLoader: function() {
			_log.debug('hideLoader', this.loader);

			if (!this.loader) {
				return;
			}

			clearTimeout(this.loaderTimeout);
			this.loader.destroy();
			this.loader = undefined;
			this.loaderText = undefined;
		},

		/**
		 * create loader element
		 * @return {void}
		 */
		_createLoader: function() {
			_log.debug('_createLoader', this.element);

			if (!this.element) {
				return;
			}

			var opts = this.options.loader;

			var loader = this.loader = new Element('div', {
				class: 'ui-loader',
				styles: {
					display: 'none'
				}
			}).inject(this.element);

			this.loaderText = new Element('span', {
				html: opts.text.default,
				class: 'loader-text'
			}).inject(loader, 'top');

			new Element('div', {
				class: 'loader-bar'
			}).inject(loader, 'bottom');
		},

		/**
		 * set loader text
		 * @return {void}
		 */
		setLoaderText: function(text) {
			//_log.debug('setLoaderText', this.loaderText, text);

			if (this.loaderText) {
				this.loaderText.set('html', text);
			} else if (text) {
				this.setStatus(this.statusBarText + text);
			}
		}

	});

	module.exports = Loader;

});
<|endoftext|>


<commit_before>/**
 * @file A service focused on sending emails.
 * @author {@link https://github.com/j-rewerts|Jared Rewerts}
 */

/**
 * This service handles sending emails using MergeTemplates.
 * TODO Performance improvements in here could be very important.
 *
 * @type {Object}
 */
var EmailService = {

  /**
   * Runs a MergeTemplate, sending emails as needed. This actually reloads the MergeTemplate,
   * so it runs the most up to date version.
   *
   * @method
   * @param {MergeTemplate} template The MergeTemplate to run.
   */
  startMergeTemplate: function(template) {
    try {
      MergeTemplateService.validate(template);
    }
    catch(e) {
      log(e);
      throw e;
    }

    template = MergeTemplateService.getByID(template.id);
    if (template === null) {
      return;
    }

    if (template.mergeData.repeater == "onform"){
      EmailService.mergeOnformTemplate(template);
    }
    else if (template.mergeData.repeater == "auto" || template.mergeData.repeater == "off"){
      EmailService.mergeAllTemplate(template);
    }

  },

  mergeOnformTemplate: function(template) {
    log('New onform submission... to template' + template.id);

    var ss = Utility.getSpreadsheet();
    var sheet = ss.getSheetByName(template.mergeData.sheet);
    var range = sheet.getDataRange();
    var headerRow = template.mergeData.headerRow;
    var i = range.getNumRows()-1;
    var rowNum = range.getRowIndex() + i;
    var row = range.offset(i, 0, 1, range.getNumColumns());
    var context = RenderService.getContext(template.mergeData.sheet, headerRow, rowNum);
    var renderOptions = { context: context };
    var header = HeaderService.get(template.mergeData.sheet, template.mergeData.headerRow);
    var conditional = template.mergeData.conditional ? RenderService.render(template.mergeData.conditional, renderOptions) : 'true';
    if (conditional && typeof conditional.toLowerCase === 'function')
       conditional = conditional.toLowerCase();
      if (conditional === 'yes') // other keywords could be added here.
        conditional = 'true';
      if (conditional === 'true') {
        var to = template.mergeData.data.to ? RenderService.render(template.mergeData.data.to, renderOptions) : null;
        var cc = template.mergeData.data.cc ? RenderService.render(template.mergeData.data.cc, renderOptions) : null;
        var bcc = template.mergeData.data.bcc ? RenderService.render(template.mergeData.data.bcc, renderOptions) : null;
        var subject = template.mergeData.data.subject ? RenderService.render(template.mergeData.data.subject, renderOptions) : null;
        var body = template.mergeData.data.body ? RenderService.render(template.mergeData.data.body, renderOptions) : null;
        try {
          // We only timestamp when the email successfully sends.
          if ((conditional === 'true') &&
            EmailService.send(to, subject, body, cc, bcc, true)) {
            var timestampName = template.mergeData.timestampColumn.replace(/(<<|>>)/g, '');
            var timeCell = row.getCell(1, header.indexOf(timestampName) + 1);
            var formattedDate = Utilities.formatDate(new Date(), 'America/Edmonton', "MM/dd/yyyy HH:mm:ss");
            timeCell.setValue(formattedDate);
          }
        }

        catch (e) {
          console.log(e);
        }
      }

    log('Ending onform submission sending...');
  },

  mergeAllTemplate: function(template) {
    log('Starting merge auto template ' + template.id);

    var ss = Utility.getSpreadsheet();
    var sheet = ss.getSheetByName(template.mergeData.sheet);
    var range = sheet.getDataRange();
    var headerRow = template.mergeData.headerRow;

    for (var i = parseInt(template.mergeData.headerRow); i < range.getNumRows(); i++) {

      var rowNum = range.getRowIndex() + i;
      var row = range.offset(i, 0, 1, range.getNumColumns());
      var context = RenderService.getContext(template.mergeData.sheet, headerRow, rowNum);
      var renderOptions = { context: context };
      var header = HeaderService.get(template.mergeData.sheet, template.mergeData.headerRow);
      var conditional = template.mergeData.conditional ? RenderService.render(template.mergeData.conditional, renderOptions) : 'true';
      if (conditional && typeof conditional.toLowerCase === 'function')
        conditional = conditional.toLowerCase();
      if (conditional === 'yes') // other keywords could be added here.
        conditional = 'true';
      if (conditional === 'true') {
        var to = template.mergeData.data.to ? RenderService.render(template.mergeData.data.to, renderOptions) : null;
        var cc = template.mergeData.data.cc ? RenderService.render(template.mergeData.data.cc, renderOptions) : null;
        var bcc = template.mergeData.data.bcc ? RenderService.render(template.mergeData.data.bcc, renderOptions) : null;
        var subject = template.mergeData.data.subject ? RenderService.render(template.mergeData.data.subject, renderOptions) : null;
        var body = template.mergeData.data.body ? RenderService.render(template.mergeData.data.body, renderOptions) : null;

        try {
          // We only timestamp when the email successfully sends.
          if ((conditional === 'true') &&
            EmailService.send(to, subject, body, cc, bcc, true)) {

            var timestampName = template.mergeData.timestampColumn.replace(/(<<|>>)/g, '');
            var timeCell = row.getCell(1, header.indexOf(timestampName) + 1);
            var formattedDate = Utilities.formatDate(new Date(), 'America/Edmonton', "MM/dd/yyyy HH:mm:ss");

            timeCell.setValue(formattedDate);
          }
        }
        catch (e) {
          console.log(e);
        }
      }
    }

    log('Ending merge auto template...');
  },

  /**
   * Sends a test email to the current user.
   *
   * @param  {string} sheetName The name of the Sheet to send from.
   * @param  {string} headerRow The 1-based row the header is in.
   * @param  {string} subject The whack-whacked subject.
   * @param  {string} body The whack whacked body.
   */
  sendTest: function(sheetName, headerRow, subject, body) {
    log('Sending test email');
    var ss = Utility.getSpreadsheet();
    var sheet = ss.getSheetByName(sheetName);
    var range = sheet.getDataRange();

    var context = RenderService.getContext(null, headerRow, range.getRowIndex()+1);
    var renderOptions = { context: context };

    var subjectRendered = subject ? RenderService.render(subject, renderOptions) : null;
    var bodyRendered = body ? RenderService.render(body, renderOptions) : null;

    EmailService.send(Session.getActiveUser().getEmail(), subjectRendered, bodyRendered, null, null, true);
  },

  /**
   * Handles sending emails. Emails that don't have "to" defined return false.
   * Emails that contain closing </html> tags result in an html email being sent.
   *
   * @param {string} to The primary recipients of the email. Can be a comma delimited list of users.
   * @param {string} subject The subject of the email. This can be an empty string.
   * @param {string} body The body of the email. This can be an empty string.
   * @param {string|undefined} cc Secondary recipients of the email. Can be a comma delimited list of users.
   * @param {string|undefined} bcc Blind copied recipients. Can be a comma delimited list of users.
   * @param {boolean} isBodyHtml isBodyHtml explicitly sets the body text to be send as html
   * @return {boolean} true if the email was sent, false if it wasn't.
   */
  send: function(to, subject, body, cc, bcc, isBodyHtml) {
    if (to === '' || to == null) {
      log('Unable send email, invalid email address... ');
      return false;
    }
    var sendTime = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM/dd/yyyy HH:mm:ss");
    log('At '+sendTime+', Sending email to ' + JSON.stringify({
      to: to,
      cc: cc,
      bcc: bcc
    }));

    if (isBodyHtml || /<html>/.test(body)) {
      GmailApp.sendEmail(to, subject, body, {
        htmlBody: body,
        cc: cc,
        bcc: bcc
      });
    }
    else {
      GmailApp.sendEmail(to, subject, body, {
        cc: cc,
        bcc: bcc
      });
    }

    return true;
  },

  /******* Private / utility functions *******/

  /**
   * Retrieves the required fields for an email to be sent.
   *
   * @param {String[]} headerRow An array of header values.
   * @param {String[]} row An array of row values.
   * @param {Object} template The MergeTemplate used to send the email. See the client-side object for info.
   * @return {Boolean} true if the email was sent, false otherwise.
   */
  getEmailFields: function(headerRow, row, template) {
    var valueMap = {};
    for (var j = 0; j < headerRow.length; j++) {
      valueMap[headerRow[j]] = row[j];
    }

    if (template.mergeData.type.toLowerCase() === 'email') {
      return {
        to: EmailService.replaceTags(template.mergeData.data.to, valueMap),
        cc: EmailService.replaceTags(template.mergeData.data.cc, valueMap),
        bcc: EmailService.replaceTags(template.mergeData.data.bcc, valueMap),
        subject: EmailService.replaceTags(template.mergeData.data.subject, valueMap),
        body: EmailService.replaceTags(template.mergeData.data.body, valueMap),
        conditional: EmailService.replaceTags(template.mergeData.conditional, valueMap).toLowerCase()
      };
    }
    else if (template.mergeData.type.toLowerCase() === 'document') {
      var bodyHTML = DocumentService.getDocumentAsHTML(template.mergeData.data.documentID);
      return {
        to: EmailService.replaceTags(template.mergeData.data.to, valueMap),
        cc: EmailService.replaceTags(template.mergeData.data.cc, valueMap),
        bcc: EmailService.replaceTags(template.mergeData.data.bcc, valueMap),
        subject: EmailService.replaceTags(template.mergeData.data.subject, valueMap),
        body: EmailService.replaceTags(bodyHTML, valueMap),
        conditional: EmailService.replaceTags(template.mergeData.conditional, valueMap).toLowerCase()
      };
    }

    return {};
  },

  /**
   * This function replaces  all instances of <<tags>> with the data in headerToData.
   *
   * @param {string} text The string that contains the tags.
   * @param {Object} headerToData A key-value pair where the key is a column name and the value is the data in the
   * column.
   * @return {string} The text with all tags replaced with data.
   */
  replaceTags: function(text, headerToData) {
    if (text == null) {
      text = '';
    }

    // This must match <<these>> and <<these>> since we need to support HTML.
    var dataText = text.replace(/<<(.*?)>>|<<(.*?)>>/g, function(match, m1, m2, offset, string) {
      if (m1 && headerToData[m1]) {
        return headerToData[m1];
      }
      else if (m2 && headerToData[m2]) {
        return headerToData[m2];
      }
      return '';
    });

    return dataText;
  }
};
<commit_msg>fix timezone<commit_after>/**
 * @file A service focused on sending emails.
 * @author {@link https://github.com/j-rewerts|Jared Rewerts}
 */

/**
 * This service handles sending emails using MergeTemplates.
 * TODO Performance improvements in here could be very important.
 *
 * @type {Object}
 */
var EmailService = {

  /**
   * Runs a MergeTemplate, sending emails as needed. This actually reloads the MergeTemplate,
   * so it runs the most up to date version.
   *
   * @method
   * @param {MergeTemplate} template The MergeTemplate to run.
   */
  startMergeTemplate: function(template) {
    try {
      MergeTemplateService.validate(template);
    }
    catch(e) {
      log(e);
      throw e;
    }

    template = MergeTemplateService.getByID(template.id);
    if (template === null) {
      return;
    }

    if (template.mergeData.repeater == "onform"){
      EmailService.mergeOnformTemplate(template);
    }
    else if (template.mergeData.repeater == "auto" || template.mergeData.repeater == "off"){
      EmailService.mergeAllTemplate(template);
    }

  },

  mergeOnformTemplate: function(template) {
    log('New onform submission... to template' + template.id);

    var ss = Utility.getSpreadsheet();
    var sheet = ss.getSheetByName(template.mergeData.sheet);
    var range = sheet.getDataRange();
    var headerRow = template.mergeData.headerRow;
    var i = range.getNumRows()-1;
    var rowNum = range.getRowIndex() + i;
    var row = range.offset(i, 0, 1, range.getNumColumns());
    var context = RenderService.getContext(template.mergeData.sheet, headerRow, rowNum);
    var renderOptions = { context: context };
    var header = HeaderService.get(template.mergeData.sheet, template.mergeData.headerRow);
    var conditional = template.mergeData.conditional ? RenderService.render(template.mergeData.conditional, renderOptions) : 'true';
    if (conditional && typeof conditional.toLowerCase === 'function')
       conditional = conditional.toLowerCase();
      if (conditional === 'yes') // other keywords could be added here.
        conditional = 'true';
      if (conditional === 'true') {
        var to = template.mergeData.data.to ? RenderService.render(template.mergeData.data.to, renderOptions) : null;
        var cc = template.mergeData.data.cc ? RenderService.render(template.mergeData.data.cc, renderOptions) : null;
        var bcc = template.mergeData.data.bcc ? RenderService.render(template.mergeData.data.bcc, renderOptions) : null;
        var subject = template.mergeData.data.subject ? RenderService.render(template.mergeData.data.subject, renderOptions) : null;
        var body = template.mergeData.data.body ? RenderService.render(template.mergeData.data.body, renderOptions) : null;
        try {
          // We only timestamp when the email successfully sends.
          if ((conditional === 'true') &&
            EmailService.send(to, subject, body, cc, bcc, true)) {
            var timestampName = template.mergeData.timestampColumn.replace(/(<<|>>)/g, '');
            var timeCell = row.getCell(1, header.indexOf(timestampName) + 1);
            var formattedDate = Utilities.formatDate(new Date(), 'America/Edmonton', "MM/dd/yyyy HH:mm:ss");
            timeCell.setValue(formattedDate);
          }
        }

        catch (e) {
          console.log(e);
        }
      }

    log('Ending onform submission sending...');
  },

  mergeAllTemplate: function(template) {
    log('Starting merge auto template ' + template.id);

    var ss = Utility.getSpreadsheet();
    var sheet = ss.getSheetByName(template.mergeData.sheet);
    var range = sheet.getDataRange();
    var headerRow = template.mergeData.headerRow;

    for (var i = parseInt(template.mergeData.headerRow); i < range.getNumRows(); i++) {

      var rowNum = range.getRowIndex() + i;
      var row = range.offset(i, 0, 1, range.getNumColumns());
      var context = RenderService.getContext(template.mergeData.sheet, headerRow, rowNum);
      var renderOptions = { context: context };
      var header = HeaderService.get(template.mergeData.sheet, template.mergeData.headerRow);
      var conditional = template.mergeData.conditional ? RenderService.render(template.mergeData.conditional, renderOptions) : 'true';
      if (conditional && typeof conditional.toLowerCase === 'function')
        conditional = conditional.toLowerCase();
      if (conditional === 'yes') // other keywords could be added here.
        conditional = 'true';
      if (conditional === 'true') {
        var to = template.mergeData.data.to ? RenderService.render(template.mergeData.data.to, renderOptions) : null;
        var cc = template.mergeData.data.cc ? RenderService.render(template.mergeData.data.cc, renderOptions) : null;
        var bcc = template.mergeData.data.bcc ? RenderService.render(template.mergeData.data.bcc, renderOptions) : null;
        var subject = template.mergeData.data.subject ? RenderService.render(template.mergeData.data.subject, renderOptions) : null;
        var body = template.mergeData.data.body ? RenderService.render(template.mergeData.data.body, renderOptions) : null;

        try {
          // We only timestamp when the email successfully sends.
          if ((conditional === 'true') &&
            EmailService.send(to, subject, body, cc, bcc, true)) {

            var timestampName = template.mergeData.timestampColumn.replace(/(<<|>>)/g, '');
            var timeCell = row.getCell(1, header.indexOf(timestampName) + 1);
            var formattedDate = Utilities.formatDate(new Date(), 'America/Edmonton', "MM/dd/yyyy HH:mm:ss");

            timeCell.setValue(formattedDate);
          }
        }
        catch (e) {
          console.log(e);
        }
      }
    }

    log('Ending merge auto template...');
  },

  /**
   * Sends a test email to the current user.
   *
   * @param  {string} sheetName The name of the Sheet to send from.
   * @param  {string} headerRow The 1-based row the header is in.
   * @param  {string} subject The whack-whacked subject.
   * @param  {string} body The whack whacked body.
   */
  sendTest: function(sheetName, headerRow, subject, body) {
    log('Sending test email');
    var ss = Utility.getSpreadsheet();
    var sheet = ss.getSheetByName(sheetName);
    var range = sheet.getDataRange();

    var context = RenderService.getContext(null, headerRow, range.getRowIndex()+1);
    var renderOptions = { context: context };

    var subjectRendered = subject ? RenderService.render(subject, renderOptions) : null;
    var bodyRendered = body ? RenderService.render(body, renderOptions) : null;

    EmailService.send(Session.getActiveUser().getEmail(), subjectRendered, bodyRendered, null, null, true);
  },

  /**
   * Handles sending emails. Emails that don't have "to" defined return false.
   * Emails that contain closing </html> tags result in an html email being sent.
   *
   * @param {string} to The primary recipients of the email. Can be a comma delimited list of users.
   * @param {string} subject The subject of the email. This can be an empty string.
   * @param {string} body The body of the email. This can be an empty string.
   * @param {string|undefined} cc Secondary recipients of the email. Can be a comma delimited list of users.
   * @param {string|undefined} bcc Blind copied recipients. Can be a comma delimited list of users.
   * @param {boolean} isBodyHtml isBodyHtml explicitly sets the body text to be send as html
   * @return {boolean} true if the email was sent, false if it wasn't.
   */
  send: function(to, subject, body, cc, bcc, isBodyHtml) {
    if (to === '' || to == null) {
      log('Unable send email, invalid email address... ');
      return false;
    }
    var sendTime = Utilities.formatDate(new Date(), 'America/Edmonton', "MM/dd/yyyy HH:mm:ss");
    log('At '+sendTime+', Sending email to ' + JSON.stringify({
      to: to,
      cc: cc,
      bcc: bcc
    }));

    if (isBodyHtml || /<html>/.test(body)) {
      GmailApp.sendEmail(to, subject, body, {
        htmlBody: body,
        cc: cc,
        bcc: bcc
      });
    }
    else {
      GmailApp.sendEmail(to, subject, body, {
        cc: cc,
        bcc: bcc
      });
    }

    return true;
  },

  /******* Private / utility functions *******/

  /**
   * Retrieves the required fields for an email to be sent.
   *
   * @param {String[]} headerRow An array of header values.
   * @param {String[]} row An array of row values.
   * @param {Object} template The MergeTemplate used to send the email. See the client-side object for info.
   * @return {Boolean} true if the email was sent, false otherwise.
   */
  getEmailFields: function(headerRow, row, template) {
    var valueMap = {};
    for (var j = 0; j < headerRow.length; j++) {
      valueMap[headerRow[j]] = row[j];
    }

    if (template.mergeData.type.toLowerCase() === 'email') {
      return {
        to: EmailService.replaceTags(template.mergeData.data.to, valueMap),
        cc: EmailService.replaceTags(template.mergeData.data.cc, valueMap),
        bcc: EmailService.replaceTags(template.mergeData.data.bcc, valueMap),
        subject: EmailService.replaceTags(template.mergeData.data.subject, valueMap),
        body: EmailService.replaceTags(template.mergeData.data.body, valueMap),
        conditional: EmailService.replaceTags(template.mergeData.conditional, valueMap).toLowerCase()
      };
    }
    else if (template.mergeData.type.toLowerCase() === 'document') {
      var bodyHTML = DocumentService.getDocumentAsHTML(template.mergeData.data.documentID);
      return {
        to: EmailService.replaceTags(template.mergeData.data.to, valueMap),
        cc: EmailService.replaceTags(template.mergeData.data.cc, valueMap),
        bcc: EmailService.replaceTags(template.mergeData.data.bcc, valueMap),
        subject: EmailService.replaceTags(template.mergeData.data.subject, valueMap),
        body: EmailService.replaceTags(bodyHTML, valueMap),
        conditional: EmailService.replaceTags(template.mergeData.conditional, valueMap).toLowerCase()
      };
    }

    return {};
  },

  /**
   * This function replaces  all instances of <<tags>> with the data in headerToData.
   *
   * @param {string} text The string that contains the tags.
   * @param {Object} headerToData A key-value pair where the key is a column name and the value is the data in the
   * column.
   * @return {string} The text with all tags replaced with data.
   */
  replaceTags: function(text, headerToData) {
    if (text == null) {
      text = '';
    }

    // This must match <<these>> and <<these>> since we need to support HTML.
    var dataText = text.replace(/<<(.*?)>>|<<(.*?)>>/g, function(match, m1, m2, offset, string) {
      if (m1 && headerToData[m1]) {
        return headerToData[m1];
      }
      else if (m2 && headerToData[m2]) {
        return headerToData[m2];
      }
      return '';
    });

    return dataText;
  }
};
<|endoftext|>


<commit_before>//= require ./main

"use strict";

var
    esPhinx;

(function ($) {

    $.extend({
        prototype: {
            removeAttr: function (attrName) {
                var
                    self = this;

                self.each(function (node) {
                    node.removeAttribute(attrName);
                });

                return self;
            },

            attr: function (attrName, value) {
                var
                    self = this,
                    first = self.first();

                if (self.length === 0) {
                    return self;
                }

                if (typeof value === "boolean" || value || value === "") {
                    self.each(function (node) {
                        node.setAttribute(attrName, value);
                    });

                    return self;
                } else {
                    if (first.attributes[attrName]) {
                        return first.attributes[attrName].value;
                    }
                }

                return undefined;
            },

            property: function (propertyName, value) {
                var
                    self = this,
                    first = self.first();

                if (self.length === 0) {
                    return self;
                }

                if (typeof value === "boolean" || value || value === "") {
                    self.each(function (node) {
                        node[propertyName] = value;
                    });

                    return self;
                } else {
                    if (first[propertyName] ||
                        typeof first[propertyName] === "boolean") {
                        return first[propertyName];
                    }
                }

                return undefined;
            },

            value: function (value) {
                var
                    self = this;

                if (value || value === "") {
                    self.property("value", value);
                    return self;
                }

                return self.property("value");
            },

            checked: function () {
                return this.property("checked");
            },

            check: function () {
                var
                    self = this;

                if (!self.checked()) {
                    self.property("checked", true);
                }

                return self;
            },

            uncheck: function () {
                var
                    self = this;

                return self.property("checked", false);
            },

            data: function (attrName, value) {
                return this.attr("data-" + attrName, value);
            },

            params: function () {
                var
                    self = this,
                    resolvedParams = {},
                    index,
                    name,
                    param,
                    key,
                    merged,
                    attr,
                    nested_attr,

                    resolveParam = function (name, value) {
                        value = value || "";

                        var
                            structure = name,
                            parsed;

                        // get keys, maybe to generical with .match()...
                        // /^[^[]+|[^[]+(?=\])/g

                        // next test. Catch attr[0-9+][]
                        // .+\[[0-9]+\]\[\](?!.)

                        structure = "{\"" + structure;
                        // obj[nested_attr][][attr]
                        if (/.+\[.+\]\[\]\[.+\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\]\[\]\[/, "\":[{\"")
                                .replace(/\[(?![\{])/g, "\":{\"")
                                .replace(/\]$/g, "\":\"" + value + "\"}]}}");
                        // obj[nested_attr][0-9+][attr]
                        } else if (/.+\[.+\]\[[0-9]+\]\[.+\](?!.)/.test(name)) {
                            index = structure.split(/\[([0-9]+)/)[1];
                            structure = structure
                                .replace(/\[(?!([0-9]+|\]))/g, "\":{\"")
                                .replace(/\]\[[0-9]+\]/g, "\":{\"" + index)
                                .replace(/\]$/g, "\":\"" + value + "\"}}}}");
                        // obj[attr][]
                        } else if (/.+\[.+\]\[\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\]\[/, "\":[\"")
                                .replace(/\[(?!\")/g, "\":{\"")
                                .replace(/\]$/g, value + "\"]}}");
                        // obj[attr]
                        } else if (/.+\[.+\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\[/, "\":{\"")
                                .replace(/\]$/, "\":\"" + value + "\"}}");
                        // attr[]
                        } else if (/.+\[\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\[\]/, "\":[\"" + value + "\"]}");
                        // attr[0-9+][]
                        // } else if (/.+\[[0-9]+\]\[\](?!.)/.test(name)) {
                        //     // structure = structure
                        //     //     .replace(/\[\]/, "\":[\"" + value + "\"]}");
                        //     debugger
                        // attr
                        } else {
                            structure += "\":\"" + value + "\"}";
                        }

                        if (parsed = JSON.parse(structure)) {
                            return parsed;
                        }
                        return false;
                    };

                self.each(function (form) {
                    if (form instanceof HTMLFormElement) {
                        $.each(form.elements, function (e, i) {
                            if (name = e.name) {
                                if (param = resolveParam(e.name, e.value)) {
                                    key = Object.keys(param)[0];
                                    if (resolvedParams[key]) {
                                        attr = Object.keys(param[key])[0];
                                        if (resolvedParams[key][attr]
                                            instanceof Array) {

                                            index = Object
                                                .keys(resolvedParams[key][attr])
                                                    .last();

                                            nested_attr = Object
                                                .keys(param[key][attr][0])[0];

                                            if (resolvedParams[key][attr][index][nested_attr]) {
                                                param[key][attr] = resolvedParams[key][attr]
                                                    .concat(param[key][attr]);
                                            } else {
                                                resolvedParams[key][attr][index] = $.merge(resolvedParams[key][attr][index], param[key][attr][0]);
                                                param[key][attr] = resolvedParams[key][attr];
                                            }
                                        } else if (resolvedParams[key][attr]
                                            instanceof Object) {
                                            param[key][attr][index] = $.merge(resolvedParams[key][attr][index], param[key][attr][index]);
                                            param[key][attr] = $.merge(resolvedParams[key][attr], param[key][attr]);
                                        }
                                        param[key] = $.merge(resolvedParams[key], param[key]);
                                    }
                                    resolvedParams = $.merge(resolvedParams, param);
                                }
                            }
                        });
                    }
                });

                return resolvedParams;
            },

        }

    });

}(esPhinx));
<commit_msg>add support to indexed_collection_attr[0-9+][]<commit_after>//= require ./main

"use strict";

var
    esPhinx;

(function ($) {

    $.extend({
        prototype: {
            removeAttr: function (attrName) {
                var
                    self = this;

                self.each(function (node) {
                    node.removeAttribute(attrName);
                });

                return self;
            },

            attr: function (attrName, value) {
                var
                    self = this,
                    first = self.first();

                if (self.length === 0) {
                    return self;
                }

                if (typeof value === "boolean" || value || value === "") {
                    self.each(function (node) {
                        node.setAttribute(attrName, value);
                    });

                    return self;
                } else {
                    if (first.attributes[attrName]) {
                        return first.attributes[attrName].value;
                    }
                }

                return undefined;
            },

            property: function (propertyName, value) {
                var
                    self = this,
                    first = self.first();

                if (self.length === 0) {
                    return self;
                }

                if (typeof value === "boolean" || value || value === "") {
                    self.each(function (node) {
                        node[propertyName] = value;
                    });

                    return self;
                } else {
                    if (first[propertyName] ||
                        typeof first[propertyName] === "boolean") {
                        return first[propertyName];
                    }
                }

                return undefined;
            },

            value: function (value) {
                var
                    self = this;

                if (value || value === "") {
                    self.property("value", value);
                    return self;
                }

                return self.property("value");
            },

            checked: function () {
                return this.property("checked");
            },

            check: function () {
                var
                    self = this;

                if (!self.checked()) {
                    self.property("checked", true);
                }

                return self;
            },

            uncheck: function () {
                var
                    self = this;

                return self.property("checked", false);
            },

            data: function (attrName, value) {
                return this.attr("data-" + attrName, value);
            },

            params: function () {
                var
                    self = this,
                    resolvedParams = {},
                    index,
                    name,
                    param,
                    key,
                    merged,
                    attr,
                    nested_attr,

                    resolveParam = function (name, value) {
                        value = value || "";

                        var
                            structure = name,
                            parsed;

                        // get any keys, maybe to generical with .match()...
                        // /^[^[]+|[^[]+(?=\])/g

                        // it's missing
                        // obj[nested_attr][][attr][]
                        // obj[nested_attr][0-9+][attr]

                        structure = "{\"" + structure;
                        // obj[nested_attr][0-9+][attr]
                        if (/.+\[.+\]\[[0-9]+\]\[.+\](?!.)/.test(name)) {
                            index = structure.split(/\[([0-9]+)/)[1];
                            structure = structure
                                .replace(/\[(?!([0-9]+|\]))/g, "\":{\"")
                                .replace(/\]\[[0-9]+\]/g, "\":{\"" + index)
                                .replace(/\]$/g, "\":\"" + value + "\"}}}}");
                        // obj[nested_attr][][attr]
                        } else if (/.+\[.+\]\[\]\[.+\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\]\[\]\[/, "\":[{\"")
                                .replace(/\[(?![\{])/g, "\":{\"")
                                .replace(/\]$/g, "\":\"" + value + "\"}]}}");
                        // indexed_collection_attr[0-9+][]
                        } else if (/.+\[[0-9]+\]\[\](?!.)/.test(name)) {
                            index = structure.split(/\[([0-9]+)/)[1];
                            structure = structure
                                .replace(/\[(?=[0-9])/, "\":{\"")
                                .replace(/\]\[\]/, "\":[\"" + value + "\"]}}");
                        // obj[collection_attr][]
                        } else if (/.+\[.+\]\[\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\]\[/, "\":[\"")
                                .replace(/\[(?!\")/g, "\":{\"")
                                .replace(/\]$/g, value + "\"]}}");
                        // obj[attr]
                        } else if (/.+\[.+\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\[/, "\":{\"")
                                .replace(/\]$/, "\":\"" + value + "\"}}");
                        // collection_attr[]
                        } else if (/.+\[\](?!.)/.test(name)) {
                            structure = structure
                                .replace(/\[\]/, "\":[\"" + value + "\"]}");
                        // attr
                        } else {
                            structure += "\":\"" + value + "\"}";
                        }

                        if (parsed = JSON.parse(structure)) {
                            return parsed;
                        }
                        return false;
                    };

                // fix
                self.each(function (form) {
                    if (form instanceof HTMLFormElement) {
                        $.each(form.elements, function (e, i) {
                            if (name = e.name) {
                                if (param = resolveParam(e.name, e.value)) {
                                    key = Object.keys(param)[0];
                                    if (resolvedParams[key]) {
                                        attr = Object.keys(param[key])[0];
                                        if (resolvedParams[key][attr]
                                            instanceof Array) {

                                            index = Object
                                                .keys(resolvedParams[key][attr])
                                                    .last();

                                            nested_attr = Object
                                                .keys(param[key][attr][0])[0];

                                            if (resolvedParams[key][attr][index][nested_attr]) {
                                                param[key][attr] = resolvedParams[key][attr]
                                                    .concat(param[key][attr]);
                                            } else {
                                                resolvedParams[key][attr][index] = $.merge(resolvedParams[key][attr][index], param[key][attr][0]);
                                                param[key][attr] = resolvedParams[key][attr];
                                            }
                                        } else if (resolvedParams[key][attr]
                                            instanceof Object) {
                                            param[key][attr][index] = $.merge(resolvedParams[key][attr][index], param[key][attr][index]);
                                            param[key][attr] = $.merge(resolvedParams[key][attr], param[key][attr]);
                                        }
                                        param[key] = $.merge(resolvedParams[key], param[key]);
                                    }
                                    resolvedParams = $.merge(resolvedParams, param);
                                }
                            }
                        });
                    }
                });

                return resolvedParams;
            },

        }

    });

}(esPhinx));
<|endoftext|>