This is the fourth post about SharePoint Framework and Vue.js. In this post I want to go through the process of creation custom Property Pane control using Vue.js.
Code: https://github.com/AJIXuMuK/vuejs/tree/master/proppane-control. In previous posts we discussed what steps are needed to make Vue.js work in SPFx projects and how to use Vue.js Single File Components inside SharePoint Framework Web Parts and Extensions. I've also mentioned Yeoman VueSpfx generator that does all needed configurations. In the current post I want to discuss how to create custom controls for Web Part Property Pane.
Files and Structure
If you look at official documentation and Reusable SPFx Property Pane Controls repo you'll notice that there is a "best practice" on how to structure files and code of custom controls for property pane when you're going to use some JavaScript framework. Note: I'm using "component" and "control" terms here. The "control" is for property pane custom control that we're creating. The "component" is for framework-specific implementation of the control. Let's say you want to create new ImageUrl control which is a simple input (text box). In that case the file structure and contents will look like that:
IPropertyFieldImageUrl.ts - file to contain custom control properties interface ("public" properties) that will be passed from the web part. This file also usually contains "internal" properties interface that extends "public" properties with IPropertyPaneCustomFieldProps interface. "Public" properties interface usually contains:
key - a UNIQUE key indicates the identity of this control.
properties - parent Web Part properties
onPropertyChange - defines an onPropertyChange function to raise when the selected value changes. Normally this function must be defined with the 'this.onPropertyChange' method of the web part object.
The "template" for the file looks like that:
IPropertyfieldImageUrlHost.ts - file with interfaces that are needed for the component's functioning. In case of React it will contain Props and State interfaces. For Vue.js we can define Props interface in this file. That interface will be used as a "contract" for our SFC.
PropertyFieldImageUrl.ts - custom control "entry point". It contains
A function to create the control (this function will be used in getPropertyPaneConfiguration method of the web part).
A "builder" class that is needed to correctly initialize, render and "orchestrate" the component. This class is instantiated from the function mentioned above.
The "template" for that file looks like that:
PropertyFieldImageUrlHost.module.scss - SASS file with all the styles used in the component.
Framework-specific files for component implementation. In case of React it will be PropertyFieldImageUrlHost.tsx. For Vue.js we'll add PropertyFieldImageUrlHost.vue and remove PropertyFieldImageUrlHost.module.scss as all the styles will be included in .vue file.
Implementation
Now, let's implement the control! General idea of the control is to provide a text box with label that will allow user to enter image url (for example, SharePoint document url) as a simple text. Later, this value could be used in the Web Part to render the image. It means that we'll need to have to additional properties in "public" properties interface:
label - to provide the label for the text box. It could be hardcoded or received directly from web part's strings. But let's pass it from the web part.
value - the value of the text box. This value could be stored in web part's properties.
It would be also great to use Office UI Fabric styles to render the control. I'll be describing the implementation based on the files mentioned above.
IPropertyFieldImageUrl.ts
As mentioned above, this file contains "public" and "internal" properties of the control. Let's extend the "public" properties interface to include label and value properties. The "internal" properties interface will remain without changes.
IPropertyfieldImageUrlHost.ts
This file contains Props interface for Vue.js component. Here we should define all the properties that are passed from the "builder" class. In our case, we need to pass current value, label, unique key and handler to call when the value has been changed in the component:
PropertyFieldImageUrl.vue - <style> Section
The section "duplicates" the styles and hierarchy that is used in TextFieldOffice UI Fabric component. It also uses "theme" variables for border colors (you can read more about theme variables here, here, and here).
PropertyFieldImageUrlHost.vue - <script> Section
The section contains the logic of Vue.js component. In our simple example we need multiple things here:
Implement IPropertyFieldImageUrlHost interface as Vue.js "props". Because I'm using vue-property-decorator(NPM package), these properties will be marked with @Prop() decorator (annotation).
Add "reactive" properties. In our case we'll have single reactive property - current value of the text box.
Add text box's onchange handler that will be used to bubble the value up to the "builder" and to the web part.
Few computed properties to be used in the template: styles to reference compiled CSS classes names and inputId for unique text box id.
This section provides markup of the component with Vue.js bindings to the class-component. The markup here duplicates markup hierarchy of TextFieldOffice UI Fabric component.
Few things to mention here.
All attributes starting with : or v- are special Vue.js attributes needed for data binding.
Same statement as above could be applied to values wrapped with mustache syntax {{...}} - similarly to handlebars.js
In our situation we could use v-bind:value only and use v-on:input instead of v-on:change because we bubble the input's value up to the web part and call re-render with passing this new value back to the component. But I decided to showcase the usage of v-model attribute. And also it assures that even if the value hasn't been passed back, we'll render correct text in the input.
PropertyFieldImageUrl.ts
Now we're ready to implement the last piece of the control: "builder" class. First, let's import Vue and ImageUrlComponent:
Now we can implement the code of the "builder" class. Most of the code is standard: we need to implement IPropertyPaneField interface, add some control-specific fields (in our case - value), correctly initialize the instance in the constructor, add onRender handler that is called to render the control:
Next thing is to handle changes of our component. For handling the changes we just need to implement a handler method an pass it as onValueChanged a property to our control. The handler method should compare new value with the previous one, update web part's property and fire callbacks to bubble new value to the web part. The code of the handler is also pretty standard:
The last part is rendering. The code for rendering is similar to one described in Default SPFx web part using Vue.js: we need to create Vue component with providing render method to the constructor. This method creates our ImageUrlComponent and passes all needed properties. The difference with the process described in Default SPFx web part using Vue.js is that we're not providing el property directly in Vue constructor. We're calling $mount method instead. This is done because parent container for the control might not be added to the actual DOM at the moment when we're creating the instance of Vue. It can exist in Virtual DOM only. In that case Vue.js will throw an error that the element (parent node) is not found. $mount method allows to use "deferred" mounting. The full code of the file, including described onRender method is listed below.
Use the Control in the Web Part
Now we can use the control in the web part similarly to any other Property Pane control. First, we need to import PropertyFieldImageUrl function from PropertyFieldImageUrl.ts.
And use the function inside getPropertyPaneConfiguration:
To make this code work you should also define imageUrl property in web part properties interface and ImageUrlFieldLabel in web part's strings. Now the value from the control can be used somewhere in the web part. The full code for this sample can be found here. The web part from this demo just shows the value from the control like that:
Yeoman Generator for Property Pane Controls
Similarly to VueSpfx generator, it would be great to have some "add-on" Yeoman generator that creates all the structure for Property Pane Custom Control and provides some sample code. And there is such generator! generator-spfx-proppane-controlYou can install it using
And use inside you SPFx web part project to create as many Property Pane controls as you like. Note: this generator doesn't work as a standalone generator. You should use it for SPFx projects that have been previously scaffolded. The generator supports not only Vue.js but React and No Framework options as well. The documentation can be found here. It's pretty simple but covers main features. Please, leave your feedback or contribute to the generator! It's highly appreciated!
Comments