svn commit: r1776930 [4/19] - in /ofbiz/trunk/specialpurpose: lucene/ lucene/src/main/java/org/apache/ofbiz/content/search/ solr/ solr/src/main/java/org/apache/ofbiz/solr/webapp/ solr/webapp/solr/ solr/webapp/solr/WEB-INF/ solr/webapp/solr/css/ solr/we...

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r1776930 [4/19] - in /ofbiz/trunk/specialpurpose: lucene/ lucene/src/main/java/org/apache/ofbiz/content/search/ solr/ solr/src/main/java/org/apache/ofbiz/solr/webapp/ solr/webapp/solr/ solr/webapp/solr/WEB-INF/ solr/webapp/solr/css/ solr/we...

shijh
Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/schema.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/schema.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/schema.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/schema.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,611 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+var cookie_schema_browser_autoload = 'schema-browser_autoload';
+
+solrAdminApp.controller('SchemaController',
+    function($scope, $routeParams, $location, $cookies, $timeout, Luke, Constants, Schema, Config) {
+        $scope.resetMenu("schema", Constants.IS_COLLECTION_PAGE);
+
+        $scope.refresh = function () {
+            Luke.schema({core: $routeParams.core}, function (schema) {
+                Luke.raw({core: $routeParams.core}, function (index) {
+                    var data = mergeIndexAndSchemaData(index, schema.schema);
+
+                    $scope.fieldsAndTypes = getFieldsAndTypes(data);
+                    $scope.is = {};
+
+                    var search = $location.search();
+                    leftbar = {};
+                    $scope.isField = $scope.isDynamicField = $scope.isType = false;
+                    $scope.showing = true;
+                    if (search.field) {
+                        $scope.selectedType = "Field";
+                        $scope.is.field = true;
+                        $scope.name = search.field;
+                        leftbar.fields = [$scope.name];
+                        var field = data.fields[$scope.name];
+                        leftbar.types = [field.type];
+                        if (field.dynamicBase) leftbar.dynamicFields = [field.dynamicBase];
+                        if (field.copySources && field.copySources.length>0) {
+                            leftbar.copyFieldSources = sortedObjectArray(field.copySources.sort());
+                        }
+                        if (field.copyDests && field.copyDests.length>0) {
+                            leftbar.copyFieldDests = sortedObjectArray(field.copyDests.sort());
+                        }
+                        $scope.fieldOrType = "field=" + $scope.name;
+                    } else if (search["dynamic-field"]) {
+                        $scope.selectedType = "Dynamic Field";
+                        $scope.is.dynamicField = true;
+                        $scope.name = search["dynamic-field"];
+                        leftbar.dynamicFields = [$scope.name];
+                        leftbar.types = [data.dynamic_fields[$scope.name].type];
+                        $scope.fieldOrType = "dynamic-field=" + $scope.name;
+                    } else if (search.type) {
+                        $scope.selectedType = "Type";
+                        $scope.is.type = true;
+                        $scope.name = search.type;
+                        leftbar.types = [$scope.name];
+                        leftbar.fields = filterFields("fields", data, $scope.name);
+                        leftbar.dynamicFields = filterFields("dynamic_fields", data, $scope.name);
+                        $scope.fieldOrType = "type=" + $scope.name;
+                    } else {
+                        $scope.showing = false;
+                    }
+                    $scope.leftbar = leftbar;
+                    $scope.core = $routeParams.core;
+                    $scope.defaultSearchField = data.default_search_field;
+                    $scope.uniqueKeyField = data.unique_key_field;
+                    $scope.isDefaultSearchField = ($scope.selectedType == "Field" && $scope.name == $scope.defaultSearchField);
+                    $scope.isUniqueKeyField = ($scope.selectedType == "Field" && $scope.name == $scope.uniqueKeyField);
+
+                    $scope.display = getFieldProperties(data, $routeParams.core, $scope.is, $scope.name);
+                    $scope.analysis = getAnalysisInfo(data, $scope.is, $scope.name);
+
+                    $scope.isAutoload = $cookies[cookie_schema_browser_autoload] == "true";
+                    if ($scope.isAutoload) {
+                        $scope.toggleTerms();
+                    }
+
+                    $scope.types = Object.keys(schema.schema.types);
+                });
+            });
+            Config.get({core: $routeParams.core}, function(data) {
+                $scope.isSchemaUpdatable = (data.config.hasOwnProperty('schemaFactory') == false || data.config.schemaFactory.class == "ManagedIndexSchemaFactory");
+            });
+        };
+        $scope.refresh();
+
+        $scope.selectFieldOrType = function() {
+            $location.search($scope.fieldOrType);
+        }
+
+        $scope.toggleAnalyzer = function(analyzer) {
+            analyzer.show = !analyzer.show;
+        }
+
+        $scope.loadTermInfo = function() {
+            var params = {fl: $scope.name, core: $routeParams.core};
+            if ($scope.topTermsCount) {
+                params.numTerms = $scope.topTermsCount;
+            }
+            $scope.isLoadingTerms = true;
+            Luke.field(params, function (data) {
+                $scope.isLoadingTerms = false;
+                $scope.termInfo = getTermInfo(data.fields[$scope.name]);
+                if (!$scope.topTermsCount) {
+                    $scope.topTermsCount = $scope.termInfo.termCount;
+                }
+            });
+        }
+
+        $scope.toggleTerms = function() {
+            $scope.showTerms = !$scope.showTerms;
+
+            if ($scope.showTerms) {
+                $scope.loadTermInfo();
+            }
+        }
+
+        $scope.loadAllTerms = function() {
+            $scope.topTermsCount = $scope.termInfo.maxTerms;
+            $scope.loadTermInfo();
+        }
+
+        $scope.toggleAutoload = function() {
+            $scope.isAutoload = !$scope.isAutoload;
+            $cookies[cookie_schema_browser_autoload] = $scope.isAutoload;
+            console.log("cookie: " + $cookies[cookie_schema_browser_autoload]);
+        }
+
+        $scope.hideAll = function() {
+            $scope.showAddField = false;
+            $scope.showAddDynamicField = false;
+            $scope.showAddCopyField = false;
+        }
+
+        $scope.toggleAddField = function() {
+            if ($scope.showAddField && $scope.adding == "field") {
+                $scope.hideAll();
+            } else {
+                $scope.hideAll();
+                $scope.showAddField = true;
+                $scope.adding = "field";
+
+                $scope.newField = {
+                    stored: "true",
+                    indexed: "true"
+                }
+                delete $scope.addErrors;
+            }
+        }
+
+        $scope.addField = function() {
+            delete $scope.addErrors;
+            var data = {"add-field": $scope.newField};
+            Schema.post({core: $routeParams.core}, data, function(data) {
+                if (data.errors) {
+                    $scope.addErrors = data.errors[0].errorMessages;
+                    if (typeof $scope.addErrors === "string") {
+                        $scope.addErrors = [$scope.addErrors];
+                    }
+                } else {
+                    $scope.added = true;
+                    $timeout(function() {
+                        $scope.showAddField = false;
+                        $scope.added = false;
+                        $scope.refresh();
+                    }, 1500);
+                }
+            });
+        }
+
+        $scope.toggleAddDynamicField = function() {
+            if ($scope.showAddField && $scope.adding == "dynamicField") {
+                $scope.hideAll();
+            } else {
+                $scope.hideAll();
+                $scope.showAddField = true;
+                $scope.adding = "dynamicField";
+
+                $scope.newField = {
+                    stored: "true",
+                    indexed: "true"
+                }
+                delete $scope.addErrors;
+            }
+        }
+
+        $scope.addDynamicField = function() {
+            delete $scope.addErrors;
+            var data = {"add-dynamic-field": $scope.newField};
+            Schema.post({core: $routeParams.core}, data, function(data) {
+                if (data.errors) {
+                    $scope.addErrors = data.errors[0].errorMessages;
+                    if (typeof $scope.addErrors === "string") {
+                        $scope.addErrors = [$scope.addErrors];
+                    }
+                } else {
+                    $scope.added = true;
+                    $timeout(function() {
+                        $scope.showAddField = false;
+                        $scope.added = false;
+                        $scope.refresh();
+                    }, 1500);
+                }
+            });
+        }
+
+        $scope.toggleAddCopyField = function() {
+            if ($scope.showAddCopyField) {
+                $scope.hideAll();
+            } else {
+                $scope.hideAll();
+                $scope.showAddCopyField = true;
+
+                $scope.copyField = {};
+                delete $scope.addCopyFieldErrors;
+            }
+        }
+        $scope.addCopyField = function() {
+            delete $scope.addCopyFieldErrors;
+            var data = {"add-copy-field": $scope.copyField};
+            Schema.post({core: $routeParams.core}, data, function(data) {
+                if (data.errors) {
+                    $scope.addCopyFieldErrors = data.errors[0].errorMessages;
+                    if (typeof $scope.addCopyFieldErrors === "string") {
+                        $scope.addCopyFieldErrors = [$scope.addCopyFieldErrors];
+                    }
+                } else {
+                    $scope.showAddCopyField = false;
+                    $timeout($scope.refresh, 1500);
+                }
+            });
+        }
+
+        $scope.toggleDelete = function() {
+            if ($scope.showDelete) {
+                $scope.showDelete = false;
+            } else {
+                if ($scope.is.field) {
+                    $scope.deleteData = {'delete-field': {name: $scope.name}};
+                } else if ($scope.is.dynamicField) {
+                    $scope.deleteData = {'delete-dynamic-field': {name: $scope.name}};
+                } else {
+                    alert("TYPE NOT KNOWN");
+                }
+                $scope.showDelete = true;
+            }
+        }
+
+        $scope.delete = function() {
+            Schema.post({core: $routeParams.core}, $scope.deleteData, function(data) {
+               if (data.errors) {
+                   $scope.deleteErrors = data.errors[0].errorMessages;
+                   if (typeof $scope.deleteErrors === "string") {
+                       $scope.deleteErrors = [$scope.deleteErrors];
+                   }
+               } else {
+                   $scope.deleted = true;
+                   $timeout(function() {
+                       $location.search("");
+                     }, 1500
+                   );
+               }
+            });
+        }
+        $scope.toggleDeleteCopyField = function(field) {
+            field.show = !field.show;
+            delete field.errors;
+        }
+        $scope.deleteCopyField = function(field, source, dest) {
+            data = {'delete-copy-field': {source: source, dest: dest}};
+            Schema.post({core: $routeParams.core}, data, function(data) {
+               if (data.errors) {
+                   field.errors = data.errors[0].errorMessages;
+                   if (typeof $scope.deleteErrors === "string") {
+                       field.errors = [field.errors];
+                   }
+               } else {
+                   field.deleted = true;
+                   $timeout($scope.refresh, 1500);
+               }
+            });
+        }
+    }
+);
+
+var getFieldsAndTypes = function(data) {
+    var fieldsAndTypes = [];
+    var fields = Object.keys(data.fields).sort();
+    for (var i in fields) {
+        fieldsAndTypes.push({
+            group: "Fields",
+            value: "field=" + fields[i],
+            label: fields[i]
+        });
+    }
+    var dynamic_fields = Object.keys(data.dynamic_fields).sort();
+    for (var i in dynamic_fields) {
+        fieldsAndTypes.push({
+            group: "Dynamic Fields",
+            value: "dynamic-field=" + dynamic_fields[i],
+            label: dynamic_fields[i]
+        });
+    }
+    var types = Object.keys(data.types).sort();
+    for (var i in types) {
+        fieldsAndTypes.push({
+            group: "Types",
+            value: "type=" + types[i],
+            label: types[i]
+        });
+    }
+    return fieldsAndTypes;
+};
+
+var filterFields = function(type, data, name) {
+    var fields = [];
+    for (var i in data.types[name].fields) {
+        var field = data.types[name].fields[i];
+        if (data[type][field]) {
+            fields.push(field)
+        }
+    }
+    return fields.sort();
+}
+
+var mergeIndexAndSchemaData = function(index, schema) {
+
+    var data = {
+        default_search_field: null,
+        unique_key_field: null,
+        key: {},
+        fields: {},
+        dynamic_fields: {},
+        types: {},
+        relations: {
+            f_df: {},
+            f_t: {},
+            df_f: {},
+            df_t: {},
+            t_f: {},
+            t_df: {}
+        }
+    };
+
+    data.fields = index.fields;
+
+    data.key = index.info.key;
+
+    data.default_search_field = schema.defaultSearchField;
+    data.unique_key_field = schema.uniqueKeyField;
+
+    data.dynamic_fields = schema.dynamicFields;
+    data.types = schema.types;
+
+    for (var field in schema.fields) {
+        data.fields[field] =
+            $.extend({}, data.fields[field], schema.fields[field]);
+    }
+
+    for (var field in data.fields) {
+        var copy_dests = data.fields[field].copyDests;
+        for (var i in copy_dests) {
+            var copy_dest = copy_dests[i];
+            if (!data.fields[copy_dest]) {
+                data.fields[copy_dest] = {
+                    partial: true,
+                    copySources: []
+                };
+            }
+
+            if (data.fields[copy_dest].partial) {
+                data.fields[copy_dest].copySources.push(field);
+            }
+        }
+
+        var copy_sources = data.fields[field].copySources;
+        for (var i in copy_sources) {
+            var copy_source = copy_sources[i];
+            if (!data.fields[copy_source]) {
+                data.fields[copy_source] = {
+                    partial: true,
+                    copyDests: []
+                };
+            }
+
+            if (data.fields[copy_source].partial) {
+                data.fields[copy_source].copyDests.push(field);
+            }
+        }
+
+        data.relations.f_t[field] = data.fields[field].type;
+
+        if (!data.relations.t_f[data.fields[field].type]) {
+            data.relations.t_f[data.fields[field].type] = [];
+        }
+        data.relations.t_f[data.fields[field].type].push(field);
+
+        if (data.fields[field].dynamicBase) {
+            data.relations.f_df[field] = data.fields[field].dynamicBase;
+
+            if (!data.relations.df_f[data.fields[field].dynamicBase]) {
+                data.relations.df_f[data.fields[field].dynamicBase] = [];
+            }
+            data.relations.df_f[data.fields[field].dynamicBase].push(field);
+        }
+    }
+
+    for (var dynamic_field in data.dynamic_fields) {
+        data.relations.df_t[dynamic_field] = data.dynamic_fields[dynamic_field].type;
+
+        if (!data.relations.t_df[data.dynamic_fields[dynamic_field].type]) {
+            data.relations.t_df[data.dynamic_fields[dynamic_field].type] = [];
+        }
+        data.relations.t_df[data.dynamic_fields[dynamic_field].type].push(dynamic_field);
+    }
+    return data;
+};
+
+var getFieldProperties = function(data, core, is, field) {
+
+    var display = {};
+
+    display.partialState = is.field && !!data.fields[field].partial;
+
+    display.columns = [];
+    display.rows = [];
+    var allFlags = "";
+
+    var addRow = function(name, flags) {
+        if (flags[0]!='(') {
+            display.rows.push({name:name, flags:flags});
+            for (var i in flags) {
+                if (flags[i]!="-" && allFlags.indexOf(flags[i])<0) {
+                    allFlags+=flags[i];
+                }
+            }
+        } else {
+            display.rows.push({name:name, comment:flags});
+        }
+    }
+
+    // Identify the rows for our field property table
+    if (is.field && data.fields[field]) {
+        if (data.fields[field].flags) {
+            addRow('Properties', data.fields[field].flags);
+        }
+        if (data.fields[field].schema) {
+            addRow('Schema', data.fields[field].schema);
+        }
+        if (data.fields[field].index) {
+            addRow('Index', data.fields[field].index);
+        }
+        display.docs = data.fields[field].docs;
+        display.docsUrl = "#/" + core + "/query?q=" + field + ":[* TO *]";
+        display.distinct = data.fields[field].distinct;
+        display.positionIncrementGap = data.fields[field].positionIncrementGap;
+        display.similarity = data.fields[field].similarity;
+    } else if (is.dynamicField && data.dynamic_fields[field] && data.dynamic_fields[field].flags) {
+        addRow('Properties', data.dynamic_fields[field].flags);
+    }
+
+    // identify columns in field property table:
+    for (var key in data.key) {
+        if (allFlags.indexOf(key)>=0) {
+            display.columns.push({key: key, name: data.key[key]});
+        }
+    }
+
+    // identify rows and cell values in field property table:
+    for (var i in display.rows) {
+        var row = display.rows[i];
+        row.cells = [];
+
+        if (!row.flags) {
+            continue; // Match the special case in the LukeRequestHandler
+        }
+
+        for (var j in display.columns) {
+            var flag = display.columns[j].key;
+            row.cells.push({key: flag, value: row.flags.indexOf(flag)>=0});
+        }
+    }
+
+    return display;
+};
+
+var getAnalysisInfo = function(data, is, name) {
+
+    var analysis = {};
+
+    if (is.field) {
+        var type = data.relations.f_t[name];
+        analysis.query = "analysis.fieldname=" + name;
+    }
+    else if (is.dynamicField) {
+        var type = data.relations.df_t[name];
+        analysis.query = "analysis.fieldtype=" + type;
+    }
+    else if (is.type) {
+        var type = name;
+        analysis.query = "analysis.fieldtype=" + name;
+    }
+
+    var processComponentType = function (label, key, componentTypeData) {
+        if (componentTypeData) {
+            var components = [];
+            for (var componentName in componentTypeData) {
+                var componentData = componentTypeData[componentName];
+                var component = {className: componentData.className, args:[]};
+                if (componentData.args) {
+                    for (var argName in componentData.args) {
+                        var argValue = componentData.args[argName];
+                        if (argValue == "1" || argValue == "true") {
+                            component.args.push({name: argName, booleanValue:true});
+                        } else if (argValue == "0" || argValue == "false") {
+                            component.args.push({name: argName, booleanValue:false});
+                        } else {
+                            component.args.push({name: argName, value:argValue});
+                        }
+                    }
+                }
+                components.push(component);
+            }
+            return {label: label, key: key, components: components};
+        } else {
+            return {label: label, key: key};
+        }
+    }
+
+    var buildAnalyzer = function (analyzerData) {
+        var analyzer = {};
+        analyzer.className = analyzerData.className;
+        analyzer.componentTypes = [];
+        if (analyzerData.tokenizer) {
+            analyzer.componentTypes.push(processComponentType("Char Filters", "charFilters", analyzerData.charFilters));
+            analyzer.componentTypes.push(processComponentType("Tokenizer", "tokenizer", {tokenizer: analyzerData.tokenizer}));
+            analyzer.componentTypes.push(processComponentType("Token Filters", "tokenFilters", analyzerData.filters));
+        }
+        return analyzer;
+    }
+
+    analysis.data = data.types[type];
+    if (analysis.data) {
+        analysis.analyzers = [
+            {key: "index", name: "Index", detail: buildAnalyzer(analysis.data.indexAnalyzer)},
+            {key: "query", name: "Query", detail: buildAnalyzer(analysis.data.queryAnalyzer)}
+        ];
+    }
+    return analysis;
+}
+
+var getTermInfo = function(data) {
+
+    var termInfo = {};
+    if (data && data.topTerms) {
+        termInfo.topTerms = [];
+
+        var currentGroup = {count: 0}
+        for (var i = 0; i < data.topTerms.length; i += 2) {
+            var count = data.topTerms[i + 1];
+            if (currentGroup.count != count) {
+                currentGroup = {count: count, terms: []};
+                termInfo.topTerms.push(currentGroup);
+            }
+            currentGroup.terms.push(data.topTerms[i]);
+        }
+        termInfo.termCount = data.topTerms.length / 2;
+        termInfo.maxTerms = data.distinct;
+    }
+
+    if(data && data.histogram) {
+        termInfo.histogram = [];
+        termInfo.histogramMax = 0;
+        for (var i = 0; i < data.histogram.length; i += 2) {
+            termInfo.histogram.push({key: data.histogram[i], value: data.histogram[i + 1]});
+            termInfo.histogramMax = Math.max(termInfo.histogramMax, data.histogram[i + 1]);
+        }
+    }
+    return termInfo;
+};
+
+var sortedObjectArray = function(list) {
+    var objarr = [];
+    for (var i in list) {
+      objarr.push({"name": list[i]});
+    }
+    return objarr;
+}
+
+/*
+        var get_width = function get_width()
+        {
+          return $( this ).width();
+        }
+
+  var max_width = 10 + Math.max.apply( Math, $( 'p', topterms_table_element ).map( get_width ).get() );
+  topterms:
+    p { width: {{maxWidth}}px !important; }
+    ul { margin-left: {{max_width + 5 }}px !important; }
+
+  var max_width = 10 + Math.max.apply( Math, $( 'dt', histogram_holder_element ).map( get_width ).get() );
+  histogram_holder:
+    ul { margin-left: {{maxWidth}}px !important; }
+    li dt { left: {{-maxWidth}}px !important; width: {{maxWidth}}px !important; }
+*/

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/segments.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/segments.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/segments.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/segments.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,99 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+var MB_FACTOR = 1024*1024;
+
+solrAdminApp.controller('SegmentsController', function($scope, $routeParams, $interval, Segments, Constants) {
+    $scope.resetMenu("segments", Constants.IS_CORE_PAGE);
+
+    $scope.refresh = function() {
+
+        Segments.get({core: $routeParams.core}, function(data) {
+            var segments = data.segments;
+
+            var segmentSizeInBytesMax = getLargestSegmentSize(segments);
+            $scope.segmentMB = Math.floor(segmentSizeInBytesMax / MB_FACTOR);
+            $scope.xaxis = calculateXAxis(segmentSizeInBytesMax);
+
+            $scope.documentCount = 0;
+            $scope.deletionCount = 0;
+
+            $scope.segments = [];
+            for (var name in segments) {
+                var segment = segments[name];
+
+                var segmentSizeInBytesLog = Math.log(segment.sizeInBytes);
+                var segmentSizeInBytesMaxLog = Math.log(segmentSizeInBytesMax);
+
+                segment.totalSize = Math.floor((segmentSizeInBytesLog / segmentSizeInBytesMaxLog ) * 100);
+
+                segment.deletedDocSize = Math.floor((segment.delCount / (segment.delCount + segment.totalSize)) * segment.totalSize);
+                if (segment.delDocSize <= 0.001) delete segment.deletedDocSize;
+
+                segment.aliveDocSize = segment.totalSize - segment.deletedDocSize;
+
+                $scope.segments.push(segment);
+
+                $scope.documentCount += segment.size;
+                $scope.deletionCount += segment.delCount;
+            }
+            $scope.deletionsPercentage = calculateDeletionsPercentage($scope.documentCount, $scope.deletionCount);
+        });
+    };
+
+    $scope.toggleAutoRefresh = function() {
+        $scope.autorefresh = !$scope.autorefresh;
+        if ($scope.autorefresh) {
+            $scope.interval = $interval($scope.refresh, 1000);
+            var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
+              $interval.cancel($scope.interval);
+              onRouteChangeOff();
+            });
+
+        } else if ($scope.interval) {
+            $interval.cancel($scope.interval);
+        }
+    };
+    $scope.refresh();
+});
+
+var calculateXAxis = function(segmentInBytesMax) {
+    var steps = [];
+    var log = Math.log(segmentInBytesMax);
+
+    for (var j=0, step=log/4; j<3; j++, step+=log/4) {
+        steps.push({pos:j, value:Math.floor((Math.pow(Math.E, step))/MB_FACTOR)})
+    }
+    return steps;
+};
+
+var getLargestSegmentSize = function(segments) {
+    var max = 0;
+    for (var name in segments) {
+        max = Math.max(max, segments[name].sizeInBytes);
+    }
+    return max;
+};
+
+var calculateDeletionsPercentage = function(docCount, delCount) {
+    if (docCount == 0) {
+        return 0;
+    } else {
+        var percent = delCount / docCount * 100;
+        return Math.round(percent * 100) / 100;
+    }
+};

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/stream.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/stream.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/stream.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/stream.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,240 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+solrAdminApp.controller('StreamController',
+  function($scope, $routeParams, $location, Query, Constants) {
+
+    $scope.resetMenu("stream", Constants.IS_COLLECTION_PAGE);
+
+    $scope.stream = {
+      wt: 'json',
+      expr: $scope.expr,
+      indent: 'on'
+    };
+    $scope.qt = "stream";
+    $scope.doExplanation = false
+
+    $scope.doStream = function() {
+
+      var params = {};
+      params.core = $routeParams.core;
+      params.handler = $scope.qt;
+      params.expr = [$scope.expr]
+      if($scope.doExplanation){
+        params.explain = [$scope.doExplanation]
+      }
+
+      $scope.lang = "json";
+      $scope.response = null;
+      $scope.url = "";
+
+      var url = Query.url(params);
+
+      Query.query(params, function(data) {
+
+        var jsonData = JSON.parse(data.toJSON().data);
+        if (undefined != jsonData["explanation"]) {
+          $scope.showExplanation = true;
+
+          streamGraphSubController($scope, jsonData["explanation"])
+          delete jsonData["explanation"]
+        } else {
+          $scope.showExplanation = false;
+        }
+
+        data.data = JSON.stringify(jsonData,null,2);
+
+        $scope.lang = "json";
+        $scope.response = data;
+        $scope.url = $location.protocol() + "://" +
+          $location.host() + ":" +
+          $location.port() + url;
+
+      });
+    };
+
+    if ($location.search().expr) {
+      $scope.expr = $location.search()["expr"];
+      $scope.doStream();
+    }
+
+  }
+);
+
+var streamGraphSubController = function($scope, explanation) {
+  $scope.showGraph = true;
+  $scope.pos = 0;
+  $scope.rows = 8;
+
+  $scope.resetGraph = function() {
+    $scope.pos = 0;
+    $scope.initGraph();
+  }
+
+  $scope.initGraph = function(explanation) {
+
+    data = explanation
+
+    var leafCount = 0;
+    var maxDepth = 0;
+    var rootNode = {};
+
+    leafCount = 0;
+
+    let recurse = function(dataNode, depth) {
+
+      if (depth > maxDepth) {
+        maxDepth = depth;
+      }
+
+      let graphNode = {
+        name: dataNode.expressionNodeId,
+        implementingClass: 'unknown',
+        data: {}
+      };
+
+      ["expressionNodeId", "expressionType", "functionName", "implementingClass", "expression", "note", "helpers"].forEach(function(key) {
+        graphNode.data[key] = dataNode[key];
+      });
+
+      if (dataNode.children && dataNode.children.length > 0) {
+        graphNode.children = [];
+        dataNode.children.forEach(function(n) {
+          graphNode.children.push(recurse(n, depth + 1));
+        });
+      } else {
+        ++leafCount;
+      }
+
+      return graphNode;
+    }
+
+    $scope.showPaging = false;
+    $scope.isRadial = false;
+    $scope.explanationData = recurse(data, 1);
+
+    $scope.depth = maxDepth + 1;
+    $scope.leafCount = leafCount;
+  };
+
+  $scope.initGraph(explanation);
+};
+
+solrAdminApp.directive('explanationGraph', function(Constants) {
+  return {
+    restrict: 'EA',
+    scope: {
+      data: "=",
+      leafCount: "=",
+      depth: "="
+    },
+    link: function(scope, element, attrs) {
+      
+      var helper_path_class = function(p) {
+        var classes = ['link'];
+
+        return classes.join(' ');
+      };
+
+      var helper_node_class = function(d) {
+        var classes = ['node'];
+
+        if (d.data && d.data.expressionType) {
+          classes.push(d.data.expressionType);
+        }
+
+        return classes.join(' ');
+      };
+
+      var helper_node_text = function(d) {
+        if (d.data && d.data.functionName) {
+          return d.data.functionName;
+        }
+
+        return d.name
+      };
+
+      var helper_tooltip = function(d) {
+
+        return [
+          "Function: " + d.data.functionName,
+          "Type: " + d.data.expressionType,
+          "Class: " + d.data.implementingClass.replace("org.apache.solr.client.solrj.io", "o.a.s.c.s.i"),
+          "=============",
+          d.data.expression
+        ].join("\n");
+      }
+
+      scope.$watch("data", function(newValue, oldValue) {
+        if (newValue) {
+          flatGraph(element, scope.data, scope.depth, scope.leafCount);
+        }
+      });
+
+      var flatGraph = function(element, graphData, depth, leafCount) {
+        var w = 100 + (depth * 100),
+          h = leafCount * 40;
+
+        var tree = d3.layout.tree().size([h, w]);
+
+        var diagonal = d3.svg.diagonal().projection(function(d) {
+          return [d.y * .7, d.x];
+        });
+
+        d3.select('#canvas', element).html('');
+        var vis = d3.select('#canvas', element).append('svg')
+          .attr('width', w)
+          .attr('height', h)
+          .append('g')
+          .attr('transform', 'translate(25, 0)');
+
+        var nodes = tree.nodes(graphData);
+
+        var link = vis.selectAll('path.link')
+          .data(tree.links(nodes))
+          .enter().append('path')
+          .attr('class', helper_path_class)
+          .attr('d', diagonal);
+
+        var node = vis.selectAll('g.node')
+          .data(nodes)
+          .enter().append('g')
+          .attr('class', helper_node_class)
+          .attr('transform', function(d) {
+            return 'translate(' + d.y * .7 + ',' + d.x + ')';
+          })
+
+        node.append('circle')
+          .attr('r', 4.5);
+
+        node.append('title')
+          .text(helper_tooltip);
+
+        node.append('text')
+          .attr('dx', function(d) {
+            return 8;
+          })
+          .attr('dy', function(d) {
+            return 5;
+          })
+          .attr('text-anchor', function(d) {
+            return 'start';
+          })
+          .text(helper_node_text)
+      };
+    }
+  };
+})

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/threads.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/threads.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/threads.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/controllers/threads.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,50 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+solrAdminApp.controller('ThreadsController',
+  function($scope, Threads, Constants){
+    $scope.resetMenu("threads", Constants.IS_ROOT_PAGE);
+    $scope.refresh = function() {
+      Threads.get(function(data) {
+        var threadDump = data.system.threadDump;
+        var threads = [];
+        for (var i=1; i<threadDump.length; i+=2) {
+          var thread = threadDump[i];
+          if (!!thread.stackTrace) {
+            var stackTrace = [];
+            for (var j=0; j<thread.stackTrace.length; j++) {
+              var trace = thread.stackTrace[j].replace("(", "\u200B("); // allow wrapping to happen, \u200B is a zero-width space
+              stackTrace.push({id:thread.id + ":" + j, trace: trace});
+            }
+            thread.stackTrace = stackTrace;
+          }
+          threads.push(thread);
+        }
+        $scope.threads = threads;
+      });
+    };
+    $scope.toggleStacktrace = function(thread) {
+      thread.showStackTrace = !thread.showStackTrace;
+    };
+    $scope.toggleStacktraces = function() {
+      $scope.showAllStacktraces = !$scope.showAllStacktraces;
+      for (var i=0; i<$scope.threads.length; i++) {
+        $scope.threads[i].showStackTrace = $scope.showAllStacktraces;
+      }
+    };
+    $scope.refresh();
+});

Added: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/services.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/services.js?rev=1776930&view=auto
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/services.js (added)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/angular/services.js Mon Jan  2 13:44:06 2017
@@ -0,0 +1,258 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+var solrAdminServices = angular.module('solrAdminServices', ['ngResource']);
+
+solrAdminServices.factory('System',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/info/system', {"wt":"json", "_":Date.now()});
+  }])
+.factory('Collections',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/collections',
+    {'wt':'json', '_':Date.now()}, {
+    "list": {params:{action: "LIST"}},
+    "status": {params:{action: "CLUSTERSTATUS"}},
+    "add": {params:{action: "CREATE"}},
+    "delete": {params:{action: "DELETE"}},
+    "rename": {params:{action: "RENAME"}},
+    "createAlias": {params:{action: "CREATEALIAS"}},
+    "deleteAlias": {params:{action: "DELETEALIAS"}},
+    "deleteReplica": {params:{action: "DELETEREPLICA"}},
+    "addReplica": {params:{action: "ADDREPLICA"}},
+    "reload": {method: "GET", params:{action:"RELOAD", core: "@core"}},
+    "optimize": {params:{}}
+    });
+  }])
+.factory('Cores',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/cores',
+    {'wt':'json', '_':Date.now()}, {
+    "query": {},
+    "list": {params:{indexInfo: false}},
+    "add": {params:{action: "CREATE"}},
+    "unload": {params:{action: "UNLOAD", core: "@core"}},
+    "rename": {params:{action: "RENAME"}},
+    "swap": {params:{action: "SWAP"}},
+    "reload": {method: "GET", params:{action:"RELOAD", core: "@core"}, headers:{doNotIntercept: "true"}},
+    "optimize": {params:{}}
+    });
+  }])
+.factory('Logging',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/info/logging', {'wt':'json', '_':Date.now()}, {
+      "events": {params: {since:'0'}},
+      "levels": {},
+      "setLevel": {}
+      });
+  }])
+.factory('Zookeeper',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/zookeeper', {wt:'json', _:Date.now()}, {
+      "simple": {},
+      "dump": {params: {dump: "true"}},
+      "liveNodes": {params: {path: '/live_nodes'}},
+      "clusterState": {params: {detail: "true", path: "/clusterstate.json"}},
+      "detail": {params: {detail: "true", path: "@path"}},
+      "configs": {params: {detail:false, path: "/configs/"}},
+      "aliases": {params: {detail: "true", path: "/aliases.json"}, transformResponse:function(data) {
+        var znode = $.parseJSON(data).znode;
+        if (znode.data) {
+          return {aliases: $.parseJSON(znode.data).collection};
+        } else {
+          return {aliases: {}};
+        }
+      }}
+    });
+  }])
+.factory('Properties',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/info/properties', {'wt':'json', '_':Date.now()});
+  }])
+.factory('Threads',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/info/threads', {'wt':'json', '_':Date.now()});
+  }])
+.factory('Properties',
+  ['$resource', function($resource) {
+    return $resource('/solr/admin/info/properties', {'wt':'json', '_':Date.now()});
+  }])
+.factory('Replication',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/replication', {'wt':'json', core: "@core", '_':Date.now()}, {
+      "details": {params: {command: "details"}},
+      "command": {params: {}}
+    });
+  }])
+.factory('CoreSystem',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/admin/system', {wt:'json', core: "@core", _:Date.now()});
+  }])
+.factory('Update',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/:handler', {core: '@core', wt:'json', _:Date.now(), handler:'update'}, {
+      "optimize": {params: { optimize: "true"}},
+      "commit": {params: {commit: "true"}},
+      "post": {headers: {'Content-type': 'application/json'}, method: "POST", params: {handler: '@handler'}},
+      "postJson": {headers: {'Content-type': 'application/json'}, method: "POST", params: {handler: '@handler'}},
+      "postXml": {headers: {'Content-type': 'text/xml'}, method: "POST", params: {handler: '@handler'}},
+      "postCsv": {headers: {'Content-type': 'application/csv'}, method: "POST", params: {handler: '@handler'}}
+    });
+  }])
+.service('FileUpload', function ($http) {
+    this.upload = function(params, file, success, error){
+        var url = "/solr/" + params.core + "/" + params.handler + "?";
+        raw = params.raw;
+        delete params.core;
+        delete params.handler;
+        delete params.raw;
+        url += $.param(params);
+        if (raw && raw.length>0) {
+            if (raw[0] != "&") raw = "&" + raw;
+            url += raw;
+        }
+        var fd = new FormData();
+        fd.append('file', file);
+        $http.post(url, fd, {
+            transformRequest: angular.identity,
+            headers: {'Content-Type': undefined}
+        }).success(success).error(error);
+    }
+})
+.factory('Luke',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/admin/luke', {core: '@core', wt:'json', _:Date.now()}, {
+      "index":  {params: {numTerms: 0, show: 'index'}},
+      "raw": {params: {numTerms: 0}},
+      "schema": {params: {show:'schema'}},
+      "field": {},
+      "fields": {params: {show:'schema'}, interceptor: {
+          response: function(response) {
+              var fieldsAndTypes = [];
+              for (var field in response.data.schema.fields) {
+                fieldsAndTypes.push({group: "Fields", label: field, value: "fieldname=" + field});
+              }
+              for (var type in response.data.schema.types) {
+                fieldsAndTypes.push({group: "Types", label: type, value: "fieldtype=" + type});
+              }
+              return fieldsAndTypes;
+          }
+      }}
+    });
+  }])
+.factory('Analysis',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/analysis/field', {core: '@core', wt:'json', _:Date.now()}, {
+      "field": {params: {"analysis.showmatch": true}}
+    });
+  }])
+.factory('DataImport',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/:name', {core: '@core', name: '@name', indent:'on', wt:'json', _:Date.now()}, {
+      "config": {params: {command: "show-config"}, headers: {doNotIntercept: "true"},
+                 transformResponse: function(data) {
+                    return {config: data};
+                 }
+                },
+      "status": {params: {command: "status"}, headers: {doNotIntercept: "true"}},
+      "reload": {params: {command: "reload-config"}},
+      "post": {method: "POST",
+                headers: {'Content-type': 'application/x-www-form-urlencoded'},
+                transformRequest: function(data) { return $.param(data) }}
+    });
+  }])
+.factory('Ping',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/admin/ping', {wt:'json', core: '@core', ts:Date.now(), _:Date.now()}, {
+     "ping": {},
+     "status": {params:{action:"status"}, headers: {doNotIntercept: "true"}
+    }});
+  }])
+.factory('Mbeans',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/admin/mbeans', {'wt':'json', core: '@core', '_':Date.now()}, {
+        stats: {params: {stats: true}},
+        info: {},
+        reference: {
+            params: {wt: "xml", stats: true}, transformResponse: function (data) {
+                return {reference: data}
+            }
+        },
+        delta: {method: "POST",
+                params: {stats: true, diff:true},
+                headers: {'Content-type': 'application/x-www-form-urlencoded'},
+                transformRequest: function(data) {
+                    return "stream.body=" + encodeURIComponent(data);
+                }
+        }
+    });
+  }])
+.factory('Files',
+  ['$resource', function($resource) {
+    return $resource('/solr/:core/admin/file', {'wt':'json', core: '@core', '_':Date.now()}, {
+      "list": {},
+      "get": {method: "GET", interceptor: {
+          response: function(config) {return config;}
+      }, transformResponse: function(data) {
+          return data;
+      }}
+    });
+  }])
+.factory('Query',
+    ['$resource', function($resource) {
+       var resource = $resource('/solr/:core/:handler', {core: '@core', handler: '@handler', '_':Date.now()}, {
+           "query": {
+             method: "GET",
+             transformResponse: function (data) {
+               return {data: data}
+             },
+             headers: {doNotIntercept: "true"}
+           }
+       });
+       resource.url = function(params) {
+           var qs = [];
+           for (key in params) {
+               if (key != "core" && key != "handler") {
+                   for (var i in params[key]) {
+                       qs.push(key + "=" + params[key][i]);
+                   }
+               }
+           }
+           return "/solr/" + params.core + "/" + params.handler + "?" + qs.sort().join("&");
+       }
+       return resource;
+}])
+.factory('Segments',
+   ['$resource', function($resource) {
+       return $resource('/solr/:core/admin/segments', {'wt':'json', core: '@core', _:Date.now()}, {
+           get: {}
+       });
+}])
+.factory('Schema',
+   ['$resource', function($resource) {
+     return $resource('/solr/:core/schema', {wt: 'json', core: '@core', _:Date.now()}, {
+       get: {method: "GET"},
+       check: {method: "GET", headers: {doNotIntercept: "true"}},
+       post: {method: "POST"}
+     });
+}])
+.factory('Config',
+   ['$resource', function($resource) {
+     return $resource('/solr/:core/config', {wt: 'json', core: '@core', _:Date.now()}, {
+       get: {method: "GET"}
+     })
+}]);

Modified: ofbiz/trunk/specialpurpose/solr/webapp/solr/js/lib/ZeroClipboard.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/solr/webapp/solr/js/lib/ZeroClipboard.js?rev=1776930&r1=1776929&r2=1776930&view=diff
==============================================================================
--- ofbiz/trunk/specialpurpose/solr/webapp/solr/js/lib/ZeroClipboard.js (original)
+++ ofbiz/trunk/specialpurpose/solr/webapp/solr/js/lib/ZeroClipboard.js Mon Jan  2 13:44:06 2017
@@ -27,316 +27,316 @@ THE SOFTWARE.
 // Author: Joseph Huckaby
 
 var ZeroClipboard = {
-
- version: "1.0.7",
- clients: {}, // registered upload clients on page, indexed by id
- moviePath: 'ZeroClipboard.swf', // URL to movie
- nextId: 1, // ID of next movie
-
- $: function(thingy) {
- // simple DOM lookup utility function
- if (typeof(thingy) == 'string') thingy = document.getElementById(thingy);
- if (!thingy.addClass) {
- // extend element with a few useful methods
- thingy.hide = function() { this.style.display = 'none'; };
- thingy.show = function() { this.style.display = ''; };
- thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; };
- thingy.removeClass = function(name) {
- var classes = this.className.split(/\s+/);
- var idx = -1;
- for (var k = 0; k < classes.length; k++) {
- if (classes[k] == name) { idx = k; k = classes.length; }
- }
- if (idx > -1) {
- classes.splice( idx, 1 );
- this.className = classes.join(' ');
- }
- return this;
- };
- thingy.hasClass = function(name) {
- return !!this.className.match( new RegExp("\\s*" + name + "\\s*") );
- };
- }
- return thingy;
- },
-
- setMoviePath: function(path) {
- // set path to ZeroClipboard.swf
- this.moviePath = path;
- },
-
- dispatch: function(id, eventName, args) {
- // receive event from flash movie, send to client
- var client = this.clients[id];
- if (client) {
- client.receiveEvent(eventName, args);
- }
- },
-
- register: function(id, client) {
- // register new client to receive events
- this.clients[id] = client;
- },
-
- getDOMObjectPosition: function(obj, stopObj) {
- // get absolute coordinates for dom element
- var info = {
- left: 0,
- top: 0,
- width: obj.width ? obj.width : obj.offsetWidth,
- height: obj.height ? obj.height : obj.offsetHeight
- };
+  
+  version: "1.0.7",
+  clients: {}, // registered upload clients on page, indexed by id
+  moviePath: 'ZeroClipboard.swf', // URL to movie
+  nextId: 1, // ID of next movie
+  
+  $: function(thingy) {
+    // simple DOM lookup utility function
+    if (typeof(thingy) == 'string') thingy = document.getElementById(thingy);
+    if (!thingy.addClass) {
+      // extend element with a few useful methods
+      thingy.hide = function() { this.style.display = 'none'; };
+      thingy.show = function() { this.style.display = ''; };
+      thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; };
+      thingy.removeClass = function(name) {
+        var classes = this.className.split(/\s+/);
+        var idx = -1;
+        for (var k = 0; k < classes.length; k++) {
+          if (classes[k] == name) { idx = k; k = classes.length; }
+        }
+        if (idx > -1) {
+          classes.splice( idx, 1 );
+          this.className = classes.join(' ');
+        }
+        return this;
+      };
+      thingy.hasClass = function(name) {
+        return !!this.className.match( new RegExp("\\s*" + name + "\\s*") );
+      };
+    }
+    return thingy;
+  },
+  
+  setMoviePath: function(path) {
+    // set path to ZeroClipboard.swf
+    this.moviePath = path;
+  },
+  
+  dispatch: function(id, eventName, args) {
+    // receive event from flash movie, send to client    
+    var client = this.clients[id];
+    if (client) {
+      client.receiveEvent(eventName, args);
+    }
+  },
+  
+  register: function(id, client) {
+    // register new client to receive events
+    this.clients[id] = client;
+  },
+  
+  getDOMObjectPosition: function(obj, stopObj) {
+    // get absolute coordinates for dom element
+    var info = {
+      left: 0,
+      top: 0,
+      width: obj.width ? obj.width : obj.offsetWidth,
+      height: obj.height ? obj.height : obj.offsetHeight
+    };
 
- while (obj && (obj != stopObj)) {
- info.left += obj.offsetLeft;
- info.top += obj.offsetTop;
- obj = obj.offsetParent;
- }
+    while (obj && (obj != stopObj)) {
+      info.left += obj.offsetLeft;
+      info.top += obj.offsetTop;
+      obj = obj.offsetParent;
+    }
 
- return info;
- },
-
- Client: function(elem) {
- // constructor for new simple upload client
- this.handlers = {};
-
- // unique ID
- this.id = ZeroClipboard.nextId++;
- this.movieId = 'ZeroClipboardMovie_' + this.id;
-
- // register client with singleton to receive flash events
- ZeroClipboard.register(this.id, this);
-
- // create movie
- if (elem) this.glue(elem);
- }
+    return info;
+  },
+  
+  Client: function(elem) {
+    // constructor for new simple upload client
+    this.handlers = {};
+    
+    // unique ID
+    this.id = ZeroClipboard.nextId++;
+    this.movieId = 'ZeroClipboardMovie_' + this.id;
+    
+    // register client with singleton to receive flash events
+    ZeroClipboard.register(this.id, this);
+    
+    // create movie
+    if (elem) this.glue(elem);
+  }
 };
 
 ZeroClipboard.Client.prototype = {
-
- id: 0, // unique ID for us
- ready: false, // whether movie is ready to receive events or not
- movie: null, // reference to movie object
- clipText: '', // text to copy to clipboard
- handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor
- cssEffects: true, // enable CSS mouse effects on dom container
- handlers: null, // user event handlers
-
- glue: function(elem, appendElem, stylesToAdd) {
- // glue to DOM element
- // elem can be ID or actual DOM element object
- this.domElement = ZeroClipboard.$(elem);
-
- // float just above object, or zIndex 99 if dom element isn't set
- var zIndex = 99;
- if (this.domElement.style.zIndex) {
- zIndex = parseInt(this.domElement.style.zIndex, 10) + 1;
- }
-
- if (typeof(appendElem) == 'string') {
- appendElem = ZeroClipboard.$(appendElem);
- }
- else if (typeof(appendElem) == 'undefined') {
- appendElem = document.getElementsByTagName('body')[0];
- }
-
- // find X/Y position of domElement
- var box = ZeroClipboard.getDOMObjectPosition(this.domElement, appendElem);
-
- // create floating DIV above element
- this.div = document.createElement('div');
- var style = this.div.style;
- style.position = 'absolute';
- style.left = '' + box.left + 'px';
- style.top = '' + box.top + 'px';
- style.width = '' + box.width + 'px';
- style.height = '' + box.height + 'px';
- style.zIndex = zIndex;
+  
+  id: 0, // unique ID for us
+  ready: false, // whether movie is ready to receive events or not
+  movie: null, // reference to movie object
+  clipText: '', // text to copy to clipboard
+  handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor
+  cssEffects: true, // enable CSS mouse effects on dom container
+  handlers: null, // user event handlers
+  
+  glue: function(elem, appendElem, stylesToAdd) {
+    // glue to DOM element
+    // elem can be ID or actual DOM element object
+    this.domElement = ZeroClipboard.$(elem);
+    
+    // float just above object, or zIndex 99 if dom element isn't set
+    var zIndex = 99;
+    if (this.domElement.style.zIndex) {
+      zIndex = parseInt(this.domElement.style.zIndex, 10) + 1;
+    }
+    
+    if (typeof(appendElem) == 'string') {
+      appendElem = ZeroClipboard.$(appendElem);
+    }
+    else if (typeof(appendElem) == 'undefined') {
+      appendElem = document.getElementsByTagName('body')[0];
+    }
+    
+    // find X/Y position of domElement
+    var box = ZeroClipboard.getDOMObjectPosition(this.domElement, appendElem);
+    
+    // create floating DIV above element
+    this.div = document.createElement('div');
+    var style = this.div.style;
+    style.position = 'absolute';
+    style.left = '' + box.left + 'px';
+    style.top = '' + box.top + 'px';
+    style.width = '' + box.width + 'px';
+    style.height = '' + box.height + 'px';
+    style.zIndex = zIndex;
 
- style.left = '0px';
- style.top = '0px';
-
- if (typeof(stylesToAdd) == 'object') {
- for (addedStyle in stylesToAdd) {
- style[addedStyle] = stylesToAdd[addedStyle];
- }
- }
-
- // style.backgroundColor = '#f00'; // debug
-
- appendElem.appendChild(this.div);
-
- this.div.innerHTML = this.getHTML( box.width, box.height );
- },
-
- getHTML: function(width, height) {
- // return HTML for movie
- var html = '';
- var flashvars = 'id=' + this.id +
- '&width=' + width +
- '&height=' + height;
-
- if (navigator.userAgent.match(/MSIE/)) {
- // IE gets an OBJECT tag
- var protocol = location.href.match(/^https/i) ? 'https://' : 'http://';
- html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>';
- }
- else {
- // all other browsers get an EMBED tag
- html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />';
- }
- return html;
- },
-
- hide: function() {
- // temporarily hide floater offscreen
- if (this.div) {
- this.div.style.left = '-2000px';
- }
- },
-
- show: function() {
- // show ourselves after a call to hide()
- this.reposition();
- },
-
- destroy: function() {
- // destroy control and floater
- if (this.domElement && this.div) {
- this.hide();
- this.div.innerHTML = '';
-
- var body = document.getElementsByTagName('body')[0];
- try { body.removeChild( this.div ); } catch(e) {;}
-
- this.domElement = null;
- this.div = null;
- }
- },
-
- reposition: function(elem) {
- // reposition our floating div, optionally to new container
- // warning: container CANNOT change size, only position
- if (elem) {
- this.domElement = ZeroClipboard.$(elem);
- if (!this.domElement) this.hide();
- }
+    style.left = '0px';
+    style.top = '0px';
+    
+    if (typeof(stylesToAdd) == 'object') {
+      for (addedStyle in stylesToAdd) {
+        style[addedStyle] = stylesToAdd[addedStyle];
+      }
+    }
+    
+    // style.backgroundColor = '#f00'; // debug
+    
+    appendElem.appendChild(this.div);
+    
+    this.div.innerHTML = this.getHTML( box.width, box.height );
+  },
+  
+  getHTML: function(width, height) {
+    // return HTML for movie
+    var html = '';
+    var flashvars = 'id=' + this.id +
+      '&width=' + width +
+      '&height=' + height;
+      
+    if (navigator.userAgent.match(/MSIE/)) {
+      // IE gets an OBJECT tag
+      var protocol = location.href.match(/^https/i) ? 'https://' : 'http://';
+      html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>';
+    }
+    else {
+      // all other browsers get an EMBED tag
+      html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />';
+    }
+    return html;
+  },
+  
+  hide: function() {
+    // temporarily hide floater offscreen
+    if (this.div) {
+      this.div.style.left = '-2000px';
+    }
+  },
+  
+  show: function() {
+    // show ourselves after a call to hide()
+    this.reposition();
+  },
+  
+  destroy: function() {
+    // destroy control and floater
+    if (this.domElement && this.div) {
+      this.hide();
+      this.div.innerHTML = '';
+      
+      var body = document.getElementsByTagName('body')[0];
+      try { body.removeChild( this.div ); } catch(e) {;}
+      
+      this.domElement = null;
+      this.div = null;
+    }
+  },
+  
+  reposition: function(elem) {
+    // reposition our floating div, optionally to new container
+    // warning: container CANNOT change size, only position
+    if (elem) {
+      this.domElement = ZeroClipboard.$(elem);
+      if (!this.domElement) this.hide();
+    }
 
- console.debug( this.domElement, this.div );
-
- if (this.domElement && this.div) {
- var box = ZeroClipboard.getDOMObjectPosition(this.domElement);
- console.debug( box );
- var style = this.div.style;
- style.left = '' + box.left + 'px';
- style.top = '' + box.top + 'px';
- }
- },
-
- setText: function(newText) {
- // set text to be copied to clipboard
- this.clipText = newText;
- if (this.ready) this.movie.setText(newText);
- },
-
- addEventListener: function(eventName, func) {
- // add user event listener for event
- // event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel
- eventName = eventName.toString().toLowerCase().replace(/^on/, '');
- if (!this.handlers[eventName]) this.handlers[eventName] = [];
- this.handlers[eventName].push(func);
- },
-
- setHandCursor: function(enabled) {
- // enable hand cursor (true), or default arrow cursor (false)
- this.handCursorEnabled = enabled;
- if (this.ready) this.movie.setHandCursor(enabled);
- },
-
- setCSSEffects: function(enabled) {
- // enable or disable CSS effects on DOM container
- this.cssEffects = !!enabled;
- },
-
- receiveEvent: function(eventName, args) {
- // receive event from flash
- eventName = eventName.toString().toLowerCase().replace(/^on/, '');
-
- // special behavior for certain events
- switch (eventName) {
- case 'load':
- // movie claims it is ready, but in IE this isn't always the case...
- // bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function
- this.movie = document.getElementById(this.movieId);
- if (!this.movie) {
- var self = this;
- setTimeout( function() { self.receiveEvent('load', null); }, 1 );
- return;
- }
-
- // firefox on pc needs a "kick" in order to set these in certain cases
- if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) {
- var self = this;
- setTimeout( function() { self.receiveEvent('load', null); }, 100 );
- this.ready = true;
- return;
- }
-
- this.ready = true;
- this.movie.setText( this.clipText );
- this.movie.setHandCursor( this.handCursorEnabled );
- break;
-
- case 'mouseover':
- if (this.domElement && this.cssEffects) {
- this.domElement.addClass('hover');
- if (this.recoverActive) this.domElement.addClass('active');
- }
- break;
-
- case 'mouseout':
- if (this.domElement && this.cssEffects) {
- this.recoverActive = false;
- if (this.domElement.hasClass('active')) {
- this.domElement.removeClass('active');
- this.recoverActive = true;
- }
- this.domElement.removeClass('hover');
- }
- break;
-
- case 'mousedown':
- if (this.domElement && this.cssEffects) {
- this.domElement.addClass('active');
- }
- break;
-
- case 'mouseup':
- if (this.domElement && this.cssEffects) {
- this.domElement.removeClass('active');
- this.recoverActive = false;
- }
- break;
- } // switch eventName
-
- if (this.handlers[eventName]) {
- for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) {
- var func = this.handlers[eventName][idx];
-
- if (typeof(func) == 'function') {
- // actual function reference
- func(this, args);
- }
- else if ((typeof(func) == 'object') && (func.length == 2)) {
- // PHP style object + method, i.e. [myObject, 'myMethod']
- func[0][ func[1] ](this, args);
- }
- else if (typeof(func) == 'string') {
- // name of function
- window[func](this, args);
- }
- } // foreach event handler defined
- } // user defined handler for event
- }
-
+    console.debug( this.domElement, this.div );
+    
+    if (this.domElement && this.div) {
+      var box = ZeroClipboard.getDOMObjectPosition(this.domElement);
+      console.debug( box );
+      var style = this.div.style;
+      style.left = '' + box.left + 'px';
+      style.top = '' + box.top + 'px';
+    }
+  },
+  
+  setText: function(newText) {
+    // set text to be copied to clipboard
+    this.clipText = newText;
+    if (this.ready) this.movie.setText(newText);
+  },
+  
+  addEventListener: function(eventName, func) {
+    // add user event listener for event
+    // event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel
+    eventName = eventName.toString().toLowerCase().replace(/^on/, '');
+    if (!this.handlers[eventName]) this.handlers[eventName] = [];
+    this.handlers[eventName].push(func);
+  },
+  
+  setHandCursor: function(enabled) {
+    // enable hand cursor (true), or default arrow cursor (false)
+    this.handCursorEnabled = enabled;
+    if (this.ready) this.movie.setHandCursor(enabled);
+  },
+  
+  setCSSEffects: function(enabled) {
+    // enable or disable CSS effects on DOM container
+    this.cssEffects = !!enabled;
+  },
+  
+  receiveEvent: function(eventName, args) {
+    // receive event from flash
+    eventName = eventName.toString().toLowerCase().replace(/^on/, '');
+        
+    // special behavior for certain events
+    switch (eventName) {
+      case 'load':
+        // movie claims it is ready, but in IE this isn't always the case...
+        // bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function
+        this.movie = document.getElementById(this.movieId);
+        if (!this.movie) {
+          var self = this;
+          setTimeout( function() { self.receiveEvent('load', null); }, 1 );
+          return;
+        }
+        
+        // firefox on pc needs a "kick" in order to set these in certain cases
+        if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) {
+          var self = this;
+          setTimeout( function() { self.receiveEvent('load', null); }, 100 );
+          this.ready = true;
+          return;
+        }
+        
+        this.ready = true;
+        this.movie.setText( this.clipText );
+        this.movie.setHandCursor( this.handCursorEnabled );
+        break;
+      
+      case 'mouseover':
+        if (this.domElement && this.cssEffects) {
+          this.domElement.addClass('hover');
+          if (this.recoverActive) this.domElement.addClass('active');
+        }
+        break;
+      
+      case 'mouseout':
+        if (this.domElement && this.cssEffects) {
+          this.recoverActive = false;
+          if (this.domElement.hasClass('active')) {
+            this.domElement.removeClass('active');
+            this.recoverActive = true;
+          }
+          this.domElement.removeClass('hover');
+        }
+        break;
+      
+      case 'mousedown':
+        if (this.domElement && this.cssEffects) {
+          this.domElement.addClass('active');
+        }
+        break;
+      
+      case 'mouseup':
+        if (this.domElement && this.cssEffects) {
+          this.domElement.removeClass('active');
+          this.recoverActive = false;
+        }
+        break;
+    } // switch eventName
+    
+    if (this.handlers[eventName]) {
+      for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) {
+        var func = this.handlers[eventName][idx];
+      
+        if (typeof(func) == 'function') {
+          // actual function reference
+          func(this, args);
+        }
+        else if ((typeof(func) == 'object') && (func.length == 2)) {
+          // PHP style object + method, i.e. [myObject, 'myMethod']
+          func[0][ func[1] ](this, args);
+        }
+        else if (typeof(func) == 'string') {
+          // name of function
+          window[func](this, args);
+        }
+      } // foreach event handler defined
+    } // user defined handler for event
+  }
+  
 };