Tutorials

Tutorials about HTML, CSS, PHP, Javascript, and Photoshop

  • Home
    Home This is where you can find all the blog posts throughout the site.
  • Categories
    Categories Displays a list of categories from this blog.
  • Tags
    Tags Displays a list of tags that has been used in the blog.
  • Archives
    Archives Contains a list of blog posts that were created previously.
  • Login
5174

We continue our exploration of the , In this tutorialNSFetchedResultsControllerClass by adding the ability to update and delete to-do items. You'll notice that updating and deleting to-do items is surprisingly easy thanks to the groundwork we laid in theprevious tutorial

1. Updating a Record's Name

Step 1: Create View Controller

Start by creating a newUIViewControllerSubclass named TSPUpdateToDoViewControllerIn TSPUpdateToDoViewController. HDeclare an outletTextFieldOf typeUITextFieldAnd two propertiesManagedObjectContextOf typeNSManagedObjectContextAnd RecordOf type NSManagedObjectAdd an import statement for the Core Data framework at the top

#import <UIKit/UIKit. H>

#import <CoreData/CoreData. Nonatomic) NSManagedObjectContext *managedObjectContext;

@property (strong, nonatomic) NSManagedObject *record;

@end, h>

@interface TSPUpdateToDoViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *textField;

@property (strong

In the view controller's implementation file,  TSPUpdateToDoViewController. MCreate two actionsCancel:AndSave:Their implementations can remain empty for the time being

#import "TSPUpdateToDoViewController.h"

@implementation TSPUpdateToDoViewController

#pragma mark -
#pragma mark Actions
- (IBAction)cancel:(id)sender{
    
}- (IBAction)save:(id)sender{
    
}@end

Step 2: Update Storyboard

 , Open the main storyboardMain. StoryboardAdd a new view controller object, and set its class toTSPUpdateToDoViewControllerIn theIdentity InspectorCreate a manual segue from theTSPViewControllerClass to theTSPUpdateToDoViewControllerClass. In the Attributes InspectorSet the segue's style toPush and its identifier to UpdateToDoViewController

Add aUITextFieldObject to the view of the TSPUpdateToDoViewControllerObject and configure it just like we did with the text field of the TSPAddToDoViewControllerClass. Don't forget to connect the view controller's outlet with the text field

As in the TSPAddToDoViewControllerClass, add two bar button items to the view controller's navigation bar, set their identities to CancelAnd SaveRespectively, and connect each bar button item to the corresponding action in the Connections Inspector

Step 3: Passing a Reference

We also need to make a few changes to the TSPViewControllerClass.  Add an import statement for the TSPUpdateToDoViewController class at the top and declare a property named SelectionOf typeNSIndexPathTo the private class extension inTSPViewController. M

#import "TSPViewController.h"

#import <CoreData/CoreData. H>

#import "TSPToDoCell.h"
#import "TSPAddToDoViewController.h"
#import "TSPUpdateToDoViewController.h"

@interface TSPViewController () <NSFetchedResultsControllerDelegate>

@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;

@property (strong, nonatomic) NSIndexPath *selection;

@end

Next, implement theTableView:didSelectRowAtIndexPath:Method of theUITableViewDelegateProtocol. We temporarily store the user's selection in the , In this methodSelection property

#pragma mark -
#pragma mark Table View Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    // Store Selection
    [self setSelection:indexPath];
}

In the class's PrepareForSegue:sender:We fetch the record that corresponds with the user's selection and pass it to the TSPUpdateToDoViewControllerInstance. To prevent any unexpected behavior, we only perform this step if selection isn't Nil and reset theSelectionProperty after fetching the record from the fetched results controller

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue. Identifier isEqualToString:@"addToDoViewController"]) {
        // Obtain Reference to View Controller
        UINavigationController *nc = (UINavigationController *)[segue destinationViewController];
        TSPAddToDoViewController *vc = (TSPAddToDoViewController *)[nc topViewController];
        
        // Configure View Controller
        [vc setManagedObjectContext:self. ManagedObjectContext];
        
    }Else if ([segue. Identifier isEqualToString:@"updateToDoViewController"]) {
        // Obtain Reference to View Controller
        TSPUpdateToDoViewController *vc = (TSPUpdateToDoViewController *)[segue destinationViewController];
        
        // Configure View Controller
        [vc setManagedObjectContext:self. ManagedObjectContext];
        
        if (self. Selection) {
            // Fetch Record
            NSManagedObject *record = [self. FetchedResultsController objectAtIndexPath:self. Selection];
            
            if (record) {
                [vc setRecord:record];
            }// Reset Selection
            [self setSelection:nil];
        }
    }
}

Step 4: Populating the Text Field

In theViewDidLoadMethod of the TSPUpdateToDoViewControllerPopulate the text field with the name of the record as shown below, class

#pragma mark -
#pragma mark View Life Cycle
- (void)viewDidLoad {
    [super viewDidLoad];
    
    if (self. Record) {
        // Update Text Field
        [self. TextField setText:[self. Record valueForKey:@"name"]];
    }
}

Step 5: Updating the Record

In theCancel:Action, we pop the update view controller from the navigation controller's navigation stack

- (IBAction)cancel:(id)sender {
    // Pop View Controller
    [self. NavigationController popViewControllerAnimated:YES];
}

In theSave:Action, we first check if the text field is empty and show an alert view if it is. We update the record's, If the text field contains a valid valueNameAttribute and pop the view controller from the navigation controller's navigation stack

- (IBAction)save:(id)sender {
    // Helpers
    NSString *name = self. TextField. Text;
    
    if (name && name. Length) {
        // Populate Record
        [self. Record setValue:name forKey:@"name"];
        
        // Save Record
        NSError *error = nil;
        
        if ([self. ManagedObjectContext save:&error]) {
            // Pop View Controller
            [self. NavigationController popViewControllerAnimated:YES];
            
        }Error, else {
            if (error) {
                NSLog(@"Unable to save record.");
                NSLog(@"%@, %@", error. LocalizedDescription);
            }// Show Alert View
            [[[UIAlertView alloc] initWithTitle:@"Warning" message:@"Your to-do could not be saved." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
        }
        
    }Else {
        // Show Alert View
        [[[UIAlertView alloc] initWithTitle:@"Warning" message:@"Your to-do needs a name." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    }
}

This is all it takes to update a record using Core Data.  Run the application once more and see if everything is working. The , The fetched results controller automatically detects the change and notifies its delegateTSPViewControllerInstance. TheTSPViewController updates the table view to reflect the change, on its turn, object. It's that easy

2. Updating a Record's State

Step 1: Updating TSPToDoCell

When a user taps the button on the right of aTSPToDoCellThe item's state needs to change. To accomplish this, we first need to update theTSPToDoCellClass. Open TSPToDoCell. MAnd add aTypedefFor a block named TSPToDoCellDidTapButtonBlockNext, declare a property of type TSPToDoCellDidTapButtonBlockAnd make sure the property is copied on assignment

#import <UIKit/UIKit. H>

typedef void (^TSPToDoCellDidTapButtonBlock)();

@interface TSPToDoCell : UITableViewCell

@property (weak, nonatomic) IBOutlet UIButton *doneButton;

@property (copy, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) TSPToDoCellDidTapButtonBlock didTapButtonBlock;

@end

 , Head to the class's implementation fileTSPToDoCell. MAnd invokeSetupViewA helper method, inAwakeFromNib

#pragma mark -
#pragma mark Initialization
- (void)awakeFromNib {
    [super awakeFromNib];
    
    // Setup View
    [self setupView];
}

InSetupViewWe configure theDoneButtonObject by setting images for each state of the button and adding the table view cell as a target. When the user taps the button, the table view cell is sent a message ofDidTapButton:In which we invoke theDidTapButtonBlockBlock. You'll see in a moment how convenient this pattern is. The images are included in thesource filesOf this tutorial, which you can find onGitHub

#pragma mark -
#pragma mark View Methods
- (void)setupView {
    UIImage *imageNormal = [UIImage imageNamed:@"button-done-normal"];
    UIImage *imageSelected = [UIImage imageNamed:@"button-done-selected"];
    
    [self. DoneButton setImage:imageNormal forState:UIControlStateNormal];
    [self. DoneButton setImage:imageNormal forState:UIControlStateDisabled];
    [self. DoneButton setImage:imageSelected forState:UIControlStateSelected];
    [self. DoneButton setImage:imageSelected forState:UIControlStateHighlighted];
    [self. DoneButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
}
#pragma mark -
#pragma mark Actions
- (void)didTapButton:(UIButton *)button {
    if (self. DidTapButtonBlock) {
        self. DidTapButtonBlock();
    }
}

Step 2: Updating TSPViewController

Thanks to the NSFetchedResultsControllerClass and the foundation we've laid, we only need to update the ConfigureCell:atIndexPath:Method in the TSPViewControllerClass

- (void)configureCell:(TSPToDoCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    // Fetch Record
    NSManagedObject *record = [self. FetchedResultsController objectAtIndexPath:indexPath];
    
    // Update Cell
    [cell. NameLabel setText:[record valueForKey:@"name"]];
    [cell. DoneButton setSelected:[[record valueForKey:@"done"] boolValue]];
    
    [cell setDidTapButtonBlock:^{
        BOOL isDone = [[record valueForKey:@"done"] boolValue];
        
        // Update Record
        [record setValue:@(. IsDone) forKey:@"done"];
    }];
}

Step 3: Saving Changes

You may be wondering why we aren't saving the managed object context. Won't we lose the changes we've made if we don't commit the changes to the persistent store. Yes and no

It is true that we need to write the changes of the managed object context to the backing store at some point. If we don't, the user will lose some of its data. There is no need to save the changes of a managed object context every time we make a change, However

A better approach is to save the managed object context the moment the application moves to the background. We can do this in the ApplicationDidEnterBackground:Method of the UIApplicationDelegate protocol.  OpenTSPAppDelegate. MAnd implement ApplicationDidEnterBackground:As shown below

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSError *error = nil;
    
    if (. [self. Error, error, managedObjectContext save:&error]) {
        if (error) {
            NSLog(@"Unable to save changes.");
            NSLog(@"%@, %@". LocalizedDescription);
        }
    }
}

This doesn't work if the application is force quit by the user, However. It's therefore a good idea to also save the managed object context when the application is terminated. The ApplicationWillTerminate:Method is another method of theUIApplicationDelegateProtocol that notifies the application't delegate when the application is about to be

- (void)applicationWillTerminate:(UIApplication *)application {
    NSError *error = nil;
    
    if (. [self. Error, error, managedObjectContext save:&error]) {
        if (error) {
            NSLog(@"Unable to save changes.");
            NSLog(@"%@, %@". LocalizedDescription);
        }
    }
}

Note that we have duplicate code inApplicationDidEnterBackground:And ApplicationWillTerminate:It's therefore a good idea to create a helper method to save the managed object context and call this helper method in both delegate methods

#pragma mark -
#pragma mark Helper Methods
- (void)saveManagedObjectContext {
    NSError *error = nil;
    
    if (. [self. Error, error, managedObjectContext save:&error]) {
        if (error) {
            NSLog(@"Unable to save changes.");
            NSLog(@"%@, %@". LocalizedDescription);
        }
    }
}

3. Deleting Records

You'll be surprised by how easy it is to delete records using the NSFetchedResultsControllerClass. Start by implementing the TableView:canEditRowAtIndexPath:Method of theUITableViewDataSourceProtocol

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

The second method of the UITableViewDataSource protocol that we need to implement isTableView:commitEditingStyle:forRowAtIndexPath:In this method we fetch the managed object the user has selected for deletion and pass it to theDeleteObject:Method of the managed object context of the fetched results controller

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        NSManagedObject *record = [self. FetchedResultsController objectAtIndexPath:indexPath];
        
        if (record) {
            [self. FetchedResultsController. ManagedObjectContext deleteObject:record];
        }
    }
}

Because we've already implemented the NSFetchedResultsControllerDelegateThe user interface is automatically updated, protocol, animations included

Conclusion

I hope you agree that the NSFetchedResultsControllerClass is a very convenient member of the Core Data framework. If you understand the basics of the Core Data framework, then it's not difficult to get up to speed with this class. I encourage you to further explore theits API to find out what else it can do for you

Continue reading
0
5954

Inthe first post in this seriesWe reviewed some of the strategies that are available as it relates to organizing our WordPress theme directories in order to make them more maintainable

We touched on how to organize:, Specifically

  1. JavaScript and CSS files
  2. Templates and Temple Parts
  3. Translations
  4. Helper Files and Utility Functions
  5. Third-Party Libraries

Ultimately, and maintainability to the work that we're doing, the goal was to provide a directory schema in which we could organize our files such that we would have a level of cohesion, understanding

But that's not all there is to writing maintainable WordPress themes. Another is to follow the conventions set forth by the WordPress Coding Standards; however, this article is not going to be taking a look at the standards.  The Codex does a fine job of that and we've covered themin another series

Instead, we're going to take a slightly more granular look at some of the strategies and tools that we can use in order to make sure we're making our themes as maintainable as possible. We're going to look at strategies, In this particular post, then we'll wrap up the series by looking at some of the available tools

Increasing Maintainability

And libraries that make up your theme; however, that's really only half of what goes into constructing a theme, it's one thing to have a logical way to organize all of the directories, files, As previously mentioned

After all, naming conventions, there are predefined templates, and tools all of which contribute to either making up the theme or to test to theme to make sure that it's not using any deprecated API calls and that it's compatible with the most recent version of WordPress, functions

To that end, I think it's important to understand each of these topics both as someone who is just starting out in building WordPress themes, and as someone who has been doing it for some time

There's never a better time to try to get better at what you're doing, After all. Besides, the comments are also a great place to continue to the discussion to share alternative ideas

Let's actually talk about some practical things we can do as it relates to WordPress theming, and that increases the maintainability of what we're doing, But before we get there

Templates

If you're not familiar with the template hierarchy, First, then I highly recommend you readthe corresponding Codex pageBefore continuing with this article

In short, templates can be described as the following:

WordPress Templates fit together like the pieces of a puzzle to generate the web pages on your WordPress site. Some templates (the header and footer template files for example) are used on all the web pages, while others are used only under specific conditions

Everything that's displayed to the user is coming from the database, Another way of thinking about it is this: When it comes to WordPress. Comments, This includes the post title, post content, post categories, post data, and so on

Templates are the vehicles through which the information is presented to the user. Styled via CSS, and may have some effects applied through the use of JavaScript, They're composed of markup and PHP

Assuming you aren't doing anything advanced with things such as custom post types and/or custom taxonomies (a topic for another post, really), then there's one unique thing about the WordPress Template Hierarchy that can serve as both a luxury and a problem of maintenance depending on how you look at it, But

Note that in the linked Codex article, WordPress will have defaultIndex. PhpHeader. PhpAndFooter. PhpTemplates. The truth is, you can really get away with creating an entire WordPress theme by using OnlyIndex. Php

Right, Sounds kind of attractive. Lean theme that does everything you need it do, I mean, small, who needs to create so many different files when you can create a nice

But then think about this from the perspective of working with a team of either your own peers, of those who may be contributing to an open repository, or of those who may eventually pick up where you left off

If all of the code required to render information from the database is contained within a single file, the odds that the complexity of that file increasing from its very creation are very high

Possible some switch statements and so on, At the most basic level, we're looking at a single file with a number of conditionals. The primary listing, the single post pages, And all of this is done because you've got to account for the archive pages, and so on, the single posts

See what I mean

Creating a separate files , But thenJustTo mitigate that complexity can be a beast all on its own. For example, let's say that you haveSingle. PhpFor displaying a single post and you havePage. PhpFor displaying a page and both of the templates have the exact same information Except Single. PhpHas post meta data andPage. PhpDoes not

This is a prime example of when you may extract post meta data into its own partial, as we discussed in the previous article. Or perhaps you may extract the code responsible for rendering the content into Its Own partial

Whatever the case may be, the point that I'm trying to make is that there is a balance to be struck when it comes with working with WordPress templates and the WordPress Template Hierarchy

I don't think it's a good idea to use a minimal number of templates, but I also don't think it's necessary to use every single supported template IfIt leads to too much code duplication. There IsA balance to be struck between templates and partials

But having this frame of mind from the outset can help provide a level of organization and maintainability that's leaps and bounds ahead of where you , I think that the experience on how to use which comes with time, UltimatelyMayStart having not done just a little bit of pre-planning

Naming Functions

When it comes to working with functions and naming conventions, there are usually two rules of thumb to follow:

  1. Uniquely name your functions
  2. Properly name them to indicate what willReturnData and what willEchoData

Or you're someone who's looking to improve their skills in doing this, But if you're someone who's just getting started in theme development, what does this practically look like

The reasons for doing this are so that you don't accidentally collide with a pre-existing function that's defined within WordPress or a plugin that may be running within your environment, When it comes to uniquely naming your functions

Let's say that you're going to be writing your own function that will filter the content before rendering it to the page, For example. The correct way to do this is to obviously use the Add_filterFunction; however, would you name your functionThe_content

Of course not, No. The reason is because WordPress already definesThe_contentYou're going to get errors, If you rename a function with that name. To that end, it's always best to prefix your functions with a unique key such as a the name of your theme or something similar

So let's say that we want to prepend a signature to the end of our content, and let's say that we have a theme we're callingAcmeIn order to do this, I recommend creating a function calledAcme_the_contentBecause it identifies the name of the theme and indicates the name of the function to which it's hooked

Let's say that you want to end each post with your name and Twitter handle. You may define this in your, To do thisFunctions. PhpFile:

<. At the end of the post content, and Twitter handle, php

add_filter( 'the_content', Tom, * @tommcfarlin, 'acme_the_content' );
/**
 * Adds a signature with my name. *
 * @since   1. 0
 * @package Acme
 * @param   string    The original post content. * @return  string    The content of the post with my signature. */
function acme_the_content( $content ) {

    $signature = '<p class="post-signature">';
        $signature. = 'Tom. @tommcfarlin';
    $signature. = '</p><. --. Post-signature -->';
    
    return $content. = $signature;

}

Isn't it, It's relatively straightforward. The short of it is that I try to use the follow format when creating my own hooked functions:Theme_name-name_of_hook

This makes it really easy to understand and follow not only when browsing the code but when viewing the functions that make up the theme or the file that's currently active in your IDE

Returning and Echoing Data

As mentioned earlier, WordPress has another convention that it uses and that has to do with if information is going to be returned from the called function or echoed from the called function

Luckily, this particular rule of thumb is really easy to remember:

  • Functions that return data are prefixed withGet_
  • Functions that echo data are not prefixed withGet_

You may find SomeAnomalies, but that's the general gist of how the WordPress API indicates how it will be giving the data back to you once you've called the function

Then you would simply call, In keeping consistent with our prior example, let's that that you want to echo the data when the function is calledThe_content()From within a custom loop, then you would call, ; however, if you want to retrieve the content from a post, sayGet_the_content()And store it in your own variable

Perhaps something like this:$content_for_later = get_the_content()Now you've read the data for the content that's currently related to the active post inThe LoopAnd you've stored it in a variable that you can use later

But knowing how WordPress names its functions for you is only half of it. Right, you're planning to build a theme or a plugin and want to make sure that you're keeping consistent with "the WordPress way" of doing things, After all

Case in point: In the example given above, where we filtered the content, we prefixed the function such that it was namedAcme_the_contentWhich indicates that theAcmeTheme is going to return the content from wherever it is called within the theme

What were to happen if we were to name the functionAcme_get_the_content()

It would be inconsistent with the WordPress API because the developer would be expecting the data to be returned to them such that they'd be able to store it in a variable or pass it to another function, technically speaking the function would still echo the data wherever it was called; however, Well

There's a mismatch between what the user expects to happen and what actually happens

Then this would be like having a switch on the wall for which the user , If we were talking about something in the real worldExpectsThat when they flip the switch, perhaps nothing happens or a light or a fan in another room comes on, in reality, a light or a fan will come on in the same room when

And that indicate , we need to be careful with the naming of our functions so that we're not only writing functions that won't collide with other functions, To that end, but that are also clearly namedHowThey will be handling the information when the function is called

What About Helpful Tools?

There are also a number of tools and settings that we can install and configure in our development environment that will help us catch any potential problems before we end up launching our theme, As mentioned at the beginning of this article

This means that we'll be able to identify functions that are going to ultimately be removed from WordPress so that we don't write out-of-date code. Indexes that are defined, we there are ways to know if variables we're trying to access don't have, say, Additionally, or other similar features

In the final article in the series, we're going to take a look at exactly that. Review what's been covered here, feel free to share your questions and comments in the feed below, In the mean time, and then we'll look to wrap this series up next week

Continue reading
0
5444

Webhook is a new site building platform which was successfully backed on Kickstarter in May 2014 and has just been released to the public. The concept behind Webhook is, if you'll excuse my enthusiasm, absolutely brilliant.

One of the most often heard requests in many CMS communities is "How can I customize the system for (fill in the blank)". The guys behind Webhook wanted to address this common need, but instead of creating a new prebuilt CMS with the idea of making it extensible they took things one step further and asked, "What if we created a CMS builder instead?".

Webhook's most unique, and quite possibly game-changing, feature is that for every single site you can control exactly what type of content can be posted through simple drag and drop form builders in the admin area.

Let's say you need to publish podcasts but you don't want to deal with typical blog posts and pages. You'd create an "Episodes" content type and drag and drop fields through which you can enter episode names, type in your episode blurbs and upload podcast files.

Or, what if you do want a blog, but you also want a place to display a gallery of videos? You can do that too, just "drag and drop" yourself a traditional blog posting area plus another section dedicated to adding videos.

And how about if you need to create several different content types that have complex relationships with one another? Yes, you can do that also. Short of full blown e-commerce or membership management, whatever your site needs you can create the CMS interface to suit.

The underpinning principle of Webhook is you should have the ability to easily create a CMS with everything you need and nothing you don't, for every single project. Let's jump in and take a look at how Webhook works, and how you can get started with it.

Webhook: Local and Live

Webhook lives in two places:

  1. The development version on your local machine.
  2. The live site on the Webhook server, at (yoursite).webhook.org or at your own domain.

You install Webhook locally and build your site offline so you can get the admin area, presentation and content just how you want it. You then deploy it to the live server when you're ready. From that point on you can also continually update your site locally and redeploy as many times as you like.

Installing Webhook

Webhook installs in just a couple of minutes via command line through NPM

After ensuring Node.js is installed, fire up a terminal (Mac / Linux) or command prompt (Windows) and run the command:

npm install -g grunt-cli wh

Note: you might need to prepend that with the command "sudo" to gain admin privileges on Mac or Linux.

This downloads all Webhook's required files and sets up your computer so it can create and manage Webhook sites from command line.

Creating a Webhook Site

Once Webhook is installed you can go ahead and create a site by running the command:

wh create your_site_name

In your terminal you'll then need to enter the email address you have associated with Webhook, and either set or enter your password:

When the site setup is complete you'll see a confirmation:

What just happened?  You'll find a folder has been created locally with the name you specified during creation and the required files have been downloaded into it:

You're now ready to run your local Webhook server so you can work on your site offline before deploying it live. To do this, first go to your site's folder in your terminal by entering:

cd your_site_name

Then run the command:

wh serve

Your local site will automatically open in your default browser, and you'll see:

When you click on the Looking for the CMS? link you'll have to login, then you'll be taken to a screen where you have to decide whether to go with an existing Webhook theme or create one from scratch:

Webhook themes play quite a different role to those on other platforms, so before you proceed you need to understand what you're actually choosing when you select a Webhook theme or elect to build a new one.

Why Webhook Themes are Different

On a traditional CMS themes control the way a site looks, while the types of content and their methods of entry are separately managed. However on Webhook the theme determines not only presentation but the type of content the site accepts, as well as how that content is posted via the admin panel.

For example, if you install the prebuilt "Podcast and blog" theme you'll get a purpose designed front-end with inclusions such as iTunes links, audio players, download links and so on:

But you'll also get a purpose designed admin area specific to the type of content you'll be posting on your site:

Notice the podcast related content types "Cast Members", "Episodes" and "Podcast Details". Note also how in the above screenshot of the "Episodes" posting interface it only has fields specific to posting a podcast i.e. episode number, audio file insertion etc. You don't have to work around any superflous content entry fields that won't be used.

Both the front-end elements and what you see in the back end are controlled by the theme. So in a sense, for each Webhook site the theme is the CMS.

In a lot of ways this makes a great deal of sense. On any platform a theme has to align itself with the type of content that can be posted on the site. On a traditional CMS if a new custom content type is added, by a plugin for example, it often can't be used if a theme doesn't support it. Webhook's approach makes absolutely sure that the theme and content types of a site match up perfectly by containing them in the same system.

Starting From a Prebuilt Theme

In future tutorials we'll be covering in depth how to build out your own custom Webhook themes, so for the purposes of introducing you to the platform I went with the prebuilt option. This gave me eight themes to choose from.

I selected "Bootstrap blog" which is a simple blogging theme styled with Bootstrap which gives you the content types "Articles" and "About Me" in the admin area:

After adding some content the front-end of my local site looked like this:

Deploying to Your Live Site

Taking what you've created locally and deploying it to your live site is also incredibly easy. Open up a terminal in the folder housing your local site and run the command:

wh deploy

Note: I found it easiest to open up a second terminal to do this, because that let me leave the first terminal I opened running my local Webhook site process.

When deployment is successful you'll get a confirmation in your terminal along with a reminder of the URL at which you can see your live site:

You can visit the demo site created above at http://tutsplusdemo.webhook.org/

Basic Admin / Content Type Customization

As I mentioned earlier we'll be giving you full tutorials on Webhook theming in the near future. Nonetheless let's still take a look at a simple example of customizing one of our site's content types so you get an idea of how easy it can be.

By default the "About Me" page shows a heading and a bio:

We're going to add the ability to append a website URL to the end of the page.

Adding a New Content Entry Field / Widget

Head to the admin area and click Add / Edit Content Types in the left sidebar:

You'll be shown a list of the existing content types:

Click the About me entry and you'll be taken to the form building system. On the left side of the screen you'll see all the different types of content entry fields you can add, called "Widgets" in Webhook terminology. Find the Website widget in the Specifics section then drag it from the left side and drop it on the right:

Click the Save Form button at the bottom right of the interface:

After saving is complete you'll be taken to the content entry form you just updated, where you'll see your new Website field. When you hover over the field you'll also be shown the tag you should use to display its contents through the appropriate template file:

Update the Template File

In the "pages" subfolder of this theme is the template file "about.html" that controls the presentation of the "About Me" page. We'll cover more on how the templating system works in our full Webhook theming tutorial.

For now, you can just open up the "about.html" file and locate the line reading:

{{ about.body|safe }}

Under that line add the following:

<a href="/{{ about.website }}" target="_blank">{{ about.website }}</a>

At which point you'll see your new link below the main text:, Save the file and your local site will detect the change and automatically refresh

More Awesome Webhook Features

After experiencing all the functionality I've described above I was already hugely impressed by Webhook and busily thinking of applications I could use it for, yet I still found even more features which continued to pique my interest

Themes Allow Leveraging NPM

Presentation and the admin area, With Webhook themes not only can you control data, but you can also have them fetchNPM packagesDuring installation

This means you can do things like pull in a preprocessor package so LESS / Sass / Stylus files can be compiled on the fly. Whenever you serve your local Webhook site it runs a "grunt watch" task so you can customize the included Gruntfile to handle these types of operations

JQuery plugins and anything else useful you might find among the, It also means you can incorporate any tools that are available via NPM such as preprocessor frameworksnear 80,000 available packages

You Can Even Style the Admin Area

You also have control over the way it's styled, Because the entire CMS is controlled from within your theme folder. All you have to do is add a link to your own custom CSS in the template filePages/cms. HtmlYou could add your own styling to make text fields wider than their default:, For example

And of course you could also put together some more comprehensive styling to completely rework the color scheme, typography and anything else you choose

Learn More

Check out these great screencasts demonstrating what Webhook can do:

Other Useful Links:

Continue reading
0
3798

We will continue to focus on our business logic, In this tutorial. We will evaluate ifRunnerFunctions. PhpTo which class, belongs to a class and if so. We will think about concerns and where methods belong. We will learn a little bit more about the concept of mocking, Finally. What are you waiting for, So. Read on


RunnerFunctions - From Procedural to Object Oriented

Nicely organized in classes, Even though we have most of our code in object oriented form, some functions are just simply sitting in a file. We need to take some in order to give the functionsRunnerFunctions. PhpIn a more object oriented aspect

$maxAnswerId = MAX_ANSWER_ID) {
	return rand($minAnswerId, $maxAnswerId), const WRONG_ANSWER_ID = 7;
const MIN_ANSWER_ID = 0;
const MAX_ANSWER_ID = 9;

function isCurrentAnswerCorrect($minAnswerId = MIN_ANSWER_ID. = WRONG_ANSWER_ID;
}5) + 1;
		$aGame->roll($dice);, function run() {
	$display = new CLIDisplay();
	$aGame = new Game($display);
	$aGame->add("Chet");
	$aGame->add("Pat");
	$aGame->add("Sue");

	do {
		$dice = rand(0
	}While (. IsCurrentAnswerCorrect()));, didSomebodyWin($aGame
}Function didSomebodyWin($aGame, $isCurrentAnswerCorrect) {
	if ($isCurrentAnswerCorrect) {
		return. $aGame->wasCorrectlyAnswered();
	}Else {
		return. $aGame->wrongAnswer();
	}
}

My first instinct is to just wrap them in a class. But it is something that makes us start changing things, This is nothing genius. Let's see if the idea can actually work

$maxAnswerId = MAX_ANSWER_ID) {
		return rand($minAnswerId, $maxAnswerId), const WRONG_ANSWER_ID = 7;
const MIN_ANSWER_ID = 0;
const MAX_ANSWER_ID = 9;

class RunnerFunctions {

	function isCurrentAnswerCorrect($minAnswerId = MIN_ANSWER_ID. = WRONG_ANSWER_ID;
	}Function run() {
		//. //
	}Function didSomebodyWin($aGame, $isCurrentAnswerCorrect) {
		//. //
	}
}

We need to modify our tests and our, If we do thatGameRunner. PhpTo use the new class. Renaming it will be easy when needed, We called the class something generic for the time being. We don't even know if this class will exist on its own or will be assimilated intoGameSo don't worry about naming just yet

Private function generateOutput($seed) {
	ob_start();
	srand($seed);
	(new RunnerFunctions())->run();
	$output = ob_get_contents();
	ob_end_clean();
	return $output;
}

In ourGoldenMasterTest. PhpWe must modify the way we run our code, file. The function isGenerateOutput()And its third line needs to be modified to create a new object and callRun()On it. But this fails

PHP Fatal error:  Call to undefined function didSomebodyWin() in

We now need to modify our new class further

Do {
	$dice = rand(0, 5) + 1;
	$aGame->roll($dice);
}While (. $this->didSomebodyWin($aGame, $this->isCurrentAnswerCorrect()));

We only needed to change the condition of theWhileStatement in theRun()Method. The new code callsDidSomebodyWin()AndIsCurrentAnswerCorrect()From the current class, by prepending$this->To them

But it brakes the runner tests, This makes the golden master pass

PHP Fatal error:  Call to undefined function isCurrentAnswerCorrect() in /. /RunnerFunctionsTest. Php on line 25

The problem is inAssertAnswersAreCorrectFor()But easily fixable by creating a runner object first

Private function assertAnswersAreCorrectFor($correctAnserIDs) {
	$runner = new RunnerFunctions();
	foreach ($correctAnserIDs as $id) {
		$this->assertTrue($runner->isCurrentAnswerCorrect($id, $id));
	}
}

This same issue needs to be addressed in three other functions as well

WRONG_ANSWER_ID));, function testItCanFindWrongAnswer() {
	$runner = new RunnerFunctions();
	$this->assertFalse($runner->isCurrentAnswerCorrect(WRONG_ANSWER_ID
}Function testItCanTellIfThereIsNoWinnerWhenACorrectAnswerIsProvided() {
	$runner = new RunnerFunctions();
	$this->assertTrue($runner->didSomebodyWin($this->aFakeGame(), $this->aCorrectAnswer()));
}Function testItCanTellIfThereIsNoWinnerWhenAWrongAnswerIsProvided() {
	$runner = new RunnerFunctions();
	$this->assertFalse($runner->didSomebodyWin($this->aFakeGame(), $this->aWrongAnswer()));
}

It introduces a bit of code duplication, While this makes the code pass. We can extract the runner creation into a, As we are now with all tests on greenSetUp()Method

Private $runner;

function setUp() {
	$this->runner = new Runner();
}Function testItCanFindCorrectAnswer() {
	$this->assertAnswersAreCorrectFor($this->getCorrectAnswerIDs());
}WRONG_ANSWER_ID));, function testItCanFindWrongAnswer() {
	$this->assertFalse($this->runner->isCurrentAnswerCorrect(WRONG_ANSWER_ID
}$this->aCorrectAnswer()));, function testItCanTellIfThereIsNoWinnerWhenACorrectAnswerIsProvided() {
	$this->assertTrue($this->runner->didSomebodyWin($this->aFakeGame()
}Function testItCanTellIfThereIsNoWinnerWhenAWrongAnswerIsProvided() {
	$this->assertFalse($this->runner->didSomebodyWin($this->aFakeGame(), $this->aWrongAnswer()));
}$id));, private function assertAnswersAreCorrectFor($correctAnserIDs) {
	foreach ($correctAnserIDs as $id) {
		$this->assertTrue($this->runner->isCurrentAnswerCorrect($id
	}
}

Nice. All these new creations and refactorings got me thinking. We named our variableRunnerMaybe our class could be called the same. Let's refactor it. It should be easy

If you didn't check "Search for text occurrences" in the box above, because the refactoring will rename the file also, don't forget to change your includes manually

Now we have a file calledGameRunner. PhpAnother one namedRunner. PhpAnd a third one namedGame. PhpI don't know about you, but this seems extremely confusing to me. I would have no idea which one does what, If I was to see these three files for the first time in my life. We need to get rid of at least one of them

The reason we created theRunnerFunctions. PhpWas to build up a way to include all the methods and files for testing, file in the early stages of our refactoring. We needed access to everything, but not run everything unless in a prepared environment in our golden master. Just not run our code from, We can still do the same thingGameRunner. PhpBefore we continue, We need to update the includes and create a class inside

Require_once __DIR__. '/Display. Php';
require_once __DIR__. '/Runner. Php';
(new Runner())->run();

That will do it. We need to includeDisplay. PhpExplicitly, so whenRunnerTries to create a newCLIDisplayIt will know what to implement


Analyzing Concerns

I believe that one of the most important characteristics of object oriented programming is defining concerns. "is this class doing what its name says?", "Is this method of concern for this object?", "Should my object care about that specific value?", I always ask myself questions like

These types of questions have a great power in clarifying both business domain and software architecture, Surprisingly. We are asking and answering these types of questions in a group at Syneto. He or she just stands up, asks for two minutes of attention from the team in order to find our opinion on a subject, Many times when a programmer has a dilemma. Those who are familiar with the code architecture will answer from a software point of view, while others, more familiar with the business domain may shed light on some essential insights about commercial aspects

Let's try to think about concerns in our case. We can continue to focus on theRunnerClass. Than, It is hugely more probable to eliminate or transform this classGame

Should a runner care about how, FirstIsCurrentAnswerCorrect()Working. Should a runner have any knowledge about questions and answers

It really seems like this method would be better off inGameI strongly believe that aGameAbout trivia should care if an answer is correct or not. I truly believe aGameMust be concerned about providing the result of the answer for the current question

It's time to act. We will do aMove methodRefactoring. I will just show you the end result, As we've seen this all before from my previous tutorials

Require_once __DIR__. '/CLIDisplay. Php';
include_once __DIR__. '/Game. Php';

class Runner {

	function run() {
		//. //
	}$isCurrentAnswerCorrect) {
		//, function didSomebodyWin($aGame. //
	}
}

But the constant defining the answer's limits also, It is essential to note that not only the method went away

But what aboutDidSomebodyWin()Should a runner decide when someone has won. If we look at the method's body, we can see a problem highlighting like a flashlight in the dark

$isCurrentAnswerCorrect) {
	if ($isCurrentAnswerCorrect) {
		return, function didSomebodyWin($aGame. $aGame->wasCorrectlyAnswered();
	}Else {
		return. $aGame->wrongAnswer();
	}
}

It does it on a, Whatever this method doesGameObject only. It verifies the current answer returned by game. Then it returns whatever a game object returns in itsWasCorrectlyAnswered()OrWrongAnswer()Methods. This method effectively does nothing on its own. All it cares about isGameThis is a classic example of a code smell calledFeature EnvyA class does something that another class should do. Time to move it

Class RunnerFunctionsTest extends PHPUnit_Framework_TestCase {

	private $runner;

	function setUp() {
		$this->runner = new Runner();
	}

}

As usual, we moved the tests first. TDD. Anyone

So this file can go now, This leaves us with no more tests to run. Deleting is my favorite part of programming

We get a nice error, And when we run our tests

Fatal error: Call to undefined method Game::didSomebodyWin()

It's now time to change the code as well. Copying and pasting the method intoGameWill magically make all the tests pass. Both the old ones and the ones moved toGameTestBut while this puts the method in the right place, it has two problems: the runner also needs to be changed and we send in a fakeGameObject which we do not need to do anymore since it is part ofGame

5) + 1;
	$aGame->roll($dice);, do {
	$dice = rand(0
}While (. $aGame->didSomebodyWin($aGame, $this->isCurrentAnswerCorrect()));

Fixing the runner is very easy. We just change$this->didSomebodyWin(. )Into$aGame->didSomebodyWin(. )After our next step, We will need to come back here and change it again. The test refactoring

Function testItCanTellIfThereIsNoWinnerWhenACorrectAnswerIsProvided() {
	$aGame = \Mockery::mock('Game[wasCorrectlyAnswered]');
	$aGame->shouldReceive('wasCorrectlyAnswered')->once()->andReturn(false);
	$this->assertTrue($aGame->didSomebodyWin($this->aCorrectAnswer()));
}

It's time for some mocking. We will use, Instead of using our fake class, defined at the end of our testsMockeryIt allows us to easily overwrite a method onGameExpect it to be called and return the value we want. We could to this by making our fake class extend, Of courseGameAnd overwrite the method ourselves. But why do a job for which a tool exists

Function testItCanTellIfThereIsNoWinnerWhenAWrongAnswerIsProvided() {
	$aGame = \Mockery::mock('Game[wrongAnswer]');
	$aGame->shouldReceive('wrongAnswer')->once()->andReturn(true);
	$this->assertFalse($aGame->didSomebodyWin($this->aWrongAnswer()));
}

We can get rid of the fake game class and any methods that initialized it, After our second method is rewritten. Problems solved

Final Thoughts

Even though we managed to think about only theRunnerWe made great progress today. We identified methods and variables that belong to another class, We learned about responsibilities. We thought on a higher level and we evolved toward a better solution. There is a strong belief that there are ways to write code well and never commit a change unless it made the code at least a little bit cleaner, In the Syneto team. This is a technique that in time, can lead to a much nicer codebase, with less dependencies, more tests and eventually less bugs

Thank you for your time

Continue reading
0
4193

Most people have the wrong idea about automation. They often think of a futuristic fantasy of robots that automatically do everything for you. That would be the ultimate in automation. More practically, automation is any assistance in performing related actions. Therefore, anytime you can get the computer to help in an activity is automation

Entering in numbers in to a spreadsheet and performing calculations with the numbers is a type of automation, For example. It's automation, Each time the spell checker corrects a misspelled word in the text. Even the notification of an email arriving is a type of automation

Taking advantage of automation is the mindset of looking for ways to have your computer help your activities. Unfortunately,  do not have the mindset to take advantage of automation, Most people. You think about how to do an activity and just do it. A mindset for automation has the thought of looking for anything performed more than two times as a candidate for automation

In order to make use of automation, you have to understand the different types of automation and how they work. With that knowledge, you will start looking for ways to put that knowledge in to action

Types of Automation

All automation comes in one of three types:Process automationTrigger automationAndHybrid automationEach of these have their own sub-types as well. By understanding these types and the applications used for these types of automation, you better know how to create an automation for your needs

Process Automation

Process automationIs the transforming one or more items in to a different item by a predetermined process. Taking a picture and transforming it in to a different file type with a set number of bit planes is a process automation, Therefore.  

The changing of a markdown text file to a HTML file is also a process automation. This would also encompass the moving of files from one place to another. You have, When you perform the process automation repetitivelyBatch automation

Trigger Automation

Trigger automationHappens when running a process upon an event. An event is anything that the computer has no direct control over, but can respond to it.  

In the real world, an alarm clock is the classic example of a trigger automation. All automations that follow aWhen… then…Construction is a trigger automation

I came up with six trigger automation subtypes:Time triggersState triggersText triggersHotkey triggersKeyword triggersAndExternal triggersI will describe each trigger subtype with it’sWhen… then…Description

Time Trigger

ATime triggerAutomation is any activation of a program on a timed interval. It can be as simple as a message about an upcoming meeting or a routine that launchesSkypeTo make the meeting possible.  

Polling a directory for new files and performing an action upon them is a time trigger automation.  

WhenA certain time interval or date happensThenPerform an action

State Trigger

State trigger automation is the process of over viewing a system and performing an action based on the systems change of state.  

A thermostat is a state trigger automation, In home automation. The thermostat triggers the turning off or on of the air conditioner, When the house temperature reaches a certain point.  

WhenThe computer is in a certain stateThenPerform an action

Text Trigger

Text triggerAutomation is a specialized form ofState triggerA text trigger automation only watches over the keyboard input to determine the sequence of the text typed. An appropriate action and/or text replacement gets performed, When a certain sequence gets detected.  

This is different fromKeyword triggersIn that this type of automation does not make use of a special input area. Any program that receives text can receiveText triggerAutomation.  

WhenThe user types a certain key sequence anywhereThenChange the text and/or perform an action

Hotkey Trigger

Hotkey triggerAutomation is another specialized form ofState triggerA hotkey trigger automation only watches for a certain combination of keys pressed together. It will perform a special action that gets assigned to that combination.  

All text editors make use of this type of automation.  WhenCertain keys get pressed togetherThenPerform a certain action

Keyword Trigger

AKeyword triggerAutomation is aText triggerAutomation in a specialized text input area. These can take extra input to perform the automation as well. The terminal is a keyword trigger automation, With this broad of a definition.  

WhenA certain text gets keyed in to a specialized text inputThenPerform an action with or without extra input from the user

External Trigger

AnExteral triggerAutomation is the triggering a process based on a stimulus from outside the program or computer. But responding to a stimulus, It is not monitoring. This is analogous to a clicker on a slide projector. The presenter clicks the clicker to get the projector to change slides.  

TheExternal triggerBut can be a service, does not have to be a device. Push notifications is an application ofExternal triggerAutomation.  

WhenAn event from outside the program or computer happensThenPerform a predetermined action

Hybrid Automation

Hybrid automationIs the combination of any of the aforementioned automation types put togetherHybrid automationIs generally the most powerful type of automation, but it often is the hardest to put together and maintain.  

The easiest form of hybrid automation is aSequential automation: one automation triggers another automation that is non-related. This differs fromBatch automationBy invoking a different type of automation

Programs for Automation

I’ll give you some programs to think about each type of automation and how you can put it to work for you, Now that I’ve explained the types of automation. This isn’t an exhaustive list of programs to use for each type of automation, but a short list to get you started

Process and Batch Automation

Two great program for doing generic process automation areDropZoneAndAlfredThese two programs allow you to process items and create customizations that fit a particular need. These programs also allow for simple batch processing of actions

Alfred Workflow for Project Management
Alfred Workflow for Project Management

Alfed WorklfowsExist for many task automations. ThePackalWebsite lists over 220 workflows. TheProject ManagementLaunching servers, workflow automates the creation of new web projects, and anything else I add to it. It’s my workhorse of project automation.  

You can see how the base of it created inAlfred Workflows for Advanced Users

Dropzone Compressing Images
Dropzone Compressing Images

DropzoneComes with several pre-built actions and the ability to add more. In the tutorialWriting Destinations for DropzoneYou see how to create an action to take any image and compress it to a smaller bit plane and to a different type. Once written for doing one image, it is automatically setup to run batch processing as well

TextSoap Cleaner Construction
TextSoap Cleaner Construction

TextSoapIs a process automation for text only. It allows you to change text in many ways: different types of cases, Markdown to HTML or Richtext, and custom text cleaners that’s built with an easy to use flowchart construction method.  

The tutorialHow to Effortlessly Create Markdown With TextSoapShows how to create text processing automations

Time Triggers

You have to have programs that know about time and can react to the time, To make use of time automation. A simple calendar program likeFantasticalIs great, but it does not automate an action. But does not help in the work you need to do, It gives great reminders

Clockwise Setting Up Action
Clockwise Setting Up Action

Programs like, ThereforeClockwiseAndAlfred CronMake for true automations. These programs allow you to run a script at a certain time point.  

ClockwiseWould be the program of choice for programming novices. It has many built-in actions along with user definable scripts.  Alfred CronIs for advanced users.  

The tutorial,  Use a Mac to Monitor Website Uptime or Other Regular TasksShows how to automate actions on time using these applications

State Triggers

There is only one trueState TriggerProgram for the Mac that I know about:ControlPlane

ControlPlaneWorks by monitoring many factors in your Mac to determine the current state. Scripts can execute to automate the Mac, Once the state changes. The tutorialTake Control With ControlPlaneShows how to use this program to automatically turn on and off file sharing based on location

A limited form ofState TriggeringHappens with programs likeLiveReloadAndHazelThese programs are known asFile State TriggeringAutomations. They watch the state of certain files. When their state changes (ie. Changed by a save file action), then they perform a pre-defined action

LiveReload
LiveReload

LiveReloadRecompiles web centric resources. Therefore, if you useCompassOrSASSSet live reload to monitor your directories, or many other web centric pre-compilers in your project. It automatically recompiles them and reloads the change in to your browser, Anytime you change a file in those directories

Hazel
Hazel

WhileLiveReloadDoes a specific type of file processingHazelIs more generic. It polls predetermines files for a large number of possible changes and performs an action.  

You can configureHazelTo function likeLiveReloadAnd more, though HazelIs not as responsive asLiveReloadFor this type of functionality because of it’s polling nature

Text Triggers

When I needText TriggersI reach toTextExpanderTo fill that area. Combined withPopClipAnd theTextExpander ExtensionI can create text expanders quickly

TextExpander Selecting Text
TextExpander: Selecting Text

You can select the text you want to expand and select theTextExpander ExtensionInPopClip

TextExpander Assigning Expanding Key
TextExpander: Assigning Expanding Key

Set the key trigger. I use;qAs my default work expander that I do not keep. I can type, to repeat that sequence of text, Now;qAnd it expands. When done, delete it or set it to a unique expansion text for future use.  

You can be sure that it will not get triggered by normal typing, By using a semi-colon before the letter sequence. This saves a lot of typing

Hotkey Triggers

For Hotkey TriggersKeyboard MaestroIs my main application withAlfredDoing the rest

Coupled withShortCatA program that allows you to select interface features solely from the keyboard, you can make some interesting automations

Keyboard Maestro and ShortCat Automating Web Forms
Keyboard Maestro and ShortCat Automating Web Forms

For example, one of my jobs is uploading video courses to Wistia and getting them formatted properly. I use aKeyboard MaestroHotkey action to create a new section in the course.  

In the aboveKeyboard MaestroA, dialogCmd-Up ArrowMoves to the top of the web page inChromeAShift-Command-SpaceCallsShortCatTo look for a field calledProject ActionThat opens a menu.  

The script callsShortCatAgain to select a menu item in that menu. What normally takes me several mouse moves is a single keyboard shortcut. That is automation at it’s finest

Keyword Triggers

To create a keyword Trigger, I mostly useAlfredBy creating a workflow for the actions needed. In anAlfredI can use any programing language I want to create the actions, workflow.  

The group of tutorials teaching the use of Alfred will help you learn to create keyword triggered actions: Alfred forBeginnersIntermediatesAdvancedAndAlfred Debugging

LaunchBar 5 AppleScript Actions
LaunchBar 5 AppleScript Actions

LaunchBarLikewise is useful in creating actions triggered by a keyword. In version 5, they had to beAppleScriptScripts.  

AnyAppleScriptScript placed in the~/Library/Application Support/LaunchBar/Actions/Directory is accessible inLaunchBarAs a keyword action

LaunchBar 6 Packaged Actions
LaunchBar 6 Packaged Actions

The latest version 6 ofLaunchBarAdds the ability to use any programming language to create scripts and a nice way to package all the needed information together

External Triggers

BothKeyboard MaestroAndAlfredAllow for programs other than itself to call their functions with anExternal Trigger 

Keyboard MaestroGoes one further and supports an internal Web Server to receive triggers from anywhere on the Internet. You can therefore have a computer somewhere on the Internet send a trigger event toKeyboard MaestroOn your computer

Alfreds External Trigger
Alfred’s External Trigger

Alfred’sExternal TriggerIs limited to a program that can run anAppleScriptScript to call it. When you define anExternal TriggerAlfred gives you the AppleScript code to use to call it

Hybrid Automation

SinceHybrid AutomationThere really is not a single application designed for this purpose, is the combining of multiple automation techniques together.  

As you can see from my list of applicationsAlfredIs in many of the categories. Since it is easy forAlfredCreating a, to call itselfHybrid AutomationIs very doable

Conclusion

I have explained computer automation and how to perform it on a Mac, In this tutorial.  

It’s up to you to transform your workflow to take advantage of automation. Just keep thinking: I can automate anything done more than twice

Continue reading
0

Testimonial

Thank you so much! We are very happy with our new website. It is easy to use and all of our customers tell us, they love it.

Contact Us

  • 13245 Atlantic Blvd. #4352
    Jacksonville, FL 32225
  • 904-240-5823