From 8fdbb909b47ee0f5043e9a5e4549fc2a31875b29 Mon Sep 17 00:00:00 2001 From: "chris.watts90@outlook.com" Date: Sun, 9 Apr 2017 22:02:08 +0100 Subject: [PATCH 01/23] add knockout contextmenu plugin code to support data bound context menus #29 --- .../www/css/knockout.contextmenu.css | 59 +++ .../www/js/knockout.contextmenu.js | 364 ++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/css/knockout.contextmenu.css create mode 100644 DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/js/knockout.contextmenu.js diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/css/knockout.contextmenu.css b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/css/knockout.contextmenu.css new file mode 100644 index 0000000..bedb129 --- /dev/null +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/css/knockout.contextmenu.css @@ -0,0 +1,59 @@ +/* knockout.contextmenu v1.0.0 + Nicolás Escalante - nlante@gmail.com + Issues: https://github.com/nescalante/knockout.contextmenu/issues + License: MIT */ + +.context-menu { + position: absolute; + padding: 0; + margin: 0; + z-index: 1030; + background-color: #ffffff; +} +.context-menu ul { + line-height: 1.6; + padding: 0; + margin: 0; + border: 1px solid #dddddd; + box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); +} +.context-menu ul > li { + padding: 4px 20px; + margin: 0; + z-index: 1031; + list-style-type: none; + cursor: pointer; + white-space: nowrap; + color: #333333; +} +.context-menu ul > li:hover { + background-color: #eeeeee; +} +.context-menu ul > li.disabled, +.context-menu ul > li.disabled a { + color: #666666; + cursor: default; +} +.context-menu ul > li.checked:before { + position: absolute; + content: "\2713"; + left: 7px; +} +.context-menu ul > li.with-url { + padding: 0; +} +.context-menu ul > li.with-url a { + display: block; + padding: 4px 20px; + text-decoration: none; + color: #333333; +} +.context-menu ul > li.separator { + margin: 4px 0; + padding: 0; + border-bottom: 1px solid #cccccc; + cursor: default; +} +.context-menu ul > li.separator:hover { + background-color: #ffffff; +} \ No newline at end of file diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/js/knockout.contextmenu.js b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/js/knockout.contextmenu.js new file mode 100644 index 0000000..ca91eff --- /dev/null +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/js/knockout.contextmenu.js @@ -0,0 +1,364 @@ +/* knockout.contextmenu v1.0.0 + Nicolás Escalante - nlante@gmail.com + Issues: https://github.com/nescalante/knockout.contextmenu/issues + License: MIT */ + +(function (undefined) { + 'use strict'; + + // client + if (typeof ko !== undefined + '') { + bindContextMenu(ko); + } + + // node + if (typeof module !== undefined + '' && module.exports && typeof require !== undefined + '') { + bindContextMenu(require('knockout')); + } + + function bindContextMenu(ko) { + var currentMenu; + var elementMapping = []; + var utils = ko.utils; + var registerEvent = utils.registerEventHandler; + var isObservable = ko.isObservable; + + registerEvent(document, 'click', function (event) { + var button = event.which || event.button; + if (!event.defaultPrevented && button < 2) { + hideCurrentMenu(); + } + }); + + utils.contextMenu = { + getMenuFor: function (element, event) { + var result = getMapping(element); + + if (result) { + return result.get(event); + } + }, + + openMenuFor: function (element, event) { + var result = getMapping(element); + + if (result) { + return result.open(event); + } + }, + + }; + + ko.bindingHandlers.contextMenu = { + init: function (element, valueAccessor, allBindingsAccessor, viewModel) { + var eventsToHandle = valueAccessor() || {}; + var allBindings = allBindingsAccessor(); + var defaultClass = allBindings.contextMenuClass || 'context-menu'; + var activeElement; + + // bind on click? bind on context click? + if (allBindings.bindMenuOnClick) { + registerEvent(element, 'click', openMenu); + } + + if (allBindings.bindMenuOnContextMenu === undefined || allBindings.bindMenuOnContextMenu) { + registerEvent(element, 'contextmenu', openMenu); + } + + elementMapping.push({ + element: element, + get: function () { + return activeElement; + }, + + open: openMenu, + hide: function () { + if (activeElement) { + activeElement.hide(); + } + }, + }); + + function mouseX(evt) { + if (evt.pageX) { + return evt.pageX; + } else if (evt.clientX) { + return evt.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft); + } else { + return null; + } + } + + function mouseY(evt) { + if (evt.pageY) { + return evt.pageY; + } else if (evt.clientY) { + return evt.clientY + (document.documentElement.scrollTop || document.body.scrollTop); + } else { + return null; + } + } + + function openMenu(event) { + var menuElement; + + activeElement = getMenu(event); + menuElement = activeElement.element; + + hideCurrentMenu(); + + if (menuElement) { + // make visibility hidden, then add to DOM so that we can get the height/width of the menu + menuElement.style.visibility = 'hidden'; + (document.body || document).appendChild(menuElement); + + // set location + if (event) { + var bottomOfViewport = window.innerHeight + window.pageYOffset; + var rightOfViewport = window.innerWidth + window.pageXOffset; + + if (mouseY(event) + menuElement.offsetHeight > bottomOfViewport) { + menuElement.style.top = 1 * (bottomOfViewport - menuElement.offsetHeight - 10) + 'px'; + } else { + menuElement.style.top = mouseY(event) + 'px'; + } + + if (mouseX(event) + menuElement.offsetWidth > rightOfViewport) { + menuElement.style.left = 1 * (rightOfViewport - menuElement.offsetWidth - 10) + 'px'; + } else { + menuElement.style.left = mouseX(event) + 'px'; + } + + event.preventDefault(); + event.stopPropagation(); + } else { + menuElement.style.top = (element.offsetTop + element.offsetHeight) + 'px'; + menuElement.style.left = (element.offsetLeft + element.offsetWidth) + 'px'; + } + + // now set to visible + menuElement.style.visibility = ''; + } + + // replace current menu with the recently created + currentMenu = menuElement; + + return activeElement; + } + + function getMenu(event) { + var menu; + var hasChecks = false; + var elements = []; + var actions = []; + var items = []; + var props = Object.keys( + ko.isObservable(eventsToHandle) ? + eventsToHandle() : + eventsToHandle + ); + + props.forEach(function (eventNameOutsideClosure) { + pushItem(eventNameOutsideClosure); + }); + + if (elements.length) { + menu = document.createElement('div'); + menu.className = defaultClass; + + // you may need padding to menus that has checks + menu.innerHTML = ''; + + // map items to actions + elements.forEach(function (item, index) { + registerEvent(menu.children[0].children[index], 'click', function (event) { + var result = actions[index](viewModel, event); + + if (!result && event) { + event.preventDefault(); + } + }); + }); + } + + return { + element: menu, + items: items, + open: openMenu, + hide: function () { + if (menu && menu.parentNode) { + menu.parentNode.removeChild(menu); + } + + currentMenu = null; + }, + }; + + function pushItem(eventName) { + var item = getMenuProperties(eventName); + var classes = []; + var id = ''; + var liHtml; + + if (item.isVisible) { + hasChecks = hasChecks || item.isBoolean; + + if (item.id) { + id = item.id; + } + + // set css classes + if (item.isChecked) { + classes.push('checked'); + } + + if (item.isDisabled) { + classes.push('disabled'); + } + + if (item.isSeparator) { + classes.push('separator'); + } + + if (item.url) { + classes.push('with-url'); + } + + liHtml = '
  • ' + + item.html + + '
  • '; + + elements.push(liHtml); + actions.push(item.action); + } + + items.push(item); + } + } + + function getMenuProperties(eventName) { + var text = ''; + var html = ''; + var currentEvent = ko.isObservable(eventsToHandle) ? + eventsToHandle()[eventName] : + eventsToHandle[eventName]; + var item = currentEvent || {}; + var id = item.id; + var url = (isObservable(item.url) ? item.url() : item.url); + var isVisible = item.visible === undefined || item.visible === null || + (isObservable(item.visible) && item.visible()) || + (!isObservable(item.visible) && !!item.visible); + var isChecked = false; + var isEnabled = !item.disabled || + (isObservable(item.disabled) && !item.disabled()) || + (isObservable(item.enabled) && item.enabled()) || + (!isObservable(item.enabled) && !!item.enabled); + var isBoolean = false; + var isDisabled = !isEnabled; + var isSeparator = !!currentEvent.separator; + + if (!isSeparator) { + text = isObservable(item.text) ? item.text() : item.text; + + if (!text) { + text = eventName; + } + + if (url) { + html = '' + text + ''; + } else { + html = text; + } + } + + if ((isObservable(item) && typeof item() === 'boolean') || + (isObservable(item.action) && typeof item.action() === 'boolean')) { + isBoolean = true; + + if ((item.action && item.action()) || + (typeof item === 'function' && item())) { + isChecked = true; + } + } + + return { + html: html, + text: text, + url: url, + id: id, + isVisible: isVisible, + isChecked: isChecked, + isEnabled: isEnabled, + isDisabled: isDisabled, + isBoolean: isBoolean, + isSeparator: isSeparator, + action: action, + }; + + function action(viewModel, event) { + var error = eventName + ' option must have an action or an url.'; + + if (isDisabled) { + return false; + } + + // check if option is a boolean + if (isObservable(item) && typeof item() === 'boolean') { + item(!item()); + } + + // is an object? well, lets check it properties + else if (typeof item === 'object') { + // check if has an action or if its a separator + if (!item.action && !url && !isSeparator) { + throw error; + } + + // evaluate action + else if (item.action) { + if (isObservable(item.action) && typeof item.action() === 'boolean') { + item.action(!item.action()); + } else { + item.action(viewModel, event); + } + } + } + + // its not an observable, should be a function + else if (typeof item === 'function') { + item(viewModel, event); + } + + // nothing to do with this + else { + throw error; + } + + return true; + } + } + }, + }; + + function hideCurrentMenu() { + if (currentMenu && currentMenu.parentNode) { + currentMenu.parentNode.removeChild(currentMenu); + } + + currentMenu = null; + } + + function getMapping(element) { + var i = 0; + + for (; i < elementMapping.length; i++) { + if (elementMapping[i].element === element) { + return elementMapping[i]; + } + } + } + } +})(); \ No newline at end of file From 6061b790333d992f77fe57805b9acbff64c0aa64 Mon Sep 17 00:00:00 2001 From: "chris.watts90@outlook.com" Date: Sun, 9 Apr 2017 22:02:40 +0100 Subject: [PATCH 02/23] proj update for new files #29 --- .../WindowsDataCenter/WindowsDataCenter.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj index b86b216..74cf1e5 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/WindowsDataCenter.csproj @@ -199,6 +199,9 @@ PreserveNewest + + Always + Always @@ -220,6 +223,9 @@ PreserveNewest + + Always + Always From 0bbbd7625332455c8e274eee111ed37f46eab471 Mon Sep 17 00:00:00 2001 From: "chris.watts90@outlook.com" Date: Sun, 9 Apr 2017 22:04:03 +0100 Subject: [PATCH 03/23] add references to knockout.contextmenu files. add data bindings to timelog table to create the correct create+edit or create only context menus. #29 --- .../WindowsDataCenter/www/index.html | 54 ++++++++++++++----- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html index 079b15d..0e1c97a 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html @@ -4,12 +4,14 @@ Flexi Time Data Viewer - - + + + + @@ -246,9 +248,6 @@

    -
    @@ -259,7 +258,7 @@ - + @@ -271,24 +270,30 @@ - + - + - + - + - - + +
    Day Of Week In
    Weekly TotalWeekly Total
    +
    - +
    @@ -333,6 +359,6 @@
    - + \ No newline at end of file From 86e0b60386eba3f3652f706cc64112e74f1feb6d Mon Sep 17 00:00:00 2001 From: "chris.watts90@outlook.com" Date: Sun, 9 Apr 2017 22:05:18 +0100 Subject: [PATCH 04/23] added create and create+edit context menus with options. created stub methods for performing the edit/create actions. #29 --- .../WindowsDataCenter/www/spa.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/spa.js b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/spa.js index c62c7de..cdc8192 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/spa.js +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/spa.js @@ -12,6 +12,7 @@ self.chosenTimeLogUserId = -1; self.selectedCalendarWeek = ko.observable(0); self.errorData = ko.observable(null); + self.manualLog = ko.observable(null); self.apiEndpoints = { root: "http://localhost:8800", getUserList: "/api/users", @@ -382,6 +383,32 @@ self.assignErrorObject(errObj.errorCode, errObj.errorMessage, "getGroups"); }); }; + self.createContextMenu = ko.observableArray([ + { text: "Create", action: createlog } + ]); + self.editContextMenu = ko.observableArray([ + { text: "text", action: clicked }, + { text: "Edit", action: editlog }, + { text: "Create", action: createlog } + ]); + function editlog (data) { + alert("edit"); + } + function createlog(data) { + self.manualLog({ + CalendarWeek:-1, + Direction:-1, + EventTime: "2017-03-01T07:44:41.0861152+00:00", + Id: -1, + IdentifierId: -1, + UserId: self.chosenTimeLogUserId(), + Year: 0 + }); + $('#manualLogDialog').modal("show"); + } + function clicked(data) { + alert('oh, you clicked me! ah, and you typed "' + data.value() + '"'); + } Sammy(function () { this.get("#users", function () { var query = this.params.query; From 55a761f2858b53767e032e488a087bdbc5e08820 Mon Sep 17 00:00:00 2001 From: "chris.watts90@outlook.com" Date: Mon, 10 Apr 2017 22:21:27 +0100 Subject: [PATCH 05/23] reference bootstrap datetime picker, remove bootstrap datepicker update edit/create dialog to pull in datetime picker editor to the create edit dialog. #29 --- .../WindowsDataCenter/www/index.html | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html index 0e1c97a..e325cd7 100644 --- a/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html +++ b/DataCenter_Windows/WindowsDataCenter/WindowsDataCenter/www/index.html @@ -5,17 +5,19 @@ - + - + + - + +