diff --git a/README.md b/README.md index 86aa387d..199c912e 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ Check out these samples if you want to take advantage of the [shared runtime](ht | [Use a shared library to migrate your Visual Studio Tools for Office add-in to an Office web add-in](Samples/VSTO-shared-code-migration) | Provides a strategy for code reuse when migrating from VSTO Add-ins to Office Add-ins. | | [Integrate an Azure function with your Excel custom function](Excel-custom-functions/AzureFunction) | Learn how to integrate Azure functions with custom functions to move to the cloud or integrate additional services. | | [Dynamic DPI code samples](Samples/dynamic-dpi) | A collection of samples for handling DPI changes in COM, VSTO, and Office Add-ins. | +| [Rubric grader task pane add-in for OneNote on the web](Samples/onenote-add-in-rubric-grader/) | Explore the basics of OneNote add-ins with a sample tool for teachers. | ## Learn more diff --git a/Samples/onenote-add-in-rubric-grader/.gitignore b/Samples/onenote-add-in-rubric-grader/.gitignore new file mode 100644 index 00000000..a3225d43 --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/.gitignore @@ -0,0 +1,6 @@ +bower_components/ +node_modules/ +.vscode/ +npm-debug.log +localhost.crt +localhost.key \ No newline at end of file diff --git a/Samples/onenote-add-in-rubric-grader/LICENSE b/Samples/onenote-add-in-rubric-grader/LICENSE new file mode 100644 index 00000000..5b7689b1 --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) Microsoft Corporation +All rights reserved. + +MIT License: +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. diff --git a/Samples/onenote-add-in-rubric-grader/README.md b/Samples/onenote-add-in-rubric-grader/README.md new file mode 100644 index 00000000..c71979f2 --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/README.md @@ -0,0 +1,120 @@ +--- +title: "Rubric grader task pane add-in for OneNote on the web" +page_type: sample +urlFragment: onenote-add-in-rubric-grader +products: + - m365 + - office + - office-onenote +languages: +- javascript +extensions: + contentType: samples + technologies: + - Add-ins + createdDate: 6/17/2025 4:00:00 PM +description: Explore the basics of OneNote add-ins with a sample tool for teachers. +--- + +# Rubric grader task pane add-in for OneNote on the web + +## Summary + +The Rubric Grader sample shows you how to use the OneNote JavaScript API in a OneNote task pane add-in. The add-in gets page content, adds an outline to the page, and opens a different page. + +The add-in helps teachers grade writing assignments based on a grading rubric. + +![Rubric Grader task pane add-in in OneNote Online](assets/readme-images/rubric-grader.png) + +## Features + +- Interact with OneNote through a custom task pane + +## Applies to + +- OneNote on the web + +## Prerequisites + +- A Microsoft 365 tenant + +## Solution + +| Solution | Author(s) | +|---------|----------| +| Rubric grader task pane add-in for OneNote on the web | Microsoft | + +## Version history + +| Version | Date | Comments | +|---------|------|---------| +| 1.0 | 6-17-2025 | Initial release | + +## Run the sample + +You can run this sample in Onenote in a browser. The add-in web files are served from this repo on GitHub. + +1. Download the **manifest.xml** file from this sample to a folder on your computer. +1. Open [Office on the web](https://office.live.com/). +1. Under **Apps**, choose **OneNote**. +1. Open a notebook that contains a couple of pages. Make sure at least one page has a paragraph of content. +1. Open the **Insert** tab on the ribbon and choose **Office Add-ins**. +1. On the **Office Add-ins** dialog, select the **MY ADD-INS** tab, choose **Upload My Add-in**. +1. Browse to the add-in manifest file, and then select **Upload**. +1. Verify that the add-in loaded successfully. You will see a **Show Taskpane** button on the **Home** tab on the ribbon. + +Once the add-in is loaded use the following steps to try out the functionality. + +## Run the sample from localhost + +If you prefer to configure a web server and host the add-in's web files from your computer, use the following steps. + +1. Install a recent version of [npm](https://www.npmjs.com/get-npm) and [Node.js](https://nodejs.org/) on your computer. To verify if you've already installed these tools, run the commands `node -v` and `npm -v` in your terminal. + +1. You need http-server to run the local web server. If you haven't installed this yet, you can do this with the following command. + + ```console + npm install --global http-server + ``` + +1. You need Office-Addin-dev-certs to generate self-signed certificates to run the local web server. If you haven't installed this yet, you can do this with the following command. + + ```console + npm install --global office-addin-dev-certs + ``` + +1. Clone or download this sample to a folder on your computer, then go to that folder in a console or terminal window. + +1. Run the following command to generate a self-signed certificate to use for the web server. + + ```console + npx office-addin-dev-certs install + ``` + + This command will display the folder location where it generated the certificate files. + +1. Go to the folder location where the certificate files were generated, then copy the **localhost.crt** and **localhost.key** files to the cloned or downloaded sample folder. + +1. Run the following command. + + ```console + http-server -S -C localhost.crt -K localhost.key --cors . -p 3000 + ``` + + The http-server will run and host the current folder's files on localhost:3000. + +1. Now that your localhost web server is running, you can sideload the **manifest-localhost.xml** file provided in the sample folder. Using this file, follow the steps in [Run the sample](#run-the-sample) to sideload and run the add-in. + +## Questions and feedback + +- Did you experience any problems with the sample? [Create an issue](https://github.com/OfficeDev/Office-Add-in-samples/issues/new/choose) and we'll help you out. +- We'd love to get your feedback about this sample. Go to our [Office samples survey](https://aka.ms/OfficeSamplesSurvey) to give feedback and suggest improvements. +- For general questions about developing Office Add-ins, go to [Microsoft Q&A](https://learn.microsoft.com/answers/topics/office-js-dev.html) using the office-js-dev tag. + +## Copyright + +Copyright (c) 2025 Microsoft Corporation. All rights reserved. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + + diff --git a/Samples/onenote-add-in-rubric-grader/app/app.css b/Samples/onenote-add-in-rubric-grader/app/app.css new file mode 100644 index 00000000..48d32467 --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/app/app.css @@ -0,0 +1,41 @@ +/* Common app styling */ + +.padding-header { + padding: 30px 30px 20px 20px; +} + +.padding-body { + padding: 5px 20px; +} + +.padding-top { + padding: 25px 0px 0px 0px; +} + +#notification-message { + background-color: #818285; + color: #fff; + position: absolute; + width: 100%; + min-height: 80px; + right: 0; + z-index: 100; + bottom: 0; + display: none; /* Hidden until invoked */ +} + + #notification-message #notification-message-header { + font-size: medium; + margin-bottom: 10px; + } + + #notification-message #notification-message-close { + background-image: url("../Images/Close.png"); + background-repeat: no-repeat; + width: 24px; + height: 24px; + position: absolute; + right: 5px; + top: 5px; + cursor: pointer; + } diff --git a/Samples/onenote-add-in-rubric-grader/app/app.js b/Samples/onenote-add-in-rubric-grader/app/app.js new file mode 100644 index 00000000..f860558b --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/app/app.js @@ -0,0 +1,30 @@ +const app = (function(){ // jshint ignore:line + 'use strict'; + + let self = {}; + + // Common initialization function (to be called from each page) + self.initialize = function(){ + jQuery('body').append( + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'); + + jQuery('#notification-message-close').click(function(){ + jQuery('#notification-message').hide(); + }); + + // After initialization, expose a common notification function + self.showNotification = function(header, text){ + jQuery('#notification-message-header').text(header); + jQuery('#notification-message-body').text(text); + jQuery('#notification-message').slideDown('fast'); + }; + }; + + return self; +})(); diff --git a/Samples/onenote-add-in-rubric-grader/app/home/grader.fabricdropdownhelper.js b/Samples/onenote-add-in-rubric-grader/app/home/grader.fabricdropdownhelper.js new file mode 100644 index 00000000..157fc616 --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/app/home/grader.fabricdropdownhelper.js @@ -0,0 +1,146 @@ +/* +* Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. +* See LICENSE in the project root for license information. +*/ + +// Helper based on Office UI Fabric dropdown, which hides the original 'select' dropdown and +// creates a "fake" dropdown that can be more easily styled across browsers. +// http://dev.office.com/fabric/components/dropdown +function useFabricDropdown (id) { + var $dropdownWrapper = $('#' + id), + $originalDropdown = $dropdownWrapper.children('.ms-Dropdown-select'), + $originalDropdownOptions = $originalDropdown.children('option'), + newDropdownTitle = '', + newDropdownItems = '', + newDropdownSource = ''; + + /** Go through the options to fill up newDropdownTitle and newDropdownItems. */ + $originalDropdownOptions.each(function (index, option) { + + /** If the option is selected, it should be the new dropdown's title. */ + if (option.selected) { + newDropdownTitle = option.text; + } + + /** Add this option to the list of items. */ + newDropdownItems += '
  • ' + option.text + '
  • '; + + }); + + /** Insert the replacement dropdown. */ + newDropdownSource = '' + newDropdownTitle + ''; + $dropdownWrapper.append(newDropdownSource); + + function _openDropdown(evt) { + if (!$dropdownWrapper.hasClass('is-disabled')) { + + /** First, let's close any open dropdowns on this page. */ + $dropdownWrapper.find('.is-open').removeClass('is-open'); + + /** Stop the click event from propagating, which would just close the dropdown immediately. */ + evt.stopPropagation(); + + /** Before opening, size the items list to match the dropdown. */ + var dropdownWidth = $(this).parents(".ms-Dropdown").width(); + $(this).next(".ms-Dropdown-items").css('width', dropdownWidth + 'px'); + + /** Go ahead and open that dropdown. */ + $dropdownWrapper.toggleClass('is-open'); + $('.ms-Dropdown').each(function(){ + if ($(this)[0] !== $dropdownWrapper[0]) { + $(this).removeClass('is-open'); + } + }); + + /** Temporarily bind an event to the document that will close this dropdown when clicking anywhere. */ + $(document).bind("click.dropdown", function() { + $dropdownWrapper.removeClass('is-open'); + $(document).unbind('click.dropdown'); + }); + } + } + + /** Toggle open/closed state of the dropdown when clicking its title. */ + $dropdownWrapper.on('click', '.ms-Dropdown-title', function(event) { + _openDropdown(event); + }); + + /** Keyboard accessibility */ + $dropdownWrapper.on('keyup', function(event) { + var keyCode = event.keyCode || event.which; + // Open dropdown on enter or arrow up or arrow down and focus on first option + if (!$(this).hasClass('is-open')) { + if (keyCode === 13 || keyCode === 38 || keyCode === 40) { + _openDropdown(event); + if (!$(this).find('.ms-Dropdown-item').hasClass('is-selected')) { + $(this).find('.ms-Dropdown-item:first').addClass('is-selected'); + } + } + } + else if ($(this).hasClass('is-open')) { + // Up arrow focuses previous option + if (keyCode === 38) { + if ($(this).find('.ms-Dropdown-item.is-selected').prev().siblings().size() > 0) { + $(this).find('.ms-Dropdown-item.is-selected').removeClass('is-selected').prev().addClass('is-selected'); + } + } + // Down arrow focuses next option + if (keyCode === 40) { + if ($(this).find('.ms-Dropdown-item.is-selected').next().siblings().size() > 0) { + $(this).find('.ms-Dropdown-item.is-selected').removeClass('is-selected').next().addClass('is-selected'); + } + } + // Enter to select item + if (keyCode === 13) { + if (!$dropdownWrapper.hasClass('is-disabled')) { + + // Item text + var selectedItemText = $(this).find('.ms-Dropdown-item.is-selected').text(); + + $(this).find('.ms-Dropdown-title').html(selectedItemText); + + /** Update the original dropdown. */ + $originalDropdown.find("option").each(function(key, value) { + if (value.text === selectedItemText) { + $(this).prop('selected', true); + } else { + $(this).prop('selected', false); + } + }); + $originalDropdown.change(); + + $(this).removeClass('is-open'); + } + } + } + + // Close dropdown on esc + if (keyCode === 27) { + $(this).removeClass('is-open'); + } + }); + + /** Select an option from the dropdown. */ + $dropdownWrapper.on('click', '.ms-Dropdown-item', function () { + if (!$dropdownWrapper.hasClass('is-disabled')) { + + /** Deselect all items and select this one. */ + $(this).siblings('.ms-Dropdown-item').removeClass('is-selected'); + $(this).addClass('is-selected'); + + /** Update the replacement dropdown's title. */ + $(this).parents().siblings('.ms-Dropdown-title').html($(this).text()); + + /** Update the original dropdown. */ + var selectedItemText = $(this).text(); + $originalDropdown.find("option").each(function(key, value) { + if (value.text === selectedItemText) { + $(this).prop('selected', true); + } else { + $(this).prop('selected', false); + } + }); + $originalDropdown.change(); + } + }); +} \ No newline at end of file diff --git a/Samples/onenote-add-in-rubric-grader/app/home/grader.html b/Samples/onenote-add-in-rubric-grader/app/home/grader.html new file mode 100644 index 00000000..6f838f5a --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/app/home/grader.html @@ -0,0 +1,123 @@ + + + + + + + Rubric Grader + + + + + + + + + + + + + + +
    + +
    +
    +
    + +
    +
    + Words: +
    + Sentences: +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + + diff --git a/Samples/onenote-add-in-rubric-grader/app/home/grader.js b/Samples/onenote-add-in-rubric-grader/app/home/grader.js new file mode 100644 index 00000000..d407c31f --- /dev/null +++ b/Samples/onenote-add-in-rubric-grader/app/home/grader.js @@ -0,0 +1,237 @@ +/* + * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. + * See LICENSE in the source repository root for complete license information. + */ + +(function () { + "use strict"; + + // Define the configurable grading criteria and score values. + const criteria = ['Content', 'Organization', 'Style', 'Grammar']; + const score = [0,1,2,3,4,5,6,7,8,9,10]; + const defaultValue = 5; + + // The initialize function is run each time the page is loaded. + Office.initialize = function (reason) { + $(document).ready(function () { + app.initialize(); + + populateScoringDropDowns(); + populatePagePickerDropDown(); + + // Set up event handlers for the UI. + $('#getStats').click(getStats); + $('#addGrade').click(createGrade); + $('#clear').click(clearGrade); + $('#openPage').click(openPage); + }); + }; + + // Populates the page picker with pages from the current section. + function populatePagePickerDropDown() { + OneNote.run(function (context) { + + // Get the ID and title of the pages in the current section. + const pages = context.application.getActiveSection().pages; + + // Queue a command to load the id and title for each page. + pages.load('id,title'); + + // Run the queued commands, and return a promise to indicate task completion. + return context.sync() + .then(function () { + + // Get the ID and title of each page, add them as picker options. + const dropdown = $('#page-picker'); + $.each(pages.items, function(index, object) { + let pageId = object.id; + let pageTitle = object.title; + + if (index === 0) { + dropdown.append($('