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.

    Screen Shot 2015-07-16 at 19.33.35.png

    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


  • Admin

    The hooks that you want to use are filter:category.create on category creationg and filter: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))
    

  • Admin

    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.


  • GNU/Linux

    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 with this and what it means. Sometimes this 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 as static. Also, function parameters are passed by value rather than by 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 then 50.

    Your browser's console is awesome for fiddling! I use chrome. πŸ™‚

    byval-byref.png

    Here you can see, that aNumberVariable is assigned 50, myMethod defined and then called. The parameter is passed to the function, where only its value is received, and put into a newly created variable functionsNumberVariable. 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 (aka undefined), but the console ignores that. Then the last line is executed, showing that aNumberVariable's value is still 50. (The showing undefined 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 access functionsNumberVariable 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:
    Untitled (3).png
    At this point you'd be perfectly right to say: "Well, nice story. But there's also an invisible return in queryDatabase 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 functions returns 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 as myMethod = function(), arguments is available and filled with everything that has been passed in the call. Great for exploring! πŸ™‚
    arguments.png


  • Community Rep

    I do hope @Joel-Murphy comes back and keeps at it.



  • @baris

    Ah thank you, I was so close πŸ‘

    @rbeer

    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 and homepage_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 πŸ˜„


  • Community Rep

    @Joel-Murphy

    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);
    			});
    		}
    	});
    });
    


  • @yariplus

    Oh you're right! Not sure why I was loading the template multiple times! Thanks

    This is the result I get. I'm guessing some further rendering functions need to be called after the template is appended to the DOM?

    Screen Shot 2015-07-24 at 13.44.40.png



  • 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 πŸ˜„


  • Community Rep

    Hmm, not sure.Templates is @psychobunny 's thing. Maybe he has the treasure.


 

| |