www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

itemFields.js (14207B)


      1 /*
      2     ***** BEGIN LICENSE BLOCK *****
      3     
      4     Copyright © 2009 Center for History and New Media
      5                      George Mason University, Fairfax, Virginia, USA
      6                      http://zotero.org
      7     
      8     This file is part of Zotero.
      9     
     10     Zotero is free software: you can redistribute it and/or modify
     11     it under the terms of the GNU Affero General Public License as published by
     12     the Free Software Foundation, either version 3 of the License, or
     13     (at your option) any later version.
     14     
     15     Zotero is distributed in the hope that it will be useful,
     16     but WITHOUT ANY WARRANTY; without even the implied warranty of
     17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18     GNU Affero General Public License for more details.
     19     
     20     You should have received a copy of the GNU Affero General Public License
     21     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     22     
     23     ***** END LICENSE BLOCK *****
     24 */
     25 
     26 
     27 Zotero.ItemFields = new function() {
     28 	// Private members
     29 	var _fields = {};
     30 	var _fieldsFormats = [];
     31 	var _fieldsLoaded;
     32 	var _itemTypeFieldsLoaded;
     33 	var _fieldFormats = [];
     34 	var _itemTypeFields = [];
     35 	var _baseTypeFields = [];
     36 	var _baseMappedFields = [];
     37 	var _typeFieldIDsByBase = {};
     38 	var _typeFieldNamesByBase = {};
     39 	var _baseFieldIDsByTypeAndField = {};
     40 	
     41 	// Privileged methods
     42 	this.getName = getName;
     43 	this.getID = getID;
     44 	this.getLocalizedString = getLocalizedString;
     45 	this.isValidForType = isValidForType;
     46 	this.isInteger = isInteger;
     47 	this.getItemTypeFields = getItemTypeFields;
     48 	this.isBaseField = isBaseField;
     49 	this.isFieldOfBase = isFieldOfBase;
     50 	this.getFieldIDFromTypeAndBase = getFieldIDFromTypeAndBase;
     51 	this.getBaseIDFromTypeAndField = getBaseIDFromTypeAndField;
     52 	this.getTypeFieldsFromBase = getTypeFieldsFromBase;
     53 	
     54 	
     55 	/*
     56 	 * Load all fields into an internal hash array
     57 	 */
     58 	this.init = Zotero.Promise.coroutine(function* () {
     59 		_fields = {};
     60 		_fieldsFormats = [];
     61 		
     62 		var result = yield Zotero.DB.queryAsync('SELECT * FROM fieldFormats');
     63 		
     64 		for (var i=0; i<result.length; i++) {
     65 			_fieldFormats[result[i]['fieldFormatID']] = {
     66 				regex: result[i]['regex'],
     67 				isInteger: result[i]['isInteger']
     68 			};
     69 		}
     70 		
     71 		var fields = yield Zotero.DB.queryAsync('SELECT * FROM fieldsCombined');
     72 		
     73 		var fieldItemTypes = yield _getFieldItemTypes();
     74 		
     75 		var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappingsCombined";
     76 		var baseFields = yield Zotero.DB.columnQueryAsync(sql);
     77 		
     78 		for (let field of fields) {
     79 			_fields[field['fieldID']] = {
     80 				id: field['fieldID'],
     81 				name: field.fieldName,
     82 				label: field.label,
     83 				custom: !!field.custom,
     84 				isBaseField: (baseFields.indexOf(field['fieldID']) != -1),
     85 				formatID: field['fieldFormatID'],
     86 				itemTypes: fieldItemTypes[field['fieldID']]
     87 			};
     88 			// Store by name as well as id
     89 			_fields[field['fieldName']] = _fields[field['fieldID']];
     90 		}
     91 		
     92 		_fieldsLoaded = true;
     93 		yield _loadBaseTypeFields();
     94 		yield _loadItemTypeFields();
     95 	});
     96 	
     97 	
     98 	/*
     99 	 * Return the fieldID for a passed fieldID or fieldName
    100 	 */
    101 	function getID(field) {
    102 		if (!_fieldsLoaded) {
    103 			throw new Zotero.Exception.UnloadedDataException("Item field data not yet loaded");
    104 		}
    105 		
    106 		if (typeof field == 'number') {
    107 			return _fields[field] ? field : false;
    108 		}
    109 		
    110 		return _fields[field] ? _fields[field]['id'] : false;
    111 	}
    112 	
    113 	
    114 	/*
    115 	 * Return the fieldName for a passed fieldID or fieldName
    116 	 */
    117 	function getName(field) {
    118 		if (!_fieldsLoaded) {
    119 			throw new Zotero.Exception.UnloadedDataException("Item field data not yet loaded");
    120 		}
    121 		
    122 		return _fields[field] ? _fields[field]['name'] : false;
    123 	}
    124 	
    125 	
    126 	function getLocalizedString(itemType, field) {
    127 		// unused currently
    128 		//var typeName = Zotero.ItemTypes.getName(itemType);
    129 		var fieldName = Zotero.ItemFields.getName(field);
    130 		
    131 		// Fields in the items table are special cases
    132 		switch (field) {
    133 			case 'dateAdded':
    134 			case 'dateModified':
    135 			case 'itemType':
    136 				return Zotero.getString("itemFields." + field);
    137 		}
    138 		
    139 		// TODO: different labels for different item types
    140 		
    141 		_fieldCheck(field, 'getLocalizedString');
    142 		
    143 		if (_fields[field].label) {
    144 			return _fields[field].label;
    145 		}
    146 		else {
    147 			try {
    148 				var loc = Zotero.getString("itemFields." + fieldName);
    149 			}
    150 			// If localized string not found, try base field
    151 			catch (e) {
    152 				Zotero.debug("Localized string not found for field '" + fieldName + "' -- trying base field");
    153 				var baseFieldID = this.getBaseIDFromTypeAndField(itemType, field);
    154 				fieldName = this.getName(baseFieldID);
    155 				var loc = Zotero.getString("itemFields." + fieldName);
    156 			}
    157 			return loc;
    158 		}
    159 	}
    160 	
    161 	
    162 	function isValidForType(fieldID, itemTypeID) {
    163 		fieldID = getID(fieldID);
    164 		if (!fieldID) return false;
    165 		
    166 		if (!_fields[fieldID]['itemTypes']) {
    167 			return false;
    168 		}
    169 		
    170 		return !!_fields[fieldID]['itemTypes'][itemTypeID];
    171 	}
    172 	
    173 	
    174 	function isInteger(fieldID) {
    175 		_fieldCheck(fieldID, 'isInteger');
    176 		
    177 		var ffid = _fields[fieldID]['formatID'];
    178 		return _fieldFormats[ffid] ? _fieldFormats[ffid]['isInteger'] : false;
    179 	}
    180 	
    181 	
    182 	this.isCustom = function (fieldID) {
    183 		_fieldCheck(fieldID, 'isCustom');
    184 		
    185 		return _fields[fieldID].custom;
    186 	}
    187 	
    188 	
    189 	/*
    190 	 * Returns an array of fieldIDs for a given item type
    191 	 */
    192 	function getItemTypeFields(itemTypeID) {
    193 		if (!itemTypeID) {
    194 			let e = new Error("Invalid item type id '" + itemTypeID + "'");
    195 			e.name = "ZoteroUnknownTypeError";
    196 			throw e;
    197 		}
    198 		
    199 		if (!_itemTypeFieldsLoaded) {
    200 			throw new Zotero.Exception.UnloadedDataException("Item field data not yet loaded");
    201 		}
    202 		
    203 		if (!_itemTypeFields[itemTypeID]) {
    204 			throw new Error("Item type field data not found for itemTypeID " + itemTypeID);
    205 		}
    206 		
    207 		return [..._itemTypeFields[itemTypeID]];
    208 	}
    209 	
    210 	
    211 	function isBaseField(field) {
    212 		_fieldCheck(field, arguments.callee.name);
    213 		
    214 		return _fields[field]['isBaseField'];
    215 	}
    216 	
    217 	
    218 	function isFieldOfBase(field, baseField) {
    219 		var fieldID = _fieldCheck(field, 'isFieldOfBase');
    220 		
    221 		var baseFieldID = this.getID(baseField);
    222 		if (!baseFieldID) {
    223 			throw new Error("Invalid field '" + baseField + '" for base field');
    224 		}
    225 		
    226 		if (fieldID == baseFieldID) {
    227 			return true;
    228 		}
    229 		
    230 		var typeFields = this.getTypeFieldsFromBase(baseFieldID);
    231 		return typeFields.indexOf(fieldID) != -1;
    232 	}
    233 	
    234 	
    235 	this.getBaseMappedFields = function () {
    236 		return _baseMappedFields.concat();
    237 	}
    238 	
    239 	
    240 	/*
    241 	 * Returns the fieldID of a type-specific field for a given base field
    242 	 * 		or false if none
    243 	 *
    244 	 * Examples:
    245 	 *
    246 	 * 'audioRecording' and 'publisher' returns label's fieldID
    247 	 * 'book' and 'publisher' returns publisher's fieldID
    248 	 * 'audioRecording' and 'number' returns false
    249 	 *
    250 	 * Accepts names or ids
    251 	 */
    252 	function getFieldIDFromTypeAndBase(itemType, baseField) {
    253 		var itemTypeID = Zotero.ItemTypes.getID(itemType);
    254 		if (!itemTypeID) {
    255 			throw new Error("Invalid item type '" + itemType + "'");
    256 		}
    257 		
    258 		var baseFieldID = this.getID(baseField);
    259 		if (!baseFieldID) {
    260 			throw new Error("Invalid field '" + baseField + '" for base field');
    261 		}
    262 		
    263 		// If field isn't a base field, return it if it's valid for the type
    264 		if (!this.isBaseField(baseFieldID)) {
    265 			return this.isValidForType(baseFieldID, itemTypeID) ? baseFieldID : false;
    266 		}
    267 		
    268 		return _baseTypeFields[itemTypeID][baseFieldID];
    269 	}
    270 	
    271 	
    272 	/*
    273 	 * Returns the fieldID of the base field for a given type-specific field
    274 	 * 		or false if none
    275 	 *
    276 	 * Examples:
    277 	 *
    278 	 * 'audioRecording' and 'label' returns publisher's fieldID
    279 	 * 'book' and 'publisher' returns publisher's fieldID
    280 	 * 'audioRecording' and 'runningTime' returns false
    281 	 *
    282 	 * Accepts names or ids
    283 	 */
    284 	function getBaseIDFromTypeAndField(itemType, typeField) {
    285 		var itemTypeID = Zotero.ItemTypes.getID(itemType);
    286 		var typeFieldID = this.getID(typeField);
    287 		
    288 		if (!itemTypeID) {
    289 			throw new Error("Invalid item type '" + itemType + "'");
    290 		}
    291 		
    292 		_fieldCheck(typeField, 'getBaseIDFromTypeAndField');
    293 		
    294 		if (!this.isValidForType(typeFieldID, itemTypeID)) {
    295 			throw new Error("'" + typeField + "' is not a valid field for '" + itemType + "'");
    296 		}
    297 		
    298 		// If typeField is already a base field, just return that
    299 		if (this.isBaseField(typeFieldID)) {
    300 			return typeFieldID;
    301 		}
    302 		
    303 		if (!_baseFieldIDsByTypeAndField[itemTypeID]) return false;
    304 		return _baseFieldIDsByTypeAndField[itemTypeID][typeFieldID] || false;
    305 	}
    306 	
    307 	
    308 	/*
    309 	 * Returns an array of fieldIDs associated with a given base field
    310 	 *
    311 	 * e.g. 'publisher' returns fieldIDs for [university, studio, label, network]
    312 	 */
    313 	function getTypeFieldsFromBase(baseField, asNames) {
    314 		var baseFieldID = this.getID(baseField);
    315 		if (!baseFieldID) {
    316 			throw ("Invalid base field '" + baseField + '" in ItemFields.getTypeFieldsFromBase()');
    317 		}
    318 		
    319 		if (asNames) {
    320 			return _typeFieldNamesByBase[baseFieldID] ?
    321 				_typeFieldNamesByBase[baseFieldID] : false;
    322 		}
    323 		
    324 		return _typeFieldIDsByBase[baseFieldID] ?
    325 			[..._typeFieldIDsByBase[baseFieldID]] : false;
    326 	}
    327 	
    328 	
    329 	this.isAutocompleteField = function (field) {
    330 		field = this.getName(field);
    331 		
    332 		var autoCompleteFields = [
    333 			'journalAbbreviation',
    334 			'series',
    335 			'seriesTitle',
    336 			'seriesText',
    337 			'libraryCatalog',
    338 			'callNumber',
    339 			'archive',
    340 			'archiveLocation',
    341 			'language',
    342 			'programmingLanguage',
    343 			'rights',
    344 			
    345 			// TEMP - NSF
    346 			'programDirector',
    347 			'institution',
    348 			'discipline'
    349 		];
    350 		
    351 		// Add the type-specific versions of these base fields
    352 		var baseACFields = ['publisher', 'publicationTitle', 'type', 'medium', 'place'];
    353 		autoCompleteFields = autoCompleteFields.concat(baseACFields);
    354 		
    355 		for (var i=0; i<baseACFields.length; i++) {
    356 			var add = Zotero.ItemFields.getTypeFieldsFromBase(baseACFields[i], true)
    357 			autoCompleteFields = autoCompleteFields.concat(add);
    358 		}
    359 		
    360 		return autoCompleteFields.indexOf(field) != -1;
    361 	}
    362 	
    363 	
    364 	/**
    365 	 * A long field expands into a multiline textbox while editing but displays
    366 	 * as a single line in non-editing mode; newlines are not allowed
    367 	 */
    368 	this.isLong = function (field) {
    369 		field = this.getName(field);
    370 		var fields = [
    371 			'title',
    372 			'bookTitle'
    373 		];
    374 		return fields.indexOf(field) != -1;
    375 	}
    376 	
    377 	
    378 	/**
    379 	 * A multiline field displays as a multiline text box in editing mode
    380 	 * and non-editing mode; newlines are allowed
    381 	 */
    382 	this.isMultiline = function (field) {
    383 		field = this.getName(field);
    384 		var fields = [
    385 			'abstractNote',
    386 			'extra',
    387 			
    388 			// TEMP - NSF
    389 			'address'
    390 		];
    391 		return fields.indexOf(field) != -1;
    392 	}
    393 	
    394 	
    395 	/**
    396 	* Check whether a field is valid, throwing an exception if not
    397 	* (since it should never actually happen)
    398 	**/
    399 	function _fieldCheck(field, func) {
    400 		var fieldID = Zotero.ItemFields.getID(field);
    401 		if (!fieldID) {
    402 			Zotero.debug((new Error).stack, 1);
    403 			throw new Error("Invalid field '" + field + (func ? "' in ItemFields." + func + "()" : "'"));
    404 		}
    405 		return fieldID;
    406 	}
    407 	
    408 	
    409 	/*
    410 	 * Returns hash array of itemTypeIDs for which a given field is valid
    411 	 */
    412 	var _getFieldItemTypes = Zotero.Promise.coroutine(function* () {
    413 		var sql = 'SELECT fieldID, itemTypeID FROM itemTypeFieldsCombined';
    414 		var results = yield Zotero.DB.queryAsync(sql);
    415 		
    416 		if (!results) {
    417 			throw ('No fields in itemTypeFields!');
    418 		}
    419 		var fields = [];
    420 		for (let i=0; i<results.length; i++) {
    421 			if (!fields[results[i].fieldID]) {
    422 				fields[results[i].fieldID] = [];
    423 			}
    424 			fields[results[i].fieldID][results[i].itemTypeID] = true;
    425 		}
    426 		return fields;
    427 	});
    428 	
    429 	
    430 	/*
    431 	 * Build a lookup table for base field mappings
    432 	 */
    433 	var _loadBaseTypeFields = Zotero.Promise.coroutine(function* () {
    434 		_typeFieldIDsByBase = {};
    435 		_typeFieldNamesByBase = {};
    436 		
    437 		// Grab all fields, base field or not
    438 		var sql = "SELECT IT.itemTypeID, F.fieldID AS baseFieldID, BFM.fieldID "
    439 			+ "FROM itemTypesCombined IT LEFT JOIN fieldsCombined F "
    440 			+ "LEFT JOIN baseFieldMappingsCombined BFM"
    441 			+ " ON (IT.itemTypeID=BFM.itemTypeID AND F.fieldID=BFM.baseFieldID)";
    442 		var rows = yield Zotero.DB.queryAsync(sql);
    443 		
    444 		var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappingsCombined";
    445 		var baseFields = yield Zotero.DB.columnQueryAsync(sql);
    446 		
    447 		var fields = [];
    448 		for (let row of rows) {
    449 			if (!fields[row.itemTypeID]) {
    450 				fields[row.itemTypeID] = [];
    451 			}
    452 			if (row.fieldID) {
    453 				fields[row.itemTypeID][row.baseFieldID] = row.fieldID;
    454 			}
    455 			// If a base field and already valid for the type, just use that
    456 			else if (isBaseField(row.baseFieldID) &&
    457 					isValidForType(row.baseFieldID, row.itemTypeID)) {
    458 				fields[row.itemTypeID][row.baseFieldID] = row.baseFieldID;
    459 			}
    460 			// Set false for other fields so that we don't need to test for
    461 			// existence
    462 			else {
    463 				fields[row.itemTypeID][row.baseFieldID] = false;
    464 			}
    465 		}
    466 		_baseTypeFields = fields;
    467 		
    468 		var sql = "SELECT itemTypeID, baseFieldID, fieldID, fieldName "
    469 			+ "FROM baseFieldMappingsCombined JOIN fieldsCombined USING (fieldID)";
    470 		var rows = yield Zotero.DB.queryAsync(sql);
    471 		for (let i = 0; i < rows.length; i++) {
    472 			let row = rows[i];
    473 			// Type fields by base
    474 			if (!_typeFieldIDsByBase[row['baseFieldID']]) {
    475 				_typeFieldIDsByBase[row['baseFieldID']] = [];
    476 				_typeFieldNamesByBase[row['baseFieldID']] = [];
    477 			}
    478 			_typeFieldIDsByBase[row['baseFieldID']].push(row['fieldID']);
    479 			_typeFieldNamesByBase[row['baseFieldID']].push(row['fieldName']);
    480 			
    481 			// Base fields by type and field
    482 			if (!_baseFieldIDsByTypeAndField[row.itemTypeID]) {
    483 				_baseFieldIDsByTypeAndField[row.itemTypeID] = {};
    484 			}
    485 			_baseFieldIDsByTypeAndField[row.itemTypeID][row.fieldID] = row.baseFieldID;
    486 			
    487 		}
    488 		
    489 		// Get all fields mapped to base types
    490 		sql = "SELECT DISTINCT fieldID FROM baseFieldMappingsCombined";
    491 		_baseMappedFields = yield Zotero.DB.columnQueryAsync(sql);
    492 		
    493 		_baseTypeFieldsLoaded = true;
    494 	});
    495 	
    496 	
    497 	var _loadItemTypeFields = Zotero.Promise.coroutine(function* () {
    498 		var sql = 'SELECT itemTypeID, fieldID FROM itemTypeFieldsCombined ORDER BY orderIndex';
    499 		var rows = yield Zotero.DB.queryAsync(sql);
    500 		
    501 		_itemTypeFields = {};
    502 		_itemTypeFields[1] = []; // notes have no fields
    503 		
    504 		for (let i=0; i<rows.length; i++) {
    505 			let row = rows[i];
    506 			let itemTypeID = row.itemTypeID;
    507 			if (!_itemTypeFields[itemTypeID]) {
    508 				_itemTypeFields[itemTypeID] = [];
    509 			}
    510 			_itemTypeFields[itemTypeID].push(row.fieldID);
    511 		}
    512 		
    513 		_itemTypeFieldsLoaded = true;
    514 	});
    515 }