/*global define */ define(['jquery', 'underscore', 'backbone', 'bootstrap', 'jqueryform', 'views/SignInView', 'text!templates/alert.html', 'text!templates/registryFields.html', 'text!templates/ldapAccountTools.html', 'text!templates/loading.html', 'text!templates/loginHeader.html', 'text!templates/insertProgress.html'], function($, _, Backbone, BootStrap, jQueryForm, SignInView, AlertTemplate, RegistryFields, LdapAccountToolsTemplate, LoadingTemplate, LoginHeaderTemplate, ProgressTemplate) { 'use strict'; // Build the main header view of the application var RegistryView = Backbone.View.extend({ type: "Editor", el: '#Content', loginEl: '#RegistryLogin', template: _.template(RegistryFields), alertTemplate: _.template(AlertTemplate), loadingTemplate: _.template(LoadingTemplate), ldapAccountToolsTemplate: _.template(LdapAccountToolsTemplate), loginHeaderTemplate: _.template(LoginHeaderTemplate), progressTemplate: _.template(ProgressTemplate), registryUrl: null, stage: null, pid: null, registryQueryString: "cfg=metacatui", events: { "click #entryFormSubmit" : "submitEntryForm", "click #entryReturnSubmit" : "submitReturnForm", "click #dataCorrect" : "submitConfirmYesForm", "click #dataWrongButton" : "submitConfirmNoForm", "click .dataWrongButton" : "submitConfirmNoForm", "click #loginButton" : "submitLoginForm", "click #registerAnotherPackage" : "registerAnotherPackage", "click #createAccount" : "createAccount", "click #lookupAccount" : "lookupAccount", "click #resetPassword" : "resetPassword", "click #changePassword" : "changePassword", "keypress input[name='password']" : "submitOnEnter", "keypress input[name='uid']" : "submitOnEnter", "click .remove-award" : "removeAward", "keypress #funding-visible" : "addAwardOnEnter", "change #RegistryEntryForm :input" : "trackChange" }, initialize: function () { }, render: function () { // request a smaller header appModel.set('headerType', 'default'); // show the loading icon this.showLoading(); //Are we using auth tokens? var tokenUrl = appModel.get("tokenUrl"); if((typeof tokenUrl != "undefined") && tokenUrl.length){ //If our app user's status hasn't been checked yet, then wait... if(!appUserModel.get("checked")){ this.listenToOnce(appUserModel, "change:checked", function(){ var thisView = appView.currentView || appView.registryView; if(!thisView){ appView.showAlert("Sorry, there was an error loading this page. (RegistryView)", "alert-error", "#Content"); return; } thisView.loadRegistry.call(thisView); }); return this; } //If the user is not logged in, show the login form else if (!appUserModel.get("loggedIn")){ this.showSignInForm(); return this; } //If the user is logged in and we're using tokens, verify the token first else if(appUserModel.get("loggedIn")){ appUserModel.checkToken(function(){ //If the token is valid, load the registry. var thisView = appView.currentView || appView.registryView; if(!thisView){ appView.showAlert("Sorry, there was an error loading this page. (RegistryView)", "alert-error", "#Content"); return; } thisView.loadRegistry.call(thisView); }, function(){ //If the token if not valid, load the sign in form var thisView = appView.currentView || appView.registryView; if(!thisView){ appView.showAlert("Sorry, there was an error loading this page. (RegistryView)", "alert-error", "#Content"); return; } thisView.showSignInForm.call(thisView); }); return this; } } //If we're not using tokens, load the registry and let the CGI script verify if the user is logged in else this.loadRegistry(); return this; }, /* * Load the registry template from the register-dataset.cgi script in Metacat. */ loadRegistry: function(){ var tokenUrl = appModel.get("tokenUrl"); if(((typeof tokenUrl != "undefined") && tokenUrl.length) && !appUserModel.get("loggedIn")){ this.showSignInForm(); return false; } //Get the registry view var viewRef = this; // look up the url from the main application model this.registryUrl = appModel.get('registryServiceUrl'); var stageParams = ''; if (this.stage) { stageParams = "&stage=" + this.stage + "&pid=" + this.pid; } // load all the registry content so all the js can run in what gets loaded var requestSettings = { type: "POST", xhrFields: { withCredentials: true }, url: this.registryUrl, data: this.registryQueryString + stageParams, success: function(data, textStatus, jqXHR) { viewRef.$el.html(data); //If this is the login page, prepend some header HTML if(data.indexOf('id="RegistryLogin"') != -1) viewRef.$el.prepend(viewRef.loginHeaderTemplate); //Check login one more time viewRef.verifyLoginStatus(); //Add additional form elements viewRef.augementForm(); viewRef.modifyLoginForm(); viewRef.$el.hide(); viewRef.$el.fadeIn('slow', function(){ viewRef.trigger("postRender"); viewRef.createAwardHelpers(); window.onbeforeunload = function(){ viewRef.confirmClose() }; }); //Start showing progress updates viewRef.listenForProgressUpdate(); }, error: function(xhr, textStatus, error){ appView.showAlert("There was an error loading this page. (Details: Error loading register-dataset.cgi: " + textStatus + error + ")", "alert-error", "#Content"); } } $.ajax(_.extend(requestSettings, appUserModel.createAjaxSettings())); }, verifyLoginStatus: function() { // CGI can be logged in, but JSESSIONID has expired var registryEntryForm = $("#RegistryEntryForm"); // if we have the registry form but it doesn't look like we are logged in, force a logout if (registryEntryForm.length && !appUserModel.get('username')) { uiRouter.navigate("signout", {trigger: true}); } }, augementForm: function() { // want to add fields to the form automatically var registryEntryForm = $("#RegistryEntryForm"); var loginForm = $(this.loginEl); // if we have the registry form we can add to it if (registryEntryForm.length) { // pull from the model configuration var formFields = registryModel.get("formFields"); _.each(formFields, function(value, key, list) { // check if it exists yet if (registryEntryForm.find("input[name='" + key + "'][value='" + value +"']").length > 0) { return; } // set in the form registryEntryForm.find("#" + key).attr("value", value); // add to the form addKeyword(); }); // replace keywords with this widget // use configuration from model for the selection var formOptions = registryModel.get("formOptions"); registryEntryForm.find("#keyword").replaceWith(this.template({formOptions: formOptions})); this.watchForTimeOut(); } else if(loginForm.length){ //Enter help items for login inputs var orgLabel = this.$("form div.text-left:contains('Organization')"); if(!orgLabel) return; //Choose unaffiliated as the default, to help the user if($("select[name='organization']").children("option[value='unaffiliated']").length) $("select[name='organization']").val("unaffiliated"); var helpIcon = $(document.createElement("i")) .addClass("tooltip-this icon icon-question-sign") .attr("data-title", "If you signed up for an account here, or you're unsure what to choose, choose 'unaffiliated'") .attr("data-placement", "top") .attr("data-trigger", "hover click"); orgLabel.append(helpIcon); helpIcon.tooltip(); } }, /* * Set up an autocomplete and table for the award numbers field */ createAwardHelpers: function() { var view = this; //Get the award number input element var input = this.$("#funding-visible"); if(!input || !input.length) return; //Add the "add" button var addBtn = $(document.createElement("a")).addClass("btn input-submit").text("Add").prepend(""); input.after(addBtn); //$(addBtn).on("click", this.addAward); //Check if there are award numbers entered into the field right now var currentAwards = $("#funding").val(); if(currentAwards){ //Add these awards to the list _.each(currentAwards.split(","), function(awardId){ //See if there is a title for this award in the award lookup appLookupModel.getGrant(awardId, function(award){ //If a match is found, add it to the list view.addAward(award); }, function(){ //If no match is found, add it to the list without a title view.addAward({ id: awardId }); }); }); } //When the user is done entering a grant number, get the grant title from the API $(input).focusout(function(){ if(appModel.get("grantsUrl")){ //Get the award title and id appLookupModel.getGrant( input.val(), function(award){ //Display this award view.addAward(award); }, function(){ //Display this award as-is view.addAward({ id: input.val() }); } ); } else{ var award = { id: input.val() } if(!award.id) return; view.addAward(award); } }); //Only proceed if we have configured this app with the grants API url if(!appModel.get("grantsUrl")) return; //Add help text when we can do a lookup input.siblings(".input-help-msg").text("Enter an award number or search for an NSF award by keyword."); //Setup the autocomplete widget $(input).hoverAutocomplete({ source: appLookupModel.getGrantAutocomplete, select: function(e, ui) { e.preventDefault(); view.addAward({ title: ui.item.label, id: ui.item.value }); }, position: { my: "left top", at: "left bottom", of: "#funding-visible", collision: "fit" }, appendTo: input.parent(), minLength: 3 }); input.parents(".accordion-body").addClass("ui-autocomplete-container"); }, addAward: function(award){ if(!award.id) return; //Don't add duplicates if($("#funding-list").find("[data-id='" + award.id + "']").length > 0){ //Clear the input $("#funding-visible").val(""); //Display an error msg var helpMsg = $("#funding").siblings(".input-help-msg"), originalMsg = helpMsg.text(); $(helpMsg).addClass('danger').text("That award was already added."); //Remove the message after some time setTimeout(function(){ helpMsg.removeClass('danger').text(originalMsg); }, 2000); return; } //Display this award var title = award.title || (appModel.get("grantsUrl")? "Award name unknown (this award number was not found in the NSF database.)" : null), titleEl = title? $(document.createElement("td")).text(title) : null, numberEl = $(document.createElement("td")).text(award.id), removeEl = $(document.createElement("td")).addClass("cell-icon").append(''), row = $(document.createElement("tr")).append(titleEl, numberEl, removeEl).attr("data-id", award.id).addClass("funding-list-item"); //Style as a warning if we are looking up awards and there is no match if(appModel.get("grantsUrl") && !award.title) row.addClass("warning"); //Add the row $("#funding-list").append(row); //Clear the input and add the new award number to the hidden input this.$("#funding-visible").val(""); if($("#funding").val()){ var ids = this.$("#funding").val().split(","); if(!_.contains(ids, award.id)) $("#funding").val($("#funding").val() + "," + award.id); } else $("#funding").val(award.id); }, removeAward: function(e){ if(!e) return; //Get the remove link that was clicked var removeLink = e.target; if(!removeLink) return; //Get the id of the award that was removed var removeId = $(removeLink).parents("tr").attr("data-id"); //Remove the table row that displays this award $("#funding-list [data-id='" + removeId + "']").remove(); //Remove the award id from the hidden input value var ids = this.$("#funding").val().split(","); this.$("#funding").val(_.without(ids, removeId).toString()); }, addAwardOnEnter: function(e){ if (e.keyCode != 13) return; var award = { id: $("#funding-visible").val() } this.addAward(award); }, modifyLoginForm: function() { // customize the login form to provide external links as needed var ldapAccountTools = $("#ldapAccountTools"); // if we have the login form we can modify it if (ldapAccountTools.length) { var ldapwebServiceUrl = appModel.get('ldapwebServiceUrl') + this.registryQueryString; var templateContent = this.ldapAccountToolsTemplate({ldapwebServiceUrl: ldapwebServiceUrl}); if (templateContent.length) { ldapAccountTools.replaceWith(templateContent); } } }, submitEntryForm: function() { var contentArea = this.$el; var view = this; //We need to use the jQuery plugin jQuery.form so we can submit files in older browsers var requestSettings = { url: this.registryUrl, cache: false, contentType: false, processData: false, type: 'POST', xhrFields: { withCredentials: true }, uploadProgress: function(evt, position, total, percentComplete) { // note incoming total is in KB var byteLabel = "KB"; // Bytes var currentBytes = 0; var totalBytes = 0; if ( total > 0 ) { if ( total > Math.pow(2, 30) ) { // Gigabytes byteLabel = "GB"; currentBytes = position/Math.pow(10, 9); totalBytes = total/Math.pow(10, 9); } else if ( total > Math.pow(2, 20) ) { // Megabytes byteLabel = "MB"; currentBytes = position/Math.pow(10, 6); totalBytes = total/Math.pow(10, 6); } else { // Kilobytes currentBytes = position; totalBytes = total; } } // Format to two significant figures totalBytes = totalBytes.toFixed(2); currentBytes = currentBytes.toFixed(2); var progressHTML = "
"; progressHTML += currentBytes; progressHTML += " "; progressHTML += byteLabel; progressHTML += " of "; progressHTML += totalBytes; progressHTML += " "; progressHTML += byteLabel; progressHTML += "
"; progressHTML += "