commit 8d5f1e62b695672953609b2b59bde3ef5ba8a2b2
parent d7990b0e0354bf054eb88e248ca21bb3ac608add
Author: David Norton <david@nortoncrew.com>
Date: Wed, 9 Aug 2006 11:43:33 +0000
Closes #47, advanced search
Closes #152, Saved Searches (interface layer)
For now, advanced search IS a saved search.
There are still bugs. The 'search' icon is ugly. I wanted to get it out there, however.
Diffstat:
8 files changed, 418 insertions(+), 29 deletions(-)
diff --git a/chrome/chromeFiles/content/scholar/bindings/scholarsearch.xml b/chrome/chromeFiles/content/scholar/bindings/scholarsearch.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0"?>
+<!--
+ Scholar
+ Copyright (C) 2006 Center for History and New Media, George Mason University, Fairfax, VA
+ http://chnm.gmu.edu/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+-->
+<bindings xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <binding id="search-box">
+ <implementation>
+ <field name="searchRef"/>
+ <property name="search" onget="return this.searchRef;">
+ <setter>
+ <![CDATA[
+ this.searchRef = val;
+
+ var conditionsBox = this.id('conditions');
+ while(conditionsBox.hasChildNodes())
+ conditionsBox.removeChild(conditionsBox.firstChild);
+
+ var conditions = this.search.getSearchConditions();
+ if(conditions.length)
+ {
+ for(var i = 0, len = conditions.length; i<len; i++)
+ {
+ this.addCondition(i);
+ }
+ }
+ ]]>
+ </setter>
+ </property>
+ <method name="onAddClicked">
+ <body>
+ <![CDATA[
+ this.search.addCondition("itemType","is","1");
+ this.addCondition();
+ ]]>
+ </body>
+ </method>
+ <method name="addCondition">
+ <body>
+ <![CDATA[
+ var conditionsBox = this.id('conditions');
+ var condition = document.createElement('searchcondition');
+ condition.setAttribute('flex','1');
+
+ conditionsBox.appendChild(condition);
+
+ condition.initWithParentAndConditionID(this, conditionsBox.childNodes.length-1);
+
+ conditionsBox.childNodes[0].id('remove').hidden = (conditionsBox.childNodes.length == 1);
+ ]]>
+ </body>
+ </method>
+ <method name="removeCondition">
+ <parameter name="idx"/>
+ <body>
+ <![CDATA[
+ var conditionsBox = this.id('conditions');
+ this.search.removeCondition(idx);
+ conditionsBox.removeChild(conditionsBox.childNodes[idx]);
+ for(var i = idx, len=conditionsBox.childNodes.length; i < len; i++)
+ conditionsBox.childNodes[i].conditionID = i;
+
+ conditionsBox.childNodes[0].id('remove').hidden = (conditionsBox.childNodes.length == 1);
+ ]]>
+ </body>
+ </method>
+ <method name="id">
+ <parameter name="id"/>
+ <body>
+ <![CDATA[
+ return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <handlers>
+ </handlers>
+ <content>
+ <xul:groupbox xbl:inherits="flex">
+ <xul:caption align="center">
+ <xul:label value="Match"/>
+ <xul:menulist>
+ <xul:menupopup>
+ <xul:menuitem label="Any" value="any"/>
+ <xul:menuitem label="All" value="all"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:label value="of the following:"/>
+ </xul:caption>
+ <xul:vbox id="conditions" style="overflow: auto;"/>
+ </xul:groupbox>
+ </content>
+ </binding>
+
+ <binding id="search-condition">
+ <implementation>
+ <constructor>
+ <![CDATA[
+ this.operators = new Array('is', 'isNot', 'contains', 'doesNotContain');
+ var conditionsList = this.id('conditionsmenu');
+ conditionsList.removeAllItems;
+
+ var conditions = Scholar.SearchConditions.getStandardConditions();
+
+ for(var i=0, len=conditions.length; i<len; i++)
+ conditionsList.appendItem(conditions[i]['name'], conditions[i]['name']);
+ conditionsList.selectedIndex = 0;
+ ]]>
+ </constructor>
+ <method name="onConditionSelected">
+ <body>
+ <![CDATA[
+ var operatorsList = this.id('operatorsmenu');
+
+ var condition = Scholar.SearchConditions.get(this.id('conditionsmenu').value);
+ var operators = condition['operators'];
+
+ var selectThis;
+ for(var i = 0, len = operatorsList.firstChild.childNodes.length; i < len; i++)
+ {
+ var hidden = !operators[operatorsList.firstChild.childNodes[i].value];
+ operatorsList.firstChild.childNodes[i].setAttribute('hidden', hidden);
+ if(selectThis == null && !hidden)
+ selectThis = i;
+ }
+
+ operatorsList.selectedIndex = selectThis;
+ ]]>
+ </body>
+ </method>
+ <field name="operators"/>
+ <field name="dontupdate"/>
+ <field name="parent"/>
+ <field name="conditionID"/>
+ <method name="initWithParentAndConditionID">
+ <parameter name="parent"/>
+ <parameter name="id"/>
+ <body>
+ <![CDATA[
+ this.parent = parent;
+ this.conditionID = id;
+
+ if(this.parent.search)
+ {
+ this.dontupdate = true; //so that the search doesn't get updated while we are creating controls.
+ var condition = this.parent.search.getSearchCondition(this.conditionID);
+
+ this.id('conditionsmenu').value = condition['condition'];
+ this.id('operatorsmenu').value = condition['operator'];
+ this.id('valuefield').value = condition['value'];
+
+ this.dontupdate = false;
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="updateSearch">
+ <body>
+ <![CDATA[
+ if(this.parent && this.parent.search && !this.dontupdate)
+ {
+ var condition = this.id('conditionsmenu').value;
+ var operator = this.id('operatorsmenu').value;
+ var value = this.id('valuefield').value;
+ this.parent.search.updateCondition(this.conditionID, condition, operator, value);
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="onRemoveClicked">
+ <body>
+ <![CDATA[
+ if(this.parent)
+ this.parent.removeCondition(this.conditionID);
+ ]]>
+ </body>
+ </method>
+ <method name="onAddClicked">
+ <body>
+ <![CDATA[
+ if(this.parent)
+ this.parent.onAddClicked();
+ ]]>
+ </body>
+ </method>
+ <method name="id">
+ <parameter name="id"/>
+ <body>
+ <![CDATA[
+ return document.getAnonymousNodes(this)[0].getElementsByAttribute('id',id)[0];
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ <content>
+ <xul:hbox xbl:inherits="flex">
+ <xul:menulist id="conditionsmenu" oncommand="this.parentNode.parentNode.onConditionSelected();">
+ <xul:menupopup/>
+ </xul:menulist>
+ <xul:menulist id="operatorsmenu" oncommand="this.parentNode.parentNode.updateSearch();">
+ <xul:menupopup>
+ <xul:menuitem label="is" value="is"/>
+ <xul:menuitem label="isNot" value="isNot"/>
+ <xul:menuitem label="contains" value="contains"/>
+ <xul:menuitem label="doesNotContain" value="doesNotContain"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:textbox id="valuefield" type="timed" timeout="1000" flex="1" oncommand="this.parentNode.parentNode.updateSearch();"/>
+ <xul:toolbarbutton id="remove" class="clicky" label="-" oncommand="this.parentNode.parentNode.onRemoveClicked();"/>
+ <xul:toolbarbutton id="add" class="clicky" label="+" oncommand="this.parentNode.parentNode.onAddClicked();"/>
+ </xul:hbox>
+ </content>
+ </binding>
+</bindings>
+\ No newline at end of file
diff --git a/chrome/chromeFiles/content/scholar/collectionTreeView.js b/chrome/chromeFiles/content/scholar/collectionTreeView.js
@@ -61,12 +61,45 @@ Scholar.CollectionTreeView.prototype.refresh = function()
var newRows = Scholar.getCollections();
for(var i = 0; i < newRows.length; i++)
- this._showItem(new Scholar.ItemGroup('collection',newRows[i]), 0, this._dataItems.length); //item ref, level, beforeRow
-
+ this._showItem(new Scholar.ItemGroup('collection',newRows[i]), 0, this._dataItems.length); //itemgroup ref, level, beforeRow
+
+ var savedSearches = Scholar.Searches.getAll();
+ for(var i = 0; i < savedSearches.length; i++)
+ {
+ this._showItem(new Scholar.ItemGroup('search',savedSearches[i]), 0, this._dataItems.length); //itemgroup ref, level, beforeRow
+ Scholar.debug(i);
+ }
+
this._refreshHashMap();
}
/*
+ * Redisplay everything
+ */
+Scholar.CollectionTreeView.prototype.reload = function()
+{
+ var openCollections = new Array();
+
+ for(var i = 0; i < this.rowCount; i++)
+ if(this.isContainer(i) && this.isContainerOpen(i))
+ openCollections.push(this._getItemAtRow(i).ref.getID());
+
+ var oldCount = this.rowCount;
+ this._treebox.beginUpdateBatch();
+ this.refresh();
+ this._treebox.rowCountChanged(0,this.rowCount - oldCount);
+
+ for(var i = 0; i < openCollections.length; i++)
+ {
+ var row = this._collectionRowMap[openCollections[i]];
+ if(row != null)
+ this.toggleOpenState(row);
+ }
+ this._treebox.invalidate();
+ this._treebox.endUpdateBatch();
+}
+
+/*
* Called by Scholar.Notifier on any changes to collections in the data layer
*/
Scholar.CollectionTreeView.prototype.notify = function(action, type, ids)
@@ -101,25 +134,7 @@ Scholar.CollectionTreeView.prototype.notify = function(action, type, ids)
}
else if(action == 'move')
{
- var openCollections = new Array();
-
- for(var i = 0; i < this.rowCount; i++)
- if(this.isContainer(i) && this.isContainerOpen(i))
- openCollections.push(this._getItemAtRow(i).ref.getID());
-
- var oldCount = this.rowCount;
- this._treebox.beginUpdateBatch();
- this.refresh();
- this._treebox.rowCountChanged(0,this.rowCount - oldCount);
-
- for(var i = 0; i < openCollections.length; i++)
- {
- var row = this._collectionRowMap[openCollections[i]];
- if(row != null)
- this.toggleOpenState(row);
- }
- this._treebox.invalidate();
- this._treebox.endUpdateBatch();
+ this.reload();
}
else if(action == 'modify')
{
@@ -291,7 +306,11 @@ Scholar.CollectionTreeView.prototype.deleteSelection = function()
for (var i=0; i<rows.length; i++)
{
//erase collection from DB:
- this._getItemAtRow(rows[i]-i).ref.erase();
+ var group = this._getItemAtRow(rows[i]-i);
+ if(group.isCollection())
+ group.ref.erase();
+ else if(group.isSearch())
+ Scholar.Searches.erase(group.ref['id']);
}
this._treebox.endUpdateBatch();
@@ -541,12 +560,19 @@ Scholar.ItemGroup.prototype.isCollection = function()
return this.type == 'collection';
}
+Scholar.ItemGroup.prototype.isSearch = function()
+{
+ return this.type == 'search';
+}
+
Scholar.ItemGroup.prototype.getName = function()
{
if(this.isCollection())
return this.ref.getName();
else if(this.isLibrary())
return Scholar.getString('pane.collections.library');
+ else if(this.isSearch())
+ return this.ref['name'];
else
return "";
}
@@ -563,6 +589,12 @@ Scholar.ItemGroup.prototype.getChildItems = function()
return Scholar.getItems(this.ref.getID());
else if(this.isLibrary())
return Scholar.getItems();
+ else if(this.isSearch())
+ {
+ var s = new Scholar.Search();
+ s.load(this.ref['id']);
+ return Scholar.Items.get(s.search());
+ }
else
return null;
}
diff --git a/chrome/chromeFiles/content/scholar/overlay.js b/chrome/chromeFiles/content/scholar/overlay.js
@@ -33,6 +33,7 @@ var ScholarPane = new function()
this.fullScreen = fullScreen;
this.newItem = newItem;
this.newCollection = newCollection;
+ this.newSearch = newSearch;
this.onCollectionSelected = onCollectionSelected;
this.itemSelected = itemSelected;
this.deleteSelectedItem = deleteSelectedItem;
@@ -167,6 +168,18 @@ var ScholarPane = new function()
Scholar.Collections.add(Scholar.getString('pane.collections.untitled'));
}
+ function newSearch()
+ {
+ var s = new Scholar.Search();
+ s.addCondition('title','contains','');
+
+ var io = {dataIn: {search: s, name: 'Untitled'}, dataOut: null};
+ window.openDialog('chrome://scholar/content/searchDialog.xul','','chrome,modal',io);
+
+ if(io.dataOut)
+ getCollectionsView().reload();
+ }
+
function onCollectionSelected()
{
if(itemsView)
@@ -177,12 +190,12 @@ var ScholarPane = new function()
if(collectionsView.selection.count == 1 && collectionsView.selection.currentIndex != -1)
{
- var collection = collectionsView._getItemAtRow(collectionsView.selection.currentIndex);
- collection.setSearch('');
+ var itemgroup = collectionsView._getItemAtRow(collectionsView.selection.currentIndex);
+ itemgroup.setSearch('');
- itemsView = new Scholar.ItemTreeView(collection);
+ itemsView = new Scholar.ItemTreeView(itemgroup);
document.getElementById('items-tree').view = itemsView;
- document.getElementById('tb-collection-rename').disabled = collection.isLibrary();
+ document.getElementById('tb-collection-rename').disabled = itemgroup.isLibrary();
itemsView.selection.clearSelection();
}
else
@@ -275,9 +288,21 @@ var ScholarPane = new function()
{
var collection = collectionsView._getItemAtRow(collectionsView.selection.currentIndex);
- var newName = prompt(Scholar.getString('pane.collections.rename'),collection.getName());
- if(newName)
- collection.ref.rename(newName);
+ if(collection.isCollection())
+ {
+ var newName = prompt(Scholar.getString('pane.collections.rename'),collection.getName());
+ if(newName)
+ collection.ref.rename(newName);
+ }
+ else
+ {
+ var s = new Scholar.Search();
+ s.load(collection.ref['id']);
+ var io = {dataIn: {search: s, name: collection.getName()}, dataOut: null};
+ window.openDialog('chrome://scholar/content/searchDialog.xul','','chrome,modal',io);
+ if(io.dataOut)
+ onCollectionSelected();
+ }
}
}
diff --git a/chrome/chromeFiles/content/scholar/overlay.xul b/chrome/chromeFiles/content/scholar/overlay.xul
@@ -70,6 +70,7 @@
<toolbar>
<toolbarbutton id="tb-collection-add" tooltiptext="&toolbar.newCollection.label;" command="cmd_scholar_newCollection"/>
<toolbarbutton id="tb-collection-rename" tooltiptext="&toolbar.renameCollection.label;" oncommand="ScholarPane.renameSelectedCollection();" disabled="true"/>
+ <toolbarbutton id="tb-collection-addsearch" label="Add Search" oncommand="ScholarPane.newSearch();"/>
<spacer flex="1"/>
<toolbarbutton id="tb-collection-menu" type="menu">
<menupopup>
diff --git a/chrome/chromeFiles/content/scholar/searchDialog.js b/chrome/chromeFiles/content/scholar/searchDialog.js
@@ -0,0 +1,44 @@
+/*
+ Scholar
+ Copyright (C) 2006 Center for History and New Media, George Mason University, Fairfax, VA
+ http://chnm.gmu.edu/
+ http://chnm.gmu.edu/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+var itemsView;
+var collectionsView;
+var io;
+
+function doLoad()
+{
+ io = window.arguments[0];
+
+ document.getElementById('search-box').search = io.dataIn.search;
+ document.getElementById('search-name').value = io.dataIn.name;
+}
+
+function doUnload()
+{
+
+}
+
+function doAccept()
+{
+ document.getElementById('search-box').search.setName(document.getElementById('search-name').value);
+ document.getElementById('search-box').search.save();
+ io.dataOut = true;
+}
+\ No newline at end of file
diff --git a/chrome/chromeFiles/content/scholar/searchDialog.xul b/chrome/chromeFiles/content/scholar/searchDialog.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<!--
+ Scholar
+ Copyright (C) 2006 Center for History and New Media, George Mason University, Fairfax, VA
+ http://chnm.gmu.edu/
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+-->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://scholar/skin/scholar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://scholar/skin/overlay.css" type="text/css"?>
+<!DOCTYPE window SYSTEM "chrome://scholar/locale/scholar.dtd">
+
+<dialog
+ id="scholar-search-dialog"
+ title="Search"
+ orient="vertical"
+ width="600" flex="1"
+ persist="width height screenX screenY"
+ buttons="cancel,accept"
+ ondialogaccept="doAccept();"
+ onload="doLoad();"
+ onunload="doUnload();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="padding:2em">
+
+ <script src="include.js"/>
+ <script src="searchDialog.js"/>
+
+ <hbox align="center"><label value="Name:"/><textbox id="search-name" flex="1"/></hbox>
+ <scholarsearch id="search-box" flex="1"/>
+</dialog>
diff --git a/chrome/chromeFiles/skin/default/scholar/scholar.css b/chrome/chromeFiles/skin/default/scholar/scholar.css
@@ -63,6 +63,16 @@ seealsobox
-moz-binding: url('chrome://scholar/content/bindings/relatedbox.xml#seealso-box');
}
+scholarsearch
+{
+ -moz-binding: url('chrome://scholar/content/bindings/scholarsearch.xml#search-box');
+}
+
+searchcondition
+{
+ -moz-binding: url('chrome://scholar/content/bindings/scholarsearch.xml#search-condition');
+}
+
.clicky
{
-moz-border-radius: 6px;
diff --git a/chrome/chromeFiles/skin/default/scholar/treesource-search.png b/chrome/chromeFiles/skin/default/scholar/treesource-search.png
Binary files differ.