You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by ma...@apache.org on 2016/12/16 14:50:36 UTC

[1/4] incubator-atlas git commit: ATLAS-1193: UI to create/update entities

Repository: incubator-atlas
Updated Branches:
  refs/heads/master 1620284e4 -> b305ba505


http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.min.css
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.min.css b/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.min.css
new file mode 100644
index 0000000..e9ec816
--- /dev/null
+++ b/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.min.css
@@ -0,0 +1,5 @@
+/*!
+ * Datetimepicker for Bootstrap 3
+ * version : 4.14.30
+ * https://github.com/Eonasdan/bootstrap-datetimepicker/
+ */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width:768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:after,.bootstrap-datetimepicker-widget.dropdown-menu:before{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;top:-6px;left:8px}.bootstrap-datetime
 picker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #fff;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:700;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootst
 rap-datetimepicker-widget .btn[data-action=incrementHours]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action=incrementMinutes]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action=decrementHours]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action=decrementMinutes]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action=showHours]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content
 :"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action=showMinutes]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action=togglePeriod]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action=clear]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action=today]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflo
 w:hidden;clip:rect(0,0,0,0);border:0;content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next
 ::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0;content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eee}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget table td.new,.bootstrap-datetimepicker-widget table td.old{color:#777}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-dateti
 mepicker-widget table td.today:before{content:'';display:inline-block;border:0 solid transparent;border-bottom-color:#337ab7;border-top-color:rgba(0,0,0,.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget table td span:hover{background:#eee}.bootstrap-datetimepicker-widget table td span.active{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.bootstrap-datetimepicker-widg
 et table td span.old{color:#777}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:0 0;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.bootstrap-datetimepicker-widget.wider{width:21em}.bootstrap-datetimepicker-widget .datepicker-decades .decade{line-height:1.8em!important}.input-group.date .input-group-addon{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/main.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/main.js b/dashboardv2/public/js/main.js
index 772d3d4..fe08754 100644
--- a/dashboardv2/public/js/main.js
+++ b/dashboardv2/public/js/main.js
@@ -94,6 +94,10 @@ require.config({
             'deps': ['d3'],
             'exports': ['d3-tip']
         },
+        'datetimepicker': {
+            'deps': ['jquery'],
+            'exports': 'datetimepicker'
+        },
         'dagreD3': {
             'deps': ['d3'],
             'exports': ['dagreD3']
@@ -130,10 +134,11 @@ require.config({
         'hbs': 'external_lib/require-handlebars-plugin/js/hbs',
         'i18nprecompile': 'external_lib/require-handlebars-plugin/js/i18nprecompile',
         'dagreD3': 'libs/dagre-d3/dagre-d3.min',
-        'select2': 'libs/select2/select2.min',
+        'select2': 'libs/select2/select2.full.min',
         'backgrid-select-all': 'libs/backgrid-select-all/backgrid-select-all.min',
         'moment': 'libs/moment/js/moment.min',
         'jquery-ui': 'external_lib/jquery-ui/jquery-ui.min',
+        'datetimepicker': 'external_lib/datetimepicker/bootstrap-datetimepicker',
         'pnotify': 'external_lib/pnotify.custom.min',
         'jquery-placeholder': 'libs/jquery-placeholder/js/jquery.placeholder',
         'platform': 'libs/platform/platform'
@@ -156,7 +161,7 @@ require(['App',
     'utils/Overrides',
     'bootstrap',
     'd3',
-    'select2' 
+    'select2'
 ], function(App, Router, CommonViewFunction, Globals, UrlLinks) {
     App.appRouter = new Router();
     CommonViewFunction.userDataFetch({

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/models/VEntity.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/models/VEntity.js b/dashboardv2/public/js/models/VEntity.js
index 4347b62..2173534 100644
--- a/dashboardv2/public/js/models/VEntity.js
+++ b/dashboardv2/public/js/models/VEntity.js
@@ -70,8 +70,20 @@ define(['require',
             }, options);
 
             return this.constructor.nonCrudOperation.call(this, url, 'GET', options);
+        },
+        createOreditEntity: function(guid, options) {
+            var url;
+            if (guid) {
+                url = UrlLinks.entitiesApiUrl(guid);
+            } else {
+                url = UrlLinks.entitiesApiUrl();
+            }
+            options = _.extend({
+                contentType: 'application/json',
+                dataType: 'json'
+            }, options);
+            return this.constructor.nonCrudOperation.call(this, url, "", options);
         }
-
     }, {});
     return VEntity;
 });

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/templates/detail_page/DetailPageLayoutView_tmpl.html
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/templates/detail_page/DetailPageLayoutView_tmpl.html b/dashboardv2/public/js/templates/detail_page/DetailPageLayoutView_tmpl.html
index f34bcea..e89555a 100644
--- a/dashboardv2/public/js/templates/detail_page/DetailPageLayoutView_tmpl.html
+++ b/dashboardv2/public/js/templates/detail_page/DetailPageLayoutView_tmpl.html
@@ -19,6 +19,7 @@
         <a href="javascript:void(0);" class="backButton" data-id="backButton"><i class="fa fa-chevron-left"></i>  Back To Results</a>
     </div>
     <h1><span  data-id="title"></span></h1>
+    <button data-id="editButton" class="btn btn-default pull-right editbutton" id="editText"><i class="fa fa-pencil"></i></button>
     <div data-id="editBox" style="margin-bottom:10px;">
         <textarea class="well well-sm col-sm-12" data-id="descriptionTextArea"></textarea>
         <div class="clearfix" align="right">

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/templates/entity/CreateEntityLayoutView_tmpl.html
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/templates/entity/CreateEntityLayoutView_tmpl.html b/dashboardv2/public/js/templates/entity/CreateEntityLayoutView_tmpl.html
new file mode 100644
index 0000000..01f2aa0
--- /dev/null
+++ b/dashboardv2/public/js/templates/entity/CreateEntityLayoutView_tmpl.html
@@ -0,0 +1,46 @@
+<!--
+ * 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.
+-->
+<form name="entityDefinitionform" class="css-form">
+    <!-- <h4 style="margin-bottom:30px"></h4> -->
+    <div class="form-group">
+        <div class="col-sm-12">
+            <div class="row">
+                {{#if guid}}
+                <div class="col-md-8">
+                    <label class="col-md-6 row-margin-bottom" data-id="assetName"></label>
+                </div>
+                {{else}}
+                <div class="col-md-8">
+                    <select class="form-control col-md-6 row-margin-bottom" data-id="entityList"></select>
+                </div>
+                {{/if}}
+                <div class="col-md-4">
+                    <span class="pull-left">Required</span>
+                    <label class="switch pull-left">
+                        <input type="checkbox" class="switch-input" name="toggleRequired" value="text">
+                        <div class="switch-slider"></div>
+                    </label>
+                    <span class="pull-left">All</span>
+                </div>
+            </div>
+        </div>
+        <div class="entityLoader" style="display:none">
+            <i class="fa fa-refresh fa-spin-custom"></i>
+        </div>
+        <div class="control-group entityInputData" data-id="entityInputData"></div>
+    </div>
+</form>

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/templates/search/SearchLayoutView_tmpl.html
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/templates/search/SearchLayoutView_tmpl.html b/dashboardv2/public/js/templates/search/SearchLayoutView_tmpl.html
index db31046..8d7f874 100644
--- a/dashboardv2/public/js/templates/search/SearchLayoutView_tmpl.html
+++ b/dashboardv2/public/js/templates/search/SearchLayoutView_tmpl.html
@@ -15,6 +15,9 @@
  * limitations under the License.
 -->
 <div class="row row-margin-bottom">
+    <div class="col-sm-12">
+        <button class="btn btn-atlasAction btn-atlas pull-left" data-id="createEntity"><i class="fa fa-plus"></i> Create Entity</button>
+    </div>
     <div class="col-sm-12" style="margin:15px 0px;">
         <div class="row">
             <div class="col-md-6">

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/utils/UrlLinks.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/utils/UrlLinks.js b/dashboardv2/public/js/utils/UrlLinks.js
index a5288be..4071b07 100644
--- a/dashboardv2/public/js/utils/UrlLinks.js
+++ b/dashboardv2/public/js/utils/UrlLinks.js
@@ -29,11 +29,11 @@ define(['require', 'utils/Enums'], function(require, Enums) {
             return this.baseUrl + '/v1/taxonomies' + '/' + name + '/terms';
         },
         entitiesApiUrl: function(guid, name) {
-            var entitiesUrl = this.baseUrlV2 + '/entity/guid';
+            var entitiesUrl = this.baseUrlV2 + '/entity';
             if (guid && name) {
-                return entitiesUrl + '/' + guid + '/classification/' + name;
+                return entitiesUrl + '/guid/' + guid + '/classification/' + name;
             } else if (guid && !name) {
-                return entitiesUrl + '/' + guid;
+                return entitiesUrl + '/guid/' + guid;
             } else {
                 return entitiesUrl;
             }

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js b/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js
index b2d2c7b..041204f 100644
--- a/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js
+++ b/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js
@@ -75,6 +75,7 @@ define(['require',
             /** ui events hash */
             events: function() {
                 var events = {};
+                events["click " + this.ui.editButton] = 'onClickEditEntity';
                 events["click " + this.ui.tagClick] = function(e) {
                     if (e.target.nodeName.toLocaleLowerCase() != "i") {
                         var scope = $(e.currentTarget);
@@ -358,6 +359,21 @@ define(['require',
                         term: true
                     }));
                 });
+            },
+            onClickEditEntity: function(e) {
+                var that = this;
+                $(e.currentTarget).blur();
+                require([
+                    'views/entity/CreateEntityLayoutView'
+                ], function(CreateEntityLayoutView) {
+                    var view = new CreateEntityLayoutView({
+                        guid: that.id,
+                        callback: function() {
+                            that.fetchCollection();
+                        }
+                    });
+
+                });
             }
         });
     return DetailPageLayoutView;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js b/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js
new file mode 100644
index 0000000..a3e0389
--- /dev/null
+++ b/dashboardv2/public/js/views/entity/CreateEntityLayoutView.js
@@ -0,0 +1,613 @@
+/**
+ * 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.
+ */
+
+define(['require',
+    'backbone',
+    'hbs!tmpl/entity/CreateEntityLayoutView_tmpl',
+    'utils/Utils',
+    'collection/VTagList',
+    'collection/VCommonList',
+    'collection/VEntityList',
+    'models/VEntity',
+    'modules/Modal',
+    'utils/Messages',
+    'datetimepicker',
+    'moment',
+    'utils/UrlLinks',
+    'collection/VSearchList',
+    'utils/Enums'
+], function(require, Backbone, CreateEntityLayoutViewTmpl, Utils, VTagList, VCommonList, VEntityList, VEntity, Modal, Messages, datepicker, moment, UrlLinks, VSearchList, Enums) {
+
+    var CreateEntityLayoutView = Backbone.Marionette.LayoutView.extend(
+        /** @lends CreateEntityLayoutView */
+        {
+            _viewName: 'CreateEntityLayoutView',
+
+            template: CreateEntityLayoutViewTmpl,
+
+            templateHelpers: function() {
+                return {
+                    guid: this.guid
+                };
+            },
+
+            /** Layout sub regions */
+            regions: {},
+
+            /** ui selector cache */
+            ui: {
+                entityName: "[data-id='entityName']",
+                entityList: "[data-id='entityList']",
+                description: "[data-id='description']",
+                entityInputData: "[data-id='entityInputData']",
+                entityLegend: "[data-id='entityLegend']",
+                toggleRequired: 'input[name="toggleRequired"]',
+                assetName: "[data-id='assetName']",
+                entityInput: "[data-id='entityInput']"
+            },
+            /** ui events hash */
+            events: function() {
+                var events = {};
+                events["change " + this.ui.entityList] = "onEntityChange";
+                events["change " + this.ui.toggleRequired] = function(e) {
+                    this.requiredAllToggle(e.currentTarget.checked)
+                };
+                return events;
+            },
+            /**
+             * intialize a new CreateEntityLayoutView Layout
+             * @constructs
+             */
+            initialize: function(options) {
+                _.extend(this, _.pick(options, 'guid', 'callback', 'showLoader'));
+                var that = this,
+                    entityTitle, okLabel;
+                this.entityDetailCollection = new VCommonList();
+                this.searchCollection = new VSearchList();
+                this.searchCollection.url = UrlLinks.searchApiUrl(Enums.searchUrlType.DSL);
+                this.selectStoreCollection = new Backbone.Collection();
+                this.entityModel = new VEntity();
+                if (this.guid) {
+                    this.collection = new VEntityList();
+                    this.collection.modelAttrName = "createEntity"
+                } else {
+                    this.collection = new VTagList();
+                }
+                this.asyncFetchCounter = 0;
+                this.required = true;
+                if (this.guid) {
+                    entityTitle = 'Edit entity';
+                    okLabel = 'Update';
+                } else {
+                    entityTitle = 'Create entity';
+                    okLabel = 'Create';
+                }
+                this.modal = new Modal({
+                    title: entityTitle,
+                    content: this,
+                    cancelText: "Cancel",
+                    okText: okLabel,
+                    allowCancel: true,
+                    okCloses: false,
+                    resizable: true,
+                    resizableOpts: {
+                        minWidth: 600,
+                        minHeight: 284,
+                        handles: "n, e, s, w",
+                        resize: function(event, ui) {
+                            that.modal.$el.find('.modal-body').css('min-height', ui.size.height - 134 + 'px');
+                            that.modal.$el.find('.modal-body').css('max-height', ui.size.height - 134 + 'px');
+                        }
+                    }
+                }).open();
+                var enable = false;
+                this.ui.entityList.val("");
+                $(this.ui.entityInputData).on('keyup change dp.change', that.modal.$el.find('input select textarea'), function(e) {
+                    that.ui.entityInputData.find("input,select,textarea").each(function() {
+                        if (this.value !== "") {
+                            if ($(this).data('select2')) {
+                                $(this).data('select2').$container.removeClass("errorClass")
+                            } else {
+                                $(this).removeClass('errorClass');
+                            }
+                        }
+                    });
+                });
+                this.modal.on('ok', function(e) {
+                    that.okButton();
+                });
+                this.modal.on('closeModal', function() {
+                    that.modal.trigger('cancel');
+                });
+            },
+            bindEvents: function() {
+                var that = this;
+                this.listenTo(this.collection, "reset", function() {
+                    --this.asyncFetchCounter;
+                    this.entityCollectionList();
+                }, this);
+                this.listenTo(this.collection, 'error', function() {
+                    --this.asyncFetchCounter
+                    this.hideLoader();
+                }, this);
+                this.listenTo(this.searchCollection, "reset", function() {
+                    this.addJsonSearchData();
+                }, this);
+                this.listenTo(this.searchCollection, 'error', function(data, key) {
+                    this.addJsonSearchData(key);
+                    this.hideLoader();
+                }, this);
+            },
+            onRender: function() {
+                this.bindEvents();
+                this.fetchCollections();
+            },
+            fetchCollections: function() {
+                this.asyncFetchCounter++;
+                if (this.guid) {
+                    this.collection.url = UrlLinks.entitiesApiUrl(this.guid);
+                    this.collection.fetch({ reset: true });
+                } else {
+                    this.collection.url = UrlLinks.entitiesDefApiUrl()
+                    this.collection.modelAttrName = 'list';
+                    this.collection.fetch({ reset: true });
+                }
+
+            },
+            entityCollectionList: function() {
+                this.ui.entityList.empty();
+                var that = this,
+                    name = "",
+                    value;
+                if (this.guid) {
+                    this.collection.each(function(val) {
+                        name += val.get("attributes").name || val.get("attributes").qualifiedName || val.get("attributes").id;
+                        that.entityData = val;
+                    });
+                    this.ui.assetName.html(name);
+                    this.onEntityChange(null, this.entityData);
+                } else {
+                    var str = '<option selected="selected" disabled="disabled">--Select entity-type--</option>';
+                    this.collection.fullCollection.comparator = function(model) {
+                        return model.get('name');
+                    }
+                    this.collection.fullCollection.sort().each(function(val) {
+                        str += '<option>' + val.get("name") + '</option>';
+                    });
+                    this.ui.entityList.html(str);
+                }
+            },
+            capitalize: function(string) {
+                return string.charAt(0).toUpperCase() + string.slice(1);
+            },
+            requiredAllToggle: function(checked) {
+                if (checked) {
+                    this.ui.entityInputData.find('div.true').show();
+                    this.ui.entityInputData.find('fieldset div.true').show();
+                    this.required = false;
+                } else {
+                    this.ui.entityInputData.find('div.true').hide();
+                    this.ui.entityInputData.find('fieldset div.true').hide();
+                    this.required = true;
+                }
+
+            },
+            onEntityChange: function(e, value) {
+                var that = this,
+                    typeName;
+                this.showLoader();
+                this.ui.entityInputData.empty();
+                if (value) {
+                    typeName = value.get("typeName");
+                }
+                if (typeName) {
+                    this.collection.url = UrlLinks.entitiesDefApiUrl(typeName);
+                } else {
+                    this.collection.url = UrlLinks.entitiesDefApiUrl(e.target.value);
+                    this.collection.modelAttrName = 'attributeDefs';
+                }
+                this.collection.fetch({
+                    success: function(model, data) {
+                        that.subAttributeData(data)
+                    },
+                    complete: function() {
+                        var _self = that;
+                        that.$('input[data-type="date"]').each(function() {
+                            if (!$(this).data('datepicker')) {
+                                $(this).datetimepicker({
+                                    format: 'DD MMMM YYYY'
+                                });
+                            }
+                        });
+                        that.$('input[data-type="long"]').each(function() {
+                            if (!$(this).data('datepicker')) {
+                                $(this).datetimepicker({
+                                    format: 'DD MMMM YYYY, HH:mm',
+                                    showTodayButton: true,
+                                    showClose: true
+                                });
+                            }
+                        });
+                        // IE9 allow input type number
+                        that.$('input[data-type="int"]').on('keydown', function(e) {
+                            var regex = /^[0-9]*([.](?=[^.]|$))*(?:\.\d{1,2})?$/; // allow only numbers [0-9] 
+                            if (!regex.test(e.currentTarget.value)) {
+                                return false;
+                            }
+                        });
+                        if (that.ui.entityInputData.find('select.true,input.true').length === 0) {
+                            that.requiredAllToggle(that.ui.entityInputData.find('select.true,input.true').length === 0);
+                            that.ui.toggleRequired.prop('checked', true);
+
+                        }
+                        // IE9 allow input type number
+                        that.$('input[data-type="int"]').on('keyup click', function(e) {
+                            e.currentTarget.value = e.currentTarget.value;
+                            var regex = /^[0-9]*([.](?=[^.]|$))*(?:\.\d{1,2})?$/; // allow only numbers [0-9] 
+                            if (!regex.test(e.currentTarget.value)) {
+                                var txtfld = e.currentTarget;
+                                var newtxt = txtfld.value.slice(0, txtfld.value.length - 1);
+                                txtfld.value = newtxt;
+                            }
+                        });
+                    },
+                    silent: true
+                });
+            },
+            subAttributeData: function(data) {
+                var that = this,
+                    attributeInput = "",
+                    alloptional = false;
+                _.each(data.attributeDefs, function(value) {
+                    if (value.isOptional == true) {
+                        alloptional = true;
+                    }
+
+                    attributeInput += that.getContainer(value);
+                });
+                if (attributeInput !== "") {
+                    entityTitle = that.getFieldSet(data, alloptional, attributeInput);
+                    that.ui.entityInputData.prepend(entityTitle);
+                }
+                if (data.superTypes && data.superTypes.length > 0) {
+                    for (var j = 0; j < data.superTypes.length; j++) {
+                        var superTypeAttr = data.superTypes[j];
+                        that.fetchTagSubData(superTypeAttr);
+                    }
+                } else {
+                    this.hideLoader();
+                }
+                if (this.required) {
+                    this.ui.entityInputData.find('fieldset div.true').hide()
+                    this.ui.entityInputData.find('div.true').hide();
+                }
+                if (!('placeholder' in HTMLInputElement.prototype)) {
+                    this.ui.entityInputData.find('input,select,textarea').placeholder();
+                }
+            },
+            getContainer: function(value) {
+                var entityLabel = this.capitalize(value.name);
+                return '<div class="row row-margin-bottom ' + value.isOptional + '"><span class="col-md-3">' +
+                    '<label class="' + value.isOptional + '">' + entityLabel + (value.isOptional == true ? '' : ' <span class="requiredInput">*</span>') + '</label></span>' +
+                    '<span class="col-md-9 position-relative">' +
+                    (value.typeName === "boolean" ? this.getSelect(value) : this.getInput(value)) +
+                    '<span class="spanEntityType" title="Data Type : ' + value.typeName + '">' + '(' + Utils.escapeHtml(value.typeName) + ')' + '</span></input></span></div>';
+            },
+            getFieldSet: function(data, alloptional, attributeInput) {
+                return '<fieldset class="scheduler-border' + (alloptional ? " alloptional" : "") + '"><legend class="scheduler-border">' + data.name + '</legend>' + attributeInput + '</fieldset>';
+            },
+            getInput: function(value) {
+                var that = this;
+                var entityValue = "";
+                if (this.guid) {
+                    var dataValue = this.entityData.get("attributes")[value.name];
+                    if (_.isObject(dataValue)) {
+                        entityValue = JSON.stringify(dataValue);
+                    } else {
+                        if (dataValue) {
+                            entityValue = dataValue;
+                        }
+                        if (value.typeName === "date" && dataValue) {
+                            entityValue = moment(dataValue).format("DD MMMM YYYY");
+                        }
+                        if (value.typeName === "long") {
+                            entityValue = moment(dataValue).format("DD MMMM YYYY, HH:mm");
+                        }
+                    }
+                }
+                if (value.typeName === "string" || value.typeName === "long" || value.typeName === "int" || value.typeName === "boolean" || value.typeName === "date") {
+                    return '<input class="form-control entityInputBox ' + (value.isOptional === true ? "false" : "true") + '"' +
+                        ' data-type="' + value.typeName + '"' +
+                        ' value="' + entityValue + '"' +
+                        ' data-key="' + value.name + '"' +
+                        ' placeholder="' + value.name + '"' +
+                        ' data-id="entityInput">';
+                } else if (value.typeName === "map<string,string>") {
+                    return '<textarea class="form-control entityInputBox ' + (value.isOptional === true ? "false" : "true") + '"' +
+                        ' data-type="' + value.typeName + '"' +
+                        ' data-key="' + value.name + '"' +
+                        ' placeholder="' + value.name + '"' +
+                        ' data-id="entityInput">' + entityValue + '</textarea>';
+                } else {
+                    var changeDatatype;
+                    if (value.typeName.indexOf("array") == -1) {
+                        changeDatatype = value.typeName;
+                    } else {
+                        if (value.typeName === "array<string>") {
+                            changeDatatype = value.typeName;
+                        } else {
+                            changeDatatype = value.typeName.split('<')[1].split('>')[0];
+                        }
+                    }
+                    $.extend(that.searchCollection.queryParams, { query: changeDatatype });
+                    that.searchCollection.fetch({ reset: true });
+                    return '<select class="form-control row-margin-bottom entityInputBox ' + (value.isOptional === true ? "false" : "true") + '" data-type="' + value.typeName +
+                        '" data-key="' + value.name + '"data-id="entitySelectData" data-queryData="' + changeDatatype + '">' + (this.guid ? entityValue : "") + '</select>';
+                }
+            },
+            getSelect: function(value) {
+                return '<select class="form-control row-margin-bottom ' + (value.isOptional === true ? "false" : "true") + '" data-type="' + value.typeName + '" data-key="' + value.name + '" data-id="entityInput">' +
+                    '<option disabled="disabled">--Select true or false--</option><option>true</option>' +
+                    '<option>false</option></select>';
+            },
+            fetchTagSubData: function(entityName) {
+                var that = this;
+                this.collection.url = UrlLinks.entitiesDefApiUrl(entityName);
+                this.collection.modelAttrName = 'attributeDefs';
+                this.asyncFetchCounter++;
+                this.collection.fetch({
+                    success: function(model, data) {
+                        that.subAttributeData(data);
+                    },
+                    complete: function() {
+                        --that.asyncFetchCounter;
+                        if (that.asyncFetchCounter === 0) {
+                            that.$('input[data-type="date"]').each(function() {
+                                if (!$(this).data('datepicker')) {
+                                    $(this).datetimepicker({
+                                        format: 'DD MMMM YYYY'
+                                    });
+                                }
+                            });
+                            that.$('input[data-type="long"]').each(function() {
+                                if (!$(this).data('datepicker')) {
+                                    $(this).datetimepicker({
+                                        format: 'DD MMMM YYYY, HH:mm',
+                                        showTodayButton: true,
+                                        showClose: true
+                                    });
+                                }
+                            });
+                            that.hideLoader();
+                        }
+                        that.$('select[data-type="boolean"]').each(function(value, key) {
+                            var dataKey = $(key).data('key');
+                            if (that.entityData) {
+                                var setValue = that.entityData.get("attributes")[dataKey];
+                                this.value = setValue;
+                            }
+                        });
+
+                    },
+                    silent: true
+                });
+            },
+
+            okButton: function() {
+                var that = this;
+                this.showLoader();
+                this.parentEntity = this.ui.entityList.val();
+                var entityAttribute = {};
+                that.validateError = false;
+                that.validateMessage = false;
+                this.ui.entityInputData.find("input,select,textarea").each(function() {
+                    var value = $(this).val();
+                    if ($(this).val() && $(this).val().trim) {
+                        value = $(this).val().trim();
+                    }
+                    if ($(this).hasClass("true")) {
+                        if (value == "" || value == undefined) {
+                            if ($(this).data('select2')) {
+                                $(this).data('select2').$container.addClass("errorClass")
+                            } else {
+                                $(this).addClass('errorClass');
+                            }
+                            that.hideLoader();
+                            that.validateError = true;
+                            that.validateMessage = true;
+                            return;
+                        }
+                    }
+                    var dataTypeEnitity = $(this).data('type');
+                    var datakeyEntity = $(this).data('key');
+                    var selectDataType = $(this).data('querydata');
+                    var pickKey = $(this).data('pickkey');
+                    if (typeof datakeyEntity === 'string' && datakeyEntity.indexOf("Time") > -1) {
+                        entityAttribute[datakeyEntity] = Date.parse($(this).val());
+                    } else if (dataTypeEnitity == "string" || dataTypeEnitity === "long" || dataTypeEnitity === "int" || dataTypeEnitity === "boolean" || dataTypeEnitity == "date") {
+                        entityAttribute[datakeyEntity] = $(this).val();
+                    } else {
+                        try {
+                            if (value !== undefined && value !== null && value !== "") {
+                                if (_.isArray(value)) {
+                                    var arrayEmptyValueCheck = value.join("")
+                                    if (arrayEmptyValueCheck === "") {
+                                        return;
+                                    }
+                                    if (dataTypeEnitity === "array<string>" || dataTypeEnitity === "map<string,string>") {
+                                        parseData = value;
+                                    } else {
+                                        if (that.selectStoreCollection.length) {
+                                            var parseData = value.map(function(val) {
+                                                var temp = {} // I9 support;
+                                                temp[pickKey] = val;
+                                                var valueData = that.selectStoreCollection.findWhere(temp).toJSON();
+                                                valueData['guid'] = valueData.id;
+                                                return valueData;
+                                            })
+                                        }
+                                    }
+                                } else {
+                                    if (that.selectStoreCollection.length && pickKey) {
+                                        var temp = {} // I9 support;
+                                        temp[pickKey] = $(this).val();
+                                        var parseData = that.selectStoreCollection.findWhere(temp).toJSON();
+                                        parseData['guid'] = parseData.id || parseData['$id$'].id;
+                                    }
+                                    // Object but maptype
+                                    if (!pickKey) {
+                                        parseData = JSON.parse($(this).val());
+                                    }
+                                }
+                                entityAttribute[datakeyEntity] = parseData
+                                $(this).removeClass('errorClass');
+                            }
+                        } catch (e) {
+                            $(this).addClass('errorClass');
+                            that.validateError = e;
+                            that.hideLoader();
+                        }
+                    }
+                });
+                var entityJson = {
+                    "typeName": this.guid ? this.entityData.get("typeName") : this.parentEntity,
+                    "attributes": entityAttribute
+                };
+                if (this.guid) {
+                    entityJson["guid"] = this.entityData.get("guid");
+                };
+                if (that.validateError) {
+                    if (that.validateMessage) {
+                        Utils.notifyError({
+                            content: "Please fill the required fields"
+                        });
+                    } else {
+                        Utils.notifyError({
+                            content: that.validateError.message
+                        });
+                    }
+                    that.validateError = null;
+                    that.hideLoader();
+                } else {
+                    this.entityModel.createOreditEntity(this.guid, {
+                        data: JSON.stringify(entityJson),
+                        type: this.guid ? "PUT" : "POST",
+                        success: function(model, response) {
+                            that.callback();
+                            that.modal.close();
+                            Utils.notifySuccess({
+                                content: "entity " + Messages[that.guid ? 'editSuccessMessage' : 'addSuccessMessage']
+                            });
+                        },
+                        error: function(response) {
+                            if (response.responseJSON) {
+                                Utils.notifyError({
+                                    content: response.responseJSON.error || response.responseJSON.errorMessage
+                                });
+                            }
+                        },
+                        complete: function() {
+                            that.hideLoader();
+                        }
+                    });
+                }
+            },
+            showLoader: function() {
+                this.$('.entityLoader').show();
+                this.$('.entityInputData').hide();
+            },
+            hideLoader: function() {
+                this.$('.entityLoader').hide();
+                this.$('.entityInputData').show();
+            },
+            addJsonSearchData: function(isError) {
+                var that = this,
+                    typename,
+                    str = '';
+                if (isError) {
+                    typename = isError.responseJSON.error.split(": ")[1];
+                } else {
+                    if (this.searchCollection.length) {
+                        typename = this.searchCollection.first().get("$typeName$");
+                        this.selectStoreCollection.push(this.searchCollection.fullCollection.models);
+                        var labelName = "";
+                        _.each(this.searchCollection.fullCollection.models, function(value, key) {
+                            if (value.get("qualifiedName")) {
+                                labelName = "qualifiedName";
+                            } else if (value.get("name")) {
+                                labelName = "name";
+                            } else if (value.get("id")) {
+                                labelName = "id";
+                            }
+                            str += '<option>' + value.get(labelName) + '</option>';
+                        });
+                    }
+                }
+                this.$('select[data-queryData="' + typename + '"]').html(str);
+                this.$('select[data-queryData="' + typename + '"]').attr('data-pickkey', labelName);
+                this.$('select[data-queryData="' + typename + '"]').each(function(value, key) {
+                    var keyData = $(this).data("key");
+                    var typeData = $(this).data("type");
+                    var placeholderName = "Select a " + typename + " from the dropdown list";
+                    var $this = $(this);
+                    $this.attr("multiple", ($this.data('type').indexOf("array") === -1 ? false : true))
+                    if (that.guid) {
+                        if (that.selectStoreCollection.length) {
+                            var selectedValue = [];
+                        }
+                        var dataValue = that.entityData.get("attributes")[keyData];
+                        that.selectStoreCollection.each(function(value) {
+                            if (dataValue !== null && _.isArray(dataValue)) {
+                                _.each(dataValue, function(obj) {
+                                    if (obj.guid === value.get("id")) {
+                                        selectedValue.push(value.get("qualifiedName") || value.get("name") || value.get("id"));
+                                    }
+                                });
+                            } else if (dataValue !== null) {
+                                if (dataValue.guid === value.get("id")) {
+                                    selectedValue.push(value.get("qualifiedName") || value.get("name") || value.get("id"));
+                                }
+                            }
+                        });
+                        if (selectedValue) {
+                            $this.val(selectedValue);
+                        } else {
+                            if (that.guid) {
+                                var dataValue = that.entityData.get("attributes")[keyData];
+                                if (dataValue !== null) {
+                                    _.each(dataValue, function(obj) {
+                                        str += '<option>' + obj + '</option>';
+                                    });
+                                    $this.html(str);
+                                }
+                            }
+                            $this.val(dataValue);
+                        }
+                    } else {
+                        $this.val("");
+                    }
+                    $this.select2({
+                        placeholder: placeholderName,
+                        allowClear: true,
+                        tags: true
+                    });
+                });
+            }
+        });
+    return CreateEntityLayoutView;
+});

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/views/search/SearchLayoutView.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/views/search/SearchLayoutView.js b/dashboardv2/public/js/views/search/SearchLayoutView.js
index d652aa4..2dd2549 100644
--- a/dashboardv2/public/js/views/search/SearchLayoutView.js
+++ b/dashboardv2/public/js/views/search/SearchLayoutView.js
@@ -42,7 +42,8 @@ define(['require',
                 searchBtn: '[data-id="searchBtn"]',
                 clearSearch: '[data-id="clearSearch"]',
                 typeLov: '[data-id="typeLOV"]',
-                refreshBtn: '[data-id="refreshBtn"]'
+                refreshBtn: '[data-id="refreshBtn"]',
+                createEntity: "[data-id='createEntity']",
             },
             /** ui events hash */
             events: function() {
@@ -63,6 +64,7 @@ define(['require',
                 events["click " + this.ui.clearSearch] = 'clearSearchData';
                 events["change " + this.ui.typeLov] = 'onChangeTypeList';
                 events["click " + this.ui.refreshBtn] = 'onRefreshButton';
+                events["click " + this.ui.createEntity] = 'onClickCreateEntity';
                 return events;
             },
             /**
@@ -247,7 +249,20 @@ define(['require',
                     mergeBrowserUrl: false,
                     trigger: true
                 });
-            }
+            },
+            onClickCreateEntity: function(e) {
+                var that = this;
+                $(e.currentTarget).blur();
+                require([
+                    'views/entity/CreateEntityLayoutView'
+                ], function(CreateEntityLayoutView) {
+                    var view = new CreateEntityLayoutView({
+                        callback: function() {
+                            that.fetchCollection();
+                        }
+                    });
+                });
+            },
         });
     return SearchLayoutView;
 });

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/views/search/SearchResultLayoutView.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/views/search/SearchResultLayoutView.js b/dashboardv2/public/js/views/search/SearchResultLayoutView.js
index 0a37834..ded4e38 100644
--- a/dashboardv2/public/js/views/search/SearchResultLayoutView.js
+++ b/dashboardv2/public/js/views/search/SearchResultLayoutView.js
@@ -57,7 +57,8 @@ define(['require',
                 previousData: "[data-id='previousData']",
                 nextData: "[data-id='nextData']",
                 pageRecordText: "[data-id='pageRecordText']",
-                addAssignTag: "[data-id='addAssignTag']"
+                addAssignTag: "[data-id='addAssignTag']",
+                editEntityButton: "[data-id='editEntityButton']"
             },
 
             /** ui events hash */
@@ -107,6 +108,7 @@ define(['require',
                 };
                 events["click " + this.ui.nextData] = "onClicknextData";
                 events["click " + this.ui.previousData] = "onClickpreviousData";
+                events["click " + this.ui.editEntityButton] = "onClickEditEntity";
                 return events;
             },
             /**
@@ -450,6 +452,7 @@ define(['require',
                                     nameHtml += '<button type="button" title="Deleted" class="btn btn-atlasAction btn-atlas deleteBtn"><i class="fa fa-trash"></i></button>';
                                     return '<div class="readOnly readOnlyLink">' + nameHtml + '</div>';
                                 } else {
+                                    nameHtml += '<button title="Edit" data-id="editEntityButton"  data-giud= "' + (model.get('$id$').id || model.get('$id$')) + '" class="btn btn-atlasAction btn-atlas editBtn"><i class="fa fa-pencil"></i></button>'
                                     return nameHtml;
                                 }
                             }
@@ -485,6 +488,7 @@ define(['require',
                                     nameHtml += '<button type="button" title="Deleted" class="btn btn-atlasAction btn-atlas deleteBtn"><i class="fa fa-trash"></i></button>';
                                     return '<div class="readOnly readOnlyLink">' + nameHtml + '</div>';
                                 } else {
+                                    nameHtml += '<button title="Edit" data-giud= "' +(model.get('$id$').id || model.get('$id$')) + '" class="btn btn-atlasAction btn-atlas editBtn"><i class="fa fa-pencil"></i></button>'
                                     return nameHtml;
                                 }
                             }
@@ -667,6 +671,22 @@ define(['require',
                 });
                 this.previousClick = true;
                 this.fetchCollection();
+            },
+
+            onClickEditEntity: function(e) {
+                var that = this;
+                $(e.currentTarget).blur();
+                var guid = $(e.currentTarget).data('giud');
+                require([
+                    'views/entity/CreateEntityLayoutView'
+                ], function(CreateEntityLayoutView) {
+                    var view = new CreateEntityLayoutView({
+                        guid: guid,
+                        callback: function() {
+                            that.fetchCollection();
+                        }
+                    });
+                });
             }
         });
     return SearchResultLayoutView;


[2/4] incubator-atlas git commit: ATLAS-1193: UI to create/update entities

Posted by ma...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.js b/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.js
new file mode 100644
index 0000000..bb930fb
--- /dev/null
+++ b/dashboardv2/public/js/external_lib/datetimepicker/bootstrap-datetimepicker.js
@@ -0,0 +1,2444 @@
+\ufeff/*! version : 4.14.30
+ =========================================================
+ bootstrap-datetimejs
+ https://github.com/Eonasdan/bootstrap-datetimepicker
+ Copyright (c) 2015 Jonathan Peterson
+ =========================================================
+ */
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2015 Jonathan Peterson
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+/*global define:false */
+/*global exports:false */
+/*global require:false */
+/*global jQuery:false */
+/*global moment:false */
+(function (factory) {
+    'use strict';
+    if (typeof define === 'function' && define.amd) {
+        // AMD is used - Register as an anonymous module.
+        define(['jquery', 'moment'], factory);
+    } else if (typeof exports === 'object') {
+        factory(require('jquery'), require('moment'));
+    } else {
+        // Neither AMD nor CommonJS used. Use global variables.
+        if (typeof jQuery === 'undefined') {
+            throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
+        }
+        if (typeof moment === 'undefined') {
+            throw 'bootstrap-datetimepicker requires Moment.js to be loaded first';
+        }
+        factory(jQuery, moment);
+    }
+}(function ($, moment) {
+    'use strict';
+    if (!moment) {
+        throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first');
+    }
+
+    var dateTimePicker = function (element, options) {
+        var picker = {},
+            date = moment().startOf('d'),
+            viewDate = date.clone(),
+            unset = true,
+            input,
+            component = false,
+            widget = false,
+            use24Hours,
+            minViewModeNumber = 0,
+            actualFormat,
+            parseFormats,
+            currentViewMode,
+            datePickerModes = [
+                {
+                    clsName: 'days',
+                    navFnc: 'M',
+                    navStep: 1
+                },
+                {
+                    clsName: 'months',
+                    navFnc: 'y',
+                    navStep: 1
+                },
+                {
+                    clsName: 'years',
+                    navFnc: 'y',
+                    navStep: 10
+                },
+                {
+                    clsName: 'decades',
+                    navFnc: 'y',
+                    navStep: 100
+                }
+            ],
+            viewModes = ['days', 'months', 'years', 'decades'],
+            verticalModes = ['top', 'bottom', 'auto'],
+            horizontalModes = ['left', 'right', 'auto'],
+            toolbarPlacements = ['default', 'top', 'bottom'],
+            keyMap = {
+                'up': 38,
+                38: 'up',
+                'down': 40,
+                40: 'down',
+                'left': 37,
+                37: 'left',
+                'right': 39,
+                39: 'right',
+                'tab': 9,
+                9: 'tab',
+                'escape': 27,
+                27: 'escape',
+                'enter': 13,
+                13: 'enter',
+                'pageUp': 33,
+                33: 'pageUp',
+                'pageDown': 34,
+                34: 'pageDown',
+                'shift': 16,
+                16: 'shift',
+                'control': 17,
+                17: 'control',
+                'space': 32,
+                32: 'space',
+                't': 84,
+                84: 't',
+                'delete': 46,
+                46: 'delete'
+            },
+            keyState = {},
+
+            /********************************************************************************
+             *
+             * Private functions
+             *
+             ********************************************************************************/
+            isEnabled = function (granularity) {
+                if (typeof granularity !== 'string' || granularity.length > 1) {
+                    throw new TypeError('isEnabled expects a single character string parameter');
+                }
+                switch (granularity) {
+                    case 'y':
+                        return actualFormat.indexOf('Y') !== -1;
+                    case 'M':
+                        return actualFormat.indexOf('M') !== -1;
+                    case 'd':
+                        return actualFormat.toLowerCase().indexOf('d') !== -1;
+                    case 'h':
+                    case 'H':
+                        return actualFormat.toLowerCase().indexOf('h') !== -1;
+                    case 'm':
+                        return actualFormat.indexOf('m') !== -1;
+                    case 's':
+                        return actualFormat.indexOf('s') !== -1;
+                    default:
+                        return false;
+                }
+            },
+            hasTime = function () {
+                return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
+            },
+
+            hasDate = function () {
+                return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
+            },
+
+            getDatePickerTemplate = function () {
+                var headTemplate = $('<thead>')
+                        .append($('<tr>')
+                            .append($('<th>').addClass('prev').attr('data-action', 'previous')
+                                .append($('<span>').addClass(options.icons.previous))
+                                )
+                            .append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5')))
+                            .append($('<th>').addClass('next').attr('data-action', 'next')
+                                .append($('<span>').addClass(options.icons.next))
+                                )
+                            ),
+                    contTemplate = $('<tbody>')
+                        .append($('<tr>')
+                            .append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7')))
+                            );
+
+                return [
+                    $('<div>').addClass('datepicker-days')
+                        .append($('<table>').addClass('table-condensed')
+                            .append(headTemplate)
+                            .append($('<tbody>'))
+                            ),
+                    $('<div>').addClass('datepicker-months')
+                        .append($('<table>').addClass('table-condensed')
+                            .append(headTemplate.clone())
+                            .append(contTemplate.clone())
+                            ),
+                    $('<div>').addClass('datepicker-years')
+                        .append($('<table>').addClass('table-condensed')
+                            .append(headTemplate.clone())
+                            .append(contTemplate.clone())
+                            ),
+                    $('<div>').addClass('datepicker-decades')
+                        .append($('<table>').addClass('table-condensed')
+                            .append(headTemplate.clone())
+                            .append(contTemplate.clone())
+                            )
+                ];
+            },
+
+            getTimePickerMainTemplate = function () {
+                var topRow = $('<tr>'),
+                    middleRow = $('<tr>'),
+                    bottomRow = $('<tr>');
+
+                if (isEnabled('h')) {
+                    topRow.append($('<td>')
+                        .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Hour'}).addClass('btn').attr('data-action', 'incrementHours')
+                            .append($('<span>').addClass(options.icons.up))));
+                    middleRow.append($('<td>')
+                        .append($('<span>').addClass('timepicker-hour').attr({'data-time-component':'hours', 'title':'Pick Hour'}).attr('data-action', 'showHours')));
+                    bottomRow.append($('<td>')
+                        .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Hour'}).addClass('btn').attr('data-action', 'decrementHours')
+                            .append($('<span>').addClass(options.icons.down))));
+                }
+                if (isEnabled('m')) {
+                    if (isEnabled('h')) {
+                        topRow.append($('<td>').addClass('separator'));
+                        middleRow.append($('<td>').addClass('separator').html(':'));
+                        bottomRow.append($('<td>').addClass('separator'));
+                    }
+                    topRow.append($('<td>')
+                        .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Minute'}).addClass('btn').attr('data-action', 'incrementMinutes')
+                            .append($('<span>').addClass(options.icons.up))));
+                    middleRow.append($('<td>')
+                        .append($('<span>').addClass('timepicker-minute').attr({'data-time-component': 'minutes', 'title':'Pick Minute'}).attr('data-action', 'showMinutes')));
+                    bottomRow.append($('<td>')
+                        .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Minute'}).addClass('btn').attr('data-action', 'decrementMinutes')
+                            .append($('<span>').addClass(options.icons.down))));
+                }
+                if (isEnabled('s')) {
+                    if (isEnabled('m')) {
+                        topRow.append($('<td>').addClass('separator'));
+                        middleRow.append($('<td>').addClass('separator').html(':'));
+                        bottomRow.append($('<td>').addClass('separator'));
+                    }
+                    topRow.append($('<td>')
+                        .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Increment Second'}).addClass('btn').attr('data-action', 'incrementSeconds')
+                            .append($('<span>').addClass(options.icons.up))));
+                    middleRow.append($('<td>')
+                        .append($('<span>').addClass('timepicker-second').attr({'data-time-component': 'seconds', 'title':'Pick Second'}).attr('data-action', 'showSeconds')));
+                    bottomRow.append($('<td>')
+                        .append($('<a>').attr({href: '#', tabindex: '-1', 'title':'Decrement Second'}).addClass('btn').attr('data-action', 'decrementSeconds')
+                            .append($('<span>').addClass(options.icons.down))));
+                }
+
+                if (!use24Hours) {
+                    topRow.append($('<td>').addClass('separator'));
+                    middleRow.append($('<td>')
+                        .append($('<button>').addClass('btn btn-primary').attr({'data-action': 'togglePeriod', tabindex: '-1', 'title':'Toggle Period'})));
+                    bottomRow.append($('<td>').addClass('separator'));
+                }
+
+                return $('<div>').addClass('timepicker-picker')
+                    .append($('<table>').addClass('table-condensed')
+                        .append([topRow, middleRow, bottomRow]));
+            },
+
+            getTimePickerTemplate = function () {
+                var hoursView = $('<div>').addClass('timepicker-hours')
+                        .append($('<table>').addClass('table-condensed')),
+                    minutesView = $('<div>').addClass('timepicker-minutes')
+                        .append($('<table>').addClass('table-condensed')),
+                    secondsView = $('<div>').addClass('timepicker-seconds')
+                        .append($('<table>').addClass('table-condensed')),
+                    ret = [getTimePickerMainTemplate()];
+
+                if (isEnabled('h')) {
+                    ret.push(hoursView);
+                }
+                if (isEnabled('m')) {
+                    ret.push(minutesView);
+                }
+                if (isEnabled('s')) {
+                    ret.push(secondsView);
+                }
+
+                return ret;
+            },
+
+            getToolbar = function () {
+                var row = [];
+                if (options.showTodayButton) {
+                    row.push($('<td>').append($('<a>').attr({'data-action':'today', 'title':'Go to today'}).append($('<span>').addClass(options.icons.today))));
+                }
+                if (!options.sideBySide && hasDate() && hasTime()) {
+                    row.push($('<td>').append($('<a>').attr({'data-action':'togglePicker', 'title':'Select Time'}).append($('<span>').addClass(options.icons.time))));
+                }
+                if (options.showClear) {
+                    row.push($('<td>').append($('<a>').attr({'data-action':'clear', 'title':'Clear selection'}).append($('<span>').addClass(options.icons.clear))));
+                }
+                if (options.showClose) {
+                    row.push($('<td>').append($('<a>').attr({'data-action':'close', 'title':'Close the picker'}).append($('<span>').addClass(options.icons.close))));
+                }
+                return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
+            },
+
+            getTemplate = function () {
+                var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),
+                    dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()),
+                    timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()),
+                    content = $('<ul>').addClass('list-unstyled'),
+                    toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar());
+
+                if (options.inline) {
+                    template.removeClass('dropdown-menu');
+                }
+
+                if (use24Hours) {
+                    template.addClass('usetwentyfour');
+                }
+                if (isEnabled('s') && !use24Hours) {
+                    template.addClass('wider');
+                }
+                if (options.sideBySide && hasDate() && hasTime()) {
+                    template.addClass('timepicker-sbs');
+                    template.append(
+                        $('<div>').addClass('row')
+                            .append(dateView.addClass('col-sm-6'))
+                            .append(timeView.addClass('col-sm-6'))
+                    );
+                    template.append(toolbar);
+                    return template;
+                }
+
+                if (options.toolbarPlacement === 'top') {
+                    content.append(toolbar);
+                }
+                if (hasDate()) {
+                    content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView));
+                }
+                if (options.toolbarPlacement === 'default') {
+                    content.append(toolbar);
+                }
+                if (hasTime()) {
+                    content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView));
+                }
+                if (options.toolbarPlacement === 'bottom') {
+                    content.append(toolbar);
+                }
+                return template.append(content);
+            },
+
+            dataToOptions = function () {
+                var eData,
+                    dataOptions = {};
+
+                if (element.is('input') || options.inline) {
+                    eData = element.data();
+                } else {
+                    eData = element.find('input').data();
+                }
+
+                if (eData.dateOptions && eData.dateOptions instanceof Object) {
+                    dataOptions = $.extend(true, dataOptions, eData.dateOptions);
+                }
+
+                $.each(options, function (key) {
+                    var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1);
+                    if (eData[attributeName] !== undefined) {
+                        dataOptions[key] = eData[attributeName];
+                    }
+                });
+                return dataOptions;
+            },
+
+            place = function () {
+                var position = (component || element).position(),
+                    offset = (component || element).offset(),
+                    vertical = options.widgetPositioning.vertical,
+                    horizontal = options.widgetPositioning.horizontal,
+                    parent;
+
+                if (options.widgetParent) {
+                    parent = options.widgetParent.append(widget);
+                } else if (element.is('input')) {
+                    parent = element.after(widget).parent();
+                } else if (options.inline) {
+                    parent = element.append(widget);
+                    return;
+                } else {
+                    parent = element;
+                    element.children().first().after(widget);
+                }
+
+                // Top and bottom logic
+                if (vertical === 'auto') {
+                    if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() &&
+                        widget.height() + element.outerHeight() < offset.top) {
+                        vertical = 'top';
+                    } else {
+                        vertical = 'bottom';
+                    }
+                }
+
+                // Left and right logic
+                if (horizontal === 'auto') {
+                    if (parent.width() < offset.left + widget.outerWidth() / 2 &&
+                        offset.left + widget.outerWidth() > $(window).width()) {
+                        horizontal = 'right';
+                    } else {
+                        horizontal = 'left';
+                    }
+                }
+
+                if (vertical === 'top') {
+                    widget.addClass('top').removeClass('bottom');
+                } else {
+                    widget.addClass('bottom').removeClass('top');
+                }
+
+                if (horizontal === 'right') {
+                    widget.addClass('pull-right');
+                } else {
+                    widget.removeClass('pull-right');
+                }
+
+                // find the first parent element that has a relative css positioning
+                if (parent.css('position') !== 'relative') {
+                    parent = parent.parents().filter(function () {
+                        return $(this).css('position') === 'relative';
+                    }).first();
+                }
+
+                if (parent.length === 0) {
+                    throw new Error('datetimepicker component should be placed within a relative positioned container');
+                }
+
+                widget.css({
+                    top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
+                    bottom: vertical === 'top' ? position.top + element.outerHeight() : 'auto',
+                    left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
+                    right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
+                });
+            },
+
+            notifyEvent = function (e) {
+                if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) {
+                    return;
+                }
+                element.trigger(e);
+            },
+
+            viewUpdate = function (e) {
+                if (e === 'y') {
+                    e = 'YYYY';
+                }
+                notifyEvent({
+                    type: 'dp.update',
+                    change: e,
+                    viewDate: viewDate.clone()
+                });
+            },
+
+            showMode = function (dir) {
+                if (!widget) {
+                    return;
+                }
+                if (dir) {
+                    currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir));
+                }
+                widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show();
+            },
+
+            fillDow = function () {
+                var row = $('<tr>'),
+                    currentDate = viewDate.clone().startOf('w').startOf('d');
+
+                if (options.calendarWeeks === true) {
+                    row.append($('<th>').addClass('cw').text('#'));
+                }
+
+                while (currentDate.isBefore(viewDate.clone().endOf('w'))) {
+                    row.append($('<th>').addClass('dow').text(currentDate.format('dd')));
+                    currentDate.add(1, 'd');
+                }
+                widget.find('.datepicker-days thead').append(row);
+            },
+
+            isInDisabledDates = function (testDate) {
+                return options.disabledDates[testDate.format('YYYY-MM-DD')] === true;
+            },
+
+            isInEnabledDates = function (testDate) {
+                return options.enabledDates[testDate.format('YYYY-MM-DD')] === true;
+            },
+
+            isInDisabledHours = function (testDate) {
+                return options.disabledHours[testDate.format('H')] === true;
+            },
+
+            isInEnabledHours = function (testDate) {
+                return options.enabledHours[testDate.format('H')] === true;
+            },
+
+            isValid = function (targetMoment, granularity) {
+                if (!targetMoment.isValid()) {
+                    return false;
+                }
+                if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) {
+                    return false;
+                }
+                if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) {
+                    return false;
+                }
+                if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) {
+                    return false;
+                }
+                if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) {
+                    return false;
+                }
+                if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {
+                    return false;
+                }
+                if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) {
+                    return false;
+                }
+                if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) {
+                    return false;
+                }
+                if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) {
+                    var found = false;
+                    $.each(options.disabledTimeIntervals, function () {
+                        if (targetMoment.isBetween(this[0], this[1])) {
+                            found = true;
+                            return false;
+                        }
+                    });
+                    if (found) {
+                        return false;
+                    }
+                }
+                return true;
+            },
+
+            fillMonths = function () {
+                var spans = [],
+                    monthsShort = viewDate.clone().startOf('y').startOf('d');
+                while (monthsShort.isSame(viewDate, 'y')) {
+                    spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));
+                    monthsShort.add(1, 'M');
+                }
+                widget.find('.datepicker-months td').empty().append(spans);
+            },
+
+            updateMonths = function () {
+                var monthsView = widget.find('.datepicker-months'),
+                    monthsViewHeader = monthsView.find('th'),
+                    months = monthsView.find('tbody').find('span');
+
+                monthsViewHeader.eq(0).find('span').attr('title', 'Previous Year');
+                monthsViewHeader.eq(1).attr('title', 'Select Year');
+                monthsViewHeader.eq(2).find('span').attr('title', 'Next Year');
+
+                monthsView.find('.disabled').removeClass('disabled');
+
+                if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) {
+                    monthsViewHeader.eq(0).addClass('disabled');
+                }
+
+                monthsViewHeader.eq(1).text(viewDate.year());
+
+                if (!isValid(viewDate.clone().add(1, 'y'), 'y')) {
+                    monthsViewHeader.eq(2).addClass('disabled');
+                }
+
+                months.removeClass('active');
+                if (date.isSame(viewDate, 'y') && !unset) {
+                    months.eq(date.month()).addClass('active');
+                }
+
+                months.each(function (index) {
+                    if (!isValid(viewDate.clone().month(index), 'M')) {
+                        $(this).addClass('disabled');
+                    }
+                });
+            },
+
+            updateYears = function () {
+                var yearsView = widget.find('.datepicker-years'),
+                    yearsViewHeader = yearsView.find('th'),
+                    startYear = viewDate.clone().subtract(5, 'y'),
+                    endYear = viewDate.clone().add(6, 'y'),
+                    html = '';
+
+                yearsViewHeader.eq(0).find('span').attr('title', 'Previous Decade');
+                yearsViewHeader.eq(1).attr('title', 'Select Decade');
+                yearsViewHeader.eq(2).find('span').attr('title', 'Next Decade');
+
+                yearsView.find('.disabled').removeClass('disabled');
+
+                if (options.minDate && options.minDate.isAfter(startYear, 'y')) {
+                    yearsViewHeader.eq(0).addClass('disabled');
+                }
+
+                yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());
+
+                if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) {
+                    yearsViewHeader.eq(2).addClass('disabled');
+                }
+
+                while (!startYear.isAfter(endYear, 'y')) {
+                    html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>';
+                    startYear.add(1, 'y');
+                }
+
+                yearsView.find('td').html(html);
+            },
+
+            updateDecades = function () {
+                var decadesView = widget.find('.datepicker-decades'),
+                    decadesViewHeader = decadesView.find('th'),
+                    startDecade = viewDate.isBefore(moment({y: 1999})) ? moment({y: 1899}) : moment({y: 1999}),
+                    endDecade = startDecade.clone().add(100, 'y'),
+                    html = '';
+
+                decadesViewHeader.eq(0).find('span').attr('title', 'Previous Century');
+                decadesViewHeader.eq(2).find('span').attr('title', 'Next Century');
+
+                decadesView.find('.disabled').removeClass('disabled');
+
+                if (startDecade.isSame(moment({y: 1900})) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
+                    decadesViewHeader.eq(0).addClass('disabled');
+                }
+
+                decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());
+
+                if (startDecade.isSame(moment({y: 2000})) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
+                    decadesViewHeader.eq(2).addClass('disabled');
+                }
+
+                while (!startDecade.isAfter(endDecade, 'y')) {
+                    html += '<span data-action="selectDecade" class="decade' + (startDecade.isSame(date, 'y') ? ' active' : '') +
+                        (!isValid(startDecade, 'y') ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
+                    startDecade.add(12, 'y');
+                }
+                html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even
+
+                decadesView.find('td').html(html);
+            },
+
+            fillDate = function () {
+                var daysView = widget.find('.datepicker-days'),
+                    daysViewHeader = daysView.find('th'),
+                    currentDate,
+                    html = [],
+                    row,
+                    clsName,
+                    i;
+
+                if (!hasDate()) {
+                    return;
+                }
+
+                daysViewHeader.eq(0).find('span').attr('title', 'Previous Month');
+                daysViewHeader.eq(1).attr('title', 'Select Month');
+                daysViewHeader.eq(2).find('span').attr('title', 'Next Month');
+
+                daysView.find('.disabled').removeClass('disabled');
+                daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));
+
+                if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) {
+                    daysViewHeader.eq(0).addClass('disabled');
+                }
+                if (!isValid(viewDate.clone().add(1, 'M'), 'M')) {
+                    daysViewHeader.eq(2).addClass('disabled');
+                }
+
+                currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d');
+
+                for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks)
+                    if (currentDate.weekday() === 0) {
+                        row = $('<tr>');
+                        if (options.calendarWeeks) {
+                            row.append('<td class="cw">' + currentDate.week() + '</td>');
+                        }
+                        html.push(row);
+                    }
+                    clsName = '';
+                    if (currentDate.isBefore(viewDate, 'M')) {
+                        clsName += ' old';
+                    }
+                    if (currentDate.isAfter(viewDate, 'M')) {
+                        clsName += ' new';
+                    }
+                    if (currentDate.isSame(date, 'd') && !unset) {
+                        clsName += ' active';
+                    }
+                    if (!isValid(currentDate, 'd')) {
+                        clsName += ' disabled';
+                    }
+                    if (currentDate.isSame(moment(), 'd')) {
+                        clsName += ' today';
+                    }
+                    if (currentDate.day() === 0 || currentDate.day() === 6) {
+                        clsName += ' weekend';
+                    }
+                    row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="day' + clsName + '">' + currentDate.date() + '</td>');
+                    currentDate.add(1, 'd');
+                }
+
+                daysView.find('tbody').empty().append(html);
+
+                updateMonths();
+
+                updateYears();
+
+                updateDecades();
+            },
+
+            fillHours = function () {
+                var table = widget.find('.timepicker-hours table'),
+                    currentHour = viewDate.clone().startOf('d'),
+                    html = [],
+                    row = $('<tr>');
+
+                if (viewDate.hour() > 11 && !use24Hours) {
+                    currentHour.hour(12);
+                }
+                while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) {
+                    if (currentHour.hour() % 4 === 0) {
+                        row = $('<tr>');
+                        html.push(row);
+                    }
+                    row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>');
+                    currentHour.add(1, 'h');
+                }
+                table.empty().append(html);
+            },
+
+            fillMinutes = function () {
+                var table = widget.find('.timepicker-minutes table'),
+                    currentMinute = viewDate.clone().startOf('h'),
+                    html = [],
+                    row = $('<tr>'),
+                    step = options.stepping === 1 ? 5 : options.stepping;
+
+                while (viewDate.isSame(currentMinute, 'h')) {
+                    if (currentMinute.minute() % (step * 4) === 0) {
+                        row = $('<tr>');
+                        html.push(row);
+                    }
+                    row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>');
+                    currentMinute.add(step, 'm');
+                }
+                table.empty().append(html);
+            },
+
+            fillSeconds = function () {
+                var table = widget.find('.timepicker-seconds table'),
+                    currentSecond = viewDate.clone().startOf('m'),
+                    html = [],
+                    row = $('<tr>');
+
+                while (viewDate.isSame(currentSecond, 'm')) {
+                    if (currentSecond.second() % 20 === 0) {
+                        row = $('<tr>');
+                        html.push(row);
+                    }
+                    row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>');
+                    currentSecond.add(5, 's');
+                }
+
+                table.empty().append(html);
+            },
+
+            fillTime = function () {
+                var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]');
+
+                if (!use24Hours) {
+                    toggle = widget.find('.timepicker [data-action=togglePeriod]');
+                    newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h');
+
+                    toggle.text(date.format('A'));
+
+                    if (isValid(newDate, 'h')) {
+                        toggle.removeClass('disabled');
+                    } else {
+                        toggle.addClass('disabled');
+                    }
+                }
+                timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh'));
+                timeComponents.filter('[data-time-component=minutes]').text(date.format('mm'));
+                timeComponents.filter('[data-time-component=seconds]').text(date.format('ss'));
+
+                fillHours();
+                fillMinutes();
+                fillSeconds();
+            },
+
+            update = function () {
+                if (!widget) {
+                    return;
+                }
+                fillDate();
+                fillTime();
+            },
+
+            setValue = function (targetMoment) {
+                var oldDate = unset ? null : date;
+
+                // case of calling setValue(null or false)
+                if (!targetMoment) {
+                    unset = true;
+                    input.val('');
+                    element.data('date', '');
+                    notifyEvent({
+                        type: 'dp.change',
+                        date: false,
+                        oldDate: oldDate
+                    });
+                    update();
+                    return;
+                }
+
+                targetMoment = targetMoment.clone().locale(options.locale);
+
+                if (options.stepping !== 1) {
+                    targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping) % 60).seconds(0);
+                }
+
+                if (isValid(targetMoment)) {
+                    date = targetMoment;
+                    viewDate = date.clone();
+                    input.val(date.format(actualFormat));
+                    element.data('date', date.format(actualFormat));
+                    unset = false;
+                    update();
+                    notifyEvent({
+                        type: 'dp.change',
+                        date: date.clone(),
+                        oldDate: oldDate
+                    });
+                } else {
+                    if (!options.keepInvalid) {
+                        input.val(unset ? '' : date.format(actualFormat));
+                    }
+                    notifyEvent({
+                        type: 'dp.error',
+                        date: targetMoment
+                    });
+                }
+            },
+
+            hide = function () {
+                ///<summary>Hides the widget. Possibly will emit dp.hide</summary>
+                var transitioning = false;
+                if (!widget) {
+                    return picker;
+                }
+                // Ignore event if in the middle of a picker transition
+                widget.find('.collapse').each(function () {
+                    var collapseData = $(this).data('collapse');
+                    if (collapseData && collapseData.transitioning) {
+                        transitioning = true;
+                        return false;
+                    }
+                    return true;
+                });
+                if (transitioning) {
+                    return picker;
+                }
+                if (component && component.hasClass('btn')) {
+                    component.toggleClass('active');
+                }
+                widget.hide();
+
+                $(window).off('resize', place);
+                widget.off('click', '[data-action]');
+                widget.off('mousedown', false);
+
+                widget.remove();
+                widget = false;
+
+                notifyEvent({
+                    type: 'dp.hide',
+                    date: date.clone()
+                });
+                return picker;
+            },
+
+            clear = function () {
+                setValue(null);
+            },
+
+            /********************************************************************************
+             *
+             * Widget UI interaction functions
+             *
+             ********************************************************************************/
+            actions = {
+                next: function () {
+                    var navFnc = datePickerModes[currentViewMode].navFnc;
+                    viewDate.add(datePickerModes[currentViewMode].navStep, navFnc);
+                    fillDate();
+                    viewUpdate(navFnc);
+                },
+
+                previous: function () {
+                    var navFnc = datePickerModes[currentViewMode].navFnc;
+                    viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc);
+                    fillDate();
+                    viewUpdate(navFnc);
+                },
+
+                pickerSwitch: function () {
+                    showMode(1);
+                },
+
+                selectMonth: function (e) {
+                    var month = $(e.target).closest('tbody').find('span').index($(e.target));
+                    viewDate.month(month);
+                    if (currentViewMode === minViewModeNumber) {
+                        setValue(date.clone().year(viewDate.year()).month(viewDate.month()));
+                        if (!options.inline) {
+                            hide();
+                        }
+                    } else {
+                        showMode(-1);
+                        fillDate();
+                    }
+                    viewUpdate('M');
+                },
+
+                selectYear: function (e) {
+                    var year = parseInt($(e.target).text(), 10) || 0;
+                    viewDate.year(year);
+                    if (currentViewMode === minViewModeNumber) {
+                        setValue(date.clone().year(viewDate.year()));
+                        if (!options.inline) {
+                            hide();
+                        }
+                    } else {
+                        showMode(-1);
+                        fillDate();
+                    }
+                    viewUpdate('YYYY');
+                },
+
+                selectDecade: function (e) {
+                    var year = parseInt($(e.target).data('selection'), 10) || 0;
+                    viewDate.year(year);
+                    if (currentViewMode === minViewModeNumber) {
+                        setValue(date.clone().year(viewDate.year()));
+                        if (!options.inline) {
+                            hide();
+                        }
+                    } else {
+                        showMode(-1);
+                        fillDate();
+                    }
+                    viewUpdate('YYYY');
+                },
+
+                selectDay: function (e) {
+                    var day = viewDate.clone();
+                    if ($(e.target).is('.old')) {
+                        day.subtract(1, 'M');
+                    }
+                    if ($(e.target).is('.new')) {
+                        day.add(1, 'M');
+                    }
+                    setValue(day.date(parseInt($(e.target).text(), 10)));
+                    if (!hasTime() && !options.keepOpen && !options.inline) {
+                        hide();
+                    }
+                },
+
+                incrementHours: function () {
+                    var newDate = date.clone().add(1, 'h');
+                    if (isValid(newDate, 'h')) {
+                        setValue(newDate);
+                    }
+                },
+
+                incrementMinutes: function () {
+                    var newDate = date.clone().add(options.stepping, 'm');
+                    if (isValid(newDate, 'm')) {
+                        setValue(newDate);
+                    }
+                },
+
+                incrementSeconds: function () {
+                    var newDate = date.clone().add(1, 's');
+                    if (isValid(newDate, 's')) {
+                        setValue(newDate);
+                    }
+                },
+
+                decrementHours: function () {
+                    var newDate = date.clone().subtract(1, 'h');
+                    if (isValid(newDate, 'h')) {
+                        setValue(newDate);
+                    }
+                },
+
+                decrementMinutes: function () {
+                    var newDate = date.clone().subtract(options.stepping, 'm');
+                    if (isValid(newDate, 'm')) {
+                        setValue(newDate);
+                    }
+                },
+
+                decrementSeconds: function () {
+                    var newDate = date.clone().subtract(1, 's');
+                    if (isValid(newDate, 's')) {
+                        setValue(newDate);
+                    }
+                },
+
+                togglePeriod: function () {
+                    setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'));
+                },
+
+                togglePicker: function (e) {
+                    var $this = $(e.target),
+                        $parent = $this.closest('ul'),
+                        expanded = $parent.find('.in'),
+                        closed = $parent.find('.collapse:not(.in)'),
+                        collapseData;
+
+                    if (expanded && expanded.length) {
+                        collapseData = expanded.data('collapse');
+                        if (collapseData && collapseData.transitioning) {
+                            return;
+                        }
+                        if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it
+                            expanded.collapse('hide');
+                            closed.collapse('show');
+                        } else { // otherwise just toggle in class on the two views
+                            expanded.removeClass('in');
+                            closed.addClass('in');
+                        }
+                        if ($this.is('span')) {
+                            $this.toggleClass(options.icons.time + ' ' + options.icons.date);
+                        } else {
+                            $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
+                        }
+
+                        // NOTE: uncomment if toggled state will be restored in show()
+                        //if (component) {
+                        //    component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
+                        //}
+                    }
+                },
+
+                showPicker: function () {
+                    widget.find('.timepicker > div:not(.timepicker-picker)').hide();
+                    widget.find('.timepicker .timepicker-picker').show();
+                },
+
+                showHours: function () {
+                    widget.find('.timepicker .timepicker-picker').hide();
+                    widget.find('.timepicker .timepicker-hours').show();
+                },
+
+                showMinutes: function () {
+                    widget.find('.timepicker .timepicker-picker').hide();
+                    widget.find('.timepicker .timepicker-minutes').show();
+                },
+
+                showSeconds: function () {
+                    widget.find('.timepicker .timepicker-picker').hide();
+                    widget.find('.timepicker .timepicker-seconds').show();
+                },
+
+                selectHour: function (e) {
+                    var hour = parseInt($(e.target).text(), 10);
+
+                    if (!use24Hours) {
+                        if (date.hours() >= 12) {
+                            if (hour !== 12) {
+                                hour += 12;
+                            }
+                        } else {
+                            if (hour === 12) {
+                                hour = 0;
+                            }
+                        }
+                    }
+                    setValue(date.clone().hours(hour));
+                    actions.showPicker.call(picker);
+                },
+
+                selectMinute: function (e) {
+                    setValue(date.clone().minutes(parseInt($(e.target).text(), 10)));
+                    actions.showPicker.call(picker);
+                },
+
+                selectSecond: function (e) {
+                    setValue(date.clone().seconds(parseInt($(e.target).text(), 10)));
+                    actions.showPicker.call(picker);
+                },
+
+                clear: clear,
+
+                today: function () {
+                    if (isValid(moment(), 'd')) {
+                        setValue(moment());
+                    }
+                },
+
+                close: hide
+            },
+
+            doAction = function (e) {
+                if ($(e.currentTarget).is('.disabled')) {
+                    return false;
+                }
+                actions[$(e.currentTarget).data('action')].apply(picker, arguments);
+                return false;
+            },
+
+            show = function () {
+                ///<summary>Shows the widget. Possibly will emit dp.show and dp.change</summary>
+                var currentMoment,
+                    useCurrentGranularity = {
+                        'year': function (m) {
+                            return m.month(0).date(1).hours(0).seconds(0).minutes(0);
+                        },
+                        'month': function (m) {
+                            return m.date(1).hours(0).seconds(0).minutes(0);
+                        },
+                        'day': function (m) {
+                            return m.hours(0).seconds(0).minutes(0);
+                        },
+                        'hour': function (m) {
+                            return m.seconds(0).minutes(0);
+                        },
+                        'minute': function (m) {
+                            return m.seconds(0);
+                        }
+                    };
+
+                if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) {
+                    return picker;
+                }
+                if (input.val() !== undefined && input.val().trim().length !== 0) {
+                    setValue(parseInputDate(input.val().trim()));
+                } else if (options.useCurrent && unset && ((input.is('input') && input.val().trim().length === 0) || options.inline)) {
+                    currentMoment = moment();
+                    if (typeof options.useCurrent === 'string') {
+                        currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
+                    }
+                    setValue(currentMoment);
+                }
+
+                widget = getTemplate();
+
+                fillDow();
+                fillMonths();
+
+                widget.find('.timepicker-hours').hide();
+                widget.find('.timepicker-minutes').hide();
+                widget.find('.timepicker-seconds').hide();
+
+                update();
+                showMode();
+
+                $(window).on('resize', place);
+                widget.on('click', '[data-action]', doAction); // this handles clicks on the widget
+                widget.on('mousedown', false);
+
+                if (component && component.hasClass('btn')) {
+                    component.toggleClass('active');
+                }
+                widget.show();
+                place();
+
+                if (options.focusOnShow && !input.is(':focus')) {
+                    input.focus();
+                }
+
+                notifyEvent({
+                    type: 'dp.show'
+                });
+                return picker;
+            },
+
+            toggle = function () {
+                /// <summary>Shows or hides the widget</summary>
+                return (widget ? hide() : show());
+            },
+
+            parseInputDate = function (inputDate) {
+                if (options.parseInputDate === undefined) {
+                    if (moment.isMoment(inputDate) || inputDate instanceof Date) {
+                        inputDate = moment(inputDate);
+                    } else {
+                        inputDate = moment(inputDate, parseFormats, options.useStrict);
+                    }
+                } else {
+                    inputDate = options.parseInputDate(inputDate);
+                }
+                inputDate.locale(options.locale);
+                return inputDate;
+            },
+
+            keydown = function (e) {
+                var handler = null,
+                    index,
+                    index2,
+                    pressedKeys = [],
+                    pressedModifiers = {},
+                    currentKey = e.which,
+                    keyBindKeys,
+                    allModifiersPressed,
+                    pressed = 'p';
+
+                keyState[currentKey] = pressed;
+
+                for (index in keyState) {
+                    if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {
+                        pressedKeys.push(index);
+                        if (parseInt(index, 10) !== currentKey) {
+                            pressedModifiers[index] = true;
+                        }
+                    }
+                }
+
+                for (index in options.keyBinds) {
+                    if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') {
+                        keyBindKeys = index.split(' ');
+                        if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {
+                            allModifiersPressed = true;
+                            for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {
+                                if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) {
+                                    allModifiersPressed = false;
+                                    break;
+                                }
+                            }
+                            if (allModifiersPressed) {
+                                handler = options.keyBinds[index];
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                if (handler) {
+                    handler.call(picker, widget);
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
+            },
+
+            keyup = function (e) {
+                keyState[e.which] = 'r';
+                e.stopPropagation();
+                e.preventDefault();
+            },
+
+            change = function (e) {
+                var val = $(e.target).val().trim(),
+                    parsedDate = val ? parseInputDate(val) : null;
+                setValue(parsedDate);
+                e.stopImmediatePropagation();
+                return false;
+            },
+
+            attachDatePickerElementEvents = function () {
+                input.on({
+                    'change': change,
+                    'blur': options.debug ? '' : hide,
+                    'keydown': keydown,
+                    'keyup': keyup,
+                    'focus': options.allowInputToggle ? show : ''
+                });
+
+                if (element.is('input')) {
+                    input.on({
+                        'focus': show
+                    });
+                } else if (component) {
+                    component.on('click', toggle);
+                    component.on('mousedown', false);
+                }
+            },
+
+            detachDatePickerElementEvents = function () {
+                input.off({
+                    'change': change,
+                    'blur': hide,
+                    'keydown': keydown,
+                    'keyup': keyup,
+                    'focus': options.allowInputToggle ? hide : ''
+                });
+
+                if (element.is('input')) {
+                    input.off({
+                        'focus': show
+                    });
+                } else if (component) {
+                    component.off('click', toggle);
+                    component.off('mousedown', false);
+                }
+            },
+
+            indexGivenDates = function (givenDatesArray) {
+                // Store given enabledDates and disabledDates as keys.
+                // This way we can check their existence in O(1) time instead of looping through whole array.
+                // (for example: options.enabledDates['2014-02-27'] === true)
+                var givenDatesIndexed = {};
+                $.each(givenDatesArray, function () {
+                    var dDate = parseInputDate(this);
+                    if (dDate.isValid()) {
+                        givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;
+                    }
+                });
+                return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false;
+            },
+
+            indexGivenHours = function (givenHoursArray) {
+                // Store given enabledHours and disabledHours as keys.
+                // This way we can check their existence in O(1) time instead of looping through whole array.
+                // (for example: options.enabledHours['2014-02-27'] === true)
+                var givenHoursIndexed = {};
+                $.each(givenHoursArray, function () {
+                    givenHoursIndexed[this] = true;
+                });
+                return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false;
+            },
+
+            initFormatting = function () {
+                var format = options.format || 'L LT';
+
+                actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) {
+                    var newinput = date.localeData().longDateFormat(formatInput) || formatInput;
+                    return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740
+                        return date.localeData().longDateFormat(formatInput2) || formatInput2;
+                    });
+                });
+
+
+                parseFormats = options.extraFormats ? options.extraFormats.slice() : [];
+                if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) {
+                    parseFormats.push(actualFormat);
+                }
+
+                use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1);
+
+                if (isEnabled('y')) {
+                    minViewModeNumber = 2;
+                }
+                if (isEnabled('M')) {
+                    minViewModeNumber = 1;
+                }
+                if (isEnabled('d')) {
+                    minViewModeNumber = 0;
+                }
+
+                currentViewMode = Math.max(minViewModeNumber, currentViewMode);
+
+                if (!unset) {
+                    setValue(date);
+                }
+            };
+
+        /********************************************************************************
+         *
+         * Public API functions
+         * =====================
+         *
+         * Important: Do not expose direct references to private objects or the options
+         * object to the outer world. Always return a clone when returning values or make
+         * a clone when setting a private variable.
+         *
+         ********************************************************************************/
+        picker.destroy = function () {
+            ///<summary>Destroys the widget and removes all attached event listeners</summary>
+            hide();
+            detachDatePickerElementEvents();
+            element.removeData('DateTimePicker');
+            element.removeData('date');
+        };
+
+        picker.toggle = toggle;
+
+        picker.show = show;
+
+        picker.hide = hide;
+
+        picker.disable = function () {
+            ///<summary>Disables the input element, the component is attached to, by adding a disabled="true" attribute to it.
+            ///If the widget was visible before that call it is hidden. Possibly emits dp.hide</summary>
+            hide();
+            if (component && component.hasClass('btn')) {
+                component.addClass('disabled');
+            }
+            input.prop('disabled', true);
+            return picker;
+        };
+
+        picker.enable = function () {
+            ///<summary>Enables the input element, the component is attached to, by removing disabled attribute from it.</summary>
+            if (component && component.hasClass('btn')) {
+                component.removeClass('disabled');
+            }
+            input.prop('disabled', false);
+            return picker;
+        };
+
+        picker.ignoreReadonly = function (ignoreReadonly) {
+            if (arguments.length === 0) {
+                return options.ignoreReadonly;
+            }
+            if (typeof ignoreReadonly !== 'boolean') {
+                throw new TypeError('ignoreReadonly () expects a boolean parameter');
+            }
+            options.ignoreReadonly = ignoreReadonly;
+            return picker;
+        };
+
+        picker.options = function (newOptions) {
+            if (arguments.length === 0) {
+                return $.extend(true, {}, options);
+            }
+
+            if (!(newOptions instanceof Object)) {
+                throw new TypeError('options() options parameter should be an object');
+            }
+            $.extend(true, options, newOptions);
+            $.each(options, function (key, value) {
+                if (picker[key] !== undefined) {
+                    picker[key](value);
+                } else {
+                    throw new TypeError('option ' + key + ' is not recognized!');
+                }
+            });
+            return picker;
+        };
+
+        picker.date = function (newDate) {
+            ///<signature helpKeyword="$.fn.datetimepicker.date">
+            ///<summary>Returns the component's model current date, a moment object or null if not set.</summary>
+            ///<returns type="Moment">date.clone()</returns>
+            ///</signature>
+            ///<signature>
+            ///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary>
+            ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, Date, moment, null parameter.</param>
+            ///</signature>
+            if (arguments.length === 0) {
+                if (unset) {
+                    return null;
+                }
+                return date.clone();
+            }
+
+            if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
+                throw new TypeError('date() parameter must be one of [null, string, moment or Date]');
+            }
+
+            setValue(newDate === null ? null : parseInputDate(newDate));
+            return picker;
+        };
+
+        picker.format = function (newFormat) {
+            ///<summary>test su</summary>
+            ///<param name="newFormat">info about para</param>
+            ///<returns type="string|boolean">returns foo</returns>
+            if (arguments.length === 0) {
+                return options.format;
+            }
+
+            if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
+                throw new TypeError('format() expects a sting or boolean:false parameter ' + newFormat);
+            }
+
+            options.format = newFormat;
+            if (actualFormat) {
+                initFormatting(); // reinit formatting
+            }
+            return picker;
+        };
+
+        picker.dayViewHeaderFormat = function (newFormat) {
+            if (arguments.length === 0) {
+                return options.dayViewHeaderFormat;
+            }
+
+            if (typeof newFormat !== 'string') {
+                throw new TypeError('dayViewHeaderFormat() expects a string parameter');
+            }
+
+            options.dayViewHeaderFormat = newFormat;
+            return picker;
+        };
+
+        picker.extraFormats = function (formats) {
+            if (arguments.length === 0) {
+                return options.extraFormats;
+            }
+
+            if (formats !== false && !(formats instanceof Array)) {
+                throw new TypeError('extraFormats() expects an array or false parameter');
+            }
+
+            options.extraFormats = formats;
+            if (parseFormats) {
+                initFormatting(); // reinit formatting
+            }
+            return picker;
+        };
+
+        picker.disabledDates = function (dates) {
+            ///<signature helpKeyword="$.fn.datetimepicker.disabledDates">
+            ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
+            ///<returns type="array">options.disabledDates</returns>
+            ///</signature>
+            ///<signature>
+            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
+            ///options.enabledDates if such exist.</summary>
+            ///<param name="dates" locid="$.fn.datetimepicker.disabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
+            ///</signature>
+            if (arguments.length === 0) {
+                return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates);
+            }
+
+            if (!dates) {
+                options.disabledDates = false;
+                update();
+                return picker;
+            }
+            if (!(dates instanceof Array)) {
+                throw new TypeError('disabledDates() expects an array parameter');
+            }
+            options.disabledDates = indexGivenDates(dates);
+            options.enabledDates = false;
+            update();
+            return picker;
+        };
+
+        picker.enabledDates = function (dates) {
+            ///<signature helpKeyword="$.fn.datetimepicker.enabledDates">
+            ///<summary>Returns an array with the currently set enabled dates on the component.</summary>
+            ///<returns type="array">options.enabledDates</returns>
+            ///</signature>
+            ///<signature>
+            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist.</summary>
+            ///<param name="dates" locid="$.fn.datetimepicker.enabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
+            ///</signature>
+            if (arguments.length === 0) {
+                return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates);
+            }
+
+            if (!dates) {
+                options.enabledDates = false;
+                update();
+                return picker;
+            }
+            if (!(dates instanceof Array)) {
+                throw new TypeError('enabledDates() expects an array parameter');
+            }
+            options.enabledDates = indexGivenDates(dates);
+            options.disabledDates = false;
+            update();
+            return picker;
+        };
+
+        picker.daysOfWeekDisabled = function (daysOfWeekDisabled) {
+            if (arguments.length === 0) {
+                return options.daysOfWeekDisabled.splice(0);
+            }
+
+            if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) {
+                options.daysOfWeekDisabled = false;
+                update();
+                return picker;
+            }
+
+            if (!(daysOfWeekDisabled instanceof Array)) {
+                throw new TypeError('daysOfWeekDisabled() expects an array parameter');
+            }
+            options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) {
+                currentValue = parseInt(currentValue, 10);
+                if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {
+                    return previousValue;
+                }
+                if (previousValue.indexOf(currentValue) === -1) {
+                    previousValue.push(currentValue);
+                }
+                return previousValue;
+            }, []).sort();
+            if (options.useCurrent && !options.keepInvalid) {
+                var tries = 0;
+                while (!isValid(date, 'd')) {
+                    date.add(1, 'd');
+                    if (tries === 7) {
+                        throw 'Tried 7 times to find a valid date';
+                    }
+                    tries++;
+                }
+                setValue(date);
+            }
+            update();
+            return picker;
+        };
+
+        picker.maxDate = function (maxDate) {
+            if (arguments.length === 0) {
+                return options.maxDate ? options.maxDate.clone() : options.maxDate;
+            }
+
+            if ((typeof maxDate === 'boolean') && maxDate === false) {
+                options.maxDate = false;
+                update();
+                return picker;
+            }
+
+            if (typeof maxDate === 'string') {
+                if (maxDate === 'now' || maxDate === 'moment') {
+                    maxDate = moment();
+                }
+            }
+
+            var parsedDate = parseInputDate(maxDate);
+
+            if (!parsedDate.isValid()) {
+                throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate);
+            }
+            if (options.minDate && parsedDate.isBefore(options.minDate)) {
+                throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat));
+            }
+            options.maxDate = parsedDate;
+            if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) {
+                setValue(options.maxDate);
+            }
+            if (viewDate.isAfter(parsedDate)) {
+                viewDate = parsedDate.clone();
+            }
+            update();
+            return picker;
+        };
+
+        picker.minDate = function (minDate) {
+            if (arguments.length === 0) {
+                return options.minDate ? options.minDate.clone() : options.minDate;
+            }
+
+            if ((typeof minDate === 'boolean') && minDate === false) {
+                options.minDate = false;
+                update();
+                return picker;
+            }
+
+            if (typeof minDate === 'string') {
+                if (minDate === 'now' || minDate === 'moment') {
+                    minDate = moment();
+                }
+            }
+
+            var parsedDate = parseInputDate(minDate);
+
+            if (!parsedDate.isValid()) {
+                throw new TypeError('minDate() Could not parse date parameter: ' + minDate);
+            }
+            if (options.maxDate && parsedDate.isAfter(options.maxDate)) {
+                throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat));
+            }
+            options.minDate = parsedDate;
+            if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) {
+                setValue(options.minDate);
+            }
+            if (viewDate.isBefore(parsedDate)) {
+                viewDate = parsedDate.clone();
+            }
+            update();
+            return picker;
+        };
+
+        picker.defaultDate = function (defaultDate) {
+            ///<signature helpKeyword="$.fn.datetimepicker.defaultDate">
+            ///<summary>Returns a moment with the options.defaultDate option configuration or false if not set</summary>
+            ///<returns type="Moment">date.clone()</returns>
+            ///</signature>
+            ///<signature>
+            ///<summary>Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared.</summary>
+            ///<param name="defaultDate" locid="$.fn.datetimepicker.defaultDate_p:defaultDate">Takes a string, Date, moment, boolean:false</param>
+            ///</signature>
+            if (arguments.length === 0) {
+                return options.defaultDate ? options.defaultDate.clone() : options.defaultDate;
+            }
+            if (!defaultDate) {
+                options.defaultDate = false;
+                return picker;
+            }
+
+            if (typeof defaultDate === 'string') {
+                if (defaultDate === 'now' || defaultDate === 'moment') {
+                    defaultDate = moment();
+                }
+            }
+
+            var parsedDate = parseInputDate(defaultDate);
+            if (!parsedDate.isValid()) {
+                throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate);
+            }
+            if (!isValid(parsedDate)) {
+                throw new TypeError('defaultDate() date passed is invalid according to component setup validations');
+            }
+
+            options.defaultDate = parsedDate;
+
+            if (options.defaultDate && options.inline || (input.val().trim() === '' && input.attr('placeholder') === undefined)) {
+                setValue(options.defaultDate);
+            }
+            return picker;
+        };
+
+        picker.locale = function (locale) {
+            if (arguments.length === 0) {
+                return options.locale;
+            }
+
+            if (!moment.localeData(locale)) {
+                throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!');
+            }
+
+            options.locale = locale;
+            date.locale(options.locale);
+            viewDate.locale(options.locale);
+
+            if (actualFormat) {
+                initFormatting(); // reinit formatting
+            }
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.stepping = function (stepping) {
+            if (arguments.length === 0) {
+                return options.stepping;
+            }
+
+            stepping = parseInt(stepping, 10);
+            if (isNaN(stepping) || stepping < 1) {
+                stepping = 1;
+            }
+            options.stepping = stepping;
+            return picker;
+        };
+
+        picker.useCurrent = function (useCurrent) {
+            var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];
+            if (arguments.length === 0) {
+                return options.useCurrent;
+            }
+
+            if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) {
+                throw new TypeError('useCurrent() expects a boolean or string parameter');
+            }
+            if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) {
+                throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));
+            }
+            options.useCurrent = useCurrent;
+            return picker;
+        };
+
+        picker.collapse = function (collapse) {
+            if (arguments.length === 0) {
+                return options.collapse;
+            }
+
+            if (typeof collapse !== 'boolean') {
+                throw new TypeError('collapse() expects a boolean parameter');
+            }
+            if (options.collapse === collapse) {
+                return picker;
+            }
+            options.collapse = collapse;
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.icons = function (icons) {
+            if (arguments.length === 0) {
+                return $.extend({}, options.icons);
+            }
+
+            if (!(icons instanceof Object)) {
+                throw new TypeError('icons() expects parameter to be an Object');
+            }
+            $.extend(options.icons, icons);
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.useStrict = function (useStrict) {
+            if (arguments.length === 0) {
+                return options.useStrict;
+            }
+
+            if (typeof useStrict !== 'boolean') {
+                throw new TypeError('useStrict() expects a boolean parameter');
+            }
+            options.useStrict = useStrict;
+            return picker;
+        };
+
+        picker.sideBySide = function (sideBySide) {
+            if (arguments.length === 0) {
+                return options.sideBySide;
+            }
+
+            if (typeof sideBySide !== 'boolean') {
+                throw new TypeError('sideBySide() expects a boolean parameter');
+            }
+            options.sideBySide = sideBySide;
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.viewMode = function (viewMode) {
+            if (arguments.length === 0) {
+                return options.viewMode;
+            }
+
+            if (typeof viewMode !== 'string') {
+                throw new TypeError('viewMode() expects a string parameter');
+            }
+
+            if (viewModes.indexOf(viewMode) === -1) {
+                throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value');
+            }
+
+            options.viewMode = viewMode;
+            currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber);
+
+            showMode();
+            return picker;
+        };
+
+        picker.toolbarPlacement = function (toolbarPlacement) {
+            if (arguments.length === 0) {
+                return options.toolbarPlacement;
+            }
+
+            if (typeof toolbarPlacement !== 'string') {
+                throw new TypeError('toolbarPlacement() expects a string parameter');
+            }
+            if (toolbarPlacements.indexOf(toolbarPlacement) === -1) {
+                throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');
+            }
+            options.toolbarPlacement = toolbarPlacement;
+
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.widgetPositioning = function (widgetPositioning) {
+            if (arguments.length === 0) {
+                return $.extend({}, options.widgetPositioning);
+            }
+
+            if (({}).toString.call(widgetPositioning) !== '[object Object]') {
+                throw new TypeError('widgetPositioning() expects an object variable');
+            }
+            if (widgetPositioning.horizontal) {
+                if (typeof widgetPositioning.horizontal !== 'string') {
+                    throw new TypeError('widgetPositioning() horizontal variable must be a string');
+                }
+                widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase();
+                if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) {
+                    throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');
+                }
+                options.widgetPositioning.horizontal = widgetPositioning.horizontal;
+            }
+            if (widgetPositioning.vertical) {
+                if (typeof widgetPositioning.vertical !== 'string') {
+                    throw new TypeError('widgetPositioning() vertical variable must be a string');
+                }
+                widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase();
+                if (verticalModes.indexOf(widgetPositioning.vertical) === -1) {
+                    throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');
+                }
+                options.widgetPositioning.vertical = widgetPositioning.vertical;
+            }
+            update();
+            return picker;
+        };
+
+        picker.calendarWeeks = function (calendarWeeks) {
+            if (arguments.length === 0) {
+                return options.calendarWeeks;
+            }
+
+            if (typeof calendarWeeks !== 'boolean') {
+                throw new TypeError('calendarWeeks() expects parameter to be a boolean value');
+            }
+
+            options.calendarWeeks = calendarWeeks;
+            update();
+            return picker;
+        };
+
+        picker.showTodayButton = function (showTodayButton) {
+            if (arguments.length === 0) {
+                return options.showTodayButton;
+            }
+
+            if (typeof showTodayButton !== 'boolean') {
+                throw new TypeError('showTodayButton() expects a boolean parameter');
+            }
+
+            options.showTodayButton = showTodayButton;
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.showClear = function (showClear) {
+            if (arguments.length === 0) {
+                return options.showClear;
+            }
+
+            if (typeof showClear !== 'boolean') {
+                throw new TypeError('showClear() expects a boolean parameter');
+            }
+
+            options.showClear = showClear;
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.widgetParent = function (widgetParent) {
+            if (arguments.length === 0) {
+                return options.widgetParent;
+            }
+
+            if (typeof widgetParent === 'string') {
+                widgetParent = $(widgetParent);
+            }
+
+            if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) {
+                throw new TypeError('widgetParent() expects a string or a jQuery object parameter');
+            }
+
+            options.widgetParent = widgetParent;
+            if (widget) {
+                hide();
+                show();
+            }
+            return picker;
+        };
+
+        picker.keepOpen = function (keepOpen) {
+            if (arguments.length === 0) {
+                return options.keepOpen;
+            }
+
+            if (typeof keepOpen !== 'boolean') {
+                throw new TypeError('keepOpen() expects a boolean parameter');
+            }
+
+            options.keepOpen = keepOpen;
+            return picker;
+        };
+
+        picker.focusOnShow = function (focusOnShow) {
+            if (arguments.length === 0) {
+                return options.focusOnShow;
+            }
+
+            if (typeof focusOnShow !== 'boolean') {
+                throw new TypeError('focusOnShow() expects a boolean parameter');
+            }
+
+            options.focusOnShow = focusOnShow;
+            return picker;
+        };
+
+        picker.inline = function (inline) {
+            if (arguments.length === 0) {
+                return options.inline;
+            }
+
+            if (typeof inline !== 'boolean') {
+                throw new TypeError('inline() expects a boolean parameter');
+            }
+
+            options.inline = inline;
+            return picker;
+        };
+
+        picker.clear = function () {
+            clear();
+            return picker;
+        };
+
+        picker.keyBinds = function (keyBinds) {
+            options.keyBinds = keyBinds;
+            return picker;
+        };
+
+        picker.debug = function (debug) {
+            if (typeof debug !== 'boolean') {
+                throw new TypeError('debug() expects a boolean parameter');
+            }
+
+            options.debug = debug;
+            return picker;
+        };
+
+        picker.allowInputToggle = function (allowInputToggle) {
+            if (arguments.length === 0) {
+                return options.allowInputToggle;
+            }
+
+            if (typeof allowInputToggle !== 'boolean') {
+                throw new TypeError('allowInputToggle() expects a boolean parameter');
+            }
+
+            options.allowInputToggle = allowInputToggle;
+            return picker;
+        };
+
+        picker.showClose = function (showClose) {
+            if (arguments.length === 0) {
+                return options.showClose;
+            }
+
+            if (typeof showClose !== 'boolean') {
+                throw new TypeError('showClose() expects a boolean parameter');
+            }
+
+            options.showClose = showClose;
+            return picker;
+        };
+
+        picker.keepInvalid = function (keepInvalid) {
+            if (arguments.length === 0) {
+                return options.keepInvalid;
+            }
+
+            if (typeof keepInvalid !== 'boolean') {
+                throw new TypeError('keepInvalid() expects a boolean parameter');
+            }
+            options.keepInvalid = keepInvalid;
+            return picker;
+        };
+
+        picker.datepickerInput = function (datepickerInput) {
+            if (arguments.length === 0) {
+                return options.datepickerInput;
+            }
+
+            if (typeof datepickerInput !== 'string') {
+                throw new TypeError('datepickerInput() expects a string parameter');
+            }
+
+            options.datepickerInput = datepickerInput;
+            return picker;
+        };
+
+        picker.parseInputDate = function (parseInputDate) {
+            if (arguments.length === 0) {
+                return options.parseInputDate;
+            }
+
+            if (typeof parseInputDate !== 'function') {
+                throw new TypeError('parseInputDate() sholud be as function');
+            }
+
+            options.parseInputDate = parseInputDate;
+
+            return picker;
+        };
+
+        picker.disabledTimeIntervals = function (disabledTimeIntervals) {
+            ///<signature helpKeyword="$.fn.datetimepicker.disabledTimeIntervals">
+            ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
+            ///<returns type="array">options.disabledTimeIntervals</returns>
+            ///</signature>
+            ///<signature>
+            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
+            ///options.enabledDates if such exist.</summary>
+            ///<param name="dates" locid="$.fn.datetimepicker.disabledTimeIntervals_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
+            ///</signature>
+            if (arguments.length === 0) {
+                return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals);
+            }
+
+            if (!disabledTimeIntervals) {
+                options.disabledTimeIntervals = false;
+                update();
+                return picker;
+            }
+            if (!(disabledTimeIntervals instanceof Array)) {
+                throw new TypeError('disabledTimeIntervals() expects an array parameter');
+            }
+            options.disabledTimeIntervals = disabledTimeIntervals;
+            update();
+            return picker;
+        };
+
+        picker.disabledHours = function (hours) {
+            //

<TRUNCATED>


[4/4] incubator-atlas git commit: ATLAS-1395: Lineage improvement for tooltip

Posted by ma...@apache.org.
ATLAS-1395: Lineage improvement for tooltip

Signed-off-by: Madhan Neethiraj <ma...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/incubator-atlas/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-atlas/commit/b305ba50
Tree: http://git-wip-us.apache.org/repos/asf/incubator-atlas/tree/b305ba50
Diff: http://git-wip-us.apache.org/repos/asf/incubator-atlas/diff/b305ba50

Branch: refs/heads/master
Commit: b305ba5059997f043a47a7a8e802e6355bb5e31f
Parents: edc4786
Author: kevalbhatt <kb...@apache.org>
Authored: Fri Dec 16 11:35:22 2016 +0530
Committer: Madhan Neethiraj <ma...@apache.org>
Committed: Fri Dec 16 06:50:25 2016 -0800

----------------------------------------------------------------------
 .../public/js/views/graph/LineageLayoutView.js  | 24 ++++++++------------
 release-log.txt                                 |  2 ++
 2 files changed, 12 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/b305ba50/dashboardv2/public/js/views/graph/LineageLayoutView.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/views/graph/LineageLayoutView.js b/dashboardv2/public/js/views/graph/LineageLayoutView.js
index d16674e..c1d860a 100644
--- a/dashboardv2/public/js/views/graph/LineageLayoutView.js
+++ b/dashboardv2/public/js/views/graph/LineageLayoutView.js
@@ -141,20 +141,16 @@ define(['require',
                     obj['shape'] = "img";
                     obj['typeName'] = relationObj.typeName
                     obj['label'] = relationObj.displayText.trunc(18);
-                    obj['toolTiplabel'] = relationObj.displayText;
-                    if (obj.typeName) {
-                        var temp = obj['label'] + ' (' + relationObj.typeName + ')';
-                        obj['toolTiplabel'] = temp
-                        obj['label'] = temp.trunc(18);
-                    }
+                    obj['toolTipLabel'] = relationObj.displayText;
                     obj['id'] = relationObj.guid;
+                    obj['queryText'] = relationObj.queryText;
                     if (relationObj.status) {
                         obj['status'] = relationObj.status;
                     }
                     if (that.typeMap && that.typeMap[relationObj.typeName]) {
                         obj['isProcess'] = _.contains(that.typeMap[relationObj.typeName], "Process") ? true : false;
                     } else {
-                        that.typeMap[relationObj.typeName] = { fetch: true }
+                        that.typeMap[relationObj.typeName] = { fetch: true };
                         fetchEntity(relationObj.typeName);
                     }
                     return obj;
@@ -303,7 +299,10 @@ define(['require',
                     .attr('class', 'd3-tip')
                     .html(function(d) {
                         var value = that.g.node(d);
-                        var htmlStr = "<h5>Name: <span style='color:#359f89'>" + value.toolTiplabel + "</span></h5> ";
+                        var htmlStr = "<h5 class='text-center'><span style='color:#359f89'>" + value.toolTipLabel + "</span></h5> ";
+                        if (value.typeName) {
+                            htmlStr += "<h5 class='text-center'><span>(" + value.typeName + ")</span></h5> ";
+                        }
                         if (value.queryText) {
                             htmlStr += "<h5>Query: <span style='color:#359f89'>" + value.queryText + "</span></h5> ";
                         }
@@ -318,22 +317,19 @@ define(['require',
                 //change text postion 
                 svgGroup.selectAll("g.nodes g.label")
                     .attr("transform", "translate(2,-30)");
-
-                svgGroup.selectAll("g.nodes image")
-                    .on('mouseover', function(d) {
+                svgGroup.selectAll("g.nodes g.node")
+                    .on('mouseenter', function(d) {
                         tooltip.show(d);
                     })
                     .on('dblclick', function(d) {
                         tooltip.hide(d);
-                        //var urlForTab = window.location.hash.split('/')[1];
-
                         Utils.setUrl({
                             url: '#!/detailPage/' + d,
                             mergeBrowserUrl: false,
                             trigger: true
                         });
                     })
-                    .on('mouseout', function(d) {
+                    .on('mouseleave', function(d) {
                         tooltip.hide(d);
                     });
                 // Center the graph

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/b305ba50/release-log.txt
----------------------------------------------------------------------
diff --git a/release-log.txt b/release-log.txt
index 792b333..f63842f 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -9,6 +9,8 @@ ATLAS-1060 Add composite indexes for exact match performance improvements for al
 ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai)
 
 ALL CHANGES:
+ATLAS-1395 Lineage improvement for tooltip (kevalbhatt via mneethiraj)
+ATLAS-1193 UI to create/update entities (Kalyanikashikar via mneethiraj)
 ATLAS-1304 Redundant code removal and code simplification (apoorvnaik via mneethiraj)
 ATLAS-1345 Enhance search APIs to resolve hierarchical references (apoorvnaik via sumasai)
 ATLAS-1287 Subtasks: ATLAS-1288/ATLAS-1289 Integrated V2 API for Lineage,Entity Details,Tag assign to entity,Tags listing,tag create (kevalbhatt)


[3/4] incubator-atlas git commit: ATLAS-1193: UI to create/update entities

Posted by ma...@apache.org.
ATLAS-1193: UI to create/update entities

Signed-off-by: Madhan Neethiraj <ma...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/incubator-atlas/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-atlas/commit/edc4786b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-atlas/tree/edc4786b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-atlas/diff/edc4786b

Branch: refs/heads/master
Commit: edc4786b957988f8029a652ce7cf1c97d0e40711
Parents: 1620284
Author: kalyanikk <ka...@freestoneinfotech.com>
Authored: Tue Dec 13 16:59:56 2016 +0530
Committer: Madhan Neethiraj <ma...@apache.org>
Committed: Fri Dec 16 06:32:45 2016 -0800

----------------------------------------------------------------------
 dashboardv2/gruntfile.js                        |    2 +-
 dashboardv2/public/css/scss/form.scss           |   20 +
 dashboardv2/public/css/scss/override.scss       |    9 +
 dashboardv2/public/css/scss/search.scss         |    1 -
 dashboardv2/public/css/scss/tag.scss            |   73 +-
 dashboardv2/public/index.html                   |    1 +
 dashboardv2/public/js/collection/VEntityList.js |   15 +-
 dashboardv2/public/js/collection/VSearchList.js |   10 +-
 .../datetimepicker/bootstrap-datetimepicker.js  | 2444 ++++++++++++++++++
 .../bootstrap-datetimepicker.min.css            |    5 +
 dashboardv2/public/js/main.js                   |    9 +-
 dashboardv2/public/js/models/VEntity.js         |   14 +-
 .../detail_page/DetailPageLayoutView_tmpl.html  |    1 +
 .../entity/CreateEntityLayoutView_tmpl.html     |   46 +
 .../templates/search/SearchLayoutView_tmpl.html |    3 +
 dashboardv2/public/js/utils/UrlLinks.js         |    6 +-
 .../views/detail_page/DetailPageLayoutView.js   |   16 +
 .../js/views/entity/CreateEntityLayoutView.js   |  613 +++++
 .../public/js/views/search/SearchLayoutView.js  |   19 +-
 .../js/views/search/SearchResultLayoutView.js   |   22 +-
 20 files changed, 3303 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/gruntfile.js
----------------------------------------------------------------------
diff --git a/dashboardv2/gruntfile.js b/dashboardv2/gruntfile.js
index a252f1d..c8da73b 100644
--- a/dashboardv2/gruntfile.js
+++ b/dashboardv2/gruntfile.js
@@ -126,7 +126,7 @@ module.exports = function(grunt) {
                     'd3': 'd3/d3.min.js',
                     'd3/': 'd3-tip/index.js',
                     'dagre-d3': 'dagre-d3/dist/dagre-d3.min.js',
-                    'select2': 'select2/dist/js/select2.min.js',
+                    'select2': 'select2/dist/js/select2.full.min.js',
                     'backgrid-select-all': 'backgrid-select-all/backgrid-select-all.min.js',
                     'moment/js': 'moment/min/moment.min.js',
                     'jquery-placeholder/js': 'jquery-placeholder/jquery.placeholder.js',

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/css/scss/form.scss
----------------------------------------------------------------------
diff --git a/dashboardv2/public/css/scss/form.scss b/dashboardv2/public/css/scss/form.scss
index e17b7bb..3ee94f5 100644
--- a/dashboardv2/public/css/scss/form.scss
+++ b/dashboardv2/public/css/scss/form.scss
@@ -227,3 +227,23 @@ button:focus {
 .block {
     display: block !important;
 }
+
+.editBtn {
+    border-color: $color_mountain_mist_approx;
+    color: $color_mountain_mist_approx;
+    cursor: default;
+    margin: 5px 10px;
+    font-size: 14px;
+    cursor: pointer;
+    padding: 0px 5px;
+    &:hover {
+        border-color: $color_mountain_mist_approx;
+        color: $color_mountain_mist_approx;
+        background-color: $transparent;
+    }
+    &:focus {
+        border-color: $color_mountain_mist_approx;
+        color: $color_mountain_mist_approx;
+        background-color: $transparent;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/css/scss/override.scss
----------------------------------------------------------------------
diff --git a/dashboardv2/public/css/scss/override.scss b/dashboardv2/public/css/scss/override.scss
index 2db5568..fab8c25 100644
--- a/dashboardv2/public/css/scss/override.scss
+++ b/dashboardv2/public/css/scss/override.scss
@@ -152,6 +152,11 @@
 
 .select2-container--default .select2-search--inline .select2-search__field {
     color: #555;
+    width: 300px !important;
+}
+
+.select2-container--default .select2-selection--single .select2-selection__rendered {
+    width: 327px !important;
 }
 
 .typeLOV {
@@ -240,3 +245,7 @@
         }
     }
 }
+
+.tab-content > .tab-pane.active {
+    overflow-x: hidden;
+}

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/css/scss/search.scss
----------------------------------------------------------------------
diff --git a/dashboardv2/public/css/scss/search.scss b/dashboardv2/public/css/scss/search.scss
index 24b2212..a754924 100644
--- a/dashboardv2/public/css/scss/search.scss
+++ b/dashboardv2/public/css/scss/search.scss
@@ -36,7 +36,6 @@ $switchTransition: .4s ease-out;
 
 .switch {
   position: relative;
-  display: inline-block;
   width: 50px;
   height: 22px;
   margin-left: 8px;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/css/scss/tag.scss
----------------------------------------------------------------------
diff --git a/dashboardv2/public/css/scss/tag.scss b/dashboardv2/public/css/scss/tag.scss
index fa194fa..0b931d0 100644
--- a/dashboardv2/public/css/scss/tag.scss
+++ b/dashboardv2/public/css/scss/tag.scss
@@ -284,7 +284,8 @@ form-control .tagInpput {
     float: left;
     padding: 2px 10px 2px 0px;
     font-weight: 600;
-    width: 70px
+    width: 70px;
+    margin-right: 5px;
 }
 
 .inputAssignTag {
@@ -300,11 +301,6 @@ form-control .tagInpput {
     cursor: pointer;
     background-color: $white;
     white-space: nowrap;
-    // i.fa {
-    //     position: relative;
-    //     right: -5px;
-    //     cursor: pointer;
-    // }
     &:hover {
         color: $color_dark_grey_approx;
         background-color: $color_mercury_approx;
@@ -346,3 +342,68 @@ form-control .tagInpput {
 .tagAttributeLabel {
     color: $color_star_dust_approx;
 }
+
+fieldset.scheduler-border {
+    border-top: 1px solid #999 !important;
+    padding: 0 1em 0em 1em !important;
+    margin: 0 0 .5em 0 !important;
+}
+
+legend.scheduler-border {
+    width: auto;
+    padding: 0 5px;
+    border-bottom: none;
+    font-size: 16px;
+    margin-left: 50%;
+    margin-bottom: 10px;
+    color: #555;
+}
+
+.requiredInput {
+    color: #d20606;
+}
+
+.spanEntityType {
+    position: absolute;
+    right: 38px;
+    top: 10px;
+    cursor: help;
+    width: 45px;
+    overflow: hidden;
+    z-index: 9;
+    text-overflow: ellipsis;
+    font-size: 14px;
+    color: #a7a19f;
+}
+
+.enitityInputBox {
+    padding-right: 69px;
+}
+
+.entityLoader {
+    margin-left: 286px;
+}
+
+.errorClass {
+    border: 1px solid red;
+}
+
+.entityInputBox {
+    padding-right: 70px;
+}
+
+.attributeTag {
+    padding: 5px 8px;
+    border: 1px solid #999999;
+    color: #999999;
+    border-radius: 1px;
+    cursor: pointer
+}
+
+.attrTopMargin {
+    margin-top: 15px;
+}
+
+.topMargin {
+    margin-top: 13px;
+}

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/index.html
----------------------------------------------------------------------
diff --git a/dashboardv2/public/index.html b/dashboardv2/public/index.html
index 04edcee..36ddef5 100644
--- a/dashboardv2/public/index.html
+++ b/dashboardv2/public/index.html
@@ -45,6 +45,7 @@
     <link rel="stylesheet" href="js/libs/select2/css/select2.min.css">
     <link rel="stylesheet" href="js/libs/bootstrap/css/bootstrap.min.css">
     <link rel="stylesheet" href="js/libs/jquery-asBreadcrumbs/css/asBreadcrumbs.min.css">
+    <link rel="stylesheet" href="js/external_lib/datetimepicker/bootstrap-datetimepicker.min.css">
     <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,600,600italic,700,700italic' rel='stylesheet' type='text/css'>
     <link rel="stylesheet" type="text/css" href="js/external_lib/jquery-ui/jquery-ui.min.css">
     <link href="css/bootstrap-sidebar.css" rel="stylesheet">

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/collection/VEntityList.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/collection/VEntityList.js b/dashboardv2/public/js/collection/VEntityList.js
index bfcbe99..99b0f3b 100644
--- a/dashboardv2/public/js/collection/VEntityList.js
+++ b/dashboardv2/public/js/collection/VEntityList.js
@@ -40,14 +40,7 @@ define(['require',
                     // if (!this.modelAttrName) {
                     //     throw new Error("this.modelAttrName not defined for " + this);
                     // }
-                    if (this.modelAttrName && this.modelAttrName.length) {
-                        if (resp[this.modelAttrName]) {
-                            return resp[this.modelAttrName];
-                        } else {
-                            return resp
-                        }
-
-                    } else {
+                    if (this.modelAttrName && this.modelAttrName === "createEntity") {
                         var arr = [];
                         arr.push({
                             attributes: resp.attributes,
@@ -56,6 +49,12 @@ define(['require',
                             typeName: resp.typeName
                         });
                         return arr;
+                    } else {
+                        if (resp[this.modelAttrName]) {
+                            return resp[this.modelAttrName];
+                        } else {
+                            return resp
+                        }
                     }
 
                 } catch (e) {

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/edc4786b/dashboardv2/public/js/collection/VSearchList.js
----------------------------------------------------------------------
diff --git a/dashboardv2/public/js/collection/VSearchList.js b/dashboardv2/public/js/collection/VSearchList.js
index 640be40..3b02f05 100644
--- a/dashboardv2/public/js/collection/VSearchList.js
+++ b/dashboardv2/public/js/collection/VSearchList.js
@@ -46,7 +46,15 @@ define(['require',
                     if (!this.modelAttrName) {
                         throw new Error("this.modelAttrName not defined for " + this);
                     }
-                    return _.reject(resp[this.modelAttrName], _.isNull);
+                    var list = _.reject(resp[this.modelAttrName], _.isNull);
+                    _.each(list, function(obj) {
+                        if (!obj.id) {
+                            if (obj['$id$'] && obj['$id$'].id) {
+                                obj.id = obj['$id$'].id
+                            }
+                        }
+                    })
+                    return list;
                 } catch (e) {
                     console.log(e);
                 }