Need further help with developing first plugin
-
I'm attempting to write my very first plugin which should allow me to add custom attributes / data fields to a category. This information should be updated in the Redis datastore after the form has been submitted.
After reading the documentation, scanning the forums, and browsing the code of existing plugins for a few days, I'm still a little unsure of how exactly I should develop a plugin to achieve this functionality.
First of all, I want to ask if this is even possible? If it is possible, I'm wondering if you lovely people could guide me towards the right direction to implementing this?
I've broken down my goals into sub-goals like so:
When the 'add category' view loads
- Add a new field to the view
When the 'add category' view is submitted
- Perform validation on the custom data field.
- Add the category as normal, but also add the custom data to the datastore.
When the 'edit category' view is loaded
- Add a custom field for the custom category, and also allow this to be edited.
- Perform validation on the custom data field.
Update the custom data field within the datastore.
I know that my plugin will require the use of injecting some html into a view with the help of jQuery, then posting custom data and inserting it into Redis. Apart from that, my first problem is that I'm finding it hard to understand which hook to use from the list of hooks as there is no description along with them. I'm taking a guess and saying
action:category.create
but I can't figure out where to go from here as the examples in the docs are just so simple.Any code samples, pointers, or any general help would be fantastic. I'm just a little confused by the very brief documentation atm but I really want to get good at this stuff!
Thanks
-
The hooks that you want to use are
filter:category.create
on category creationg andfilter:category.update
when a category is updated.filter:category.create
is passed the category object and the data that is being used to create the category, I just added that now.filter:category:updated
is passed the fields that are being modified. -
@baris Thanks for your reply and for committing the new hook code.
Unfortunately, I can't get any further than the code I have here as I can't figure out how to return
categoryObj
back to the parent code in a callback. Time to give up and go back to PHP I think(function(MyPlugin) { 'use strict' MyPlugin.myMethod = function(categoryObj) { // do something with the data categoryObj.category.homepageSection = 1; console.log(categoryObj); }, MyPlugin.myMethod1 = function(categoryObj) { // do something with the data console.log(categoryObj); } }(module.exports))
-
Don't give up so easily
All the filters take a callback parameter which you can call after you are done modifying the data.
In your case it would look something like this
MyPlugin.myMethod = function(categoryObj, callback) { // do something with the data categoryObj.category.homepageSection = 1; console.log(categoryObj); callback(null, categoryObj); };
The first param is an error object and then you pass the data that you modified back into the callback so other plugins that listen for the same hook can keep on modifying the data.
-
baris said:
Don't give up so easily
Yes! Yes! Yes!
JavaScript is a prototypal language, which makes it a bit confusing, but very powerful. You can structure your programs in various ways. I'm only gonna go into how it's (mostly) done in nodeJS:
Although you define objects with members, it is uncommon to actually create instances of those objects. JS has a
new
keyword that allows you to do that, but there it gets messy withthis
and what it means. Sometimesthis
points to an object, sometimes a function - it all depends on where you use it and who called what. You should read up on 'function scope' and 'closure', in case you're interested. I myself still like to use it. But anyway, most code you will encounter treats objects asstatic
. Also, function parameters are passedby value
rather thanby reference
(you may know this from C as passing a variable vs. passing a pointer). Let's have a look:var aNumberVariable = 50; myMethod = function(functionsNumberVariable) { functionsNumberVariable = 100; console.log(functionsNumberVariable); }; myMethod(aNumberVariable); console.log(aNumberVariable);
When you execute this snippet, the output will read
100
and then50
.Your browser's console is awesome for fiddling! I use chrome.
Here you can see, that
aNumberVariable
is assigned50
,myMethod
defined and then called. The parameter is passed to the function, where only its value is received, and put into a newly created variablefunctionsNumberVariable
. This then is set to 100 and written to the console. The function call (myMethod()
) is done synchronously, so everything following 'waits' for the function to finish/return. It does so with no value (akaundefined
), but the console ignores that. Then the last line is executed, showing thataNumberVariable
's value is still 50. (The showingundefined
in the picture is actually not from anywhere in my code. I think that is the result of a function, the console's VM wraps your statements in. Once again the scope thingy.)But this also means, that once
myMethod
is through doing its thing, you have no way to accessfunctionsNumberVariable
anymore (before mentioned closure or function scope). Even worse, it's a candidate to be gobbled up by the garbage collector in one of its next runs. Now, you could, of course, easily just return its value, but this is where nodeJS's magic of asynchronicity gets in between.Everything below would have to wait for your function to return, so a read from a database or from a network resource could easily block your process for a lengthy amount of time - not so good.
Fortunately, JavaScript is capable of passing functions as parameters. . This - we're almost there - is the callback (in our case here; you can do heaps of other neat things with it). You basically pass the next step in your chain of processing as this function, passing it the values which you otherwise would have returned. I fail to come up with a quick and easy code example to run, right now. So I'll draw it in a diagram:
At this point you'd be perfectly right to say: "Well, nice story. But there's also an invisible return inqueryDatabase
which the program has to wait for.". Yes, it is. That's why I painted its declaration yellowish. Your JavaScript runs also in nodeJS on a single thread, but under the hood, when V8 (JS engine) passes such calls to the I/O layer it utilises multi-threading. This game of passing callbacks is played along the chain, until V8 'queries' some system I/O function. There it is split off into another thread, from where (on the return path, if you will) the callbacks are called one after another. The actual functionsreturn
s are done as soon V8 has branched off this new thread. I'm not 100% sure about this last part, though. I urge everyone who knows more about this to correct me!By the way: Every function comes with several 'invisible' variables set. One of which is
arguments
. That's an array of all parameters passed to the function. Disregarding the declarations (function(var1, var2)) you did. So even when you define your function asmyMethod = function()
,arguments
is available and filled with everything that has been passed in the call. Great for exploring!
-
I do hope @Joel-Murphy comes back and keeps at it.
-
Ah thank you, I was so close
Wow thank you so much for your long, detailed post. I decided I needed a refresher on JS after reading your post, so that's where I've been for the past few days. I completely forgot about closures, but now I think I'm back up to scratch with them so things should make a lot more sense now
@BDHarrington7
Oh I will don't worry! I've already started translating NodeBB to Welsh and am keen to get good at plugin development.
-
One thing I'm still unsure about though is how logic works in templates. Without logic inside of statements, doesn't it make it near impossible to do anything more than show or hide certain elements?
Assume I want to order (or more precisely, group) categories on the homepage, how should I do this?
<ul class="categories" itemscope itemtype="http://www.schema.org/ItemList"> <p>A few threads:</p> <!-- BEGIN categories --> {categories.homepage_section} put categories with a homepage_section value of '1' here. <!-- IMPORT partials/categories/item.tpl --> <!-- END categories --> <li class="line-seperator"></li> <p>Other threads:</p> <!-- BEGIN categories --> {categories.homepage_section} put categories with a homepage_section value of '2' here. <!-- IMPORT partials/categories/item.tpl --> <!-- END categories --> </ul>
This should produce something like this:
A few threads
- Test1 (1)
- Test2 (1)
- Test3 (1)
Other threads
- Test1 (2)
- Test2 (2)
- Test3 (2)
Anyone have any ideas? The only obvious solution I can think of right now is adding attributes such as
homepage_section1 = true
andhomepage_section2 = false
to each category. But this seems very long winded and hardly efficient.Thanks
-
Okay, after more fiddling around, I've figured out even more about writing plugins. I've now written some code using client side javascript to group each category together according to its
homepageSection
id. Now I just need to figure out how to render the page after asynchronously rendering each category item.$(document).ready(function() { console.log('Client side JS loaded'); $(window).on('action:ajaxify.contentLoaded', function(ev, data) { if (data.tpl === 'categories') { var sections = {}; var sectionNames = ['A few threads', 'Other threads']; var categoriesHTML = ""; // Loop over each category and assign them to the section array based on their homepageSection value. for(var i = 0; i < ajaxify.data.categories.length; i++){ if( (typeof ajaxify.data.categories[i].homepageSection != 'undefined') && (ajaxify.data.categories[i].hasOwnProperty("homepageSection") )){ sections["section"+ajaxify.data.categories[i].homepageSection] = sections["section"+ajaxify.data.categories[i].homepageSection] || []; sections["section"+ajaxify.data.categories[i].homepageSection].push(ajaxify.data.categories[i]); } } // Loop over each section in the sections array, then render each category in each section for(section in sections){ for (var i = 0; i < sections[section].length; i++) { var myData = sections[section][i]; ajaxify.loadTemplate('partials/categories/item', function(myTemplate) { var html = templates.parse(myTemplate, myData); categoriesHTML += html; ; // loadTemplate is asynchronous and so this is where I suck @ node. Time for some more learning... }); }; } } }); });
Slow progress, but I'm nearly there
-
Get the template first, then parse.
$(document).ready(function() { console.log('Client side JS loaded'); $(window).on('action:ajaxify.contentLoaded', function(ev, data) { if (data.tpl === 'categories') { var sections = {}; var sectionNames = ['A few threads', 'Other threads']; var categoriesHTML = ""; // Loop over each category and assign them to the section array based on their homepageSection value. for(var i = 0; i < ajaxify.data.categories.length; i++){ if( (typeof ajaxify.data.categories[i].homepageSection != 'undefined') && (ajaxify.data.categories[i].hasOwnProperty("homepageSection") )){ sections["section"+ajaxify.data.categories[i].homepageSection] = sections["section"+ajaxify.data.categories[i].homepageSection] || []; sections["section"+ajaxify.data.categories[i].homepageSection].push(ajaxify.data.categories[i]); } } ajaxify.loadTemplate('partials/categories/item', function(myTemplate) { // Loop over each section in the sections array, then render each category in each section for(section in sections){ for (var i = 0; i < sections[section].length; i++) { var myData = sections[section][i]; var html = templates.parse(myTemplate, myData); categoriesHTML += html; }; } // Done parsing, add it. $('.categories').html(categoriesHTML); }); } }); });
-
Another update:
The rendering is actually working fine.
The following HTML:
<small>{../description}</small>
Should be changed to:
<small>{description}</small>
To make those properties show.
However, functions within the theme don't appear to be working. E.g.<div class="icon hidden-sm hidden-xs pull-left" style="{function.generateCategoryBackground}"> <i class="fa fa-fw {../icon}"></i> </div>
I'm guessing this is due to the way I'm passing an object to a template when it's being rendered?
Edit:
I've tried defining a test helper inside of
main.js
:templates.registerHelper('testingz',function(data, iterator, numblocks) { console.log('{function.testingz}'); return "testingz"; });
And calling it from my HTML template using
{function.testingz}
but nothing happens. Am I doing this correctly? As soon I figure this out, I should be able to finish my first plugin -
Hmm, not sure.Templates is @psychobunny 's thing. Maybe he has the treasure.