http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.ttf
----------------------------------------------------------------------
diff --git 
a/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.ttf 
b/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 0000000..1413fc6
Binary files /dev/null and 
b/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.ttf differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff
----------------------------------------------------------------------
diff --git 
a/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff 
b/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 0000000..9e61285
Binary files /dev/null and 
b/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff2
----------------------------------------------------------------------
diff --git 
a/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 
b/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff2
new file mode 100644
index 0000000..64539b5
Binary files /dev/null and 
b/webapp/curator/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/html/upload-files.html
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/html/upload-files.html 
b/webapp/curator/src/main/webapp/html/upload-files.html
new file mode 100644
index 0000000..f29b664
--- /dev/null
+++ b/webapp/curator/src/main/webapp/html/upload-files.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Curator File Upload</title>
+    <script src="../js/lib/dropzone.js"></script>
+    <link rel="stylesheet" href="../css/lib/dropzone.css">
+  </head>
+  <body>
+    <form action="../services/local-file-upload" class="dropzone" 
id="local-file-upload"></form
+    </div>
+  </body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/icons/directory.png
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/icons/directory.png 
b/webapp/curator/src/main/webapp/icons/directory.png
new file mode 100644
index 0000000..cf31d63
Binary files /dev/null and b/webapp/curator/src/main/webapp/icons/directory.png 
differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/icons/error.jpg
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/icons/error.jpg 
b/webapp/curator/src/main/webapp/icons/error.jpg
new file mode 100644
index 0000000..259365a
Binary files /dev/null and b/webapp/curator/src/main/webapp/icons/error.jpg 
differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/icons/file.png
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/icons/file.png 
b/webapp/curator/src/main/webapp/icons/file.png
new file mode 100644
index 0000000..e107523
Binary files /dev/null and b/webapp/curator/src/main/webapp/icons/file.png 
differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/icons/icon_move_into_cell.png
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/icons/icon_move_into_cell.png 
b/webapp/curator/src/main/webapp/icons/icon_move_into_cell.png
new file mode 100644
index 0000000..7e22c61
Binary files /dev/null and 
b/webapp/curator/src/main/webapp/icons/icon_move_into_cell.png differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/icons/success.jpg
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/icons/success.jpg 
b/webapp/curator/src/main/webapp/icons/success.jpg
new file mode 100644
index 0000000..df6c974
Binary files /dev/null and b/webapp/curator/src/main/webapp/icons/success.jpg 
differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/icons/waiting.gif
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/icons/waiting.gif 
b/webapp/curator/src/main/webapp/icons/waiting.gif
new file mode 100644
index 0000000..2a6336c
Binary files /dev/null and b/webapp/curator/src/main/webapp/icons/waiting.gif 
differ

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/index.html
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/index.html 
b/webapp/curator/src/main/webapp/index.html
new file mode 100644
index 0000000..e2feea7
--- /dev/null
+++ b/webapp/curator/src/main/webapp/index.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<html>
+       <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Celgene Data Curator</title>
+        <script src="lib/require.js" data-main="main.js"></script>
+        <link href="css/bootstrap.min.css" rel="stylesheet">
+        <link rel="stylesheet" type="text/css" href="css/style.css">
+        <link rel="stylesheet" type="text/css" 
href="lib/themes/default/style.min.css" />
+        <link rel="stylesheet" type="text/css" 
href="lib/css/jquery.dataTables_themeroller.css" />
+        <link rel="stylesheet" type="text/css" 
href="lib/css/jquery.dataTables.css" />
+        <link rel="stylesheet" type="text/css" href="lib/css/dropzone.css" />
+        <!-- DA Generator stuff -->
+        <link rel="stylesheet" type="text/css" 
href="/da-generator/bootstrap/css/bootstrap.css">
+        <link rel="stylesheet" type="text/css" 
href="/da-generator/bootstrap/css/bootstrap-theme.css">
+        <link rel="stylesheet" type="text/css" href="/da-generator/style.css">
+
+       </head>
+       <body>
+        <div id="wrapper" class="content container-fluid">
+            <div class="row intro">
+                <div class="col-md-offset-1 col-md-10">
+                    <h1>Celgene Product Curation</h1>
+                    <hr/>
+                    <p>
+                    The OODT CAS-Curator allows the user to ingest files from 
the given staging area. The user may also upload files into the staging area, 
and edit the metadata pertaining to these files before the ingestion takes 
place.
+                    </p>
+                </div>
+            </div>
+            <div class="row">
+                <div class="col-md-4">
+                    <h4>1. Select Files</h4>
+                    <h5>Select files for which to curate metadata and 
ingest.</h5>
+                </div>
+                <div class="col-md-4">
+                    <h4>2. Edit Metadata</h4>
+                    <h5>Edit the metadata associated with the selected files 
and start ingestion.</h5>
+                </div>
+                <div class="col-md-4">
+                    <h4>3. Ingest Files</h4>
+                    <h5>*Coming Soon*: Inspect ingesting files here.</h5>
+                </div>
+            </div>
+            <div class="row">
+                <div id="files" class="panel col-md-4"></div>
+                <div id="metadata-top" class="panel col-md-4">
+                    <div id="extractors"></div>
+                    <div id="metadata"></div>
+                    <div id="met-buttons"></div>
+                </div>
+                <div id="ingest" class="panel col-md-4"></div>
+            </div>
+            <!-- DA Number generator -->
+            <h3 class="big">Celgene Data Asset Number Generator</h3>
+            <div id="danum" class="big">ungenerated</div>
+                <button id="gen" class="btn btn-danger big">Generate DA 
Number</button>
+                <div id="errors"></div>
+            </div>
+       </body>
+</html>

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/Startup.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/Startup.js 
b/webapp/curator/src/main/webapp/js-new/Startup.js
new file mode 100644
index 0000000..ea101dc
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/Startup.js
@@ -0,0 +1,142 @@
+/**
+ * Start-Up module.
+ * 
+ * Allows the caller to pass in the Configuration and thus
+ * change the behavior on stat-up while not needing to re-implement
+ * the below start-up code.
+ * 
+ * @author starchmd
+ */
+define(["jquery",
+        "js-new/models/SetupModels",
+        "js-new/views/TreeView",
+        "js-new/views/UploadView",
+        "js-new/views/MetadataEntryView",
+        "js-new/views/ExtractorView",
+        "js-new/views/MetadataButtonsView",
+        "js-new/views/IngestView",
+        "js-new/control/MetadataControl",
+        "js-new/control/ExtractorControl",
+        "js-new/control/IngestControl",
+        "js-new/control/TreeControl",
+        "js-new/utils/utils",
+        "js-new/config/Configuration",
+        "lib/text! template.html"],
+    
function($,Models,TreeView,UploadView,MetadataEntryView,ExtractorView,MetadataButtonsView,IngestView,MetadataControl,ExtractorControl,IngestControl,TreeControl,utils,Config,html)
 {
+        return function(entryCB,ingestCB) {
+            //Load cookie, and set if nothing
+            GLOBAL_USER = 
document.cookie.replace(/(?:(?:^|.*;\s*)user\s*\=\s*([^;]*).*$)|^.*$/, "$1");
+            if (GLOBAL_USER == "") {
+                GLOBAL_USER = String(new Date().getTime());
+                document.cookie = "user="+GLOBAL_USER;
+            }
+            //Setup templates
+            $("body").append(html);
+            //Build views
+            var views = [
+                    {
+                        "class" :   IngestView,
+                        "params" :  
{"name":"ingest-view","ingest":Models.ingest},
+                        "element" : "ingest"
+                    },
+                    {
+                        "class" :   MetadataEntryView,
+                        "params" :  
{"name":"metadata-view","datamodel":Models.datamodel,"model":Models.metadata,"working-set":Models.working},
+                        "element" : "metadata"
+                    },
+                    {
+                        "class" :   ExtractorView,
+                        "params" :  
{"name":"extractor-view","extractors":Models.extractor},
+                        "element" : "extractors"
+                    },
+                    {
+                        "class" :   UploadView,
+                        "params" :  
{"name":"upload-view","upload":Models.upload,"notify":Models.directory},
+                        "element" : "files"
+                    },
+                    {
+                        "class" :   TreeView,
+                        "params" :  
{"name":"tree-view","directory":Models.directory,"selection":Models.metadata},
+                        "element" : "files"
+                    },
+                    {
+                        "class" :   MetadataButtonsView,
+                        "params" :  
{"name":"btns-view","buttonText":Config.INGEST_BUTTON_TEXT},
+                        "element" : "met-buttons"
+                    },
+                    
+            ];
+            var constructed = {};
+            var view;
+            var jqel;
+            for (var i = 0; i < views.length; i++) {
+                view = views[i];
+                jqel = $("#"+view["element"]);
+                if (jqel.length == 0) {
+                    console.log("Could not find element with id: 
"+view["element"]);
+                    continue;
+                }
+                view["params"]["el"] = jqel;
+                constructed[view["params"]["name"]] = new 
view["class"](view["params"]);
+            }
+            //Build controllers
+            var controls = [
+                    {
+                        "name" :    "metadata-control",
+                        "class" :   MetadataControl,
+                        "params" :  
[constructed["metadata-view"],constructed["tree-view"],constructed["btns-view"],Models.metadata,Models.ingest,Models.working,Models.directory]
+                    },
+                    {
+                        "name" :    "extractor-control",
+                        "class" :   ExtractorControl,
+                        "params" :  
[constructed["extractor-view"],Models.extractor,Models.metadata]
+                    },
+                    {
+                        "name" :    "ingest-control",
+                        "class" :   IngestControl,
+                        "params" :  
[constructed["ingest-view"],Models.ingest,constructed["btns-view"]]
+                    },
+                    {
+                        "name" :    "tree-control",
+                        "class" :   TreeControl,
+                        "params" :  
[constructed["tree-view"],constructed["metadata-view"]]
+                    }
+            ];
+            var control;
+            for (var i = 0; i < controls.length; i++) {
+                control = controls[i];
+                if (typeof(control["params"][0]) == "undefined")
+                    continue;
+                control["params"].splice(0,0,null);
+                constructed[control["name"]] = new 
(Function.prototype.bind.apply(control["class"],control["params"]));  
+            }
+            //Setup bindings and callbacks
+            if (typeof(constructed["extractor-view"]) != "undefined") {
+                
Models.extractor.fetch({"success":constructed["extractor-view"].render.bind(constructed["extractor-view"])});
+            }
+            if (typeof(constructed["ingest-view"]) != "undefined") {
+                Models.refresh(constructed["ingest-view"]);
+                
//setInterval(Models.refresh.bind(Models,constructed["ingest-view"]),Config.FILE_SYSTEM_REFRESH_INTERVAL);
+                if (typeof(constructed["metadata-control"]) != "undefined") {
+                    constructed["metadata-control"].enableIngestWait();
+                }
+            }
+            if (typeof(constructed["metadata-view"]) != "undefined") {
+                constructed["metadata-control"].setEntryCallback(entryCB);
+                constructed["metadata-control"].setIngestCallback(ingestCB);
+                
Models.datamodel.fetch({"success":constructed["metadata-view"].render.bind(constructed["metadata-view"],true)});
+            }
+            //If we are not allowed to select files, use /dev/null 0 length 
system files
+            if (typeof(constructed["tree-view"]) == "undefined") {
+                Models.metadata.add({"id":"/dev/null"});
+            }
+            //Render upload view
+            if (typeof(constructed["upload-view"]) != "undefined") {
+                constructed["upload-view"].render();
+            }            
+            //Returns a function to allow external data entry
+            return function(name,value) {
+                
constructed["metadata-control"].dataEntry.call({"name":name,"value":value});
+            }
+        }
+    });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/config/Configuration.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/config/Configuration.js 
b/webapp/curator/src/main/webapp/js-new/config/Configuration.js
new file mode 100644
index 0000000..d034678
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/config/Configuration.js
@@ -0,0 +1,80 @@
+/**
+ * Configuration object for javascript application
+ * @author starchmd
+ */
+_GLOBAL_CONFIG_VAR = null;
+define(["jquery"],
+    //TODO: Return configuration from server via REST call
+    /**
+     * Configuration object
+     */
+    function($) {
+        /**
+         * Configuration object acts like a map of configuration
+         * keys to values, with the added function addConfig to allow adding 
keys.
+         * 
+         * Note: A Singleton configuration is created and returned to all.
+         */
+        function ConfigObject() {
+            var _self = this;
+
+            var path = $(location).attr("pathname");
+            //Basic metadata
+            _self.SETUP_CONFIG = {
+                "hidden":[],
+                "presets":{}
+            },
+            _self.METADATA_REST_SERVICE = "services/metadata";
+            _self.DIRECTORY_REST_SERVICE = "services/directory";
+            _self.EXTRACTOR_REST_SERVICE = "services/metadata/extractors";
+            _self.INGEST_REST_SERVICE = "services/ingest";
+            _self.VALIDATION_REST_SERVICE = "services/validation";
+            //Dropzone requires full path
+            _self.UPLOAD_REST_SERVICE = 
path.substr(0,path.lastIndexOf("/")+1)+"services/upload/file";
+            _self.FILE_SYSTEM_REFRESH_INTERVAL = 1000;
+            _self.DEFAULT_TYPE = "GenericFile";
+            _self.STAGING_BASE="/"
+            
+            _self.addConfig = 
+                /**
+                 * Add configuration
+                 * @param conf - anonymous object to copy into config
+                 */
+                function(conf) {
+                    for (var key in conf) {
+                        //Add key if not there
+                        if (typeof(_self[key]) == "object" && 
typeof(conf[key]) == "object") {
+                            for (var child in conf[key]) {
+                                _self[key][child] = conf[key][child]; 
+                            }  
+                        } else if (typeof(_self[key]) != "object") {
+                            _self[key] = conf[key];
+                        }
+                    }
+                };
+            _self.addPreset = 
+                /**
+                 * Adds a preset to the configuration
+                 * @param key - key to assign preset to
+                 * @param value - value of preset
+                 */
+                function(key,value) {
+                    //If config is malformed reconstruct right keys
+                    if (!("SETUP_CONFIG" in _self)) {
+                        _self.SETUP_CONFIG = {"hidden":[],"presets":{}};
+                    }
+                    if (!("presets" in _self.SETUP_CONFIG)) {
+                        _self.SETUP_CONFIG["presets"] = {}
+                    }
+                    _self.SETUP_CONFIG["presets"][key] = value;
+                };
+                
+        }
+        /**
+         * Global configuration singleton
+         */
+        if (_GLOBAL_CONFIG_VAR == null) {
+            var _GLOBAL_CONFIG_VAR = new ConfigObject();
+        }
+        return _GLOBAL_CONFIG_VAR;
+    });

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/control/ExtractorControl.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/control/ExtractorControl.js 
b/webapp/curator/src/main/webapp/js-new/control/ExtractorControl.js
new file mode 100644
index 0000000..005f486
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/control/ExtractorControl.js
@@ -0,0 +1,33 @@
+/**
+ * A controller mechanism
+ * @author starchmd
+ */
+define(["jquery"],
+    function($) {
+        /**
+         * Controller for matching datamodel, metadata model to metadataview.
+         */
+        return function(view,model,metadata) {
+            var _self = this;
+            _self.view = view;
+            _self.model = model;
+            _self.metadata = metadata;
+            
+            _self.model.on("change:extractors",_self.view.render,_self.view);
+            _self.onchange = 
+                /**
+                 * What the controller does upon change in extractor.
+                 * @param e - item changed
+                 */
+                function(e) {
+                    var value = this.value;
+                    _self.model.set("selected",value);
+                    _self.view.render();
+                    _self.metadata.each(
+                        function(elem) {
+                            elem.fetch();
+                        });
+                };
+            _self.view.setOnChangeFunction(_self.onchange);
+        }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/control/IngestControl.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/control/IngestControl.js 
b/webapp/curator/src/main/webapp/js-new/control/IngestControl.js
new file mode 100644
index 0000000..e8b8910
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/control/IngestControl.js
@@ -0,0 +1,41 @@
+/**
+ * A controller mechanism
+ * @author starchmd
+ */
+define(["jquery", "js-new/utils/EventBus"],
+    function($, EventBus) {
+        /**
+         * Controller for matching datamodel, metadata model to metadataview.
+         */
+        return function(view,model,buttons) {
+            var _self = this;
+            _self.view = view;
+            _self.model = model;
+            _self.buttons = buttons;
+
+            _self.onclick = 
+                /**
+                 * What the controller does upon click of clear.
+                 * @param e - item changed
+                 */
+                function(e) {
+                    _self.model.sync("delete",_self.model);
+                    _self.model.fetch();
+                };
+            _self.onRefresh = 
+                /**
+                 * What to do on refresh
+                 */
+                function() {
+                    _self.buttons.setIngesting(false);
+                    _self.buttons.render();
+                };
+            _self.view.setOnRefresh(_self.onRefresh);
+            _self.view.setIngestClear(_self.onclick);
+
+            EventBus.events.bind('ingest:execute', function(){
+                
_self.model.fetch({"success":_self.view.render.bind(_self.view)});
+            });
+
+        }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/control/MetadataControl.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/control/MetadataControl.js 
b/webapp/curator/src/main/webapp/js-new/control/MetadataControl.js
new file mode 100644
index 0000000..9d558ae
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/control/MetadataControl.js
@@ -0,0 +1,209 @@
+/**
+ * A controller mechanism
+ * @author starchmd
+ */
+define(["jquery","js-new/utils/utils", "js-new/config/Configuration", 
"js-new/utils/EventBus", "blockui"],
+    function($,utils, Configuration, EventBus) {
+        /**
+         * Controller for matching datamodel, metadata model to metadataview.
+         * @param view - metadata entry view
+         * @param tree - file tree view
+         * @param model - metadata model
+         * @param ingest - ingesting view
+         * @param buttons - metadata buttons view
+         * @param working - working set metadata (instance of metadata model)
+         * @param directory - directory model
+         */
+        return function(view,tree,buttons,model,ingest,working,directory) {
+            var _self = this;
+            _self.view = view;
+            _self.tree = tree;
+            _self.model = model;
+            _self.model.on("remove 
reset",_self.view.render.bind(_self.view,false));
+            _self.ingest = ingest;
+            _self.buttons = buttons;
+            _self.working = working;
+            _self.directory = directory;
+            _self.waitOnIngest = false;
+            _self.ingestCB = function(){};
+            _self.entryCB = function(){};
+            
+            _self.dataEntry = 
+                /**
+                 * What the controller does upon data entry.
+                 * @param e
+                 */
+                function(e) {
+                    if(e!=undefined) {
+                        $.blockUI({
+                            css: {
+                                border: 'none',
+                                padding: '15px',
+                                backgroundColor: '#000',
+                                '-webkit-border-radius': '10px',
+                                '-moz-border-radius': '10px',
+                                opacity: .5,
+                                color: '#fff'
+                            }
+                        });
+                    }
+                    var value = this.value;
+                    var name = this.name;
+                    var filler = "filler" in this && this.filler;
+                    var fullRefreshNeeded = false;
+                    //Loop through all the selected files setting metadata
+                    var updateModel = function(elem) {
+                        var root = elem.get("root");
+                        if (typeof(name) != "undefined" && name in 
root.children && (!filler || root.children[name].values[0]=="")) {
+                            root.children[name].values[0] = value;
+                            if (name == "ProductType" && value != 
_self.view.getProductType()) {
+                                _self.view.setProductType(value);
+                                fullRefreshNeeded = true;
+                            }
+                        } else if (typeof(name) != "undefined" && !(name in 
root.children)) {
+                            root.children[name] = 
{"name":name,"values":[value],"children":{}}
+                            if (name == "ProductType" && value != 
_self.view.getProductType()) {
+                                _self.view.setProductType(value);
+                                fullRefreshNeeded = true;
+                            }
+                        }
+                        //Update from presets
+                        utils.updateFromPresets(elem);
+                    };
+                    //Save real models
+                    _self.model.each(function(elem) {
+                        updateModel(elem);
+                        elem.save(null,{"success":
+                            function(){
+                                if ("collection" in elem) {
+                                    elem.fetch({"success": function(){
+                                        
_self.view.render.bind(_self.view,false);
+                                        $.unblockUI();
+                                    }
+                                    });
+                                }
+                            },"validate": false});                        
+                        });
+                    updateModel(_self.working);
+                    if (typeof(value) != "undefined" && typeof(name) != 
"undefined" && value != null && name != null)
+                    {
+                      //  _self.entryCB(name,value);
+                    }
+                    //Render should be delayed, allowing focus to trigger
+                    
setTimeout(_self.view.render.bind(_self.view,fullRefreshNeeded),1);
+                };
+            _self.ingestClick =
+                /**
+                 * What to do on ingest click
+                 * @param e - event (likely ignored)
+                 */
+                function(e) {
+                    var selects = [];
+                    var torm = [];
+                    var valid = true;
+                    _self.model.each(
+                        function(elem) {
+                            try {
+                                valid = valid && elem.isValid();
+                                if (!valid) {
+                                    return;
+                                }
+                                var timestamp = new Date().getTime();
+                                var id = elem.get("id");
+                                var productName = 
elem.get("root")["children"]["ProductName"]["values"][0];
+                                
selects.push({"timestamp":timestamp,"file":id,"size":0,"pname":productName});
+                                if (elem.id != "/dev/null") {
+                                    torm.push(elem);
+                                }
+                            } catch(err) {
+                                console.log("Error: Failed to parse 
ingestibles"+err);
+                            }
+                        }
+                    );
+                    //Run ingest, removing selected files from collection
+                    if (valid && selects.length > 0) {
+                        if (_self.waitOnIngest) {
+                            _self.buttons.setIngesting(true);
+                            _self.buttons.render();
+                        }
+                        _self.ingest.save({"entries":selects}, {
+                            dataType:'text',
+                            success: function(){
+                            EventBus.events.trigger('ingest:execute');
+
+                        },
+                        error: function(data, xhr){
+                            console.log(xhr);
+                        }});
+                        setTimeout(_self.ingestCB.bind(null,selects),50);
+                        for (var i = 0; i < torm.length; i++) {
+                            _self.model.remove(torm[i]);
+                            
_self.directory.rmFile(Configuration.STAGING_BASE+torm[i].id)
+                        }
+                        if (typeof(_self.tree) != "undefined") {
+                            _self.tree.render();
+                        }
+                    }
+                    _self.view.render(true);
+                };
+                _self.metaClear =
+                    /**
+                     * What to do on metadata clear
+                     * @param e - event (likely ignored)
+                     */
+                    function(e) {
+                        var destroy = [];
+                        //Clear working set
+                        _self.working.get("root").children = {};
+                        var ids = [];
+                        _self.model.each(
+                            function(elem) {
+                                destroy.push(elem);
+                                ids.push(elem.get("id"));
+                            }
+                        );
+                        //Destroy first
+                        for (var i = 0; i < destroy.length; i++) {
+                            destroy[i].destroy();
+                        }
+                        //Then render
+                        _self.view.render();
+                        //Now add back, and refetch
+                        for (var i = 0; i < ids.length; i++) {
+                            _self.model.add({"id":ids[i]});
+                        }
+                        //Refresh and update view
+                        _self.model.each(
+                            function(elem) {
+                                
elem.fetch({"success":_self.view.render.bind(_self.view,false)});
+                            });
+                    };
+             _self.enableIngestWait = 
+                 /**
+                  * Enables the ingesting wait
+                  */
+                 function() {
+                     _self.waitOnIngest = true;
+                 };
+             _self.setIngestCallback = 
+                 /**
+                  * Enables callbacks on ingest
+                  * @param ingestCB - ingestCB
+                  */
+                 function(ingestCB) {
+                     _self.ingestCB = ingestCB;
+                 };
+             _self.setEntryCallback = 
+                 /**
+                  * Enables callback on entry
+                  * @param entryCB - ingestCB
+                  */
+                 function(entryCB) {
+                     _self.entryCB = entryCB;
+                 };
+                
+            _self.view.setOnEntryFunction(_self.dataEntry);
+            _self.buttons.setIngestClick(_self.ingestClick);
+            _self.buttons.setMetadataClear(_self.metaClear);
+        }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/control/TreeControl.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/control/TreeControl.js 
b/webapp/curator/src/main/webapp/js-new/control/TreeControl.js
new file mode 100644
index 0000000..feb5c30
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/control/TreeControl.js
@@ -0,0 +1,29 @@
+/**
+ * A controller mechanism for the tree view
+ * @author starchmd
+ */
+define(["jquery", "js-new/utils/EventBus"],
+    function($, EventBus) {
+        /**
+         * 
+         */
+        return function(tree,metadata) {
+            var _self = this;
+            _self.tree = tree;
+            _self.metadata = metadata;
+            
+            _self.onupdate = 
+                /**
+                 * What the controller does upon update of the tree view
+                 */
+                function() {
+                    _self.metadata.render(true);
+                };
+           
+            _self.tree.setUpdate(_self.onupdate);
+
+            EventBus.events.bind('ingest:execute', function(){
+                _self.tree.refresh_tree();
+            });
+        }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/DirectoryModel.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/DirectoryModel.js 
b/webapp/curator/src/main/webapp/js-new/models/DirectoryModel.js
new file mode 100644
index 0000000..28b8656
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/DirectoryModel.js
@@ -0,0 +1,53 @@
+/**
+ * A model that represents a directory listing.
+ * @author starchmd
+ */
+define(["jquery",
+        "underscore",
+        "lib/backbone",
+        "js-new/config/Configuration"],
+    function($,_,Backbone,Config) {
+        
+        function rmFile(path, children) {
+            var o;
+            if(children === undefined) {
+                var o = this.get("files");
+            }
+            else{
+                o = children;
+            }
+            if( o.path === path ){
+                return o;
+            }
+            var result, p;
+            for (p in o.children) {
+                if( typeof o.children[p] === 'object' ) {
+                    result = rmFile(path,o.children[p]);
+                    if(result){
+                        o.children.splice(p, 1);
+                        return;
+                    }
+                }
+            }
+            return result;
+        }
+        /**
+         * Parse the results of the REST-call from the file request
+         * @param response - JSON response from directory listing
+         */
+        function parse(response) {
+            if (!_.isEqual(response,this.get("files")))
+                return {"files":response};
+            return {};
+        };
+        /**
+         * Backbone instance 
+         */
+        return Backbone.Model.extend({
+            "urlRoot":Config.DIRECTORY_REST_SERVICE,
+            "defaults":{"files":{}},
+            "parse":parse,
+            "rmFile":rmFile
+            });
+    }
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/ExtractorModel.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/ExtractorModel.js 
b/webapp/curator/src/main/webapp/js-new/models/ExtractorModel.js
new file mode 100644
index 0000000..d0e9241
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/ExtractorModel.js
@@ -0,0 +1,30 @@
+/**
+ * A backbone model that wraps the list of extractors
+ * @author starchmd
+ */
+define(["jquery",
+        "underscore",
+        "lib/backbone",
+        "js-new/config/Configuration"],
+    function($,_,Backbone,Config) {
+        /**
+         * Parse results of extractor REST call
+         * @return extractor model
+         */
+        function parse(result) {
+            var tmp = this.get("extractors");
+            if (!_.isEqual(tmp,result)) {
+                return 
{"extractors":result,"selected":result[result.length-1]};
+            }
+            return {};
+        }
+        /**
+         * Backbone model for extractors
+         */
+        return Backbone.Model.extend({
+                "url":Config.EXTRACTOR_REST_SERVICE,
+                "parse":parse,
+                "defaults":{"extractors":[]}
+            });
+    }
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/IngestModel.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/IngestModel.js 
b/webapp/curator/src/main/webapp/js-new/models/IngestModel.js
new file mode 100644
index 0000000..37996ef
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/IngestModel.js
@@ -0,0 +1,55 @@
+/**
+ * A backbone model that wraps the ingest control
+ * @author starchmd
+ */
+define(["jquery",
+        "underscore",
+        "lib/backbone",
+        "js-new/config/Configuration"],
+    function($,_,Backbone,Config) {
+        /**
+         * Generate an ingest URL
+         */
+        function url() {
+            var query = "";
+            if (GLOBAL_USER != "" && typeof(GLOBAL_USER) !== "undefined") {   
+                query += ((query == "")?"?":"&")+ "user="+GLOBAL_USER;
+            }
+            return Config.INGEST_REST_SERVICE + query;
+        }
+        /**
+         * Parse a model
+         */
+        function parse(response) {
+            var ret = [];
+            var map = {};
+            if (typeof(this.get("status")) !== "undefined") {
+                var key = "";
+                /*for (var i = 0; i < this.get("status").length; i++) {
+                    key = 
this.get("status")[i]["timestamp"]+"-"+this.get("status")[i]["file"];
+                    ret.push(this.get("status")[i]);
+                    map[key] = i;
+                }*/
+                if(typeof response != "string") {
+                    for (var i = 0; i < response["status"].length; i++) {
+                        //key = 
response["status"][i]["timestamp"]+"-"+response["status"][i]["file"];
+                        //if (key in map) {
+                        //    ret[map[key]] = response["status"][i];
+                        //} else {
+                        ret.push(response["status"][i]);
+                        //}
+                    }
+                }
+            }
+            return {"status":ret};
+        }
+        /**
+         * Backbone model for ingest
+         */
+        return Backbone.Model.extend({
+                "url":url,
+                "defualts": {"status":[]},
+                "parse":parse
+            });
+    }
+);

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/MetadataCollection.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/MetadataCollection.js 
b/webapp/curator/src/main/webapp/js-new/models/MetadataCollection.js
new file mode 100644
index 0000000..58f4636
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/MetadataCollection.js
@@ -0,0 +1,29 @@
+/**
+ * Model representing metadata collection and backend
+ * @author starchmd
+ */
+define(["jquery",
+        "underscore",
+        "lib/backbone",
+        "js-new/models/MetadataModel",
+        "js-new/config/Configuration"],
+    function($,_,Backbone,Metadata,Config) {
+        /**
+         * Initialize the collection, with extractors
+         * @param options - options defining extractors
+         */
+        function init(dumby,options) {
+            //Replicate options locally
+            for (var key in options)
+                this[key] = options[key];
+        };
+        /**
+         * Backbone Metadata collection
+         */
+        return Backbone.Collection.extend({
+            "initialize":init,
+            "model":Metadata,
+            "url":Config.METADATA_REST_SERVICE
+        });
+    }
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/MetadataDataModel.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/MetadataDataModel.js 
b/webapp/curator/src/main/webapp/js-new/models/MetadataDataModel.js
new file mode 100644
index 0000000..aa244dd
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/MetadataDataModel.js
@@ -0,0 +1,44 @@
+/**
+ * This model represents the data model of the metadata structure.
+ * It should read from the Validation Rest end point.
+ * 
+ * @starchmd
+ */
+define(["lib/backbone",
+        "js-new/utils/utils",
+        "js-new/config/Configuration"],
+    function(Backbone,utils,Config) {
+        /**
+         * Parse the response
+         * @param response - response from the backend server
+         */
+        function parse(response) {
+            //Modify the response
+            for (type in response) {
+                //Cleanse the results of CAS. from element fields
+                for (var i = 0; i < response[type].length; i++) {
+                    if (response[type][i].elementName.indexOf("CAS.") == 0) {
+                        response[type][i].elementName = 
response[type][i].elementName.replace("CAS.","");
+                    }
+                }
+                //Apply hidden elements
+                var toHide = utils.getHiddenConfigFields();
+                for (var i = 0; i < toHide.length; i++) {
+                    for (var j = 0; j < response[type].length; j++) {
+                        if (toHide[i] == response[type][j].elementName)
+                        {
+                            response[type][j].attachments["hidden"] = "";
+                        }
+                    }
+                }
+            }
+            return {"types":response};
+        };
+        
+        //Return very basic model
+        return Backbone.Model.extend({
+                "defaults":{"types":{}},
+                "url":Config.VALIDATION_REST_SERVICE,
+                "parse": parse
+            });
+    });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/MetadataModel.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/MetadataModel.js 
b/webapp/curator/src/main/webapp/js-new/models/MetadataModel.js
new file mode 100644
index 0000000..3de1b15
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/MetadataModel.js
@@ -0,0 +1,93 @@
+/**
+ * Model representing metadata backend
+ * @author starchmd
+ */
+define(["jquery",
+        "underscore",
+        "lib/backbone",
+        "js-new/config/Configuration"],
+    function($,_,Backbone,Config) {
+    
+        /**
+         * Initialize function
+         */
+        function init(options) {
+            for (var key in options)
+                this[key] = options[key];
+        };
+        /**
+         * Parse the REST-call for metadata
+         * @param response - JSON metadata object
+         * @return metadata under the root node
+         */
+        function parse(response) {
+            var ret = {}; 
+            if (typeof(response["root"].children) != "undefined" && 
typeof(response["root"].children.fill) != "undefined") {
+                ret["fill"] = response["root"].children.fill.values[0];
+                delete response["root"].children.fill;
+            }
+            ret["root"] = response["root"];
+            return ret;
+        }
+        /**
+         * Return the url for the Metadata object
+         * @return url to the Metadata REST service
+         */
+        function url() {
+            var extractor = this.collection.extractors.get("selected");
+            var query = "";
+            if (extractor != "" && extractor != null && typeof(extractor) !== 
"undefined")
+                query += "?extractor="+extractor;
+            if (GLOBAL_USER != "" && typeof(GLOBAL_USER) !== "undefined") {   
+                query += ((query == "")?"?":"&")+"user="+GLOBAL_USER;
+            }
+            return Config.METADATA_REST_SERVICE+"/"+this.get("id")+query;
+        }
+        /**
+         * Validation function to validate Metadata Model attributes
+         * @param attrs - attributes to validate
+         * @param options - ??
+         * @returns validation successful true/false
+         */
+        function validate(attrs,options) {
+            //Get validation setup
+            var type = ("root" in attrs && "ProductType" in 
attrs["root"].children
+                        && attrs["root"].children["ProductType"].values.length 
>= 1) ?
+                        attrs["root"].children["ProductType"].values[0] : 
"GenericFile";
+            var dataModel = this.collection["datamodel"].get("types")[type];
+            //Check all the fields
+            error = {};
+            for (var i = 0; i < dataModel.length; i++) {
+                var attachments = dataModel[i].attachments;
+                var key = dataModel[i].elementName;
+                if ("required" in attachments && 
+                   (!(key in attrs["root"].children) || 
+                    (attrs["root"].children[key].values.length == 0) ||
+                    (attrs["root"].children[key].values[0] == ""))) {
+                    error[key] = key +" is required.";
+                } else if ("values" in attachments && 
+                       (!(key in attrs["root"].children) || 
+                        (attrs["root"].children[key].values.length == 0) ||
+                        
(attachments.values.split(",").indexOf(attrs["root"].children[key].values[0]) 
== -1))) {
+                    var tmptmptmp = attachments.values.split(",");
+                    error[key] = key +" must be one of: "+attachments.values;
+                }
+            }
+            //Valid only returns something on error
+            if (!($.isEmptyObject(error))) {
+                return error;
+            }
+        };
+        /**
+         * Backbone metadata object
+         */
+         return Backbone.Model.extend({
+            "initialize":init,
+            "parse":parse,
+            "url":url,
+            "defaults":{"root":{"name":"root","values":[],"children":{}}},
+            "validate":validate
+        });
+
+    }
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/SetupModels.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/SetupModels.js 
b/webapp/curator/src/main/webapp/js-new/models/SetupModels.js
new file mode 100644
index 0000000..cfe4178
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/SetupModels.js
@@ -0,0 +1,36 @@
+/**
+ * Module to load all the models and return them
+ * @author starchmd
+ */
+define(["js-new/models/DirectoryModel",
+        "js-new/models/MetadataModel",
+        "js-new/models/MetadataCollection",
+        "js-new/models/ExtractorModel",
+        "js-new/models/UploadModel",
+        "js-new/models/IngestModel",
+        "js-new/models/MetadataDataModel",
+        "js-new/utils/utils"],
+    
function(DirectoryModel,Metadata,MetadataCollection,ExtractorCollection,UploadModel,IngestModel,MetadataDataModel,utils)
 {
+        /**
+         * Return a set of happy models
+         */
+        var models = {
+            "directory":new DirectoryModel({"id":"files"}),
+            "extractor":new ExtractorCollection([],{"id":"extractor"}),
+            "upload": UploadModel,
+            "ingest": new IngestModel({"id":"ingest"}),
+            "datamodel" : new MetadataDataModel({"id":"datamodel"}),
+            "refresh" : function(inview) {
+                    models.directory.fetch();
+                    
models.ingest.fetch({"success":inview.render.bind(inview)});
+                },
+            "refreshTree": function(){
+                models.directory.fetch();
+            },
+            "working": new Metadata({"id":"working-set"})
+        };
+        //models.datamodel.fetch();
+        models.metadata = new 
MetadataCollection([],{"id":"metadata","extractors":models.extractor,"datamodel":models.datamodel});
+        return models;
+    }
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/models/UploadModel.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/models/UploadModel.js 
b/webapp/curator/src/main/webapp/js-new/models/UploadModel.js
new file mode 100644
index 0000000..136ee50
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/models/UploadModel.js
@@ -0,0 +1,12 @@
+/**
+ * Non-backbone "model" for dropzone upload
+ * @author starchmd
+ */
+define(["js-new/config/Configuration"],
+    /**
+     * Non-backbone model REST hookup
+     */
+    function(Config) {
+        return {"url": Config.UPLOAD_REST_SERVICE};
+    }
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/nls/root/ui.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/nls/root/ui.js 
b/webapp/curator/src/main/webapp/js-new/nls/root/ui.js
new file mode 100644
index 0000000..2f6fde9
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/nls/root/ui.js
@@ -0,0 +1,3 @@
+define({
+    "title": "Linux Computer Something System (LCARS)"
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/nls/ui.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/nls/ui.js 
b/webapp/curator/src/main/webapp/js-new/nls/ui.js
new file mode 100644
index 0000000..b415c33
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/nls/ui.js
@@ -0,0 +1,4 @@
+define({
+    "root": true,
+    "zh-tw": true
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/nls/zh-tw/ui.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/nls/zh-tw/ui.js 
b/webapp/curator/src/main/webapp/js-new/nls/zh-tw/ui.js
new file mode 100644
index 0000000..da15929
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/nls/zh-tw/ui.js
@@ -0,0 +1,3 @@
+define({
+    "title": "Linux 電腦工作系統 (LCARS)"
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/utils/EventBus.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/utils/EventBus.js 
b/webapp/curator/src/main/webapp/js-new/utils/EventBus.js
new file mode 100644
index 0000000..e5f2af0
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/utils/EventBus.js
@@ -0,0 +1,10 @@
+/**
+ * Created by bugg on 20/05/16.
+ */
+
+define(["jquery","underscore","lib/backbone"],
+    function($,_, Backbone) {
+        return {
+            events: _.extend({}, Backbone.Events)
+        }
+    });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/utils/utils.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/utils/utils.js 
b/webapp/curator/src/main/webapp/js-new/utils/utils.js
new file mode 100644
index 0000000..c777b57
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/utils/utils.js
@@ -0,0 +1,114 @@
+/**
+ * Utility functions for use in the rest of the project
+ * @author starchmd
+ */
+define(["underscore","js-new/config/Configuration"],
+    function(_,Config) {
+        /**
+         * Function used to deep-clone an object with circular-reference 
detection
+         * @param stack - stack to look for circular-reference
+         * @param object - jsObject to deep clone
+         * @param callback - (optional) callback to run on each child
+         */     
+        function deepHelper(stack,object,callback) {
+            var tpe = typeof(object);
+            if (["string","number","undefined", "boolean"].indexOf(tpe) != -1)
+                return object;
+            else if (stack.indexOf(object) != -1)
+                throw "Circular reference detected";
+            stack.push(object);
+            //Shallow clone this object
+            object = _.clone(object);
+            //Clone all children
+            for (var key in object) {
+                object[key] = deepHelper(stack,object[key],callback);
+            }
+            //Call the callback on the newly cloned object
+            if (typeof(callback) != "undefined")
+                callback(object);
+            return object;
+        }
+        /**
+         * Function used to deep-clone an object
+         * @param object - jsObject to deep clone
+         * @param callback - (optional) callback to run on each child
+         */  
+        function deep(object,callback) {
+            return deepHelper([],object,callback);
+        }
+        /**
+         * Harvest query parameters
+         */
+        function queryParameters() {
+            var vars = {"hidden":[]};
+            if (window.location.href.indexOf('?') >= 0)
+            {
+                var pairs = 
window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
+                for(var i = 0; i < pairs.length; i++)
+                {
+                    var key = pairs[i].split('=')[0];
+                    var value = pairs[i].split('=')[1];
+                    if (!(key in vars))
+                        vars[key] = [];
+                    vars[key].push(value);
+                }
+            }
+            return vars;
+        }
+        /**
+         * Returns a function-callback mediator 
+         * @param context - context to bind to
+         * @param functionName - name of function to call
+         * @returns a function, bound to the context which will call the named 
function, if not null
+         */
+        function getMediator(context, functionName) {
+            var mediator = function(e) {
+                if (context[functionName] != null)
+                    context[functionName].call(this,e);
+            };
+            return mediator;
+        }
+        
+        
+        /**
+         * Get the hidden fields
+         */
+        function getHiddenConfigFields() {
+            //Configuration must be well formed
+            if (!("SETUP_CONFIG" in Config) || !("hidden" in 
Config["SETUP_CONFIG"]))
+            {
+                return [];
+            }
+            return Config["SETUP_CONFIG"]["hidden"];
+            
+        };
+        /**
+         * Handle query parameters from the above
+         */
+        function getPresetConfigFields() {
+            var ret = {};
+            if (("SETUP_CONFIG" in Config) && ("presets" in 
Config["SETUP_CONFIG"]))
+            {
+                ret = Config["SETUP_CONFIG"]["presets"];
+            }
+            return ret;
+        };
+        /**
+         * Update presets into metadata
+         * @param metadata - metadata model to fill with presets
+         */
+        function updateFromPresets(metadata) {
+            var presets = getPresetConfigFields();
+            for (var key in presets) {
+                var pval = presets[key];
+                if (typeof(pval) == "function") {
+                    pval = pval(metadata);
+                }
+                if (pval != null) {
+                    metadata.get("root").children[key] = 
{"children":{},"values":[pval],"name":key};
+                }
+            }
+        };
+        return 
{"deep":deep,"queryParameters":queryParameters,"getMediator":getMediator,"getHiddenConfigFields":getHiddenConfigFields,"getPresetConfigFields":getPresetConfigFields,"updateFromPresets":updateFromPresets};
+    }
+);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/views/ExtractorView.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/views/ExtractorView.js 
b/webapp/curator/src/main/webapp/js-new/views/ExtractorView.js
new file mode 100644
index 0000000..173357f
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/views/ExtractorView.js
@@ -0,0 +1,41 @@
+define(["jquery",
+        "underscore",
+        "lib/backbone",
+        "datatables",
+        "js-new/utils/utils"],
+    function($,_,Backbone,DataTable,utils) {
+        /**
+         * Init this view
+         * @param options - map to
+         *     {
+         *         extractors - extractors model
+         *     }
+         */
+        function init(options) {
+            this._template = 
_.template($("script#template-extractor-list").html());
+            this.extractors = options["extractors"];
+            this.onchange = null;           
+        };
+        /**
+         * Set the function to call "on-change"
+         * @param func - function to change
+         */
+        function setOnChangeFunction(func) {
+            this.onchange = func;
+        };
+        /**
+         * Render the extractor list
+         */
+        function render() {
+            this.$el.html(this._template({"model":this.extractors}));
+            
$(this.$el).find("select").on("change",utils.getMediator(this,"onchange"));
+        };
+        /**
+         * Extractor view
+         */
+        return Backbone.View.extend({
+            initialize: init,
+            render: render,
+            setOnChangeFunction: setOnChangeFunction
+        });
+    });

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/views/IngestView.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/views/IngestView.js 
b/webapp/curator/src/main/webapp/js-new/views/IngestView.js
new file mode 100644
index 0000000..6c661fa
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/views/IngestView.js
@@ -0,0 +1,61 @@
+/**
+ * View for ingesting files files
+ * @author starchmd
+ */
+define(["jquery",
+        "underscore",
+        "lib/backbone","js-new/utils/utils", "js-new/utils/EventBus"],
+    function($,_,Backbone,utils,EventBus) {
+        var initCall = false;
+        /**
+         * Initialize function
+         * @param options - options for initialization
+         */
+        function init(options) {
+            var _self = this;
+            //Replicate options locally
+            for (var key in options)
+                this[key] = options[key];
+            this.ingest.on("change:status",this.render,this);
+            var htmlText = $("script#template-ingesting").html();
+            this._template = _.template(htmlText);
+            this.onRefresh = function(){};
+            this._ingestClear = null;
+        };
+        /**
+         * Render the view
+         */
+        function render() {
+            this.onRefresh();
+            
this.$el.html(this._template({"statuses":this.ingest.get("status")}));
+            
$(this.$el).find("button#ingest-clear-errors").on("click",utils.getMediator(this,"_ingestClear"));
+            if(!initCall){
+                EventBus.events.trigger('ingest:execute');
+                initCall=true;
+            }
+        };
+        /**
+         * A function to set the "on click" for ingest clear button
+         * @param func - function to call back (should come from controller)
+         */
+        function setIngestClear(func) {
+            this._ingestClear = func;
+        };
+        /**
+         * A function to set "on refresh" function from controller
+         * @param func - function to call
+         */
+        function setOnRefresh(func) {
+            this.onRefresh = func;
+        }
+        /**
+         * Return uploads views
+         */
+        return Backbone.View.extend({
+            initialize: init,
+            render: render,
+            setIngestClear: setIngestClear,
+            setOnRefresh: setOnRefresh
+        });
+    }
+);

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/views/MetadataButtonsView.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/views/MetadataButtonsView.js 
b/webapp/curator/src/main/webapp/js-new/views/MetadataButtonsView.js
new file mode 100644
index 0000000..b737a71
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/views/MetadataButtonsView.js
@@ -0,0 +1,72 @@
+/**
+ * View for ingest and clear buttons
+ * @author starchmd
+ */
+define(["jquery",
+        "underscore",
+        "lib/backbone","js-new/utils/utils"],
+    function($,_,Backbone,utils) {
+        /**
+         * Initialize function
+         * @param options - options for initialization
+         */
+        function init(options) {
+            var _self = this;
+            //Replicate options locally
+            for (var key in options)
+                this[key] = options[key];
+            this.ingesting = false;
+            this._template = 
_.template($("script#template-metadata-buttons").html());
+            this._template_status = 
_.template($("script#template-ingest-status").html());
+            this.first = true;
+            this.render();
+        };
+        /**
+         * Render the view
+         */
+        function render() {
+            if (this.first) {
+                this.first = false;
+                this.$el.html(this._template({"buttonText":this.buttonText}));
+            }
+            
this.$el.find("span#ingest-status").html(this._template_status({"ingesting":this.ingesting}));
+        };
+        /**
+         * A function to set the "on click" for ingest button
+         * @param func - function to call back (should come from controller)
+         */
+        function setIngestClick(func) {
+            //Buttons
+            this.ingestClick = func;
+            $(this.$el).find("button#ingest").off("click");
+            $(this.$el).find("button#ingest").on("click",this.ingestClick);
+        };
+        /**
+         * A function to set the "on click" for metadata clear button
+         * @param func - function to call back (should come from controller)
+         */
+        function setMetadataClear(func) {
+            //Buttons
+            this.metaClear = func;
+            $(this.$el).find("button#clear-metadata").off("click");
+            
$(this.$el).find("button#clear-metadata").on("click",this.metaClear);
+        };
+        /**
+         * Set ingesting status
+         * @param ingesting - is the html ingesting?
+         */
+        function setIngesting(ingesting) {
+            this.ingesting = ingesting;
+        };
+        /**
+         * Return uploads views
+         */
+        return Backbone.View.extend({
+            initialize: init,
+            render: render,
+            setIngestClick: setIngestClick,
+            setMetadataClear: setMetadataClear,
+            setIngesting: setIngesting
+        });
+    }
+);

http://git-wip-us.apache.org/repos/asf/oodt/blob/50df1e1d/webapp/curator/src/main/webapp/js-new/views/MetadataEntryView.js
----------------------------------------------------------------------
diff --git a/webapp/curator/src/main/webapp/js-new/views/MetadataEntryView.js 
b/webapp/curator/src/main/webapp/js-new/views/MetadataEntryView.js
new file mode 100644
index 0000000..b75e0e6
--- /dev/null
+++ b/webapp/curator/src/main/webapp/js-new/views/MetadataEntryView.js
@@ -0,0 +1,340 @@
+define(["jquery",
+        "underscore",
+        "lib/backbone",
+        "datatables",
+        "js-new/config/Configuration",
+        "js-new/utils/utils",
+        "popover",
+        "blockui",
+        "typeahead"],
+    function($,_,Backbone,DataTable,Configuration,utils, typeahead) {
+        
+        /**
+         * Initialize this view
+         * @param options - map of
+         *   {
+         *     datamodel - metadata data model
+         *     model - metadata model
+         *   }
+         */
+        function init(options) {
+            //Pull in parameters
+            this.datamodel = options["datamodel"];
+            this.model = options["model"];
+            //Set inital product type
+            this.type = (Configuration.DEFAULT_TYPE in 
this.datamodel.get("types") || Object.keys(this.datamodel.get("types")).length 
== 0)?Configuration.DEFAULT_TYPE:Object.keys(this.datamodel.get("types"))[0];
+            this.datamodel.on("change:types",this.render,this);
+            this.focused = null;
+            this.ingesting = false;
+            this._template = 
_.template($("script#template-elements-table").html());
+            this._entryTemplate = 
_.template($("script#template-table-element").html());
+            this.working = options["working-set"];
+            this._dataEntry = null;
+        };
+        /**
+         * Returns delimited list
+         * @returns string of values separated by '|'
+         */
+        function getDelimitedList(list1,list2) {
+            var tmp = _.zip(list1,list2);
+            for (var i = 0; i < tmp.length; i++) {
+                tmp[i] = tmp[i].join("|");
+            }
+            return tmp;
+        }
+        /**
+         * Recursively merge two metadata objects
+         * @param mer - original metadata object (merge into)
+         * @param met - new metadat object (merge from)
+         * @param ignoreFilled - ignore the field if a value is there
+         */
+        function recurseMerge(mer,met,ignoreFilled) {
+            if (mer.name != met.name)
+                return;
+            //Lock if values don't match (if ignore filled, don't do anything 
and default to mer)
+            if (!ignoreFilled && !_.isEqual(mer.values,met.values)) {
+                mer.locked = true;
+                mer.display = "** Multiple Files Selected 
**";//getDelimitedList(mer.values,met.values);
+            } else {
+                delete mer.display;
+            }
+            //Merge the children (a metadata object technically has children
+            for (var key in met.children) {
+                if (!(key in mer.children))
+                    mer.children[key] = 
{"name":met.children[key].name,"values":met.children[key].values,"children":{}};
+                recurseMerge(mer.children[key],met.children[key],ignoreFilled);
+            }
+        };
+        /**
+         * Build an individual element entry
+         * @param element - element to build
+         * @param merged - merged metadata
+         * @param index - tabindex for item
+         * @returns {name - name of element, html- html for this element's 
input}
+         */
+        function renderElementInput(element,merged,index) {
+            var values = null;
+            //Flag attributes
+            var locked = "attachments" in element && "locked" in 
element.attachments || this.model.size() == 0;
+            var modelsize = this.model.size();
+            var required = "attachments" in element && "required" in 
element.attachments && this.model.size() > 0;
+            //Hidden fields: Can be hidden via policy from Filemanager or via 
configuration passed into curator setup in main Javascript File
+            // If the field is required, errors will be reported as general 
error at the bottom of the entry table
+            var hidden = "attachments" in element && "hidden" in 
element.attachments || element.elementName in utils.getHiddenConfigFields();
+
+            var error = (element.elementName in merged.errors) ? 
merged.errors[element.elementName]:"";
+            var tmparray = [];
+            //Get values, (will create dropdown)
+            if ("attachments" in element && "values" in element.attachments) {
+                values = element.attachments.values.split(",");
+                
if(Configuration.METADATA_FILTERS.hasOwnProperty(element.elementId)){
+                    var validels = 
Configuration.METADATA_FILTERS[element.elementId];
+                    for(var i = 0; i < values.length; i++){
+                        if(validels.indexOf(values[i]) !== -1){
+                            tmparray.push(values[i]);
+                        }
+                    }
+                    values = tmparray;
+                }
+            }
+            //Grab in the current metadata
+            var value = "";
+            if (element.elementName in merged.children) {
+                var current = merged.children[element.elementName];
+                if ("display" in current) {
+                    value = current["display"];
+                } else if ("values" in current && current.values.length > 0) {
+                    value = current.values[0];
+                }
+                //Lock it if multi-valued
+                if ("locked" in current) {
+                    locked = locked | current["locked"];
+                }
+            }
+            var typeahead = false;
+            if(Configuration.INPUT_SUGGESTIONS != undefined && 
"input-"+element.elementName in Configuration.INPUT_SUGGESTIONS){
+                typeahead = true;
+            }
+            // Determine if element is uneditable text field
+            var textfield = false;
+            if ((Configuration.TEXT_FIELDS != undefined) && 
(Configuration.TEXT_FIELDS.indexOf(element.elementName) !== -1)) {
+                textfield = true;
+            }
+
+            //Grab the template and build it
+            var obj = {
+                "id":"input-"+element.elementName,
+                "name":element.elementName,
+                "displayName":("attachments" in element && "displayName" in 
element.attachments)?element.attachments.displayName:element.elementName,
+                "description":("attachments" in element && "description" in 
element.attachments)?element.attachments.description:"",
+                "locked":locked,
+                "required":required,
+                "hidden":hidden,
+                "values":values,
+                "value":(element.elementName == "ProductType")?this.type:value,
+                "error":error,
+                "index":index++,
+                "modelsize": modelsize,
+                "textfield": textfield,
+                "typeahead": typeahead
+            };
+            obj.html = this._entryTemplate(obj);
+
+            return obj;
+        }
+        /**
+         * Inspect the elements for equality and return true if "same"
+         * @param obj1 - first object
+         * @param obj2 - second object
+         * @returns true if equal or false 
+         */
+        function equateObjects(obj1,obj2) {
+            var fields = 
["id","name","displayName","description","locked","required","hidden","values","value","error"];
+            //Check undefined status
+            if (typeof(obj1) != typeof(obj2) || typeof(obj1) === "undefined") {
+                return false;
+            }
+            for (var i = 0; i < fields.length; i++) {
+                if (!$.isArray(obj1[fields[i]]) && !$.isArray(obj2[fields[i]]) 
&& obj1[fields[i]] != obj2[fields[i]]) {
+                    return false;
+                } else if ($.isArray(obj1[fields[i]]) != 
$.isArray(obj2[fields[i]])) {
+                    return false;
+                } else if ($.isArray(obj1[fields[i]]) && 
obj1[fields[i]].length != obj2[fields[i]].length) {
+                    return false;
+                } else if ($.isArray(obj1[fields[i]])) {
+                    for (var j = 0; j < obj1[fields[i]].length; j++) {
+                        if (obj1[fields[i]][j] != obj2[fields[i]][j]) {
+                            return false;
+                        }
+                    }
+                } 
+            }
+            return true;
+        }
+        /**
+         * Render this view, based on the given data model
+         * @param forceRefresh - force a refresh
+         */
+        function render(forceRefresh) {
+            var self = this;
+            var oldType = this.type;
+            //Update preset keys
+            forceRefresh = (typeof(forceRefresh) === 
"undefined")?false:forceRefresh;
+            //Set the type by first element
+            if (this.model.size() > 0 && 
typeof(this.model.first().get("root")) !== "undefined" &&
+                    "ProductType" in this.model.first().get("root").children 
&& this.model.first().get("root").children["ProductType"].values.length > 0) {
+                var t = this.model.first();
+                this.type =  
this.model.first().get("root").children["ProductType"].values[0];
+            }
+            var completeRefresh = (oldType != this.type || forceRefresh);
+            //Clear on blur for render updates
+            $(this.$el).find("table:first").find("input,select").off("blur");
+            var items = (this.type in this.datamodel.get("types")) ? 
this.datamodel.get("types")[this.type] : [];
+            var inputHtmls = [];
+            //Merge metadata together for display
+            var merged = {"name":"root","values":[],"children":{},"errors":{}};
+            if(this.model.models.length == 0){
+                $('.metadata').find('input:text').val('');
+            }
+            //For each item in the collection, merge together
+            this.model.each(
+                function(elem) {
+                    if (typeof(elem.get("root")) === "undefined")
+                        return;
+                    //Reset the model's type from selected type
+                    if (!("ProductType" in elem.get("root").children) || 
elem.get("root").children["ProductType"].values.length == 0) {
+                        elem.get("root").children["ProductType"] = 
{"name":"ProductType","values":[self.type],"children":{}}
+                    }
+                    recurseMerge(merged,utils.deep(elem.get("root")),false);
+                    if (typeof(elem.validationError) === "undefined" || 
elem.validationError == null)
+                        return;
+                    //Update errors
+                    for (var key in elem.validationError) {
+                        merged.errors[key] = elem.validationError[key];
+                    }
+
+                });
+            //Mask in working set metadata
+            recurseMerge(merged,utils.deep(this.working.get("root")),true);
+            
+            //Build this element
+            var index = 1;
+            for (var i = 0; i < items.length; i++) {
+                
inputHtmls.push(this.renderElementInput(items[i],merged,index));
+            }
+            //Completely refresh or just update?
+            if (completeRefresh) {
+                
this.$el.html(this._template({"$":$,"htmls":inputHtmls,"disabled":this.model.size()
 == 0,"ingesting":this.ingesting,"index":index++}));
+                for (var i = 0; i < inputHtmls.length; i++) {
+                    $("tr#"+inputHtmls[i].id).data(inputHtmls[i]);
+                }
+                //DataTables JS registering
+                var table = $(this.$el).find("table:first");
+                table.DataTable({"paging": false,"bFilter": false,"bSort": 
false});
+                //Refocus event
+                if (this.focused != null && this.focused != "") {
+                    $("#"+this.focused).focus();
+                }
+                //Force update from current working set (this allows ghosting 
of metadata)
+                for (var key in this.working.get("root").children) {
+                    var childEntry = this.working.get("root").children[key];
+                    var cntx = {
+                        "name":childEntry.name,
+                        "value":childEntry.values[0],
+                        "filler":true
+                    };
+                    (utils.getMediator(this,"_dataEntry").bind(cntx))();
+                }
+                //Attach events to the controls bindings.
+                
$(this.$el).find("table:first").find("input,select").on("change",utils.getMediator(this,"_dataEntry"));
+                $('[data-toggle="popover"]').popover({trigger: 
'hover','placement': 'top',delay: { "show": 500, "hide": 100 }});
+
+            } else {
+                
$(this.$el).find("table:first").find("input,select").off("change");
+                //Check elements for updates
+                for (var i = 0; i < inputHtmls.length; i++) {
+                    var cur = inputHtmls[i];
+                    var obj = $("tr#"+cur.id).data();
+                    if (!equateObjects(obj,cur)) {
+                        $("tr#"+cur.id).html(cur.html);
+                    }
+                    $("tr#"+cur.id).data(cur);
+                }
+                
$(this.$el).find("table:first").find("input,select").on("change",utils.getMediator(this,"_dataEntry"));
+            }
+            if(forceRefresh==true) {
+                $.unblockUI();
+            }
+            var typeaheads = $(this.$el).find(".typeahead");
+            typeaheads.each(function(id, el){
+                var suggestions = Configuration.INPUT_SUGGESTIONS[el.id];
+                $(el).typeahead({
+                        hint: false,
+                        highlight: false,
+                        minLength: 1
+                    },
+                    {
+                        name: id,
+                        source: substringMatcher(suggestions)
+                    });
+            });
+
+
+        };
+        /**
+         * A function to set the "on change" call-back for data inputs
+         * @param func - function to call back (should come from controller)
+         */
+        function setOnEntryFunction(func) {
+            this._dataEntry = func;
+        }
+
+        function substringMatcher(strs) {
+            return function findMatches(q, cb) {
+                var matches, substringRegex;
+
+                // an array that will be populated with substring matches
+                matches = [];
+
+                // regex used to determine if a string contains the substring 
`q`
+                substrRegex = new RegExp(q, 'i');
+
+                // iterate through the pool of strings and for any string that
+                // contains the substring `q`, add it to the `matches` array
+                $.each(strs, function(i, str) {
+                    if (substrRegex.test(str)) {
+                        matches.push(str);
+                    }
+                });
+
+                cb(matches);
+            };
+        };
+
+
+
+        /**
+         * Update the current product type from the controller
+         * @param type - product type
+         */
+        function setProductType(type) {
+            this.type = type;
+        };
+        /**
+         * Return the current product type to the controller
+         */
+        function getProductType() {
+            return this.type;
+        };
+        //Return backbone view
+        return Backbone.View.extend({
+            initialize: init,
+            render: render,
+            setOnEntryFunction: setOnEntryFunction,
+            getProductType: getProductType,
+            setProductType: setProductType,
+            //Private functions needing correct "this"
+            renderElementInput: renderElementInput
+            
+        });
+    });

Reply via email to