Adding UIkit to Pinegrow: Pt 5
Namaste!
Welcome to part 5 of this series! In this section, we will be injecting our template resources, covering some helper functions, and then we will add in a custom control for adding UIkit icons to Pinegrow using a built-in modal picker.
To recap our past posts in this series, we set-up a structure for our plugin, added the basic fields to initialize a framework and wrote some code to add the ‘Alert’ component from the UIkit framework. We also covered the various basic actions and action types. One of the other things we did in the basic setup was to add a simple template.
If the end-user elects to utilize our template and saves it as a page/project, all of our template scripts and CSS will be copied to the new save location. However, if someone elects to start with their own template and then use the UIkit plugin, we need to tell Pinegrow what resources they will need in the new project.
Resources are added into a project using the ‘PgComponentTypeResource
‘ constructor. This constructor can receive two arguments, ‘url
‘ and ‘code
‘. Having noted this, I can’t find a single example of how and why the ‘code
‘ argument should be used, but it seems to be used as a comparator to ensure that only one copy of the resource is loaded. The main component is ‘url
‘ which give a location of the file/folder that should be copied over to the new project.
Now we have some choices to make. Do we elect to copy over the minified CSS, or do we elect to copy over the entire UIkit theme SCSS files? In this case, I think we will assume that all of our Pinegrow peeps are power users and bring in the SCSS files! That means we have a total of two Javascript files, the ‘custom.css
‘ file, the ‘custom.scss
‘ file, and the folder of SCSS resources. To make this a little easier, rather than bringing in each individually, let’s write a loop.
//Add resources
var resource_files = [
'js/uikit.min.js',
'js/uikit-icons.min.js',
'css/custom.scss',
'css/custom.css'
];
resource_files.forEach(function(resource_file) {
var file = framework.getResourceFile('template/resources/' + resource_file);
var resource = new PgComponentTypeResource(file);
resource.relative_url = resource_file;
resource.source = toLocalPath(file);
resource.footer = resource_file.indexOf('.js') >= 0;
framework.resources.add(resource);
});
Okay, we start by defining an array of each of our resources and their location in the resources folder. So, the first two files are in the ‘js
‘ folder, the second two in the ‘css
‘ folder, and the final folder of UIkit SCSS is at the root.
Next, we run the array through a ‘forEach
‘ loop where we add each of the files to the framework resources. First, we set the variable ‘file
‘ equal to the source of our passed in file using the ‘getResourceFile
‘ function. In this case, I’m adding on the directory relative to the uikitPinegrow.js file, ‘template/resources
‘.We then define a new object, ‘PgComponentTypeResource
‘, and pass in ‘file
‘ as a value. We are also passing in three other values, the ‘relative_url
‘, the ‘source’, and a true/false value for ‘footer
‘. The first two give Pinegrow information about where the files it will need to copy are located. The third, ‘footer
‘ is used to tell Pinegrow that the file should be placed into the footer of the newly generated HTML page that is utilizing our resources. There are a couple of other values that we can set for the resource object. One of them is ‘isFolder
‘. While it would probably be better to include that for our folder of SCSS files, the object creator is written in such a way that we don’t HAVE to explicitly tell it.
Next up, I’d like to outline three helper functions that I find useful. First, in any framework where you have elements that take up space but are placed on the page empty, like the UIkit ‘container’ you can add a space filler by passing in ‘empty_placeholder: true
‘ as a ‘key:value
‘ pair. However, it is also nice in other elements to present an image. One easy way to do this is to utilize a Pingrow function, ‘getPlaceholderImage()
‘. In order to use this in our framework we have to link to that function like so:
var getPlaceholderImage = function () {
return pinegrow.getPlaceholderImage();
};
Now, whenever we want to add a holder image we just call this function when defining the code for the element, in the UIkit card media for example:
code: '<div class="uikit-card-media"><img src=' + getPlaceholderImage() + ' alt="" /></div>',
Another useful function is one that allows us to refresh the page when certain elements have been added.
var refreshPage = function () {
var selectedPage = pinegrow.getSelectedPage();
selectedPage.refresh();
};
In this case we use the Pinegrow function to get the currently active page using ‘getSelectedPage()
‘ and then perform a ‘refresh()
‘. This can be called from the element by using the key, ‘on_inserted
‘, like so:
on_inserted : function() {
refreshPage();
},
A last helper function that already exists in Pinegrow that I would like to talk about allows us to display important information about an element or action to the user. It is the function ‘crsaQuickMessage()
‘ and it take two arguments – ‘message
‘ and ‘time
‘. The message is obvious and the time is how much time the message should be displayed for in milliseconds. This is commonly used in conjunction with the ‘on_inserted
‘ key, like so:
on_inserted: function(){crsaQuickMessage('Thanks for including me!!', 3000)},
Moving on to the final part of this tutorial, I want to outline how you can utilize another built-in aspect of Pinegrow as a helper in our own plugin. In previous tutorials, I’ve outlined how to add in action fields for your HTML elements that allow the user to set the class or an attribute on the element from a drop-down or checkbox control. This works well if you have a binary selection or a limited number of choices. What if you have 141 choices? That is a mighty long list! Instead, the developers of Pinegrow came up with the solution of having a pop-up modal containing a grid of choices, which is perfect for including the UIkit icons.
Just as a refresher, to add any component to Pinegrow we create a ‘PgComponentType
‘ object with several ‘key:value
‘ pairs. These include a ‘selector
‘ to identify the element, a ‘code
‘ key that contains the HTML to be added to the page as value, and a ‘sections
‘ key which receives an object as the value. That object contains the name of the section and then a series of field objects which outline each of the actions for adding classes, attributes, etc… Each of the individual icons, in this case, will be options for one action field. We will also need to add an action field to allow the user to select the ratio for the icon.
To start designing this element, I first made an array containing all of the icon names. Given the length of the list, I’ll leave it to you, my dear reader, to exercise your imaginative scripting/regex powers to extract the names from the uikit-icons.js file. (Which is to say that I managed to do it late at night and don’t exactly remember my steps) Once we have that array, we can generate an object containing three ‘key:value
‘ pairs.
var uikit_icon_options = [];
ui_icon_list.forEach((each_icon) => {
uikit_icon_options.push({
key: each_icon,
name: each_icon,
html: '<a uk-icon icon="' + each_icon + '" ratio="2"></a>'
});
});
First, we define an empty array named ‘uikit_icon_options’ to receive our options for later use. Next we loop through each of the icon names and push a new object with three ‘key:value’ pairs. If you remember from past tutorials, the key, ‘key
‘ receives a value equal to what the class or attribute value will be set to. The ‘name
‘ key is what is displayed to the user. The new key here is ‘html
‘. It is used to define how the particular item is presented to the user. In this case, we are creating a UIkit element with the attribute ‘uk-icon
‘ and the ‘icon
‘ attribute set to the name of the icon we want displayed. I’ve also added the ‘ratio
‘ attribute to display the icon a little larger.
There are several different types of icons in the UIkit framework. Two of them – spans and links – have overlapping options. While it is a bit overkill, let’s keep it DRY!! First, we’ll write a function to create the overlapping and unique action fields that we need.
var addUiLinkFields = function (icon_type) {
var common_icon_options_fields = {
['ui_' + icon_type + '_icon']: {
type: 'select',
name: 'Icon',
options: uikit_icon_options,
rich: {
title: 'Select Icon',
modal: true,
class: 'icons-grid'
},
action: 'element_attribute',
attribute: 'icon'
}
};
var uikit_icon_common_option = {
ui_icon_sizes: {
type: 'text',
name: 'Ratio Number',
action: 'element_attribute',
attribute: 'ratio'
}
};
var ui_link_icon_fields = {
ui_link_modifier: {
type: 'checkbox',
name: 'Add link modifier?',
action: 'apply_class',
value: 'uk-icon-link'
},
ui_link_button: {
type: 'checkbox',
name: 'Add button modifier?',
action: 'apply_class',
value: 'uk-icon-button'
}
};
//Declare new object
var list = {};
//walk through and add each new field
$.each(common_icon_options_fields, function (key, value) {
list[key] = value;
});
$.each(uikit_icon_common_option, function (key, value) {
list[key] = value;
});
if ('link' === icon_type) {
$.each(ui_link_icon_fields, function (key, value) {
list[key] = value;
});
}
return list;
};
Walking through the code from the top. This function can take a single argument. In this case, either span or link depending on which component we are generating the options to be associated. The first variable within that function generates an object that is uniquely named with the ‘icon_type
‘ that is passed in. Note the brackets around the key – this allows us to use a variable in the key name. We then say that we will be using a ‘type
‘ of ‘select
‘ indicating that there will be multiple options and that the ‘action
‘ will be to set the ‘element_attribute
‘. In this case, we are setting the ‘icon
‘ attribute equal to the value of the icon selected. All of this should look familiar from the earlier tutorials. Two things that look a little different. One, for the ‘options
‘ key we are passing in our object we created earlier, ‘ui_icon_list
‘. Second (and here is where the magic happens), we are using a new key, ‘rich
‘. Inception time again!
The ‘rich
‘ key takes an object composed of up to four ‘key:value
‘ pairs. Here we are passing in three. First, a ‘title
‘ for our selection screen, second we tell Pinegrow we want the options displayed in a pop-over modal, and third we are passing in a ‘class
‘ of ‘icons-grid
‘. In looking through the code I’ve seen one more example of using the ‘rich
‘ key that took an additional pair of ‘multiple: false
‘ and ‘class: 'rich-select'
‘ I’ll leave that to others to experiment.
The other three fields add, respectively, a text input for the size of the icon, and two modifiers that can be added to any icon displayed in a link (<a></a>
). The code following the three variables simply builds an object called ‘list
‘ that is composed of items from the three variables and then returns that list to whoever called the function. Overall, this is a fairly common structure to use when adding actions to multiple elements. We will revisit it when we come back to adding some of the universal options.
Now that we have the action fields built we need to figure out the best way to wrap those action fields in a unique section. Again, let’s use a simple function.
var getUiIconSections = function (icon_type) {
var icon_options_sections = {
['ui_' + icon_type + '_options']: {
name: 'UIkit ' + icon_type + ' Icon Options',
fields: addUiLinkFields(icon_type)
}
};
return icon_options_sections;
};
Once again, the function takes one argument, the ‘icon_type’. It uses that type to generate a unique name and then populates the fields for that section by calling our ‘addUiLinkFields()’ function before returning the object. I could have written this directly into the first function, but for teaching, I think breaking it down into steps is a little easier.
Finally, we can create our new components.
var uikit_span_icon = new PgComponentType('ui_span_icon', 'UIkit Span Icons', {
selector: 'span[uk-icon]',
tags: 'major',
code: '<span uk-icon icon="star"></span>',
sections: getUiIconSections('span'),
on_inserted: showIconMessage
});
framework.addComponentType(uikit_span_icon);
var uikit_link_icon = new PgComponentType('ui_link_icon', 'UIkit Link Icons', {
selector: 'a[uk-icon]',
tags: 'major',
code: '<a uk-icon icon="star" href="#"></a>',
sections: getUiIconSections('link'),
on_inserted: showIconMessage
});
framework.addComponentType(uikit_link_icon);
The code should be fairly self-explanatory if you have followed along. Note that the ‘on_inserted’ key is taking a function as a value that I’m not showing. It utilizes the ‘crsaQuickMessage’ that I noted earlier to send the user a message if they don’t have the icon script loaded on their page. Don’t forget to add your new components onto the page using ‘PgFrameworkLibSection
‘, ‘setComponentTypes
‘, and ‘addLibSection
‘!
There are just two basic steps left that feel a little kludgy, but I haven’t been able to find a work-around. In order to get the icons to display in the editor, you have to paste the ‘uikit.min.js
‘ and ‘uikit-icons.min.js
‘ files after your plugin function at the very end of the file. I tried injecting the scripts without luck, but this may be due to my lack of knowledge about how UIkit works. If anyone knows a way to “initialize” UIkit, please reach out in the comments. In the meantime, how does our component look?
That is it, I think it looks pretty okay! Note, I injected a little CSS styling to make our selector stand out a little. In a future tutorial, I’ll cover script injection. As always, please, please, please, leave feedback. I’m open to any questions either here in the comments or through e-mail.
Metta!!
Update: I have now finished a full-length version of the UIkit plugin for Pinegrow. Purchase of this plugin not only adds the UIkit framework to Pinegrow for unlimited sites, but it also gives you access to the completed code for learning purposes! As a bonus, I’ll give you access to a Facebook page to discuss coding for Pinegrow. Click here to learn more!!