swpmvc_todos
文件大小: unknow
源码售价: 5 个金币 积分规则     积分充值
资源说明:Todos App tutorial for swpMVC framework
#swpMVC Todos

To get familiar with the swpMVC framework as quickly as possible, we'll walk through the obligatory Todo App example.

We'll leverage as much of the framework as is reasonable, and upon completion we'll aim to have the following pages in our
app:

*   The /todos url will show a listing of all users with todo lists, and each listing links to that users todo lists
*   The /mytodos url will show the todo list of the currently logged in user, or redirect to login page if logged out. When viewing
your own todo list, you will be able to add and edit items on your list.
*   The /todos/{{user\_nicename}} url will show a specific users todo list. When viewing another users todo list, it will be shown in a
read only mode

##Preparing the main plugin file

While swpMVC does not feature code generation, there is a template plugin included in the framework to get us started,
so we first copy the contents of the swpmvc/starter_plugin folder to a new directory where our todos plugin will live.

Once those files have been copied over, open up plugin.php in your editor, and make the following changes:

*   Edit the plugin header comments at top to contain your info.
*   Change the plugin class name, and references to it on lines 12, 26, and 47 from swpMVC\_Starter to swpMVC\_Todos

##Creating a controller

Before we fill out the add\_routes method in our plugin method, we should create a controller for our app that will handle those routes,
and require it in the require\_dependencies method of the plugin class. Create a new file in the controllers subfolder of your plugin
directory called TodosController.php and add the following code:

     'TodosController', 'method' => 'show_todos_list',
                'route' => '/todos/:p'
            );
            return $routes;
        }
        
Now in the TodosController class on line 5 of TodosController.php, let's add the todos_list method that will handle this
request, with some dummy code to make sure it works.

    set404();
            get_header();
            echo 'Hello '.$user_nicename.'!';
            get_footer();
        }
        
Right now our method simply checks that a parameter has been passed (defined in our route definition by the ":p" tag at the end of
the route.) If not, it returns a 404 page provided by our WordPress theme, and if one is provided, will say hello using the parameter
provided.

To test this out, make sure the Todos plugin is active, and go to the /todos/developer url on our site. You should see a
very underwhelming "Hello developer!" message there. Try a few other routes, like /todos/dawg, /todos/fool, etc, to see
the dynamic goodness, and when you're bored of that, let's move on to more interesting stuff.

##First Real Route

For our app to do anything (no pun,) we need to get some data into the database. Let's get the personal todos interface
working so we can save a list and start seeing things work. First add the route to the add\_routes method in your plugin
class as follows:

     'TodosController', 'method' => 'show_todos_list',
                'route' => '/todos/:p'
            );
            $routes[] = array(
                'controller' => 'TodosController', 'method' => 'edit_todos_list',
                'route' => '/mytodos'
            );
            return $routes;
        }
        
And now let's add the edit\_todos\_list method to the controller:

    prefix . "swpmvc_todos";
            $sql = "CREATE TABLE IF NOT EXISTS $table_name (
                id mediumint(9) NOT NULL AUTO_INCREMENT,
                user_id mediumint(9) NOT NULL,
                description text NOT NULL,
                completed tinyint(1) NOT NULL,
                UNIQUE KEY id (id)
              )
              CHARACTER SET utf8 COLLATE utf8_general_ci;";
              require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
              dbDelta($sql);
        }
        
And then add it to the plugin activation hook at the bottom of plugin.php as follows:

     'Todo', 'foreign_key' => 'user_id');
        }
        
Now add a call to that method in the plugin class constructor, right after require\_dependencies is called:

    require_dependencies();
            $this->update_relationships();
            $this->add_actions();
        }
    
        
Now that we've told ActiveRecord that our User has many todos, which are modeled by the Todo class, let's create the
Todo class, which will model Todos based on the table our plugin has created.

First create a file called Todo.php in the models subdirectory of the plugin folder, and require it from the
require\_dependencies of the plugin class as follows:

    private function require_dependencies()
    {
        require_once(dirname(__FILE__).'/models/Todo.php');
        require_once(dirname(__FILE__).'/controllers/TodosController.php');
    }

Add the following code to your Todo.php:

    prefix.'swpmvc_todos';
            }
            
            public static $belongs_to = array(
                array('user', 'class' => 'User', 'foreign_key' => 'user_id')
            );
        }
    
We've now told ActiveRecord that our Todos model is based off of the wp_swpmvc_todos table,
(with a multisite aware prefix,) and that it belongs to a property called user, which is modeled using the User class,
and linked up by the value of the user_id property of a Todos model instance.

##Defining Model controls with a ControlRenderer

We'll want to generate our form controls programatically and make sure that code is reusable, so let's
[define a ControlRenderer ](http://streetwise-media.github.com/Streetwise-Wordpress-MVC/#model-extenders/controlrenderers)
for the Todo model. First create a folder called control\_renderers in your plugin folder, and there add a file called
TodoControlRenderer.php, then require it from the plugin class require\_dependencies method as follows:

    private function require_dependencies()
    {
        require_once(dirname(__FILE__).'/models/Todo.php');
        require_once(dirname(__FILE__).'/controllers/TodosController.php');
        require_once(dirname(__FILE__).'/control_renderers/TodoControlRenderer.php');
    }

Now we will define the TodoControlRenderer class as follows:

    
        
            

Add a Todo

##Rendering the Template Before we can easily access our templates, we need to set the view folder for our controller. Define a \_\_construct method for the TodosController as follows: _templatedir = dirname(__FILE__).'/../templates/'; parent::__construct(); } We can now render the template from our controller method. Replace the "more to come" notice in the TodosController edit_todos_list method with the code below: form_helper()->_prefix = 'new_todo'; new TodoControlRenderer($new_todo); get_header(); echo $this->template('edit_todos')->replace('new_todo_form', $new_todo->render($this->template('edit_todos')->copy('new_todo_form')) ); get_footer(); } Reload /mytodos in your browser, and you should see something a bit more interesting, although it probably could use some styling. Create an assets subdirectory in your plugins folder, then a css folder in the assets folder, and in there place a style.css file with the below contents: .btn-primary { background-color: #2B54FB; } .form-actions, .existing_todo { background-color: #ECECEC; margin-top: 18px; margin-bottom: 18px; width: 100%; } .existing_todo { display:block; overflow:hidden; } .existing_todo .completed { float:right; } .existing_todo_desc_input { display:none; } .existing_todo.completed_1 { text-decoration: line-through; } .todos_user span { font-style: italic; } Let's assume we want to include our stylesheet on all of the routes handled by this controller. The easy way to do this is using the controller [before method](http://streetwise-media.github.com/Streetwise-Wordpress-MVC/#controllers/public-function-before) and the controller [_styles property](http://streetwise-media.github.com/Streetwise-Wordpress-MVC/#controllers/this-_styles). Create the following before method to enqueue the stylesheet on all routes handled by the TodosController, adjusting the url to match your plugin folder structure: _styles = array( array('todos_styles', '/wp-content/plugins/swp_todos/assets/css/style.css') ); } Reload the /mytodos page again and you should see the styles applied to the form. ##Processing form submission We've pointed our form to /todo/create, so we should probably point a route there and create a controller method to handle it. Once we're done, we'll refactor the way we define this in the view to prevent us having to update references to the route if we decide to change that url later. First add the following to the add_routes method of your swpMVC_Todos class: 'TodosController', 'method' => 'show_todos_list', 'route' => '/todos/:p' ); $routes[] = array( 'controller' => 'TodosController', 'method' => 'edit_todos_list', 'route' => 'mytodos' ); $routes[] = array( 'controller' => 'TodosController', 'method' => 'create_todo', 'route' => '/todo/create' ); return $routes; } Now we need to create the controller method we've pointed that route to. Add the following to your TodosController: user_id = get_current_user_id(); $new_todo->save(); return header('Location: '.$redirect); } With this in place, you can reload your /mytodos page, and submit a few times. We're not showing existing todo items yet, but if you look at the wp_swpmvc_todos table in your database, you should see the todo items being saved. Note the [link method](http://streetwise-media.github.com/Streetwise-Wordpress-MVC/#controllers/self-link) used to generate the redirect. By creating our URLs this way, we will not need to update the references to these routes if we update the URLs associated with those methods in our plugin routes. Let's go back and refactor the hard coded target URL in the new todos form. In your edit_todos.tpl view file, change the form tag to the following:
We now have dynamic content that is not specific to only one model in our template, which is a good time to add a view to organize the population of this template. ##Using a View Create a file called TodoListing.php in the views subdirectory of your plugin, and require it from the require\_dependencies method of your plugin class as follows: private function require_dependencies() { require_once(dirname(__FILE__).'/models/Todo.php'); require_once(dirname(__FILE__).'/controllers/TodosController.php'); require_once(dirname(__FILE__).'/control_renderers/TodoControlRenderer.php'); require_once(dirname(__FILE__).'/views/TodoListing.php'); } Now add the following to the TodoListing.php file: new_todo->render(clone $output->copy('new_todo_form')); } } We can now refactor the edit_todos method of the TodosController class to use our view as follows: new_todo = new Todo(); $view->new_todo->form_helper()->_prefix = 'new_todo'; new TodoControlRenderer($view->new_todo); get_header(); echo $view->render($this->template('edit_todos')); get_footer(); } ##Showing existing Todos Now that we can get data in, it's time to get that data out, and show existing todos in the TodoListing view. First let's query for the Todos items in the edit\_todos\_list controller method, and log the results to the console so we can see what we get back. If you haven't yet, enable the PHP Quick Profiler on your WordPress installation by adding the following to your wp-config.php file: array('user_id = ?', get_current_user_id()))); Console::log($todos); $view = new TodoListing(); $view->new_todo = new Todo(); $view->new_todo->form_helper()->_prefix = 'new_todo'; new TodoControlRenderer($view->new_todo); get_header(); echo $view->render($this->template('edit_todos')); get_footer(); } Refresh your /mytodos page and look in the Console of the PHP Quick Profiler at the bottom of the page, and you'll see an array containing any Todos you've submitted using the form we've created. Now that we're getting them from the db, let's update our template and view to populate them in the /mytodos route. Add the following to your edit_todos.tpl file:

Existing Todos


(...new todo form markup) Note the existing\_todos\_block tags, which denote the area that will be automatically stripped out if there are no existing todos. Next add an existing\_todos method to the TodoListing view as follows: public function existing_todos($output) { $r = ''; foreach($this->existing_todos as $todo) { $todo->form_helper()->_prefix = 'todo_'.$todo->id; $r .= $todo->render(clone $output->copy('existing_todos')); } return $r; } We need to set the queried todos on the view so the existing\_todos method on the view has access to them, and we need to add our control renderer to the queried todos so they can handle their form elements. We do that by assigning the results of the query directly to the view, then using the batch\_inject method on the TodoControlRenderer class to attach a TodoControlRenderer to each todo object in the array: existing_todos = Todo::all(array('conditions' => array('user_id = ?', get_current_user_id()))); TodoControlRenderer::batch_inject($view->existing_todos); $view->new_todo = new Todo(); $view->new_todo->form_helper()->_prefix = 'new_todo'; new TodoControlRenderer($view->new_todo); get_header(); $output = $view->render($this->template('edit_todos')); echo $output; get_footer(); } Now let's add a little javascript to make our existing todos listing editable. In the assets subdirectory of your plugin folder, create a folder called js, and in that, place a file called edit\_todos.js with the following contents: (function($, exports) { $('document').ready(function() { $('.existing_todo_description').dblclick(function() { $(this).hide(); $(this).parent().find('.existing_todo_desc_input').css('display', 'inline'); }); $('.existing_todo_desc_input input').keyup(function(e) { var $el = $(this); if(e.keyCode !== 13) return; $el.parent().hide().parent().find('.existing_todo_description').text($el.val()).show(); $.post(todoEdit.update_url+'/'+$el.attr('id').split('_')[1], {description: $el.val()}, function() { $.noop(); }); }); $('.completed input').click(function() { var $el = $(this); $.get(todoEdit.toggle_url+'/'+$el.attr('id').split('_')[1], function() { $.noop(); }); }); }); }(jQuery, window)); With the javascript written, we need to enqueue it and localize it so it will have access to the todoEdit variable referenced on lines 11 and 17 of edit\_todos.js, with update_url and toggle_url properties defined correctly. Add the following to the edit\_todos\_list method of your TodosController: _scripts = array( array('edit_todos', get_bloginfo('url').'/wp-content/plugins/swp_todos/assets/js/edit_todos.js', array('jquery')) ); $this->_script_localizations = array( array('edit_todos', 'todoEdit', array( 'update_url' => get_bloginfo('url').'/todo/update', 'toggle_url' => get_bloginfo('url').'/todo/toggle' ) ) ); // query for todos and render output Be sure to update the url to your js file to reflect your plugin folder structure. We've invented two new URLs, so let's add the routes and controller methods to handle them. Again we'll then refactor our script localizations to use the controller::link method rather than hard coding urls. Add the following to the add\_routes method of your swpMVC_Todos class: 'TodosController', 'method' => 'update_todo', 'route' => '/todo/update/:p' ); $routes[] = array( 'controller' => 'TodosController', 'method' => 'toggle_todo', 'route' => '/todo/toggle/:p' ); return $routes; } Add the following two methods to your TodosController to handle these new routes: user_id !== get_current_user_id()) return; $todo->description = $_POST['description']; $todo->save(); return; } public function toggle_todo($id=false) { if(!$id) return; if(!is_user_logged_in()) return; $todo = Todo::find($id); if (!$todo) return; if ($todo->user_id !== get_current_user_id()) return; $todo->completed = (intval($todo->completed) === 0) ? 1 : 0; $todo->save(); return; } Our /mytodos page is now fully functional (could use a delete feature but I'll leave that to you to implement.) Let's go back and refactor the script localization to use the controller::link methods instead of hard coded URLs. Update the script localization definitions in the edit\_todos\_list method of the TodosController as follows: _script_localizations = array( array('edit_todos', 'todoEdit', array( 'update_url' => self::link('TodosController', 'update_todo', array('')), 'toggle_url' => self::link('TodosController', 'toggle_todo', array('')) ) ) ); And then update the edit_todos.js file to account for the new trailing slashes in the URLs, on line 11 $.post(todoEdit.update_url+$el.attr('id').split('_')[1], {description: $el.val()}, function() { and on line 17 $.get(todoEdit.toggle_url+$el.attr('id').split('_')[1], function() { With this in good shape, we can start on the next page of our app. ##Looking at other users todos The next route we want to take on is the /todos/{{user_nicename}} URL, which should show a read only version of a users todo list. Add the following route to your swpMVC_Todos->add_routes method: 'TodosController', 'method' => 'show_users_todos', 'route' => '/todos/:p' ); And then the following method to your TodosController: set404(); $user = User::find(array('conditions' => array('user_nicename = ?', $user_nicename), 'include' => 'todos')); if (!$user) return $this->set404(); get_header(); Console::log($user); get_footer(); } Go to /todos/admin (or whatever your user_nicename is) and you should see a user object in the PHP Quick Profiler Console, with the related todos nested in the relationships property. Add a file called show_users_todos.tpl to the templates subdirectory of your plugin folder, with the following contents:

s Todos

No todos yet
Next we need to add two new methods to the TodoListing view to handle our two new template tags: public function no_todos() { return empty($this->existing_todos); } public function display_name() { return $this->user->display_name; } Now we can update the TodosController->show\_users\_todos method to render the view as follows: set404(); $user = User::find(array('conditions' => array('user_nicename = ?', $user_nicename), 'include' => 'todos')); if (!$user) return $this->set404(); $view = new TodoListing(); $view->user = $user; $view->existing_todos = $user->todos; get_header(); echo $view->render($this->template('show_users_todos')); get_footer(); } Reload todos/admin and have a look. Easy, huh? Let's take care of that last route and wrap things up. ##Showing an Index of users with Todos Again we start by adding the route in the plugin class add\_routes method: 'TodosController', 'method' => 'index', 'route' => '/todos' ); Then we start the controller method and get our data: 'DISTINCT user_id')), function($td) { return $td->user_id; }); $users = TodosUser::find($ids, array('include' => 'todos')); get_header(); Console::log($users); get_footer(); } You'll notice use of \_::map in this method. This functionality is provided by a [slightly modified version of Underscore PHP](http://streetwise-media.github.com/Streetwise-Wordpress-MVC/#logging-utility/underscore-php) which is included in the swpMVC framework. Now add the following to a new file called index.tpl in the views subdirectory of your plugin folder:

Users with Todos

complete remaining
None yet
Next we'll create a view to render this template. Add TodosUserListing.php to the views subdirectory of your plugin, require it from your plugin class' require\_dependencies method, and add the following code to it: users as $user) $r .= $user->render(clone $output->copy('todos_user')); return $r; } public function no_todos_users() { return !empty($this->users); } } We'll also need a renderer for a Todos User, so that view\_link, finished\_count, and unfinished\_count will be populated with the correct values for each user by the TodosUserListing::todos\_user method. Create a file called TodosUserRenderer.php, save it in the renderers subdirectory of your plugin, require it from the require\_dependencies method of your plugin class, and add the following code: model->user_nicename)); } public function finished_count() { return count(_::filter($this->model->todos, function($td) { return intval($td->completed) === 1; })); } public function unfinished_count() { return count(_::filter($this->model->todos, function($td) { return intval($td->completed) === 0; })); } } Finally update your controller method with the following: public function index() { $ids = _::map(Todo::all(array('select' => 'DISTINCT user_id')), function($td) { return $td->user_id; }); $users = User::find($ids, array('include' => 'todos')); if (!is_array($users)) $users = array($users); $view = new TodosUserListing(); $view->users = $users; TodosUserRenderer::batch_inject($view->users); get_header(); echo $view->render($this->template('index')); get_footer(); } That completes the Todos App tutorial, and hopefully gives you a reasonably in-depth view of the swpMVC framework. For further detail, see the [API Docs](http://streetwise-media.github.com/Streetwise-Wordpress-MVC/) or [open an issue](https://github.com/Streetwise-Media/Streetwise-Wordpress-MVC/issues?state=open) on the framework repo or the repo for this Todo plugin.
本源码包内暂不包含可直接显示的源代码文件,请下载源码包。