source: main/trunk/greenstone3/web/interfaces/default/js/documentedit_scripts.js@ 32852

Last change on this file since 32852 was 32852, checked in by ak19, 5 years ago

Dr Bainbridge solved the weird bug whereby when you're in doc editing mode and quickly try to leave/reload the page before it's finished changing, you're asked to save changes. But no changes were made. The cause was complex: it checks for changes on page unload by comparing the initial states of editable elements against their final state on unload. When the page is unloaded prematurely (before the doc has finished loading) not all the editable elements have finished initialising, so the array keep track of initial states of editable elements, editableInitStates, does not match the final state. Dr Bainbridge added a counter variable to keep track of the number of editable elements that still need to finish initialising. It's incremented for every metadataTable row, since each is editable, and for every sectionText as this gets turned into a CKEditor. The count then gets decremented when each such individual element has finished initialising, which happens in the CKEditor.instanceReady handler for CKEditor items and in the setTimeout() which initialises metadataTable row elements. Previously the call to CKEditor's instanceReady() took place too late, in function documentedit_scripts::readyPageForEditing(), which got called much after CKEditor instances were all ready, and that's why adding the CKEDITOR's instanceReady handler in there didn't work. That problem is now overcome in adding the CKEDITOR instanceReady handler on documentReady. Another notable change is that the beforeunload() handler is now moved into the end of documentedit_scripts::readyPageForEditing(), since we don't want check for changes that need to be saved until the page has fully loaded. Further, it won't find any changes until editableInitStates and editableLastStates (current states) are both ready/all initialised, so that any popup about unsaved changes is now also accurate if the doc-editing page gets prematurely unloaded.

File size: 17.9 KB
Line 
1/** Javascript file for editing a single document's content - metadata and text */
2/** uses other functions in documentedit_scripts_util.js */
3
4/* some vars for document editing */
5/* if true, will look through all the metadata for the document, and add each namespace into the list of metadata sets. If set to false, will only add in the ones defined in setStaticMetadataSets function (defined below) - override this function to make a custom list of sets */
6var dynamic_metadata_set_list = true;
7/* if false, will hide the metadata list selector. So the user will only get to see the default metadata set. */
8var display_metadata_set_selector = true;
9/* if true, will make the editing controls stay visible even on page scrolling */
10var keep_editing_controls_visible = true;
11/* Here you can choose which save buttons you like. Choose from 'save', 'rebuild', 'saveandrebuild' */
12var save_and_rebuild_buttons = ["saveandrebuild"];
13//var save_and_rebuild_buttons = ["save", "rebuild", "saveandrebuild"];
14
15/* What kind of metadata element selection do we provide?
16plain: just a text input box
17fixedlist: a drop down menu with a fixed list of options (provided by the availableMetadataElements list)
18autocomplete: a text input box with a list of suggestions to choose from (provided by the availableMetadataElements list). Allows additional input other than the fixed list
19 */
20var new_metadata_field_input_type = "plain";
21/* add all metadata button? only valid with fixedlist or autocomplete metadata element selection */
22var enable_add_all_metadata_button = true;
23
24/* Metadata elements to be used in the fixedlist/autocomplete options above */
25var availableMetadataElements = ["dc.Title", "dc.Subject"];
26/* metadata elements that have a list of values/suggestions */
27var autocompleteMetadata = new Array();
28/* for each metadata element specified here, one should provide an array of values. The name is the meta_name + "_values", but you must strip . and _ from the name.
29for example
30var autocompleteMetadata = ["dc.Subject"];
31var dcSubject_values = ["Kings", "Queens", "others"];
32 */
33
34/* The metadata specified in multiValuedMetadata array will be treated as a delimited list, using mvm_delimiter. On saving, the values will be separated and saved individually */
35
36var multiValuedMetadata = new Array(); // eg ["xx.Person", "xx.Location"];
37var mvm_delimiter = ";";
38
39/************************
40 * METADATA EDIT SCRIPTS *
41 ************************/
42
43function addEditMetadataLink(cell) {
44 cell = $(cell);
45 var id = cell.attr("id").substring(6);
46 var metaTable = gs.jqGet("meta" + id);
47 var row = cell.parent();
48 var newCell = $("<td>", {
49 "style": "font-size:0.7em; padding:0px 10px",
50 "class": "editMetadataButton"
51 });
52 var linkSpan = $("<span>", {
53 "class": "ui-state-default ui-corner-all",
54 "style": "padding: 2px; float:left;"
55 });
56
57 var linkLabel = $("<span>" + gs.text.de.edit_metadata + "</span>");
58 var linkIcon = $("<span>", {
59 "class": "ui-icon ui-icon-folder-collapsed"
60 });
61 newCell.linkIcon = linkIcon;
62 newCell.linkLabel = linkLabel;
63
64 var uList = $("<ul>", {
65 "style": "outline: 0 none; margin:0px; padding:0px;"
66 });
67 var labelItem = $("<li>", {
68 "style": "float:left; list-style:none outside none;"
69 });
70 var iconItem = $("<li>", {
71 "style": "float:left; list-style:none outside none;"
72 });
73
74 uList.append(iconItem);
75 uList.append(labelItem);
76 labelItem.append(linkLabel);
77 iconItem.append(linkIcon);
78
79 var newLink = $("<a>", {
80 "href": "javascript:;"
81 });
82 newLink.click(function () {
83 if (metaTable.css("display") == "none") {
84 linkLabel.html(gs.text.de.hide_metadata);
85 linkIcon.attr("class", "ui-icon ui-icon-folder-open");
86 metaTable.css("display", "block");
87 metaTable.metaNameField.css("display", "inline");
88 metaTable.addRowButton.css("display", "inline");
89 if (enable_add_all_metadata_button == true) {
90 metaTable.addAllButton.css("display", "inline");
91 }
92 } else {
93 linkLabel.html(gs.text.de.edit_metadata);
94 linkIcon.attr("class", "ui-icon ui-icon-folder-collapsed");
95 metaTable.css("display", "none");
96 metaTable.metaNameField.css("display", "none");
97 metaTable.addRowButton.css("display", "none");
98 if (enable_add_all_metadata_button == true) {
99 metaTable.addAllButton.css("display", "none");
100 }
101 }
102 });
103
104 newLink.append(uList);
105 linkSpan.append(newLink);
106 newCell.append(linkSpan);
107 row.append(newCell);
108
109 addFunctionalityToTable(metaTable);
110 metaTable.metaNameField.css("display", "none");
111 metaTable.addRowButton.css("display", "none");
112 if (enable_add_all_metadata_button == true) {
113 metaTable.addAllButton.css("display", "none");
114 }
115}
116
117var gsmap_store = {};
118var gps_metadata_name = "GPS.mapOverlay";
119
120// Called by documentedit_scripts_util.js when saving and rebuilding.
121// This function should return all the doc sections' map overlay data so that
122// setArchivesMetadata can be called for each and the entire collection rebuilt with the changes
123function getDocMapsEditDataForSaving(collName) {
124 var map_editors_array = Object.values(gsmap_store);
125 var modifiedMaps = []; // the array that is the return value: an array of only all the modified maps
126
127
128 for(var i = 0; i < map_editors_array.length; i++) {
129 var map_editor = map_editors_array[i];
130 var oldMapData = map_editor.savedOverlays; // stringified JSON shape
131 var newMapData = JSON.stringify(ShapesUtil.overlayToJSON(map_editor.overlays)); // stringified JSON shape too
132
133 // We only consider a map editor's map data to have been modified in the following cases:
134 // - if oldMapData is null, new mapData should not be empty array
135 // - OR oldMapData had some value and it's not the same as newMapData
136 if(!oldMapData && newMapData !== "[]" || oldMapData && oldMapData !== newMapData) {
137 var nodeID = map_editors_array[i].id;
138 //console.log("old vs new mapdata for nodeID " + nodeID);
139 //console.log("OLD: " + oldMapData);
140 //console.log("NEW: " + newMapData);
141
142 modifiedMaps.push({
143 collection: collName,
144 docID: nodeID,
145 name:gps_metadata_name,
146 metapos: 0,
147 value:newMapData
148 });
149
150 // Save the new overlay values as the old ones for the state after saving and rebuilding is done.
151 // Ideally this should be do after saveAndRebuild has completed successfully, since if saveAndRebuild goes wrong
152 // we'd not have saved newMapData AND because savedOverlays will contain the newMapData, we won't detect these unsaved
153 // values in future attempts to save.
154 // But for now, we're doing this here *because* this is the procedure with regular metadata (works out changes,
155 // then saves those changes as initStates BEFORE saveAndRebuild is called)
156 map_editor.savedOverlays = newMapData;
157 }
158
159 }
160
161 return modifiedMaps;
162}
163
164
165function addEditMapGPSLink(cell) {
166 cell = $(cell);
167 var id = cell.attr("id").substring(6);
168 //console.log(id);
169 var mapGPScontainer = gs.jqGet("map-and-controls-" + id);
170 var row = cell.parent();
171 var newCell = $("<td>", {
172 "style": "font-size:0.7em; padding:0px 10px",
173 "class": "editMapGPSButton"
174 });
175 var linkSpan = $("<span>", {
176 "class": "ui-state-default ui-corner-all",
177 "style": "padding: 2px; float:left;"
178 });
179
180 var linkLabel = $("<span>" + gs.text.de.edit_map_gps + "</span>");
181 var linkIcon = $("<span>", {
182 "class": "ui-icon ui-icon-folder-collapsed"
183 });
184 newCell.linkIcon = linkIcon;
185 newCell.linkLabel = linkLabel;
186
187 var uList = $("<ul>", {
188 "style": "outline: 0 none; margin:0px; padding:0px;"
189 });
190 var labelItem = $("<li>", {
191 "style": "float:left; list-style:none outside none;"
192 });
193 var iconItem = $("<li>", {
194 "style": "float:left; list-style:none outside none;"
195 });
196
197 uList.append(iconItem);
198 uList.append(labelItem);
199 labelItem.append(linkLabel);
200 iconItem.append(linkIcon);
201
202 var mapEditor = new MapEditor(id);
203 gsmap_store["map-" + id] = mapEditor;
204
205 var newLink = $("<a>", {
206 "href": "javascript:;"
207 });
208 newLink.click(function () {
209 //console.log(" Show/Hide Map Editor ");
210 var clicked_mapEditor = gsmap_store["map-" + id];
211
212 if (clicked_mapEditor.map == null) {
213 clicked_mapEditor.initMapEditorControls();
214 clicked_mapEditor.initMapEditor();
215 }
216 if (mapGPScontainer.css("display") == "none") {
217 linkLabel.html(gs.text.de.hide_map_gps);
218 linkIcon.attr("class", "ui-icon ui-icon-folder-open");
219 mapGPScontainer.css("display", "block");
220 } else {
221 linkLabel.html(gs.text.de.edit_map_gps);
222 linkIcon.attr("class", "ui-icon ui-icon-folder-collapsed");
223 mapGPScontainer.css("display", "none");
224
225 }
226 });
227
228 newLink.append(uList);
229 linkSpan.append(newLink);
230 newCell.append(linkSpan);
231 row.append(newCell);
232
233 mapGPScontainer.css("display", "none");
234
235 addFunctionalityToTable(mapGPScontainer); // **********************************
236 mapGPScontainer.metaNameField.css("display", "none");
237 mapGPScontainer.addRowButton.css("display", "none");
238 if (enable_add_all_metadata_button == true) {
239 mapGPScontainer.addAllButton.css("display", "none");
240 }
241
242}
243
244function setEditingFeaturesVisible(visible) {
245 if (visible) {
246 $("#editContentButton").html(gs.text.de.hide_editor);
247 $("#editContentButtonDiv").attr("class", "ui-state-default ui-corner-all");
248 } else {
249 $("#editContentButton").html(gs.text.de.edit_content);
250 $("#editContentButtonDiv").attr("class", "");
251 }
252
253 var visibility = (visible ? "" : "none");
254 if (display_metadata_set_selector == true) {
255 $("#metadataListLabel, #metadataSetList").css("display", visibility);
256 }
257
258 $(".editMetadataButton").each(function () {
259 $(this).css("display", visibility);
260 $(this.linkLabel).html(gs.text.de.edit_metadata);
261 $(this.linkIcon).attr("class", "ui-icon ui-icon-folder-collapsed");
262 });
263 /*
264 $(".editMapGPS").each(function(){
265 $(this).css("display", visibility);
266 $(this.linkLabel).html(gs.text.de.edit_map_gps);
267 $(this.linkIcon).attr("class", "ui-icon ui-icon-folder-collapsed");
268 });
269 */
270
271 $("table").each(function () {
272 if ($(this).attr("id") && $(this).attr("id").search(/^meta/) != -1) {
273 $(this).css("display", "none");
274 $(this.metaNameField).css("display", "none");
275 $(this.addRowButton).css("display", "none");
276 if (enable_add_all_metadata_button == true) {
277 $(this.addAllButton).css("display", "none");
278 }
279 }
280 });
281}
282
283/* override this function in other interface/site/collection if you want
284a different set of metadata sets
285Use in conjunction with the dynamic_metadata_set_list variable. */
286function setStaticMetadataSets(list) {
287 addOptionToList(list, "All", gs.text.de.all_metadata);
288}
289
290function readyPageForEditing() {
291
292 // CKEDITOR.on('instanceReady', function(evt) {
293 // addCKEEditableState(evt,editableInitStates);
294 // });
295
296 if ($("#metadataSetList").length) {
297 var setList = $("#metadataSetList");
298 if (!setList.css("display") || setList.css("display") == "") {
299 setEditingFeaturesVisible(false);
300 } else {
301 setEditingFeaturesVisible(true);
302 }
303 return;
304 }
305
306 $("#editContentButton").html(gs.text.de.hide_editor);
307 //wait for 0.5 sec to let ckeditor up
308
309 // The following is now done in the CKEDTIOR.on('instanceReady') handler, which is added when docReady, see documentedit_scripts_util::$( document ).ready(...)
310 // Attempting CKEDTIOR.on('instanceReady') at the start of this method didn't work because it was probably too late in page load phase to add the event handler then
311 // (the instanceReady() event would have been triggered before this method finally got called).
312 /*
313 setTimeout(function () {
314 $(".sectionText").each(function () {
315 addEditableState(this, editableInitStates);
316 });
317 }, 500);
318 */
319 var editBar = $("#editBarLeft");
320
321 var visibleMetadataList = $("<select>", {
322 "id": "metadataSetList",
323 "class": "ui-state-default"
324 });
325 setStaticMetadataSets(visibleMetadataList);
326
327 if (display_metadata_set_selector == true) {
328 var metadataListLabel = $("<span>", {
329 "id": "metadataListLabel",
330 "style": "margin-left:20px;"
331 });
332 metadataListLabel.html(gs.text.de.visible_metadata);
333 editBar.append(metadataListLabel);
334 } else {
335 visibleMetadataList.css("display", "none");
336 }
337 editBar.append(visibleMetadataList);
338 visibleMetadataList.change(onVisibleMetadataSetChange);
339 editBar.append("<br>");
340
341 for (var i = 0; i < save_and_rebuild_buttons.length; i++) {
342 var button_type = save_and_rebuild_buttons[i];
343 if (button_type == "save") {
344 var saveButton = $("<button>", {
345 "id": "saveButton",
346 "class": "ui-state-default ui-corner-all"
347 });
348 saveButton.click(save);
349 saveButton.html(gs.text.de.save);
350 editBar.append(saveButton);
351 } else if (button_type == "rebuild") {
352 var rebuildButton = $("<button>", {
353 "id": "rebuildButton",
354 "class": "ui-state-default ui-corner-all"
355 });
356 rebuildButton.click(rebuildCurrentCollection);
357 rebuildButton.html(gs.text.de.rebuild);
358 editBar.append(rebuildButton);
359 } else if (button_type == "saveandrebuild") {
360 var saveAndRebuildButton = $("<button>", {
361 "id": "saveAndRebuildButton",
362 "class": "ui-state-default ui-corner-all"
363 });
364 saveAndRebuildButton.click(saveAndRebuild);
365 saveAndRebuildButton.html(gs.text.de.saverebuild);
366 editBar.append(saveAndRebuildButton);
367
368 }
369 }
370 var statusBarDiv = $("<div>");
371 editBar.append(statusBarDiv);
372 _statusBar = new StatusBar(statusBarDiv[0]);
373
374 var titleDivs = $(".sectionTitle");
375 for (var i = 0; i < titleDivs.length; i++) {
376 addEditMetadataLink(titleDivs[i]);
377 addEditMapGPSLink(titleDivs[i]);
378 }
379
380 // We need to keep track of editableElementsInitialisationProgress: the number of editable elements that need to be initialised/need to finish initialising
381 // As CKEditors will be added, meaning more editable elements, must increment our counter editableElementsInitialisationProgress
382 var $num_editable_textareas = $(".sectionText"); // consider searching for 'contenteditable="true"' as this is what CKEDITOR is looking for (we think!)
383 editableElementsInitialisationProgress += $num_editable_textareas.length;
384
385 _baseURL = gs.xsltParams.library_name;
386 onVisibleMetadataSetChange(); // make sure that the selected item in the list is active
387
388 // If the user is attempting to leave the page, check if there are unsaved changes
389 // and if so, display an "Are you sure you want to leave" message.
390 // https://stackoverflow.com/questions/7080269/javascript-before-leaving-the-page
391 // Newer versions of Firefox/Chrome don't display custom message (security feature):
392 // https://stackoverflow.com/questions/22776544/why-is-jquery-onbeforeunload-not-working-in-chrome-and-firefox
393 // and http://jsfiddle.net/XZAWS/
394 // jquery bind() is deprecated: https://stackoverflow.com/questions/33654716/is-jquery-bind-deprecated
395 console.log("**** away to set up beforeunload handler!");
396 $(window).on("beforeunload", function(event) {
397
398 if(gs.cgiParams.docEdit == "1") { // like document.xsl, which checks the same var upon onload
399 // shouldn't check for whether changes are saved unless on Doc Editing page (DocEdit=1)
400 // else the following pop up always ends up appearing when attempting
401 // to leave a doc view page in Doc Editing Mode (when not yet actually Doc Editing)
402
403 // Because we've done extra work now in maintaining "editableElementsInitialisationProgress", which is
404 // the number of editable elements that still need to finish initialising, we can now be confident that
405 // the call to changesToUpdate() below won't return the wrong answers if a page with docEdit turned on
406 // is asked to unload (e.g. by pressing Reload) before the page has finished loading.
407 var changes = changesToUpdate();
408
409 console.log("#### CHANGES before page reload: ", changes);
410
411 if(changes.length > 0) {
412 console.log("The collection hasn't yet been saved after editing. Are you sure you want to leave?");
413 return "The collection hasn't yet been saved after editing. Are you sure you want to leave?";
414 }
415 }
416 });
417
418
419}
420
421// override the one in documentmaker_scripts_util
422// currently not used if other one is present. need to get the js include order right
423function enableSaveButtons(enabled) {
424 if (enabled) {
425 $("#saveButton, #rebuildButton, #saveAndRebuildButton").removeAttr("disabled");
426 } else {
427 $("#saveButton, #rebuildButton, #saveAndRebuildButton").attr("disabled", "disabled");
428 }
429}
430
431/* this is a cut down version of save() from documentmaker_scripts_util.js
432going back to using save, will delete this once everything working*/
433function saveMetadataChangesOld() {
434
435 console.log("Saving metadata changes");
436
437 // get collection name
438 var collection = gs.cgiParams.c;
439
440 // get document id
441 var docID = gs.cgiParams.d;
442
443 var metadataChanges = new Array();
444 if (_deletedMetadata.length > 0) {
445
446 for (var i = 0; i < _deletedMetadata.length; i++) {
447
448 var currentRow = _deletedMetadata[i];
449
450 //Get metadata name
451 var cells = currentRow.getElementsByTagName("TD");
452 var nameCell = cells[0];
453 var name = nameCell.innerHTML;
454 var valueCell = cells[1];
455 var value = valueCell.innerHTML;
456 metadataChanges.push({
457 type: 'delete',
458 docID: docID,
459 name: name,
460 value: value
461 });
462 removeFromParent(currentRow);
463 }
464 }
465
466 if (metadataChanges.length == 0) {
467 console.log(gs.text.de.no_changes);
468 return;
469 }
470
471 var processChangesLoop = function (index) {
472 var change = metadataChanges[index];
473
474 var callbackFunction;
475 if (index + 1 == metadataChanges.length) {
476 callbackFunction = function () {
477 console.log("Completed saving metadata changes. You must rebuild the collection for the changes to take effect.");
478 };
479 } else {
480 callbackFunction = function () {
481 processChangesLoop(index + 1)
482 };
483 }
484 if (change.type == "delete") {
485 gs.functions.removeArchivesMetadata(collection, gs.xsltParams.site_name, change.docID, change.name, null, change.value, function () {
486 callbackFunction();
487 });
488 } else {
489 if (change.orig) {
490 gs.functions.setArchivesMetadata(collection, gs.xsltParams.site_name, docID, change.name, null, change.value, change.orig, "override", function () {
491 callbackFunction();
492 });
493 } else {
494 gs.functions.setArchivesMetadata(collection, gs.xsltParams.site_name, docID, change.name, null, change.value, null, "accumulate", function () {
495 callbackFunction();
496 });
497 }
498 }
499 }
500 processChangesLoop(0);
501 /* need to clear the changes from the page */
502 while (_deletedMetadata.length > 0) {
503 _deletedMetadata.pop();
504 }
505
506}
Note: See TracBrowser for help on using the repository browser.