<commit_before>const moviedb = require('moviedb');
const convertTmdbToSeasoned = require('src/tmdb/convertTmdbToSeasoned');
var methodTypes = { 'movie': 'searchMovie', 'show': 'searchTv', 'multi': 'searchMulti', 'movieInfo': 'movieInfo',
	'tvInfo': 'tvInfo', 'upcomingMovies': 'miscUpcomingMovies', 'discoverMovie': 'discoverMovie',
	'discoverShow': 'discoverTv', 'popularMovies': 'miscPopularMovies', 'popularShows': 'miscPopularTvs',
	'nowPlayingMovies': 'miscNowPlayingMovies', 'nowAiringShows': 'tvOnTheAir', 'movieSimilar': 'movieSimilar',
	'showSimilar': 'tvSimilar' };

class TMDB {
	constructor(apiKey, tmdbLibrary) {
		this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
	}

	/**
	* Retrive list of of items from TMDB matching the query and/or type given.
	* @param {queryText, page, type} the page number to specify in the request for discover,
	* @returns {Promise} dict with query results, current page and total_pages
	*/
	search(queryText, page = 1, type = 'multi') {
		// Setup query object for tmdb api search
		const query = { 'query': queryText, 'page': page };
		return Promise.resolve()
		 .then(() => this.tmdb(type, query))  // Search the tmdb api
		 .catch(() => { throw new Error('Could not search for movies.'); })  // If any error at all when fetching
		 .then((reponse) => {
		 	try {
		 		// We want to filter because there are movies really low rated that are not interesting to us.
		 		let filteredTmdbItems = reponse.results.filter(function(tmdbResultItem) {
		 			return ((tmdbResultItem.vote_count >= 80 || tmdbResultItem.popularity > 18) && (tmdbResultItem.release_date !== undefined || tmdbResultItem.first_air_date !== undefined))
		 		})

		 		// Here we convert the filtered result from the tmdb api to seaonsed objects
		 		let seasonedItems = filteredTmdbItems.map((tmdbItem) => {
					if (type === 'movie')
						return convertTmdbToSeasoned(tmdbItem, 'movie');
					else if (type === 'show')
						return convertTmdbToSeaosned(tmdbItem, 'show');
					else
		 				return convertTmdbToSeasoned(tmdbItem);
		 		});

		 		// TODO add page number if results are larger than 20
		 		return { 'results': seasonedItems, 'number_of_items_on_page': seasonedItems,
						'page': 1, 'total_pages': 1 };

		 	} catch (parseError) {
		 		console.log(parseError)
		 		throw new Error('Could not parse result.');
		 	}
		 });
	}

	/**
	* Retrive list of discover section of movies from TMDB.
	* @param {Page, type} the page number to specify in the request for discover,
	* and type for movie or show
	* @returns {Promise} dict with discover results, current page and total_pages
	*/
	discover(page, type='movie') {
		// Sets the tmdb function type to the corresponding type from query
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'discoverMovie';
		} else if (type === 'show') {
			tmdbType = 'discoverShow';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		// Build a query for tmdb with pagenumber
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch discover.'); })
			.then((response) => {
				try {
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (error) {
					console.log(error)
					throw new Error('Error while parsing discover list.')
				}
			});
	}

	/**
	* Retrive list of popular section of movies or shows from TMDB.
	* @param {Page, type} the page number to specify in the request for popular,
	* and type for movie or show
	* @returns {Promise} dict with popular results, current page and total_pages
	*/
	// TODO add filter for language
	popular(page, type='movie') {
		// Sets the tmdb function type to the corresponding type from query
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'popularMovies';
		} else if (type === 'show') {
			tmdbType = 'popularShows';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		// Build a query for tmdb with pagenumber
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch popular.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (error) {
					console.log(error)
					throw new Error('Error while parsing discover list.')
				}
			});
	}

	/**
	* Retrive list of now playing/airing section of movies or shows from TMDB.
	* @param {Page, type} the page number to specify in the request for now playing/airing,
	* and type for movie or show
	* @returns {Promise} dict with nowplaying results, current page and total_pages
	*/
	// TODO add filter for language
	nowplaying(page, type='movie') {
		// Sets the tmdb function type to the corresponding type from query
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'nowPlayingMovies';
		} else if (type === 'show') {
			tmdbType = 'nowAiringShows';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		// Build a query for tmdb with pagenumber
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch popular.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (error) {
					console.log(error)
					throw new Error('Error while parsing discover list.')
				}
			});
	}

	/**
	* Retrive list of upcoming movies from TMDB.
	* @param {Page} the page number to specify in the request for upcoming movies
	* @returns {Promise} dict with upcoming results, current page and total_pages
	*/
	// TODO add filter for language
	upcoming(page) {
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb('upcomingMovies', query))
			.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, 'movie'); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (parseError) {
					throw new Error('Error while parsing upcoming movies list.')
				}
			});
	}

	/**
	* Retrive list of upcmoing movies from TMDB.
	* @param {Page} the page number to specify in the request for upcoming movies
	* @returns {Promise} dict with similar results, current page and total_pages
	*/
	// TODO add filter for language
	similar(identifier, type) {
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'movieSimilar';
		} else if (type === 'show') {
			tmdbType = 'showSimilar';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		const query = { id: identifier }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (parseError) {
					throw new Error('Error while parsing silimar media list.')
				}
			});
	}

	/**
	* Retrieve a specific movie by id from TMDB.
	* @param {Number} identifier of the movie you want to retrieve
	* @returns {Promise} succeeds if movie was found
	*/
	lookup(identifier, queryType = 'movie') {
		var type;
		if (queryType === 'movie') { type = 'movieInfo'}
		else if (queryType === 'show') { type = 'tvInfo'}
		else {
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}
		const query = { id: identifier };
		return Promise.resolve()
			.then(() => this.tmdb(type, query))
			.catch(() => { throw new Error('Could not find a movie with that id.'); })
			.then((response) => {
			  try {
			    var car = convertTmdbToSeasoned(response, queryType);
			    console.log(car);
			    return car;
			  } catch (parseError) {
			    throw new Error('Could not parse movie.');
			  }
			});
	}

	// TODO ADD CACHE LOOKUP
	tmdb(method, argument) {
		return new Promise((resolve, reject) => {
			const callback = (error, reponse) => {
				if (error) {
					return reject(error);
				}
				resolve(reponse);
			};

			if (!argument) {
				this.tmdbLibrary[methodTypes[method]](callback);
				// this.tmdbLibrary['miscUpcomingMovies']
			} else {
				this.tmdbLibrary[methodTypes[method]](argument, callback);
			}
		})
	}
}

module.exports = TMDB;
<commit_msg>Change a grammar error<commit_after>const moviedb = require('moviedb');
const convertTmdbToSeasoned = require('src/tmdb/convertTmdbToSeasoned');
var methodTypes = { 'movie': 'searchMovie', 'show': 'searchTv', 'multi': 'searchMulti', 'movieInfo': 'movieInfo',
	'tvInfo': 'tvInfo', 'upcomingMovies': 'miscUpcomingMovies', 'discoverMovie': 'discoverMovie',
	'discoverShow': 'discoverTv', 'popularMovies': 'miscPopularMovies', 'popularShows': 'miscPopularTvs',
	'nowPlayingMovies': 'miscNowPlayingMovies', 'nowAiringShows': 'tvOnTheAir', 'movieSimilar': 'movieSimilar',
	'showSimilar': 'tvSimilar' };

class TMDB {
	constructor(apiKey, tmdbLibrary) {
		this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
	}

	/**
	* Retrive list of of items from TMDB matching the query and/or type given.
	* @param {queryText, page, type} the page number to specify in the request for discover,
	* @returns {Promise} dict with query results, current page and total_pages
	*/
	search(queryText, page = 1, type = 'multi') {
		// Setup query object for tmdb api search
		const query = { 'query': queryText, 'page': page };
		return Promise.resolve()
		 .then(() => this.tmdb(type, query))  // Search the tmdb api
		 .catch(() => { throw new Error('Could not search for movies.'); })  // If any error at all when fetching
		 .then((reponse) => {
		 	try {
		 		// We want to filter because there are movies really low rated that are not interesting to us.
		 		let filteredTmdbItems = reponse.results.filter(function(tmdbResultItem) {
		 			return ((tmdbResultItem.vote_count >= 80 || tmdbResultItem.popularity > 18) && (tmdbResultItem.release_date !== undefined || tmdbResultItem.first_air_date !== undefined))
		 		})

		 		// Here we convert the filtered result from the tmdb api to seaonsed objects
		 		let seasonedItems = filteredTmdbItems.map((tmdbItem) => {
					if (type === 'movie')
						return convertTmdbToSeasoned(tmdbItem, 'movie');
					else if (type === 'show')
						return convertTmdbToSeasoned(tmdbItem, 'show');
					else
		 				return convertTmdbToSeasoned(tmdbItem);
		 		});

		 		// TODO add page number if results are larger than 20
		 		return { 'results': seasonedItems, 'number_of_items_on_page': seasonedItems,
						'page': 1, 'total_pages': 1 };

		 	} catch (parseError) {
		 		console.log(parseError)
		 		throw new Error('Could not parse result.');
		 	}
		 });
	}

	/**
	* Retrive list of discover section of movies from TMDB.
	* @param {Page, type} the page number to specify in the request for discover,
	* and type for movie or show
	* @returns {Promise} dict with discover results, current page and total_pages
	*/
	discover(page, type='movie') {
		// Sets the tmdb function type to the corresponding type from query
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'discoverMovie';
		} else if (type === 'show') {
			tmdbType = 'discoverShow';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		// Build a query for tmdb with pagenumber
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch discover.'); })
			.then((response) => {
				try {
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (error) {
					console.log(error)
					throw new Error('Error while parsing discover list.')
				}
			});
	}

	/**
	* Retrive list of popular section of movies or shows from TMDB.
	* @param {Page, type} the page number to specify in the request for popular,
	* and type for movie or show
	* @returns {Promise} dict with popular results, current page and total_pages
	*/
	// TODO add filter for language
	popular(page, type='movie') {
		// Sets the tmdb function type to the corresponding type from query
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'popularMovies';
		} else if (type === 'show') {
			tmdbType = 'popularShows';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		// Build a query for tmdb with pagenumber
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch popular.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (error) {
					console.log(error)
					throw new Error('Error while parsing discover list.')
				}
			});
	}

	/**
	* Retrive list of now playing/airing section of movies or shows from TMDB.
	* @param {Page, type} the page number to specify in the request for now playing/airing,
	* and type for movie or show
	* @returns {Promise} dict with nowplaying results, current page and total_pages
	*/
	// TODO add filter for language
	nowplaying(page, type='movie') {
		// Sets the tmdb function type to the corresponding type from query
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'nowPlayingMovies';
		} else if (type === 'show') {
			tmdbType = 'nowAiringShows';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		// Build a query for tmdb with pagenumber
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch popular.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (error) {
					console.log(error)
					throw new Error('Error while parsing discover list.')
				}
			});
	}

	/**
	* Retrive list of upcoming movies from TMDB.
	* @param {Page} the page number to specify in the request for upcoming movies
	* @returns {Promise} dict with upcoming results, current page and total_pages
	*/
	// TODO add filter for language
	upcoming(page) {
		const query = { 'page': page }
		return Promise.resolve()
			.then(() => this.tmdb('upcomingMovies', query))
			.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, 'movie'); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (parseError) {
					throw new Error('Error while parsing upcoming movies list.')
				}
			});
	}

	/**
	* Retrive list of upcmoing movies from TMDB.
	* @param {Page} the page number to specify in the request for upcoming movies
	* @returns {Promise} dict with similar results, current page and total_pages
	*/
	// TODO add filter for language
	similar(identifier, type) {
		var tmdbType;
		if (type === 'movie') {
			tmdbType = 'movieSimilar';
		} else if (type === 'show') {
			tmdbType = 'showSimilar';
		} else {
			// Throw error if invalid type from query
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}

		const query = { id: identifier }
		return Promise.resolve()
			.then(() => this.tmdb(tmdbType, query))
			.catch(() => { throw new Error('Could not fetch upcoming movies.'); })
			.then((response) => {
				try {
					var seasonedResponse = response.results.map((result) => {
						return convertTmdbToSeasoned(result, type); }
					);
					// Return a object that has the results and a variable for page, total_pages
					// and seasonedResponse
					return { 'results': seasonedResponse,
						'page': response.page, 'total_pages': response.total_pages };
				} catch (parseError) {
					throw new Error('Error while parsing silimar media list.')
				}
			});
	}

	/**
	* Retrieve a specific movie by id from TMDB.
	* @param {Number} identifier of the movie you want to retrieve
	* @returns {Promise} succeeds if movie was found
	*/
	lookup(identifier, queryType = 'movie') {
		var type;
		if (queryType === 'movie') { type = 'movieInfo'}
		else if (queryType === 'show') { type = 'tvInfo'}
		else {
			return Promise.resolve()
			.then(() => {
				throw new Error('Invalid type declaration.')
			})
		}
		const query = { id: identifier };
		return Promise.resolve()
			.then(() => this.tmdb(type, query))
			.catch(() => { throw new Error('Could not find a movie with that id.'); })
			.then((response) => {
			  try {
			    var car = convertTmdbToSeasoned(response, queryType);
			    console.log(car);
			    return car;
			  } catch (parseError) {
			    throw new Error('Could not parse movie.');
			  }
			});
	}

	// TODO ADD CACHE LOOKUP
	tmdb(method, argument) {
		return new Promise((resolve, reject) => {
			const callback = (error, reponse) => {
				if (error) {
					return reject(error);
				}
				resolve(reponse);
			};

			if (!argument) {
				this.tmdbLibrary[methodTypes[method]](callback);
				// this.tmdbLibrary['miscUpcomingMovies']
			} else {
				this.tmdbLibrary[methodTypes[method]](argument, callback);
			}
		})
	}
}

module.exports = TMDB;
<|endoftext|>


<commit_before>/*
   Map browser based on OpenLayers 5.
   Copyright (C) 2017-2018 Øyvind Hanssen, LA7ECA, ohanssen@acm.org
   AGPL licenced.
   ----

   This source file is based on OpenLayers examples:
   http://openlayers.org/en/latest/examples/measure.html
   Copyright 2005-present OpenLayers Contributors. All rights reserved.

   Redistribution and use in source and binary forms, with or without modification,
   are permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this
      list of conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation and/or
      other materials provided with the distribution.

   THIS SOFTWARE IS PROVIDED BY OPENLAYERS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
   SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
   OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

   The views and conclusions contained in the software and documentation are those
   of the authors and should not be interpreted as representing official policies,
   either expressed or implied, of OpenLayers Contributors.
 */

pol.core.Measure = class {

    constructor() {
        const t = this;
        let tooltipElement;
        let tooltip;
        let listener;
        let sketch;
        t.tooltips = [];

        t.vector = CONFIG.mb.addVectorLayer(
           new ol.style.Style({
              fill: new ol.style.Fill({
                color: 'rgba(255, 255, 255, 0.2)'
              }),
              stroke: new ol.style.Stroke({
                color: '#d11',
                width: 2
              })
            }));

        t.draw = new ol.interaction.Draw({
           source: t.vector.getSource(),
           type: "LineString",
           style: new ol.style.Style({
                fill: new ol.style.Fill({
                  color: 'rgba(255, 255, 255, 0.2)'
                }),
                stroke: new ol.style.Stroke({
                  color: 'rgba(0, 0, 250, 0.6)',
                  lineDash: [7, 8],
                  width: 2
                }),
            image: new ol.style.Circle({
              radius: 5,
              stroke: new ol.style.Stroke({
                color: 'rgba(0, 0, 0, 0.7)'
              }),
              fill: new ol.style.Fill({
                color: 'rgba(255, 255, 255, 0.2)'
              })
            })
          })
        });

        CONFIG.mb.map.addInteraction(t.draw);

        t.draw.on("drawstart",  evt => {
            sketch = evt.feature;

            listener = sketch.getGeometry().on('change', evt => {
               const tooltipCoord = evt.target.getLastCoordinate();
               tooltipElement.innerHTML = formatLength(evt.target);
               tooltip.setPosition(tooltipCoord);
            });
        }, t);

        t.draw.on("drawend", () => {
           tooltipElement.className = 'tooltip tooltip-static';
           tooltip.setOffset([0, -7]);
           // unset sketch
           sketch = null;
           // unset tooltip so that a new one can be created
           tooltipElement = null;
           t.tooltips.push(createTooltip());
           ol.Observable.unByKey(listener);
        }, t);

        t.tooltips.push(createTooltip());

        function createTooltip() {
           if (tooltipElement) {
              tooltipElement.parentNode.removeChild(measureTooltipElement);
           }
           tooltipElement = document.createElement('div');
           tooltipElement.className = 'tooltip tooltip-measure';
           tooltip = new ol.Overlay({
              element: tooltipElement,
              offset: [0, -15],
              positioning: 'bottom-center'
           });
           CONFIG.mb.map.addOverlay(tooltip);
           return tooltip;
        }

        function formatLength(line) {
            const coordinates = line.getCoordinates();
            const sourceProj = CONFIG.mb.view.getProjection();
            let length = 0;
            for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) {
                const c1 = ol.proj.transform(coordinates[i], sourceProj, 'EPSG:4326');
                const c2 = ol.proj.transform(coordinates[i + 1], sourceProj, 'EPSG:4326');
                length += ol.sphere.getDistance(c1, c2);
            }

            let output = "";
            if (length > 100) {
                output = (Math.round(length / 1000 * 100) / 100) +
                    ' ' + 'km';
            } else {
                output = (Math.round(length * 100) / 100) +
                    ' ' + 'm';
            }
            return output;
        };
    } /* constructor */

    deactivate(a) {
       this.draw.setActive(false);
       CONFIG.mb.map.removeInteraction(this.draw);
       CONFIG.mb.removeLayer(this.vector);
       for (const x of this.tooltips)
          CONFIG.mb.map.removeOverlay(x);
    }

} /* class */

<commit_msg>Add bearing (of last segment) to measurement<commit_after>/*
   Map browser based on OpenLayers 5.
   Copyright (C) 2017-2018 Øyvind Hanssen, LA7ECA, ohanssen@acm.org
   AGPL licenced.
   ----

   This source file is based on OpenLayers examples:
   http://openlayers.org/en/latest/examples/measure.html
   Copyright 2005-present OpenLayers Contributors. All rights reserved.

   Redistribution and use in source and binary forms, with or without modification,
   are permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this
      list of conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation and/or
      other materials provided with the distribution.

   THIS SOFTWARE IS PROVIDED BY OPENLAYERS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
   SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
   OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

   The views and conclusions contained in the software and documentation are those
   of the authors and should not be interpreted as representing official policies,
   either expressed or implied, of OpenLayers Contributors.
 */

pol.core.Measure = class {

    constructor() {
        const t = this;
        let tooltipElement;
        let tooltip;
        let listener;
        let sketch;
        t.tooltips = [];

        t.vector = CONFIG.mb.addVectorLayer(
           new ol.style.Style({
              fill: new ol.style.Fill({
                color: 'rgba(255, 255, 255, 0.2)'
              }),
              stroke: new ol.style.Stroke({
                color: '#d11',
                width: 2
              })
            }));

        t.draw = new ol.interaction.Draw({
           source: t.vector.getSource(),
           type: "LineString",
           style: new ol.style.Style({
                fill: new ol.style.Fill({
                  color: 'rgba(255, 255, 255, 0.2)'
                }),
                stroke: new ol.style.Stroke({
                  color: 'rgba(0, 0, 250, 0.6)',
                  lineDash: [7, 8],
                  width: 2
                }),
            image: new ol.style.Circle({
              radius: 5,
              stroke: new ol.style.Stroke({
                color: 'rgba(0, 0, 0, 0.7)'
              }),
              fill: new ol.style.Fill({
                color: 'rgba(255, 255, 255, 0.2)'
              })
            })
          })
        });

        CONFIG.mb.map.addInteraction(t.draw);

        t.draw.on("drawstart",  evt => {
            sketch = evt.feature;

            listener = sketch.getGeometry().on('change', evt => {
               const tooltipCoord = evt.target.getLastCoordinate();
               tooltipElement.innerHTML = formatLength(evt.target);
               tooltip.setPosition(tooltipCoord);
            });
        }, t);

        t.draw.on("drawend", () => {
           tooltipElement.className = 'tooltip tooltip-static';
           tooltip.setOffset([0, -7]);
           // unset sketch
           sketch = null;
           // unset tooltip so that a new one can be created
           tooltipElement = null;
           t.tooltips.push(createTooltip());
           ol.Observable.unByKey(listener);
        }, t);

        t.tooltips.push(createTooltip());

        function createTooltip() {
           if (tooltipElement) {
              tooltipElement.parentNode.removeChild(measureTooltipElement);
           }
           tooltipElement = document.createElement('div');
           tooltipElement.className = 'tooltip tooltip-measure';
           tooltip = new ol.Overlay({
              element: tooltipElement,
              offset: [0, -15],
              positioning: 'bottom-center'
           });
           CONFIG.mb.map.addOverlay(tooltip);
           return tooltip;
        }

        function bearing(c1, c2) {
            var cr1 = toRadians(c1[1]), cr2 = toRadians(c2[1]);
            var d = toRadians(c2[0]-c1[0]);
            var y = Math.sin(d) * Math.cos(cr2);
            var x = Math.cos(cr1)*Math.sin(cr2) -
                Math.sin(cr1) * Math.cos(cr2) * Math.cos(d);
            var brng = Math.atan2(y, x);
            return (toDegrees(brng) + 360) % 360;
        }

        // Converts from degrees to radians.
        function toRadians(degrees) {
            return degrees * Math.PI / 180;
        }

        // Converts from radians to degrees.
        function toDegrees(radians) {
            return radians * 180 / Math.PI;
        }

        function formatLength(line) {
            const coordinates = line.getCoordinates();
            const sourceProj = CONFIG.mb.view.getProjection();
            let length = 0;
            let c1=0, c2=0;
            for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) {
                c1 = ol.proj.transform(coordinates[i], sourceProj, 'EPSG:4326');
                c2 = ol.proj.transform(coordinates[i + 1], sourceProj, 'EPSG:4326');
                length += ol.sphere.getDistance(c1, c2);
            }

            let brng = bearing(c1, c2);

            let output = "";
            if (length > 100) {
                output = (Math.round(length / 1000 * 100) / 100) +
                    ' ' + 'km';
            } else {
                output = (Math.round(length * 100) / 100) +
                    ' ' + 'm';
            }
            output += " &nbsp"+Math.round(brng*10)/10+"\u00B0";
            return output;
        };
    } /* constructor */

    deactivate(a) {
       this.draw.setActive(false);
       CONFIG.mb.map.removeInteraction(this.draw);
       CONFIG.mb.removeLayer(this.vector);
       for (const x of this.tooltips)
          CONFIG.mb.map.removeOverlay(x);
    }

} /* class */

<|endoftext|>


<commit_before>const winston = require('winston')
const Q = require('Q')
const dbClient = require('mariasql');
const message = require('../messages/message')
const utils = require('../../utilities')
const user_settings = require('../../user_settings')

var MessageManager = {
    message_states: Object.freeze(
        {
            MSG_PENDING: 1,
            MSG_SENDING: 2,
            MSG_SENT: 3,
            MSG_DELIVERED_SERVICE: 4,
            MSG_DELIVERED_USER: 5,
            MSG_READ: 6,
            MSG_REPLIED: 7,
            MSG_FAILED: 8
        }
    ),

    message_directions: Object.freeze({
        DIRECTION_INBOUND: 0,
        DIRECTION_OUTBOUND: 1
    }),

    createEmptyMessage: function () {
        let m = Object.create(message.Message)
        return m
    },

    createNewMessage: function (msg_body, msg_contact_id, msg_direction, msg_servicechain, msg_is_reply_to = null) {
        return Q.fcall(function () {
            return MessageManager.createEmptyMessage()
        })
            .then(function (msg) {
                msg.state = MessageManager.message_states.MSG_PENDING
                msg.body = msg_body
                return msg
            })
            .then(function (msg) {
                return utils.verifyNumberIsInteger(msg_contact_id)
                    .then(function (parsed_cont_id) {
                        return utils.verifyContactExists(parsed_cont_id)
                    })
                    .then(function (cont_id) {
                        msg.contact = cont_id
                        return msg
                    })
            })
            .then(function (msg) {
                // TODO: Validate all of these
                msg.servicechain = msg_servicechain
                msg.direction = msg_direction
                msg.is_reply_to = msg_is_reply_to
                return msg
            })
            .then(function (msg) {
                // TODO: Register message in the database
                return MessageManager.insertMessageIntoDatabase(msg)
            })
    },

    sendMessage: function (message) {
        // TODO: Make this work
        /*
        The `message` object has a property, `servicechain`, which holds the ID of the servicechain we should use
        to send this message. We need to retrieve the servicechain from the database and iterate through all of
        plugins until something returns True.

        Promises can help us here - by chaining a series of promises together, we can automatically iterate through
        plugin chains - is the idea!
        */

    },

    insertMessageIntoDatabase: function (msg) {
        let deferred = Q.defer()

        let client = new dbClient(user_settings.db_credentials)
        // TODO: Make this into a stored procedure one day
        let prep = client.prepare("INSERT into messages (body, servicechain, contact, is_reply_to, direction, message_state) VALUES (?,?,?,?,?,?)")
        let query = client.query(prep([msg.body, msg.servicechain, msg.contact, msg.is_reply_to, msg.direction, msg.state]), function (err, rows) {
            if (err) {
                deferred.reject(err)
                winston.error(err.message)
            } else {
                deferred.resolve(msg)
            }
        })

        client.end()
        return deferred.promise
    },

    getHundredMessageIds: function (offset = 0) {
        let deferred = Q.defer()

        let client = new dbClient(user_settings.db_credentials);

        let prep = client.prepare("CALL GetOneHundredMessages(?)")
        let query = client.query(prep([offset]), function (err, rows) {
            if (err) {
                deferred.reject(err)
                winston.error(err.message)
            } else {
                deferred.resolve(rows)
            }
        })

        client.end()

        return deferred.promise
    }
}

module.exports = MessageManager<commit_msg>Ticked off!<commit_after>const winston = require('winston')
const Q = require('Q')
const dbClient = require('mariasql');
const message = require('../messages/message')
const utils = require('../../utilities')
const user_settings = require('../../user_settings')

var MessageManager = {
    message_states: Object.freeze(
        {
            MSG_PENDING: 1,
            MSG_SENDING: 2,
            MSG_SENT: 3,
            MSG_DELIVERED_SERVICE: 4,
            MSG_DELIVERED_USER: 5,
            MSG_READ: 6,
            MSG_REPLIED: 7,
            MSG_FAILED: 8
        }
    ),

    message_directions: Object.freeze({
        DIRECTION_INBOUND: 0,
        DIRECTION_OUTBOUND: 1
    }),

    createEmptyMessage: function () {
        let m = Object.create(message.Message)
        return m
    },

    createNewMessage: function (msg_body, msg_contact_id, msg_direction, msg_servicechain, msg_is_reply_to = null) {
        return Q.fcall(function () {
            return MessageManager.createEmptyMessage()
        })
            .then(function (msg) {
                msg.state = MessageManager.message_states.MSG_PENDING
                msg.body = msg_body
                return msg
            })
            .then(function (msg) {
                return utils.verifyNumberIsInteger(msg_contact_id)
                    .then(function (parsed_cont_id) {
                        return utils.verifyContactExists(parsed_cont_id)
                    })
                    .then(function (cont_id) {
                        msg.contact = cont_id
                        return msg
                    })
            })
            .then(function (msg) {
                // TODO: Validate all of these
                msg.servicechain = msg_servicechain
                msg.direction = msg_direction
                msg.is_reply_to = msg_is_reply_to
                return msg
            })
            .then(function (msg) {
                return MessageManager.insertMessageIntoDatabase(msg)
            })
    },

    sendMessage: function (message) {
        // TODO: Make this work
        /*
        The `message` object has a property, `servicechain`, which holds the ID of the servicechain we should use
        to send this message. We need to retrieve the servicechain from the database and iterate through all of
        plugins until something returns True.

        Promises can help us here - by chaining a series of promises together, we can automatically iterate through
        plugin chains - is the idea!
        */

    },

    insertMessageIntoDatabase: function (msg) {
        let deferred = Q.defer()

        let client = new dbClient(user_settings.db_credentials)
        // TODO: Make this into a stored procedure one day
        let prep = client.prepare("INSERT into messages (body, servicechain, contact, is_reply_to, direction, message_state) VALUES (?,?,?,?,?,?)")
        let query = client.query(prep([msg.body, msg.servicechain, msg.contact, msg.is_reply_to, msg.direction, msg.state]), function (err, rows) {
            if (err) {
                deferred.reject(err)
                winston.error(err.message)
            } else {
                deferred.resolve(msg)
            }
        })

        client.end()
        return deferred.promise
    },

    getHundredMessageIds: function (offset = 0) {
        let deferred = Q.defer()

        let client = new dbClient(user_settings.db_credentials);

        let prep = client.prepare("CALL GetOneHundredMessages(?)")
        let query = client.query(prep([offset]), function (err, rows) {
            if (err) {
                deferred.reject(err)
                winston.error(err.message)
            } else {
                deferred.resolve(rows)
            }
        })

        client.end()

        return deferred.promise
    }
}

module.exports = MessageManager<|endoftext|>


<commit_before>

//expose the main layout panels for use later.
 var northPanel, southPanel, eastPanel, westPanel, centerPanel;

onSubmit = function() {

}

//The simple function will be called when the document is finished loading.  See event at bottem of file
Simple = function() {

   return {
       init : function() {
           var mainLayout = new Ext.BorderLayout("layout", {
               north: {
                   split: false, initialSize: 30
               },
               south: {
                   split: false, initialSize: 400
               },
               east: {
                   split: true, initialSize: 0
               },
               west: {
                   split: true, initialSize: 300
               },
               center: { split: true, initialSize: 80
               }
           });
           mainLayout.beginUpdate();
           mainLayout.add('north', northPanel = new Ext.ContentPanel('north-div', {
              fitToFrame: true, closable: false, autoScroll: true
           }));
           mainLayout.add('south', southPanel = new Ext.ContentPanel('south-div', {
               fitToFrame: true, closable: false
           }));
           mainLayout.add('east', eastPanel = new Ext.ContentPanel('east-div', {
               fitToFrame: true, closable: false, autoScroll: true
           }));
           mainLayout.add('west', westPanel = new Ext.ContentPanel('west-div', {
               fitToFrame: true, closable: false, autoScroll: true
           }));
           mainLayout.add('center', centerPanel = new Ext.ContentPanel('center-div', {
               fitToFrame: true , autoScroll: true
           }));
           mainLayout.endUpdate();
                       mainLayout.getRegion('east').hide();
                       mainLayout.getRegion('north').hide();
                       initBioMaterialGrid( );
                       initTree( );
               }
   };
}();

var showbms = function( id ) {
       var ids =  id.split(",");
       // note how we pass the new array in directly, without wraping it in an object first.
       ds.load({params:[ids]});
};

var ds;

var initBioMaterialGrid = function(div) {
       var     recordType = Ext.data.Record.create([
                       {name:"id", type:"int"},
                       {name:"name", type:"string"},
                       {name:"description", type:"string"}
               ]);

       ds = new Ext.data.Store(
               {
                       proxy:new Ext.data.DWRProxy(BioMaterialController.getBioMaterials),
                       reader:new Ext.data.ListRangeReader({id:"id"}, recordType),
                       remoteSort:false,
                       sortInfo:{field:'name'}
               });

       var cm = new Ext.grid.ColumnModel([
                       {header: "Name", width: 50, dataIndex:"name"},
                       {header: "Description",  width: 80, dataIndex:"description"}
                       ]);
       cm.defaultSortable = true;

       grid = new Ext.grid.Grid("south-div", {autoSizeColumns: true, ds:ds,cm:cm, loadMask: true });
       grid.render();

       var id = dwr.util.getValue("cslist");
       showbms(id);
};

var initTree = function(div){

   var tree = new Ext.tree.TreePanel("west-div", {
       animate:true,
       loader: new Ext.tree.DwrTreeLoader({dataUrl:MgedOntologyService.getBioMaterialTerms})
   });

   tree.on('click', function(node){
                   MgedOntologyService.getTerm(node.id,displayRestrictionsPanel);
          });

   // set the root node
   var root = new Ext.tree.AsyncTreeNode({
       text: 'Top of the tree',
       draggable:false,
       allowChildre:true,
       id:'root'
   });
   tree.setRootNode(root);

   // render the tree
   tree.render();
   root.expand();

};

var vocabC;

//The call back method for the dwr call
var displayRestrictionsPanel = function(node){
	console.log(dwr.util.toDescriptiveString(node, 10));

	vocabC = { termUri : node.uri,properties : [] };
	createRestrictionGui(node, vocabC);

	var saveButton = new Ext.Button("center-div", {text : 'save'});
	saveButton.on("click", saveHandler);

};

var saveHandler = function(event){
	console.log(dwr.util.toDescriptiveString(vocabC,10))
	//MgedOntologyService.saveTerm(vocabC);

}

//Recursive function that walks the node given to it and creates a coresponding form to fill in
var createRestrictionGui = function(node, vc, indent, parentDivId) {

		var dh = Ext.DomHelper;  //allows html output

		if (indent === undefined){
			dh.overwrite("center-div", {html : ""});
			indent = 0;
		}
		if (parentDivId === undefined) {
			parentDivId = "center-div";

		}

		dh.append(parentDivId, {html : node.term+ ": " + node.comment});

        var res = node.restrictions;
        if ( (res !== undefined) && (res !== null) && (res.size() > 0) ) {

            for ( var i = 0, len = res.length; i < len; i++ ) {
            	var restrictedOn = res[i].restrictionOn;
            	var restrictedTo = res[i].restrictedTo;
            	//make a nested div for adding to.
            	var divId = (Math.random() * 100000).toFixed();
            	dh.append(parentDivId, {tag: 'div', id: divId, style : "border-width:thin;border-style:dotted;padding:5px;margin:5px;"});
                dh.append(divId, {html : restrictedOn.label});

            	if (restrictedOn.type !== undefined ) {	//Primitive Type

                    	var simple = new Ext.form.Form({
					        labelWidth: 75, // label settings here cascade unless overridden
					        url:'save-form.php'
					    });

					    var handler = createPrimitiveTypeHandler(restrictedOn, vc);

					    var valueField = new Ext.form.TextField({
					            fieldLabel: restrictedOn.label ,
					            name: 'hasValue',
					            width:175,
					            allowBlank:false,
					        });

					   valueField.vocabId = divId;

					   valueField.on('valid', handler);

					   simple.add(valueField);
                       dh.append(divId, {tag: 'h3', html : 'Value:' });

					   simple.render(divId);

            	} else if ( (restrictedTo !== undefined) && (restrictedTo !== null)) {	//Class restriction

                    if (restrictedTo.restrictions === undefined || restrictedTo.restrictions === null || restrictedTo.restrictions.size() === 0){	// ie) we are at a leaf node so display gui
                        dh.append(divId, {tag: 'h3', html : "Create an instance of: " +restrictedTo.term });

	                    var simple = createForm(restrictedOn,vc, divId);

    	                if (restrictedTo.individuals !== undefined && restrictedTo.individuals !== null && restrictedTo.individuals.size() > 0){  //are there examples?
    	                		var combo = createComboBox(restrictedOn, restrictedTo.individuals, vc, divId);
        		            	simple.column({width:285},combo);
                	    }

	                    simple.render(divId);
                    } else{        //Not a leaf node. recurse down another level
                    	//do i need to use propertiesPush here? ie do i want a field id associated with this?
                        var vcChild = { termUri : restrictedOn.uri, object : { termUri : restrictedTo.uri }, properties : []};
                        propertiesPush(vc, divId,vcChild);
                        //vc.properties.push(vcChild);
    	            	createRestrictionGui( restrictedTo, vcChild.object, indent + 3, divId );
                   }

                } else if ( res[i].cardinality !== undefined  ) { //Cardinality Type
                    // this will be rare.
                    var cardinality = res[i].cardinality;
                    var cardinalityType = res[i].cardinalityType;
                    dh.append(parentDivId,{ html: " Cardinality Slot to fill in: " + restrictedOn.label + " with " + cardinalityType.term + " "
                            + cardinality + " things" });
                    // todo check range of the property (what 'things' should be) if specified.
                } else{
                	  dh.append(parentDivId,{ html: " Error: Not a typical type.  " + restrictedOn.label});
                }
            }
        }
        else{//No restrictions must be a leaf node.

			//Create new div
        	var divId = (Math.random() * 100000).toFixed();
           	dh.append(parentDivId, {tag: 'div', id: divId, style : "border-width:thin;border-style:dotted;padding:5px;margin:5px;"});
           	dh.append(divId, {tag: 'h3', html :  "Create an instance of: " + node.term });
           	var simple = createForm(restrictedOn,vc, divId);

           	//If there already exisit individuals display them in a drop down box so the user can select one.
            if ((node.individuals !== undefined) && (node.individuals !== null) && (node.individuals.size() > 0))
            	simple.column({width:285},createComboBox(restrictedOn, node.individuals, divId));

        	simple.render(divId);
        }
	};

//subject
var createComboBox = function(subject, individuals, vc, divId){
	     	var recordType = Ext.data.Record.create([
							{name:"label", type : "string" },
							{name:"uri", type: "string" }
						]);

						//convert the arry of objects into an array of records
						var records = [];
						for(var i = 0, len = individuals.length; i < len; i++) {
							records.push(new recordType(individuals[i]));
						}

	                    var store = new Ext.data.Store({recordType : recordType} );
						store.add(records);

					    var combo = new Ext.form.ComboBox({
					    	fieldLabel: 'pick one',
					        store: store,
					        displayField:'label',
					        typeAhead: true,
					        mode: 'local',
					        triggerAction: 'all',
					        emptyText:'Select an individual',
					        selectOnFocus:true
					    });

					    combo.vocabId = divId;

					    var comboHandler = function(field,record,index){
					    	var vcChild = { termUri : subject.uri, object : {termUri : record.data.uri}};

					    	if (vc.properties === undefined){
    	                	 	vc.properties = [];
    	                	}
    	                	propertiesPush(vc, field.vocabId, vcChild);

    	                };

    	                combo.on('select', comboHandler);

					    return combo;

};

//the vc is the VocabCharacteristic we would like to add to.
//The fieldId = the div ID of the component that contains the gui component
//Will check to make sure that the fieldId doesn't already exisit in the properties and if it does update it
//if not create a new one.  This is to resolve the problem of duplicates and enforces that the VC that is created is
//well formed.

var propertiesPush = function(vc, fieldId, toAdd){

	//Make sure that the vc we've been given has a properties field
	if ((vc.properties === undefined) || (vc.properties === null) || (vc.properties.length === 0)){
		vc.properties = [];
		toAdd.fieldId = fieldId;
		vc.properties.push(toAdd);
		return;
	}

	//Check if the field id already has a property set
	for(var i=0; i < vc.properties.length; i++){

		if (vc.properties[i].fieldId === undefined){
			console.log("The fieldId should be defined: " + vc.properties[i]);
			continue;
		}

		if (vc.properties[i].fieldId === fieldId) {
			toAdd.fieldId = fieldId;
			vc.properties[i] = toAdd;
			return;
		}
	}

	//If we made it this far then the property hasn't been set already.
	toAdd.fieldId = fieldId;
	vc.properties.push(toAdd);

}

//creates the Lookup textbox and the custom textbox.  The form is just used for alignment purposes.
//The subject = the OntologyTerm that we are trying to create the vocabulary characteristic for
//the vc = the vocabulary charactersit we are trying to create.
var createForm = function(subject, vc, divId){

	var lookUp = 	new Ext.form.TextField({
            fieldLabel: 'Lookup',
            name: 'lookup',
            width:150,
            allowBlank:true	});
    lookUp.vocabId = divId;

	var custom =   new Ext.form.TextField({
            fieldLabel: 'custom',
            name: 'custom',
            width:150 });

    custom.vocabId = divId;

	var customHandler = function(field){
							var newVC = {termUri : subject.uri, object : {value: field.getValue()}};
    	                	if (vc.properties === undefined){
    	                	 	vc.properties = [];
    	                	}
    	                	propertiesPush(vc, field.vocabId, newVC);
    	                };

    	       custom.on('valid', customHandler);

	var simple = new Ext.form.Form({
		        labelWidth: 50, // label settings here cascade unless overridden
    });

    simple.column(
    	{width:250},
        lookUp
        );

	simple.column(
		{width:250},
		custom
    );

   return simple;
}

//Do this for scope reasons.
//By creating a function on the fly the scope of the restrictions will be fixed for the handler.
var createPrimitiveTypeHandler = function(restrictedOn, vc){

	return 	function(field) {

					     	var newVc = {termUri : restrictedOn.uri, data : field.getValue(), type : restrictedOn.type};

					     	if (vc.properties === undefined){
    	                	 	vc.properties = [];
    	                	}

					     	propertiesPush(vc, field.vocabId,newVc);
			}
};

Ext.EventManager.onDocumentReady(Simple.init, Simple, true);

<commit_msg>fixed a bug in the base leaf case. Added biomaterial Selection. Tries to persist to sever on save.<commit_after>

//expose the main layout panels for use later.
 var northPanel, southPanel, eastPanel, westPanel, centerPanel;

onSubmit = function() {

}

//The simple function will be called when the document is finished loading.  See event at bottem of file
Simple = function() {

   return {
       init : function() {
           var mainLayout = new Ext.BorderLayout("layout", {
               north: {
                   split: false, initialSize: 30
               },
               south: {
                   split: false, initialSize: 400
               },
               east: {
                   split: true, initialSize: 0
               },
               west: {
                   split: true, initialSize: 300
               },
               center: { split: true, initialSize: 80
               }
           });
           mainLayout.beginUpdate();
           mainLayout.add('north', northPanel = new Ext.ContentPanel('north-div', {
              fitToFrame: true, closable: false, autoScroll: true
           }));
           mainLayout.add('south', southPanel = new Ext.ContentPanel('south-div', {
               fitToFrame: true, closable: false
           }));
           mainLayout.add('east', eastPanel = new Ext.ContentPanel('east-div', {
               fitToFrame: true, closable: false, autoScroll: true
           }));
           mainLayout.add('west', westPanel = new Ext.ContentPanel('west-div', {
               fitToFrame: true, closable: false, autoScroll: true
           }));
           mainLayout.add('center', centerPanel = new Ext.ContentPanel('center-div', {
               fitToFrame: true , autoScroll: true
           }));
           mainLayout.endUpdate();
                       mainLayout.getRegion('east').hide();
                       mainLayout.getRegion('north').hide();
                       initBioMaterialGrid( );
                       initTree( );
               }
   };
}();

var showbms = function( id ) {
       var ids =  id.split(",");
       // note how we pass the new array in directly, without wraping it in an object first.
       ds.load({params:[ids]});
};

var ds;
var bioMaterialList;

var initBioMaterialGrid = function(div) {
       var     recordType = Ext.data.Record.create([
                       {name:"id", type:"int"},
                       {name:"name", type:"string"},
                       {name:"description", type:"string"}
               ]);

       ds = new Ext.data.Store(
               {
                       proxy:new Ext.data.DWRProxy(BioMaterialController.getBioMaterials),
                       reader:new Ext.data.ListRangeReader({id:"id"}, recordType),
                       remoteSort:false,
                       sortInfo:{field:'name'}
               });

       var cm = new Ext.grid.ColumnModel([
                       {header: "Name", width: 50, dataIndex:"name"},
                       {header: "Description",  width: 80, dataIndex:"description"}
                       ]);
       cm.defaultSortable = true;

       grid = new Ext.grid.Grid("south-div", {autoSizeColumns: true, ds:ds,cm:cm, loadMask: true });

       var gridClickHandler = function(grid, rowIndex, event){
       		//Get the ids of the selected biomaterials and put them in BiomatierialList
	       	var selected = grid.getSelectionModel().getSelections();

	    	bioMaterialList = [];
	    	for(var index=0; index<selected.length; index++) {
	    		bioMaterialList.push(selected[index].id);
	    	}

       }

       grid.on("rowclick", gridClickHandler);

       grid.render();

       var id = dwr.util.getValue("cslist");
       showbms(id);
};

var initTree = function(div){

   var tree = new Ext.tree.TreePanel("west-div", {
       animate:true,
       loader: new Ext.tree.DwrTreeLoader({dataUrl:MgedOntologyService.getBioMaterialTerms})
   });

   tree.on('click', function(node){
                   MgedOntologyService.getTerm(node.id,displayRestrictionsPanel);
          });

   // set the root node
   var root = new Ext.tree.AsyncTreeNode({
       text: 'Top of the tree',
       draggable:false,
       allowChildre:true,
       id:'root'
   });
   tree.setRootNode(root);

   // render the tree
   tree.render();
   root.expand();

};

var vocabC;

//The call back method for the dwr call
var displayRestrictionsPanel = function(node){
	console.log(dwr.util.toDescriptiveString(node, 10));

	vocabC = { termUri : node.uri,properties : [] };
	createRestrictionGui(node, vocabC);

	var saveButton = new Ext.Button("center-div", {text : 'save'});
	saveButton.on("click", saveHandler);

};

var saveHandler = function(event){
	console.log(dwr.util.toDescriptiveString(vocabC,10))
	MgedOntologyService.saveStatement(vocabC, bioMaterialList);

}

//Recursive function that walks the node given to it and creates a coresponding form to fill in
var createRestrictionGui = function(node, vc, indent, parentDivId) {

		var dh = Ext.DomHelper;  //allows html output

		if (indent === undefined){
			dh.overwrite("center-div", {html : ""});
			indent = 0;
		}
		if (parentDivId === undefined) {
			parentDivId = "center-div";

		}

		dh.append(parentDivId, {html : node.term+ ": " + node.comment});

        var res = node.restrictions;
        if ( (res !== undefined) && (res !== null) && (res.size() > 0) ) {

            for ( var i = 0, len = res.length; i < len; i++ ) {
            	var restrictedOn = res[i].restrictionOn;
            	var restrictedTo = res[i].restrictedTo;
            	//make a nested div for adding to.
            	var divId = (Math.random() * 100000).toFixed();
            	dh.append(parentDivId, {tag: 'div', id: divId, style : "border-width:thin;border-style:dotted;padding:5px;margin:5px;"});
                dh.append(divId, {html : restrictedOn.label});

            	if (restrictedOn.type !== undefined ) {	//Primitive Type

                    	var simple = new Ext.form.Form({
					        labelWidth: 75, // label settings here cascade unless overridden
					        url:'save-form.php'
					    });

					    var handler = createPrimitiveTypeHandler(restrictedOn, vc);

					    var valueField = new Ext.form.TextField({
					            fieldLabel: restrictedOn.label ,
					            name: 'hasValue',
					            width:175,
					            allowBlank:false,
					        });

					   valueField.vocabId = divId;

					   valueField.on('valid', handler);

					   simple.add(valueField);
                       dh.append(divId, {tag: 'h3', html : 'Value:' });

					   simple.render(divId);

            	} else if ( (restrictedTo !== undefined) && (restrictedTo !== null)) {	//Class restriction

                    if (restrictedTo.restrictions === undefined || restrictedTo.restrictions === null || restrictedTo.restrictions.size() === 0){	// ie) we are at a leaf node so display gui
                        dh.append(divId, {tag: 'h3', html : "Create an instance of: " +restrictedTo.term });

	                    var simple = createForm(restrictedOn,vc, divId);

    	                if (restrictedTo.individuals !== undefined && restrictedTo.individuals !== null && restrictedTo.individuals.size() > 0){  //are there examples?
    	                		var combo = createComboBox(restrictedOn, restrictedTo.individuals, vc, divId);
        		            	simple.column({width:285},combo);
                	    }

	                    simple.render(divId);
                    } else{        //Not a leaf node. recurse down another level
                    	//do i need to use propertiesPush here? ie do i want a field id associated with this?
                        var vcChild = { termUri : restrictedOn.uri, object : { termUri : restrictedTo.uri }, properties : []};
                        propertiesPush(vc, divId,vcChild);
                        //vc.properties.push(vcChild);
    	            	createRestrictionGui( restrictedTo, vcChild.object, indent + 3, divId );
                   }

                } else if ( res[i].cardinality !== undefined  ) { //Cardinality Type
                    // this will be rare.
                    var cardinality = res[i].cardinality;
                    var cardinalityType = res[i].cardinalityType;
                    dh.append(parentDivId,{ html: " Cardinality Slot to fill in: " + restrictedOn.label + " with " + cardinalityType.term + " "
                            + cardinality + " things" });
                    // todo check range of the property (what 'things' should be) if specified.
                } else{
                	  dh.append(parentDivId,{ html: " Error: Not a typical type.  " + restrictedOn.label});
                }
            }
        }
        else{//No restrictions must be a leaf node.

			//Create new div
        	var divId = (Math.random() * 100000).toFixed();
           	dh.append(parentDivId, {tag: 'div', id: divId, style : "border-width:thin;border-style:dotted;padding:5px;margin:5px;"});
           	dh.append(divId, {tag: 'h3', html :  "Create an instance of: " + node.term });
           	var simple = createForm(node,vc, divId);

           	//If there already exisit individuals display them in a drop down box so the user can select one.
            if ((node.individuals !== undefined) && (node.individuals !== null) && (node.individuals.size() > 0))
            	simple.column({width:285},createComboBox(node, node.individuals, vc, divId));

        	simple.render(divId);
        }
	};

//subject
var createComboBox = function(subject, individuals, vc, divId){
	     	var recordType = Ext.data.Record.create([
							{name:"label", type : "string" },
							{name:"uri", type: "string" }
						]);

						//convert the arry of objects into an array of records
						var records = [];
						for(var i = 0, len = individuals.length; i < len; i++) {
							records.push(new recordType(individuals[i]));
						}

	                    var store = new Ext.data.Store({recordType : recordType} );
						store.add(records);

					    var combo = new Ext.form.ComboBox({
					    	fieldLabel: 'pick one',
					        store: store,
					        displayField:'label',
					        typeAhead: true,
					        mode: 'local',
					        triggerAction: 'all',
					        emptyText:'Select an individual',
					        selectOnFocus:true
					    });

					    combo.vocabId = divId;

					    var comboHandler = function(field,record,index){
					    	var vcChild = { termUri : subject.uri, object : {termUri : record.data.uri}};

					    	if (vc.properties === undefined){
    	                	 	vc.properties = [];
    	                	}
    	                	propertiesPush(vc, field.vocabId, vcChild);

    	                };

    	                combo.on('select', comboHandler);

					    return combo;

};

//the vc is the VocabCharacteristic we would like to add to.
//The fieldId = the div ID of the component that contains the gui component
//Will check to make sure that the fieldId doesn't already exisit in the properties and if it does update it
//if not create a new one.  This is to resolve the problem of duplicates and enforces that the VC that is created is
//well formed.

var propertiesPush = function(vc, fieldId, toAdd){

	//Make sure that the vc we've been given has a properties field
	if ((vc.properties === undefined) || (vc.properties === null) || (vc.properties.length === 0)){
		vc.properties = [];
		toAdd.fieldId = fieldId;
		vc.properties.push(toAdd);
		return;
	}

	//Check if the field id already has a property set
	for(var i=0; i < vc.properties.length; i++){

		if (vc.properties[i].fieldId === undefined){
			console.log("The fieldId should be defined: " + vc.properties[i]);
			continue;
		}

		if (vc.properties[i].fieldId === fieldId) {
			toAdd.fieldId = fieldId;
			vc.properties[i] = toAdd;
			return;
		}
	}

	//If we made it this far then the property hasn't been set already.
	toAdd.fieldId = fieldId;
	vc.properties.push(toAdd);

}

//creates the Lookup textbox and the custom textbox.  The form is just used for alignment purposes.
//The subject = the OntologyTerm that we are trying to create the vocabulary characteristic for
//the vc = the vocabulary charactersit we are trying to create.
var createForm = function(subject, vc, divId){

	var lookUp = 	new Ext.form.TextField({
            fieldLabel: 'Lookup',
            name: 'lookup',
            width:150,
            allowBlank:true	});
    lookUp.vocabId = divId;

	var custom =   new Ext.form.TextField({
            fieldLabel: 'custom',
            name: 'custom',
            width:150 });

    custom.vocabId = divId;

	var customHandler = function(field){
							var newVC = {termUri : subject.uri, object : {value: field.getValue()}};
    	                	if (vc.properties === undefined){
    	                	 	vc.properties = [];
    	                	}
    	                	propertiesPush(vc, field.vocabId, newVC);
    	                };

    	       custom.on('valid', customHandler);

	var simple = new Ext.form.Form({
		        labelWidth: 50, // label settings here cascade unless overridden
    });

    simple.column(
    	{width:250},
        lookUp
        );

	simple.column(
		{width:250},
		custom
    );

   return simple;
}

//Do this for scope reasons.
//By creating a function on the fly the scope of the restrictions will be fixed for the handler.
var createPrimitiveTypeHandler = function(restrictedOn, vc){

	return 	function(field) {

					     	var newVc = {termUri : restrictedOn.uri, data : field.getValue(), type : restrictedOn.type};

					     	if (vc.properties === undefined){
    	                	 	vc.properties = [];
    	                	}

					     	propertiesPush(vc, field.vocabId,newVc);
			}
};

Ext.EventManager.onDocumentReady(Simple.init, Simple, true);

<|endoftext|>


<commit_before>define( [

  "dojo/_base/declare"
, "dojo/topic"
, "dojo/on"
, "dojo/when"
, "dojo/dom-class"
, "dojo/_base/lang"

, "dijit/_WidgetBase"
, "dijit/_TemplatedMixin"
, "dijit/_WidgetsInTemplateMixin"
, "dijit/form/TextBox"
, "dijit/Destroyable"
, 'dijit/layout/_ContentPaneResizeMixin'
, "dijit/_Container"

, "hotplate/hotDojoSubmit/defaultSubmit"

, "hotplate/hotDojoWidgets/AlertBar"
, "hotplate/hotDojoWidgets/_OverlayMixin"
, "hotplate/hotDojoStores/stores"

], function(

  declare
, topic
, on
, when
, domClass
, lang

, _WidgetBase
, _TemplatedMixin
, _WidgetsInTemplateMixin
, TextBox
, Destroyable
, _ContentPaneResizeMixin
, _Container

, ds

, AlertBar
, _OverlayMixin
, stores

){

  return declare( [ _WidgetBase, _TemplatedMixin, _Container, _ContentPaneResizeMixin, _WidgetsInTemplateMixin, _OverlayMixin, Destroyable ] , {

    templateString: '

You need to set a template for this widget!

',

    // These will be defined by the templateString
    formWidget: null,
    alertBarWidget: null,
    buttonWidget: null,

    alertBarDomPlacement: 'first',

    storeName: '',
    storeParameters: {},
    storeExtraParameters: {},

    // Internal store variable
    store: null,

    //storeFields: {},
    recordId: null,

    // Default values for editing form
    defaultValues: {},

    resetOnSuccess: false,

    baseClass: 'editing-widget',

     // The store for records
    store: null,

    _firstResize: true,

    _idTextBox: null,

    constructor: function(){
    },

    postMixInProperties: function(){
      this.inherited(arguments);

      // Copy attribute over from the prototype
      this.storeExtraParameters = lang.mixin( this.storeExtraParameters );

      // Mixin the extra parameters with storeParameters
      this.storeParameters = lang.mixin( {}, this.storeParameters, this.storeExtraParameters );

      // Make up the store internal variable
      this.store = stores( this.storeName, this.storeParameters );
    },

    postCreate: function(){
      this.inherited(arguments);

      var self = this;

      // If there is no alertBarWidget defined, then make one up and
      // place it at the top of the form
      if( ! this.alertBarWidget ){
        this.alertBarWidget = new AlertBar();
        this.alertBarWidget.placeAt( this.formWidget.containerNode, this.alertBarDomPlacement );
      }

      // Artificially add a hidden id field to the form. This way, once the form has loaded,
      // the id will be stored and it will then be re-submitted
      if( self.recordId ) {
        self._idTextBox = new TextBox( { type:"hidden", name: self.store.idProperty, value: self.recordId } );
        self._idTextBox.placeAt( self.formWidget.containerNode, 'first' );
      }

      self.own(

        // This is to make sure that DnD, if active, doesn't do a nasty preventDefault()
        // and therefore stops text selecting and moving cursor with mouse
        // Finding this one was a bit of a birch
        on( self.domNode, 'mousedown', function( e ){

          // For some reason, stopping propagation here will prevent focus
          // from happening if placeHolder there (!?). So, forcing focus on
          // previousSibling IF the click was on a placeholder;
          // TODO: UNDERSTAND WHY. This may have side effects. Stopping
          // propagation doesn't prevent the default from happening
          // if the placeHolder isn't there...
          if( domClass.contains( e.srcElement, 'dijitPlaceHolder' ) ){
            e.srcElement.previousSibling.focus();
          }

          // Stopping propagation, so that event won't reach the DnD
          // element above
          e.stopPropagation();
        }),

        // When the form's overlay is clicked, try and show the form again
        on( self, 'overlayClick', function( e ){
          self.set( 'overlayStatus', { overlayed: false, clickable: false }  ); // CLICK OFF
          self.loadInfo();
        }),

        // Check self the record doesn't get updated by remote
        topic.subscribe('storeRecordUpdate', function( from, message, remote ){

          if( remote ){
            if( message.storeName === self.storeName && message.objectId == self.recordId ){
              self.set( 'overlayStatus', { overlayed: true, clickable: true }  ); // CLICK ON
            }
          }
        })
      );

      // Submit form, trying to save values
      this.formWidget.onSubmit = ds.defaultSubmit(this.formWidget, this.buttonWidget, function(){

        // Set the values about to be saved
        var formValues = self.formWidget.get('value');

        self.set('overlayStatus', { overlayed: true, clickable: false  } ); // LOADING ON

        // Try to save the values
        when( self.store.put( formValues )) .then(
          ds.UIMsg( self.buttonWidget, self.alertBarWidget, 'Saved!' ),
          ds.UIErrorMsg( self.formWidget, self.buttonWidget, self.alertBarWidget )
        ).then(
          function( res ){

            if( !self.recordId ){

              // Set recordId since we now have it
              self.recordId = res[ self.store.idProperty ];

              if( ! self.resetOnSuccess ){

                // Add the hidden value, so that from now on subsequent saves will overwrite
                // newly created records
                if( self._idTextBox ) self._idTextBox.destroy(); // Just in case
                self._idTextBox = new TextBox( { type:"hidden", name: self.store.idProperty, value: self.recordId } );
                self._idTextBox.placeAt( self.formWidget.containerNode, 'first' );
              }

              // Let everybody know that we now have a recordId
              self.emit( 'successfulcreation', { bubbles: false, submitted: formValues, received: res } );
              topic.publish( 'successfulcreation', { store: self.store, submitted: formValues, received: res }  );

              self.emit( 'gotrecordid', { bubbles: false } );

            }

            // Let anybody interested know that submit was successful
            self.emit( 'successfulsubmit', { bubbles: false, submitted: formValues, received: res }  );
            topic.publish( 'successfulsubmit', { store: self.store, submitted: formValues, received: res }  );

            // Take the loading overlay off
            self.set( 'overlayStatus', { overlayed: false, clickable: false  } ); // LOADING OFF

            // Reset the form if required
            if( self.resetOnSuccess ){
              self.formWidget.reset();

            }
            return res;
          },
          function( err ){
            self.emit( 'unsuccessfulsubmit', { bubbles: false, submitted: formValues, error: err }  );
            topic.publish( 'unsuccessfulsubmit', { store: self.store, submitted: formValues, error: err }  );

            self.set( 'overlayStatus', { overlayed: false, clickable: false  } ); // LOADING OFF
            throw( err );
          }
        );

      }); // this.formWidget.onSubmit
    }, // postCreate()

    // It calls updateInfo in the first widget's resize
    resize: function(){
      if( this._firstResize ){
        this._firstResize = false;
        this.loadInfo();
      }
      this.inherited(arguments);
    },

    // loadInfo
    loadInfo: function(){

      this.inherited(arguments);

      var self = this;

      // ****************************************************************
      // There is a recordId set: it's the update of an existing value
      // ****************************************************************
      if( self.recordId ){

        // Set the overlay if loading. By default, this widget is overlayed
        this.set( 'overlayStatus', { overlayed: true, clickable: false } ); // LOADING ON

        when( this.store.get( this.recordId )).then(
          ds.UIMsg(),
          ds.UIErrorMsg(null, null, this.alertBarWidget )
        ).then(
          function(res){

            // OK things worked out: the overlay can go, values are assigned to form
            self.set( 'overlayStatus', { overlayed: false, clickable: false } ); // LOADING OFF
            self.formWidget.set( 'value', res ); // Thanks Karl Tiedt :D

            // Return for chaining...
            return res;

          },
          function(err){
            self.set( 'overlayStatus', { overlayed: false, clickable: false } ); // LOADING OFF - Take out the unclickable overlay
            self.set( 'overlayStatus', { overlayed: true, clickable: true } ); // CLICK ON -- Put a new clickable one in

            // Error handlers need to rethrow...
            throw( err );
          }
        );

      // ****************************************************************
      // There is no recordId set: simply show defaults
      // ****************************************************************
      } else {
        self.formWidget.set( 'value', self.defaultValues ); // Thanks Karl Tiedt :D

      }
    },

    destroy: function(){
      this.inherited(arguments);

      var self = this;

      self._idTextBox.destroy();
    },

  });

});

<commit_msg>Destroy the textbox only if it was actually there<commit_after>define( [

  "dojo/_base/declare"
, "dojo/topic"
, "dojo/on"
, "dojo/when"
, "dojo/dom-class"
, "dojo/_base/lang"

, "dijit/_WidgetBase"
, "dijit/_TemplatedMixin"
, "dijit/_WidgetsInTemplateMixin"
, "dijit/form/TextBox"
, "dijit/Destroyable"
, 'dijit/layout/_ContentPaneResizeMixin'
, "dijit/_Container"

, "hotplate/hotDojoSubmit/defaultSubmit"

, "hotplate/hotDojoWidgets/AlertBar"
, "hotplate/hotDojoWidgets/_OverlayMixin"
, "hotplate/hotDojoStores/stores"

], function(

  declare
, topic
, on
, when
, domClass
, lang

, _WidgetBase
, _TemplatedMixin
, _WidgetsInTemplateMixin
, TextBox
, Destroyable
, _ContentPaneResizeMixin
, _Container

, ds

, AlertBar
, _OverlayMixin
, stores

){

  return declare( [ _WidgetBase, _TemplatedMixin, _Container, _ContentPaneResizeMixin, _WidgetsInTemplateMixin, _OverlayMixin, Destroyable ] , {

    templateString: '

You need to set a template for this widget!

',

    // These will be defined by the templateString
    formWidget: null,
    alertBarWidget: null,
    buttonWidget: null,

    alertBarDomPlacement: 'first',

    storeName: '',
    storeParameters: {},
    storeExtraParameters: {},

    // Internal store variable
    store: null,

    //storeFields: {},
    recordId: null,

    // Default values for editing form
    defaultValues: {},

    resetOnSuccess: false,

    baseClass: 'editing-widget',

     // The store for records
    store: null,

    _firstResize: true,

    _idTextBox: null,

    constructor: function(){
    },

    postMixInProperties: function(){
      this.inherited(arguments);

      // Copy attribute over from the prototype
      this.storeExtraParameters = lang.mixin( this.storeExtraParameters );

      // Mixin the extra parameters with storeParameters
      this.storeParameters = lang.mixin( {}, this.storeParameters, this.storeExtraParameters );

      // Make up the store internal variable
      this.store = stores( this.storeName, this.storeParameters );
    },

    postCreate: function(){
      this.inherited(arguments);

      var self = this;

      // If there is no alertBarWidget defined, then make one up and
      // place it at the top of the form
      if( ! this.alertBarWidget ){
        this.alertBarWidget = new AlertBar();
        this.alertBarWidget.placeAt( this.formWidget.containerNode, this.alertBarDomPlacement );
      }

      // Artificially add a hidden id field to the form. This way, once the form has loaded,
      // the id will be stored and it will then be re-submitted
      if( self.recordId ) {
        self._idTextBox = new TextBox( { type:"hidden", name: self.store.idProperty, value: self.recordId } );
        self._idTextBox.placeAt( self.formWidget.containerNode, 'first' );
      }

      self.own(

        // This is to make sure that DnD, if active, doesn't do a nasty preventDefault()
        // and therefore stops text selecting and moving cursor with mouse
        // Finding this one was a bit of a birch
        on( self.domNode, 'mousedown', function( e ){

          // For some reason, stopping propagation here will prevent focus
          // from happening if placeHolder there (!?). So, forcing focus on
          // previousSibling IF the click was on a placeholder;
          // TODO: UNDERSTAND WHY. This may have side effects. Stopping
          // propagation doesn't prevent the default from happening
          // if the placeHolder isn't there...
          if( domClass.contains( e.srcElement, 'dijitPlaceHolder' ) ){
            e.srcElement.previousSibling.focus();
          }

          // Stopping propagation, so that event won't reach the DnD
          // element above
          e.stopPropagation();
        }),

        // When the form's overlay is clicked, try and show the form again
        on( self, 'overlayClick', function( e ){
          self.set( 'overlayStatus', { overlayed: false, clickable: false }  ); // CLICK OFF
          self.loadInfo();
        }),

        // Check self the record doesn't get updated by remote
        topic.subscribe('storeRecordUpdate', function( from, message, remote ){

          if( remote ){
            if( message.storeName === self.storeName && message.objectId == self.recordId ){
              self.set( 'overlayStatus', { overlayed: true, clickable: true }  ); // CLICK ON
            }
          }
        })
      );

      // Submit form, trying to save values
      this.formWidget.onSubmit = ds.defaultSubmit(this.formWidget, this.buttonWidget, function(){

        // Set the values about to be saved
        var formValues = self.formWidget.get('value');

        self.set('overlayStatus', { overlayed: true, clickable: false  } ); // LOADING ON

        // Try to save the values
        when( self.store.put( formValues )) .then(
          ds.UIMsg( self.buttonWidget, self.alertBarWidget, 'Saved!' ),
          ds.UIErrorMsg( self.formWidget, self.buttonWidget, self.alertBarWidget )
        ).then(
          function( res ){

            if( !self.recordId ){

              // Set recordId since we now have it
              self.recordId = res[ self.store.idProperty ];

              if( ! self.resetOnSuccess ){

                // Add the hidden value, so that from now on subsequent saves will overwrite
                // newly created records
                if( self._idTextBox ) self._idTextBox.destroy(); // Just in case
                self._idTextBox = new TextBox( { type:"hidden", name: self.store.idProperty, value: self.recordId } );
                self._idTextBox.placeAt( self.formWidget.containerNode, 'first' );
              }

              // Let everybody know that we now have a recordId
              self.emit( 'successfulcreation', { bubbles: false, submitted: formValues, received: res } );
              topic.publish( 'successfulcreation', { store: self.store, submitted: formValues, received: res }  );

              self.emit( 'gotrecordid', { bubbles: false } );

            }

            // Let anybody interested know that submit was successful
            self.emit( 'successfulsubmit', { bubbles: false, submitted: formValues, received: res }  );
            topic.publish( 'successfulsubmit', { store: self.store, submitted: formValues, received: res }  );

            // Take the loading overlay off
            self.set( 'overlayStatus', { overlayed: false, clickable: false  } ); // LOADING OFF

            // Reset the form if required
            if( self.resetOnSuccess ){
              self.formWidget.reset();

            }
            return res;
          },
          function( err ){
            self.emit( 'unsuccessfulsubmit', { bubbles: false, submitted: formValues, error: err }  );
            topic.publish( 'unsuccessfulsubmit', { store: self.store, submitted: formValues, error: err }  );

            self.set( 'overlayStatus', { overlayed: false, clickable: false  } ); // LOADING OFF
            throw( err );
          }
        );

      }); // this.formWidget.onSubmit
    }, // postCreate()

    // It calls updateInfo in the first widget's resize
    resize: function(){
      if( this._firstResize ){
        this._firstResize = false;
        this.loadInfo();
      }
      this.inherited(arguments);
    },

    // loadInfo
    loadInfo: function(){

      this.inherited(arguments);

      var self = this;

      // ****************************************************************
      // There is a recordId set: it's the update of an existing value
      // ****************************************************************
      if( self.recordId ){

        // Set the overlay if loading. By default, this widget is overlayed
        this.set( 'overlayStatus', { overlayed: true, clickable: false } ); // LOADING ON

        when( this.store.get( this.recordId )).then(
          ds.UIMsg(),
          ds.UIErrorMsg(null, null, this.alertBarWidget )
        ).then(
          function(res){

            // OK things worked out: the overlay can go, values are assigned to form
            self.set( 'overlayStatus', { overlayed: false, clickable: false } ); // LOADING OFF
            self.formWidget.set( 'value', res ); // Thanks Karl Tiedt :D

            // Return for chaining...
            return res;

          },
          function(err){
            self.set( 'overlayStatus', { overlayed: false, clickable: false } ); // LOADING OFF - Take out the unclickable overlay
            self.set( 'overlayStatus', { overlayed: true, clickable: true } ); // CLICK ON -- Put a new clickable one in

            // Error handlers need to rethrow...
            throw( err );
          }
        );

      // ****************************************************************
      // There is no recordId set: simply show defaults
      // ****************************************************************
      } else {
        self.formWidget.set( 'value', self.defaultValues ); // Thanks Karl Tiedt :D

      }
    },

    destroy: function(){
      this.inherited(arguments);

      var self = this;

      if( self._idTextBox ) self._idTextBox.destroy();
    },

  });

});

<|endoftext|>