├── .eslintrc
├── .gitignore
├── README.md
├── dev
├── dev.aspx
├── index.html
├── index.js
└── src
│ ├── ChoiceFieldDemo
│ ├── ChoiceFieldDemo.html
│ ├── ChoiceFieldDemo.js
│ └── ChoiceFieldDemo.less
│ ├── ColumnPickerDemo
│ ├── ColumnPickerDemo.html
│ └── ColumnPickerDemo.js
│ ├── DateTimeFieldDemo
│ ├── DateTimeFieldDemo.html
│ └── DateTimeFieldDemo.js
│ ├── ListPickerDemo
│ ├── ListPickerDemo.html
│ └── ListPickerDemo.js
│ ├── LookupFieldDemo
│ ├── LookupFieldDemo.html
│ └── LookupFieldDemo.js
│ ├── PersonaCardDemo
│ ├── PersonaCardDemo.html
│ ├── PersonaCardDemo.js
│ └── PersonaCardDemo.less
│ ├── SPFilterPanelDemo
│ ├── SPFilterPanelDemo.html
│ └── SPFilterPanelDemo.js
│ ├── SPPeoplePickerDemo
│ ├── SPPeoplePickerDemo.html
│ └── SPPeoplePickerDemo.js
│ └── setup
│ ├── common.js
│ └── setup.js
├── dist
├── SPWidgets.js
├── SPWidgets.js.map
├── SPWidgets.min.js
└── SPWidgets.min.js.map
├── jsdoc.conf.json
├── package.json
├── src
├── collections
│ ├── ListColumnsCollection.js
│ └── ListItemsCollection.js
├── index.js
├── models
│ ├── ListColumnModel.js
│ ├── ListItemModel.js
│ ├── ListModel.js
│ └── UserProfileModel.js
├── spapi
│ ├── getCurrentUser.js
│ ├── getList.js
│ ├── getListColumns.js
│ ├── getListContentTypes.js
│ ├── getListFormCollection.js
│ ├── getListItems.js
│ ├── getSiteListCollection.js
│ ├── getSiteUrl.js
│ ├── getSiteWebUrl.js
│ ├── getUserProfile.js
│ ├── resolvePrincipals.js
│ ├── rest
│ │ ├── ensureUser.js
│ │ ├── getContextInfo.js
│ │ ├── getCurrentUser.js
│ │ ├── getListItems.js
│ │ ├── getWebUrlFromPageUrl.js
│ │ ├── searchPeoplePicker.js
│ │ └── updateListItems.js
│ ├── searchPrincipals.js
│ └── updateListItems.js
├── sputils
│ ├── apiFetch.js
│ ├── cache.js
│ ├── constants.js
│ ├── doesMsgHaveError.js
│ ├── fillTemplate.js
│ ├── getCamlLogical.js
│ ├── getDateString.js
│ ├── getFullUrl.js
│ ├── getMsgError.js
│ ├── getNodesFromXml.js
│ ├── getSPVersion.js
│ ├── parseDateString.js
│ ├── parseLookupFieldValue.js
│ ├── parsePeopleField.js
│ ├── restUtils.js
│ └── xmlEscape.js
└── widgets
│ ├── BooleanField
│ ├── BooleanField.html
│ ├── BooleanField.js
│ └── BooleanField.less
│ ├── ChoiceField
│ ├── ChoiceField.html
│ ├── ChoiceField.js
│ ├── ChoiceField.less
│ └── ChoiceItem
│ │ ├── ChoiceItem.html
│ │ ├── ChoiceItem.js
│ │ └── ChoiceItem.less
│ ├── ColumnPicker
│ └── ColumnPicker.js
│ ├── ContentTypeField
│ └── ContentTypeField.js
│ ├── DateTimeField
│ ├── DateTimeField.html
│ ├── DateTimeField.js
│ └── DateTimeField.less
│ ├── FilterPanel
│ ├── ColumnSelector
│ │ ├── ColumnSelector.html
│ │ ├── ColumnSelector.js
│ │ ├── ColumnSelector.less
│ │ └── column.html
│ ├── FilterColumn
│ │ ├── FilterColumn.html
│ │ ├── FilterColumn.js
│ │ └── FilterColumn.less
│ ├── FilterColumnAttachmentsField
│ │ └── FilterColumnAttachmentsField.js
│ ├── FilterColumnBooleanField
│ │ └── FilterColumnBooleanField.js
│ ├── FilterColumnChoiceField
│ │ └── FilterColumnChoiceField.js
│ ├── FilterColumnContentTypeField
│ │ └── FilterColumnContentTypeField.js
│ ├── FilterColumnDateTimeField
│ │ └── FilterColumnDateTimeField.js
│ ├── FilterColumnLookupField
│ │ └── FilterColumnLookupField.js
│ ├── FilterColumnNumberField
│ │ └── FilterColumnNumberField.js
│ ├── FilterColumnTextField
│ │ └── FilterColumnTextField.js
│ ├── FilterColumnUserField
│ │ └── FilterColumnUserField.js
│ ├── FilterModel.js
│ ├── FilterPanel.html
│ ├── FilterPanel.js
│ ├── FilterPanel.less
│ └── FiltersCollection.js
│ ├── ItemPicker
│ └── ItemPicker.js
│ ├── List
│ ├── List.html
│ └── List.js
│ ├── ListItem
│ ├── ListItem.js
│ ├── ListItem.less
│ ├── ListItemFull.html
│ └── ListItemSimple.html
│ ├── ListPicker
│ └── ListPicker.js
│ ├── LookupField
│ ├── LookupField.html
│ ├── LookupField.js
│ ├── LookupField.less
│ └── SelectedItem
│ │ ├── SelectedItem.html
│ │ ├── SelectedItem.js
│ │ └── SelectedItem.less
│ ├── Message
│ ├── Message.html
│ ├── Message.js
│ └── Message.less
│ ├── PeoplePicker
│ ├── PeoplePicker.html
│ ├── PeoplePicker.js
│ ├── PeoplePicker.less
│ ├── PeoplePickerPersona
│ │ ├── PeoplePickerPersona.html
│ │ ├── PeoplePickerPersona.js
│ │ └── PeoplePickerPersona.less
│ ├── PeoplePickerREST.js
│ ├── PeoplePickerUserProfileModel.js
│ ├── Result
│ │ ├── Result.html
│ │ ├── Result.js
│ │ └── Result.less
│ └── ResultGroup
│ │ ├── ResultGroup.html
│ │ └── ResultGroup.js
│ ├── Persona
│ ├── Persona.html
│ ├── Persona.js
│ └── Persona.less
│ ├── PersonaCard
│ ├── PersonaCard.html
│ ├── PersonaCard.js
│ ├── PersonaCard.less
│ ├── PersonaCardActionDetails
│ │ ├── PersonaCardActionDetails.html
│ │ ├── PersonaCardActionDetails.js
│ │ └── detailLine.html
│ └── PersonaCardActions
│ │ ├── PersonaCardActions.html
│ │ ├── PersonaCardActions.js
│ │ └── action.html
│ └── TextField
│ ├── TextField.html
│ ├── TextField.js
│ └── TextField.less
├── test
├── .jshintrc
├── index.html
├── server
│ ├── mock.soap.GetList.js
│ ├── mock.soap.GetListFormCollection.js
│ ├── mock.soap.SearchPrincipals.js
│ ├── mock.soap.WebUrlFromPageUrl.js
│ ├── mock.soap.getListItems.js
│ └── soapMsgs
│ │ ├── error.ErrorCode.bad.xml
│ │ ├── error.ErrorCode.good.xml
│ │ ├── error.copyResult.xml
│ │ ├── error.faultcode.xml
│ │ ├── forms.GetListFormCollection.response.success.xml
│ │ ├── list.GetList.response.success.xml
│ │ ├── list.GetListItems.response.success.xml
│ │ ├── list.GetListItemsChangesSinceTokenResponse.response.success.xml
│ │ ├── login.operation.response.cannotBeNull.xml
│ │ ├── login.operation.response.noError.xml
│ │ ├── login.operation.response.passwordNotMatch.xml
│ │ ├── people.searchPrincipals.response.success.xml
│ │ └── web.webUrlFromPageUrl.response.success.xml
├── setup
│ ├── jasmine-boot.js
│ └── requirejs.config.js
├── specs
│ ├── jquery.SPWidgets.js
│ ├── jsutils
│ │ └── Compose.js
│ ├── models
│ │ ├── ListColumnModel.js
│ │ ├── ListItemModel.js
│ │ └── ListModel.js
│ ├── spapi
│ │ ├── getList.js
│ │ ├── getListColumns.js
│ │ ├── getListFormCollection.js
│ │ ├── getListItems.js
│ │ └── searchPrincipals.js
│ └── sputils
│ │ ├── doesMsgHaveError.js
│ │ └── getMsgError.js
├── suite.js
└── test.SPWidgets.aspx
└── tools
├── copy.process.minifyHtml.js
└── server.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended"
4 | ],
5 | "parserOptions": {
6 | "ecmaVersion": 6,
7 | "sourceType": "module"
8 | },
9 | "parser": "babel-eslint",
10 | "env": {
11 | "browser": true,
12 | "node": true,
13 | "es6": true
14 | },
15 | "plugins": [],
16 | "rules": {
17 | "no-console": 0,
18 | "no-underscore-dangle": 0,
19 | "quotes": [2, "double"]
20 | }
21 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #*
2 | *$
3 | *.BAK
4 | *.Z
5 | *.bak
6 | *.class
7 | *.elc
8 | *.ln
9 | *.log
10 | *.o
11 | *.obj
12 | *.olb
13 | *.old
14 | *.orig
15 | *.pyc
16 | *.pyo
17 | *.rej
18 | *~
19 | ,*
20 | .#*
21 | .DS_Store
22 | .del-*
23 | .deployables
24 | .make.state
25 | .nse_depinfo
26 | CVS.adm
27 | RCS
28 | RCSLOG
29 | SCCS
30 | _$*
31 | .settings*
32 | .project*
33 | my.*
34 | me.*
35 | BUILD*
36 | _BUILD*
37 | vendor
38 | node_modules
39 |
40 |
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SPWidgets
2 | =========
3 |
4 | SharePoint Custom UI Widgets that make building custom User Interfaces easier. All widgets are self-contained and use the office-ui-fabric styling.
5 |
6 | ## CURRENT STATUS
7 |
8 | **THIS VERSION (3.0.0) REMAINS IN **BETA** AS THE DOCUMENTATION HAS NOT BEEN CREATED AND THERE ARE NO TEST CASES. WIDGETS, HOWEVER, HAVE PROVEN (UNDER MY OWN PROJECTS) TO BE STABLE ENOUGH FOR USAGE.**
9 |
10 | Version 3.0 was a large effort that removed jQuery and jQuery UI from the code base and instead adopted native JavaScript APIs and the new Office UI Fabric for styling. It also replaced the use of Grunt with npm script and Webpack. My goal with this project has always been to have a set of SharePoint widgets I can use in any context and independent of any framework. To that extent, v3.0 has proven to do that as I have used them directly within SharePoint as a "drop in" library as well as in a project that uses VueJS.
11 |
12 | At this point, I'm still not unsure if I will dedicate any more time to the activities I believe are needed remove the `beta` indicator from v3.0. I'm leaning towards moving these widgets to Custom Elements and fully embrace the web platform in providing framework/library agnostic widgets.
13 |
14 |
15 | ## example:
16 |
17 | ```html
18 |
19 |
20 |
21 |
22 |
23 | SP Widgets
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 | ```
37 |
38 | To play with the available widgets, see this bin:
39 |
40 | http://jsbin.com/sesayohecu/edit?html,js,output
41 |
42 | __HOWEVER:__ note that several widgets will likely fail, since they require a SharePoint env. to access its APIs)
43 |
44 |
45 | Documentation
46 | -------------
47 |
48 | All widgets are built into the `dist/SPWidgets.js` bundle. When using this file directly on a page (as the example above shows), all widgets will be available under `window.SPWidgets.default`. This path is an object containing all of the exported utilities and widgets (see `src/index.js` for a full list)
49 |
50 | Full documentation is TBD...
51 |
52 | Currently, I mainly use this library as a dependency into some other projects, thus documentation (outside of the jsdocs in each widgets) has not been a focus.
53 |
54 |
55 | License
56 | -------
57 |
58 | - MIT http://www.opensource.org/licenses/mit-license.php
59 |
60 |
61 | Contributions
62 | -------------
63 |
64 | Contributions are welcomed.
65 |
66 |
67 |
68 | Developing
69 | ----------
70 |
71 | The `npm server:sp` task assists with running an environment that allows for connection to a real sharepoint instance to use its APIs. Note that this is achieve by staring a separate instance of Chrome with security turned off.
72 |
73 | > __IMPORTANT__: Do not use this version of chrome for regular internet browsing.
74 |
75 | Before staring development, create a file under `dev/` folder named `my.sp.dev.js`. In this file, add the following:
76 |
77 | ```javascript
78 | window._spPageContextInfo = {
79 | webServerRelativeUrl: "/sites/your-site-name",
80 | webAbsoluteUrl: "https://my-sharepoint-site-here.sharepoint.com/sites/your-site-name"
81 | };
82 | ```
83 |
84 | Change the above to include your SharePoint tenant information. Now run:
85 |
86 | ```bash
87 | npm run serve:sp
88 | ```
89 |
90 | All content is now being served from the `dev/` folder. Ensure that you access your sharepoint URL defined above from the same browser instance that is displaying the development content.
91 |
92 |
93 | ## Contributors
94 |
95 | - [@TerryMooreII](https://github.com/TerryMooreII)
96 | - [@donsuhr](https://github.com/donsuhr)
97 |
98 |
99 |
--------------------------------------------------------------------------------
/dev/dev.aspx:
--------------------------------------------------------------------------------
1 | <%-- SPWidgetsDemos --%>
2 | <%-- Generated by yeoman sharepoint-spa --%>
3 | <%@ Page language="C#" MasterPageFile="~masterurl/default.master"
4 | Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,Microsoft.SharePoint,Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>
5 | <%@ Register
6 | Tagprefix="SharePoint"
7 | Namespace="Microsoft.SharePoint.WebControls"
8 | Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
9 | <%@ Register
10 | Tagprefix="Utilities"
11 | Namespace="Microsoft.SharePoint.Utilities"
12 | Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
13 | <%@ Import Namespace="Microsoft.SharePoint" %>
14 | <%@ Register
15 | Tagprefix="WebPartPages"
16 | Namespace="Microsoft.SharePoint.WebPartPages"
17 | Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
18 | SPWidgetsDemos
19 | SPWidgetsDemos
20 |
21 |
22 |
23 | SPWidgetsDemos
24 |
25 |
26 |
27 |
33 |
34 |
35 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/dev/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/dev/index.js:
--------------------------------------------------------------------------------
1 | import "./src/setup/setup.js"
--------------------------------------------------------------------------------
/dev/src/ChoiceFieldDemo/ChoiceFieldDemo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Status field from Task List
4 |
5 |
6 |
7 |
8 |
9 |
Single select using custom list of choices
10 |
11 |
12 |
13 |
14 |
15 |
Tasks "Status" column, but overwritten to allow selection of multiple values. "Completed" should be preselected
16 |
17 |
18 |
19 |
20 |
21 |
Choice Fields using "layout" option of "inline"
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/dev/src/ChoiceFieldDemo/ChoiceFieldDemo.less:
--------------------------------------------------------------------------------
1 | .demo-ChoiceFieldDemo {
2 | margin-top: 1em;
3 |
4 | .demo {
5 | margin: auto;
6 | margin-top: 1em;
7 | border-bottom: 1px solid;
8 | width: 80%;
9 | padding: 2em;
10 | }
11 |
12 | }
--------------------------------------------------------------------------------
/dev/src/ColumnPickerDemo/ColumnPickerDemo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/src/ColumnPickerDemo/ColumnPickerDemo.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget"
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
4 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
5 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
6 |
7 | import ListPickerDemoTemplate from "./ColumnPickerDemo.html"
8 |
9 | import ColumnPicker from "../../../src/widgets/ColumnPicker/ColumnPicker"
10 |
11 | const PRIVATE = dataStore.create();
12 |
13 | /**
14 | * ColumnPickerDemo Widget
15 | *
16 | * @class ColumnPickerDemo
17 | * @extends Widget
18 | *
19 | * @param {Object} options
20 | */
21 | const ColumnPickerDemo = Widget.extend(/** @lends ColumnPickerDemo.prototype */{
22 | init: function (options) {
23 | var inst = {
24 | opt: objectExtend({}, ColumnPickerDemo.defaults, options)
25 | };
26 |
27 | PRIVATE.set(this, inst);
28 |
29 | this.$ui = parseHTML(
30 | fillTemplate(this.getTemplate(), inst.opt)
31 | ).firstChild;
32 |
33 | setupDemo1.call(this);
34 |
35 | this.onDestroy(function () {
36 |
37 |
38 | // Destroy all Compose object
39 | Object.keys(inst).forEach(function (prop) {
40 | if (inst[prop]) {
41 | // Widgets
42 | if (inst[prop].destroy) {
43 | inst[prop].destroy();
44 |
45 | // DOM events
46 | } else if (inst[prop].remove) {
47 | inst[prop].remove();
48 |
49 | // EventEmitter events
50 | } else if (inst[prop].off) {
51 | inst[prop].off();
52 | }
53 |
54 | inst[prop] = undefined;
55 | }
56 | });
57 |
58 | PRIVATE.delete(this);
59 | }.bind(this));
60 | },
61 |
62 | /**
63 | * returns the widget's template
64 | * @return {String}
65 | */
66 | getTemplate: function () {
67 | return ListPickerDemoTemplate;
68 | }
69 | });
70 |
71 | function setupDemo1() {
72 |
73 | var inst = PRIVATE.get(this);
74 |
75 | inst.demo1 = ColumnPicker.create({
76 | listName: "Tasks"
77 | });
78 | inst.demo1.appendTo(this.getEle());
79 |
80 | var $out = parseHTML('
').firstChild;
81 | this.getEle().appendChild($out);
82 |
83 | inst.demo1.on("item-selected", function(list){
84 | $out.textContent = JSON.stringify(list, null, 2);
85 | });
86 | }
87 |
88 |
89 | ColumnPickerDemo.defaults = {};
90 |
91 | export default ColumnPickerDemo;
92 |
--------------------------------------------------------------------------------
/dev/src/DateTimeFieldDemo/DateTimeFieldDemo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/src/DateTimeFieldDemo/DateTimeFieldDemo.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget"
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
4 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
5 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
6 | import DateTimeField from "../../../src/widgets/DateTimeField/DateTimeField"
7 | import DateTimeFieldDemoTemplate from "./DateTimeFieldDemo.html"
8 |
9 | var
10 | PRIVATE = dataStore.create(),
11 |
12 | /**
13 | * Widget description
14 | *
15 | * @class DateTimeFieldDemo
16 | * @extends Widget
17 | *
18 | * @param {Object} options
19 | */
20 | DateTimeFieldDemo = /** @lends DateTimeFieldDemo.prototype */{
21 | init: function (options) {
22 | var inst = {
23 | opt: objectExtend({}, DateTimeFieldDemo.defaults, options)
24 | };
25 |
26 | PRIVATE.set(this, inst);
27 |
28 | this.$ui = parseHTML(
29 | fillTemplate(DateTimeFieldDemoTemplate, inst.opt)
30 | ).firstChild;
31 |
32 | inst.uiFind = this.$ui.querySelector.bind(this.$ui);
33 |
34 | setupDemo1.call(this);
35 |
36 | this.onDestroy(function () {
37 | Object.keys(inst).forEach(function(prop){
38 | if (inst[prop] && inst[prop].destroy) {
39 | inst[prop].destroy();
40 | }
41 | });
42 | PRIVATE.delete(this);
43 | }.bind(this));
44 | }
45 | };
46 |
47 | function setupDemo1(){
48 | var inst = PRIVATE.get(this);
49 | var $demo1Cntr = inst.uiFind("#DateTimeField_demo1");
50 |
51 | inst.demo1 = DateTimeField.create({
52 | column: {
53 | DisplayName: "Due Date",
54 | Description: "the date the item is due"
55 | }
56 | });
57 | inst.demo1.appendTo($demo1Cntr);
58 | }
59 |
60 | DateTimeFieldDemo = Widget.extend(DateTimeFieldDemo);
61 | DateTimeFieldDemo.defaults = {};
62 |
63 | export default DateTimeFieldDemo;
64 |
--------------------------------------------------------------------------------
/dev/src/ListPickerDemo/ListPickerDemo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/src/ListPickerDemo/ListPickerDemo.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget"
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
4 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
5 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
6 |
7 | import ListPickerDemoTemplate from "./ListPickerDemo.html"
8 |
9 | import ListPicker from "../../../src/widgets/ListPicker/ListPicker"
10 |
11 | const PRIVATE = dataStore.create();
12 |
13 | /**
14 | * ListPickerDemo Widget
15 | *
16 | * @class ListPickerDemo
17 | * @extends Widget
18 | *
19 | * @param {Object} options
20 | */
21 | const ListPickerDemo = Widget.extend(/** @lends ListPickerDemo.prototype */{
22 | init: function (options) {
23 | var inst = {
24 | opt: objectExtend({}, ListPickerDemo.defaults, options)
25 | };
26 |
27 | PRIVATE.set(this, inst);
28 |
29 | this.$ui = parseHTML(
30 | fillTemplate(this.getTemplate(), inst.opt)
31 | ).firstChild;
32 |
33 | setupDemo1.call(this);
34 |
35 | this.onDestroy(function () {
36 |
37 |
38 | // Destroy all Compose object
39 | Object.keys(inst).forEach(function (prop) {
40 | if (inst[prop]) {
41 | // Widgets
42 | if (inst[prop].destroy) {
43 | inst[prop].destroy();
44 |
45 | // DOM events
46 | } else if (inst[prop].remove) {
47 | inst[prop].remove();
48 |
49 | // EventEmitter events
50 | } else if (inst[prop].off) {
51 | inst[prop].off();
52 | }
53 |
54 | inst[prop] = undefined;
55 | }
56 | });
57 |
58 | PRIVATE.delete(this);
59 | }.bind(this));
60 | },
61 |
62 | /**
63 | * returns the widget's template
64 | * @return {String}
65 | */
66 | getTemplate: function () {
67 | return ListPickerDemoTemplate;
68 | }
69 | });
70 |
71 | function setupDemo1() {
72 |
73 | var inst = PRIVATE.get(this);
74 |
75 | inst.demo1 = ListPicker.create();
76 | inst.demo1.appendTo(this.getEle());
77 |
78 | var $out = parseHTML('
').firstChild;
79 | this.getEle().appendChild($out);
80 |
81 | inst.demo1.on("item-selected", function(list){
82 | $out.textContent = JSON.stringify(list, null, 2);
83 | });
84 | }
85 |
86 |
87 | ListPickerDemo.defaults = {};
88 |
89 | export default ListPickerDemo;
90 |
--------------------------------------------------------------------------------
/dev/src/LookupFieldDemo/LookupFieldDemo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Type of column set to LookupMulti
5 |
6 |
7 |
Type of column set to Lookup
8 |
9 |
10 |
--------------------------------------------------------------------------------
/dev/src/LookupFieldDemo/LookupFieldDemo.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget"
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
4 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
5 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
6 | import LookupField from "../../../src/widgets/LookupField/LookupField"
7 | import getListColumns from "../../../src/spapi/getListColumns"
8 | import LookupFieldDemoTemplate from "./LookupFieldDemo.html"
9 |
10 | import common from "../setup/common";
11 |
12 | var
13 | PRIVATE = dataStore.create(),
14 |
15 | /**
16 | * Widget description
17 | *
18 | * @class LookupFieldDemo
19 | * @extends Widget
20 | *
21 | * @param {Object} options
22 | */
23 | LookupFieldDemo = /** @lends LookupFieldDemo.prototype */{
24 | init: function (options) {
25 | var inst = {
26 | opt: objectExtend({}, LookupFieldDemo.defaults, options)
27 | };
28 |
29 | PRIVATE.set(this, inst);
30 |
31 | this.$ui = parseHTML(
32 | fillTemplate(LookupFieldDemoTemplate, inst.opt)
33 | ).firstChild;
34 |
35 | setupDemo1.call(this);
36 | setupDemo2.call(this);
37 |
38 | this.onDestroy(function () {
39 | Object.keys(inst).forEach(function(prop){
40 | if (inst[prop] && inst[prop].destroy) {
41 | inst[prop].destroy();
42 | }
43 | });
44 | PRIVATE.delete(this);
45 | }.bind(this));
46 | }
47 | };
48 |
49 | function setupDemo1() {
50 | var inst = PRIVATE.get(this);
51 |
52 | getListColumns({
53 | listName: "Tasks",
54 | webURL: common.getWebURL()
55 | })
56 | .then(function(columns){
57 | inst.demo1 = LookupField.create({
58 | column: columns.getColumn("Predecessors")
59 | });
60 | inst.demo1.appendTo(this.getEle().querySelector("#lookupFieldDemo_1"));
61 |
62 | }.bind(this))["catch"](function(e){
63 | console.log(e); // jshint ignore:line
64 | });
65 | }
66 |
67 |
68 | function setupDemo2() {
69 | var inst = PRIVATE.get(this);
70 |
71 | getListColumns({
72 | listName: "Tasks",
73 | webURL: common.getWebURL()
74 | })
75 | .then(function(columns){
76 | let col = columns.getColumn("Predecessors");
77 | col.Type = "Lookup";
78 |
79 | inst.demo1 = LookupField.create({
80 | column: columns.getColumn("Predecessors")
81 | });
82 | inst.demo1.appendTo(this.getEle().querySelector("#lookupFieldDemo_2"));
83 |
84 | }.bind(this))["catch"](function(e){
85 | console.log(e); // jshint ignore:line
86 | });
87 | }
88 |
89 | LookupFieldDemo = Widget.extend(LookupFieldDemo);
90 | LookupFieldDemo.defaults = {};
91 |
92 | export default LookupFieldDemo;
93 |
--------------------------------------------------------------------------------
/dev/src/PersonaCardDemo/PersonaCardDemo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/src/PersonaCardDemo/PersonaCardDemo.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget"
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
4 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
5 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
6 |
7 | import getUserProfile from "../../../src/spapi/getUserProfile"
8 | import PeoplePicker from "../../../src/widgets/PeoplePicker/PeoplePicker"
9 | import PersonaCard from "../../../src/widgets/PersonaCard/PersonaCard"
10 |
11 | import PersonaCardDemoTemplate from "./PersonaCardDemo.html"
12 | import "./PersonaCardDemo.less"
13 |
14 | //==========================================================================
15 | const PRIVATE = dataStore.create();
16 |
17 |
18 | /**
19 | * PersonaCardDemo Widget
20 | *
21 | * @class PersonaCardDemo
22 | * @extends Widget
23 | *
24 | * @param {Object} options
25 | */
26 | const PersonaCardDemo = Widget.extend(/** @lends PersonaCardDemo.prototype */{
27 | init(options) {
28 | var inst = {
29 | opt: objectExtend({}, this.getFactory().defaults, options)
30 | };
31 |
32 | PRIVATE.set(this, inst);
33 |
34 | let $ui = this.$ui = this.getTemplate();
35 |
36 | if (typeof $ui === "string") {
37 | $ui = this.$ui = parseHTML(fillTemplate($ui, inst)).firstChild;
38 | }
39 |
40 | let uiFind = $ui.querySelector.bind($ui);
41 | let $cardHolder = uiFind(".card");
42 |
43 | inst.peoplePicker = PeoplePicker.create({
44 | allowMultiples: false,
45 | showSelected: false
46 | });
47 | inst.peoplePicker.appendTo(uiFind(".picker"));
48 |
49 | inst.peoplePicker.on("select", (/** @type PeoplePickerUserProfileModel */person) => {
50 | inst.peoplePicker.hideResults();
51 |
52 | if (inst.personaCard) {
53 | inst.personaCard.destroy();
54 | inst.personaCard = null;
55 | }
56 |
57 | getUserProfile({
58 | accountName: person.AccountName,
59 | webURL: person.webURL
60 | }).then((personProfile) => {
61 | inst.personaCard = PersonaCard.create({
62 | userProfile: personProfile
63 | });
64 | inst.personaCard.appendTo($cardHolder);
65 | });
66 | });
67 |
68 |
69 | this.onDestroy(() => {
70 | // Destroy all Compose object
71 | Object.keys(inst).forEach(function (prop) {
72 | if (inst[prop]) {
73 | [
74 | "destroy", // Compose
75 | "remove", // DOM Events Listeners
76 | "off" // EventEmitter Listeners
77 | ].some((method) => {
78 | if (inst[prop][method]) {
79 | inst[prop][method]();
80 | return true;
81 | }
82 | });
83 |
84 | inst[prop] = undefined;
85 | }
86 | });
87 |
88 | PRIVATE['delete'](this);
89 | });
90 | },
91 |
92 | /**
93 | * returns the widget's template
94 | * @return {String}
95 | */
96 | getTemplate(){
97 | return PersonaCardDemoTemplate;
98 | }
99 | });
100 |
101 | PersonaCardDemo.defaults = {};
102 |
103 | export default PersonaCardDemo;
104 |
--------------------------------------------------------------------------------
/dev/src/PersonaCardDemo/PersonaCardDemo.less:
--------------------------------------------------------------------------------
1 | .spwidgets-PersonaCardDemo {
2 | margin-top: 2em;
3 | padding: 0 3em;
4 |
5 | .picker,
6 | .card {
7 | display: inline-block;
8 | width: ~"calc(49% - 4em)";
9 | box-sizing: border-box;
10 | vertical-align: top;
11 | padding: 1em;
12 | }
13 | }
--------------------------------------------------------------------------------
/dev/src/SPFilterPanelDemo/SPFilterPanelDemo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dev/src/SPFilterPanelDemo/SPFilterPanelDemo.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget";
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
4 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate";
5 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML";
6 | import xmlEscape from "common-micro-libs/src/jsutils/xmlEscape"
7 | import FilterPanel from "../../../src/widgets/FilterPanel/FilterPanel";
8 | import SPFilterPanelDemoTemplate from "./SPFilterPanelDemo.html";
9 |
10 | var PRIVATE = dataStore.create();
11 |
12 | /**
13 | * Widget description
14 | *
15 | * @class SPFilterPanelDemo
16 | * @extends Widget
17 | */
18 | var SPFilterPanelDemo = /** @lends SPFilterPanelDemo.prototype */{
19 | init: function (options) {
20 | var inst = {
21 | opt: objectExtend({}, SPFilterPanelDemo.defaults, options)
22 | };
23 |
24 | PRIVATE.set(this, inst);
25 |
26 | this.$ui = parseHTML(
27 | fillTemplate(SPFilterPanelDemoTemplate, inst.opt)
28 | ).firstChild;
29 |
30 | inst.uiFind = this.$ui.querySelector.bind(this.$ui);
31 |
32 | setupDemo1.call(this);
33 |
34 | this.onDestroy(function () {
35 | PRIVATE.delete(this);
36 | }.bind(this));
37 | }
38 | };
39 |
40 | function setupDemo1(){
41 | var inst = PRIVATE.get(this);
42 | var filterPanel = FilterPanel.create({listName: "Tasks", bodyHeight: `${window.innerHeight - 200}px`});
43 | var cntr = inst.uiFind("#spfilterpaneldemo_1");
44 | var out = inst.uiFind("#spfilterpaneldemo_1_out");
45 |
46 | filterPanel.appendTo(cntr);
47 | filterPanel.getEle().style.width = "50%";
48 | filterPanel.getEle().style.marginLeft = "20%";
49 |
50 | filterPanel.on("*", function(evName){
51 | out.appendChild(
52 | parseHTML('Event: ' + evName + '
')
53 | );
54 |
55 | if (evName === 'find') {
56 | out.appendChild(
57 | parseHTML(
58 | '' + JSON.stringify(
59 | filterPanel.getFilters().slice(),
60 | null,
61 | 2
62 | ) + "\r\n URL PARAMS: " +
63 | filterPanel.getFilters().toURLParams() +
64 | "\r\n URL PARAMS W/abreviated keys: " +
65 | filterPanel.getFilters().toURLParams({
66 | stringifyProperties: [
67 | {column: "c"},
68 | {values: 'v'},
69 | {logicalOperator: 'lO'}
70 | ]
71 | }) +
72 | "\r\n toCAMLQuery: " +
73 | xmlEscape.escape(filterPanel.getFilters().toCAMLQuery()) +
74 | ' '
75 | )
76 | );
77 | }
78 | });
79 |
80 | window.filterPanel1 = filterPanel;
81 | console.info("window.filterPanel1 created");
82 |
83 | this.onDestroy(function() {
84 | filterPanel.destroy();
85 | window.filterPanel1 = null;
86 | });
87 | }
88 |
89 | SPFilterPanelDemo = Widget.extend(SPFilterPanelDemo);
90 | SPFilterPanelDemo.defaults = {};
91 |
92 | export default SPFilterPanelDemo;
93 |
--------------------------------------------------------------------------------
/dev/src/SPPeoplePickerDemo/SPPeoplePickerDemo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Instance of People Picker does not allow multiples to be selected. Search for suggestions occur as soon as a value is typed.
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/dev/src/SPPeoplePickerDemo/SPPeoplePickerDemo.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget";
2 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML";
3 | import domFind from "common-micro-libs/src/domutils/domFind";
4 | import SPPeoplePicker from "../../../src/widgets/PeoplePicker/PeoplePicker";
5 | import template from "./SPPeoplePickerDemo.html";
6 |
7 | import common from "../setup/common";
8 |
9 | var $CONSOLE_LOG_OUT,
10 | log = function(data){
11 | $CONSOLE_LOG_OUT.appendChild(
12 | parseHTML('' + data + '
')
13 | );
14 | };
15 |
16 | export default Widget.extend({
17 | init: function(){
18 | this.$ui = parseHTML(template).firstChild;
19 | $CONSOLE_LOG_OUT = domFind(this.$ui, "#SPPeoplePicker_console_out").shift();
20 |
21 | initDemo1.call(this);
22 | initDemo2.call(this);
23 | }
24 | });
25 |
26 | function initDemo1() {
27 | var picker = SPPeoplePicker.create({
28 | webURL: common.getWebURL()
29 | });
30 | picker.appendTo(domFind(this.$ui, "#SPPeoplePickerDemo1")[0]);
31 | }
32 |
33 | function initDemo2() {
34 | var picker = SPPeoplePicker.create({
35 | allowMultiples: false,
36 | minLength: 0,
37 | webURL: common.getWebURL()
38 | });
39 | picker.appendTo(domFind(this.$ui, "#SPPeoplePickerDemo2")[0]);
40 |
41 | picker.on("remove", function(person){
42 | log("Event Triggered: remove:\n" +
43 | JSON.stringify(person || {}, null, 2)
44 | .replace("<", "<")
45 | .replace(">", ">")
46 | );
47 | });
48 |
49 | picker.on("select", function(person){
50 | log("Event Triggered: select:\n" +
51 | JSON.stringify(person || {}, null, 2)
52 | .replace("<", "<")
53 | .replace(">", ">")
54 | );
55 | });
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/dev/src/setup/common.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | getWebURL: function(){
4 | return location.search.substr(1);
5 | }
6 | };
--------------------------------------------------------------------------------
/dev/src/setup/setup.js:
--------------------------------------------------------------------------------
1 | import page from "page"
2 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
3 |
4 | import SPPeoplePickerDemo from '../SPPeoplePickerDemo/SPPeoplePickerDemo'
5 | import SPFilterPanelDemo from '../SPFilterPanelDemo/SPFilterPanelDemo'
6 | import LookupFieldDemo from '../LookupFieldDemo/LookupFieldDemo'
7 | import ListPickerDemo from '../ListPickerDemo/ListPickerDemo'
8 | import ColumnPickerDemo from '../ColumnPickerDemo/ColumnPickerDemo'
9 | import DateTimeFieldDemo from '../DateTimeFieldDemo/DateTimeFieldDemo'
10 | import ChoiceFieldDemo from '../ChoiceFieldDemo/ChoiceFieldDemo'
11 | import PersonaCardDemo from '../PersonaCardDemo/PersonaCardDemo'
12 |
13 |
14 | //==============================================================
15 |
16 |
17 | let currentDemo;
18 | let demoCntr = document.querySelector("#spwidgets_dev_demo");
19 | let demoSelector = document.querySelector("#demo_selector");
20 | let demoComponents = {
21 | SPPeoplePickerDemo,
22 | SPFilterPanelDemo,
23 | LookupFieldDemo,
24 | ListPickerDemo,
25 | ColumnPickerDemo,
26 | DateTimeFieldDemo,
27 | ChoiceFieldDemo,
28 | PersonaCardDemo
29 | };
30 |
31 | Object.keys(demoComponents).forEach((demoName) => {
32 | demoSelector.appendChild(parseHTML('' + demoName+ ' '));
33 | });
34 |
35 | demoSelector.addEventListener("change", function(){
36 | location.hash = `#/${demoSelector.value}`;
37 | });
38 |
39 | page.base("/#");
40 | page("/:demoName", function(ctx){
41 | if (currentDemo) {
42 | currentDemo.destroy();
43 | currentDemo = null;
44 | }
45 |
46 | let demoName = ctx.params.demoName;
47 |
48 | if (demoComponents[demoName]) {
49 | currentDemo = demoComponents[demoName].create();
50 | currentDemo.appendTo(demoCntr);
51 | }
52 | });
53 | page.start();
54 |
55 | demoSelector.style.display = "";
56 |
57 |
--------------------------------------------------------------------------------
/jsdoc.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "include": [
4 | "./src/models",
5 | "./src/collections",
6 | "./src/widgets",
7 | "./node_modules/common-micro-libs/src"
8 | ],
9 | "includePattern": ".+\\.js(doc)?$",
10 | "excludePattern": "(^|\\/|\\\\)_"
11 | },
12 | "opts": {
13 | "recurse": true,
14 | "destination": "./_DOCS/"
15 | }
16 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SPWidgets",
3 | "version": "3.0.0-beta.1",
4 | "description": "SharePoint widgets and tools for custom client side web solutions",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "open": "opener http://127.0.0.1:8184",
8 | "serve": "webpack-dev-server --config node_modules/project-base/configs/webpack.dev.js --progress --hot --color --port 8184 --entry ./dev/index.js",
9 | "serve:sp": "opener chrome.exe --user-data-dir=\"C:/chromeDevSession\" --disable-web-security http://127.0.0.1:8184 && npm run serve -- --no-open",
10 | "build": "webpack --config node_modules/project-base/configs/webpack.dev.js",
11 | "build:ie": "webpack --config node_modules/project-base/configs/webpack.prod.js --entry ./dev/index.js --output-path ./dev --output-filename ie-test-bundle.js",
12 | "build:prod": "webpack --config node_modules/project-base/configs/webpack.prod.js",
13 | "build:prod:min": "webpack --config node_modules/project-base/configs/webpack.prod.uglify.js",
14 | "build:apiDocs": "jsdoc -c node_modules/project-base/configs/jsdoc.conf.json",
15 | "dist": "npm run build:prod&&npm run build:prod:min",
16 | "setup:dev": "node node_modules/project-base/scripts/create-dev",
17 | "test": "tape -r @std/esm test/**/*.js",
18 | "lint": "eslint src/**/*.js",
19 | "lint:fix": "eslint src/**/*.js --fix"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git@github.com:purtuga/SPWidgets.git"
24 | },
25 | "author": {
26 | "name": "Paul Tavares"
27 | },
28 | "homepage": "http://purtuga.github.io/SPWidgets",
29 | "license": "MIT",
30 | "dependencies": {
31 | "common-micro-libs": "purtuga/common-micro-libs#release/v2x",
32 | "flatpickr": "^1.9.1",
33 | "observable-data": "github:purtuga/observable-data#release/v2x"
34 | },
35 | "devDependencies": {
36 | "opener": "^1.4.1",
37 | "page": "^1.7.1",
38 | "project-base": "github:purtuga/project-base#beta/v2.0.0"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/collections/ListColumnsCollection.js:
--------------------------------------------------------------------------------
1 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
2 | import Collection from "observable-data/src/ObservableArray"
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
4 |
5 |
6 | var PRIVATE = dataStore.create();
7 |
8 | /**
9 | * A collection of List Columns
10 | *
11 | * @class ListColumnsCollection
12 | * @extends Collection
13 | *
14 | * @param {Array} itemsList
15 | * @param {Object} options
16 | * @param {Object} options.listDef
17 | */
18 | export default Collection.extend({
19 | init: function(itemsList, options){
20 | Collection.prototype.init.call(this, itemsList);
21 |
22 | var opt = objectExtend({}, {
23 | listDef: null
24 | }, options);
25 |
26 | PRIVATE.set(this, opt);
27 | },
28 |
29 | /**
30 | * Returns an object with the definition for the given column
31 | *
32 | * @param {String} name
33 | * Name of column - external or internal.
34 | *
35 | * @return {ListColumnModel}
36 | */
37 | getColumn: function(name){
38 | var col;
39 | this.some(function(thisCol){
40 | if (thisCol.Name === name || thisCol.DisplayName === name || thisCol.StaticName === name){
41 | col = thisCol;
42 | }
43 | });
44 | return col;
45 | },
46 |
47 | /**
48 | * returns the ListModel for the list for which the collection was requested.
49 | *
50 | * @return {ListModel}
51 | */
52 | getList: function(){
53 | return PRIVATE.get(this).listDef;
54 | }
55 |
56 | });
57 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import getMsgError from "./sputils/getMsgError"
2 | import doesMsgHaveError from "./sputils/doesMsgHaveError"
3 | import xmlEscape from "./sputils/xmlEscape"
4 | import fillTemplate from "./sputils/fillTemplate"
5 | import getCamlLogical from "./sputils/getCamlLogical"
6 | import getSPVersion from "./sputils/getSPVersion"
7 | import parseDateString from "./sputils/parseDateString"
8 | import parseLookupFieldValue from "./sputils/parseLookupFieldValue"
9 | import getDateString from "./sputils/getDateString"
10 | import getNodesFromXml from "./sputils/getNodesFromXml"
11 | import getList from "./spapi/getList"
12 | import getListColumns from "./spapi/getListColumns"
13 | import getListFormCollection from "./spapi/getListFormCollection"
14 | import getListItems from "./spapi/getListItems"
15 | import getSiteListCollection from "./spapi/getSiteListCollection"
16 | import getSiteWebUrl from "./spapi/getSiteWebUrl"
17 | import getUserProfile from "./spapi/getUserProfile"
18 | import resolvePrincipals from "./spapi/resolvePrincipals"
19 | import searchPrincipals from "./spapi/searchPrincipals"
20 | import updateListItems from "./spapi/updateListItems"
21 |
22 | import ChoiceField from "./widgets/ChoiceField/ChoiceField"
23 | import DateTimeField from "./widgets/DateTimeField/DateTimeField"
24 | import List from "./widgets/List/List"
25 | import ListItem from "./widgets/ListItem/ListItem"
26 | import LookupField from "./widgets/LookupField/LookupField"
27 | import Message from "./widgets/Message/Message"
28 | import PeoplePicker from "./widgets/PeoplePicker/PeoplePicker"
29 | import Persona from "./widgets/Persona/Persona"
30 | import PersonaCard from "./widgets/PersonaCard/PersonaCard"
31 | import TextField from "./widgets/TextField/TextField"
32 |
33 | export default {
34 | getMsgError: getMsgError,
35 | doesMsgHaveError: doesMsgHaveError,
36 | xmlEscape: xmlEscape,
37 | fillTemplate: fillTemplate,
38 | getCamlLogical: getCamlLogical,
39 | getSPVersion: getSPVersion,
40 | parseDateString: parseDateString,
41 | parseLookupFieldValue: parseLookupFieldValue,
42 | getDateString: getDateString,
43 | getNodesFromXml: getNodesFromXml,
44 | getList: getList,
45 | getListColumns: getListColumns,
46 | getListFormCollection: getListFormCollection,
47 | getListItems: getListItems,
48 | getSiteListCollection: getSiteListCollection,
49 | getSiteWebUrl: getSiteWebUrl,
50 | getUserProfile: getUserProfile,
51 | resolvePrincipals: resolvePrincipals,
52 | searchPrincipals: searchPrincipals,
53 | updateListItems: updateListItems,
54 |
55 | ChoiceField: ChoiceField,
56 | DateTimeField: DateTimeField,
57 | List: List,
58 | ListItem: ListItem,
59 | LookupField: LookupField,
60 | Message: Message,
61 | PeoplePicker: PeoplePicker,
62 | Persona: Persona,
63 | PersonaCard: PersonaCard,
64 | TextField: TextField
65 | };
66 |
--------------------------------------------------------------------------------
/src/models/ListItemModel.js:
--------------------------------------------------------------------------------
1 | import ObservableObject from "observable-data/src/ObservableObject"
2 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
4 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
5 | import domFind from "common-micro-libs/src/domutils/domFind"
6 |
7 |
8 | var PRIVATE = dataStore.stash;
9 |
10 | /**
11 | * Model for SharePoint List Items (rows). Object returned will include all of
12 | * the properties that were given on input (row). In addition, if `options`
13 | * are provided on input and those have a `CAMLViewFields`, then the model
14 | * will have one attribute for each - even if those were not included in the
15 | * `itemData` (SharePoint does not return empty attributes)
16 | *
17 | * @class ListItemModel
18 | * @extends ObservableObject
19 | *
20 | * @param {Object} itemData
21 | * An object with the properties for the model
22 | * @param {Object} [options]
23 | * An object with the options used to get the row from SP
24 | *
25 | */
26 | const ListItemModel = ObservableObject.extend(/** @lends ListItemModel.prototype */{
27 | init: function(itemData, options){
28 | if (PRIVATE.has(this)) {
29 | return;
30 | }
31 |
32 | ObservableObject.prototype.init.call(this, itemData, options);
33 |
34 | var opt = objectExtend({}, {
35 | listName: "",
36 | webURL: ""
37 | }, options);
38 |
39 | // If options has CAMLViewFields, then ensure the model has
40 | // those fields defined as attributes
41 | if (opt && opt.CAMLViewFields) {
42 | domFind(parseHTML(opt.CAMLViewFields), "FieldRef").forEach(fieldEle => {
43 | let fieldName = fieldEle.getAttribute("Name");
44 | if (fieldName && !this.hasOwnProperty(fieldName)) {
45 | this[fieldName] = "";
46 | }
47 | });
48 | }
49 |
50 | PRIVATE.set(this, opt);
51 | this.onDestroy(() => PRIVATE["delete"](this));
52 | },
53 |
54 | /**
55 | * Returns an object with the `listName` and `webURL`
56 | * attributes needed to retrieve list information. Data
57 | * will only be available if provided on input when model
58 | * was initialized.
59 | *
60 | * @returns {Object}
61 | */
62 | getListInfo: function(){
63 | return PRIVATE.get(this);
64 | }
65 | });
66 |
67 | export default ListItemModel
--------------------------------------------------------------------------------
/src/spapi/getListContentTypes.js:
--------------------------------------------------------------------------------
1 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
2 | import getSiteWebUrl from "./getSiteWebUrl"
3 | import cache from "../sputils/cache";
4 | import getNodesFromXml from "../sputils/getNodesFromXml";
5 | import apiFetch from "../sputils/apiFetch";
6 |
7 | //========================================================================
8 |
9 | /**
10 | * Retrieves the list of content types for a given list.
11 | *
12 | * @param {Object} options
13 | *
14 | * @param {String} options.listName
15 | * list Name or ID
16 | *
17 | * @param {String} [options.webURL=currentSite]
18 | * The url to the site where list is located. Defaults to current site.
19 | *
20 | * @param {Boolean} [options.cache=true]
21 | * If true (default), content will be cached.
22 | *
23 | * @return {Promise}
24 | * Resolved with an array-of-object with the content types.
25 | *
26 | * @see https://msdn.microsoft.com/en-us/library/lists.lists.getlistcontenttypes.aspx
27 | *
28 | * @example Content type object
29 | *
30 | * {
31 | * Description: "Track a work item that you or your team needs to complete.",
32 | * ID: "0x010800719988A683552B489A4C7F1E2288B466",
33 | * Name: "Task",
34 | * Scope: "https://tenant.sharepoint.com/sites/sitea/Lists/Tasks",
35 | * Version: "16"
36 | * }
37 | */
38 | const getListContentTypes = function(options) {
39 | let opt = objectExtend({}, getListContentTypes.defaults, options);
40 |
41 | return getSiteWebUrl(opt.webURL).then(function(webURL) {
42 | opt.cacheKey = opt.webURL + "?getListContentTypes=" + opt.listName;
43 |
44 | // IF cache was requested and we have it cached, resolve now
45 | if (opt.cache && cache.isCached(opt.cacheKey)) {
46 | return JSON.parse(JSON.stringify(cache.get(opt.cacheKey)));
47 | }
48 |
49 | return apiFetch(webURL + "_vti_bin/Lists.asmx", {
50 | method: "POST",
51 | headers: {
52 | "Content-Type": "text/xml;charset=UTF-8"
53 | },
54 | body: "" +
55 | "" +
56 | opt.listName + " "
57 | })
58 | .then(function(response){
59 | let contentTypes = getNodesFromXml({
60 | xDoc: response.content,
61 | nodeName: "ContentType"
62 | });
63 |
64 | if (opt.cache) {
65 | cache(opt.cacheKey, contentTypes);
66 | }
67 |
68 | return JSON.parse(JSON.stringify(contentTypes));
69 | });
70 | });
71 | };
72 |
73 | getListContentTypes.defaults = {
74 | listName: "",
75 | webURL: "",
76 | cache: true
77 | };
78 |
79 | export default getListContentTypes;
80 |
--------------------------------------------------------------------------------
/src/spapi/rest/ensureUser.js:
--------------------------------------------------------------------------------
1 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
2 | import getContextInfo from "./getContextInfo"
3 | import apiFetch from "../../sputils/apiFetch"
4 | import {
5 | getRestHeaders,
6 | processUserInfo } from "../../sputils/restUtils"
7 | import cache from "../../sputils/cache"
8 | import UserProfileModel from "../../models/UserProfileModel"
9 |
10 | //===========================================================================
11 |
12 | /**
13 | * Ensures that a given user is added to the current site, which then returns the
14 | * `ID` of that user as present in the User information table.
15 | *
16 | * @param options
17 | * @param {String} options.logonName
18 | * @param {String} [options.webURL]
19 | * @param {Boolean} [options.cache=true]
20 | * @param {UserProfileModel} [options.UserProfileModel=UserProfileModel]
21 | *
22 | * @return {Promise}
23 | *
24 | * @see https://msdn.microsoft.com/en-us/library/office/dn499819%28v=office.15%29.aspx?f=255&MSPPError=-2147217396#bk_WebEnsureUser
25 | * @see https://msdn.microsoft.com/en-us/library/office/dn531432.aspx#bk_User
26 | */
27 | export default function ensureUser (options) {
28 | const opt = objectExtend({
29 | logonName: "",
30 | webURL: "",
31 | cache: true,
32 | UserProfileModel
33 | }, options);
34 |
35 | return getContextInfo(opt.webURL)
36 | .then(contextInfo => {
37 | opt.webURL = contextInfo.WebFullUrl + "/";
38 | const cacheKey = `${opt.webURL}?${ opt.logonName }`;
39 |
40 | if (opt.cache && cache.get(cacheKey)) {
41 | return cache.get(cacheKey).then(response => processApiResponse(response, opt));
42 | }
43 | else if (!opt.cache) {
44 | cache.clear(cacheKey);
45 | }
46 |
47 | const apiRequest = apiFetch(`${ contextInfo.WebFullUrl }/_api/web/ensureuser`, {
48 | method: "POST",
49 | headers: getRestHeaders(contextInfo, true),
50 | body: JSON.stringify({ logonName: opt.logonName })
51 | });
52 |
53 | if (opt.cache) {
54 | cache.set(cacheKey, apiRequest);
55 |
56 | apiRequest.catch(e => {
57 | cache.clear(cacheKey);
58 | console.log(e); // eslint-disable-line
59 | });
60 | }
61 |
62 | return apiRequest.then(response => processApiResponse(response, opt));
63 | });
64 | }
65 |
66 | function processApiResponse(response, opt) {
67 | return opt.UserProfileModel.create(processUserInfo(response.content.d), opt);
68 | }
69 |
70 | // SAMPLE RESPONSE:
71 | //
72 | // {
73 | // "d": {
74 | // "__metadata": {
75 | // "id": "https://tenant.sharepoint.com/sites/siteName/_api/Web/GetUserById(11)",
76 | // "uri": "https://tenant.sharepoint.com/sites/siteName/_api/Web/GetUserById(11)",
77 | // "type": "SP.User"
78 | // },
79 | // "Alerts": {"__deferred": {"uri": "https://tenant.sharepoint.com/sites/siteName/_api/Web/GetUserById(11)/Alerts"}},
80 | // "Groups": {"__deferred": {"uri": "https://tenant.sharepoint.com/sites/siteName/_api/Web/GetUserById(11)/Groups"}},
81 | // "Id": 11,
82 | // "IsHiddenInUI": false,
83 | // "LoginName": "i:0#.f|membership|paul.tavares@tenantname.com",
84 | // "Title": "Paul Tavares",
85 | // "PrincipalType": 1,
86 | // "Email": "paultavares@tenantname.com",
87 | // "IsEmailAuthenticationGuestUser": false,
88 | // "IsShareByEmailGuestUser": false,
89 | // "IsSiteAdmin": true,
90 | // "UserId": {
91 | // "__metadata": {"type": "SP.UserIdInfo"},
92 | // "NameId": "10033fff8524baa1",
93 | // "NameIdIssuer": "urn:federation:microsoftonline"
94 | // }
95 | // }
96 | // }
97 |
98 |
--------------------------------------------------------------------------------
/src/spapi/rest/getListItems.js:
--------------------------------------------------------------------------------
1 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
2 | import getContextInfo from "./getContextInfo"
3 | import apiFetch from "../../sputils/apiFetch"
4 | import { getRestHeaders, processResults } from "../../sputils/restUtils"
5 | import ListItemModel from "../../models/ListItemModel"
6 | import ListItemsCollection from "../../collections/ListItemsCollection"
7 | import {IS_GUID_RE} from "../../sputils/constants";
8 |
9 |
10 | //==================================================================
11 | const encodeURIComponent = window.encodeURIComponent;
12 |
13 |
14 | /**
15 | * Method to retrieve data from a SharePoint lists
16 | *
17 | * @function
18 | *
19 | * @param {Object} options
20 | *
21 | * @param {String} options.list
22 | * The list name or ID
23 | *
24 | * @param {String} [options.web=__current_web__]
25 | *
26 | * @param {String} [options.select=""]
27 | *
28 | * @param {String} [options.filter=""]
29 | *
30 | * @param {String} [options.orderBy=""]
31 | *
32 | * @param {String} [options.expand=""]
33 | *
34 | * @param {Boolean} [options.ListItemsCollection=ListItemsCollection]
35 | *
36 | * @param {Boolean} [options.ListItemModel=ListItemModel]
37 | * The model to be used for each row retrieved. Model constructor must
38 | * support a .create() method.
39 | *
40 | * @return {Promise}
41 | * Promise is resolved with a Collection, or rejected with an Error object
42 | *
43 | * @example
44 | *
45 | * getListItems({list: "tasks"})
46 | */
47 | export function getListItems(options) {
48 | const opt = objectExtend({}, getListItems.defaults, options);
49 |
50 | return getContextInfo(opt.web)
51 | .then(contextInfo => {
52 | let requestUrl = `${ contextInfo.WebFullUrl }/_api/web/lists${
53 | IS_GUID_RE.test(opt.list) ?
54 | `(guid'${opt.list.replace(/[{}]/g, "")}')` :
55 | `/getbytitle('${encodeURIComponent(opt.list)}')`
56 | }/items?`;
57 |
58 | // FIXME: should encodeURIComponent() be used for below options?
59 |
60 | if (opt.filter) {
61 | requestUrl+= `&$filter=${opt.filter}`;
62 | }
63 |
64 | if (opt.select) {
65 | requestUrl+= `&$select=${opt.select}`;
66 | }
67 |
68 | if (opt.orderBy) {
69 | requestUrl+= `&$orderby=${opt.orderBy}`;
70 | }
71 |
72 | if (opt.expand) {
73 | requestUrl+= `&$expand=${opt.expand}`;
74 | }
75 |
76 | opt.requestUrl = requestUrl;
77 | opt.isREST = true;
78 |
79 | return apiFetch(requestUrl, {
80 | method: "GET",
81 | headers: getRestHeaders(contextInfo)
82 | })
83 | .then(fetchResponse => {
84 | return ListItemsCollection.create( // FIXME: convert to Class
85 | fetchResponse.content.value.map(item => {
86 | processResults(item);
87 | return new opt.ListItemModel(item, opt);
88 | })
89 | )
90 | });
91 | });
92 | }
93 | export default getListItems;
94 |
95 | /**
96 | * Default options for `getListItems` REST method
97 | *
98 | * @type {{list: string, web: string, select: string, filter: string, expand: string, orderBy: string, ListItemsCollection, ListItemModel}}
99 | */
100 | getListItems.defaults = {
101 | list: "",
102 | web: "",
103 | select: "",
104 | filter: "",
105 | expand: "",
106 | orderBy: "",
107 | ListItemsCollection,
108 | ListItemModel
109 | };
110 |
--------------------------------------------------------------------------------
/src/spapi/rest/getWebUrlFromPageUrl.js:
--------------------------------------------------------------------------------
1 | /* global _spPageContextInfo */
2 | import Promise from "common-micro-libs/src/jsutils/es6-promise"
3 | import getFullUrl from "../../sputils/getFullUrl"
4 | import { getRestHeaders } from "../../sputils/restUtils"
5 | import cache from "../../sputils/cache"
6 | import apiFetch from "../../sputils/apiFetch"
7 |
8 | /**
9 | * Returns the Site Web url for a given page url.
10 | * Url returned will end with a forward slash (`/`).
11 | * Example:
12 | *
13 | * https://yourtenant.sharepoint.com/sites/A/
14 | *
15 | * @param {String} [pageUrl=location.href]
16 | * current serving page will be used if left empty
17 | *
18 | * @return {Promise}
19 | */
20 | export default function getWebUrlFromPageUrl(pageUrl) {
21 | let isThisPage = false;
22 |
23 | if (!pageUrl) {
24 | pageUrl = location.href;
25 | isThisPage = true;
26 | }
27 |
28 | // Get only the pure url up to the page... no URL params or hash.
29 | if (pageUrl.indexOf("?") > -1) {
30 | pageUrl = pageUrl.substr(0, pageUrl.indexOf("?"));
31 |
32 | }
33 | else if (pageUrl.indexOf("#") > -1) {
34 | pageUrl = pageUrl.substr(0, pageUrl.indexOf("#"));
35 | }
36 |
37 | pageUrl = getFullUrl(pageUrl);
38 | const cacheKey = `getWebUrlFromPageUrl():${ pageUrl }`.toLowerCase();
39 |
40 | if (cache.get(cacheKey)) {
41 | return cache.get(cacheKey);
42 | }
43 |
44 | let siteUrl = "";
45 |
46 | // DO we have _spPageContextInfo to work with? Then use it to locate the web URL in
47 | // one of several params. We'll then use that to query SP or resolve this request if
48 | // the current page URL is being used.
49 | if (typeof _spPageContextInfo !== "undefined") {
50 | ["webAbsoluteUrl", "webServerRelativeUrl"].some(function(attr){
51 | if (_spPageContextInfo[attr]) {
52 | siteUrl = _spPageContextInfo[attr];
53 | return true;
54 | }
55 | });
56 | }
57 |
58 | // If it is the current page, then try to determine the siteUrl
59 | // based on variables set by SharePoint
60 | if (isThisPage && siteUrl) {
61 | siteUrl = getFullUrl(siteUrl);
62 | cache.set(cacheKey, Promise.resolve(siteUrl));
63 | return cache.get(cacheKey);
64 | }
65 |
66 | // Last resolve - make an API call
67 | const apiRequest = apiFetch(
68 | `${ getFullUrl(`${ siteUrl }/_api`) }sp.web.getweburlfrompageurl(@v)?@v='${ encodeURIComponent(pageUrl) }'`,
69 | {
70 | method: "GET",
71 | headers: getRestHeaders()
72 | }
73 | )
74 | .then(response => getFullUrl(response.content.value));
75 |
76 | cache.set(cacheKey, apiRequest);
77 | apiRequest.catch(e => {
78 | cache.clear(cacheKey);
79 | console.log(e); // eslint-disable-line
80 | });
81 |
82 | return apiRequest;
83 | }
84 |
--------------------------------------------------------------------------------
/src/spapi/rest/updateListItems.js:
--------------------------------------------------------------------------------
1 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
2 | import getContextInfo from "./getContextInfo"
3 | import apiFetch from "../../sputils/apiFetch"
4 | import { getRestHeaders } from "../../sputils/restUtils"
5 | import ListItemModel from "../../models/ListItemModel"
6 | import {IS_GUID_RE} from "../../sputils/constants";
7 |
8 |
9 | //==================================================================
10 | const encodeURIComponent = window.encodeURIComponent;
11 |
12 | ////// FIXME: support Array of updates (bulk)
13 | ////// FIXME: support updates defined as as string (pass it to `body` of request as is)
14 |
15 |
16 | /**
17 | * Makes updates to list items
18 | *
19 | * @function
20 | *
21 | * @param {Object} options
22 | *
23 | * @param {String} options.list
24 | * The list name or ID
25 | *
26 | * @param {String|Object|Array} options.updates
27 | *
28 | * @param {String} [options.type="update"]
29 | * The type of update... Possible values are:
30 | * - `update` (`PATCH` will be used)
31 | * - `create` (`POST` will be used)
32 | *
33 | * @param {String} [options.web=__current_web__]
34 | *
35 | * @param {ListItemModel} [options.ListItemModel=ListItemModel]
36 | *
37 | * @return {Promise}
38 | *
39 | * @example
40 | *
41 | * FIXME: example here
42 | */
43 | export function updateListItems(options) {
44 | const opt = objectExtend({}, updateListItems.defaults, options);
45 |
46 | if (Array.isArray(opt.update)) {
47 | throw new Error("options.updates as an Array not yet supported!");
48 | }
49 |
50 | return getContextInfo(opt.web)
51 | .then(contextInfo => {
52 | const isCreate = opt.type.toLowerCase() === "create";
53 |
54 | let requestUrl = `${ contextInfo.WebFullUrl }/_api/web/lists${
55 | IS_GUID_RE.test(opt.list) ?
56 | `(guid'${opt.list.replace(/[{}]/g, "")}')` :
57 | `/getbytitle('${encodeURIComponent(opt.list)}')`
58 | }/items`;
59 |
60 | if (!isCreate) {
61 | requestUrl += `(${opt.updates.ID})`
62 | }
63 |
64 | requestUrl += "?";
65 |
66 | // FIXME: should encodeURIComponent() be used for below options?
67 |
68 | if (opt.filter) {
69 | requestUrl+= `&$filter=${opt.filter}`;
70 | }
71 |
72 | if (opt.select) {
73 | requestUrl+= `&$select=${opt.select}`;
74 | }
75 |
76 | if (opt.orderBy) {
77 | requestUrl+= `&$orderby=${opt.orderBy}`;
78 | }
79 |
80 | if (opt.expand) {
81 | requestUrl+= `&$expand=${opt.expand}`;
82 | }
83 |
84 | opt.requestUrl = requestUrl;
85 | opt.isREST = true;
86 |
87 | const headers = getRestHeaders(contextInfo);
88 |
89 | if (!isCreate) {
90 | headers["X-HTTP-Method"] = "MERGE"; // FIXME: these should be input options for greater flexibility
91 | headers["If-Match"] = "*";
92 | }
93 |
94 | return apiFetch(requestUrl, {
95 | method: "POST",
96 | headers,
97 | body: JSON.stringify(opt.updates)
98 | }).then(fetchResponse => {
99 | return new opt.ListItemModel(fetchResponse.content, opt);
100 | });
101 | });
102 | }
103 | export default updateListItems;
104 |
105 |
106 |
107 | /**
108 | * Default options for `updateListItems` REST method
109 | *
110 | * @type {{list: string, web: string, select: string, filter: string, expand: string, orderBy: string, ListItemsCollection, ListItemModel}}
111 | */
112 | updateListItems.defaults = {
113 | list: "",
114 | web: "",
115 | type: "update",
116 | updates: null,
117 | ListItemModel
118 | };
119 |
--------------------------------------------------------------------------------
/src/sputils/apiFetch.js:
--------------------------------------------------------------------------------
1 | import fetchPolyfill from "common-micro-libs/src/jsutils/es7-fetch"
2 | import parseXML from "common-micro-libs/src/jsutils/parseXML"
3 | import Promise from "common-micro-libs/src/jsutils/es6-promise"
4 | import doesMsgHaveError from "./doesMsgHaveError"
5 | import getMsgError from "./getMsgError"
6 |
7 | var fetch = fetchPolyfill.fetch;
8 |
9 | /**
10 | * Handles API calls to SharePoint using the low level ES7 fetch() api,
11 | * thus is has the same input signature. Response will be processed for
12 | * Sharepoint Status errors and then data parsed, returning instead an
13 | * object.
14 | *
15 | * @param {String|Request} input
16 | * @param {Object} init
17 | *
18 | * @return {Promise}
19 | * Promise is resolved with an object containing the following:
20 | *
21 | * {
22 | * content: {}, // XMLDocument
23 | * msgType: 'xml', // String
24 | * response: response // A Response object
25 | * }
26 | *
27 | */
28 | var apiFetch = function(input, init){
29 | return fetch(input, init)
30 | .then(parseApiResponse)
31 | .then(checkForSharePointErrors)
32 | .then(checkForHttpErrors);
33 | },
34 |
35 | /**
36 | * Checks the HTTP resposne to see if there was an HTTP error.
37 | *
38 | * @private
39 | *
40 | * @param response
41 | *
42 | * @returns {*}
43 | */
44 | checkForHttpErrors = function(response) {
45 | var res = response.status ? response : response.response ? response.response : {};
46 |
47 | // If server returned an error code, then reject promise
48 | if (res.status >= 200 && res.status < 300) {
49 | return response;
50 |
51 | } else {
52 | var error = new Error(`HTTP ${ res.status }: ${ res.statusText } (${ res.url })`);
53 | error.response = response;
54 | return Promise.reject(error);
55 | }
56 | },
57 |
58 | /**
59 | * Parses the API response into either XML or JSON
60 | *
61 | * @private
62 | *
63 | * @param response
64 | * @returns {*}
65 | */
66 | parseApiResponse = function(response){
67 | // If the message return is JSON, then parse that.
68 | if (response.headers.map["content-type"].join("").toLowerCase().indexOf("application/json") !== -1) {
69 | return response.json().then(content => ({
70 | content,
71 | msgType: "json",
72 | response: response
73 | }));
74 | }
75 |
76 | // Get the response text and then parse it.
77 | return response.text().then(function(responseString){
78 | /**
79 | * A sharepoint API response
80 | *
81 | * @typedef {Object} ApiFetchResponse
82 | *
83 | * @property {Document} content
84 | * @property {String} msgType
85 | * Valid value: `xml`
86 | * @property {Object} response
87 | * API fetch response
88 | */
89 | return {
90 | content: parseXML(responseString),
91 | msgType: responseString ? "xml" : "", // responseString could be empty - example: HTTP 403
92 | response: response
93 | };
94 | });
95 | },
96 |
97 | /**
98 | * Checks the API response for any SharePoint processing errors.
99 | *
100 | * @private
101 | *
102 | * @param {Object} response
103 | *
104 | * @returns {*}
105 | */
106 | checkForSharePointErrors = function(response){
107 | if (response.msgType === "xml"){
108 | if (doesMsgHaveError(response.content)) {
109 | var error = new Error(getMsgError(response.content));
110 | error.response = response;
111 | return Promise.reject(error);
112 | }
113 | }
114 |
115 | return response;
116 | };
117 |
118 | export default apiFetch;
119 |
120 |
--------------------------------------------------------------------------------
/src/sputils/cache.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Simple caching function.
3 | * @function
4 | *
5 | * @param {Sting} key
6 | * @param {Object} value
7 | *
8 | * @return {undefined}
9 | *
10 | * Methods:
11 | *
12 | * cache("myKey") // getter. Same as cache.get()
13 | * cache("myKey", "value") // Setter. Same as cache.set();
14 | * cache.clear(key)
15 | * cache.clearAll()
16 | * cache.get(key),
17 | * cache.set(key, value),
18 | * cache.isCached(key)
19 | *
20 | * Dependencies:
21 | *
22 | * none
23 | *
24 | */
25 | var cache = (function(){
26 |
27 | var cacheData = {},
28 | fnCaller = function cache(key, value){
29 |
30 | if (!key) {
31 |
32 | return;
33 |
34 | }
35 |
36 | // Getter
37 | if (typeof value === "undefined"){
38 |
39 | return fnCaller.get(key);
40 |
41 | }
42 |
43 | // Setter
44 | return fnCaller.set(key, value);
45 |
46 | };
47 |
48 | /**
49 | * Clear specific key from cache.
50 | * @function cache.clear
51 | * @param {String} key
52 | */
53 | fnCaller.clear = function(key){
54 |
55 | delete cacheData[key];
56 |
57 | };
58 | /**
59 | * Clears all cached data
60 | * @fucntion cache.clearAll
61 | */
62 | fnCaller.clearAll = function(){
63 |
64 | cacheData = {};
65 |
66 | };
67 | /**
68 | * Gets a cached piece of data
69 | * @function cache.get
70 | * @param {String} key
71 | */
72 | fnCaller.get = function(key) {
73 |
74 | return cacheData[key];
75 |
76 | };
77 | /**
78 | * Caches a piece of data.
79 | * @function cache.set
80 | * @param {String} key
81 | * @param {*} value
82 | */
83 | fnCaller.set = function(key, value) {
84 |
85 | cacheData[key] = value;
86 | return value;
87 |
88 | };
89 | /**
90 | * Returns a boolean indicating if the give key has cached data.
91 | * @function cache.isCached
92 | * @param {String} key
93 | * @return {Boolean}
94 | */
95 | fnCaller.isCached = function(key){
96 |
97 | if (cacheData.hasOwnProperty(key)) {
98 |
99 | return true;
100 |
101 | }
102 |
103 | return false;
104 | };
105 |
106 | return fnCaller;
107 |
108 | })(); //end: cache method.
109 |
110 | export default cache;
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/sputils/constants.js:
--------------------------------------------------------------------------------
1 | import {objectKeys} from "common-micro-libs/src/jsutils/runtime-aliases"
2 | /**
3 | * The typical REST API headers for JSON data structures. Includes `odata=verbose`
4 | *
5 | * @type {{"Content-Type": string, Accept: string}}
6 | */
7 | export const REST_HEADERS = {
8 | "Content-Type": "application/json;odata=verbose",
9 | "Accept": "application/json;odata=verbose"
10 | };
11 |
12 | /**
13 | * The typical REST API headers, but with `odata=nometadata` - ideal for working
14 | * with Create/Update operation and not have to define the item type
15 | * @type {{"Content-Type": string, Accept: string}}
16 | */
17 | export const REST_HEADERS_NO_METADATA = Object.assign({}, REST_HEADERS);
18 | objectKeys(REST_HEADERS_NO_METADATA)
19 | .forEach(key => REST_HEADERS_NO_METADATA[key] = REST_HEADERS_NO_METADATA[key].replace("verbose", "nometadata"));
20 |
21 |
22 | /**
23 | * RegExp to validate GUID's
24 | *
25 | * @type {RegExp}
26 | */
27 | export const IS_GUID_RE = /^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/;
28 |
--------------------------------------------------------------------------------
/src/sputils/doesMsgHaveError.js:
--------------------------------------------------------------------------------
1 | import domFind from "common-micro-libs/src/domutils/domFind";
2 |
3 | /**
4 | * Checks if an xml message has an error. Taken from
5 | * SPWidgets.
6 | *
7 | * @param {XMLDocument} xmlMsg
8 | *
9 | * @return {Boolean}
10 | */
11 | export default function(xmlMsg) {
12 |
13 | // BACKWARD COMPATIBILITY
14 | // if xmlMsg seems to be a jQuery object, then get it native element
15 | if (xmlMsg.jquery) {
16 | xmlMsg = xmlMsg[0];
17 | }
18 |
19 | // if xmlDocument does not support querySelector, throw error
20 | if (!xmlMsg.querySelector) {
21 | throw new Error("input is not an XML Document!");
22 | }
23 |
24 | var spErrCode = domFind(xmlMsg, "ErrorCode"),
25 | response = false;
26 |
27 | // If we don't have elements, then check other stuff
28 | // that sharepoint can return in error conditions
29 | if (!spErrCode.length) {
30 | // Any "fauldcode" nodes?
31 | if (domFind(xmlMsg, "faultcode").length) {
32 | return true;
33 | }
34 |
35 | // Any CopyResult nodes with ErrorMessage
36 | if (domFind(xmlMsg, "CopyResult[ErrorMessage]").length){
37 | return true;
38 | }
39 |
40 | return response;
41 | }
42 |
43 | spErrCode.some(function(errorCodeEle){
44 | var errorCodeString = errorCodeEle.textContent;
45 | if (errorCodeString !== "0x00000000" && errorCodeString !== "NoError" ) {
46 | response = true;
47 | return true;
48 | }
49 | });
50 |
51 | return response;
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/src/sputils/fillTemplate.js:
--------------------------------------------------------------------------------
1 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate";
2 | export default fillTemplate;
3 |
--------------------------------------------------------------------------------
/src/sputils/getDateString.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | /**
4 | * Returns a date string in the format expected by Sharepoint
5 | * Date/time fields. Usefull in doing filtering queries.
6 | *
7 | * Credit: Matt (twitter @iOnline247)
8 | * {@see http://spservices.codeplex.com/discussions/349356}
9 | *
10 | * @param {Date} [dateObj=Date()]
11 | * @param {String} [formatType='local']
12 | * Possible formats: local, utc
13 | *
14 | * @return {String} a date string.
15 | *
16 | */
17 | var getDateString = function( dateObj, formatType ) {
18 |
19 | formatType = String(formatType || "local").toLowerCase();
20 | dateObj = dateObj || new Date();
21 |
22 | function pad( n ) {
23 |
24 | return n < 10 ? "0" + n : n;
25 |
26 | }
27 |
28 | var ret = "";
29 |
30 | if (formatType === "utc") {
31 |
32 | ret = dateObj.getUTCFullYear() + "-" +
33 | pad( dateObj.getUTCMonth() + 1 ) + "-" +
34 | pad( dateObj.getUTCDate() ) + "T" +
35 | pad( dateObj.getUTCHours() ) + ":" +
36 | pad( dateObj.getUTCMinutes() )+ ":" +
37 | pad( dateObj.getUTCSeconds() )+ "Z";
38 |
39 | } else {
40 |
41 | ret = dateObj.getFullYear() + "-" +
42 | pad( dateObj.getMonth() + 1 ) + "-" +
43 | pad( dateObj.getDate() ) + "T" +
44 | pad( dateObj.getHours() ) + ":" +
45 | pad( dateObj.getMinutes() )+ ":" +
46 | pad( dateObj.getSeconds() );
47 |
48 | }
49 |
50 | return ret;
51 |
52 | }; //end: SPGetDateString()
53 |
54 | export default getDateString;
55 |
56 |
--------------------------------------------------------------------------------
/src/sputils/getFullUrl.js:
--------------------------------------------------------------------------------
1 | const DOCUMENT_LOCATION = document.location;
2 |
3 | /**
4 | * Returns the full URL (starting with `http...` for a given page address
5 | *
6 | * @param {String} pageAddress
7 | * @param {Boolean} [noEndSlash=false]
8 | * By default, the returned url will be ensured to end with a `/`. set this
9 | * param to `true` to not append this character if needed.
10 | *
11 | * @returns {string}
12 | */
13 | export default function getFullUrl(pageAddress, noEndSlash) {
14 |
15 | // if URL does not end with "/" then insert it
16 | if (pageAddress && !noEndSlash && pageAddress.charAt(pageAddress.length - 1) !== "/") {
17 | pageAddress += "/";
18 | }
19 |
20 | if (pageAddress.toLowerCase().indexOf("http") > -1) {
21 | return pageAddress;
22 | }
23 |
24 | pageAddress = DOCUMENT_LOCATION.protocol + "//" +
25 | DOCUMENT_LOCATION.hostname +
26 | ( Number(DOCUMENT_LOCATION.port) !== 80 &&
27 | Number(DOCUMENT_LOCATION.port) > 0 ?
28 | ":" + DOCUMENT_LOCATION.port :
29 | ""
30 | ) +
31 | pageAddress;
32 |
33 | return pageAddress;
34 | }
35 |
--------------------------------------------------------------------------------
/src/sputils/getMsgError.js:
--------------------------------------------------------------------------------
1 | import domFind from "common-micro-libs/src/domutils/domFind";
2 | import parseXML from "common-micro-libs/src/jsutils/parseXML";
3 |
4 | /**
5 | * Given a sharepoint webservices response, this method will
6 | * look to see if it contains an error and return that error
7 | * formated as a string.
8 | *
9 | * @param {XMLDocument|String} xmlMsg
10 | *
11 | * @return {String} errorMessage
12 | *
13 | */
14 | export default function getMsgError(xmlMsg){
15 |
16 | if (typeof xmlMsg === "string") {
17 | xmlMsg = parseXML(xmlMsg);
18 |
19 | // Backwards compatible
20 | // if xmlMsg is a jquery object, get the native ele
21 | } else if (xmlMsg && xmlMsg.jquery) {
22 | xmlMsg = xmlMsg[0];
23 | }
24 |
25 | // if xmlDocument does not support querySelector, throw error
26 | if (!xmlMsg.querySelector) {
27 | throw new Error("input is not an XML Document!");
28 | }
29 |
30 | var error = "",
31 | spErr = domFind(xmlMsg, "ErrorCode"),
32 | count = 0;
33 |
34 | if (!spErr.length) {
35 | spErr = domFind(xmlMsg, "faultcode");
36 | }
37 |
38 | // See if any Elements with ErrorMessage attribute
39 | if (!spErr.length) {
40 | spErr = domFind(xmlMsg, "CopyResult[ErrorMessage]");
41 |
42 | if (spErr.length) {
43 | spErr.forEach(function(thisErr){
44 | count += 1;
45 | error += "(" + count + ") " +
46 | (thisErr.getAttribute("ErrorCode") || "unknown") +
47 | ": " +
48 | thisErr.getAttribute("ErrorMessage") + "\n";
49 | });
50 | return count + " error(s) encountered! \n" + error;
51 | }
52 | }
53 |
54 | if (!spErr.length) {
55 | return "";
56 | }
57 |
58 | // Loop through and get all errors.
59 | spErr.forEach(function(thisErr){
60 | var textContent = thisErr.textContent;
61 | if ( textContent !== "0x00000000" ) {
62 | count += 1;
63 | error += "(" + count + ") " + textContent + ": " +
64 | domFind(thisErr.parentNode, "*")
65 | .filter(function(ele){
66 | return ele !== thisErr;
67 | })
68 | .reduce(function(text, ele){
69 | return text + " " + ele.textContent;
70 | }, "") +
71 | "\n";
72 | }
73 | });
74 |
75 | error = count + " error(s) encountered! \n" + error;
76 | return error;
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/src/sputils/getSPVersion.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | /* global SP, _spPageContextInfo */
4 |
5 | /**
6 | * Returns the SharePoint version number. This is accomplished by
7 | * looking for the SP namespace and if it is define, parsing the
8 | * SP.ClientSchemeversions value.
9 | *
10 | * @param {Boolean} returnExternal
11 | * If true, then the external version (ex. 2007, 2010) is
12 | * returned. Default is to return the internal version number
13 | * (ex. 12, 14)
14 | *
15 | * @return {String}
16 | *
17 | */
18 | var getSPVersion = function getSPVersion(returnExternal) {
19 |
20 | // Some approaches below taken from:
21 | // http://sharepoint.stackexchange.com/questions/74978/can-i-tell-what-version-of-sharepoint-is-being-used-from-javascript
22 |
23 | var versionMap = {
24 | 12: "2007",
25 | 14: "2010",
26 | 15: "2013"
27 | },
28 | version = 12,
29 | foundIt = false;
30 |
31 | // If the SP variable is defined, then its at least SP2010
32 | if (typeof SP !== "undefined") {
33 |
34 | version = 14;
35 |
36 | if (SP.ClientSchemaVersions) {
37 |
38 | if (SP.ClientSchemaVersions.currentVersion) {
39 |
40 | version = parseInt(SP.ClientSchemaVersions.currentVersion);
41 | foundIt = true;
42 |
43 | }
44 |
45 | }
46 |
47 | if (!foundIt && (typeof _spPageContextInfo !== "undefined")) {
48 |
49 | version = parseInt(_spPageContextInfo.webUIVersion);
50 |
51 | if (version === 4) {
52 |
53 | version = 14;
54 |
55 | }
56 |
57 | }
58 |
59 | }
60 |
61 | // TODO: implement method detailed by Jeremy Thake: http://www.jeremythake.com/2013/08/get-sharepoint-version-number-of-your-platform-quickly/
62 | // Queries: /_vti_pvt/service.cnf ... Works in SP2010 / 2013, 2007 as well.
63 | // OUTPUT:
64 | // vti_encoding:SR|utf8-nl
65 | // ti_extenderversion:SR|16.0.0.1216
66 |
67 | if (returnExternal) {
68 |
69 | version = versionMap[version] || version;
70 |
71 | }
72 |
73 | return version;
74 |
75 | }; //end: getSPVersion();
76 |
77 | export default getSPVersion;
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/sputils/parseDateString.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parses a date string in ISO 8601 format into a Date object.
3 | * Date format supported on input:
4 | * 2013-09-01T01:00:00
5 | * 2013-09-01T01:00:00Z
6 | * 2013-09-01T01:00:00Z+05:00
7 | *
8 | * @param {String} dateString
9 | * The date string to be parsed.
10 | *
11 | * @return {Date|Null}
12 | * If unable to parse string, a Null value will be returned.
13 | *
14 | * @see {https://github.com/csnover/js-iso8601}
15 | * Method was developed using some of the code from js-iso8601
16 | * project on github by csnover.
17 | *
18 | */
19 | var parseDateString = function parseDateString(dateString) {
20 |
21 | var dtObj = null,
22 | re, dtPieces, i, j, numericKeys, minOffset;
23 |
24 | if (!dateString) {
25 |
26 | return dtObj;
27 |
28 | }
29 |
30 | // let's see if Date.parse() can do it?
31 | // We append 'T00:00' to the date string case it is
32 | // only in format YYYY-MM-DD
33 | dtObj = Date.parse(
34 | dateString.length === 10 ?
35 | (dateString + "T00:00") :
36 | dateString
37 | );
38 |
39 | if (dtObj) {
40 |
41 | return new Date(dtObj);
42 |
43 | }
44 |
45 | // Once we parse the date string, these locations
46 | // in the array must be Numbers.
47 | numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
48 |
49 | // Define regEx
50 | re = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/; // eslint-disable-line
51 |
52 | // dtPieces:
53 | // [0]
54 | // [1] YYYY
55 | // [2] MM
56 | // [3] DD
57 | // [4] HH
58 | // [5] mm
59 | // [6] ss
60 | // [7] msec
61 | // [8] Z
62 | // [9] +|-
63 | // [10] Z HH
64 | // [11] Z mm
65 | dtPieces = dateString.match(re);
66 |
67 |
68 | if( !dtPieces ){
69 |
70 | return dtObj;
71 |
72 | }
73 |
74 | for(i=0,j=numericKeys.length; i}
10 | * Array of objects. Each object has two keys; `Title` and `ID`
11 | *
12 | * @example
13 | *
14 | * parseLookupFieldValue("1;#item one title;#2;#item two title");
15 | * // Returns:
16 | * [
17 | * {
18 | * ID: "1",
19 | * Title: "item one title"
20 | * },
21 | * {
22 | * ID: "2",
23 | * Title: "item two title"
24 | * }
25 | * ]
26 | */
27 | const parseLookupFieldValue = function(lookupValue) {
28 | var response = [],
29 | valueTokens = String(lookupValue).split(";#"),
30 | total = valueTokens.length,
31 | i, vId, vTitle;
32 |
33 | if (!lookupValue) {
34 | return response;
35 | }
36 |
37 | for (i=0; i}
16 | */
17 | const parsePeopleField = function(peopleString, PersonModel) {
18 | PersonModel = PersonModel || parsePeopleField.defaults.PersonModel;
19 |
20 | return parseLookupFieldValue(String(peopleString || "")).map(function(person){
21 | var personInfo = {
22 | ID: person.id || "",
23 | Name: person.title || ""
24 | };
25 |
26 | // If the Name field seems to have data that is returned when you
27 | // expand the field during the API call, then parse that now into
28 | // individual attributes... See for more info. on these attributes:
29 | // http://msdn.microsoft.com/en-us/library/cc264031%28v=office.14%29.aspx
30 | // O365 seems to return some additional values from what is documented.
31 | // Example of expanded values in an array (from o365):
32 | // [
33 | // "First Last",
34 | // "i:0#.f|membership|somename@domain.com",
35 | // "someName@domain.com",
36 | // "",
37 | // "First Last",
38 | // "https://someDomain-my.sharepoint.com:443/User%20Photos/.....jpg", // Not using it now
39 | // "", // Not using it now
40 | // "" // Not using it now
41 | // ]
42 | if (personInfo.Name.indexOf(",#") > -1) {
43 | let additionalAttributes = [
44 | "Name",
45 | "AccountName",
46 | "Email",
47 | "SIP",
48 | "DisplayName"
49 | //,
50 | //"UserPhoto" // not adding it to the info object because this normally points to the "my site" which requires additional login
51 | ];
52 |
53 | personInfo.Name.split(/,#/g).forEach(function(expandedValue, index){
54 | if (additionalAttributes[index]) {
55 | personInfo[additionalAttributes[index]] = String(expandedValue || "").replace(/,,/g, ",")
56 | }
57 | });
58 | }
59 |
60 | // Create the model and populate with the attr. from above.
61 | return PersonModel.create(personInfo);
62 | });
63 | };
64 |
65 | /**
66 | * Defaults for the function
67 | *
68 | * @name parsePeopleField.defaults
69 | * @type {Object}
70 | */
71 | parsePeopleField.defaults = {
72 | PersonModel: UserProfileModel
73 | };
74 |
75 | export default parsePeopleField;
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/sputils/restUtils.js:
--------------------------------------------------------------------------------
1 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
2 | import {objectDefineProperty} from "common-micro-libs/src/jsutils/runtime-aliases"
3 | import { REST_HEADERS, REST_HEADERS_NO_METADATA } from "./constants"
4 | import apiFetch from "./apiFetch";
5 | import getContextInfo from "../spapi/rest/getContextInfo";
6 |
7 | //===========================================================
8 |
9 | /**
10 | * Returns an object with the standard JSON headers for a SharePoint REST call.
11 | *
12 | * @param {ContextWebInformation} [contextInfo]
13 | * @param {Boolean} [oDataVerbose=false]
14 | * If set to `true`, then the REST headers that indicate `odata=verbose` will be used.
15 | * Default is to use `odata=nometadata`.
16 | *
17 | * @return {Object}
18 | */
19 | export function getRestHeaders (contextInfo, oDataVerbose = false) {
20 | return objectExtend(
21 | {},
22 | oDataVerbose ? REST_HEADERS : REST_HEADERS_NO_METADATA,
23 | contextInfo ? {"X-RequestDigest": contextInfo.FormDigestValue} : null
24 | );
25 | }
26 |
27 |
28 | /**
29 | * Given a user info object returnd by SP REST, this method will augument that data and
30 | * return back a UserProfileModel instance.
31 | *
32 | * @param {Object} userInfo
33 | *
34 | * @returns {UserProfileModel}
35 | */
36 | export function processUserInfo (userInfo) {
37 | if (userInfo.Title && !userInfo.FirstName) {
38 | [ userInfo.FirstName, userInfo.LastName ] = userInfo.Title.split(" ");
39 | }
40 | return userInfo;
41 | }
42 |
43 | /**
44 | * Processes REST API response results. Does:
45 | *
46 | * - Added a `.load()` method to any property value that has a `__deferred` object
47 | *
48 | * @param {Array|Object} results
49 | */
50 | export function processResults(results) {
51 | results = Array.isArray(results) ? results : [results];
52 | for (let x=0, t=results.length; x < t; x++) {
53 | if (results[x]){
54 | for (let attrName in results[x]) {
55 | if (results[x].hasOwnProperty(attrName) && results[x][attrName] && results[x][attrName].__deferred) {
56 | objectDefineProperty(
57 | results[x][attrName],
58 | "load",
59 | { configurable: true, writable: true, value: fetchDeferredUri }
60 | );
61 | }
62 | }
63 | }
64 | }
65 | }
66 |
67 |
68 | function fetchDeferredUri () {
69 | return getContextInfo(this.__deferred.uri.substr(0, this.__deferred.uri.indexOf("_api")))
70 | .then(contextInfo => {
71 | return apiFetch(this.__deferred.uri, {
72 | method: "GET",
73 | headers: getRestHeaders(contextInfo, true)
74 | });
75 | });
76 | }
77 |
--------------------------------------------------------------------------------
/src/sputils/xmlEscape.js:
--------------------------------------------------------------------------------
1 | import xmlEscape from "common-micro-libs/src/jsutils/xmlEscape";
2 | // FIXME: remove references to this file and replace with 'common-micro-libs/src/jsutils/xmlEscape'
3 | export default xmlEscape;
4 |
5 |
--------------------------------------------------------------------------------
/src/widgets/BooleanField/BooleanField.html:
--------------------------------------------------------------------------------
1 |
2 | {{opt.column.DisplayName}}
3 |
4 |
5 | {{opt.labels.no}}
6 | {{opt.labels.yes}}
7 |
8 | {{opt.column.description}}
9 |
10 |
--------------------------------------------------------------------------------
/src/widgets/BooleanField/BooleanField.less:
--------------------------------------------------------------------------------
1 | .spwidgets-BooleanField {
2 | position: relative;
3 |
4 | &-displayName {
5 | display: block;
6 | }
7 |
8 | //=================================
9 | // modifiers
10 | //=================================
11 | &--noLabel &-displayName {
12 | display: none;
13 | }
14 |
15 | &--noDescription .ms-Toggle-description {
16 | display: none;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/widgets/ChoiceField/ChoiceField.html:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/widgets/ChoiceField/ChoiceField.less:
--------------------------------------------------------------------------------
1 | .spwidgets-ChoiceField {
2 |
3 | &-choices {
4 | padding: 0.5em;
5 | overflow: auto;
6 | border: 1px solid #eaeaea;
7 | min-height: 5em;
8 |
9 | &::-webkit-scrollbar {
10 | width: 0.5em;
11 | background-color: #F5F5F5;
12 | }
13 |
14 | &::-webkit-scrollbar-thumb {
15 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
16 | background-color : #555;
17 | }
18 | }
19 |
20 | //---------------------------
21 | // NO MODIFIERS
22 | //---------------------------
23 | &--noLabel .ms-ChoiceFieldGroup-title {
24 | display: none;
25 | }
26 |
27 | &--noDescription &-description {
28 | display: none;
29 | }
30 |
31 | &--inline &-choices > * {
32 | display: inline-block;
33 | margin-right: 1em;
34 | padding-right: 0.5em;
35 |
36 | &:last-child {
37 | margin-right: 0;
38 | }
39 | }
40 |
41 | &--noSelectedCount &-selectedCount {
42 | display: none;
43 | }
44 | }
--------------------------------------------------------------------------------
/src/widgets/ChoiceField/ChoiceItem/ChoiceItem.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/widgets/ChoiceField/ChoiceItem/ChoiceItem.less:
--------------------------------------------------------------------------------
1 | .spwidgets-ChoiceField-ChoiceItem {
2 | padding-left: 0.5em;
3 | padding-right: 0.5em;
4 |
5 | &-label {
6 | display: block;
7 | }
8 | }
--------------------------------------------------------------------------------
/src/widgets/ContentTypeField/ContentTypeField.js:
--------------------------------------------------------------------------------
1 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
2 | import Deferred from "common-micro-libs/src/jsutils/Deferred"
3 | import Promise from "common-micro-libs/src/jsutils/es6-promise"
4 | import ChoiceField from "../ChoiceField/ChoiceField"
5 | import getListContentTypes from "../../spapi/getListContentTypes"
6 |
7 | //=================================================================
8 | const PRIVATE = dataStore.create();
9 | const ChoiceFieldPrototype = ChoiceField.prototype;
10 |
11 | /**
12 | * Widget picker for List content types. Note that the `value` of the selected
13 | * options will be the Content Type internal ID, which in order to be used
14 | * when a query, one would have to use the `ContentTypeId` field of the list.
15 | *
16 | * @class ContentTypeField
17 | * @extends ChoiceField
18 | */
19 | export default ChoiceField.extend({
20 | init(options) {
21 | if (options && !options.choiceList) {
22 | options.choiceList = [];
23 | }
24 | ChoiceFieldPrototype.init.call(this, options);
25 | const deferred = Deferred.create();
26 | const state = {
27 | onReady: deferred.promise.then(() => this)
28 | };
29 | PRIVATE.set(this, state);
30 |
31 | ChoiceFieldPrototype.onReady.call(this)
32 | .then(() => getListContentTypes({listName: options.listName, webURL: options.webURL}))
33 | .then(contentTypes => {
34 | contentTypes.forEach(contentType => {
35 | contentType.title = contentType.Name;
36 | contentType.value = contentType.ID;
37 | });
38 | state.onReady = Promise.resolve(this);
39 | this.setChoices.call(this, contentTypes);
40 | deferred.resolve();
41 | });
42 |
43 | this.onDestroy(() => PRIVATE.delete(state));
44 | },
45 |
46 | onReady() {
47 | return PRIVATE.get(this).onReady;
48 | },
49 |
50 | setSelected(...vals) {
51 | return this.onReady().then(() => ChoiceFieldPrototype.setSelected.call(this, ...vals));
52 | }
53 | });
54 |
--------------------------------------------------------------------------------
/src/widgets/DateTimeField/DateTimeField.html:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/src/widgets/DateTimeField/DateTimeField.less:
--------------------------------------------------------------------------------
1 | .spwidgets-DateTimeField {
2 | position: relative;
3 | box-sizing: border-box;
4 |
5 | &-inputHolder {
6 | position: relative;
7 | }
8 |
9 | &-calIcon,
10 | &-clearIcon {
11 | position: absolute;
12 | top: 0;
13 | right: 54px;
14 | border: 1px solid #c8c8c8;
15 | min-width: 32px;
16 | }
17 |
18 | &-clearIcon {
19 | right: 0;
20 | }
21 |
22 | &--noLabel .ms-Label {
23 | display: none;
24 | }
25 | &--noDescription .ms-TextField-description {
26 | display: none;
27 | }
28 | &--inlinePicker &-calIcon {
29 | display: none;
30 | }
31 | }
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/ColumnSelector/ColumnSelector.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/ColumnSelector/ColumnSelector.less:
--------------------------------------------------------------------------------
1 | .spwidgets-FilterPanel-ColumnSelector {
2 |
3 | @class-name-ms-icon-checkmark: ms-Icon--CheckMark;
4 |
5 | //-----------------------------------------------------------
6 |
7 | box-sizing: border-box;
8 | position: absolute;
9 | top: 0;
10 | bottom: 0;
11 | left: 0;
12 | right: 0;
13 | //width: 100%;
14 | //height: 100%;
15 | border-style: solid;
16 | border-width: 1px;
17 |
18 | &-header {
19 | padding: 0.5em;
20 | border-width: 1px;
21 | border-style: solid;
22 | }
23 |
24 | &-body {
25 | box-sizing: border-box;
26 | position: absolute;
27 | height: 100%;
28 | width: 100%;
29 | top: 0;
30 | left: 0;
31 | padding: 3em 0.5em 3.5em;
32 |
33 | &-content {
34 | box-sizing: border-box;
35 | height: 100%;
36 | overflow: auto;
37 | }
38 | }
39 |
40 | &-footer {
41 | box-sizing: border-box;
42 | position: absolute;
43 | bottom: 0;
44 | left: 0;
45 | width: 100%;
46 | padding: 0.5em;
47 | text-align: right;
48 | }
49 |
50 | &-col {
51 | box-sizing: border-box;
52 | display: inline-block;
53 | width: ~"calc((100% / 3) - 1em)";
54 | cursor: pointer;
55 | margin: 0.5em 0.5em 0em 0em;
56 | position: relative;
57 |
58 | &:last-child {
59 | margin-right: 0;
60 | }
61 |
62 | &-icon,
63 | &-title {
64 | padding: 0.5em;
65 | box-sizing: border-box;
66 | }
67 |
68 | &-icon {
69 | position: absolute;
70 | left: 0;
71 | top: 0;
72 | height: 100%;
73 | width: 2.5em;
74 | padding: 0.5em;
75 | text-align: center;
76 |
77 | .@{class-name-ms-icon-checkmark} {
78 | display: none;
79 | }
80 | }
81 |
82 | &-title {
83 | width: 100%;
84 | padding-left: 3em;
85 | text-overflow: ellipsis;
86 | overflow: hidden;
87 | white-space: nowrap;
88 | }
89 |
90 | // MODIFIER
91 | &--selected {
92 | color: #0078d7; // same as ms-Link
93 | }
94 | &--selected &-icon .@{class-name-ms-icon-checkmark} {
95 | display: inline-block;
96 | }
97 | }
98 |
99 |
100 | //---------------------
101 | // MODIFIERS
102 | //---------------------
103 | &--1-col &-col {
104 | width: ~"calc(100% - 1em)";
105 | }
106 | &--2-col &-col {
107 | width: ~"calc(50% - 1em)";
108 | }
109 | }
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/ColumnSelector/column.html:
--------------------------------------------------------------------------------
1 |
2 | {{DisplayName}}
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumn/FilterColumn.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumn/FilterColumn.less:
--------------------------------------------------------------------------------
1 | .spwidgets-FilterPanel-FilterColumn {
2 | box-sizing: border-box;
3 | padding: 0.5em 2.5em 1em 0.5em;
4 | border-bottom-width: 1px;
5 | border-bottom-style: solid;
6 | position: relative;
7 |
8 | &:last-child,
9 | &:only-child {
10 | margin-bottom: 0.5em;
11 | }
12 |
13 | &:last-child {
14 | border-bottom: none;
15 | }
16 |
17 | &:only-child &-move {
18 | display: none;
19 | }
20 |
21 | &:hover {
22 | background-color: #F4F4F4;
23 | transition: background-color 0.5s 0s;
24 | }
25 |
26 | &-title {
27 | margin: 0;
28 | }
29 | &-title > * {
30 | vertical-align: middle;
31 | }
32 |
33 | &-dirtyIndicator {
34 | color: #C8C8C8;
35 | font-size: 8px;
36 | }
37 |
38 | &-options {
39 | font-size: 0.8em;
40 | opacity: 0.7;
41 | display: none;
42 | padding-top: 0.5em;
43 | text-align: right;
44 |
45 | &:hover {
46 | opacity: 1;
47 | }
48 | }
49 |
50 | &-input {
51 | margin-top: 0.5em;
52 | }
53 |
54 | &-info {
55 | position: relative;
56 | min-height: 1.2em;
57 |
58 | &-keywords {
59 | width: 70%;
60 | }
61 |
62 | &-optLink {
63 | position: absolute;
64 | right: 0;
65 | top: 0;
66 | }
67 | }
68 |
69 | &-move {
70 | position: absolute;
71 | top: 0;
72 | right: 0;
73 | height: 100%;
74 | width: 1.5em;
75 | visibility: hidden;
76 |
77 | > a {
78 | display: block;
79 | height: 49.8%;
80 | box-sizing: border-box;
81 | padding-top: 70%;
82 | text-align: center;
83 | font-size: 1.3em;
84 | cursor: pointer;
85 |
86 | &:hover {
87 | background-color: #EAEAEA;
88 | }
89 | }
90 |
91 | }
92 | &:hover &-move {
93 | visibility: visible;
94 | }
95 |
96 |
97 | // Choice fields: make Choices holder white
98 | .spwidgets-ChoiceField-choices {
99 | background-color: white;
100 | }
101 |
102 | //------------------------------
103 | // MODIFIERS
104 | //------------------------------
105 |
106 | // ==> show the options for column
107 | &--showOptions &-options {
108 | display: block;
109 | }
110 | &--showOptions &-info-optLink {
111 | color: #A6A6A6;
112 | }
113 |
114 | // ==> No Options Link
115 | &--noOptionsToggle &-info-optLink {
116 | display: none;
117 | }
118 |
119 |
120 | // ==> Hide the input
121 | &--hideInput &-input-holder {
122 | display: none;
123 | }
124 | &--hideInput &-info {
125 | display: none;
126 | }
127 |
128 |
129 | // ==> Column is dirty (something was changed/entered)
130 | &--isDirty &-dirtyIndicator {
131 | color: #107C10;
132 | }
133 | }
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnAttachmentsField/FilterColumnAttachmentsField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn"
2 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
4 | import ChoiceField from "../../ChoiceField/ChoiceField"
5 |
6 |
7 | //====================================================================
8 | const PRIVATE = dataStore.stash;
9 |
10 | /**
11 | * Filter column allowing the user to filter items based on whether
12 | * they have attachments or not.
13 | *
14 | * @class FilterColumnAttachmentsField
15 | * @extends FilterColumn
16 | *
17 | * @param {Object} options
18 | */
19 | let FilterColumnAttachmentsField = /** @lends FilterColumnAttachmentsField.prototype */{
20 | init: function (options) {
21 | FilterColumn.prototype.init.call(this,
22 | objectExtend({}, this.getFactory().defaults, options)
23 | );
24 |
25 | var
26 | inst = PRIVATE.get(this),
27 | opt = inst.opt,
28 | labels = opt.labels,
29 | attachmentsField = inst.inputWdg = ChoiceField.create({
30 | layout: "inline",
31 | hideLabel: true,
32 | column: opt.column,
33 | labels: labels
34 | });
35 |
36 | attachmentsField.setChoices([labels.any, labels.yes, labels.no]);
37 |
38 | attachmentsField.on("change", function(){
39 | this.evalDirtyState();
40 | }.bind(this));
41 |
42 | inst.inputWdg.setValue(options.selected || "");
43 | inst.inputWdg.appendTo(inst.inputHolder);
44 |
45 | this.setCompareOperatorDefault("Eq");
46 | this.setKeywordInfo(opt.labels.attachmentsInfo);
47 | }
48 | };
49 |
50 | FilterColumnAttachmentsField = FilterColumn.extend(FilterColumnAttachmentsField);
51 | FilterColumnAttachmentsField.defaults = {
52 | layout: "inline",
53 | selected: "",
54 | labels: {
55 | any: "Any",
56 | yes: "Yes",
57 | no: "No"
58 | }
59 | };
60 |
61 | export default FilterColumnAttachmentsField;
62 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnBooleanField/FilterColumnBooleanField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn";
2 | import BooleanField from "../../BooleanField/BooleanField";
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
4 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
5 |
6 | const PRIVATE = dataStore.stash;
7 |
8 | /**
9 | * A text field column for filter panel
10 | *
11 | * @class FilterColumnBooleanField
12 | * @extends FilterColumn
13 | *
14 | * @param {Object} options
15 | * In addition to the options required/supported by
16 | * [FilterColumn]{@link FilterColumn}, this Widget supports this additional
17 | * set documented below
18 | *
19 | */
20 | const FilterColumnBooleanField = FilterColumn.extend(/** @lends FilterColumnBooleanField.prototype */{
21 | init: function (options) {
22 | FilterColumn.prototype.init.call(this,
23 | objectExtend({}, this.getFactory().defaults, options)
24 | );
25 |
26 | const inst = PRIVATE.get(this);
27 | const opt = inst.opt;
28 | const inputWdg = inst.inputWdg = BooleanField.create({
29 | column: opt.column,
30 | value: opt.value,
31 | hideLabel: true,
32 | hideDescription: true
33 | });
34 |
35 | inputWdg.on("change", () => this.evalDirtyState());
36 | inputWdg.appendTo(inst.inputHolder);
37 | this.removeCompareOperators("Contains");
38 | this.setCompareOperatorDefault("Eq");
39 | this.setKeywordInfo("");
40 | this.evalDirtyState();
41 | },
42 |
43 | appendTo(ele) {
44 | FilterColumn.prototype.appendTo.call(this, ele);
45 | this.evalDirtyState();
46 | }
47 | });
48 |
49 |
50 | export default FilterColumnBooleanField;
51 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnChoiceField/FilterColumnChoiceField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn";
2 | import ChoiceField from "../../ChoiceField/ChoiceField";
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
4 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
5 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate";
6 |
7 | var
8 | PRIVATE = dataStore.stash,
9 |
10 | /**
11 | * Widget description
12 | *
13 | * @class FilterColumnChoiceField
14 | * @extends FilterColumn
15 | *
16 | * @param {Object} options
17 | */
18 | FilterColumnChoiceField = /** @lends FilterColumnChoiceField.prototype */{
19 | init: function (options) {
20 | FilterColumn.prototype.init.call(this,
21 | objectExtend({}, this.getFactory().defaults, options)
22 | );
23 |
24 | var
25 | inst = PRIVATE.get(this),
26 | opt = inst.opt,
27 | column = opt.column,
28 | choice;
29 |
30 | // Change the type temporarily so that the widget is
31 | // created with Checkboxes
32 | choice = inst.inputWdg = ChoiceField.create(
33 | objectExtend(
34 | {
35 | column: column,
36 | hideLabel: true,
37 | isMulti: true
38 | },
39 | options
40 | )
41 | );
42 |
43 | choice.appendTo(inst.inputHolder);
44 | choice.on("change", function(){
45 | this.evalDirtyState();
46 |
47 | var totalSelected = choice.getValue().length;
48 |
49 | if (totalSelected) {
50 | this.setKeywordInfo(fillTemplate(opt.labels.totalSelected, {total: totalSelected}));
51 |
52 | } else {
53 | this.setKeywordInfo("");
54 | }
55 | }.bind(this));
56 |
57 | if (column.Type === "Choice") {
58 | this.setCompareOperatorDefault("Eq");
59 | }
60 |
61 | this.setKeywordInfo("");
62 | },
63 |
64 | setFilter: function(filter){
65 | var inst = PRIVATE.get(this);
66 | inst.setFieldCommonFilters.call(this, filter);
67 | return inst.inputWdg.setSelected(filter.values).then(() => this.evalDirtyState());
68 | }
69 | };
70 |
71 | FilterColumnChoiceField = FilterColumn.extend(FilterColumnChoiceField);
72 | FilterColumnChoiceField.defaults = {};
73 |
74 | export default FilterColumnChoiceField;
75 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnContentTypeField/FilterColumnContentTypeField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn";
2 | import ContentTypeField from "../../ContentTypeField/ContentTypeField";
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
4 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
5 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate";
6 |
7 | const PRIVATE = dataStore.stash;
8 |
9 |
10 | /**
11 | * Widget description
12 | *
13 | * @class FilterColumnContentTypeField
14 | * @extends FilterColumn
15 | *
16 | * @param {Object} options
17 | */
18 | const FilterColumnContentTypeField = FilterColumn.extend(/** @lends FilterColumnContentTypeField.prototype */{
19 | init: function (options) {
20 | FilterColumn.prototype.init.call(this,
21 | objectExtend({}, this.getFactory().defaults, options)
22 | );
23 |
24 | const inst = PRIVATE.get(this);
25 | const opt = inst.opt;
26 | const column = opt.column;
27 |
28 | // Change the type temporarily so that the widget is
29 | // created with Checkboxes
30 | const choice = inst.inputWdg = ContentTypeField
31 | .extend({
32 | setChoices(choices) {
33 | choices = choices || [];
34 | return ContentTypeField.prototype.setChoices.call(this, choices.map(choice => {
35 | choice.value = choice.title;
36 | return choice;
37 | }));
38 | }
39 | }).create(
40 | objectExtend(
41 | {
42 | column: column,
43 | hideLabel: true,
44 | isMulti: true
45 | },
46 | options
47 | )
48 | );
49 |
50 | choice.appendTo(inst.inputHolder);
51 | choice.on("change", function(){
52 | this.evalDirtyState();
53 |
54 | const totalSelected = choice.getValue().length;
55 |
56 | if (totalSelected) {
57 | this.setKeywordInfo(fillTemplate(opt.labels.totalSelected, {total: totalSelected}));
58 |
59 | } else {
60 | this.setKeywordInfo("");
61 | }
62 | }.bind(this));
63 |
64 | this.removeCompareOperators("Contains");
65 | this.setCompareOperatorDefault("Eq");
66 | this.setKeywordInfo("");
67 | },
68 |
69 | setFilter: function(filter){
70 | const inst = PRIVATE.get(this);
71 | inst.setFieldCommonFilters.call(this, filter);
72 | return inst.inputWdg.setSelected(filter.values).then(() => this.evalDirtyState());
73 | }
74 | });
75 |
76 | // FilterColumnContentTypeField.defaults = {};
77 |
78 | export default FilterColumnContentTypeField;
79 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnDateTimeField/FilterColumnDateTimeField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn";
2 | import DateTimeField from "../../DateTimeField/DateTimeField";
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
4 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
5 | import Promise from "common-micro-libs/src/jsutils/es6-promise";
6 |
7 | //-------------------------------------------------------------------
8 | const PRIVATE = dataStore.stash;
9 | const FilterColumnPrototype = FilterColumn.prototype;
10 |
11 |
12 | /**
13 | * Filter Panel date/time field
14 | *
15 | * @class FilterColumnDateTimeField
16 | * @extends FilterColumn
17 | *
18 | * @param {Object} options
19 | */
20 | const FilterColumnDateTimeField = FilterColumn.extend(/** @lends FilterColumnDateTimeField.prototype */{
21 | init: function (options) {
22 | FilterColumnPrototype.init.call(this,
23 | objectExtend({}, this.getFactory().defaults, options)
24 | );
25 |
26 | var
27 | inst = PRIVATE.get(this),
28 | opt = inst.opt,
29 | inputWdg = inst.inputWdg = DateTimeField.create({
30 | column: opt.column,
31 | hideLabel: true,
32 | hideDescription: true,
33 | allowMultiples: true
34 | });
35 |
36 | inputWdg.on("change", this.evalDirtyState.bind(this));
37 | inputWdg.appendTo(inst.inputHolder);
38 | inputWdg.pipe(this, "DateTimeField");
39 |
40 | this.setKeywordInfo("");
41 | this.removeCompareOperators("Contains");
42 | this.addCompareOperators([
43 | {title: opt.labels.after, value: "Gt"},
44 | {title: opt.labels.before, value: "Lt"}
45 | ]);
46 | this.setCompareOperatorDefault("Eq");
47 | },
48 |
49 | getValue: function(){
50 | let dateValue = PRIVATE.get(this).inputWdg.getValue();
51 | if (!dateValue || !dateValue.dateObj) {
52 | return [];
53 | }
54 | return [ dateValue.dateObj.toISOString() ];
55 | },
56 |
57 | setFilter: function(filter){
58 | let inst = PRIVATE.get(this);
59 | let values = filter.values;
60 |
61 | inst.setFieldCommonFilters(filter);
62 | inst.inputWdg.setValue(Array.isArray(values) ? values[0] : values);
63 | this.evalDirtyState();
64 | return Promise.resolve();
65 | }
66 | });
67 |
68 | FilterColumnDateTimeField.defaults = {};
69 |
70 | export default FilterColumnDateTimeField;
71 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnLookupField/FilterColumnLookupField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn";
2 | import LookupField from "../../LookupField/LookupField";
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
4 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
5 |
6 | var
7 | PRIVATE = dataStore.stash,
8 |
9 | /**
10 | * Filter panel lookup field
11 | *
12 | * @class FilterColumnLookupField
13 | * @extends FilterColumn
14 | *
15 | * @param {Object} options
16 | */
17 | FilterColumnLookupField = /** @lends FilterColumnLookupField.prototype */{
18 | init: function (options) {
19 | FilterColumn.prototype.init.call(this,
20 | objectExtend({}, this.getFactory().defaults, options)
21 | );
22 |
23 | var
24 | inst = PRIVATE.get(this),
25 | opt = inst.opt,
26 | inputWdg = inst.inputWdg = LookupField.create({
27 | column: opt.column,
28 | hideLabel: true,
29 | hideDescription: true,
30 | allowMultiples: true,
31 | choicesZIndex: opt.zIndex
32 | });
33 |
34 | inputWdg.on("item:selected", this.evalDirtyState.bind(this));
35 | inputWdg.on("item:unselected", this.evalDirtyState.bind(this));
36 | inputWdg.appendTo(inst.inputHolder);
37 | inputWdg.pipe(this, "LookupField:");
38 |
39 | this.setKeywordInfo("");
40 | },
41 |
42 | getValue: function(){
43 | return PRIVATE.get(this).inputWdg.getSelected();
44 | },
45 |
46 | setFilter: function(filter){
47 | var inst = PRIVATE.get(this);
48 | inst.setFieldCommonFilters(filter);
49 | return inst.inputWdg.setSelected(filter.values);
50 | }
51 | };
52 |
53 | FilterColumnLookupField = FilterColumn.extend(FilterColumnLookupField);
54 | FilterColumnLookupField.defaults = {};
55 |
56 | export default FilterColumnLookupField;
57 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnNumberField/FilterColumnNumberField.js:
--------------------------------------------------------------------------------
1 | import FilterColumnTextField from "../FilterColumnTextField/FilterColumnTextField";
2 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
4 |
5 | const PRIVATE = dataStore.stash;
6 | const FilterColumnTextFieldPrototype = FilterColumnTextField.prototype;
7 | const toPrct = value => String(Number(value) * 100);
8 | const toDecimal = value => String(Number(value) / 100);
9 |
10 | /**
11 | * Widget description
12 | *
13 | * @class FilterColumnNumberField
14 | * @extends FilterColumnTextField
15 | *
16 | * @param {Object} options
17 | */
18 | const FilterColumnNumberField = FilterColumnTextField.extend(/** @lends FilterColumnNumberField.prototype */{
19 | init: function (options) {
20 | if (options && options.value) {
21 | options.value = toPrct(options.value);
22 | }
23 |
24 | FilterColumnTextFieldPrototype.init.call(this,
25 | objectExtend({}, this.getFactory().defaults, options)
26 | );
27 |
28 | const labels = PRIVATE.get(this).opt.labels;
29 |
30 | this.addCompareOperators([
31 | {
32 | value: "Gt",
33 | title: labels.greaterThan
34 | },
35 | {
36 | value: "Lt",
37 | title: labels.lessThan
38 | }
39 | ]);
40 |
41 | this.setCompareOperatorDefault("Eq");
42 | },
43 |
44 | isPercent() {
45 | return !!(PRIVATE.get(this).opt.column || {}).Percentage;
46 | },
47 |
48 | setFilter(filter) {
49 | if (this.isPercent()){
50 | if (filter && filter.values) {
51 | filter.values.forEach((decimalValue, i) => filter.values[i] = toPrct(decimalValue));
52 | }
53 | }
54 | FilterColumnTextFieldPrototype.setFilter.call(this, filter);
55 | },
56 |
57 | getValue() {
58 | const response = FilterColumnTextFieldPrototype.getValue.call(this);
59 | return this.isPercent() ? toDecimal(response) : response;
60 | }
61 | });
62 |
63 | FilterColumnNumberField.defaults = {};
64 |
65 | export default FilterColumnNumberField;
66 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnTextField/FilterColumnTextField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn";
2 | import TextField from "../../TextField/TextField";
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
4 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
5 |
6 | var
7 | PRIVATE = dataStore.stash,
8 |
9 | /**
10 | * A text field column for filter panel
11 | *
12 | * @class FilterColumnTextField
13 | * @extends FilterColumn
14 | *
15 | * @param {Object} options
16 | * In addition to the options required/supported by
17 | * [FilterColumn]{@link FilterColumn}, this Widget supports this additional
18 | * set documented below
19 | *
20 | * // FIXME document options
21 | */
22 | FilterColumnTextField = /** @lends FilterColumnTextField.prototype */{
23 | init: function (options) {
24 | FilterColumn.prototype.init.call(this,
25 | objectExtend({}, this.getFactory().defaults, options)
26 | );
27 |
28 | var inst = PRIVATE.get(this),
29 | opt = inst.opt;
30 |
31 | inst.inputWdg = TextField.create({
32 | column: opt.column,
33 | hideLabel: true,
34 | hideDescription: true,
35 | placeholder: opt.inputKeywords
36 | });
37 |
38 | inst.inputWdg.on("change", function() {
39 | this.evalDirtyState();
40 | }.bind(this));
41 |
42 | inst.inputWdg.appendTo(inst.inputHolder);
43 | },
44 |
45 | getKeywords: function(){
46 | var
47 | opt = PRIVATE.get(this).opt,
48 | delimiter = opt.delimeter || ";",
49 | reIgnore = opt.ignoreKeywords;
50 |
51 | return this.getValue()
52 | .split(delimiter)
53 | .map(function(keyword){
54 | return keyword.trim();
55 | })
56 | .filter(function(keyword){
57 | return (keyword && !reIgnore.test(keyword));
58 | });
59 | }
60 | };
61 |
62 | FilterColumnTextField = FilterColumn.extend(FilterColumnTextField);
63 | FilterColumnTextField.defaults = {
64 | delimiter: ";",
65 | ignoreKeywords: new RegExp()
66 | };
67 |
68 | export default FilterColumnTextField;
69 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterColumnUserField/FilterColumnUserField.js:
--------------------------------------------------------------------------------
1 | import FilterColumn from "../FilterColumn/FilterColumn";
2 | import PeoplePicker from "../../PeoplePicker/PeoplePicker";
3 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
4 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
5 |
6 | //============================================================================
7 | const PRIVATE = dataStore.stash;
8 |
9 | /**
10 | * Filter Panel User field.
11 | *
12 | * @class FilterColumnUserField
13 | * @extends FilterColumn
14 | *
15 | * @param {Object} options
16 | */
17 | const FilterColumnUserField = FilterColumn.extend(/** @lends FilterColumnUserField.prototype */{
18 | init: function (options) {
19 | const opt = objectExtend({}, this.getFactory().defaults, options);
20 | FilterColumn.prototype.init.call(this, opt);
21 |
22 | const inst = PRIVATE.get(this);
23 | const userSelectionMode = opt.column ? opt.column.UserSelectionMode : null;
24 | const peoplePicker = inst.inputWdg = opt.PeoplePickerWidget.create({
25 | resultsZIndex: inst.opt.zIndex,
26 | type: userSelectionMode && (userSelectionMode === "PeopleOnly" || String(userSelectionMode) === "0") ? "User" : "All"
27 | });
28 |
29 | ["remove", "select"].forEach(function(evName){
30 | peoplePicker.on(evName, this.evalDirtyState.bind(this));
31 | }.bind(this));
32 |
33 | peoplePicker.appendTo(inst.inputHolder);
34 | this.setKeywordInfo("");
35 | },
36 |
37 | getValue: function(){
38 | return PRIVATE.get(this).inputWdg.getSelected();
39 | },
40 |
41 | setFilter: function(filter){
42 | var inst = PRIVATE.get(this);
43 |
44 | inst.setFieldCommonFilters.call(this, filter);
45 |
46 | return inst.inputWdg
47 | .add(filter.values)
48 | .then(function(){
49 | this.evalDirtyState();
50 | }.bind(this));
51 | }
52 | });
53 |
54 | FilterColumnUserField.defaults = {
55 | PeoplePickerWidget: PeoplePicker
56 | };
57 |
58 | export default FilterColumnUserField;
59 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterPanel.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/FilterPanel/FilterPanel.less:
--------------------------------------------------------------------------------
1 | .spwidgets-FilterPanel {
2 |
3 | .border-1px-solid(@prop: border) {
4 | @{prop}-width: 1px;
5 | @{prop}-style: solid;
6 | }
7 |
8 | position: relative;
9 | box-sizing: border-box;
10 |
11 | button {
12 | min-width: 0;
13 | }
14 |
15 | &-header {
16 | padding: 0.5em;
17 | position: relative;
18 |
19 | &-title {
20 | display: block;
21 | padding-right: 4em;
22 | }
23 |
24 | &-close {
25 | display: block;
26 | position: absolute;
27 | top: 0;
28 | right: 0;
29 | height: 100%;
30 | border: none;
31 |
32 | .ms-Button-icon {
33 | display: inline-block;
34 | }
35 | }
36 | }
37 |
38 | &-main {
39 | box-sizing: border-box;
40 | position: relative;
41 | .border-1px-solid()
42 | }
43 |
44 | &-body {
45 | box-sizing: border-box;
46 | min-height: 15em;
47 | }
48 |
49 | &-footer {
50 | box-sizing: border-box;
51 | position: relative;
52 | padding-top: 20px;
53 |
54 | &-actions {
55 | padding: 0.5em;
56 | text-align: right;
57 | .border-1px-solid(border-top);
58 | }
59 | }
60 |
61 | // ==> No Header
62 | &--noHeader &-header {
63 | display: none;
64 | }
65 |
66 | // ==> Fixed height filter panel
67 | &--fixHeight &-body {
68 | overflow-y: auto;
69 | }
70 |
71 | // ==> No Find button
72 | &--noFindButton &-footer-action-find {
73 | display: none;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/widgets/ItemPicker/ItemPicker.js:
--------------------------------------------------------------------------------
1 | import Picker from "common-micro-libs/src/widgets/Picker/Picker"
2 | import domAddClass from "common-micro-libs/src/domutils/domAddClass"
3 |
4 | //==============================================================================
5 |
6 | /**
7 | * Same as Picker, but applies styles using UI Fabric
8 | *
9 | * @class ItemPicker
10 | * @extend Picker
11 | */
12 | export default Picker.extend({
13 | init: function (options) {
14 | Picker.prototype.init.call(this, options);
15 |
16 | var CSS_MS_FONT_M = "ms-font-m";
17 | var CSS_MS_ICON = "ms-Icon ms-Icon";
18 | var CSS_PICKER = "Picker";
19 | var $ui = this.getEle();
20 | var uiFind = $ui.querySelector.bind($ui);
21 | var $ele;
22 |
23 | domAddClass($ui, CSS_MS_FONT_M);
24 | domAddClass($ui, "ms-borderColor-neutralSecondary--hover");
25 | domAddClass(this.getPopupWidget().getEle(), CSS_MS_FONT_M);
26 |
27 | $ele = uiFind(`.${CSS_PICKER}-clear`);
28 | $ele.textContent = "";
29 | domAddClass($ele, `${CSS_MS_ICON}--ChromeClose`);
30 |
31 | $ele = uiFind(`.${CSS_PICKER}-showMenu`);
32 | $ele.textContent = "";
33 | domAddClass($ele, `${CSS_MS_ICON}--ChevronDownMed`);
34 | }
35 | });
36 |
--------------------------------------------------------------------------------
/src/widgets/List/List.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/ListItem/ListItem.less:
--------------------------------------------------------------------------------
1 | .spwidgets-ListItem {
2 | @MS_PRIMARY_TEXT: ms-ListItem-primaryText;
3 |
4 | &-simple {
5 | .@{MS_PRIMARY_TEXT} {
6 | padding-right: 0;
7 | }
8 | }
9 |
10 | &--hover {
11 | background-color: #eaeaea;
12 | cursor: pointer;
13 | outline: 1px solid transparent
14 | }
15 |
16 | &.is-selected .ms-ListItem-primaryText {
17 | color: #0078d7;
18 | }
19 | }
--------------------------------------------------------------------------------
/src/widgets/ListItem/ListItemFull.html:
--------------------------------------------------------------------------------
1 |
2 | {{_ui.primary}}
3 | {{_ui.secondary}}
4 | {{_ui.tertiary}}
5 | {{_ui.meta}}
6 |
7 |
8 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/src/widgets/ListItem/ListItemSimple.html:
--------------------------------------------------------------------------------
1 |
2 | {{_ui.primary}}
3 |
4 |
--------------------------------------------------------------------------------
/src/widgets/LookupField/LookupField.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/LookupField/LookupField.less:
--------------------------------------------------------------------------------
1 | .spwidgets-LookupField {
2 |
3 | .scrollBar() {
4 | &::-webkit-scrollbar {
5 | width: 0.5em;
6 | background-color: #F5F5F5;
7 | }
8 |
9 | &::-webkit-scrollbar-thumb {
10 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
11 | background-color : #555;
12 | }
13 | }
14 |
15 | background-color: white;
16 |
17 | &-items {
18 | border: 1px solid #c8c8c8;
19 | position: relative;
20 | box-sizing: border-box;
21 |
22 | &:hover {
23 | border-color: #767676;
24 | }
25 |
26 | &-count {
27 | display: inline-block;
28 | vertical-align: middle;
29 | padding: 0 1em;
30 | font-style: italic;
31 | }
32 |
33 | &-clear {
34 | vertical-align: middle;
35 | display: inline-block;
36 | cursor: pointer;
37 | }
38 | }
39 |
40 | &-selected {
41 | max-height: 15em;
42 | overflow-y: auto;
43 |
44 | .scrollBar()
45 | }
46 |
47 |
48 | &-input {
49 | position: relative;
50 | box-sizing: border-box;
51 |
52 | & > input {
53 | box-sizing: border-box;
54 | display: inline-block;
55 | border: 0;
56 | height: 38px;
57 | outline: none;
58 | padding-left: 8px;
59 | }
60 |
61 | &-choices {
62 | .scrollBar();
63 |
64 | box-sizing: border-box;
65 | display: none;
66 | position: absolute;
67 | top: 100%;
68 | left: 0;
69 | min-height: 2em;
70 | border: 1px solid #ababab;
71 | border-top: none;
72 | padding: 0.5em 1em;
73 | box-shadow: 0 0 5px 0 rgba(0,0,0,.4);
74 | background-color: white;
75 | z-index: 5;
76 | max-width: 100%;
77 | max-height: 20em;
78 | overflow-y: auto;
79 | }
80 | }
81 |
82 |
83 | //---------------------------
84 | // NO MODIFIERS
85 | //---------------------------
86 |
87 | //==> Hide label
88 | &--noLabel &-label {
89 | display: none;
90 | }
91 |
92 | //==> Hide description
93 | &--noDescription &-description {
94 | display: none;
95 | }
96 |
97 | //==> -displayInline
98 | &--displayInline &-selected,
99 | &--displayInline &-selected > *,
100 | &--displayInline &-input {
101 | display: inline-block;
102 | }
103 | &--displayInline &-selected {
104 | max-height: none;
105 | }
106 | &--displayInline &-input-choices {
107 | max-width: 450px;
108 | }
109 |
110 | }
--------------------------------------------------------------------------------
/src/widgets/LookupField/SelectedItem/SelectedItem.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/LookupField/SelectedItem/SelectedItem.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget";
2 | import EventEmitter from "common-micro-libs/src/jsutils/EventEmitter";
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore";
4 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend";
5 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate";
6 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML";
7 | import domAddEventListener from "common-micro-libs/src/domutils/domAddEventListener";
8 | import SelectedItemTemplate from "./SelectedItem.html";
9 | import "./SelectedItem.less";
10 |
11 | var
12 | PRIVATE = dataStore.create(),
13 |
14 | CSS_CLASS_REMOVE_BUTTON = "spwidgets-LookupField-SelectedItem-remove",
15 |
16 | /**
17 | * Displays a selected item from the LookupField list of choices.
18 | *
19 | * @class SelectedItem
20 | * @extends Widget
21 | * @extends EventEmitter
22 | *
23 | * @param {Object} options
24 | *
25 | * @fires SelectedItem#remove
26 | */
27 | SelectedItem = /** @lends SelectedItem.prototype */{
28 | init: function (options) {
29 | var inst = {
30 | opt: objectExtend({}, this.getFactory().defaults, options)
31 | };
32 |
33 | PRIVATE.set(this, inst);
34 |
35 | this.$ui = parseHTML(
36 | fillTemplate(this.getTemplate(), inst.opt)
37 | ).firstChild;
38 |
39 | inst.$removeBtn = this.$ui.querySelector("." + CSS_CLASS_REMOVE_BUTTON);
40 |
41 | domAddEventListener(inst.$removeBtn, "click", function(ev){
42 | ev.stopPropagation();
43 | /**
44 | * User clicked on the Remove button. The `item` object provided on input
45 | * will be given to listeners of this event.
46 | *
47 | * @event SelectedItem#remove
48 | * @type {Object}
49 | */
50 | this.emit("remove", inst.opt.item);
51 | }.bind(this));
52 |
53 | this.onDestroy(function () {
54 | PRIVATE.delete(this);
55 | }.bind(this));
56 | },
57 |
58 | /**
59 | * Returns the template for the widget.
60 | *
61 | * @return {String}
62 | */
63 | getTemplate: function(){
64 | return SelectedItemTemplate;
65 | },
66 |
67 | /**
68 | * Same as user clicking the remove button of the selected item
69 | */
70 | remove: function(){
71 | PRIVATE.get(this).$removeBtn.click();
72 | },
73 |
74 | /**
75 | * Returns the value for this selected item (default is the item passed on input)
76 | *
77 | * @return {Object}
78 | */
79 | getValue: function(){
80 | return PRIVATE.get(this).opt.item;
81 | }
82 | };
83 |
84 | SelectedItem = EventEmitter.extend(Widget, SelectedItem);
85 | SelectedItem.defaults = {
86 | item: null
87 | };
88 |
89 | export default SelectedItem;
90 |
--------------------------------------------------------------------------------
/src/widgets/LookupField/SelectedItem/SelectedItem.less:
--------------------------------------------------------------------------------
1 | .spwidgets-LookupField-SelectedItem {
2 | position: relative;
3 | margin: 4px;
4 | padding: 0;
5 | border: 1px solid transparent;
6 |
7 | &:hover {
8 | border-color: #C8C8C8;
9 | }
10 |
11 | &-info {
12 | padding-right: 34px;
13 | padding-left: 0.5em;
14 | background-color: #f4f4f4;
15 | min-height: 32px;
16 | line-height: 32px;
17 | white-space: nowrap;
18 | text-overflow: ellipsis;
19 | overflow: hidden;
20 | }
21 |
22 | &-remove {
23 | position: absolute;
24 | height: 100%;
25 | top: 0;
26 | right: 0;
27 | min-width: 0;
28 |
29 | .ms-Button-icon {
30 | display: inline-block;
31 | }
32 |
33 | &:hover {
34 | background-color: #eaeaea;
35 | color: #333;
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/widgets/Message/Message.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/Message/Message.less:
--------------------------------------------------------------------------------
1 | .spwidgets-message {
2 | position: relative;
3 | padding: 0.5em;
4 |
5 | &-iconHolder {
6 | display: block;
7 | position: absolute;
8 | font-size: 1.5em;
9 | line-height: 1em;
10 | }
11 |
12 | &-msgHolder {
13 | padding-left: 2em;
14 | min-height: 2em;
15 |
16 | &-msg > * {
17 | display: inline;
18 |
19 | }
20 |
21 | }
22 |
23 | &-msg {
24 | &-showMore {
25 | display: none;
26 | cursor: pointer;
27 | }
28 |
29 | &-more {
30 | display: none;
31 | white-space: pre-wrap;
32 | }
33 | }
34 |
35 | //--------------------------------
36 | // modifiers
37 | //--------------------------------
38 |
39 | &--hasMore &-msg-showMore {
40 | display: inline;
41 |
42 | }
43 |
44 | &--showMore &-msg-more {
45 | display: block;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/PeoplePicker.html:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/PeoplePicker.less:
--------------------------------------------------------------------------------
1 | .spwidgets-PeoplePicker {
2 |
3 | // Overrides needed due to sharepoint styles (in 2013)
4 | button {
5 | min-width: 0;
6 | padding: initial;
7 | margin-left: auto;
8 | font-size: initial;
9 | }
10 |
11 | &-searchFieldCntr {
12 | min-width: 180px;
13 |
14 | input {
15 | min-width: 100%;
16 | }
17 | }
18 |
19 | &-suggestions {
20 | min-width: 250px;
21 | position: absolute;
22 | z-index: 5;
23 |
24 | &-groups {
25 | padding: 1px; // needed to avoid unnecessary scroll bar
26 | overflow-y: auto;
27 | overflow-x: hidden;
28 | max-height: 20em;
29 |
30 | &::-webkit-scrollbar {
31 | width: 0.5em;
32 | background-color: #F5F5F5;
33 | }
34 |
35 | &::-webkit-scrollbar-thumb {
36 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
37 | background-color : #555;
38 | }
39 | }
40 | }
41 |
42 | &-searchInfo {
43 | border-top: 1px solid #eaeaea;
44 | padding: 1em;
45 |
46 | &-iconHolder {
47 | position: absolute;
48 | }
49 |
50 | > p {
51 | padding-left: 2em;
52 | margin: 0;
53 | }
54 |
55 | &-searchingMsg {
56 | display: none;
57 | }
58 | }
59 |
60 | &-busy {
61 | display: none;
62 | }
63 |
64 | // Override UI Fabric so that each persona card has
65 | // a max-width of 190px instead of fixed width
66 | .ms-Persona {
67 | &-details > * {
68 | width: auto;
69 | max-width: 190px;
70 | }
71 | }
72 |
73 |
74 |
75 | //-----------------------------------------
76 | // MODIFIERS
77 | //-----------------------------------------
78 | &-suggestions.is-searching .ms-Icon--Search {
79 | display: none;
80 | }
81 | &-suggestions.is-searching &-searchInfo-msg {
82 | display: none;
83 | }
84 |
85 | &-suggestions.is-searching &-searchInfo-searchingMsg {
86 | display: block;
87 | }
88 | &-suggestions.is-searching &-busy {
89 | display: inline-block;
90 | -webkit-animation: spwidgets-PeoplePicker-rotate 0.8s linear infinite;
91 | animation: spwidgets-PeoplePicker-rotate 0.8s linear infinite;
92 | }
93 |
94 |
95 | &--suggestionsRight &-suggestions {
96 | right: 0;
97 | }
98 |
99 | //---------------------------------------
100 | // ANIMATIONS
101 | //---------------------------------------
102 | @-webkit-keyframes spwidgets-PeoplePicker-rotate {
103 | from {
104 | -ms-transform: rotate(0deg);
105 | -moz-transform: rotate(0deg);
106 | -webkit-transform: rotate(0deg);
107 | -o-transform: rotate(0deg);
108 | transform: rotate(0deg);
109 | }
110 | to {
111 | -ms-transform: rotate(360deg);
112 | -moz-transform: rotate(360deg);
113 | -webkit-transform: rotate(360deg);
114 | -o-transform: rotate(360deg);
115 | transform: rotate(360deg);
116 | }
117 | }
118 | @keyframes spwidgets-PeoplePicker-rotate {
119 | from {
120 | -ms-transform: rotate(0deg);
121 | -moz-transform: rotate(0deg);
122 | -webkit-transform: rotate(0deg);
123 | -o-transform: rotate(0deg);
124 | transform: rotate(0deg);
125 | }
126 | to {
127 | -ms-transform: rotate(360deg);
128 | -moz-transform: rotate(360deg);
129 | -webkit-transform: rotate(360deg);
130 | -o-transform: rotate(360deg);
131 | transform: rotate(360deg);
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/PeoplePickerPersona/PeoplePickerPersona.html:
--------------------------------------------------------------------------------
1 |
2 | {{PersonaTemplateHtml}}
3 |
4 |
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/PeoplePickerPersona/PeoplePickerPersona.js:
--------------------------------------------------------------------------------
1 | import Persona from "../../Persona/Persona"
2 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
3 | import domAddClass from "common-micro-libs/src/domutils/domAddClass"
4 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
5 |
6 | import PeoplePickerPersonaTemplate from "./PeoplePickerPersona.html"
7 | import "./PeoplePickerPersona.less";
8 |
9 | //=========================================================================
10 |
11 | /**
12 | * A Selected Persona widget for the people picker, which includes
13 | * a button to remove the entry from the selected list.
14 | *
15 | * @class PeoplePickerPersona
16 | * @extends Persona
17 | *
18 | * @triggers PeoplePickerPersona#remove
19 | */
20 | let PeoplePickerPersona = /** @lends PeoplePickerPersona.prototype **/{
21 | init: function (options) {
22 | let opt = objectExtend({}, this.getFactory().defaults, options);
23 |
24 | Persona.prototype.init.call(this, opt);
25 |
26 | let evActionClickListener = this.on("action-click", () => {
27 | /**
28 | * User clicked the remove button on the people picker persona.
29 | *
30 | * @event PeoplePickerPersona#remove
31 | */
32 | this.emit("remove");
33 | });
34 |
35 | this.onDestroy(function(){
36 | evActionClickListener.off();
37 | });
38 |
39 | if (String(this.getUserProfile().ID).toLowerCase() === " ") {
40 | this.setAsCurrentUser();
41 | this.setPresence("noPresence");
42 | }
43 | },
44 |
45 | getTemplate: function(){
46 | return fillTemplate(
47 | PeoplePickerPersonaTemplate,
48 | { PersonaTemplateHtml: Persona.prototype.getTemplate.call(this) }
49 | );
50 | },
51 |
52 | /**
53 | * Used to highlight the persona that it is not a specific user, but
54 | * rather the pseudo entry that point to the currently logged in user.
55 | */
56 | setAsCurrentUser: function(){
57 | domAddClass(this.getEle(), "is-currentUserEntry");
58 | }
59 | };
60 |
61 | PeoplePickerPersona = Persona.extend(PeoplePickerPersona);
62 | PeoplePickerPersona.defaults = objectExtend({}, PeoplePickerPersona.defaults, {
63 | variant: "token",
64 | hideAction: false
65 | });
66 |
67 | export default PeoplePickerPersona;
68 |
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/PeoplePickerPersona/PeoplePickerPersona.less:
--------------------------------------------------------------------------------
1 | .spwidgets-PeoplePicker-persona {
2 |
3 | display: inline-block;
4 |
5 | &.is-currentUserEntry .ms-Persona-primaryText {
6 | color: #005A9E;
7 | font-style: italic;
8 | padding-right: 0.5em; // so that last letter is not truncated due to italics
9 | }
10 | }
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/PeoplePickerREST.js:
--------------------------------------------------------------------------------
1 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
3 | import PeoplePicker from "./PeoplePicker"
4 | import searchPeoplePicker from "../../spapi/rest/searchPeoplePicker"
5 | import ensureUser from "../../spapi/rest/ensureUser"
6 |
7 | // PeoplePicker widget based on SharePoint's REST api
8 | const PRIVATE = dataStore.create();
9 | const PeoplePickerREST = PeoplePicker.extend({});
10 |
11 | PeoplePickerREST.defaults = objectExtend({}, PeoplePickerREST.defaults, {
12 | apiSearch: searchPeoplePicker,
13 | UserProfileModel: PeoplePicker.defaults.UserProfileModel.extend({
14 | resolvePrincipal: function(){
15 | if (this.ID && this.ID !== "-1") {
16 | return Promise.resolve(this);
17 | }
18 |
19 | let inst;
20 |
21 | if (PRIVATE.has(this)) {
22 | inst = PRIVATE.get(this);
23 | } else {
24 | inst = { resolvePromise: null };
25 | PRIVATE.set(this, inst);
26 | }
27 |
28 | if (inst.resolvePromise) {
29 | return inst.resolvePromise;
30 | }
31 |
32 | inst.resolvePromise = ensureUser({
33 | webURL: this.webURL,
34 | logonName: this.AccountName || this.LoginName
35 | }).then(response => {
36 | this.ID = response.ID;
37 | return this;
38 | });
39 |
40 | return inst.resolvePromise
41 | }
42 | })
43 | });
44 |
45 | export default PeoplePickerREST;
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/PeoplePickerUserProfileModel.js:
--------------------------------------------------------------------------------
1 | import Promise from "common-micro-libs/src/jsutils/es6-promise"
2 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
3 |
4 | import UserProfileModel from "../../models/UserProfileModel";
5 | import resolvePrincipals from "../../spapi/resolvePrincipals"
6 |
7 |
8 | //=======================================================================
9 | const PRIVATE = dataStore.create();
10 |
11 | /**
12 | * People picker user profile model used to model each user profile
13 | *
14 | * @class PeoplePickerUserProfileModel
15 | * @extends UserProfileModel
16 | */
17 | let PeoplePickerUserProfileModel = UserProfileModel.extend(/** @lends PeoplePickerProfileModel.prototype */{
18 | /**
19 | * The web URL from where this user was retrieved. Used in resolved principal
20 | * @type {String}
21 | */
22 | webURL: "",
23 |
24 | /**
25 | * Returns the AccountName url encoded.
26 | *
27 | * @returns {string}
28 | */
29 | getAccountNameUrlEncoded: function(){
30 | return encodeURIComponent(this.AccountName);
31 | },
32 |
33 | /**
34 | * Resolves the person against the site (`webURL`) by calling
35 | * the `ResolvePrincipal` API. API is only called if `ID` is `-1`
36 | *
37 | * @returns {Promise}
38 | */
39 | resolvePrincipal: function(){
40 | if (this.ID && this.ID !== "-1") {
41 | return Promise.resolve(this);
42 | }
43 |
44 | let inst;
45 |
46 | if (PRIVATE.has(this)) {
47 | inst = PRIVATE.get(this);
48 | } else {
49 | inst = { resolvePromise: null };
50 | PRIVATE.set(this, inst);
51 | }
52 |
53 | if (inst.resolvePromise) {
54 | return inst.resolvePromise;
55 | }
56 |
57 | inst.resolvePromise = resolvePrincipals({
58 | webURL: this.webURL,
59 | principalKeys: this.AccountName
60 | }).then(function(userList){
61 | // See Issue #42 for the possibility of the results returning
62 | // multiples, even when only one principalKey was provided on
63 | // input to the API.
64 | // https://github.com/purtuga/SPWidgets/issues/42
65 | userList.some(function(resolvedUser){
66 | if (
67 | resolvedUser.ID &&
68 | String(resolvedUser.ID) !== "-1" &&
69 | (
70 | (
71 | resolvedUser.AccountName &&
72 | resolvedUser.AccountName === this.AccountName
73 | ) ||
74 | (
75 | resolvedUser.Email &&
76 | resolvedUser.Email === this.Email
77 | ) ||
78 | (
79 | resolvedUser.DisplayName &&
80 | resolvedUser.DisplayName === this.DisplayName
81 | )
82 |
83 | )
84 | ) {
85 | this.UserInfoID = this.ID = resolvedUser.ID;
86 | return true;
87 | }
88 |
89 | }.bind(this));
90 |
91 | return this;
92 | }.bind(this));
93 |
94 | return inst.resolvePromise
95 | }
96 | });
97 |
98 | export default PeoplePickerUserProfileModel;
99 |
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/Result/Result.html:
--------------------------------------------------------------------------------
1 |
2 | {{PersonaTemplateHtml}}
3 |
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/Result/Result.js:
--------------------------------------------------------------------------------
1 | import Persona from "../../Persona/Persona"
2 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
3 | import domHasClass from "common-micro-libs/src/domutils/domHasClass"
4 | import domAddClass from "common-micro-libs/src/domutils/domAddClass"
5 | import domRemoveClass from "common-micro-libs/src/domutils/domRemoveClass"
6 |
7 | import ResultTemplate from "./Result.html"
8 | import "./Result.less";
9 |
10 |
11 | //============================================================================
12 |
13 | const CSS_CLASS_MS_PICKER_RESULT = "spwidgets-PeoplePicker-Result";
14 | const CSS_CLASS_MS_PICKER_RESULT_FOCUS = CSS_CLASS_MS_PICKER_RESULT + "--focus";
15 |
16 | /**
17 | * A People picker suggestion result
18 | *
19 | * @class Result
20 | * @extends Persona
21 | *
22 | * @param {Object} options
23 | * @param {Object} options.userProfile
24 | */
25 | let Result = {
26 | init: function(){
27 | Persona.prototype.init.apply(this, arguments);
28 | if (this.getUserProfile().ID === " ") {
29 | this.setAsCurrentUser();
30 | }
31 | this.setSize("sm");
32 | },
33 | // Returns the People Picker Result wrapper with the persona template inside.
34 | getTemplate: function(){
35 | return fillTemplate(
36 | ResultTemplate,
37 | { PersonaTemplateHtml: Persona.prototype.getTemplate.call(this) }
38 | );
39 | },
40 |
41 | /**
42 | * Highlights he result items
43 | */
44 | setFocus: function(){
45 | domAddClass(this.getEle(), CSS_CLASS_MS_PICKER_RESULT_FOCUS);
46 | },
47 |
48 | /**
49 | * Removes the highlight of the item
50 | */
51 | removeFocus: function(){
52 | domRemoveClass(this.getEle(), CSS_CLASS_MS_PICKER_RESULT_FOCUS);
53 | },
54 |
55 | /**
56 | * Returns a boolean indicating if item is currently focused
57 | */
58 | hasFocus: function(){
59 | return domHasClass(this.getEle(), CSS_CLASS_MS_PICKER_RESULT_FOCUS);
60 | },
61 |
62 | /**
63 | * Used to highlight the persona that it is not a specific user, but
64 | * rather the pseudo entry that point to the currently logged in user.
65 | */
66 | setAsCurrentUser: function(){
67 | domAddClass(this.getEle(), "is-currentUserEntry");
68 | }
69 | };
70 |
71 | Result = Persona.extend(Result);
72 |
73 | export default Result;
74 |
75 |
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/Result/Result.less:
--------------------------------------------------------------------------------
1 | .spwidgets-PeoplePicker-Result {
2 | &.is-currentUserEntry .ms-Persona-primaryText {
3 | color: #005A9E;
4 | font-style: italic;
5 | }
6 |
7 | &--focus {
8 | background-color: #eaeaea;
9 | outline: 1px solid transparent;
10 | }
11 |
12 | //&--no-button &-resultAction {
13 | // display: none;
14 | //}
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/widgets/PeoplePicker/ResultGroup/ResultGroup.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/widgets/Persona/Persona.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/Persona/Persona.less:
--------------------------------------------------------------------------------
1 | .spwidgets-Persona {
2 |
3 | .ms-Image-image {
4 | width: 100%;
5 | }
6 |
7 | .ms-Persona-initials:empty:before {
8 | content: "?";
9 | }
10 |
11 | //--------------------------
12 | // MODIFIERS
13 | //--------------------------
14 | &--noDetails .ms-Persona-details {
15 | display: none;
16 | }
17 |
18 | &--noAction .ms-Persona-actionIcon {
19 | display: none;
20 | }
21 |
22 | // hide image if forced to initials
23 | &--showInitials .ms-Persona-image {
24 | display: none;
25 | }
26 |
27 | &.ms-Persona--nopresence .ms-Persona-presence {
28 | display: none;
29 | }
30 | }
--------------------------------------------------------------------------------
/src/widgets/PersonaCard/PersonaCard.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/PersonaCard/PersonaCard.less:
--------------------------------------------------------------------------------
1 | .spwidgets-PersonaCard {
2 |
3 | }
--------------------------------------------------------------------------------
/src/widgets/PersonaCard/PersonaCardActionDetails/PersonaCardActionDetails.html:
--------------------------------------------------------------------------------
1 |
2 | {{detailsHTML}}
3 |
--------------------------------------------------------------------------------
/src/widgets/PersonaCard/PersonaCardActionDetails/PersonaCardActionDetails.js:
--------------------------------------------------------------------------------
1 | import Widget from "common-micro-libs/src/jsutils/Widget"
2 | import EventEmitter from "common-micro-libs/src/jsutils/EventEmitter"
3 | import dataStore from "common-micro-libs/src/jsutils/dataStore"
4 | import objectExtend from "common-micro-libs/src/jsutils/objectExtend"
5 | import fillTemplate from "common-micro-libs/src/jsutils/fillTemplate"
6 | import parseHTML from "common-micro-libs/src/jsutils/parseHTML"
7 |
8 | import PersonaCardActionDetailsTemplate from "./PersonaCardActionDetails.html"
9 | import detailLineTemplate from "./detailLine.html"
10 |
11 | //==========================================================================
12 | const PRIVATE = dataStore.create();
13 |
14 |
15 | /**
16 | * PersonaCardActionDetails Widget Display the content of an action when the
17 | * user clicks on it.
18 | *
19 | * @class PersonaCardActionDetails
20 | * @extends Widget
21 | * @extends EventEmitter
22 | *
23 | * @param {Object} options
24 | * @param {Array} options.details
25 | * Each Detail object can have the following properties:
26 | *
27 | * - `label`: The label for the value
28 | * - `value`: the value for detail item. Could be a string of HTML
29 | *
30 | */
31 | const PersonaCardActionDetails = EventEmitter.extend(Widget).extend(/** @lends PersonaCardActionDetails.prototype */{
32 | init(options) {
33 | var inst = {
34 | opt: objectExtend({}, this.getFactory().defaults, options)
35 | };
36 |
37 | PRIVATE.set(this, inst);
38 |
39 | let $ui = this.$ui = this.getTemplate();
40 |
41 | if (typeof $ui === "string") {
42 | $ui = this.$ui = parseHTML(fillTemplate($ui, {
43 | detailsHTML: fillTemplate(this.getDetailLineTemplate(), inst.opt.details)
44 | })).firstChild;
45 | }
46 |
47 |
48 | this.onDestroy(() => {
49 | // Destroy all Compose object
50 | Object.keys(inst).forEach(function (prop) {
51 | if (inst[prop]) {
52 | [
53 | "destroy", // Compose
54 | "remove", // DOM Events Listeners
55 | "off" // EventEmitter Listeners
56 | ].some((method) => {
57 | if (inst[prop][method]) {
58 | inst[prop][method]();
59 | return true;
60 | }
61 | });
62 |
63 | inst[prop] = undefined;
64 | }
65 | });
66 |
67 | PRIVATE["delete"](this);
68 | });
69 | },
70 |
71 | /**
72 | * returns the widget's template
73 | * @return {String}
74 | */
75 | getTemplate(){
76 | return PersonaCardActionDetailsTemplate;
77 | },
78 |
79 | /**
80 | * Returns the HTML template for an insividual item
81 | *
82 | * @return {String}
83 | */
84 | getDetailLineTemplate(){
85 | return detailLineTemplate;
86 | }
87 | });
88 |
89 | PersonaCardActionDetails.defaults = {
90 | details: null // Array
91 | };
92 |
93 | export default PersonaCardActionDetails;
94 |
--------------------------------------------------------------------------------
/src/widgets/PersonaCard/PersonaCardActionDetails/detailLine.html:
--------------------------------------------------------------------------------
1 |
2 | {{label}}
3 | {{value}}
4 |
--------------------------------------------------------------------------------
/src/widgets/PersonaCard/PersonaCardActions/PersonaCardActions.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/widgets/PersonaCard/PersonaCardActions/action.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{title}}
4 |
--------------------------------------------------------------------------------
/src/widgets/TextField/TextField.html:
--------------------------------------------------------------------------------
1 |
2 | {{column.DisplayName}}
3 |
4 | {{column.Description}}
5 |
--------------------------------------------------------------------------------
/src/widgets/TextField/TextField.less:
--------------------------------------------------------------------------------
1 | .spwidgets-TextField {
2 |
3 | input.ms-TextField-field {
4 | min-width: 100%;
5 | }
6 |
7 | &--noLabel .ms-Label {
8 | display: none;
9 | }
10 |
11 | &--noDescription .ms-TextField-description {
12 | display: none;
13 | }
14 | }
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "SP": true,
4 | "window": true,
5 | "document": true,
6 | "location": true,
7 | "alert": true,
8 | "tinymce": true,
9 | "unescape": true,
10 | "prompt": true,
11 | "confirm": true,
12 | "define": true,
13 | "jQuery": true,
14 | "describe": true,
15 | "it": true,
16 | "expect": true,
17 | "beforeEach": true,
18 | "afterEach": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | SPWidgets Test Suite
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/test/server/mock.soap.GetList.js:
--------------------------------------------------------------------------------
1 | define([
2 | "text!./soapMsgs/list.GetList.response.success.xml"
3 | ], function(
4 | getListResponseSuccessXML
5 | ){
6 |
7 | return {
8 |
9 | // requests must have 'auto_respond' in the request message
10 | install: function(){
11 |
12 | jasmine.Ajax.stubRequest(
13 | /.*\/_vti_bin\/Lists\.asmx/, // url
14 | /.*
31 | //
32 | //
33 | // https://site.sharepoint.com/sites/mysite/Shared%20Documents/page.apsxp
34 | //
35 | //
36 | //
37 |
38 | });
39 |
--------------------------------------------------------------------------------
/test/server/mock.soap.getListItems.js:
--------------------------------------------------------------------------------
1 | define([
2 | "text!./soapMsgs/list.GetListItems.response.success.xml",
3 | "text!./soapMsgs/list.GetListItemsChangesSinceTokenResponse.response.success.xml"
4 | ], function(
5 | getListItemsResponseSuccessXML,
6 | getListItemsChangesSinceTokenResponseSuccessXML
7 | ){
8 |
9 | return {
10 | // requests must have 'auto_respond' in the request message
11 | install: function(){
12 |
13 | jasmine.Ajax.stubRequest(
14 | /.*\/_vti_bin\/Lists\.asmx/, // url
15 | /.*
2 |
4 |
5 |
6 | 0
7 |
8 | 0x85300000
9 | Some error message
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/error.ErrorCode.good.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | 0
7 |
8 | 0x00000000
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/error.copyResult.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | err101
8 | Some Message here
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/error.faultcode.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | soap:Server
7 | Exception of type 'Microsoft.SharePoint.SoapServer.SoapServerException' was thrown.
8 |
9 |
10 | The specified name is already in use.
11 | 0x81020067
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/forms.GetListFormCollection.response.success.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/list.GetListItems.response.success.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/login.operation.response.cannotBeNull.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | soap:Server
7 | Server was unable to process request. ---> Value cannot be null.
8 | Parameter name: userName
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/login.operation.response.noError.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | FedAuth
8 | NoError
9 | 1800
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/login.operation.response.passwordNotMatch.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 | PasswordNotMatch
8 | 0
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/people.searchPrincipals.response.success.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | i:0#.f|membership|joe.doe@test.com
8 | 11
9 | Joe Doe
10 | joe.doe@test.com
11 | true
12 | User
13 |
14 |
15 | i:0#.f|membership|amy.smith@test.com
16 | 12
17 | Amy Smith
18 | amy.smith@test.com
19 | true
20 | User
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/test/server/soapMsgs/web.webUrlFromPageUrl.response.success.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | https://tenant.sharepoint.com/sites/test
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/setup/requirejs.config.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | baseUrl: "../",
3 | urlArgs: '@BUILD',
4 | paths: {
5 | jquery: 'vendor/jquery/dist/jquery',
6 | 'jquery-ui': 'vendor/jquery-ui/jquery-ui',
7 | less: 'vendor/require-less/less',
8 | lessc: 'vendor/require-less/lessc',
9 | normalize: 'vendor/require-less/normalize',
10 | text: 'vendor/requirejs-text/text',
11 | 'vendor/jsutils': 'vendor/common-micro-libs/src/jsutils',
12 | 'vendor/domutils': 'vendor/common-micro-libs/src/domutils'
13 | },
14 | shim: {
15 | 'jquery-ui': {
16 | deps: ['jquery']
17 | },
18 | 'SPWidgets': {
19 | deps: ['jquery', 'jquery-ui']
20 | }
21 | },
22 | less: {
23 | relativeUrls: true,
24 | logLevel : 2
25 | }
26 | });
--------------------------------------------------------------------------------
/test/specs/models/ListColumnModel.js:
--------------------------------------------------------------------------------
1 | define([
2 | "src/models/ListColumnModel"
3 | ],
4 | function(
5 | ListColumnModel
6 | ){
7 |
8 | var xmlField = (function(){
9 | var
10 | parser = new DOMParser(),
11 | xml = parser.parseFromString(
12 | '' +
13 | '' +
14 | '(1) High ' +
15 | '(2) Normal ' +
16 | '(3) Low ' +
17 | ' ' +
18 | '' +
19 | '(1) High ' +
20 | '(2) Normal ' +
21 | '(3) Low ' +
22 | ' ' +
23 | '(2) Normal ' +
24 | ' ', "text/xml");
25 | return xml;
26 | }());
27 |
28 | describe("ListColumnModel", function(){
29 |
30 | beforeEach(function(){
31 | beforeEach(function(){
32 | this.col = ListColumnModel.create(
33 | {
34 | ID: "{a8eb573e-9e11-481a-a8c9-1104a54b2fbd}",
35 | Type: "Choice",
36 | Name: "Priority",
37 | DisplayName: "Priority",
38 | SourceID: "http://schemas.microsoft.com/sharepoint/v3",
39 | StaticName: "Priority",
40 | ColName: "nvarchar3",
41 | MyBoolean: "True"
42 | },
43 | {
44 | source: xmlField
45 | }
46 | );
47 | });
48 | });
49 |
50 | it("exposes defaults", function(){
51 | expect(ListColumnModel.defaults).toBeDefined();
52 | });
53 |
54 | it("Returns a ListcolumnModel instance", function(){
55 | expect(ListColumnModel.isInstanceOf(this.col)).toBe(true);
56 | });
57 |
58 | it("has object propertis for the column", function(){
59 | expect(this.col.Type).toMatch("Choice");
60 | expect(this.col.DisplayName).toMatch("Priority");
61 | expect(this.col.MyBoolean).toMatch("True");
62 | });
63 |
64 | it("has .getColumnValues() method", function(){
65 | expect(this.col.getColumnValues).toBeDefined();
66 | });
67 |
68 | it(".getColumnValues() returns Promise", function(){
69 | expect(this.col.getColumnValues().then).toBeDefined();
70 | });
71 |
72 | it(".getColumnValues() Promise resolves to array of values for CHOICE column", function(done){
73 | this.col.getColumnValues().then(function (values) {
74 | expect(values.length).toBe(3);
75 | done();
76 | });
77 | });
78 |
79 | // FYI: the actual checking that this method returns values is done in getListColumn()
80 |
81 | });
82 |
83 | });
84 |
--------------------------------------------------------------------------------
/test/specs/models/ListItemModel.js:
--------------------------------------------------------------------------------
1 | define([
2 | "src/models/ListItemModel"
3 | ], function(ListItemModel){
4 |
5 | describe("ListItemModel", function(){
6 |
7 | describe("Instances", function(){
8 | beforeEach(function(){
9 | this.itemObj = {
10 | Name: "Test item",
11 | ID: "123"
12 | };
13 | this.listItem = ListItemModel.create(this.itemObj);
14 | });
15 |
16 | it("has properties defined on input", function(){
17 | expect(this.listItem).toEqual(this.itemObj);
18 | });
19 | });
20 |
21 | });
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/test/specs/models/ListModel.js:
--------------------------------------------------------------------------------
1 | define([
2 | "src/models/ListModel",
3 | "test/server/mock.soap.GetList"
4 | ], function(
5 | ListModel,
6 | mockSoapGetList
7 | ){
8 |
9 | var xmlDoc = (function(){
10 | var
11 | parser = new DOMParser(),
12 | xmlDoc = parser.parseFromString(mockSoapGetList.msgSuccess, "text/xml");
13 | return xmlDoc;
14 | }());
15 |
16 | describe("ListModel", function(){
17 |
18 | describe("General validations", function(){
19 | it("exposes defaults options", function(){
20 | expect(ListModel.defaults).toBeDefined();
21 | });
22 |
23 | it("instanciates", function(){
24 | var list = ListModel.create(xmlDoc);
25 | expect(ListModel.isInstanceOf(list)).toBe(true);
26 | });
27 | });
28 |
29 | describe("Create from XML", function(){
30 | beforeEach(function(){
31 | this.list = ListModel.create(xmlDoc, {
32 | webURL: "https://test.com/sites/test"
33 | });
34 | });
35 |
36 | it("responds to getListUrl()", function(){
37 | expect("function" === typeof this.list.getListUrl).toBe(true);
38 | });
39 |
40 | it("contains list properties", function(){
41 | expect(this.list.Title).toBe("Tasks");
42 | expect(this.list.MaxItemsPerThrottledOperation).toBe("5000");
43 | });
44 |
45 | it("converts true/false strings to Boolean types", function(){
46 | expect(this.list.Followable).toBe(false);
47 | expect(this.list.ShowUser).toBe(true);
48 | });
49 |
50 | it("getSource() returns original data source", function(){
51 | expect(this.list.getSource()).toBe(xmlDoc);
52 | });
53 | });
54 |
55 | describe("Method", function(){
56 | beforeEach(function(){
57 | this.list = ListModel.create(xmlDoc, {
58 | webURL: "https://test.com/sites/test"
59 | });
60 | });
61 |
62 | describe("getListUrl()", function(){
63 | it("returns full URL of list when webURL is known", function(){
64 | expect(this.list.getListUrl()).toBe("https://test.com/sites/test/Lists/Tasks");
65 | });
66 | it("returns list root URL when webURL is NOT known", function(){
67 | this.list = ListModel.create(xmlDoc);
68 | expect(this.list.getListUrl()).toBe("/sites/test/Lists/Tasks");
69 | });
70 | });
71 |
72 | });
73 | });
74 |
75 | });
76 |
77 |
--------------------------------------------------------------------------------
/test/specs/spapi/getList.js:
--------------------------------------------------------------------------------
1 | define([
2 | "src/spapi/getList",
3 | "../../server/mock.soap.GetList",
4 | "../../server/mock.soap.WebUrlFromPageUrl"
5 | ], function(
6 | getList,
7 | mockSoapGetList,
8 | mockSoapWebUrlFromPageUrl
9 | ){
10 |
11 | describe("getList SP API", function(){
12 |
13 | beforeEach(function(){
14 | jasmine.Ajax.install();
15 | mockSoapWebUrlFromPageUrl.install();
16 | mockSoapGetList.install();
17 | });
18 |
19 | afterEach(function(){
20 | jasmine.Ajax.uninstall();
21 | });
22 |
23 | describe("Interface", function() {
24 |
25 | it("return a promise", function(){
26 | var req = getList({listName: "auto_respond"});
27 | expect(req).toBeDefined();
28 | expect(req.then).toBeDefined();
29 | });
30 |
31 | it("exposes defaults", function(){
32 | expect(getList.defaults).toBeDefined();
33 | });
34 |
35 | });
36 |
37 | describe("Data retrieval", function(){
38 |
39 | it("returns an object that is an instance of ListModel", function(done){
40 | getList({listName: "auto_respond"}).then(function(list){
41 | expect(getList.defaults.ListModel.isInstanceOf(list)).toBe(true);
42 | done();
43 | });
44 | });
45 |
46 | it("ListModel instance contains list definition attributes", function(done){
47 | getList({listName: "auto_respond"}).then(function(list){
48 | expect(list.ID).toMatch("{7EE477D9-D257-47F5-A25D-A882D882E51F}");
49 | expect(list.Title).toMatch("Tasks");
50 | expect(list.HasUniqueScopes).toBe(false);
51 | expect(list.ShowUser).toBe(true);
52 | done();
53 | });
54 | });
55 |
56 | it("caches data by default", function(done){
57 | getList({listName: "auto_respond"}).then(function(list1){
58 | getList({listName: "auto_respond"}).then(function(list2){
59 | expect(list1 === list2).toBe(true);
60 | done();
61 | });
62 | });
63 | });
64 |
65 | });
66 |
67 | });
68 |
69 | });
70 |
--------------------------------------------------------------------------------
/test/specs/spapi/getListFormCollection.js:
--------------------------------------------------------------------------------
1 | define([
2 | "../../server/mock.soap.GetListFormCollection",
3 | "../../server/mock.soap.WebUrlFromPageUrl",
4 | "src/spapi/getListFormCollection"
5 | ], function(
6 | mockSoapGetListFormCollection,
7 | mockSoapWebUrlFromPageUrl,
8 | getListFormCollection
9 | ){
10 |
11 | describe("getListFormCollection SP API", function(){
12 |
13 | beforeEach(function(){
14 | jasmine.Ajax.install();
15 | mockSoapWebUrlFromPageUrl.install();
16 | mockSoapGetListFormCollection.install();
17 | });
18 |
19 | afterEach(function(){
20 | jasmine.Ajax.uninstall();
21 | });
22 |
23 | describe("Interface", function() {
24 | it("exposes defaults", function(){
25 | expect(getListFormCollection.defaults).toBeDefined();
26 | });
27 |
28 | it("return a promise", function(done){
29 | var req = getListFormCollection({listName: "auto_respond"});
30 | expect(req).toBeDefined();
31 | expect(req.then).toBeDefined();
32 |
33 | req
34 | .then(function(){
35 | done();
36 | })
37 | .catch(function(e){
38 | console.log("Request failed: " + e);
39 | });
40 |
41 | });
42 | });
43 |
44 | describe("Data retrieval", function(){
45 |
46 | it("resolves to an array", function(done){
47 | getListFormCollection({listName: "auto_respond"})
48 | .then(function(forms){
49 | expect(Array.isArray(forms)).toBe(true);
50 | done();
51 | })
52 | .catch(function (err) {
53 | console.log("----: ERROR :-----");
54 | console.log(err);
55 | });
56 | });
57 |
58 | it("Array contains objects with forms", function(done){
59 | getListFormCollection({listName: "auto_respond"}).then(function(forms){
60 | expect(typeof forms[0]).toBe("object");
61 | done();
62 | });
63 | });
64 |
65 | it("Array objects contains type and url attributes", function(done){
66 | getListFormCollection({listName: "auto_respond"}).then(function(forms){
67 | expect(forms[0].url).toBeDefined();
68 | expect(forms[0].type).toBeDefined();
69 | done();
70 | });
71 | });
72 |
73 | it("Form url attribute starts with http", function(done){
74 | getListFormCollection({listName: "auto_respond"}).then(function(forms){
75 | expect(forms[0].url.toLowerCase().indexOf('http') === 0).toBe(true);
76 | done();
77 | });
78 | });
79 |
80 | });
81 | });
82 | });
83 |
--------------------------------------------------------------------------------
/test/specs/spapi/searchPrincipals.js:
--------------------------------------------------------------------------------
1 | define([
2 | "src/spapi/searchPrincipals",
3 | "test/server/mock.soap.webUrlFromPageUrl",
4 | "../../server/mock.soap.SearchPrincipals"
5 | ], function(
6 | searchPrincipals,
7 | mockSoapWebUrlFromPageUrl,
8 | mockSoapSearchPrincipals
9 | ){
10 |
11 | describe("SearchPrincipals SP API", function(){
12 |
13 | beforeEach(function(){
14 | jasmine.Ajax.install();
15 | mockSoapWebUrlFromPageUrl.install();
16 | mockSoapSearchPrincipals.install();
17 | });
18 |
19 | afterEach(function(){
20 | jasmine.Ajax.uninstall();
21 | });
22 |
23 | describe("Method API", function(){
24 | it("is a function", function(){
25 | expect(typeof searchPrincipals).toMatch("function");
26 | });
27 |
28 | it("exposes defaults", function(){
29 | expect(searchPrincipals.defaults).toBeDefined();
30 | });
31 |
32 | it("has defaults.searchText", function(){
33 | expect(searchPrincipals.defaults.searchText).toBeDefined();
34 | });
35 |
36 | it("has defaults.maxResults", function(){
37 | expect(searchPrincipals.defaults.maxResults).toBeDefined();
38 | });
39 |
40 | it("defaults.maxResults is a Number", function(){
41 | expect(typeof searchPrincipals.defaults.maxResults).toMatch("number");
42 | });
43 |
44 | it("has defaults.principalType", function(){
45 | expect(searchPrincipals.defaults.principalType).toBeDefined();
46 | });
47 |
48 | it("has defaults.webURL", function(){
49 | expect(searchPrincipals.defaults.webURL).toBeDefined();
50 | });
51 |
52 | it("has defaults.cache", function(){
53 | expect(searchPrincipals.defaults.cache).toBeDefined();
54 | });
55 |
56 | it("defaults.cache is set to false", function(){
57 | expect(searchPrincipals.defaults.cache).toBe(true);
58 | });
59 |
60 | it("has defaults.UserProfileModel", function(){
61 | expect(searchPrincipals.defaults.UserProfileModel).toBeDefined();
62 | });
63 |
64 | });
65 |
66 | describe("DATA Retrieval", function(){
67 |
68 | beforeEach(function(){
69 | this.searchReq = searchPrincipals({
70 | searchText: "auto_respond"
71 | });
72 | });
73 |
74 | it("returns a promise", function(done){
75 | expect(this.searchReq.then).toBeDefined();
76 | this.searchReq.then(function(){
77 | done();
78 | });
79 | });
80 |
81 | it("Promise resolved with Array", function(done){
82 | this.searchReq.then(function(results){
83 | expect(results).toBeDefined();
84 | expect(Array.isArray(results)).toBe(true);
85 | done();
86 | });
87 | });
88 | });
89 | });
90 | });
91 |
--------------------------------------------------------------------------------
/test/specs/sputils/doesMsgHaveError.js:
--------------------------------------------------------------------------------
1 | define([
2 | "src/sputils/doesMsgHaveError",
3 | "vendor/jsutils/parseXML",
4 | "text!../../server/soapMsgs/login.operation.response.noError.xml",
5 | "text!../../server/soapMsgs/error.faultcode.xml",
6 | "text!../../server/soapMsgs/error.ErrorCode.good.xml",
7 | "text!../../server/soapMsgs/error.ErrorCode.bad.xml",
8 | "text!../../server/soapMsgs/error.copyResult.xml"
9 | ], function(
10 | doesMsgHaveError,
11 | parseXML,
12 | msgNoError,
13 | msgFaultcode,
14 | msgErrorCodeGood,
15 | msgErrorCodeBad,
16 | msgCopyResult
17 | ){
18 |
19 | describe("doesMsgHaveError", function(){
20 |
21 | it("is a function", function(){
22 | expect(typeof doesMsgHaveError).toMatch("function");
23 | });
24 |
25 | it("finds bad ErrorCode", function(){
26 | var xml = parseXML(msgErrorCodeBad);
27 | expect(doesMsgHaveError(xml)).toBe(true);
28 | });
29 |
30 | it("Ignores good ErrorCode", function(){
31 | var xml = parseXML(msgErrorCodeGood);
32 | expect(doesMsgHaveError(xml)).toBe(false);
33 | });
34 |
35 | it("finds faultcode", function(){
36 | var xml = parseXML(msgFaultcode);
37 | expect(doesMsgHaveError(xml)).toBe(true);
38 | });
39 |
40 | it("finds CopyResult ErrorMessage", function(){
41 | var xml = parseXML(msgCopyResult);
42 | expect(doesMsgHaveError(xml)).toBe(true);
43 | });
44 | });
45 |
46 | });
--------------------------------------------------------------------------------
/test/specs/sputils/getMsgError.js:
--------------------------------------------------------------------------------
1 | define([
2 | "src/sputils/getMsgError",
3 | "vendor/jsutils/parseXML",
4 | "text!../../server/soapMsgs/login.operation.response.noError.xml",
5 | "text!../../server/soapMsgs/error.faultcode.xml",
6 | "text!../../server/soapMsgs/error.ErrorCode.good.xml",
7 | "text!../../server/soapMsgs/error.ErrorCode.bad.xml",
8 | "text!../../server/soapMsgs/error.copyResult.xml"
9 | ], function(
10 | getMsgError,
11 | parseXML,
12 | msgNoError,
13 | msgFaultcode,
14 | msgErrorCodeGood,
15 | msgErrorCodeBad,
16 | msgCopyResult
17 | ){
18 |
19 | describe("getMsgError", function(){
20 |
21 | it("is a function", function(){
22 | expect(typeof getMsgError).toMatch("function");
23 | });
24 |
25 | it("finds ErrorCode", function(){
26 | var xml = parseXML(msgErrorCodeBad);
27 | expect(getMsgError(xml)).toMatch(/Some error message/);
28 | });
29 |
30 | it("finds faultcode", function(){
31 | var xml = parseXML(msgFaultcode);
32 | expect(getMsgError(xml)).toMatch(/Exception of type/);
33 | expect(getMsgError(xml)).toMatch(/0x81020067/);
34 | });
35 |
36 | it("finds CopyResult ErrorMessage", function(){
37 | var xml = parseXML(msgCopyResult);
38 | expect(getMsgError(xml)).toMatch(/Some Message here/);
39 | expect(getMsgError(xml)).toMatch(/err101/);
40 | });
41 | });
42 |
43 | });
--------------------------------------------------------------------------------
/test/suite.js:
--------------------------------------------------------------------------------
1 | define([
2 | // All test cases should resize in the test/specs folder and be
3 | // referenced below as a dependency.
4 | "./specs/jquery.SPWidgets",
5 | "./specs/jsutils/Compose",
6 | "./specs/models/ListItemModel",
7 | "./specs/models/ListModel",
8 | "./specs/models/ListColumnModel",
9 | "./specs/spapi/getList",
10 | "./specs/spapi/getListColumns",
11 | "./specs/spapi/getListFormCollection",
12 | "./specs/spapi/getListItems",
13 | "./specs/spapi/searchPrincipals",
14 | "./specs/sputils/doesMsgHaveError",
15 | "./specs/sputils/getMsgError"
16 |
17 | ], function(){});
18 |
--------------------------------------------------------------------------------
/test/test.SPWidgets.aspx:
--------------------------------------------------------------------------------
1 | <%-- SPWIDGETS DEV PAGE--%>
2 | <%@ Page language="C#" MasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,Microsoft.SharePoint,Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>
3 | <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
4 | <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
5 | <%@ Import Namespace="Microsoft.SharePoint" %>
6 | <%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
7 |
8 | SPWidgets Test Page
9 |
10 |
11 | SPWidgets Test Page
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
26 |
27 |
35 |
36 |
37 |
38 |
39 |
40 | SPWidgets - Widgets for building custom UIs
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/tools/copy.process.minifyHtml.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | var htmlMinifier= require('html-minifier').minify,
4 | grunt = require("grunt"),
5 | isHtmlFile = /\.html|htm$/i;
6 |
7 | /**
8 | * Grunt copy task processor that returns minified HTML markup. Meant to be
9 | * used with the processContent/process option of the copy task.
10 | *
11 | * @param {String} fileContent
12 | * @param {String} filePath
13 | * @return {String} file content
14 | *
15 | * @see https://www.npmjs.com/package/html-minifier
16 | *
17 | * @example
18 | *
19 | * copy: {
20 | * build: {
21 | * src: '',
22 | * dest: '',
23 | * expand: true,
24 | * options: {
25 | * processConent: minifyHtml
26 | * }
27 | * }
28 | * }
29 | *
30 | */
31 | exports.minifyHtml = function(fileContent, filePath){
32 |
33 | if (isHtmlFile.test(filePath)) {
34 |
35 | grunt.verbose.writeln("minifyHtml: minifying: " + filePath);
36 |
37 | return htmlMinifier(fileContent, {
38 | removeComments : true,
39 | collapseWhitespace : true,
40 | conservativeCollapse : true,
41 | collapseBooleanAttributes : true,
42 | removeEmptyAttributes : true,
43 | caseSensitive : true,
44 | ignoreCustomComments : [
45 | /^\s+ko/,
46 | /\/ko\s+$/
47 | ]
48 | });
49 |
50 | }
51 | return fileContent;
52 | };
53 |
54 | }());
55 |
--------------------------------------------------------------------------------
/tools/server.js:
--------------------------------------------------------------------------------
1 | var connect = require('../node_modules/grunt-contrib-connect/node_modules/connect');
2 | var serveStatic = require('serve-static');
3 | var path = require("path");
4 | var open = require("open");
5 | connect().use(
6 | serveStatic(path.join(__dirname, ".."))
7 | )
8 | .listen(8080);
9 | console.log("Started on port 8080");
10 | open("http://127.0.0.1:8080/test/index.html");
11 |
--------------------------------------------------------------------------------