diff --git a/www/js/Application.js b/www/js/Application.js index 05b2d11e..37862015 100755 --- a/www/js/Application.js +++ b/www/js/Application.js @@ -333,7 +333,16 @@ define(function (require) { // this.checkDBSchema(); }, + checkDBSchema: function () { + // verify we're on the latest DB schema (upgrade if necessary) + return projModel.checkSchema(); + }, + + // ----------- // Routes from AppRouter (router.js) + // ----------- + + // Home page (main view) home: function () { // First, look for projects in the project list that aren't complete; // this can happen if the user clicks the back button before completing the @@ -396,18 +405,13 @@ define(function (require) { } }); }, - - checkDBSchema: function () { - // verify we're on the latest DB schema (upgrade if necessary) - return projModel.checkSchema(); - }, - + // Set UI language view (language can also be set within project settings / edit project view > UI settings) setUILanguage: function () { langView = new HomeViews.UILanguageView(); langView.delegateEvents(); window.Application.main.show(langView); }, - + // Edit project view editProject: function (id) { // edit the selected project var proj = this.ProjectList.where({projectid: id}); @@ -415,7 +419,7 @@ define(function (require) { window.Application.main.show(new ProjectViews.EditProjectView({model: proj[0]})); } }, - + // Copy project view copyProject: function () { var proj = new projModel.Project(); copyProjectView = new ProjectViews.CopyProjectView({model: proj}); @@ -423,7 +427,7 @@ define(function (require) { this.ProjectList.add(proj); this.main.show(copyProjectView); }, - + // New Project view (wizard) newProject: function () { var proj = new projModel.Project(); newProjectView = new ProjectViews.NewProjectView({model: proj}); @@ -431,28 +435,23 @@ define(function (require) { this.ProjectList.add(proj); this.main.show(newProjectView); }, - + // KB editor view editKB: function (id) { console.log("editKB"); - // update the KB and source Phrase lists, then show the KB editor view - $.when(window.Application.kbList.fetch({reset: true, data: {projectid: id}})).done(function () { - var proj = window.Application.ProjectList.where({projectid: id}); - editTUView = new SearchViews.TUListView({model: proj[0]}); - editTUView.delegateEvents(); - window.Application.main.show(editTUView); - }); + // show the KB editor view (KB refresh happens inside the view's onShow()) + var proj = window.Application.ProjectList.where({projectid: id}); + editTUView = new SearchViews.TUListView({model: proj[0]}); + editTUView.delegateEvents(); + window.Application.main.show(editTUView); }, - + // New Target Unit view newTU: function() { console.log("newTU"); - // update the KB list, then show the view - $.when(window.Application.kbList.fetch({reset: true, data: {projectid: window.Application.currentProject.get("projectid")}})).done(function () { - newTransView = new SearchViews.NewTUView(); - newTransView.delegateEvents(); - window.Application.main.show(newTransView); - }); + newTransView = new SearchViews.NewTUView(); + newTransView.delegateEvents(); + window.Application.main.show(newTransView); }, - + // View / edit TU editTU: function (id) { console.log("editTU"); // update the KB list, then show the view @@ -472,7 +471,7 @@ define(function (require) { window.Application.main.show(showTransView); }); }, - + // Show translations (edit TU, but also includes the "current translation" / SP) showTranslations: function (id) { console.log("showTranslations"); // update the KB and source phrase list, then display the Translations screen with the currently-selected sourcephrase @@ -535,43 +534,7 @@ define(function (require) { }); }); }, - - // Another process has sent us a file via URL. Get the File handle and send it along to - // importFileFromURL (below). - processFileEntry: function (fileEntry) { - console.log("processFileEntry: enter"); - fileEntry.file(window.Application.importFileFromURL, window.Application.importFail); - }, - - processError: function (error) { - // log the error and continue processing - console.log("getDirectory error: " + error.code); - alert("error: " + error.code); - }, - - // This is similar to importBooks, EXCEPT that another process is sending a file to us to - // open/import (rather than the user picking a file out of a list). Call - // ImportDocumentView::importFile() to import the file. - importFileFromURL: function (file) { - console.log("importFile: enter"); - var proj = window.Application.currentProject; - if (proj !== null) { - // We have a project -- load the ImportDocumentView to do the work - importDocView = new DocumentViews.ImportDocumentView({model: proj}); - importDocView.isLoadingFromURL = true; - importDocView.delegateEvents(); - window.Application.main.show(importDocView); - // call ImportDocumentView::importFromURL() to import the file - importDocView.importFromURL(file, proj); - } else { - alert("No current project defined -- ignoring open() call"); - } - }, - - importFail: function () { - alert("Unable to open file."); - }, - + // import doc view importBooks: function (id) { console.log("importBooks"); // update the book and chapter lists, then show the import docs view @@ -583,7 +546,7 @@ define(function (require) { window.Application.main.show(importDocView); } }, - + // Export doc view exportBooks: function (id) { console.log("exportBooks"); var proj = window.Application.currentProject; @@ -595,7 +558,7 @@ define(function (require) { window.Application.main.show(exportDocView); } }, - + // Search / browse chapter view lookupChapter: function (id) { console.log("lookupChapter"); $.when(window.Application.ProjectList.fetch({reset: true, data: {name: ""}})).done(function () { @@ -610,7 +573,7 @@ define(function (require) { }); }); }, - + // Adapt View (the reason we're here) adaptChapter: function (id) { console.log("adaptChapter"); // refresh the models @@ -649,6 +612,44 @@ define(function (require) { } }); }); + }, + // ---- + // External document route helper methods: + // Another process has sent us a file via URL. + // ---- + // Helper method to get the File handle from the external process and send it along to + // importFileFromURL (below). + processFileEntry: function (fileEntry) { + console.log("processFileEntry: enter"); + fileEntry.file(window.Application.importFileFromURL, window.Application.importFail); + }, + // helper callback to process a getDirectory() error + processError: function (error) { + // log the error and continue processing + console.log("getDirectory error: " + error.code); + alert("error: " + error.code); + }, + // This is similar to importBooks, EXCEPT that another process is sending a file to us to + // open/import (rather than the user picking a file out of a list). Call + // ImportDocumentView::importFile() to import the file. + importFileFromURL: function (file) { + console.log("importFile: enter"); + var proj = window.Application.currentProject; + if (proj !== null) { + // We have a project -- load the ImportDocumentView to do the work + importDocView = new DocumentViews.ImportDocumentView({model: proj}); + importDocView.isLoadingFromURL = true; + importDocView.delegateEvents(); + window.Application.main.show(importDocView); + // call ImportDocumentView::importFromURL() to import the file + importDocView.importFromURL(file, proj); + } else { + alert("No current project defined -- ignoring open() call"); + } + }, + // Helper callback for processFileEntry() failure (above) + importFail: function () { + alert("Unable to open file."); } }); diff --git a/www/js/models/sql/targetunit.js b/www/js/models/sql/targetunit.js index 778b2ef1..6fea3d2a 100644 --- a/www/js/models/sql/targetunit.js +++ b/www/js/models/sql/targetunit.js @@ -127,6 +127,7 @@ define(function (require) { TargetUnitCollection = Backbone.Collection.extend({ model: TargetUnit, + page_size: 0, resetFromDB: function () { var i = 0, @@ -226,6 +227,34 @@ define(function (require) { return deferred.promise(); }, + // Return the count (only) of items in the targetunit table matching the specified projectid and isGloss setting, + // and optionally a source filter + getCount: function(options) { + var deferred = $.Deferred(); + var source = ""; + var retValue = null; + var sql = "SELECT COUNT(*) as tot from targetunit WHERE projectid='" + options.data.projectid + "' AND isGloss=" + options.data.isGloss; + if (options.data.hasOwnProperty('source')) { + source = options.data.source; + if (source.length > 0) { + sql += " AND source LIKE '%" + source + "%'" + } + } + sql += ";"; + window.Application.db.transaction(function (tx) { + tx.executeSql(sql, [], function (tx, res) { + retValue = res.rows.item(0).tot; + console.log("SELECT ok: " + retValue + " targetunit items"); + deferred.resolve(retValue); + }, function (tx, err) { + console.log("SELECT COUNT(*) error: " + err.message); + }); + }, function (e) { + deferred.reject(e); + }); + return deferred.promise(); + }, + // UPDATE an array of TargetUnit objects updateBatch: function (models) { var deferred = $.Deferred(); @@ -261,6 +290,7 @@ define(function (require) { var deferred = $.Deferred(); var len = 0; var i = 0; + var source = ""; var projectid = null; var retValue = null; var results = null; @@ -270,44 +300,58 @@ define(function (require) { options.success(data); }); } else if (options.data.hasOwnProperty('projectid')) { - projectid = options.data.projectid; - results = targetunits.filter(function (element) { - return element.attributes.projectid === projectid.toLowerCase(); - }); - if (results.length === 0) { - // not in collection -- retrieve them from the db (alphabetized) - window.Application.db.transaction(function (tx) { - tx.executeSql("SELECT * from targetunit WHERE projectid=? ORDER BY source;", [projectid], function (tx, res) { - var tmpString = ""; - for (i = 0, len = res.rows.length; i < len; ++i) { - // add the chapter - var tu = new TargetUnit(); - tu.off("change"); - tu.set(res.rows.item(i)); - // convert refstring back into an array object - tmpString = tu.get('refstring'); - tu.set('refstring', JSON.parse(tmpString)); - targetunits.push(tu); - tu.on("change", tu.save, tu); - } - console.log("SELECT ok: " + res.rows.length + " targetunit items"); - retValue = targetunits; - options.success(retValue); - deferred.resolve(retValue); - }); - }, function (e) { - options.error(); - deferred.reject(e); - }); - } else { - // results already in collection -- return them - options.success(results); - deferred.resolve(results); + // get data in a specified projectid + projectid = options.data.projectid; + targetunits.length = 0; // reset the collection + // build the sql statement + var sql = "SELECT * from targetunit WHERE projectid='" + projectid + "'"; + if (options.data.hasOwnProperty('isGloss')) { + sql += " AND isGloss=" + options.data.isGloss; + } + if (options.data.hasOwnProperty('source')) { + if (options.data.source.length > 0) { + sql += " AND source LIKE '%" + options.data.source + "%'"; + } + } + sql += " ORDER BY source"; + if (options.data.hasOwnProperty('limit')) { + sql += " LIMIT " + options.data.limit; + if (options.data.hasOwnProperty('offset')) { + sql += " OFFSET " + options.data.offset; + } } + sql += ";"; + // retrieve them from the db (ordered by source) + window.Application.db.transaction(function (tx) { + tx.executeSql(sql, [], function (tx, res) { + var tmpString = ""; + for (i = 0, len = res.rows.length; i < len; ++i) { + // add the chapter + var tu = new TargetUnit(); + tu.off("change"); + tu.set(res.rows.item(i)); + // convert refstring back into an array object + tmpString = tu.get('refstring'); + tu.set('refstring', JSON.parse(tmpString)); + targetunits.push(tu); + tu.on("change", tu.save, tu); + } + console.log("SELECT ok: " + res.rows.length + " targetunit items"); + retValue = targetunits; + options.success(retValue); + deferred.resolve(retValue); + }, function (tx, err) { + console.log("TargetUnit SELECT error: " + err.message); + }); + }, function (e) { + options.error(); + deferred.reject(e); + }); + // return the promise return deferred.promise(); } else if (options.data.hasOwnProperty('source')) { - var source = options.data.source; + source = options.data.source; results = targetunits.filter(function (element) { return element.attributes.source.toLowerCase().indexOf(source.toLowerCase()) > -1; }); diff --git a/www/js/views/SearchViews.js b/www/js/views/SearchViews.js index 057ef381..b00db4ee 100644 --- a/www/js/views/SearchViews.js +++ b/www/js/views/SearchViews.js @@ -34,7 +34,9 @@ define(function (require) { caseSource = [], caseTarget = [], refstrings = [], - PAGE_SIZE = 100, // arbitrary # of search results to display at once + PAGE_SIZE = 100, // arbitrary # of search results to display at once + nKBTotal = 0, + nFilteredTotal = 0, //////// // STATIC METHODS @@ -100,6 +102,33 @@ define(function (require) { } }, + // Helper method that returns the
  • elements shown in the KB list editor + buildTUList = function (coll) { + var strResult = "", + rs = null; + coll.comparator = 'source'; + coll.sort(); + coll.each(function (model, index) { + // TODO: what to do with placeholder text? Currently filtered out here + if (model.get("source").length > 0) { + strResult += "
  • " + model.get("source") + "
    "; + rs = model.get("refstring"); + if (rs.length > 1) { + // multiple translations - give a count + strResult += i18next.t("view.ttlTotalTranslations", {total: rs.length}); + } else if (rs.length === 1) { + // exactly 1 translation - just display it + strResult += rs[0].target; + } else { + // no translations (shouldn't happen) + strResult += i18next.t("view.ttlNoTranslations"); + } + strResult += "
  • "; + } + }); + return strResult; + }, + // Helper method to find all items with .chk-selected UI class and delete their associated books/chapters // (also does some project cleanup if the current / all books are deleted) deleteSelectedDocs = function () { @@ -165,11 +194,14 @@ define(function (require) { TUListView = Marionette.ItemView.extend({ template: Handlebars.compile(tplTUList), + searchCursor: 0, initialize: function () { this.render(); }, events: { "input #search": "search", + "click #SearchPrev": "onSearchPrevPage", + "click #SearchNext": "onSearchNextPage", "click .big-link": "onClickTU", "click #btnNewTU": "onClickNewTU" }, @@ -186,110 +218,155 @@ define(function (require) { console.log("onClickNewTU - entry"); window.Application.router.navigate("tu", {trigger: true}); }, - + // User clicked on the Previous button - retrieve the previous page of TU items + onSearchPrevPage: function () { + console.log("onSearchPrevPage: entry"); + searchCursor = searchCursor - PAGE_SIZE; + // shouldn't happen, but just in case + if (searchCursor < 0) { + searchCursor = 0; + } + var key = $('#search').val().trim(), + self = this, + total = 0, + lstTU = ""; + if (key.length > 0) { + // filtered total + total = nFilteredTotal; + } else { + // unfiltered total + total = nKBTotal; + } + $.when(window.Application.kbList.fetch({reset: true, data: {projectid: window.Application.currentProject.get('projectid'), isGloss: 0, source: key, limit: PAGE_SIZE, offset: this.searchCursor}})).done(function () { + // fetch the previous page, then display the results + lstTU = buildTUList(window.Application.kbList); + $("#lstTU").html(lstTU); + if (self.searchCursor > 0) { + // User can go back + $("#SearchPrev").prop("disabled", false); + } else { + // User can't go back + $("#SearchPrev").prop("disabled", true); + } + if (total > (self.searchCursor + PAGE_SIZE)) { + // more than 1 page of results forward - enable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: self.searchCursor, pgEnd: (self.searchCursor + PAGE_SIZE), total: total})); + $("#SearchNext").prop("disabled", false); + } else { + // <= 1 page of results left -- disable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: self.searchCursor, pgEnd: total, total: total})); + $("#SearchNext").prop("disabled", true); + } + }); + }, + // User clicked the Next button -- retrieve the next page of TU items + onSearchNextPage: function () { + console.log("onSearchNextPage: entry"); + searchCursor = searchCursor + PAGE_SIZE; + var key = $('#search').val().trim(), + self = this, + total = 0, + lstTU = ""; + if (key.length > 0) { + // filtered total + total = nFilteredTotal; + } else { + // unfiltered total + total = nKBTotal; + } + $.when(window.Application.kbList.fetch({reset: true, data: {projectid: window.Application.currentProject.get('projectid'), isGloss: 0, source: key, limit: PAGE_SIZE, offset: this.searchCursor}})).done(function () { + // fetch the previous page, then display the results + lstTU = buildTUList(window.Application.kbList); + $("#lstTU").html(lstTU); + if (self.searchCursor > 0) { + // User can go back + $("#SearchPrev").prop("disabled", false); + } else { + // User can't go back + $("#SearchPrev").prop("disabled", true); + } + if (total > (self.searchCursor + PAGE_SIZE)) { + // more than 1 page of results forward - enable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: self.searchCursor, pgEnd: (self.searchCursor + PAGE_SIZE), total: total})); + $("#SearchNext").prop("disabled", false); + } else { + // <= 1 page of results left -- disable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: self.searchCursor, pgEnd: total, total: total})); + $("#SearchNext").prop("disabled", true); + } + }); + }, search: function (event) { - var lstTU = ""; - var rs = null; if (event.keycode === 13) { // enter key pressed event.preventDefault(); } - this.TUList = window.Application.kbList; + this.searchCursor = 0; // reset to page 1 of search results var key = $('#search').val().trim(); - console.log("searching KB for: " + key); - // filter based on search text + console.log("search: looking for pattern: " + key); + // Get the count of items matching this filter + $.when(window.Application.kbList.getCount({data: {projectid: window.Application.currentProject.get('projectid'), isGloss: "0", source: key}})).done(function (n) { + console.log("search: filtered total KB entries = " + n); + nFilteredTotal = n; // store the total count in a static + }); $("#lstTU").html(""); - var self = this, - lstTU = "", - rs = null; - this.TUList.fetch({data: {source: key}}).done( function() { - console.log("fetch returns - element count: " + self.TUList.length); - self.TUList.comparator = 'source'; - self.TUList.sort(); - self.TUList.each(function (model, index) { - // TODO: what to do with placeholder text? Currently filtered out here - if (model.get("source").length > 0) { - lstTU += "
  • " + model.get("source") + "
    "; - rs = model.get("refstring"); - if (rs.length > 1) { - // multiple translations - give a count - lstTU += i18next.t("view.ttlTotalTranslations", {total: rs.length}); - } else if (rs.length === 1) { - // exactly 1 translation - just display it - lstTU += rs[0].target; - } else { - // no translations (shouldn't happen) - lstTU += i18next.t("view.ttlNoTranslations"); - } - lstTU += "
  • "; - } - }); - if (self.TUList.length === 0) { + var lstTU = ""; + // Get the first page of filtered items + $.when(window.Application.kbList.fetch({reset: true, data: {projectid: window.Application.currentProject.get("projectid"), isGloss: 0, limit: 100}})).done(function () { + lstTU = buildTUList(window.Application.kbList); + $("#lstTU").html(lstTU); + // nFilteredTotal from our getCount above + if (nFilteredTotal === 0) { $("#lblTotals").html(i18next.t("view.lblNoEntries")); $("#SearchPrev").prop("disabled", true); $("#SearchNext").prop("disabled", true); } else { - $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: 1, pgEnd: 100, total: self.TUList.length})); $("#SearchPrev").prop("disabled", true); - if (self.TUList.length > PAGE_SIZE) { + if (nFilteredTotal > PAGE_SIZE) { // more than 1 page of results - enable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: 1, pgEnd: PAGE_SIZE, total: nFilteredTotal})); $("#SearchNext").prop("disabled", false); } else { + // <= 1 page -- disable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: 1, pgEnd: nFilteredTotal, total: nFilteredTotal})); $("#SearchNext").prop("disabled", true); } - $("#SearchNext").prop("disabled", true); } - $("#lstTU").html(lstTU); - }); }, onShow: function () { var lstTU = ""; - var rs = null; - var strInfo = ""; - this.TUList = window.Application.kbList; - console.log("onShow: this.TUList.length = " + this.TUList.length); - // initial sort - name - this.TUList.comparator = 'source'; - this.TUList.sort(); - this.TUList.each(function (model, index) { - // TODO: what to do with placeholder text? Currently filtered out here - if (model.get("source").length > 0) { - lstTU += "
  • " + model.get("source") + "
    "; - rs = model.get("refstring"); - if (rs.length > 1) { - // multiple translations - give a count - lstTU += i18next.t("view.ttlTotalTranslations", {total: rs.length}); - } else if (rs.length === 1) { - // exactly 1 translation - just display it - lstTU += rs[0].target; + this.searchCursor = 0; + // total KB count (non-gloss) for this project + $.when(window.Application.kbList.getCount({data: {projectid: window.Application.currentProject.get('projectid'), isGloss: "0"}})).done(function (n) { + console.log("onShow: total KB entries for project (non-gloss) = " + n); + nKBTotal = n; // store the total count in a static + var strInfo = i18next.t("view.ttlProjectName", {name: window.Application.currentProject.get("name")}) + "
    " + i18next.t("view.ttlTotalEntries", {total: n}); + $("#lblProjInfo").html(strInfo); + }); + // retrieve and display first page of (unfiltered) TU entries + $.when(window.Application.kbList.fetch({reset: true, data: {projectid: window.Application.currentProject.get("projectid"), isGloss: 0, limit: 100}})).done(function () { + lstTU = buildTUList(window.Application.kbList); + $("#lstTU").html(lstTU); + // are there any entries in the KB? + if (window.Application.kbList.length === 0) { + // KB is empty - tell the user and disable the prev/next buttons + $("#lblTotals").html(i18next.t("view.lblNoEntries")); + $("#SearchPrev").prop("disabled", true); + $("#SearchNext").prop("disabled", true); + } else { + // some items to display -- give totals and enable UI + $("#SearchPrev").prop("disabled", true); + if (nKBTotal > PAGE_SIZE) { + // more than 1 page of results - enable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: 1, pgEnd: PAGE_SIZE, total: nKBTotal})); + $("#SearchNext").prop("disabled", false); } else { - // no translations (shouldn't happen) - lstTU += i18next.t("view.ttlNoTranslations"); + // <= 1 page of results -- disable next button + $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: 1, pgEnd: nKBTotal, total: nKBTotal})); + $("#SearchNext").prop("disabled", true); } - lstTU += "
  • "; } }); - $("#lstTU").html(lstTU); - strInfo = i18next.t("view.ttlProjectName", {name: window.Application.currentProject.get("name")}) + "
    " + i18next.t("view.ttlTotalEntries", {total: this.TUList.length}); - $("#lblProjInfo").html(strInfo); - // are there any entries in the KB? - if (this.TUList.length === 0) { - // KB is empty - tell the user and disable the prev/next buttons - $("#lblTotals").html(i18next.t("view.lblNoEntries")); - $("#SearchPrev").prop("disabled", true); - $("#SearchNext").prop("disabled", true); - } else { - // some items to display -- give totals and enable UI - $("#lblTotals").html(i18next.t("view.lblRange", {pgStart: 1, pgEnd: PAGE_SIZE, total: this.TUList.length})); - $("#SearchPrev").prop("disabled", true); - if (this.TUList.length > PAGE_SIZE) { - // more than 1 page of results - enable next button - $("#SearchNext").prop("disabled", false); - } else { - $("#SearchNext").prop("disabled", true); - } - $("#SearchNext").prop("disabled", true); - } } }), diff --git a/www/tpl/TargetUnitList.html b/www/tpl/TargetUnitList.html index db137a21..57563ece 100644 --- a/www/tpl/TargetUnitList.html +++ b/www/tpl/TargetUnitList.html @@ -22,9 +22,9 @@