├── .gitattributes ├── .gitignore ├── CONTRIBUTING.md ├── app ├── Impl │ ├── Exception │ │ ├── ExceptionServiceProvider.php │ │ ├── HandlerInterface.php │ │ ├── ImplException.php │ │ └── NotifyHandler.php │ ├── Pagination │ │ └── GumbyPresenter.php │ ├── Repo │ │ ├── Article │ │ │ ├── AbstractArticleDecorator.php │ │ │ ├── ArticleInterface.php │ │ │ ├── CacheDecorator.php │ │ │ └── EloquentArticle.php │ │ ├── RepoAbstract.php │ │ ├── RepoServiceProvider.php │ │ ├── Status │ │ │ ├── EloquentStatus.php │ │ │ └── StatusInterface.php │ │ └── Tag │ │ │ ├── EloquentTag.php │ │ │ └── TagInterface.php │ └── Service │ │ ├── Cache │ │ ├── CacheInterface.php │ │ └── LaravelCache.php │ │ ├── Form │ │ ├── Article │ │ │ ├── ArticleForm.php │ │ │ └── ArticleFormLaravelValidator.php │ │ └── FormServiceProvider.php │ │ ├── Notification │ │ ├── NotificationServiceProvider.php │ │ ├── NotifierInterface.php │ │ └── SmsNotifier.php │ │ └── Validation │ │ ├── AbstractLaravelValidator.php │ │ └── ValidableInterface.php ├── commands │ └── .gitkeep ├── config │ ├── app.php │ ├── auth.php │ ├── cache.php │ ├── compile.php │ ├── database.php │ ├── mail.php │ ├── packages │ │ └── .gitkeep │ ├── queue.php │ ├── session.php │ ├── testing │ │ ├── cache.php │ │ └── session.php │ ├── twilio.php │ ├── view.php │ └── workbench.php ├── controllers │ ├── .gitkeep │ ├── BaseController.php │ ├── ContentController.php │ ├── HomeController.php │ └── admin │ │ └── ArticleController.php ├── database │ ├── migrations │ │ ├── .gitkeep │ │ ├── 2013_08_16_013614_create_articles_table.php │ │ ├── 2013_08_16_013714_create_statuses_table.php │ │ ├── 2013_08_16_013804_create_tags_table.php │ │ ├── 2013_08_16_013911_create_articles_tags_table.php │ │ └── 2013_08_18_204038_create_users_table.php │ ├── production.sqlite │ └── seeds │ │ ├── .gitkeep │ │ ├── ArticleTableSeeder.php │ │ ├── DatabaseSeeder.php │ │ ├── StatusesTableSeeder.php │ │ └── UserTableSeeder.php ├── filters.php ├── lang │ └── en │ │ ├── pagination.php │ │ ├── reminders.php │ │ └── validation.php ├── models │ ├── Article.php │ ├── Status.php │ ├── Tag.php │ └── User.php ├── routes.php ├── start │ ├── artisan.php │ ├── global.php │ └── local.php ├── storage │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── logs │ │ └── .gitignore │ ├── meta │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore ├── tests │ ├── ExampleTest.php │ └── TestCase.php └── views │ ├── admin │ ├── article_create.blade.php │ ├── article_edit.blade.php │ └── article_list.blade.php │ ├── article.blade.php │ ├── emails │ └── auth │ │ └── reminder.blade.php │ ├── hello.php │ ├── home.blade.php │ ├── layout.blade.php │ └── pagination.php ├── artisan ├── bootstrap ├── autoload.php ├── paths.php └── start.php ├── composer.json ├── manuscript ├── 000_thanks.txt ├── 001_who_is_this_for.txt ├── 002-5_solid.txt ├── 002_a_note_on_opinions.txt ├── 003_the_container.txt ├── 004_dependency_injection.txt ├── 005_the_sample_application.txt ├── 006_installation.txt ├── 007_application_setup.txt ├── 008_repository_pattern.txt ├── 009_repository_pattern_cacheing.txt ├── 010_validation.txt ├── 011_form_processing.txt ├── 012_error_handling.txt ├── 013_notifications.txt ├── 014_conclusion.txt ├── Book.txt ├── Preview.txt ├── Sample.txt ├── images │ └── title_page.png └── xxx_addendum.txt ├── phpunit.xml ├── public ├── .htaccess ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── css │ ├── gumby.css │ └── style.css ├── facebook.jpg ├── favicon.ico ├── favicon.png ├── fonts │ └── icons │ │ ├── entypo.eot │ │ ├── entypo.ttf │ │ └── entypo.woff ├── humans.txt ├── img │ ├── gumby_mainlogo.png │ ├── gumby_mainlogo@2x.png │ └── img_silence_demo.jpg ├── index.php ├── js │ ├── libs │ │ ├── gumby.init.js │ │ ├── gumby.js │ │ ├── gumby.min.js │ │ ├── jquery-1.10.1.min.js │ │ ├── jquery-2.0.2.min.js │ │ ├── jquery.mobile.custom.min.js │ │ ├── modernizr-2.6.2.min.js │ │ └── ui │ │ │ ├── gumby.checkbox.js │ │ │ ├── gumby.fittext.js │ │ │ ├── gumby.fixed.js │ │ │ ├── gumby.navbar.js │ │ │ ├── gumby.radiobtn.js │ │ │ ├── gumby.retina.js │ │ │ ├── gumby.skiplink.js │ │ │ ├── gumby.tabs.js │ │ │ ├── gumby.toggleswitch.js │ │ │ └── jquery.validation.js │ ├── main.js │ └── plugins.js ├── packages │ └── .gitkeep ├── robots.txt └── sass │ ├── _base.scss │ ├── _custom.scss │ ├── _fonts.scss │ ├── _grid.scss │ ├── _typography.scss │ ├── extensions │ ├── modular-scale │ │ ├── lib │ │ │ └── modular-scale.rb │ │ └── stylesheets │ │ │ └── _modular-scale.scss │ └── sassy-math │ │ ├── lib │ │ └── sassy-math.rb │ │ └── stylesheets │ │ └── _math.scss │ ├── functions │ ├── _all.scss │ ├── _breakpoints.scss │ ├── _buttons.scss │ ├── _clearfix.scss │ ├── _forms.scss │ ├── _grid-calc.scss │ ├── _height-calc.scss │ ├── _line-and-height.scss │ ├── _responsivity.scss │ ├── _semantic-grid.scss │ ├── _strip-units.scss │ ├── _typography.scss │ └── _visibility.scss │ ├── gumby.scss │ ├── ui │ ├── _all.scss │ ├── _buttons.scss │ ├── _forms.scss │ ├── _icons.scss │ ├── _images.scss │ ├── _labels.scss │ ├── _navbar.scss │ ├── _tables.scss │ ├── _tabs.scss │ ├── _toggles.scss │ └── _video.scss │ └── var │ ├── _lists.scss │ ├── _settings.scss │ └── icons │ ├── _entypo-icon-list.scss │ └── _entypo.scss ├── readme.md └── server.php /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bootstrap/compiled.php 2 | /vendor 3 | composer.phar 4 | composer.lock 5 | .DS_Store 6 | ._* 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Please submit all issues and pull requests to the [laravel/framework](http://github.com/laravel/framework) repository! -------------------------------------------------------------------------------- /app/Impl/Exception/ExceptionServiceProvider.php: -------------------------------------------------------------------------------- 1 | app; 11 | 12 | $app['impl.exception'] = $app->share(function($app) 13 | { 14 | return new NotifyHandler( $app['impl.notifier'] ); 15 | }); 16 | } 17 | 18 | /** 19 | * Bootstrap the application events. 20 | * 21 | * @return void 22 | */ 23 | public function boot() 24 | { 25 | $app = $this->app; 26 | 27 | $app->error(function(ImplException $e) use ($app) 28 | { 29 | $app['impl.exception']->handle($e); 30 | }); 31 | } 32 | } -------------------------------------------------------------------------------- /app/Impl/Exception/HandlerInterface.php: -------------------------------------------------------------------------------- 1 | notifier = $notifier; 12 | } 13 | 14 | /** 15 | * Handle Impl Exceptions 16 | * 17 | * @param \Impl\Exception\ImplException 18 | * @return void 19 | */ 20 | public function handle(ImplException $exception) 21 | { 22 | $this->sendException($exception); 23 | } 24 | 25 | /** 26 | * Send Exception to notifier 27 | * @param \Exception $exception Send notification of exception 28 | * @return void 29 | */ 30 | protected function sendException(\Exception $e) 31 | { 32 | $this->notifier->notify('Error: '.get_class($e), $e->getMessage()); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/Impl/Repo/Article/AbstractArticleDecorator.php: -------------------------------------------------------------------------------- 1 | nextArticle = $nextArticle; 10 | } 11 | 12 | /** 13 | * {@inheritdoc} 14 | */ 15 | public function byId($id) 16 | { 17 | return $this->nextArticle->byId($id); 18 | } 19 | 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function byPage($page=1, $limit=10, $all=false) 24 | { 25 | return $this->nextArticle->byPage($page, $limit, $all); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function bySlug($slug) 32 | { 33 | return $this->nextArticle->bySlug($slug); 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function byTag($tag, $page=1, $limit=10) 40 | { 41 | return $this->nextArticle->byTag($tag, $page, $limit); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function create(array $data) 48 | { 49 | return $this->nextArticle->create($data); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function update(array $data) 56 | { 57 | return $this->nextArticle->update($data); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /app/Impl/Repo/Article/ArticleInterface.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 13 | } 14 | 15 | /** 16 | * Attempt to retrieve from cache 17 | * by ID 18 | * {@inheritdoc} 19 | */ 20 | public function byId($id) 21 | { 22 | $key = md5('id.'.$id); 23 | 24 | if( $this->cache->has($key) ) 25 | { 26 | return $this->cache->get($key); 27 | } 28 | 29 | $article = $this->nextArticle->byId($id); 30 | 31 | $this->cache->put($key, $article); 32 | 33 | return $article; 34 | } 35 | 36 | /** 37 | * Attempt to retrieve from cache 38 | * {@inheritdoc} 39 | */ 40 | public function byPage($page=1, $limit=10, $all=false) 41 | { 42 | $allkey = ($all) ? '.all' : ''; 43 | $key = md5('page.'.$page.'.'.$limit.$allkey); 44 | 45 | if( $this->cache->has($key) ) 46 | { 47 | return $this->cache->get($key); 48 | } 49 | 50 | $paginated = $this->nextArticle->byPage($page, $limit); 51 | 52 | $this->cache->put($key, $paginated); 53 | 54 | return $paginated; 55 | } 56 | 57 | /** 58 | * Attempt to retrieve from cache 59 | * {@inheritdoc} 60 | */ 61 | public function bySlug($slug) 62 | { 63 | $key = md5('slug.'.$slug); 64 | 65 | if( $this->cache->has($key) ) 66 | { 67 | return $this->cache->get($key); 68 | } 69 | 70 | $article = $this->nextArticle->bySlug($slug); 71 | 72 | $this->cache->put($key, $article); 73 | 74 | return $article; 75 | } 76 | 77 | /** 78 | * Attempt to retrieve from cache 79 | * {@inheritdoc} 80 | */ 81 | public function byTag($tag, $page=1, $limit=10) 82 | { 83 | $key = md5('tag.'.$tag.'.'.$page.'.'.$limit); 84 | 85 | if( $this->cache->has($key) ) 86 | { 87 | return $this->cache->get($key); 88 | } 89 | 90 | $paginated = $this->nextArticle->byTag($tag, $page, $limit); 91 | 92 | $this->cache->put($key, $paginated); 93 | 94 | return $paginated; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/Impl/Repo/RepoAbstract.php: -------------------------------------------------------------------------------- 1 | app; 23 | 24 | $app->bind('Impl\Repo\Article\ArticleInterface', function($app) 25 | { 26 | $article = new EloquentArticle( 27 | new Article, 28 | $app->make('Impl\Repo\Tag\TagInterface') 29 | ); 30 | 31 | if( $app['config']->get('is_admin', false) == false ) 32 | { 33 | $article = new CacheDecorator( 34 | $article, 35 | new LaravelCache($app['cache'], 'articles', 10) 36 | ); 37 | } 38 | 39 | return $article; 40 | 41 | }); 42 | 43 | $app->bind('Impl\Repo\Tag\TagInterface', function($app) 44 | { 45 | return new EloquentTag( 46 | new Tag, 47 | new LaravelCache($app['cache'], 'tags', 10) 48 | ); 49 | }); 50 | 51 | $app->bind('Impl\Repo\Status\StatusInterface', function($app) 52 | { 53 | return new EloquentStatus( 54 | new Status 55 | ); 56 | }); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/Impl/Repo/Status/EloquentStatus.php: -------------------------------------------------------------------------------- 1 | status = $status; 13 | } 14 | 15 | /** 16 | * Get all Statuses 17 | * @return Array Arrayable collection 18 | */ 19 | public function all() 20 | { 21 | return $this->status->all(); 22 | } 23 | 24 | /** 25 | * Get specific status 26 | * @param int $id Status ID 27 | * @return object Status object 28 | */ 29 | public function byId($id) 30 | { 31 | return $this->status->find($id); 32 | } 33 | 34 | /** 35 | * Get specific status 36 | * @param string $slug Status slug 37 | * @return object Status object 38 | */ 39 | public function byStatus($slug) 40 | { 41 | return $this->status->where('slug', $slug); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/Impl/Repo/Status/StatusInterface.php: -------------------------------------------------------------------------------- 1 | tag = $tag; 16 | $this->cache = $cache; 17 | } 18 | 19 | /** 20 | * Find existing tags or create if they don't exist 21 | * 22 | * @param array $tags Array of strings, each representing a tag 23 | * @return array Array or Arrayable collection of Tag objects 24 | */ 25 | public function findOrCreate(array $tags) 26 | { 27 | $foundTags = $this->tag->whereIn('tag', $tags)->get(); 28 | 29 | $returnTags = array(); 30 | 31 | if( $foundTags ) 32 | { 33 | foreach( $foundTags as $tag ) 34 | { 35 | $pos = array_search($tag->tag, $tags); 36 | 37 | // Add returned tags to array 38 | if( $pos !== false ) 39 | { 40 | $returnTags[] = $tag; 41 | unset($tags[$pos]); 42 | } 43 | } 44 | } 45 | 46 | // Add remainings tags as new 47 | foreach( $tags as $tag ) 48 | { 49 | $returnTags[] = $this->tag->create(array( 50 | 'tag' => $tag, 51 | 'slug' => $this->slug($tag), 52 | )); 53 | } 54 | 55 | return $returnTags; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/Impl/Repo/Tag/TagInterface.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 14 | $this->cachekey = $cachekey; 15 | $this->minutes = $minutes; 16 | } 17 | 18 | /** 19 | * Retrieve data from cache 20 | * 21 | * @param string Cache item key 22 | * @return mixed PHP data result of cache 23 | */ 24 | public function get($key) 25 | { 26 | return $this->cache->section($this->cachekey)->get($key); 27 | } 28 | 29 | /** 30 | * Add data to the cache 31 | * 32 | * @param string Cache item key 33 | * @param mixed The data to store 34 | * @param integer The number of minutes to store the item 35 | * @return mixed $value variable returned for convenience 36 | */ 37 | public function put($key, $value, $minutes=null) 38 | { 39 | if( is_null($minutes) ) 40 | { 41 | $minutes = $this->minutes; 42 | } 43 | 44 | return $this->cache->section($this->cachekey)->put($key, $value, $minutes); 45 | } 46 | 47 | /** 48 | * Add data to the cache 49 | * taking pagination data into account 50 | * 51 | * @param integer Page of the cached items 52 | * @param integer Number of results per page 53 | * @param integer Total number of possible items 54 | * @param mixed The actual items for this page 55 | * @param string Cache item key 56 | * @param integer The number of minutes to store the item 57 | * @return mixed $items variable returned for convenience 58 | */ 59 | public function putPaginated($currentPage, $perPage, $totalItems, $items, $key, $minutes=null) 60 | { 61 | $cached = new \StdClass; 62 | 63 | $cached->currentPage = $currentPage; 64 | $cached->items = $items; 65 | $cached->totalItems = $totalItems; 66 | $cached->perPage = $perPage; 67 | 68 | $this->put($key, $cached, $minutes); 69 | 70 | return $cached; 71 | } 72 | 73 | /** 74 | * Test if item exists in cache 75 | * Only returns true if exists && is not expired 76 | * 77 | * @param string Cache item key 78 | * @return bool If cache item exists 79 | */ 80 | public function has($key) 81 | { 82 | return $this->cache->section($this->cachekey)->has($key); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/Impl/Service/Form/Article/ArticleForm.php: -------------------------------------------------------------------------------- 1 | validator = $validator; 32 | $this->article = $article; 33 | } 34 | 35 | /** 36 | * Create an new article 37 | * 38 | * @return boolean 39 | */ 40 | public function save(array $input) 41 | { 42 | if( ! $this->valid($input) ) 43 | { 44 | return false; 45 | } 46 | 47 | $input['tags'] = $this->processTags($input['tags']); 48 | 49 | return $this->article->create($input); 50 | } 51 | 52 | /** 53 | * Update an existing article 54 | * 55 | * @return boolean 56 | */ 57 | public function update(array $input) 58 | { 59 | if( ! $this->valid($input) ) 60 | { 61 | return false; 62 | } 63 | 64 | $input['tags'] = $this->processTags($input['tags']); 65 | 66 | return $this->article->update($input); 67 | } 68 | 69 | /** 70 | * Return any validation errors 71 | * 72 | * @return array 73 | */ 74 | public function errors() 75 | { 76 | return $this->validator->errors(); 77 | } 78 | 79 | /** 80 | * Test if form validator passes 81 | * 82 | * @return boolean 83 | */ 84 | protected function valid(array $input) 85 | { 86 | return $this->validator->with($input)->passes(); 87 | } 88 | 89 | /** 90 | * Convert string of tags to 91 | * array of tags 92 | * 93 | * @param string 94 | * @return array 95 | */ 96 | protected function processTags($tags) 97 | { 98 | $tags = explode(',', $tags); 99 | 100 | foreach( $tags as $key => $tag ) 101 | { 102 | $tags[$key] = trim($tag); 103 | } 104 | 105 | return $tags; 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /app/Impl/Service/Form/Article/ArticleFormLaravelValidator.php: -------------------------------------------------------------------------------- 1 | 'required', 14 | 'user_id' => 'required|exists:users,id', // Assumes db connection 15 | 'status_id' => 'required|exists:statuses,id', // Assumes db connection 16 | 'excerpt' => 'required', 17 | 'content' => 'required', 18 | 'tags' => 'required', 19 | ); 20 | 21 | } -------------------------------------------------------------------------------- /app/Impl/Service/Form/FormServiceProvider.php: -------------------------------------------------------------------------------- 1 | app; 17 | 18 | $app->bind('Impl\Service\Form\Article\ArticleForm', function($app) 19 | { 20 | return new ArticleForm( 21 | new ArticleFormLaravelValidator( $app['validator'] ), 22 | $app->make('Impl\Repo\Article\ArticleInterface') 23 | ); 24 | }); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /app/Impl/Service/Notification/NotificationServiceProvider.php: -------------------------------------------------------------------------------- 1 | app; 16 | 17 | $app['impl.notifier'] = $app->share(function() use ($app) 18 | { 19 | $config = $app['config']; 20 | 21 | $twilio = new Services_Twilio( 22 | $config->get('twilio.account_id'), 23 | $config->get('twilio.auth_token') 24 | ); 25 | 26 | $notifier = new SmsNotifier( $twilio ); 27 | 28 | $notifier->from( $config['twilio.from'] ) 29 | ->to( $config['twilio.to'] ); 30 | 31 | return $notifier; 32 | }); 33 | } 34 | 35 | public function provides() 36 | { 37 | return array('impl.notifier'); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /app/Impl/Service/Notification/NotifierInterface.php: -------------------------------------------------------------------------------- 1 | twilio = $twilio; 28 | } 29 | 30 | /** 31 | * Recipients of notification 32 | * @param string $to The recipient 33 | * @return Impl\Service\Notification\SmsNotifier $this Return self for chainability 34 | */ 35 | public function to($to) 36 | { 37 | $this->to = $to; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * Sender of notification 44 | * @param string $from The sender 45 | * @return Impl\Service\Notification\NotifierInterface $this Return self for chainability 46 | */ 47 | public function from($from) 48 | { 49 | $this->from = $from; 50 | 51 | return $this; 52 | } 53 | 54 | public function notify($subject, $message) 55 | { 56 | $sms = $this->twilio 57 | ->account 58 | ->sms_messages 59 | ->create( 60 | $this->from, 61 | $this->to, 62 | $subject."\n".$message 63 | ); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /app/Impl/Service/Validation/AbstractLaravelValidator.php: -------------------------------------------------------------------------------- 1 | value array 16 | * 17 | * @var Array 18 | */ 19 | protected $data = array(); 20 | 21 | /** 22 | * Validation errors 23 | * 24 | * @var Array 25 | */ 26 | protected $errors = array(); 27 | 28 | /** 29 | * Validation rules 30 | * 31 | * @var Array 32 | */ 33 | protected $rules = array(); 34 | 35 | public function __construct(Factory $validator) 36 | { 37 | $this->validator = $validator; 38 | } 39 | 40 | /** 41 | * Set data to validate 42 | * 43 | * @return \Impl\Service\Validation\AbstractLaravelValidator 44 | */ 45 | public function with(array $data) 46 | { 47 | $this->data = $data; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * Validation passes or fails 54 | * 55 | * @return Boolean 56 | */ 57 | public function passes() 58 | { 59 | $validator = $this->validator->make($this->data, $this->rules); 60 | 61 | if( $validator->fails() ) 62 | { 63 | $this->errors = $validator->messages(); 64 | return false; 65 | } 66 | 67 | return true; 68 | } 69 | 70 | /** 71 | * Return errors, if any 72 | * 73 | * @return array 74 | */ 75 | public function errors() 76 | { 77 | return $this->errors; 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /app/Impl/Service/Validation/ValidableInterface.php: -------------------------------------------------------------------------------- 1 | 'eloquent', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Authentication Model 23 | |-------------------------------------------------------------------------- 24 | | 25 | | When using the "Eloquent" authentication driver, we need to know which 26 | | Eloquent model should be used to retrieve your users. Of course, it 27 | | is often just the "User" model but you may use whatever you like. 28 | | 29 | */ 30 | 31 | 'model' => 'User', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Authentication Table 36 | |-------------------------------------------------------------------------- 37 | | 38 | | When using the "Database" authentication driver, we need to know which 39 | | table should be used to retrieve your users. We have chosen a basic 40 | | default value but you may easily change it to any table you like. 41 | | 42 | */ 43 | 44 | 'table' => 'users', 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Password Reminder Settings 49 | |-------------------------------------------------------------------------- 50 | | 51 | | Here you may set the settings for password reminders, including a view 52 | | that should be used as your password reminder e-mail. You will also 53 | | be able to set the name of the table that holds the reset tokens. 54 | | 55 | */ 56 | 57 | 'reminder' => array( 58 | 59 | 'email' => 'emails.auth.reminder', 60 | 61 | 'table' => 'password_reminders', 62 | 63 | 'expire' => 60, 64 | 65 | ), 66 | 67 | ); -------------------------------------------------------------------------------- /app/config/cache.php: -------------------------------------------------------------------------------- 1 | 'memcached', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | File Cache Location 23 | |-------------------------------------------------------------------------- 24 | | 25 | | When using the "file" cache driver, we need a location where the cache 26 | | files may be stored. A sensible default has been specified, but you 27 | | are free to change it to any other place on disk that you desire. 28 | | 29 | */ 30 | 31 | 'path' => storage_path().'/cache', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Database Cache Connection 36 | |-------------------------------------------------------------------------- 37 | | 38 | | When using the "database" cache driver you may specify the connection 39 | | that should be used to store the cached items. When this option is 40 | | null the default database connection will be utilized for cache. 41 | | 42 | */ 43 | 44 | 'connection' => null, 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Database Cache Table 49 | |-------------------------------------------------------------------------- 50 | | 51 | | When using the "database" cache driver we need to know the table that 52 | | should be used to store the cached items. A default table name has 53 | | been provided but you're free to change it however you deem fit. 54 | | 55 | */ 56 | 57 | 'table' => 'cache', 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | Memcached Servers 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Now you may specify an array of your Memcached servers that should be 65 | | used when utilizing the Memcached cache driver. All of the servers 66 | | should contain a value for "host", "port", and "weight" options. 67 | | 68 | */ 69 | 70 | 'memcached' => array( 71 | 72 | array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100), 73 | 74 | ), 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Cache Key Prefix 79 | |-------------------------------------------------------------------------- 80 | | 81 | | When utilizing a RAM based store such as APC or Memcached, there might 82 | | be other applications utilizing the same cache. So, we'll specify a 83 | | value to get prefixed to all our keys so we can avoid collisions. 84 | | 85 | */ 86 | 87 | 'prefix' => 'laravel', 88 | 89 | ); 90 | -------------------------------------------------------------------------------- /app/config/compile.php: -------------------------------------------------------------------------------- 1 | PDO::FETCH_CLASS, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Database Connection Name 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may specify which of the database connections below you wish 24 | | to use as your default connection for all database work. Of course 25 | | you may use many connections at once using the Database library. 26 | | 27 | */ 28 | 29 | 'default' => 'mysql', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Database Connections 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here are each of the database connections setup for your application. 37 | | Of course, examples of configuring each database platform that is 38 | | supported by Laravel is shown below to make development simple. 39 | | 40 | | 41 | | All database work in Laravel is done through the PHP PDO facilities 42 | | so make sure you have the driver for your particular database of 43 | | choice installed on your machine before you begin development. 44 | | 45 | */ 46 | 47 | 'connections' => array( 48 | 49 | 'sqlite' => array( 50 | 'driver' => 'sqlite', 51 | 'database' => __DIR__.'/../database/production.sqlite', 52 | 'prefix' => '', 53 | ), 54 | 55 | 'mysql' => array( 56 | 'driver' => 'mysql', 57 | 'host' => 'localhost', 58 | 'database' => 'implementing', 59 | 'username' => 'root', 60 | 'password' => 'root', 61 | 'charset' => 'utf8', 62 | 'collation' => 'utf8_unicode_ci', 63 | 'prefix' => '', 64 | ), 65 | 66 | 'pgsql' => array( 67 | 'driver' => 'pgsql', 68 | 'host' => 'localhost', 69 | 'database' => 'database', 70 | 'username' => 'root', 71 | 'password' => '', 72 | 'charset' => 'utf8', 73 | 'prefix' => '', 74 | 'schema' => 'public', 75 | ), 76 | 77 | 'sqlsrv' => array( 78 | 'driver' => 'sqlsrv', 79 | 'host' => 'localhost', 80 | 'database' => 'database', 81 | 'username' => 'root', 82 | 'password' => '', 83 | 'prefix' => '', 84 | ), 85 | 86 | ), 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Migration Repository Table 91 | |-------------------------------------------------------------------------- 92 | | 93 | | This table keeps track of all the migrations that have already run for 94 | | your application. Using this information, we can determine which of 95 | | the migrations on disk have not actually be run in the databases. 96 | | 97 | */ 98 | 99 | 'migrations' => 'migrations', 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Redis Databases 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Redis is an open source, fast, and advanced key-value store that also 107 | | provides a richer set of commands than a typical key-value systems 108 | | such as APC or Memcached. Laravel makes it easy to dig right in. 109 | | 110 | */ 111 | 112 | 'redis' => array( 113 | 114 | 'cluster' => true, 115 | 116 | 'default' => array( 117 | 'host' => '127.0.0.1', 118 | 'port' => 6379, 119 | 'database' => 0, 120 | ), 121 | 122 | ), 123 | 124 | ); 125 | -------------------------------------------------------------------------------- /app/config/mail.php: -------------------------------------------------------------------------------- 1 | 'smtp', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | SMTP Host Address 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may provide the host address of the SMTP server used by your 26 | | applications. A default option is provided that is compatible with 27 | | the Postmark mail service, which will provide reliable delivery. 28 | | 29 | */ 30 | 31 | 'host' => 'smtp.mailgun.org', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | SMTP Host Port 36 | |-------------------------------------------------------------------------- 37 | | 38 | | This is the SMTP port used by your application to delivery e-mails to 39 | | users of your application. Like the host we have set this value to 40 | | stay compatible with the Postmark e-mail application by default. 41 | | 42 | */ 43 | 44 | 'port' => 587, 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Global "From" Address 49 | |-------------------------------------------------------------------------- 50 | | 51 | | You may wish for all e-mails sent by your application to be sent from 52 | | the same address. Here, you may specify a name and address that is 53 | | used globally for all e-mails that are sent by your application. 54 | | 55 | */ 56 | 57 | 'from' => array('address' => null, 'name' => null), 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | E-Mail Encryption Protocol 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Here you may specify the encryption protocol that should be used when 65 | | the application send e-mail messages. A sensible default using the 66 | | transport layer security protocol should provide great security. 67 | | 68 | */ 69 | 70 | 'encryption' => 'tls', 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | SMTP Server Username 75 | |-------------------------------------------------------------------------- 76 | | 77 | | If your SMTP server requires a username for authentication, you should 78 | | set it here. This will get used to authenticate with your server on 79 | | connection. You may also set the "password" value below this one. 80 | | 81 | */ 82 | 83 | 'username' => null, 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | SMTP Server Password 88 | |-------------------------------------------------------------------------- 89 | | 90 | | Here you may set the password required by your SMTP server to send out 91 | | messages from your application. This will be given to the server on 92 | | connection so that the application will be able to send messages. 93 | | 94 | */ 95 | 96 | 'password' => null, 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Sendmail System Path 101 | |-------------------------------------------------------------------------- 102 | | 103 | | When using the "sendmail" driver to send e-mails, we will need to know 104 | | the path to where Sendmail lives on this server. A default path has 105 | | been provided here, which will work well on most of your systems. 106 | | 107 | */ 108 | 109 | 'sendmail' => '/usr/sbin/sendmail -bs', 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Mail "Pretend" 114 | |-------------------------------------------------------------------------- 115 | | 116 | | When this option is enabled, e-mail will not actually be sent over the 117 | | web and will instead be written to your application's logs files so 118 | | you may inspect the message. This is great for local development. 119 | | 120 | */ 121 | 122 | 'pretend' => false, 123 | 124 | ); -------------------------------------------------------------------------------- /app/config/packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/app/config/packages/.gitkeep -------------------------------------------------------------------------------- /app/config/queue.php: -------------------------------------------------------------------------------- 1 | 'sync', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => array( 32 | 33 | 'sync' => array( 34 | 'driver' => 'sync', 35 | ), 36 | 37 | 'beanstalkd' => array( 38 | 'driver' => 'beanstalkd', 39 | 'host' => 'localhost', 40 | 'queue' => 'default', 41 | ), 42 | 43 | 'sqs' => array( 44 | 'driver' => 'sqs', 45 | 'key' => 'your-public-key', 46 | 'secret' => 'your-secret-key', 47 | 'queue' => 'your-queue-url', 48 | 'region' => 'us-east-1', 49 | ), 50 | 51 | 'iron' => array( 52 | 'driver' => 'iron', 53 | 'project' => 'your-project-id', 54 | 'token' => 'your-token', 55 | 'queue' => 'your-queue-name', 56 | ), 57 | 58 | ), 59 | 60 | ); 61 | -------------------------------------------------------------------------------- /app/config/session.php: -------------------------------------------------------------------------------- 1 | 'native', 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Session Lifetime 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may specify the number of minutes that you wish the session 27 | | to be allowed to remain idle for it is expired. If you want them 28 | | to immediately expire when the browser closes, set it to zero. 29 | | 30 | */ 31 | 32 | 'lifetime' => 120, 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Session File Location 37 | |-------------------------------------------------------------------------- 38 | | 39 | | When using the native session driver, we need a location where session 40 | | files may be stored. A default has been set for you but a different 41 | | location may be specified. This is only needed for file sessions. 42 | | 43 | */ 44 | 45 | 'files' => storage_path().'/sessions', 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Session Database Connection 50 | |-------------------------------------------------------------------------- 51 | | 52 | | When using the "database" session driver, you may specify the database 53 | | connection that should be used to manage your sessions. This should 54 | | correspond to a connection in your "database" configuration file. 55 | | 56 | */ 57 | 58 | 'connection' => null, 59 | 60 | /* 61 | |-------------------------------------------------------------------------- 62 | | Session Database Table 63 | |-------------------------------------------------------------------------- 64 | | 65 | | When using the "database" session driver, you may specify the table we 66 | | should use to manage the sessions. Of course, a sensible default is 67 | | provided for you; however, you are free to change this as needed. 68 | | 69 | */ 70 | 71 | 'table' => 'sessions', 72 | 73 | /* 74 | |-------------------------------------------------------------------------- 75 | | Session Sweeping Lottery 76 | |-------------------------------------------------------------------------- 77 | | 78 | | Some session drivers must manually sweep their storage location to get 79 | | rid of old sessions from storage. Here are the chances that it will 80 | | happen on a given request. By default, the odds are 2 out of 100. 81 | | 82 | */ 83 | 84 | 'lottery' => array(2, 100), 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Session Cookie Name 89 | |-------------------------------------------------------------------------- 90 | | 91 | | Here you may change the name of the cookie used to identify a session 92 | | instance by ID. The name specified here will get used every time a 93 | | new session cookie is created by the framework for every driver. 94 | | 95 | */ 96 | 97 | 'cookie' => 'laravel_session', 98 | 99 | /* 100 | |-------------------------------------------------------------------------- 101 | | Session Cookie Path 102 | |-------------------------------------------------------------------------- 103 | | 104 | | The session cookie path determines the path for which the cookie will 105 | | be regarded as available. Typically, this will be the root path of 106 | | your application but you are free to change this when necessary. 107 | | 108 | */ 109 | 110 | 'path' => '/', 111 | 112 | /* 113 | |-------------------------------------------------------------------------- 114 | | Session Cookie Domain 115 | |-------------------------------------------------------------------------- 116 | | 117 | | Here you may change the domain of the cookie used to identify a session 118 | | in your application. This will determine which domains the cookie is 119 | | available to in your application. A sensible default has been set. 120 | | 121 | */ 122 | 123 | 'domain' => null, 124 | 125 | ); 126 | -------------------------------------------------------------------------------- /app/config/testing/cache.php: -------------------------------------------------------------------------------- 1 | 'array', 19 | 20 | ); -------------------------------------------------------------------------------- /app/config/testing/session.php: -------------------------------------------------------------------------------- 1 | 'array', 20 | 21 | ); -------------------------------------------------------------------------------- /app/config/twilio.php: -------------------------------------------------------------------------------- 1 | '555-1234', 6 | 7 | 'to' => '555-5678', 8 | 9 | 'account_id' => 'abc1234', 10 | 11 | 'auth_token' => '1111111', 12 | 13 | ); -------------------------------------------------------------------------------- /app/config/view.php: -------------------------------------------------------------------------------- 1 | array(__DIR__.'/../views'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Pagination View 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This view will be used to render the pagination link output, and can 24 | | be easily customized here to show any view you like. A clean view 25 | | compatible with Twitter's Bootstrap is given to you by default. 26 | | 27 | */ 28 | 29 | 'pagination' => 'pagination', 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /app/config/workbench.php: -------------------------------------------------------------------------------- 1 | '', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Workbench Author E-Mail Address 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Like the option above, your e-mail address is used when generating new 24 | | workbench packages. The e-mail is placed in your composer.json file 25 | | automatically after the package is created by the workbench tool. 26 | | 27 | */ 28 | 29 | 'email' => '', 30 | 31 | ); -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/app/controllers/.gitkeep -------------------------------------------------------------------------------- /app/controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | layout)) 13 | { 14 | $this->layout = View::make($this->layout); 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /app/controllers/ContentController.php: -------------------------------------------------------------------------------- 1 | article = $article; 14 | } 15 | 16 | /** 17 | * Paginated articles 18 | * GET / 19 | */ 20 | public function home() 21 | { 22 | $page = Input::get('page', 1); 23 | 24 | // Candidate for config item 25 | $perPage = 3; 26 | 27 | $pagiData = $this->article->byPage($page, $perPage); 28 | 29 | $articles = Paginator::make($pagiData->items, $pagiData->totalItems, $perPage); 30 | 31 | $this->layout->content = View::make('home')->with('articles', $articles); 32 | } 33 | 34 | /** 35 | * Single article 36 | * GET /{slug} 37 | */ 38 | public function article($slug) 39 | { 40 | $article = $this->article->bySlug($slug); 41 | 42 | if( ! $article ) 43 | { 44 | App::abort(404); 45 | } 46 | 47 | $this->layout->content = View::make('article')->with('article', $article); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/controllers/HomeController.php: -------------------------------------------------------------------------------- 1 | article = $article; 18 | $this->articleform = $articleform; 19 | $this->status = $status; 20 | } 21 | 22 | /** 23 | * List articles 24 | * GET /admin/article 25 | */ 26 | public function index() 27 | { 28 | $page = Input::get('page', 1); 29 | 30 | // Candidate for config item 31 | $perPage = 3; 32 | 33 | $pagiData = $this->article->byPage($page, $perPage, true); 34 | 35 | $articles = Paginator::make($pagiData->items, $pagiData->totalItems, $perPage); 36 | 37 | $this->layout->content = View::make('admin.article_list')->with('articles', $articles); 38 | } 39 | 40 | /** 41 | * Show single article. We only want to show edit form 42 | * @param int $id Article ID 43 | * @return Redirect 44 | */ 45 | public function show($id) 46 | { 47 | return Redirect::to('/admin/article/'.$id.'/edit'); 48 | } 49 | 50 | /** 51 | * Create article form 52 | * GET /admin/article/create 53 | */ 54 | public function create() 55 | { 56 | $statuses = $this->status->all(); 57 | 58 | $this->layout->content = View::make('admin.article_create', array( 59 | 'statuses' => $statuses, 60 | 'input' => Session::getOldInput(), 61 | )); 62 | } 63 | 64 | /** 65 | * Create article form processing 66 | * POST /admin/article 67 | */ 68 | public function store() 69 | { 70 | $input = array_merge(Input::all(), array('user_id' => 1)); 71 | 72 | if( $this->articleform->save( $input ) ) 73 | { 74 | // Success! 75 | return Redirect::to('/admin/article') 76 | ->with('status', 'success'); 77 | } else { 78 | 79 | return Redirect::to('/admin/article/create') 80 | ->withInput() 81 | ->withErrors( $this->articleform->errors() ) 82 | ->with('status', 'error'); 83 | } 84 | } 85 | 86 | /** 87 | * Create article form 88 | * GET /admin/article/{id}/edit 89 | */ 90 | public function edit($id) 91 | { 92 | $article = $this->article->byId($id); 93 | $statuses = $this->status->all(); 94 | 95 | $tags = ''; 96 | $article->tags->each(function($tag) use(&$tags) 97 | { 98 | $tags .= $tag->tag.', '; 99 | }); 100 | 101 | $tags = substr($tags, 0, -2); 102 | 103 | $this->layout->content = View::make('admin.article_edit', array( 104 | 'article' => $article, 105 | 'tags' => $tags, 106 | 'statuses' => $statuses, 107 | 'input' => Session::getOldInput() 108 | )); 109 | } 110 | 111 | /** 112 | * Create article form 113 | * PUT /admin/article/{id} 114 | */ 115 | public function update() 116 | { 117 | $input = array_merge(Input::all(), array('user_id' => 1)); 118 | 119 | if( $this->articleform->update( $input ) ) 120 | { 121 | // Success! 122 | return Redirect::to('admin/article') 123 | ->with('status', 'success'); 124 | } else { 125 | 126 | // Need article ID 127 | return Redirect::to('admin/article/'.Input::get('id').'/edit') 128 | ->withInput() 129 | ->withErrors( $this->articleform->errors() ) 130 | ->with('status', 'error'); 131 | } 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /app/database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/app/database/migrations/.gitkeep -------------------------------------------------------------------------------- /app/database/migrations/2013_08_16_013614_create_articles_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('user_id')->unsigned(); 19 | $table->integer('status_id')->unsigned(); 20 | $table->string('title'); 21 | $table->string('slug'); 22 | $table->text('excerpt'); 23 | $table->text('content'); 24 | $table->softDeletes(); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::drop('articles'); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/database/migrations/2013_08_16_013714_create_statuses_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('status'); 19 | $table->string('slug'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('statuses'); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/database/migrations/2013_08_16_013804_create_tags_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('tag'); 19 | $table->string('slug'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('tags'); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/database/migrations/2013_08_16_013911_create_articles_tags_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('article_id')->unsigned()->index(); 19 | $table->integer('tag_id')->unsigned()->index(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('articles_tags'); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/database/migrations/2013_08_18_204038_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('email')->unique()->index(); 19 | $table->string('password'); 20 | $table->softDeletes(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::drop('users'); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/database/production.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/app/database/production.sqlite -------------------------------------------------------------------------------- /app/database/seeds/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/app/database/seeds/.gitkeep -------------------------------------------------------------------------------- /app/database/seeds/ArticleTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 8 | 9 | /* 10 | Assumes: 11 | 12 | "published" has status_id = 1 13 | "draft" has status_id = 2 14 | 15 | "author" has user_id = 1 16 | */ 17 | 18 | Article::create(array( 19 | 'user_id' => 1, 20 | 'status_id' => 1, 21 | 'title' => 'My first article', 22 | 'slug' => 'my-first-article', 23 | 'excerpt' => 'This is my first article, and here is a short description of it! Tally-ho!', 24 | 'content' => "This will be parsed as markdown and so needs some line-breaks. 25 | 26 | ## A h2 headline 27 | The content under-which is about the H2 headline, because Google knows everything about SEO and tells you have to build your html.", 28 | )); 29 | 30 | Article::create(array( 31 | 'user_id' => 1, 32 | 'status_id' => 1, 33 | 'title' => 'My second article', 34 | 'slug' => 'my-second-article', 35 | 'excerpt' => 'This is my second article, and here is a short description of said second article!', 36 | 'content' => "Synth nulla Banksy, sriracha odio ennui forage artisan keytar DIY. Meggings accusamus proident, meh ugh PBR single-origin coffee 3 wolf moon cliche twee dreamcatcher. 37 | 38 | ## Laborum thundercats gluten-free 39 | Terry Richardson ex semiotics mixtape wolf sunt proident salvia. Church-key Banksy bitters, ex mollit exercitation bicycle rights chambray gluten-free quis aute sriracha forage flexitarian vero.", 40 | )); 41 | 42 | Article::create(array( 43 | 'user_id' => 1, 44 | 'status_id' => 1, 45 | 'title' => 'My third article', 46 | 'slug' => 'my-third-article', 47 | 'excerpt' => 'This is my third article, and here is a short description of said third article!', 48 | 'content' => "Bacon ipsum dolor sit amet pork belly meatloaf ham hock jerky short ribs pastrami brisket ball tip swine ham fatback capicola spare ribs shank. 49 | 50 | ## Pancetta short ribs 51 | Pancetta jerky pork loin tenderloin, drumstick strip steak pork belly spare ribs fatback. Strip steak tongue sirloin pancetta tenderloin, ground round fatback sausage. Flank tenderloin beef shank jerky ham chuck jowl chicken. Kielbasa tenderloin beef ribs, capicola ham pancetta turducken shankle filet mignon pork loin.", 52 | )); 53 | 54 | Article::create(array( 55 | 'user_id' => 1, 56 | 'status_id' => 1, 57 | 'title' => 'My fourth article', 58 | 'slug' => 'my-fourth-article', 59 | 'excerpt' => 'This is my fourth article, and here is a short description of said fourth article!', 60 | 'content' => "Cliche quinoa swag roof party sartorial american apparel. Helvetica Brooklyn chambray PBR, intelligentsia scenester cupidatat 3 wolf moon food truck elit Pinterest ullamco master cleanse. 61 | 62 | Meh YOLO put a bird on it velit, minim banh mi non thundercats vegan enim sapiente irure assumenda photo booth. Aute Tonx flannel blog retro McSweeney's. Salvia ennui eu, fingerstache pickled blog twee minim polaroid authentic Brooklyn mixtape.", 63 | )); 64 | 65 | Article::create(array( 66 | 'user_id' => 1, 67 | 'status_id' => 2, 68 | 'title' => 'My greatest life achievement', 69 | 'slug' => 'my-greatest-life-achievement', 70 | 'excerpt' => "IT'S STILL A DRAFT! I HAVEN'T AHCIEVED ANYTHING!!!! I'VE FAILED AT YOLOING.", 71 | 'content' => "This is the story of a man, who is afraid. But then he just *crushes it* hardcore.", 72 | )); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /app/database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call('UserTableSeeder'); 15 | $this->call('StatusesTableSeeder'); 16 | $this->call('ArticleTableSeeder'); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/database/seeds/StatusesTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 8 | 9 | Status::create(array( 10 | 'status' => 'Published', 11 | 'slug' => 'published', 12 | )); 13 | 14 | Status::create(array( 15 | 'status' => 'Draft', 16 | 'slug' => 'draft', 17 | )); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/database/seeds/UserTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 8 | 9 | User::create(array( 10 | 'email' => 'fideloper@example.com', 11 | 'password' => Hash::make('password') 12 | )); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /app/filters.php: -------------------------------------------------------------------------------- 1 | segment(1) === 'admin' ) 17 | { 18 | Config::set("is_admin", true); 19 | } else { 20 | Config::set("is_admin", false); 21 | } 22 | }); 23 | 24 | 25 | App::after(function($request, $response) 26 | { 27 | // 28 | }); 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Authentication Filters 33 | |-------------------------------------------------------------------------- 34 | | 35 | | The following filters are used to verify that the user of the current 36 | | session is logged into this application. The "basic" filter easily 37 | | integrates HTTP Basic authentication for quick, simple checking. 38 | | 39 | */ 40 | 41 | Route::filter('auth', function() 42 | { 43 | if (Auth::guest()) return Redirect::guest('login'); 44 | }); 45 | 46 | 47 | Route::filter('auth.basic', function() 48 | { 49 | return Auth::basic(); 50 | }); 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Guest Filter 55 | |-------------------------------------------------------------------------- 56 | | 57 | | The "guest" filter is the counterpart of the authentication filters as 58 | | it simply checks that the current user is not logged in. A redirect 59 | | response will be issued if they are, which you may freely change. 60 | | 61 | */ 62 | 63 | Route::filter('guest', function() 64 | { 65 | if (Auth::check()) return Redirect::to('/'); 66 | }); 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | CSRF Protection Filter 71 | |-------------------------------------------------------------------------- 72 | | 73 | | The CSRF filter is responsible for protecting your application against 74 | | cross-site request forgery attacks. If this special token in a user 75 | | session does not match the one given in this request, we'll bail. 76 | | 77 | */ 78 | 79 | Route::filter('csrf', function() 80 | { 81 | if (Session::token() != Input::get('_token')) 82 | { 83 | throw new Illuminate\Session\TokenMismatchException; 84 | } 85 | }); -------------------------------------------------------------------------------- /app/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 18 | 'next' => 'Next »', 19 | 20 | ); -------------------------------------------------------------------------------- /app/lang/en/reminders.php: -------------------------------------------------------------------------------- 1 | "Passwords must be six characters and match the confirmation.", 17 | 18 | "user" => "We can't find a user with that e-mail address.", 19 | 20 | "token" => "This password reset token is invalid.", 21 | 22 | ); -------------------------------------------------------------------------------- /app/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | "The :attribute must be accepted.", 17 | "active_url" => "The :attribute is not a valid URL.", 18 | "after" => "The :attribute must be a date after :date.", 19 | "alpha" => "The :attribute may only contain letters.", 20 | "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", 21 | "alpha_num" => "The :attribute may only contain letters and numbers.", 22 | "array" => "The :attribute must be an array.", 23 | "before" => "The :attribute must be a date before :date.", 24 | "between" => array( 25 | "numeric" => "The :attribute must be between :min - :max.", 26 | "file" => "The :attribute must be between :min - :max kilobytes.", 27 | "string" => "The :attribute must be between :min - :max characters.", 28 | "array" => "The :attribute must have between :min - :max items.", 29 | ), 30 | "confirmed" => "The :attribute confirmation does not match.", 31 | "date" => "The :attribute is not a valid date.", 32 | "date_format" => "The :attribute does not match the format :format.", 33 | "different" => "The :attribute and :other must be different.", 34 | "digits" => "The :attribute must be :digits digits.", 35 | "digits_between" => "The :attribute must be between :min and :max digits.", 36 | "email" => "The :attribute format is invalid.", 37 | "exists" => "The selected :attribute is invalid.", 38 | "image" => "The :attribute must be an image.", 39 | "in" => "The selected :attribute is invalid.", 40 | "integer" => "The :attribute must be an integer.", 41 | "ip" => "The :attribute must be a valid IP address.", 42 | "max" => array( 43 | "numeric" => "The :attribute may not be greater than :max.", 44 | "file" => "The :attribute may not be greater than :max kilobytes.", 45 | "string" => "The :attribute may not be greater than :max characters.", 46 | "array" => "The :attribute may not have more than :max items.", 47 | ), 48 | "mimes" => "The :attribute must be a file of type: :values.", 49 | "min" => array( 50 | "numeric" => "The :attribute must be at least :min.", 51 | "file" => "The :attribute must be at least :min kilobytes.", 52 | "string" => "The :attribute must be at least :min characters.", 53 | "array" => "The :attribute must have at least :min items.", 54 | ), 55 | "not_in" => "The selected :attribute is invalid.", 56 | "numeric" => "The :attribute must be a number.", 57 | "regex" => "The :attribute format is invalid.", 58 | "required" => "The :attribute field is required.", 59 | "required_if" => "The :attribute field is required when :other is :value.", 60 | "required_with" => "The :attribute field is required when :values is present.", 61 | "required_without" => "The :attribute field is required when :values is not present.", 62 | "same" => "The :attribute and :other must match.", 63 | "size" => array( 64 | "numeric" => "The :attribute must be :size.", 65 | "file" => "The :attribute must be :size kilobytes.", 66 | "string" => "The :attribute must be :size characters.", 67 | "array" => "The :attribute must contain :size items.", 68 | ), 69 | "unique" => "The :attribute has already been taken.", 70 | "url" => "The :attribute format is invalid.", 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | Custom Validation Language Lines 75 | |-------------------------------------------------------------------------- 76 | | 77 | | Here you may specify custom validation messages for attributes using the 78 | | convention "attribute.rule" to name the lines. This makes it quick to 79 | | specify a specific custom language line for a given attribute rule. 80 | | 81 | */ 82 | 83 | 'custom' => array(), 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | Custom Validation Attributes 88 | |-------------------------------------------------------------------------- 89 | | 90 | | The following language lines are used to swap attribute place-holders 91 | | with something more reader friendly such as E-Mail Address instead 92 | | of "email". This simply helps us make messages a little cleaner. 93 | | 94 | */ 95 | 96 | 'attributes' => array(), 97 | 98 | ); 99 | -------------------------------------------------------------------------------- /app/models/Article.php: -------------------------------------------------------------------------------- 1 | belongsTo('User'); 42 | } 43 | 44 | /** 45 | * Define a one-to-one relationship. 46 | * 47 | * @return \Illuminate\Database\Eloquent\Relations\HasOne 48 | */ 49 | public function status() 50 | { 51 | return $this->belongsTo('Status'); 52 | } 53 | 54 | /** 55 | * Define a many-to-many relationship. 56 | * 57 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 58 | */ 59 | public function tags() 60 | { 61 | return $this->belongsToMany('Tag', 'articles_tags', 'article_id', 'tag_id')->withTimestamps(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/models/Status.php: -------------------------------------------------------------------------------- 1 | belongsToMany('Article', 'articles_tags', 'tag_id', 'article_id'); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/models/User.php: -------------------------------------------------------------------------------- 1 | getKey(); 30 | } 31 | 32 | /** 33 | * Get the password for the user. 34 | * 35 | * @return string 36 | */ 37 | public function getAuthPassword() 38 | { 39 | return $this->password; 40 | } 41 | 42 | /** 43 | * Get the e-mail address where password reminders are sent. 44 | * 45 | * @return string 46 | */ 47 | public function getReminderEmail() 48 | { 49 | return $this->email; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /app/routes.php: -------------------------------------------------------------------------------- 1 | 'admin'), function() 18 | { 19 | Route::resource('article', 'ArticleController'); 20 | }); -------------------------------------------------------------------------------- /app/start/artisan.php: -------------------------------------------------------------------------------- 1 | client->request('GET', '/'); 13 | 14 | $this->assertTrue($this->client->getResponse()->isOk()); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 2 | @if( count($errors->all()) ) 3 | 8 | @endif 9 |
10 | 36 |
37 |
38 | -------------------------------------------------------------------------------- /app/views/admin/article_edit.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @if( count($errors->all()) ) 3 | 8 | @endif 9 |
10 | 36 | 37 | 38 |
39 |
40 |
-------------------------------------------------------------------------------- /app/views/admin/article_list.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | @foreach($articles as $article) 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @endforeach 23 | 24 |
Articles
IDTitleStatusPublished OnUpdated On
{{ $article->id }}{{ $article->title }}{{ $article->status->status }}{{ $article->created_at }}{{ $article->updated_at }}
25 |
26 |
27 | {{ $articles->links() }} 28 |
-------------------------------------------------------------------------------- /app/views/article.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

{{ $article->title }}

3 | {{ \Michelf\MarkdownExtra::defaultTransform($article->content) }} 4 |
-------------------------------------------------------------------------------- /app/views/emails/auth/reminder.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Password Reset

8 | 9 |
10 | To reset your password, complete this form: {{ URL::to('password/reset', array($token)) }}. 11 |
12 | 13 | -------------------------------------------------------------------------------- /app/views/home.blade.php: -------------------------------------------------------------------------------- 1 | @foreach( $articles as $article ) 2 |
3 |

{{ $article->title }}

4 | {{ \Michelf\MarkdownExtra::defaultTransform($article->excerpt) }} 5 |
6 | @endforeach 7 |
8 | {{ $articles->links() }} 9 |
-------------------------------------------------------------------------------- /app/views/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Home | Implementing Laravel Blog 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 36 | 37 |
38 | {{ $content }} 39 |
40 | 41 | 49 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 70 | 71 | 73 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/views/pagination.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | getLastPage() > 1): ?> 6 | 11 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | boot(); 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Load The Artisan Console Application 37 | |-------------------------------------------------------------------------- 38 | | 39 | | We'll need to run the script to load and return the Artisan console 40 | | application. We keep this in its own script so that we will load 41 | | the console application independent of running commands which 42 | | will allow us to fire commands from Routes when we want to. 43 | | 44 | */ 45 | 46 | $artisan = Illuminate\Console\Application::start($app); 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Run The Artisan Application 51 | |-------------------------------------------------------------------------- 52 | | 53 | | When we run the console application, the current CLI command will be 54 | | executed in this console and the response sent back to a terminal 55 | | or another output device for the developers. Here goes nothing! 56 | | 57 | */ 58 | 59 | $status = $artisan->run(); 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Shutdown The Application 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Once Artisan has finished running. We will fire off the shutdown events 67 | | so that any final work may be done by the application before we shut 68 | | down the process. This is the last thing to happen to the request. 69 | | 70 | */ 71 | 72 | $app->shutdown(); 73 | 74 | exit($status); -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/../app', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Public Path 21 | |-------------------------------------------------------------------------- 22 | | 23 | | The public path contains the assets for your web application, such as 24 | | your JavaScript and CSS files, and also contains the primary entry 25 | | point for web requests into these applications from the outside. 26 | | 27 | */ 28 | 29 | 'public' => __DIR__.'/../public', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Base Path 34 | |-------------------------------------------------------------------------- 35 | | 36 | | The base path is the root of the Laravel installation. Most likely you 37 | | will not need to change this value. But, if for some wild reason it 38 | | is necessary you will do so here, just proceed with some caution. 39 | | 40 | */ 41 | 42 | 'base' => __DIR__.'/..', 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Storage Path 47 | |-------------------------------------------------------------------------- 48 | | 49 | | The storage path is used by Laravel to store cached Blade views, logs 50 | | and other pieces of information. You may modify the path here when 51 | | you want to change the location of this directory for your apps. 52 | | 53 | */ 54 | 55 | 'storage' => __DIR__.'/../app/storage', 56 | 57 | ); 58 | -------------------------------------------------------------------------------- /bootstrap/start.php: -------------------------------------------------------------------------------- 1 | redirectIfTrailingSlash(); 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Detect The Application Environment 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Laravel takes a dead simple approach to your application environments 24 | | so you can just specify a machine name or HTTP host that matches a 25 | | given environment, then we will automatically detect it for you. 26 | | 27 | */ 28 | 29 | $env = $app->detectEnvironment(function() 30 | { 31 | return getenv('LARA_ENV') ?: 'development'; 32 | }); 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Bind Paths 37 | |-------------------------------------------------------------------------- 38 | | 39 | | Here we are binding the paths configured in paths.php to the app. You 40 | | should not be changing these here. If you need to change these you 41 | | may do so within the paths.php file and they will be bound here. 42 | | 43 | */ 44 | 45 | $app->bindInstallPaths(require __DIR__.'/paths.php'); 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Load The Application 50 | |-------------------------------------------------------------------------- 51 | | 52 | | Here we will load the Illuminate application. We'll keep this is in a 53 | | separate location so we can isolate the creation of an application 54 | | from the actual running of the application with a given request. 55 | | 56 | */ 57 | 58 | $framework = $app['path.base'].'/vendor/laravel/framework/src'; 59 | 60 | require $framework.'/Illuminate/Foundation/start.php'; 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Return The Application 65 | |-------------------------------------------------------------------------- 66 | | 67 | | This script returns the application instance. The instance is given to 68 | | the calling script so we can separate the building of the instances 69 | | from the actual running of the application and sending responses. 70 | | 71 | */ 72 | 73 | return $app; 74 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "description": "The Laravel Framework.", 4 | "keywords": ["framework", "laravel"], 5 | "require": { 6 | "laravel/framework": "4.0.*", 7 | "twilio/sdk": "dev-master", 8 | "michelf/php-markdown": "1.3.*@dev" 9 | }, 10 | "autoload": { 11 | "classmap": [ 12 | "app/commands", 13 | "app/controllers", 14 | "app/models", 15 | "app/database/migrations", 16 | "app/database/seeds", 17 | "app/tests/TestCase.php" 18 | ], 19 | "psr-0": { 20 | "Impl": "app" 21 | } 22 | }, 23 | "scripts": { 24 | "post-install-cmd": [ 25 | "php artisan optimize" 26 | ], 27 | "pre-update-cmd": [ 28 | "php artisan clear-compiled" 29 | ], 30 | "post-update-cmd": [ 31 | "php artisan optimize" 32 | ], 33 | "post-create-project-cmd": [ 34 | "php artisan key:generate" 35 | ] 36 | }, 37 | "config": { 38 | "preferred-install": "dist" 39 | }, 40 | "minimum-stability": "dev" 41 | } 42 | -------------------------------------------------------------------------------- /manuscript/000_thanks.txt: -------------------------------------------------------------------------------- 1 | {frontmatter} 2 | 3 | # Thanks 4 | 5 | *Thanks to Natalie for her patience, my reviewers for helping make this so much better and the team at Digital Surgeons for their support (and [Gumby](http://gumbyframework.com))!* -------------------------------------------------------------------------------- /manuscript/001_who_is_this_for.txt: -------------------------------------------------------------------------------- 1 | -# Introduction 2 | 3 | # Who Is This For? 4 | 5 | ## Who Will Get the Most Benefit? 6 | This book is written for those who know the fundamentals of Laravel and are looking to see more advanced examples of implementing their knowledge in a testable and maintainable manner. 7 | 8 | From the lessons here, you will see how to apply architectural concepts to Laravel in a variety of ways, with the hope that you can use and adapt them for your own needs. 9 | 10 | ## What To Know Ahead of Time 11 | We all have varying levels of knowledge. This book is written for those who are familiar with Laravel 4 and its core concepts. It therefore assumes some knowledge of the reader. 12 | 13 | Taylor Otwell's book [*Laravel: From Apprentice to Artisan*](https://leanpub.com/laravel) is a great prerequisite. Although I'll cover these on a basic level, readers will hopefully already have a basic understanding of the principles of SOLID and Laravel's IoC container. -------------------------------------------------------------------------------- /manuscript/002-5_solid.txt: -------------------------------------------------------------------------------- 1 | {mainmatter} 2 | 3 | # SOLID 4 | 5 | Since I'll mention SOLID principles in passing throughout this book, I'll include a very brief explanation of them here, mostly taken from the [Wikipedia entry](http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) with some extra explanation in context of Laravel. 6 | 7 | ### Single Responsibility Principle 8 | 9 | A class (or unit of code) should have one responsibility. 10 | 11 | ### Open/Closed Principle 12 | 13 | A class should be open for extension but closed for modification. 14 | 15 | You can extend a class or implement and interface, but you should not be able to modify a class directly. This means you should extend a class and use the new extension rather than change a class directly. 16 | 17 | Additionally, this means setting class attributes and methods as private or protected properly so they cannot be modified by external code. 18 | 19 | ### Liskov Substitution Principle 20 | 21 | Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. 22 | 23 | In PHP, this often means creating interfaces for your code to implement. You can then change (switch-out) implementations of the interfaces. Doing so should be possible without having to change how your application code interacts with the implementation. The interface serves as a contract, guaranteeing that certain methods will be available. 24 | 25 | ### Interface Segregation Principle 26 | 27 | Many client-specific interfaces are better than one general-purpose interface. 28 | 29 | In general, it's preferable to create an interface and implement it many times over than create a general-purpose class which attempts to work in all situations. 30 | 31 | ### Dependency Inversion Principle 32 | 33 | One should depend upon abstractions rather than concrete classes. 34 | 35 | You should define class dependencies as an interface rather than a concrete class. This allows you to switch an implementation of the interface out without having to change the class using the dependency. 36 | -------------------------------------------------------------------------------- /manuscript/002_a_note_on_opinions.txt: -------------------------------------------------------------------------------- 1 | # A Note on Opinions 2 | 3 | Knowing the benefits (and pitfalls!) of Repository, Dependency Injection, Container, Service Locator patterns and other tools from our architectural tool set can be both liberating and exciting. 4 | 5 | The use of those tools, however, can be plagued with unexpected and often nuanced issues. 6 | 7 | As such, there are many opinions about how to go about crafting "good code" with such tools. 8 | 9 | As I use many real examples in this book, I have implicitly (and sometimes explicitly!) included my own opinions in this book. Always, however, inform your own opinion with both what you read and your own experience! 10 | 11 | W> ## "When all you have is a hammer..." 12 | W> 13 | W> Overuse of any of these tools can cause it's own issues. The chapters here are examples of how you *can* implement the architectural tools available to us. Knowing when *not* to use them is also an important decision to keep in mind. -------------------------------------------------------------------------------- /manuscript/005_the_sample_application.txt: -------------------------------------------------------------------------------- 1 | -# Setting Up Laravel 2 | 3 | # The Sample Application 4 | 5 | This book will use a sample application. We will build upon a simple blog application - everybody's favorite weekend learning project. 6 | 7 | The application is viewable on Github at [fideloper\Implementing-Laravel](https://github.com/fideloper/implementing-laravel). There you can view code and see working examples from this book. 8 | 9 | Here is an overview of some information about the application. 10 | 11 | ## Database 12 | 13 | We'll have a database with the following tables and fields: 14 | 15 | * **articles** - id, user_id, status_id, title, slug, excerpt, content, created_at, updated_at, deleted_at 16 | * **statuses** - id, status, slug, created_at, updated_at 17 | * **tags** - id, tag, slug 18 | * **articles_tags** - article_id, tag_id 19 | * **users** - id, email, password, created_at, updated_at, deleted_at 20 | 21 | You'll find migrations for these tables, as well as some seeds, in the `app/database/migrations` directory on Github. 22 | 23 | ## Models 24 | 25 | Each of the database tables will have a corresponding Eloquent model class, inside of the `app/models` directory. 26 | 27 | * `models/Article.php` 28 | * `models/Status.php` 29 | * `models/Tag.php` 30 | * `models/User.php` 31 | 32 | ## Relationships 33 | 34 | Users can create articles and so there is a "one-to-many" relationship between users and articles; Each user can write multiple articles, but each article only has one user. This relationship is created within both the `User` and `Article` models. 35 | 36 | Like users, there is a "one to many" relationship between statuses and articles - Each article is assigned a status, and a status can be assigned to many articles. 37 | 38 | Finally, the Articles and Tags have a relationship. Each article can have one or many tags. Each tag can be assigned to one or many articles. Therefore, there is a "many to many" relationship between articles and tags. This relationship is defined within the `Article` and `Tag` models, and uses the `Articles_Tags` pivot table. 39 | 40 | ## Testability and Maintainability 41 | 42 | I'll use the phrase "testable and maintainable" a lot. In most contexts, you can assume: 43 | 44 | 1. **Testable** code practices SOLID principles in a way that allows us to unit test - to test one specific unit of code (or class) without bringing in its dependencies. This is most notable in the use of Dependency Injection which directly allows the use of mocking to abstract away class dependencies. 45 | 2. **Maintainable** code looks to the long-term cost in development time. This means that making changes to the code should be easy, even after months or years. Popular examples of a change that should be easy is switching out one email provider for another or one data-store for another. This is most directly realized through the use of interfaces and making use of inversion of control. 46 | 47 | ## Architectural Notes 48 | This book will cover creating an application library which contains most application code for the sample blog. The structure of the application library makes some assumptions about how we go about building the application code. 49 | 50 | First, you'll note that I do not put Controllers into my application code. This is on purpose! 51 | 52 | Laravel is a web-framework, designed to handle an HTTP request and route to your application code. The Request, Router and Controller classes are all designed to operate at the HTTP level. 53 | 54 | Our application, however, does not need any such requirement. We're simply writing application logic which revolves around our business goals. 55 | 56 | An HTTP request being routed to a controller function can be seen as a convenient way for a request to reach our application code (we're all on the internet, after all). However, our application does not necessarily need to "know" about the code calling our application, or HTTP at all. 57 | 58 | This is an extension of the concept "separation of concerns". While we certainly are unlikely to use our applications out of context of the internet, it is useful to think of your web framework as an implementation detail of your application, rather than the core of it. 59 | 60 | Think of it this way: our applications are not an implementation of the Laravel framework. Instead, Laravel is an implementation of our applications. 61 | 62 | Taken to an extreme, an application would be able to be implemented by any framework or code capable of implementing the interfaces we define. 63 | 64 | Extremes, however, are not pragmatic, and so we use Laravel to accomplish most of our application goals. Laravel is the means to most of our ends - it does things extremely well! 65 | 66 | This also shapes the application library structure I build up to in this book. I'll create a series of directories reflecting application code functions, such as a data repository and cache services. These functional areas tend to be interfaced, which we implement using various Illuminate packages. -------------------------------------------------------------------------------- /manuscript/007_application_setup.txt: -------------------------------------------------------------------------------- 1 | # Application Setup 2 | 3 | If you've ever asked yourself "Where should I put my code?", this chapter will answer that question. 4 | 5 | **Always** create an application library. 6 | 7 | An application-specific library works well for code which isn't generic enough to warrant its own package, and also isn't a direct use of Laravel's core classes, such as a controller. An example is business logic code or integration of third-party libraries. 8 | 9 | Basically, anything that you want to keep out of your controllers (most code!) should belong in your application library. 10 | 11 | ## Setting Up the Application Library 12 | 13 | To accomplish this, we'll start by creating a namespace for the application. In our example application, I'll choose the easy-to-type name "Impl", short for "Implementing Laravel". This will be both our application's top-level namespace and the directory name. We will create the directory `app/Impl` to house this application code. 14 | 15 | Here's what the folder structure will look like: 16 | 17 | Implementing Laravel 18 | |- app 19 | |--- commands 20 | |--- config 21 | |--- [ and so on ] 22 | |--- Impl 23 | |------ Exception 24 | |------ Repo 25 | |------ Service 26 | 27 | 28 | As you likely know, the namespace, file name and directory structure matters - they allow us to autoload the PHP files based on the [PSR-0 autoloading standard](http://www.php-fig.org/psr/0/). 29 | 30 | For example, a sample class in this structure might look like this: 31 | 32 | {title="File: app/Impl/Repo/EloquentArticle.php", lang=php} 33 | Using [Composer's dump-autoload](http://getcomposer.org/doc/03-cli.md#dump-autoload) is a way of telling Composer to find new classes in a classmap package (Laravel's controllers, models, etc). For PSR-0 autoloading, it can also rebuild composer's optimized autoloader, saving time when the application is run. 74 | 75 | Now anywhere in our code, we can instantiate the `Impl\Repo\EloquentArticle` class and PHP will know to autoload it! 76 | 77 | {title="File: app/routes.php"} 78 | Route::get('/', function() 79 | { 80 | $articleRepo = new Impl\Repo\EloquentArticle; 81 | 82 | return View::make('home')->with('articles', $articleRepo->all()); 83 | } 84 | 85 | ## Wrapping Up 86 | 87 | We saw how to create a separate application library to house our application code. This library is where our business logic, extensions, IoC bindings and more will be added. 88 | 89 | T>## Location, Location, Location 90 | T> 91 | T> Your application library can go anywhere. By convention, I add mine into the `app` directory, but you are certainly not limited to that location. You may want to use another location, or even consider creating a package out of your application library that can be added as a Composer dependency! -------------------------------------------------------------------------------- /manuscript/014_conclusion.txt: -------------------------------------------------------------------------------- 1 | -# Conclusion 2 | 3 | # Review 4 | 5 | We covered a lot of ground in this book! Some of the topics included: 6 | 7 | ## Installation 8 | 9 | Installing Laravel, including environment setup and production considerations. 10 | 11 | ## Application Setup 12 | 13 | Using an application library. 14 | 15 | ## Repository Pattern 16 | 17 | The how and why of using repositories as an interface to your data storage. 18 | 19 | ## Caching In the Repository 20 | 21 | We added a service layer into our code repository. In this example, we added a layer of caching. 22 | 23 | ## Validation 24 | 25 | We created validation as a service, helping us abstract out the work of validation and implement validation classes specific to our needed use cases. 26 | 27 | ## Form Processing 28 | 29 | We created classes for orchestrating the validation of input and the interaction with our data repositories. These form classes are decoupled from HTTP requests and are able to be used outside of a controller. This also makes them easier to test. 30 | 31 | ## Error Handling 32 | 33 | We went over some considerations of how and when to use error handling, and saw an example of how it might be done to catch application-specific errors. 34 | 35 | ## Third Party Libraries 36 | 37 | We used Twilio's SDK to build a notification library so could send SMS notifications within our error handler. 38 | 39 | # What Did You Gain? 40 | 41 | Most of this book lays the ground work for building a full featured application. 42 | 43 | What I hope you gained from reading this is insight on how SOLID principles can be used in Laravel. The use of interfaces, the IoC container and the Service Providers all provide a powerful way to create a highly testable and maintainable PHP application. 44 | 45 | ## The Future 46 | 47 | I'm hoping the early editions of this book serve as a base on which to build. 48 | 49 | The answer to many questions such as "How do I use queues effectively?", "How do I integrate a search engine?" or "What's an advanced usage of Service Providers" will rely and expand on the fundamentals covered here. 50 | 51 | With any luck, this will be just the beginning. 52 | 53 | Happy coding! -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | 000_thanks.txt 2 | 001_who_is_this_for.txt 3 | 002_a_note_on_opinions.txt 4 | 002-5_solid.txt 5 | 003_the_container.txt 6 | 004_dependency_injection.txt 7 | 005_the_sample_application.txt 8 | 006_installation.txt 9 | 007_application_setup.txt 10 | 008_repository_pattern.txt 11 | 009_repository_pattern_cacheing.txt 12 | 010_validation.txt 13 | 011_form_processing.txt 14 | 012_error_handling.txt 15 | 013_notifications.txt 16 | 014_conclusion.txt -------------------------------------------------------------------------------- /manuscript/Preview.txt: -------------------------------------------------------------------------------- 1 | 003_the_container.txt 2 | 004_dependency_injection.txt 3 | -------------------------------------------------------------------------------- /manuscript/Sample.txt: -------------------------------------------------------------------------------- 1 | 003_the_container.txt 2 | 004_dependency_injection.txt 3 | -------------------------------------------------------------------------------- /manuscript/images/title_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/manuscript/images/title_page.png -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./app/tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | SetEnv LARA_ENV local 2 | 3 | 4 | Options -MultiViews 5 | RewriteEngine On 6 | 7 | RewriteCond %{REQUEST_FILENAME} !-d 8 | RewriteCond %{REQUEST_FILENAME} !-f 9 | RewriteRule ^ index.php [L] 10 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | We highly recommend you use SASS and write your custom styles in sass/_custom.scss. 3 | However, this blank file is available if you prefer 4 | */ 5 | -------------------------------------------------------------------------------- /public/facebook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/facebook.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/favicon.png -------------------------------------------------------------------------------- /public/fonts/icons/entypo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/fonts/icons/entypo.eot -------------------------------------------------------------------------------- /public/fonts/icons/entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/fonts/icons/entypo.ttf -------------------------------------------------------------------------------- /public/fonts/icons/entypo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/fonts/icons/entypo.woff -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | /* TEAM */ 2 | 3 | Digital Surgeons 4 | Twitter: @digitalsurgeons 5 | Twitter: @gumbycss 6 | Web: www.digitalsurgeons.com 7 | Web: www.gumbyframework.com 8 | 9 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 10 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 11 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 12 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:~~~====~,,,,,,,,,,,,, 13 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,=================+,,,,,,,,,,,,, 14 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,:==================,,,,,,,,,,,,, 15 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,+=================:,,,,,,,,,,,, 16 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,==================:,,,,,,,,,,,, 17 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~=================~,,,,,,,,,,,, 18 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~=================~,,,,,,,,,,,, 19 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,=================~,,,,,,,,,,,, 20 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,==================,,,,,,,,,,,, 21 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,==================,,,,,,,,,,,, 22 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,==================,,,,,,,,,,,, 23 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,==================,,,,,,,,,,,, 24 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,==================,,,,,,,,,,,, 25 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~==============:,,,,,,,,,,,,,, 26 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,==========:,,,,,,,,,,,,,,,,,,, 27 | ,,,,,,,,,,,,,,,,,,,,,,,,:~=========:,,,,,,,,,,,,,,,,,,,,,,,, 28 | ,,,,,,,,,,,,,,,,,:================,,,,,,,,,,,,,,,,,,,,,,,,,, 29 | ,,,,,,,,,,,,:=====================,,,,,,,,,,,,,,,,,,,,,,,,,, 30 | ,,,,,,,,,,,=======================,,,,,,,,,,,,,,,,,,,,,,,,,, 31 | ,,,,,,,,,,,=======================:,,,,,,,,,,,,,,,,,,,,,,,,, 32 | ,,,,,,,,,,,=======================~,,,,,,,,,,,,,,,,,,,,,,,,, 33 | ,,,,,,,,,,,,=~====================~,,,,,,,,,,,,,,,,,,,,,,,,, 34 | ,,,,,,,,,,,,=~~~~~~~~~~~~~~~~~~~~~=,,,,,,,,,,,,,,,,,,,,,,,,, 35 | ,,,,,,,,,,,,~~==~~~~~~~~~~~~~~=====,,,,,,,,,,,,,,,,,,,,,,,,, 36 | ,,,,,,,,,,,,,=~~~~~~~~~~~~~~~~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,, 37 | ,,,,,,,,,,,,,=~~~~~~~~~~~~~~~~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,, 38 | ,,,,,,,,,,,,,,~~~~~~~~~~~~~~~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,, 39 | ,,,,,,,,,,,,,,~~~~~~~~~~~~~~~~:,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 40 | ,,,,,,,,,,,,,,~~~~~~~~~~~~~:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 41 | ,,,,,,,,,,,,,,,~~~~~~~~,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 42 | ,,,,,,,,,,,,,,,~~~~:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 43 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 44 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.. 45 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.... 46 | ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..... 47 | -------------------------------------------------------------------------------- /public/img/gumby_mainlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/img/gumby_mainlogo.png -------------------------------------------------------------------------------- /public/img/gumby_mainlogo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/img/gumby_mainlogo@2x.png -------------------------------------------------------------------------------- /public/img/img_silence_demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/img/img_silence_demo.jpg -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | /* 10 | |-------------------------------------------------------------------------- 11 | | Register The Auto Loader 12 | |-------------------------------------------------------------------------- 13 | | 14 | | Composer provides a convenient, automatically generated class loader 15 | | for our application. We just need to utilize it! We'll require it 16 | | into the script here so that we do not have to worry about the 17 | | loading of any our classes "manually". Feels great to relax. 18 | | 19 | */ 20 | 21 | require __DIR__.'/../bootstrap/autoload.php'; 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Turn On The Lights 26 | |-------------------------------------------------------------------------- 27 | | 28 | | We need to illuminate PHP development, so let's turn on the lights. 29 | | This bootstraps the framework and gets it ready for use, then it 30 | | will load up this application so that we can run it and send 31 | | the responses back to the browser and delight these users. 32 | | 33 | */ 34 | 35 | $app = require_once __DIR__.'/../bootstrap/start.php'; 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Run The Application 40 | |-------------------------------------------------------------------------- 41 | | 42 | | Once we have the application, we can simply call the run method, 43 | | which will execute the request and send the response back to 44 | | the client's browser allowing them to enjoy the creative 45 | | and wonderful applications we have created for them. 46 | | 47 | */ 48 | 49 | $app->run(); 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Shutdown The Application 54 | |-------------------------------------------------------------------------- 55 | | 56 | | Once the app has finished running, we will fire off the shutdown events 57 | | so that any final work may be done by the application before we shut 58 | | down the process. This is the last thing to happen to the request. 59 | | 60 | */ 61 | 62 | $app->shutdown(); -------------------------------------------------------------------------------- /public/js/libs/gumby.init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby Init 3 | */ 4 | 5 | // test for touch event support 6 | Modernizr.load({ 7 | test: Modernizr.touch, 8 | 9 | // if present load custom jQuery mobile build and update Gumby.click 10 | yep: Gumby.path+'/jquery.mobile.custom.min.js', 11 | callback: function(url, result, key) { 12 | // check jQuery mobile has successfully loaded before using tap events 13 | if($.mobile) { 14 | window.Gumby.click += ' tap'; 15 | } 16 | }, 17 | 18 | // either way initialize Gumby 19 | complete: function() { 20 | window.Gumby.init(); 21 | 22 | // if AMD return Gumby object to define 23 | if(typeof define == "function" && define.amd) { 24 | define(window.Gumby); 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /public/js/libs/gumby.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby Framework 3 | * --------------- 4 | * 5 | * Follow @gumbycss on twitter and spread the love. 6 | * We worked super hard on making this awesome and released it to the web. 7 | * All we ask is you leave this intact. #gumbyisawesome 8 | * 9 | * Gumby Framework 10 | * http://gumbyframework.com 11 | * 12 | * Built with love by your friends @digitalsurgeons 13 | * http://www.digitalsurgeons.com 14 | * 15 | * Free to use under the MIT license. 16 | * http://www.opensource.org/licenses/mit-license.php 17 | */ 18 | !function() { 19 | 20 | 'use strict'; 21 | 22 | function Gumby() { 23 | this.$dom = $(document); 24 | this.isOldie = !!this.$dom.find('html').hasClass('oldie'); 25 | this.click = 'click'; 26 | this.onReady = this.onOldie = this.onTouch = false; 27 | this.uiModules = {}; 28 | this.inits = {}; 29 | 30 | // check and set path with js/libs default 31 | this.path = $('script[gumby-path]').attr('gumby-path') || 'js/libs'; 32 | 33 | // check and set breakpoint with 1024 default 34 | this.breakpoint = Number($('script[gumby-breakpoint]').attr('gumby-breakpoint')) || 1024; 35 | } 36 | 37 | // initialize Gumby 38 | Gumby.prototype.init = function() { 39 | var scope = this; 40 | 41 | // call ready() code when dom is ready 42 | this.$dom.ready(function() { 43 | // init UI modules 44 | scope.initUIModules(); 45 | 46 | if(scope.onReady) { 47 | scope.onReady(); 48 | } 49 | 50 | // call oldie() callback if applicable 51 | if(scope.isOldie && scope.onOldie) { 52 | scope.onOldie(); 53 | } 54 | 55 | // call touch() callback if applicable 56 | if(Modernizr.touch && scope.onTouch) { 57 | scope.onTouch(); 58 | } 59 | }); 60 | }; 61 | 62 | // public helper - set Gumby ready callback 63 | Gumby.prototype.ready = function(code) { 64 | if(code && typeof code === 'function') { 65 | this.onReady = code; 66 | } 67 | }; 68 | 69 | // public helper - set oldie callback 70 | Gumby.prototype.oldie = function(code) { 71 | if(code && typeof code === 'function') { 72 | this.onOldie = code; 73 | } 74 | }; 75 | 76 | // public helper - set touch callback 77 | Gumby.prototype.touch = function(code) { 78 | if(code && typeof code === 'function') { 79 | this.onTouch = code; 80 | } 81 | }; 82 | 83 | // public helper - return debuggin object including uiModules object 84 | Gumby.prototype.debug = function() { 85 | return { 86 | $dom: this.$dom, 87 | isOldie: this.isOldie, 88 | uiModules: this.uiModules, 89 | click: this.click 90 | }; 91 | }; 92 | 93 | // grab attribute value, testing data- gumby- and no prefix 94 | Gumby.prototype.selectAttr = function() { 95 | var i = 0; 96 | 97 | // any number of attributes can be passed 98 | for(; i < arguments.length; i++) { 99 | // various formats 100 | var attr = arguments[i], 101 | dataAttr = 'data-'+arguments[i], 102 | gumbyAttr = 'gumby-'+arguments[i]; 103 | 104 | // first test for data-attr 105 | if(this.is('['+dataAttr+']')) { 106 | return this.attr(dataAttr) ? this.attr(dataAttr) : true; 107 | 108 | // next test for gumby-attr 109 | } else if(this.is('['+gumbyAttr+']')) { 110 | return this.attr(gumbyAttr) ? this.attr(gumbyAttr) : true; 111 | 112 | // finally no prefix 113 | } else if(this.is('['+attr+']')) { 114 | return this.attr(attr) ? this.attr(attr) : true; 115 | } 116 | } 117 | 118 | // none found 119 | return false; 120 | }; 121 | 122 | // add an initialisation method 123 | Gumby.prototype.addInitalisation = function(ref, code) { 124 | this.inits[ref] = code; 125 | }; 126 | 127 | // initialize a uiModule 128 | Gumby.prototype.initialize = function(ref, all) { 129 | if(this.inits[ref] && typeof this.inits[ref] === 'function') { 130 | this.inits[ref](all); 131 | } 132 | }; 133 | 134 | // store a UI module 135 | Gumby.prototype.UIModule = function(data) { 136 | var module = data.module; 137 | this.uiModules[module] = data; 138 | }; 139 | 140 | // loop round and init all UI modules 141 | Gumby.prototype.initUIModules = function() { 142 | var x; 143 | for(x in this.uiModules) { 144 | this.uiModules[x].init(); 145 | } 146 | }; 147 | 148 | window.Gumby = new Gumby(); 149 | 150 | }(); 151 | -------------------------------------------------------------------------------- /public/js/libs/ui/gumby.checkbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby Checkbox 3 | */ 4 | !function() { 5 | 6 | 'use strict'; 7 | 8 | function Checkbox($el) { 9 | 10 | this.$el = $el; 11 | this.$input = this.$el.find('input[type=checkbox]'); 12 | 13 | var scope = this; 14 | 15 | // listen for click event and custom gumby check/uncheck events 16 | this.$el.on(Gumby.click, function(e) { 17 | // prevent propagation 18 | e.stopImmediatePropagation(); 19 | 20 | // prevent checkbox checking, we'll do that manually 21 | e.preventDefault(); 22 | 23 | // do nothing if checkbox is disabled 24 | if(scope.$input.is('[disabled]')) { 25 | return; 26 | } 27 | 28 | // check/uncheck 29 | if(scope.$el.hasClass('checked')) { 30 | scope.update(false); 31 | } else { 32 | scope.update(true); 33 | } 34 | }).on('gumby.check', function() { 35 | scope.update(true); 36 | }).on('gumby.uncheck', function() { 37 | scope.update(false); 38 | }); 39 | 40 | // update any .checked checkboxes on load 41 | if(scope.$el.hasClass('checked')) { 42 | scope.update(true); 43 | } 44 | } 45 | 46 | // update checkbox, check equals true/false to sepcify check/uncheck 47 | Checkbox.prototype.update = function(check) { 48 | 49 | var $span = this.$el.find('span'); 50 | 51 | // check checkbox - check input, add checked class, append 52 | if(check) { 53 | 54 | $span.append(''); 55 | 56 | this.$input.prop('checked', true).end() 57 | .addClass('checked') 58 | .trigger('gumby.onCheck').trigger('gumby.onChange'); 59 | 60 | // uncheck checkbox - uncheck input, remove checked class, remove 61 | } else { 62 | this.$input.prop('checked', false).end() 63 | .find('i').remove().end() 64 | .removeClass('checked').trigger('gumby.onUncheck').trigger('gumby.onChange'); 65 | } 66 | }; 67 | 68 | // add initialisation 69 | Gumby.addInitalisation('checkboxes', function() { 70 | $('.checkbox').each(function() { 71 | var $this = $(this); 72 | // this element has already been initialized 73 | if($this.data('isCheckbox')) { 74 | return true; 75 | } 76 | // mark element as initialized 77 | $this.data('isCheckbox', true); 78 | new Checkbox($this); 79 | }); 80 | }); 81 | 82 | // register UI module 83 | Gumby.UIModule({ 84 | module: 'checkbox', 85 | events: ['onCheck', 'onUncheck', 'onChange', 'check', 'uncheck'], 86 | init: function() { 87 | Gumby.initialize('checkboxes'); 88 | } 89 | }); 90 | }(); 91 | -------------------------------------------------------------------------------- /public/js/libs/ui/gumby.fittext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby FitText 3 | * 4 | * Adapted from the awesome FitText jQuery plugin 5 | * brought to you by Paravel - http://paravelinc.com/ 6 | */ 7 | !function() { 8 | 9 | 'use strict'; 10 | 11 | function FitText($el) { 12 | this.$el = $el; 13 | 14 | this.rate = 0; 15 | this.fontSizes = {}; 16 | 17 | // set up module based on attributes 18 | this.setup(); 19 | 20 | var scope = this; 21 | 22 | // re-initialize module 23 | this.$el.on('gumby.initialize', function() { 24 | scope.setup(); 25 | }); 26 | 27 | // lets go 28 | $(window).on('load resize orientationchange', function() { 29 | scope.resize(); 30 | }); 31 | } 32 | 33 | // set up module based on attributes 34 | FitText.prototype.setup = function() { 35 | // optional compressor rate 36 | this.rate = Number(Gumby.selectAttr.apply(this.$el, ['rate'])) || 1; 37 | // optional font sizes (min|max) 38 | this.fontSizes = this.parseSizes(Gumby.selectAttr.apply(this.$el, ['sizes'])); 39 | }; 40 | 41 | // apply the resizing 42 | FitText.prototype.resize = function() { 43 | this.$el.css('font-size', this.calculateSize()); 44 | }; 45 | 46 | // calculate the font size 47 | FitText.prototype.calculateSize = function() { 48 | return Math.max(Math.min(this.$el.width() / (this.rate*10), parseFloat(this.fontSizes.max)), parseFloat(this.fontSizes.min)); 49 | }; 50 | 51 | // parse size attributes with min|max syntax 52 | FitText.prototype.parseSizes = function(attrStr) { 53 | var sizes = { 54 | min: Number.NEGATIVE_INFINITY, 55 | max: Number.POSITIVE_INFINITY 56 | }; 57 | 58 | // attribute is optional 59 | if(!attrStr) { return sizes; } 60 | 61 | // min and/or max specified 62 | if(attrStr.indexOf('|') > -1) { 63 | attrStr = attrStr.split('|'); 64 | 65 | // both are optional 66 | sizes.min = Number(attrStr[0]) || sizes.min; 67 | sizes.max = Number(attrStr[1]) || sizes.max; 68 | } 69 | 70 | // only one value specific without | so use as min 71 | sizes.min = Number(attrStr) || sizes.min; 72 | 73 | return sizes; 74 | }; 75 | 76 | // add initialisation 77 | Gumby.addInitalisation('fittext', function(all) { 78 | $('.fittext').each(function() { 79 | var $this = $(this); 80 | 81 | // this element has already been initialized 82 | // and we're only initializing new modules 83 | if($this.data('isFittext') && !all) { 84 | return true; 85 | 86 | // this element has already been initialized 87 | // and we need to reinitialize it 88 | } else if($this.data('isFittext') && all) { 89 | $this.trigger('gumby.initialize'); 90 | return true; 91 | } 92 | 93 | // mark element as initialized 94 | $this.data('isFittext', true); 95 | new FitText($this); 96 | }); 97 | }); 98 | 99 | // register UI module 100 | Gumby.UIModule({ 101 | module: 'fittext', 102 | events: [], 103 | init: function() { 104 | Gumby.initialize('fittext'); 105 | } 106 | }); 107 | }(); 108 | -------------------------------------------------------------------------------- /public/js/libs/ui/gumby.navbar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby Navbar 3 | */ 4 | !function() { 5 | 6 | 'use strict'; 7 | 8 | var $html = Gumby.$dom.find('html'); 9 | 10 | // define and init module on touch enabled devices only 11 | // when we are at tablet size or smaller 12 | if(!Modernizr.touch || $(window).width() > Gumby.breakpoint) { 13 | 14 | // add Gumby no touch class 15 | $html.addClass('gumby-no-touch'); 16 | return; 17 | } 18 | 19 | // add Gumby touch class 20 | $html.addClass('gumby-touch'); 21 | 22 | function Navbar($el) { 23 | this.$el = $el; 24 | this.$dropDowns = this.$el.find('li:has(.dropdown)'); 25 | var scope = this; 26 | 27 | // when navbar items 28 | this.$dropDowns 29 | // are tapped hide/show dropdowns 30 | .on('tap', this.toggleDropdown) 31 | // are swiped right open link 32 | .on('swiperight', this.openLink); 33 | 34 | // if there's a link set 35 | if(this.$dropDowns.children('a').attr('href') !== '#') { 36 | // append an icon 37 | this.$dropDowns.children('a').append('').children('i') 38 | // and bind to click event to open link 39 | .on('tap', this.openLink); 40 | } 41 | 42 | // override with childlinks 43 | this.$dropDowns.find('.dropdown li:not(:has(.dropdown)) a[href]').on('tap', this.openLink); 44 | 45 | // on mousemove and touchstart toggle modernizr classes and disable/enable this module 46 | // workaround for Pixel and other multi input devices 47 | $(window).on('mousemove touchstart', function(e) { 48 | e.stopImmediatePropagation(); 49 | if(e.type === 'mousemove') { 50 | scope.$dropDowns.on('mouseover mouseout', scope.toggleDropdown); 51 | } 52 | }); 53 | } 54 | 55 | Navbar.prototype.toggleDropdown = function(e) { 56 | // prevent click from triggering here too 57 | e.stopImmediatePropagation(); 58 | e.preventDefault(); 59 | 60 | var $this = $(this); 61 | 62 | if($this.hasClass('active')) { 63 | $this.removeClass('active'); 64 | } else { 65 | $this.addClass('active'); 66 | } 67 | }; 68 | 69 | // handle opening list item link 70 | Navbar.prototype.openLink = function(e) { 71 | e.stopImmediatePropagation(); 72 | e.preventDefault(); 73 | 74 | var $this = $(this), 75 | $el = $this, href; 76 | 77 | // tapped icon 78 | if($this.is('i')) { 79 | $el = $this.parent('a'); 80 | // swiped li 81 | } else if($this.is('li')) { 82 | $el = $this.children('a'); 83 | } 84 | 85 | href = $el.attr('href'); 86 | 87 | // open in new window 88 | if($el.attr('target') == 'blank') { 89 | window.open(href); 90 | // regular relocation 91 | } else { 92 | window.location = href; 93 | } 94 | }; 95 | 96 | // add initialisation 97 | Gumby.addInitalisation('navbars', function() { 98 | $('.navbar').each(function() { 99 | var $this = $(this); 100 | // this element has already been initialized 101 | if($this.data('isNavbar')) { 102 | return true; 103 | } 104 | // mark element as initialized 105 | $this.data('isNavbar', true); 106 | new Navbar($this); 107 | }); 108 | }); 109 | 110 | // register UI module 111 | Gumby.UIModule({ 112 | module: 'navbar', 113 | events: [], 114 | init: function() { 115 | Gumby.initialize('navbars'); 116 | } 117 | }); 118 | }(); 119 | -------------------------------------------------------------------------------- /public/js/libs/ui/gumby.radiobtn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby RadioBtn 3 | */ 4 | !function() { 5 | 6 | 'use strict'; 7 | 8 | function RadioBtn($el) { 9 | 10 | this.$el = $el; 11 | this.$input = this.$el.find('input[type=radio]'); 12 | 13 | var scope = this; 14 | 15 | // listen for click event and custom gumby check event 16 | this.$el.on(Gumby.click, function(e) { 17 | // prevent propagation 18 | e.stopImmediatePropagation(); 19 | 20 | // prevent radio button checking, we'll do that manually 21 | e.preventDefault(); 22 | 23 | // do nothing if radio is disabled 24 | if (scope.$input.is('[disabled]')) { 25 | return; 26 | } 27 | 28 | // check radio button 29 | scope.update(); 30 | }).on('gumby.check', function() { 31 | scope.update(); 32 | }); 33 | 34 | // update any .checked checkboxes on load 35 | if(scope.$el.hasClass('checked')) { 36 | scope.update(); 37 | } 38 | } 39 | 40 | // check radio button, uncheck all others in name group 41 | RadioBtn.prototype.update = function() { 42 | var $span = this.$el.find('span'), 43 | // the group of radio buttons 44 | group = 'input[name="'+this.$input.attr('name')+'"]'; 45 | 46 | // uncheck radio buttons in same group - uncheck input, remove checked class, remove 47 | $('.radio').has(group).removeClass('checked') 48 | .find('input').prop('checked', false).end() 49 | .find('i').remove(); 50 | 51 | // check this radio button - check input, add checked class, append 52 | this.$input.prop('checked', true); 53 | $span.append(''); 54 | this.$el.addClass('checked').trigger('gumby.onChange'); 55 | }; 56 | 57 | // add initialisation 58 | Gumby.addInitalisation('radiobtns', function() { 59 | $('.radio').each(function() { 60 | var $this = $(this); 61 | // this element has already been initialized 62 | if($this.data('isRadioBtn')) { 63 | return true; 64 | } 65 | // mark element as initialized 66 | $this.data('isRadioBtn', true); 67 | new RadioBtn($this); 68 | }); 69 | }); 70 | 71 | // register UI module 72 | Gumby.UIModule({ 73 | module: 'radiobtn', 74 | events: ['onChange', 'check'], 75 | init: function() { 76 | Gumby.initialize('radiobtns'); 77 | } 78 | }); 79 | }(); 80 | -------------------------------------------------------------------------------- /public/js/libs/ui/gumby.retina.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby Retina 3 | */ 4 | !function() { 5 | 6 | 'use strict'; 7 | 8 | function Retina($el) { 9 | this.$el = $el; 10 | this.imageSrc = this.$el.attr('src'); 11 | this.retinaSrc = this.fetchRetinaImage(); 12 | this.$retinaImg = $(new Image()); 13 | 14 | var scope = this; 15 | 16 | // image src not valid 17 | if(!this.retinaSrc) { 18 | return false; 19 | } 20 | 21 | // load retina image 22 | this.$retinaImg.attr('src', this.retinaSrc).load(function() { 23 | scope.retinaImageLoaded(); 24 | }); 25 | } 26 | 27 | // fetch retina src by appending '@2x' to image string before extension 28 | Retina.prototype.fetchRetinaImage = function() { 29 | var imgSrc = this.imageSrc, 30 | index = this.imageSrc.search(/(\.|\/)(gif|jpe?g|png)$/i); 31 | 32 | // image src is not valid 33 | if(index < 0) { 34 | return false; 35 | } 36 | 37 | // return retina src 38 | return imgSrc.substr(0, index) + '@2x' + imgSrc.substr(index, imgSrc.length); 39 | }; 40 | 41 | // once retina image loaded swap original src 42 | Retina.prototype.retinaImageLoaded = function() { 43 | this.$el.attr('src', this.$retinaImg.attr('src')).trigger('gumby.onRetina'); 44 | }; 45 | 46 | // add initialisation 47 | Gumby.addInitalisation('retina', function() { 48 | 49 | // this module is for retina devices only 50 | if(!window.devicePixelRatio || window.devicePixelRatio <= 1) { 51 | return; 52 | } 53 | 54 | $('img[data-retina],img[gumby-retina],img[retina]').each(function() { 55 | var $this = $(this); 56 | // this element has already been initialized 57 | if($this.data('isRetina')) { 58 | return true; 59 | } 60 | // mark element as initialized 61 | $this.data('isRetina', true); 62 | new Retina($this); 63 | }); 64 | }); 65 | 66 | // register UI module 67 | Gumby.UIModule({ 68 | module: 'retina', 69 | events: ['onRetina'], 70 | init: function() { 71 | Gumby.initialize('retina'); 72 | } 73 | }); 74 | }(); 75 | -------------------------------------------------------------------------------- /public/js/libs/ui/gumby.skiplink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby SkipLink 3 | */ 4 | !function() { 5 | 6 | 'use strict'; 7 | 8 | function SkipLink($el) { 9 | this.$el = $el; 10 | this.targetPos = 0; 11 | this.duration = 0; 12 | this.offset = false; 13 | this.easing = ''; 14 | this.update = false; 15 | 16 | // set up module based on attributes 17 | this.setup(); 18 | 19 | var scope = this; 20 | 21 | // skip to target element on click or trigger of gumby.skipTo event 22 | this.$el.on(Gumby.click+' gumby.skip', function(e) { 23 | 24 | e.stopImmediatePropagation(); 25 | e.preventDefault(); 26 | 27 | // calculate target on each click if update var set to true 28 | if(scope.update) { 29 | scope.calculateTarget(scope.skipTo); 30 | 31 | // skip straight to target 32 | } else { 33 | scope.skipTo(); 34 | } 35 | }).on('gumby.initialize', function() { 36 | scope.setup(); 37 | }); 38 | } 39 | 40 | // set up module based on attributes 41 | SkipLink.prototype.setup = function() { 42 | this.duration = Number(Gumby.selectAttr.apply(this.$el, ['duration'])) || 200; 43 | this.offset = Gumby.selectAttr.apply(this.$el, ['offset']) || false; 44 | this.easing = Gumby.selectAttr.apply(this.$el, ['easing']) || 'swing'; 45 | this.update = Gumby.selectAttr.apply(this.$el, ['update']) ? true : false; 46 | 47 | this.calculateTarget(); 48 | }; 49 | 50 | // calculate target px point to skip to 51 | SkipLink.prototype.calculateTarget = function(cb) { 52 | 53 | var scope = this, 54 | target = Gumby.selectAttr.apply(this.$el, ['goto']), 55 | $target; 56 | 57 | // 'top' specified so target is 0px 58 | if(target == 'top') { 59 | this.targetPos = 0; 60 | 61 | // px point specified 62 | } else if($.isNumeric(target)) { 63 | this.targetPos = Number(target); 64 | } else { 65 | 66 | // check for element with target as selector 67 | $target = $(target); 68 | 69 | // target does not exist, we need a target 70 | if(!$target) { 71 | return false; 72 | } 73 | 74 | this.targetPos = $target.offset().top; 75 | } 76 | 77 | if(cb) { 78 | cb.apply(this); 79 | } 80 | }; 81 | 82 | // animate body, html scrollTop value to target px point 83 | SkipLink.prototype.skipTo = function() { 84 | var scope = this; 85 | 86 | // slide to position of target 87 | $('html,body').animate({ 88 | 'scrollTop' : this.calculateOffset() 89 | }, this.duration, this.easing).promise().done(function() { 90 | scope.$el.trigger('gumby.onComplete'); 91 | }); 92 | }; 93 | 94 | // calculate offset with current target point 95 | SkipLink.prototype.calculateOffset = function() { 96 | // no offset so return target here 97 | if(!this.offset) { 98 | return this.targetPos; 99 | } 100 | 101 | // negative / positive 102 | var op = this.offset.substr(0, 1), 103 | off = Number(this.offset.substr(1, this.offset.length)); 104 | 105 | // subtract offset from target position 106 | if(op === '-') { 107 | return this.targetPos - off; 108 | // add offset to target position 109 | } else if(op === '+') { 110 | return this.targetPos + off; 111 | } 112 | }; 113 | 114 | // add initialisation 115 | Gumby.addInitalisation('skiplinks', function(all) { 116 | $('.skiplink > a, .skip').each(function() { 117 | var $this = $(this); 118 | 119 | // this element has already been initialized 120 | // and we're only initializing new modules 121 | if($this.data('isSkipLink') && !all) { 122 | return true; 123 | 124 | // this element has already been initialized 125 | // and we need to reinitialize it 126 | } else if($this.data('isSkipLink') && all) { 127 | $this.trigger('gumby.initialize'); 128 | return true; 129 | } 130 | 131 | // mark element as initialized 132 | $this.data('isSkipLink', true); 133 | new SkipLink($this); 134 | }); 135 | }); 136 | 137 | // register UI module 138 | Gumby.UIModule({ 139 | module: 'skiplink', 140 | events: ['onComplete', 'skip'], 141 | init: function() { 142 | Gumby.initialize('skiplinks'); 143 | } 144 | }); 145 | }(); 146 | -------------------------------------------------------------------------------- /public/js/libs/ui/gumby.tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby Tabs 3 | */ 4 | !function() { 5 | 6 | 'use strict'; 7 | 8 | function Tabs($el) { 9 | 10 | this.$el = $el; 11 | this.$nav = this.$el.find('ul.tab-nav > li'); 12 | this.$content = this.$el.find('.tab-content'); 13 | 14 | var scope = this; 15 | 16 | // listen for click event on tab nav and custom gumby set event 17 | this.$nav.children('a').on(Gumby.click, function(e) { 18 | e.stopImmediatePropagation(); 19 | e.preventDefault(); 20 | scope.click($(this)); 21 | }); 22 | 23 | // listen for gumby.set value for dynamically set tabs 24 | this.$el.on('gumby.set', function(e, index) { 25 | scope.set(e, index); 26 | }); 27 | } 28 | 29 | // handle tab nav click event 30 | Tabs.prototype.click = function($this) { 31 | // index of item to activate 32 | var index = $this.parent().index(); 33 | 34 | // deactivate other tab navigation and content 35 | this.$nav.add(this.$content).removeClass('active'); 36 | 37 | // activate this tab nav link and content 38 | this.$nav.eq(index).add(this.$content.eq(index)).addClass('active'); 39 | 40 | // trigger gumby.change event and pass current active tab index 41 | this.$el.trigger('gumby.onChange', index); 42 | }; 43 | 44 | // set specific tab 45 | Tabs.prototype.set = function(e, index) { 46 | this.$nav.eq(index).find('a').trigger(Gumby.click); 47 | }; 48 | 49 | // add initialisation 50 | Gumby.addInitalisation('tabs', function() { 51 | $('.tabs').each(function() { 52 | var $this = $(this); 53 | // this element has already been initialized 54 | if($this.data('isTabs')) { 55 | return true; 56 | } 57 | // mark element as initialized 58 | $this.data('isTabs', true); 59 | new Tabs($this); 60 | }); 61 | }); 62 | 63 | // register UI module 64 | Gumby.UIModule({ 65 | module: 'tabs', 66 | events: ['onChange', 'set'], 67 | init: function() { 68 | Gumby.initialize('tabs'); 69 | } 70 | }); 71 | }(); 72 | -------------------------------------------------------------------------------- /public/js/libs/ui/jquery.validation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby jQuery Validation Plugin 3 | */ 4 | !function($) { 5 | 6 | 'use strict'; 7 | 8 | function Validation($this, req) { 9 | 10 | // input and holder .field 11 | this.$this = $this; 12 | this.$field = this.$this.parents('.field'); 13 | 14 | // supplied validation function with default length check 15 | this.req = req || function() { 16 | return !!this.$this.val().length; 17 | }; 18 | 19 | // reference to this class 20 | var scope = this; 21 | 22 | // checkboxes and radio buttons use gumby.onChange event to validate 23 | if(this.$this.is('[type=checkbox], [type=radio]')) { 24 | this.$field = this.$this.parent('label'); 25 | this.$field.on('gumby.onChange', function() { 26 | scope.validate(); 27 | }); 28 | 29 | // selects validate on change 30 | } else if(this.$this.is('select')) { 31 | this.$field = this.$this.parents('.picker'); 32 | this.$field.on('change', function() { 33 | scope.validate(); 34 | }); 35 | 36 | // others (text input, textarea) use blur 37 | } else { 38 | this.$this.on('blur', function(e) { 39 | // ignore tab 40 | if(e.which !== 9) { 41 | scope.validate(); 42 | } 43 | }); 44 | } 45 | } 46 | 47 | // validate field 48 | Validation.prototype.validate = function() { 49 | 50 | var result = this.req(this.$this); 51 | 52 | // failed 53 | if(!result) { 54 | this.$field.removeClass('success').addClass('danger'); 55 | 56 | // passed 57 | } else { 58 | //} else if(this.$field.hasClass('danger')) { 59 | this.$field.removeClass('danger').addClass('success'); 60 | } 61 | 62 | return result; 63 | }; 64 | 65 | // jQuery plugin definition 66 | $.fn.validation = function(options) { 67 | 68 | var // extend params with defaults 69 | settings = $.extend({ 70 | submit : false, 71 | fail: false, 72 | required : [] 73 | }, options), 74 | // store validation objects 75 | validations = []; 76 | 77 | // init each form plugin is called on 78 | return this.each(function() { 79 | 80 | // no required fields so plugin is pointless 81 | if(!settings.required.length) { 82 | return false; 83 | } 84 | 85 | var $this = $(this), 86 | reqLength = settings.required.length, 87 | i; 88 | 89 | // loop round each required field and instantiate new validation object 90 | for(i = 0; i < reqLength; i++) { 91 | validations.push(new Validation( 92 | $this.find('[name="'+settings.required[i].name+'"]'), 93 | settings.required[i].validate || false 94 | )); 95 | } 96 | 97 | // hijack submit event 98 | $this.on('submit', function(e) { 99 | 100 | // reference to whole form pass/fail 101 | var failed = false; 102 | 103 | // if no passed attribute found we should halt form submit 104 | if(!$this.data('passed')) { 105 | e.preventDefault(); 106 | 107 | // loop round validation objects and validate each 108 | var reqLength = validations.length, i; 109 | for(i = 0; i < reqLength; i++) { 110 | if(!validations[i].validate()) { 111 | failed = true; 112 | } 113 | } 114 | 115 | // passed 116 | if(!failed) { 117 | // if submit method present call that otherwise submit form 118 | if(settings.submit && typeof settings.submit === 'function') { 119 | settings.submit($this.serializeArray()); 120 | return; 121 | } 122 | 123 | // store passed bool and re-submit 124 | $this.data('passed', true).submit(); 125 | 126 | // failed 127 | } else { 128 | // call fail method if present 129 | if(settings.fail && typeof settings.fail === 'function') { 130 | settings.fail(); 131 | return; 132 | } 133 | } 134 | } 135 | }); 136 | }); 137 | }; 138 | }(jQuery); 139 | -------------------------------------------------------------------------------- /public/js/main.js: -------------------------------------------------------------------------------- 1 | // Gumby is ready to go 2 | Gumby.ready(function() { 3 | console.log('Gumby is ready to go...', Gumby.debug()); 4 | 5 | // placeholder polyfil 6 | if(Gumby.isOldie || Gumby.$dom.find('html').hasClass('ie9')) { 7 | $('input, textarea').placeholder(); 8 | } 9 | }); 10 | 11 | // Oldie document loaded 12 | Gumby.oldie(function() { 13 | console.log("This is an oldie browser..."); 14 | }); 15 | 16 | // Touch devices loaded 17 | Gumby.touch(function() { 18 | console.log("This is a touch enabled device..."); 19 | }); 20 | 21 | // Document ready 22 | $(function() { 23 | $('#skip-switch').on('gumby.onComplete', function() { 24 | $(this).trigger('gumby.trigger'); 25 | }); 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /public/js/plugins.js: -------------------------------------------------------------------------------- 1 | window.log=function(){log.history=log.history||[];log.history.push(arguments);if(this.console){arguments.callee=arguments.callee.caller;var a=[].slice.call(arguments);(typeof console.log==="object"?log.apply.call(console.log,console,a):console.log.apply(console,a))}}; 2 | (function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try 3 | {console.log();return window.console;}catch(err){return window.console={};}})()); 4 | 5 | /*! http://mths.be/placeholder v2.0.7 by @mathias */ 6 | ;(function(f,h,$){var a='placeholder' in h.createElement('input'),d='placeholder' in h.createElement('textarea'),i=$.fn,c=$.valHooks,k,j;if(a&&d){j=i.placeholder=function(){return this};j.input=j.textarea=true}else{j=i.placeholder=function(){var l=this;l.filter((a?'textarea':':input')+'[placeholder]').not('.placeholder').bind({'focus.placeholder':b,'blur.placeholder':e}).data('placeholder-enabled',true).trigger('blur.placeholder');return l};j.input=a;j.textarea=d;k={get:function(m){var l=$(m);return l.data('placeholder-enabled')&&l.hasClass('placeholder')?'':m.value},set:function(m,n){var l=$(m);if(!l.data('placeholder-enabled')){return m.value=n}if(n==''){m.value=n;if(m!=h.activeElement){e.call(m)}}else{if(l.hasClass('placeholder')){b.call(m,true,n)||(m.value=n)}else{m.value=n}}return l}};a||(c.input=k);d||(c.textarea=k);$(function(){$(h).delegate('form','submit.placeholder',function(){var l=$('.placeholder',this).each(b);setTimeout(function(){l.each(e)},10)})});$(f).bind('beforeunload.placeholder',function(){$('.placeholder').each(function(){this.value=''})})}function g(m){var l={},n=/^jQuery\d+$/;$.each(m.attributes,function(p,o){if(o.specified&&!n.test(o.name)){l[o.name]=o.value}});return l}function b(m,n){var l=this,o=$(l);if(l.value==o.attr('placeholder')&&o.hasClass('placeholder')){if(o.data('placeholder-password')){o=o.hide().next().show().attr('id',o.removeAttr('id').data('placeholder-id'));if(m===true){return o[0].value=n}o.focus()}else{l.value='';o.removeClass('placeholder');l==h.activeElement&&l.select()}}}function e(){var q,l=this,p=$(l),m=p,o=this.id;if(l.value==''){if(l.type=='password'){if(!p.data('placeholder-textinput')){try{q=p.clone().attr({type:'text'})}catch(n){q=$('').attr($.extend(g(this),{type:'text'}))}q.removeAttr('name').data({'placeholder-password':true,'placeholder-id':o}).bind('focus.placeholder',b);p.data({'placeholder-textinput':q,'placeholder-id':o}).before(q)}p=p.removeAttr('id').hide().prev().attr('id',o).show()}p.addClass('placeholder');p[0].value=p.attr('placeholder')}else{p.removeClass('placeholder')}}}(this,document,jQuery)); 7 | 8 | // place any jQuery/helper plugins in here, instead of separate, slower script files. 9 | -------------------------------------------------------------------------------- /public/packages/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fideloper/Implementing-Laravel/c033c07e097325554b9c220db1547ab20ec2f93d/public/packages/.gitkeep -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/sass/_base.scss: -------------------------------------------------------------------------------- 1 | @import "compass/typography/vertical_rhythm"; 2 | 3 | @include establish-baseline; 4 | 5 | html * { 6 | @include box-sizing(border-box); 7 | } 8 | 9 | body { 10 | background: $global-bg-color; 11 | font-family: $font-family; 12 | font-weight: $body-font-weight; 13 | color: $body-font-color; 14 | position: relative; 15 | -webkit-font-smoothing: $font-smoothing; 16 | @include respond(all-phones) { 17 | -webkit-text-size-adjust: none; 18 | -ms-text-size-adjust: none; 19 | width: 100%; 20 | min-width: 0; 21 | } 22 | } 23 | 24 | html, body { 25 | height: 100%; 26 | } 27 | 28 | .ie9 { 29 | font-family: $font-family; 30 | * { 31 | font-family: $font-family; 32 | } 33 | } 34 | 35 | .hide { 36 | display: none; 37 | } 38 | 39 | .hide.active, .show { 40 | display: block; 41 | } 42 | 43 | .fixed { 44 | position: fixed; 45 | &.pinned { 46 | position: absolute; 47 | } 48 | @include respond(portrait-tablets) { 49 | position: relative !important; 50 | top: auto !important; 51 | left: auto !important; 52 | } 53 | } 54 | 55 | .unfixed { 56 | position: relative !important; 57 | top: auto !important; 58 | left: auto !important; 59 | } 60 | 61 | .text-center { 62 | text-align: center; 63 | } 64 | 65 | .text-left { 66 | text-align: left; 67 | } 68 | 69 | .text-right { 70 | text-align: right; 71 | } 72 | -------------------------------------------------------------------------------- /public/sass/_custom.scss: -------------------------------------------------------------------------------- 1 | // Your custom SCSS should be written here... 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/sass/_fonts.scss: -------------------------------------------------------------------------------- 1 | /* Fonts */ 2 | 3 | // Import Google Web Fonts 4 | @import url(//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700); 5 | 6 | 7 | // Set local icon font 8 | @font-face { 9 | font-family: '#{$icons}'; 10 | font-style: normal; 11 | font-weight: 400; 12 | src: url(../fonts/icons/#{$icons}.eot); 13 | src: url('../fonts/icons/#{$icons}.eot?#iefix') format('ie9-skip-eot'), 14 | url('../fonts/icons/#{$icons}.woff') format('woff'), 15 | url('../fonts/icons/#{$icons}.ttf') format('truetype'); 16 | } 17 | 18 | // To include your own, local copies of fonts, use the following template 19 | // 20 | //@font-face { 21 | // font-family: '#{$some-font-variable}'; 22 | // font-style: normal; 23 | // font-weight: 400; 24 | // src: url(../fonts/icons/#{$some-font-variable}.eot); 25 | // src: url('../fonts/icons/#{$some-font-variable}.eot?#iefix') format('ie9-skip-eot'), 26 | // url('../fonts/icons/#{$some-font-variable}.woff') format('woff'), 27 | // url('../fonts/icons/#{$some-font-variable}.ttf') format('truetype'); 28 | //} 29 | -------------------------------------------------------------------------------- /public/sass/extensions/modular-scale/lib/modular-scale.rb: -------------------------------------------------------------------------------- 1 | require 'compass' 2 | Compass::Frameworks.register('sassy-math', :path => File.expand_path("../..")) 3 | 4 | 5 | # This tells Compass what your Compass extension is called, and where to find 6 | # its files 7 | # Replace 'extension' with the name of your extension. Spaces allowed. 8 | extension_path = File.expand_path(File.join(File.dirname(__FILE__), "..")) 9 | Compass::Frameworks.register('modular-scale', :path => extension_path) 10 | 11 | # Version and date of version for your Compass extension. 12 | # Replace Extension with the name of your extension 13 | # Letters, numbers, and underscores only 14 | # Version is a number. If a version contains alphas, it will be created as 15 | # a prerelease version 16 | # Date is in the form of YYYY-MM-DD 17 | module ModularScale 18 | VERSION = "1.0.6" 19 | DATE = "2012-08-13" 20 | end 21 | 22 | # This is where any custom SassScript should be placed. The functions will be 23 | # available on require of your extension without the need for users to import 24 | # any partials. Uncomment below. 25 | 26 | # Modular Scale Sass Script 27 | module Sass::Script 28 | class Number < Literal 29 | # Comparison 30 | def <=>(other) 31 | value <=> other.value 32 | end 33 | end 34 | end 35 | 36 | module Sass::Script::Functions 37 | # Modular Scale 38 | def double_octave 39 | value = 4 / 1.0 40 | Sass::Script::Number.new(value) 41 | end 42 | def major_twelfth 43 | value = 3 / 1.0 44 | Sass::Script::Number.new(value) 45 | end 46 | def major_eleventh 47 | value = 8 / 3.0 48 | Sass::Script::Number.new(value) 49 | end 50 | def major_tenth 51 | value = 5 / 2.0 52 | Sass::Script::Number.new(value) 53 | end 54 | def octave 55 | value = 2 / 1.0 56 | Sass::Script::Number.new(value) 57 | end 58 | def major_seventh 59 | value = 15 / 8.0 60 | Sass::Script::Number.new(value) 61 | end 62 | def minor_seventh 63 | value = 16 /9.0 64 | Sass::Script::Number.new(value) 65 | end 66 | def major_sixth 67 | value = 5 / 3.0 68 | Sass::Script::Number.new(value) 69 | end 70 | def minor_sixth 71 | value = 8 / 5.0 72 | Sass::Script::Number.new(value) 73 | end 74 | def fifth 75 | value = 3 / 2.0 76 | Sass::Script::Number.new(value) 77 | end 78 | def augmented_fourth 79 | value = Math.sqrt(2) / 1.0 80 | Sass::Script::Number.new(value) 81 | end 82 | def fourth 83 | value = 4 / 3.0 84 | Sass::Script::Number.new(value) 85 | end 86 | def major_third 87 | value = 5 / 4.0 88 | Sass::Script::Number.new(value) 89 | end 90 | def minor_third 91 | value = 6 / 5.0 92 | Sass::Script::Number.new(value) 93 | end 94 | def major_second 95 | value = 9 / 8.0 96 | Sass::Script::Number.new(value) 97 | end 98 | def minor_second 99 | value = 16 / 15.0 100 | Sass::Script::Number.new(value) 101 | end 102 | 103 | # Lists 104 | def sort_list(list) 105 | sep = list.separator if list.is_a?(Sass::Script::List) 106 | list = list.to_a.sort 107 | Sass::Script::List.new(list, sep) 108 | end 109 | def reverse_list(list) 110 | sep = list.separator if list.is_a?(Sass::Script::List) 111 | list = list.to_a.reverse 112 | Sass::Script::List.new(list, sep) 113 | end 114 | def trim_list(list, threshold, ascending) 115 | # remove list items above or below a threshold 116 | sep = list.separator if list.is_a?(Sass::Script::List) 117 | list = list.to_a 118 | if ascending.value 119 | list = list.delete_if { 120 | |x| x.value <= threshold.value 121 | } 122 | else 123 | list = list.delete_if { 124 | |x| x.value >= threshold.value 125 | } 126 | end 127 | Sass::Script::List.new(list, sep) 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /public/sass/extensions/sassy-math/lib/sassy-math.rb: -------------------------------------------------------------------------------- 1 | require 'compass' 2 | Compass::Frameworks.register("sassy-math", :path => "#{File.dirname(__FILE__)}/..") 3 | 4 | # Sassy math Functions 5 | module Sass::Script::Functions 6 | # Exponents 7 | def exponent(base, powerNum, powerDen) 8 | base = base.value.to_f 9 | powerNum = powerNum.value.to_f 10 | powerDen = powerDen.value.to_f 11 | result = base ** (powerNum / powerDen) 12 | Sass::Script::Number.new(result) 13 | end 14 | def power(base, exponent) 15 | base = base.value.to_f 16 | exponent = exponent.value.to_f 17 | result = base ** exponent 18 | Sass::Script::Number.new(result) 19 | end 20 | def sqrt(number) 21 | number = number.value.to_f 22 | result = Math.sqrt(number) 23 | Sass::Script::Number.new(result) 24 | end 25 | def nth_root(number, root) 26 | number = number.value.to_f 27 | root = root.value.to_f 28 | result = number ** (1.0 / root) 29 | Sass::Script::Number.new(result) 30 | end 31 | # Logarithms 32 | def ln(num) 33 | result = Math.log(num.value) 34 | Sass::Script::Number.new(result) 35 | end 36 | def log10(num) 37 | result = Math.log10(num.value) 38 | Sass::Script::Number.new(result) 39 | end 40 | # Miscellaneous 41 | def factorial(number) 42 | result = 1 43 | number = number.value 44 | if number > 0 45 | (1..number).each do |i| 46 | result = result * i 47 | end 48 | end 49 | Sass::Script::Number.new(result) 50 | end 51 | def random(max = Sass::Script::Number.new(100)) ## shamelessly taken from here: https://gist.github.com/1561650 52 | Sass::Script::Number.new(rand(max.value), max.numerator_units, max.denominator_units) 53 | end 54 | def hypot(a, b) 55 | a = a.value.to_f 56 | b = b.value.to_f 57 | result = Math.hypot(a, b) 58 | Sass::Script::Number.new(result) 59 | end 60 | # Constants 61 | def pi 62 | pi = Math::PI 63 | Sass::Script::Number.new(pi) 64 | end 65 | def e 66 | e = Math::E 67 | Sass::Script::Number.new(e) 68 | end 69 | def golden_ratio() 70 | result = (1.0 / 2.0) + (Math.sqrt(5) / 2.0) 71 | Sass::Script::Number.new(result) 72 | end 73 | # Comparative Functions 74 | def is_int(number) 75 | number = number.value.to_f 76 | if number - number.floor != 0 77 | result = false 78 | else 79 | result = true 80 | end 81 | Sass::Script::Bool.new(result) 82 | end 83 | def is_float(number) 84 | number = number.value 85 | if number - number.floor != 0 86 | result = true 87 | else 88 | result = false 89 | end 90 | Sass::Script::Bool.new(result) 91 | end 92 | # Trigonometric Functions 93 | def deg_to_rad(degree) 94 | result = degree.value.to_f * Math::PI / 180 95 | Sass::Script::Number.new(result) 96 | end 97 | def rad_to_deg(rad) 98 | result = rad.value.to_f * 180 / Math::PI 99 | Sass::Script::Number.new(result) 100 | end 101 | def cosh(rad) 102 | rad = rad.value.to_f 103 | result = Math.cosh(rad) 104 | Sass::Script::Number.new(result) 105 | end 106 | def acos(rad) 107 | rad = rad.value.to_f 108 | result = Math.acos(rad) 109 | Sass::Script::Number.new(result) 110 | end 111 | def acosh(rad) 112 | rad = rad.value.to_f 113 | result = Math.acosh(rad) 114 | Sass::Script::Number.new(result) 115 | end 116 | def sinh(rad) 117 | rad = rad.value.to_f 118 | result = Math.sinh(rad) 119 | Sass::Script::Number.new(result) 120 | end 121 | def asin(rad) 122 | rad = rad.value.to_f 123 | result = Math.asin(rad) 124 | Sass::Script::Number.new(result) 125 | end 126 | def asinh(rad) 127 | rad = rad.value.to_f 128 | result = Math.asinh(rad) 129 | Sass::Script::Number.new(result) 130 | end 131 | def tanh(rad) 132 | rad = rad.value.to_f 133 | result = Math.tanh(rad) 134 | Sass::Script::Number.new(result) 135 | end 136 | def atan(rad) 137 | rad = rad.value.to_f 138 | result = Math.atan(rad) 139 | Sass::Script::Number.new(result) 140 | end 141 | def atan2(y, x) 142 | y = y.value.to_f 143 | x = x.value.to_f 144 | result = Math.atan2(y, x) 145 | Sass::Script::Number.new(result) 146 | end 147 | def atanh(rad) 148 | rad = rad.value.to_f 149 | result = Math.atanh(rad) 150 | Sass::Script::Number.new(result) 151 | end 152 | end 153 | 154 | module SassyMath 155 | 156 | VERSION = "1.5" 157 | DATE = "2012-07-29" 158 | 159 | end -------------------------------------------------------------------------------- /public/sass/functions/_all.scss: -------------------------------------------------------------------------------- 1 | // Global Gumby Functions 2 | 3 | @import "breakpoints"; 4 | @import "strip-units"; 5 | @import "grid-calc"; 6 | 7 | // Global Gumby Mixins 8 | 9 | @import "clearfix"; 10 | @import "typography"; 11 | @import "responsivity"; 12 | @import "line-and-height"; 13 | @import "height-calc"; 14 | @import "semantic-grid"; 15 | @import "visibility"; 16 | -------------------------------------------------------------------------------- /public/sass/functions/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | @function breakpoint($breakpoint) { 2 | @if $breakpoint == $document-width { 3 | @return $document-width - 1; 4 | } 5 | @if $breakpoint == $tablet-device-width { 6 | @return $tablet-device-width - 1; 7 | } 8 | @if $breakpoint == $min-device-width { 9 | @return $min-device-width + 1; 10 | } 11 | } -------------------------------------------------------------------------------- /public/sass/functions/_buttons.scss: -------------------------------------------------------------------------------- 1 | @mixin button-size($size) { 2 | $n: 0; 3 | @if $size == xlarge { 4 | $n: $xlarge-button-font-size; 5 | } 6 | @if $size == large { 7 | $n: $large-button-font-size; 8 | } 9 | @if $size == medium { 10 | $n: $medium-button-font-size; 11 | } 12 | @if $size == small { 13 | $n: $small-button-font-size; 14 | } 15 | $button-font-size: $n; 16 | $button-height: ms($ratio, $button-font-size) + 1; 17 | $line-height: $button-height - 2; 18 | 19 | @include font-size($button-font-size); 20 | @include line-and-height($button-height); 21 | 22 | a { 23 | position:relative; 24 | padding: 0 ms(0, $button-font-size); 25 | } 26 | 27 | &.icon-left { 28 | a { 29 | padding-left: $button-height; 30 | &:before { 31 | left: $button-font-size / 1.5; 32 | } 33 | } 34 | } 35 | 36 | &.icon-right { 37 | a { 38 | padding-right: $button-height; 39 | &:after { 40 | right: $button-font-size / 1.5; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/sass/functions/_clearfix.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix() { 2 | *zoom:1; 3 | &:before, &:after { 4 | content: ""; 5 | display: table; 6 | } 7 | &:after { 8 | clear: both; 9 | } 10 | } 11 | 12 | @mixin mobilefix() { 13 | @include respond(all-phones) { 14 | &:before, &:after { 15 | content: " "; 16 | display: table; 17 | } 18 | &:after { 19 | clear: both; 20 | } 21 | &:last-child { 22 | float: none; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /public/sass/functions/_forms.scss: -------------------------------------------------------------------------------- 1 | @mixin input-size($size) { 2 | @if $size == xxwide { $size: 100%; } 3 | @if $size == xwide { $size: 82.6666666667%; } 4 | @if $size == wide { $size: 65.3333333333%; } 5 | @if $size == normal { $size: 48%; } 6 | @if $size == narrow { $size: 30.6666666667%; } 7 | @if $size == xnarrow { $size: 13.3333333333%; } 8 | 9 | width: $size; 10 | } 11 | 12 | @mixin input-sizes-list() { 13 | $sizes: (); 14 | @each $item in $field-sizes { 15 | $sizes: join($sizes, unquote(".#{$item} "), comma); 16 | } 17 | #{$sizes} { @content } 18 | } 19 | -------------------------------------------------------------------------------- /public/sass/functions/_grid-calc.scss: -------------------------------------------------------------------------------- 1 | // Calculate grid values 2 | $gutter: percentage($gutter-in-px / $row-max-width); // 2.1276596 3 | 4 | // Return single column width 5 | @function oneCol($hybrid-grid: false) { 6 | @if ($hybrid-grid == true){ 7 | @return (100% - ($gutter * ($hybrid - 1))) / $hybrid; 8 | } 9 | @else{ 10 | @return (100% - ($gutter * ($cols - 1))) / $cols; 11 | } 12 | } 13 | 14 | // Calculate Grid Column Widths 15 | @function columns($num, $hybrid-grid: false){ 16 | @if ($hybrid-grid == true) { 17 | @return (oneCol(true) * $num) + ($gutter * ($num - 1)); 18 | } 19 | @else { 20 | @return (oneCol() * $num) + ($gutter * ($num - 1)); // (One column * 'x') + (gutter * ('x' - 1)) = Column Width 21 | } 22 | } 23 | 24 | 25 | // Calculate Push Class Margins 26 | @function push_x($num, $first-child: false, $is-hybrid: false) { 27 | @if $first-child and $is-hybrid { 28 | @return (oneCol(true) * $num) + ($gutter * ($num - 1)) + $gutter; // Column width + gutter 29 | } 30 | @else if $first-child != true and $is_hybrid{ 31 | @return (oneCol(true) * $num) + ($gutter * ($num - 1)) + ($gutter * 2); // Column width + (gutter * 2) 32 | } 33 | @else if $first-child and $is_hybrid != true{ 34 | @return (oneCol() * $num) + ($gutter * ($num - 1)) + $gutter; 35 | } 36 | @else { 37 | @return (oneCol() * $num) + ($gutter * ($num - 1)) + ($gutter * 2); // Column width + (gutter * 2) 38 | } 39 | } 40 | 41 | // Calculate Centered Class Margins 42 | @function centered($num, $hybrid-grid: false) { 43 | @if $hybrid-grid{ 44 | @return 50% - ((($num * (oneCol(true))) + (($num - 1) * $gutter)) / 2); 45 | } 46 | @else{ 47 | @return 50% - ((($num * (oneCol())) + (($num - 1) * $gutter)) / 2); 48 | } 49 | } 50 | 51 | // Create class names from column count integers 52 | @function number-as-word($number){ 53 | $w: "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", 54 | "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", 55 | "twenty", "twenty-one", "twenty-two", "twenty-three", "twenty-four", "twenty-five", "twenty-six", "twenty-seven", 56 | "twenty-eight", "twenty-nine", "thirty", "thirty-one", "thirty-two", "thirty-three", 57 | "thirty-four", "thirty-five", "thirty-six"; 58 | @return nth($w, $number); 59 | } 60 | -------------------------------------------------------------------------------- /public/sass/functions/_height-calc.scss: -------------------------------------------------------------------------------- 1 | 2 | // Calculate the height of an object based on its scale 3 | 4 | @function height-calc($size) { 5 | @return ms($ratio, $size) + 1; 6 | } 7 | -------------------------------------------------------------------------------- /public/sass/functions/_line-and-height.scss: -------------------------------------------------------------------------------- 1 | 2 | // Make line-height equal to an element's height 3 | 4 | @mixin line-and-height($height) { 5 | height: $height; 6 | line-height: $height - 2; 7 | } 8 | -------------------------------------------------------------------------------- /public/sass/functions/_responsivity.scss: -------------------------------------------------------------------------------- 1 | // Responsive Mixins 2 | 3 | @mixin respond($media) { 4 | @if $media == portrait-phones { 5 | @media only screen and (max-width: $min-device-width) { @content; } 6 | } 7 | @else if $media == landscape-phones { 8 | @media only screen and (min-width: breakpoint($min-device-width)) and (max-width: breakpoint($tablet-device-width)) { @content; } 9 | } 10 | @else if $media == all-phones { 11 | @media only screen and (max-width: breakpoint($tablet-device-width)) { @content; } 12 | } 13 | @else if $media == portrait-tablets { 14 | @media only screen and (max-width: $tablet-device-width) { @content; } 15 | } 16 | @else if $media == tablets { 17 | @media only screen and (min-width: $tablet-device-width) and (max-width: $document-width - 1) { @content; } 18 | } 19 | @else if $media == desktop { 20 | @media only screen and (min-width: $tablet-device-width) { @content; } 21 | } 22 | @else if $media == document-width { 23 | @media only screen and (max-width: $document-width + 20) { @content; } 24 | } 25 | @else if $media == large-screens { 26 | @media only screen and (min-width: $max-device-width) { @content; } 27 | } 28 | @else if $media == print { 29 | @media print { @content; } 30 | } 31 | @else { 32 | @media only screen and ($media) { @content; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/sass/functions/_semantic-grid.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Gumby Semantic Grid Mixin // 4 | 5 | 6 | // Mixin for rows 7 | 8 | @mixin row($nested: false) { 9 | @if $nested == nested { 10 | width: auto; 11 | min-width: 0px; 12 | max-width: none; 13 | @extend %clearfix; 14 | } 15 | @else { 16 | width: 100%; 17 | max-width: $row-max-width; 18 | min-width: $min-device-width; 19 | margin: 0 auto; 20 | @extend %clearfix; 21 | } 22 | > *:first-child { 23 | margin-left: 0px; 24 | } 25 | @include respond(document-width) { 26 | padding: 0 20px; 27 | } 28 | @include respond(all-phones) { 29 | width: auto; 30 | min-width: 0; 31 | margin-left: 0; 32 | margin-right: 0; 33 | } 34 | } 35 | 36 | // Mixin for rows that are nested within columns 37 | 38 | @mixin nestedRow() { 39 | width: auto; 40 | min-width: 0px; 41 | max-width: none; 42 | @extend %clearfix; 43 | } 44 | 45 | 46 | @mixin column($columns:$columns, $alignment: false, $behavior: false) { 47 | @if $alignment == center { 48 | @extend %columnconfig; 49 | float: none; 50 | margin-left: auto !important; 51 | margin-right: auto !important; 52 | width: columns($columns); 53 | @include respond(all-phones) { 54 | float: left; 55 | margin-left: 0; 56 | width: 100%; 57 | } 58 | } 59 | @else if $behavior == collapse { 60 | @extend %columnconfig; 61 | @extend %collapse; 62 | width: columns($columns); 63 | @include respond(all-phones) { 64 | float: left; 65 | width: 100%; 66 | } 67 | } 68 | @else { 69 | @extend %columnconfig; 70 | width: columns($columns); 71 | @include respond(all-phones) { 72 | float: left; 73 | margin-left: 0; 74 | width: 100%; 75 | } 76 | } 77 | } 78 | 79 | @mixin hybrid($columns:$columns, $alignment: false, $behavior: false) { 80 | @if $alignment == center { 81 | @extend %columnconfig; 82 | float: none; 83 | margin-left: auto !important; 84 | margin-right: auto !important; 85 | width: columns($columns, true); 86 | @include respond(all-phones) { 87 | float: left; 88 | margin-left: 0; 89 | width: 100%; 90 | } 91 | } 92 | @else if $behavior == collapse { 93 | @extend %columnconfig; 94 | @extend %collapse; 95 | width: columns($columns, true); 96 | @include respond(all-phones) { 97 | float: left; 98 | width: 100%; 99 | } 100 | } 101 | @else { 102 | @extend %columnconfig; 103 | width: columns($columns, true); 104 | @include respond(all-phones) { 105 | float: left; 106 | margin-left: 0; 107 | width: 100%; 108 | } 109 | } 110 | } 111 | 112 | @mixin push($columns, $hybrid-grid: false) { 113 | @if $hybrid-grid == hybrid { 114 | margin-left: push_x($columns, false, true); 115 | &:first-child { 116 | margin-left: push_x($columns, true, true); 117 | } 118 | @include respond(all-phones) { 119 | margin-left: 0; 120 | &:first-child { 121 | margin-left: 0; 122 | } 123 | } 124 | } 125 | @else { 126 | margin-left: push_x($columns); 127 | &:first-child { 128 | margin-left: push_x($columns, true); 129 | } 130 | @include respond(all-phones) { 131 | margin-left: 0; 132 | &:first-child { 133 | margin-left: 0; 134 | } 135 | } 136 | } 137 | } 138 | 139 | @mixin pull($direction:false) { 140 | @if $direction == left { 141 | @extend %pull-left; 142 | } 143 | @elseif $direction == none { 144 | @extend %pull-none; 145 | } 146 | @else { 147 | @extend %pull-right; 148 | } 149 | } 150 | 151 | 152 | // Placeholders for the Semantic Grid 153 | 154 | %container { 155 | padding: 0px $gutter-in-px + px; 156 | @include respond(all-phones) { 157 | min-width: 0; 158 | margin-left: 0; 159 | margin-right: 0; 160 | } 161 | } 162 | 163 | // Clearfix placeholder 164 | %clearfix { @include clearfix(); } 165 | 166 | // Clearfix placeholder for mobile 167 | %mobilefix { @include mobilefix(); } 168 | 169 | // Row placeholders 170 | %row { @include row(); } 171 | %nestedrow { @include row(); } 172 | 173 | // Column Configuration placeholder 174 | %columnconfig { 175 | margin-left: $gutter; 176 | float: $default-float; 177 | min-height: 1px; 178 | position: relative; 179 | @include box-sizing(border-box); 180 | } 181 | 182 | %pull-right { float: right; } 183 | %pull-left { float: left; } 184 | %pull-none { float: none; } 185 | 186 | // Collapse Gutters 187 | %collapse { 188 | margin-left: 0px; 189 | } 190 | -------------------------------------------------------------------------------- /public/sass/functions/_strip-units.scss: -------------------------------------------------------------------------------- 1 | 2 | // Strip out units to do math functions. 3 | @function strip-units($number) { 4 | @return $number / ($number * 0 + 1); 5 | } 6 | -------------------------------------------------------------------------------- /public/sass/functions/_typography.scss: -------------------------------------------------------------------------------- 1 | // Typography mixins 2 | 3 | // Fonts in rems with px fallback 4 | 5 | @mixin font-size($size, $is-important: false) { 6 | $size: if(unitless($size), $size, $size / 1px); 7 | 8 | @if $is-important { 9 | font-size: $size + px !important; 10 | font-size: ($size / strip-units($base-font-size)) + rem !important; 11 | } @else { 12 | font-size: $size + px; 13 | font-size: ($size / strip-units($base-font-size)) + rem; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /public/sass/functions/_visibility.scss: -------------------------------------------------------------------------------- 1 | // Visibility Mixins 2 | 3 | // Mixin for hidden 4 | 5 | @mixin hidden($media) { 6 | @include respond($media) { 7 | display: none !important; 8 | } 9 | } 10 | 11 | // Mixin for visible 12 | 13 | @mixin visible($media) { 14 | @include respond($media) { 15 | display: inherit !important; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/sass/gumby.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Gumby Framework 3 | * --------------- 4 | * 5 | * Follow @gumbycss on twitter and spread the love. 6 | * We worked super hard on making this awesome and released it to the web. 7 | * All we ask is you leave this intact. #gumbyisawesome 8 | * 9 | * Gumby Framework 10 | * http://gumbyframework.com 11 | * 12 | * Built with love by your friends @digitalsurgeons 13 | * http://www.digitalsurgeons.com 14 | * 15 | * Free to use under the MIT license. 16 | * http://www.opensource.org/licenses/mit-license.php 17 | */ 18 | 19 | @charset "UTF-8"; 20 | 21 | @import "modular-scale"; 22 | 23 | @import "var/settings"; 24 | @import "var/lists"; 25 | 26 | @import "compass"; 27 | @import "compass/reset"; 28 | 29 | @import "functions/all"; 30 | 31 | @import "base"; 32 | @import "fonts"; 33 | @import "typography"; 34 | @import "grid"; 35 | @import "ui/all"; 36 | 37 | @import "custom"; -------------------------------------------------------------------------------- /public/sass/ui/_all.scss: -------------------------------------------------------------------------------- 1 | @import "navbar"; 2 | @import "buttons"; 3 | @import "icons"; 4 | @import "forms"; 5 | @import "labels"; 6 | @import "tabs"; 7 | @import "images"; 8 | @import "video"; 9 | @import "toggles"; 10 | @import "tables"; 11 | -------------------------------------------------------------------------------- /public/sass/ui/_buttons.scss: -------------------------------------------------------------------------------- 1 | /* Buttons */ 2 | 3 | // Buttons 4 | @import "../functions/buttons"; 5 | 6 | .btn, .skiplink { 7 | display: inline-block; 8 | width: auto; 9 | background: $default-color; 10 | -webkit-appearance: none; 11 | font-family: $font-family; 12 | font-weight: $button-font-weight; 13 | padding: 0 !important; 14 | text-align: center; 15 | .pretty & { @extend .pretty.btn; } 16 | .metro & { @extend .metro.btn; } 17 | 18 | > a, input, button { 19 | display: block; 20 | padding: 0 $default-button-padding; 21 | color: $white; 22 | height: 100%; 23 | } 24 | 25 | input, button { 26 | background: none; 27 | border: none; 28 | width: 100%; 29 | font-size: 100%; 30 | cursor: pointer; 31 | font-weight: $type-font-weight; 32 | @include appearance(none); 33 | } 34 | 35 | &.xlarge { 36 | @include button-size(xlarge); 37 | } 38 | &.large { 39 | @include button-size(large); 40 | } 41 | &.medium { 42 | @include button-size(medium); 43 | a { 44 | padding: 0 ms(1); 45 | } 46 | } 47 | &.small { 48 | @include button-size(small); 49 | a { 50 | padding: 0 ms(-1); 51 | } 52 | } 53 | 54 | &.oval { 55 | @include border-radius(1000px); 56 | } 57 | 58 | &.pill-left { 59 | @include border-radius(500px 0 0 500px); 60 | } 61 | 62 | &.pill-right { 63 | @include border-radius(0 500px 500px 0); 64 | } 65 | 66 | @each $shade in $ui-coloring { 67 | &.#{nth($shade, 1)} { 68 | background: nth($shade, 2); 69 | border: 1px solid nth($shade, 2); 70 | &:hover { 71 | background: lighten(nth($shade, 2), 10%); 72 | } 73 | &:active { 74 | background: darken(nth($shade, 2), 10%); 75 | } 76 | @if nth($shade, 1) == default { 77 | color: darken(nth($shade, 2), 61.5%); 78 | border: 1px solid nth($shade, 2); 79 | &:hover { 80 | border: 1px solid darken(nth($shade, 2), 5%); 81 | } 82 | a, input, button { 83 | color: darken(nth($shade, 2), 61.5%); 84 | } 85 | } 86 | @if nth($shade, 1) == warning { 87 | color: darken(nth($shade, 2), 40%); 88 | a, input, button { 89 | color: darken(nth($shade, 2), 40%); 90 | } 91 | } 92 | } 93 | } 94 | 95 | @each $style in $styling { 96 | &.#{nth($style, 1)} { 97 | @include border-radius(nth($style, 2)); 98 | &:hover { 99 | @extend .btn.#{nth($style, 1)} 100 | } 101 | &:active { 102 | @extend .btn.#{nth($style, 1)} 103 | } 104 | @if nth($style, 1) == metro { 105 | &.rounded { 106 | @include border-radius($button-radius); 107 | } 108 | } 109 | @if nth($style, 1) == pretty { 110 | &.squared { 111 | @include border-radius($metro-radius); 112 | } 113 | } 114 | } 115 | } 116 | 117 | &.pretty { 118 | @each $grade in $ui-coloring { 119 | &.#{nth($grade, 1)} { 120 | @include background-image(linear-gradient(lighten(nth($grade, 2), 20%), saturate(nth($grade, 2), 5%))); 121 | @include box-shadow(inset 0 0 3px lighten(nth($grade, 2), 45%)); 122 | border: 1px solid darken(nth($grade, 2), 15%); 123 | &:hover { 124 | @include background-image(linear-gradient(lighten(nth($grade, 3), 15%), saturate(nth($grade, 3), 5%))); 125 | @include box-shadow(inset 0 0 3px lighten(nth($grade, 3), 40%)); 126 | border: 1px solid darken(nth($grade, 3), 15%); 127 | } 128 | &:active { 129 | @include background-image(linear-gradient(saturate(nth($grade, 2), 5%), lighten(nth($grade, 2), 20%))); 130 | @include box-shadow(inset 0 0 3px lighten(nth($grade, 2), 50%)); 131 | } 132 | @if nth($grade, 1) == default { 133 | a, input, button { 134 | text-shadow: 0 1px 1px lighten(nth($grade, 2), 20%); 135 | } 136 | } 137 | @elseif nth($grade, 1) == warning { 138 | color: darken(nth($grade, 2), 40%); 139 | a, input, button { 140 | text-shadow: 0 1px 1px lighten(nth($grade, 2), 20%); 141 | } 142 | } 143 | @else { 144 | a, input, button { 145 | text-shadow: 0 1px 1px darken(nth($grade, 2), 20%); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /public/sass/ui/_icons.scss: -------------------------------------------------------------------------------- 1 | /* Icons */ 2 | 3 | @import "../var/icons/entypo"; 4 | 5 | [class^="icon-"] a:before, 6 | [class*=" icon-"] a:before, 7 | [class^="icon-"] a:after, 8 | [class*=" icon-"] a:after, 9 | i[class^="icon-"], 10 | i[class*=" icon-"] { 11 | font-family: "#{$icons}"; 12 | position:absolute; 13 | text-decoration:none; 14 | zoom: 1; 15 | } 16 | 17 | i[class^="icon-"], 18 | i[class*=" icon-"] { 19 | display: inline-block; 20 | position: static; 21 | min-width: 20px; 22 | margin: 0 5px; 23 | text-align: center; 24 | } 25 | 26 | @each $icon in $entypo-icons { 27 | .#{nth($icon, 1)} { 28 | &.icon-left a:before, &.icon-right a:after { 29 | content: "#{nth($icon, 2)}"; 30 | height: inherit; 31 | } 32 | } 33 | i.#{nth($icon, 1)}:before { 34 | content: "#{nth($icon, 2)}"; 35 | height: inherit; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/sass/ui/_images.scss: -------------------------------------------------------------------------------- 1 | .image { 2 | line-height:0; 3 | margin-bottom: 20px; 4 | &.circle { 5 | @include border-radius(50% !important); 6 | overflow: hidden; 7 | width: auto; 8 | } 9 | &.rounded { 10 | overflow: hidden; 11 | @include border-radius($button-radius $button-radius); 12 | } 13 | &.photo { 14 | border: 5px solid #fff; 15 | @include box-shadow(0 0 1px $body-font-color); 16 | &.polaroid { 17 | padding-bottom: 50px; 18 | background: #fff; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /public/sass/ui/_labels.scss: -------------------------------------------------------------------------------- 1 | /* Labels */ 2 | 3 | .badge, .label { 4 | height: 20px; 5 | display: inline-block; 6 | font-family: Helvetica, arial, verdana, sans-serif; 7 | font-weight: bold; 8 | line-height: 20px; 9 | text-align:center; 10 | color: #fff; 11 | a { 12 | color: #fff; 13 | } 14 | @each $shade in $ui-coloring { 15 | &.#{nth($shade, 1)} { 16 | background: nth($shade, 2); 17 | @if nth($shade, 1) == default { 18 | color: darken(nth($shade, 2), 61.5%); 19 | border: 1px solid nth($shade, 2); 20 | &:hover { 21 | border: 1px solid darken(nth($shade, 2), 5%); 22 | } 23 | a { 24 | color: darken(nth($shade, 2), 61.5%); 25 | } 26 | } 27 | @if nth($shade, 1) == warning { 28 | color: darken(nth($shade, 2), 40%); 29 | a { 30 | color: darken(nth($shade, 2), 40%); 31 | } 32 | } 33 | } 34 | &.light { 35 | background: #fff; 36 | color: $body-font-color; 37 | border: 1px solid $default-color; 38 | a { 39 | color: $body-link-color; 40 | } 41 | } 42 | &.dark { 43 | background: #212121; 44 | } 45 | } 46 | } 47 | 48 | .badge { 49 | padding: 0 10px; 50 | @include font-size(ms(0, 14px)); 51 | @include border-radius(10px); 52 | } 53 | 54 | .label { 55 | padding: 0 10px; 56 | @include font-size(ms(0, 12px)); 57 | @include border-radius(2px); 58 | } 59 | 60 | .alert { 61 | padding: 0 10px; 62 | font-family: $font-family; 63 | font-weight: $font-weight-semibold; 64 | list-style-type: none; 65 | word-wrap: break-word; 66 | margin-bottom: $norm / 2; 67 | @include font-size(ms(0, 14px)); 68 | @include border-radius($button-radius); 69 | @each $shade in $ui-coloring { 70 | &.#{nth($shade, 1)} { 71 | background: lighten(nth($shade, 2), 20%); 72 | border: 1px solid nth($shade, 2); 73 | color: darken(nth($shade, 2), 20%); 74 | @if nth($shade, 1) == info { 75 | color: $default-color; 76 | } 77 | @if nth($shade, 1) == default { 78 | color: darken(nth($shade, 2), 61.5%); 79 | border: 1px solid nth($shade, 2); 80 | } 81 | @if nth($shade, 1) == warning { 82 | color: darken(nth($shade, 2), 40%); 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /public/sass/ui/_tables.scss: -------------------------------------------------------------------------------- 1 | table { 2 | display: table; 3 | background-color: $table-bgcolor; 4 | border-collapse: collapse; 5 | border-spacing: 0; 6 | margin-bottom: 20px; 7 | width: 100%; 8 | border: $table-border-size $table-border-style $table-border-color; 9 | 10 | caption { 11 | text-align: center; 12 | font-size: $larger; 13 | padding: .75em; 14 | } 15 | 16 | thead th, 17 | tbody td, 18 | tr td { 19 | display: table-cell; 20 | padding: 10px; 21 | vertical-align: top; 22 | text-align: left; 23 | border-top: $table-cell-border-size $table-cell-border-style $table-cell-border-color; 24 | } 25 | 26 | tr td, tbody tr td { 27 | font-size: $norm; 28 | } 29 | 30 | tr td:first-child { 31 | font-weight: $table-row-first-cell-font-weight; 32 | } 33 | 34 | thead { 35 | background-color: $table-thead-bgcolor; 36 | color: #fff; 37 | 38 | tr th { 39 | font-size: $norm; 40 | font-weight: bold; 41 | vertical-align: bottom; 42 | } 43 | } 44 | 45 | &.striped tr:nth-of-type(even), 46 | table tr.stripe, 47 | table tr.striped { 48 | background-color: $table-stripe-bgcolor; 49 | } 50 | 51 | &.rounded { 52 | border-radius: $table-border-radius; 53 | border-collapse: separate; 54 | 55 | caption + thead tr:first-child th:first-child, 56 | caption + tr td:first-child, 57 | > thead tr:first-child th:first-child, 58 | > thead tr:first-child td:first-child, 59 | > tr:first-child td:first-child { 60 | border-top-left-radius: $table-border-radius; 61 | } 62 | 63 | caption + thead tr:first-child th:last-child, 64 | caption + tr td:last-child, 65 | > thead tr:first-child th:last-child, 66 | > thead tr:first-child td:last-child, 67 | > tr:first-child td:last-child { 68 | border-top-right-radius: $table-border-radius; 69 | } 70 | 71 | thead ~ tr:last-child td:last-child, 72 | tbody tr:last-child td:last-child { 73 | border-bottom-right-radius: $table-border-radius; 74 | } 75 | 76 | thead ~ tr:last-child td:first-child, 77 | tbody tr:last-child td:first-child { 78 | border-bottom-left-radius: $table-border-radius; 79 | } 80 | 81 | thead th, thead td, 82 | caption + tbody tr:first-child td, 83 | > tbody:first-child tr:first-child td { 84 | border-top: 0; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /public/sass/ui/_tabs.scss: -------------------------------------------------------------------------------- 1 | /* Tabs */ 2 | 3 | .tabs { 4 | display: block; 5 | .tab-nav { 6 | margin: 0; 7 | padding: 0; 8 | border-bottom: 1px solid darken($default-color, 5%); 9 | > li { 10 | display: inline-block; 11 | width: auto; 12 | padding: 0; 13 | margin: 0 $gutter 0 0; 14 | cursor: default; 15 | top: 1px; 16 | @include box-shadow(0 1px 0 $white); 17 | > a { 18 | display: block; 19 | width: auto; 20 | padding: 0 $norm; 21 | margin: 0; 22 | color: $body-font-color; 23 | font-family: $font-family; 24 | font-weight: $tabs-font-weight; 25 | border: 1px solid darken($default-color, 5%); 26 | border-width: 1px 1px 0 1px; 27 | text-shadow: 0 1px 1px lighten($default-color, 5%); 28 | background: $default-color; 29 | cursor: pointer; 30 | @include border-radius($button-radius $button-radius 0 0); 31 | @include line-and-height($tab-height); 32 | &:hover { 33 | text-decoration: none; 34 | background: lighten($default-color, 1%); 35 | } 36 | &:active { 37 | background: darken($default-color, 2%); 38 | } 39 | } 40 | &.active > a { 41 | @include line-and-height($tab-height + 1); 42 | background: $white; 43 | cursor: default; 44 | } 45 | &:last-child { 46 | margin-right: 0; 47 | } 48 | } 49 | } 50 | .tab-content { 51 | display: none; 52 | padding: 20px 10px; 53 | &.active { 54 | display: block; 55 | } 56 | } 57 | &.pill .tab-nav { 58 | width: 100%; /* remove if you dont want the tabs to span the full container width */ 59 | display: table; 60 | overflow: hidden; 61 | border: 1px solid darken($default-color, 5%); 62 | @include border-radius($button-radius); 63 | > li { 64 | display: table-cell; 65 | margin: 0; 66 | margin-left: -4px; 67 | text-align: center; 68 | top: 0; 69 | &:first-child { 70 | margin-left: 0; 71 | } 72 | > a { 73 | border: none; 74 | border-right: 1px solid darken($default-color, 5%); 75 | @include border-radius(0); 76 | @include line-and-height($tab-height); 77 | } 78 | &:last-child > a { 79 | border-right:none; 80 | } 81 | } 82 | } 83 | &.vertical { 84 | .tab-nav { 85 | border: none; 86 | > li { 87 | display: block; 88 | margin: 0; 89 | margin-bottom: 5px; 90 | &.active { 91 | position: relative; 92 | z-index: 99; 93 | > a { 94 | border-right: 1px solid $global-bg-color; 95 | } 96 | } 97 | > a { 98 | border: 1px solid darken($default-color, 5%); 99 | @include border-radius($button-radius 0 0 $button-radius); 100 | } 101 | } 102 | } 103 | .tab-content { 104 | padding: 10px 0 30px 20px; 105 | margin-left: -1px; 106 | border-left: 1px solid darken($default-color, 5%); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /public/sass/ui/_toggles.scss: -------------------------------------------------------------------------------- 1 | 2 | .drawer { 3 | position: relative; 4 | width: 100%; 5 | max-height: 0; 6 | background: $drawer-background-color; 7 | @include box-shadow( 8 | inset $drawer-inner-shadow-x-offset #{-$drawer-inner-shadow-y-offset} $drawer-inner-shadow-blur $drawer-inner-shadow-color, 9 | inset $drawer-inner-shadow-x-offset $drawer-inner-shadow-y-offset $drawer-inner-shadow-blur $drawer-inner-shadow-color); 10 | ; 11 | overflow: hidden; 12 | @include transition-duration(.3s); 13 | &.active { 14 | height: auto; 15 | max-height: 800px; 16 | @include transition-duration(.5s); 17 | } 18 | } 19 | 20 | .modal { 21 | width: 100%; 22 | height: 100%; 23 | position: fixed; 24 | top: 0; 25 | left: 0; 26 | z-index: -999999; 27 | background: rgb(0, 0, 0); 28 | background: $modal-overlay-color; 29 | > .content { 30 | width: 50%; 31 | min-height: 50%; 32 | max-height: 65%; 33 | position: relative; 34 | top: 25%; 35 | margin: 0 auto; 36 | padding: $gutter-in-px; 37 | background: $modal-window-color; 38 | z-index: 2; 39 | overflow: auto; 40 | @include respond(portrait-tablets) { 41 | width: 80%; 42 | min-height: 80%; 43 | max-height: 80%; 44 | top: 10%; 45 | } 46 | @include respond(all-phones) { 47 | width: 92.5%; 48 | min-height: 92.5%; 49 | max-height: 92.5%; 50 | top: 3.75%; 51 | } 52 | > .close { 53 | position: absolute; 54 | top: 10px; 55 | right: 10px; 56 | cursor: pointer; 57 | } 58 | } 59 | &, > .content { 60 | @include opacity(0); 61 | @include transition-duration(.3s); 62 | } 63 | &.active { 64 | z-index: 999999; 65 | &, > .content { 66 | @include opacity(1); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/sass/ui/_video.scss: -------------------------------------------------------------------------------- 1 | body .video { 2 | width: 100%; 3 | position: relative; 4 | height: 0; 5 | padding-bottom: 56.25%; 6 | &.twitch, &.youtube.show_controls { 7 | padding-top: 30px; 8 | // Use .show_controls f you want the play/pause controls 9 | // to show before the video plays, like on older youtube. 10 | } 11 | &.youtube, &.vimeo { 12 | // Nothing goes here anymore. Both use overlay transports. 13 | } 14 | } 15 | 16 | .video > video, .video > iframe, .video > object, .video > embed { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | width: 100%; 21 | height: 100%; 22 | } 23 | -------------------------------------------------------------------------------- /public/sass/var/_lists.scss: -------------------------------------------------------------------------------- 1 | // Lists 2 | 3 | // UI Coloring List 4 | $ui-coloring: 5 | primary $primary-color $primary-hover-color, 6 | secondary $secondary-color $secondary-hover-color, 7 | default $default-color $default-hover-color, 8 | info $info-color $info-hover-color, 9 | danger $danger-color $danger-hover-color, 10 | warning $warning-color $warning-hover-color, 11 | success $success-color $success-hover-color; 12 | 13 | // UI Styling List 14 | $styling: metro $metro-radius, pretty $button-radius; 15 | 16 | 17 | // Form field types 18 | $field-types: text, email, password, numeric, search, combined, prepend, append, double; 19 | 20 | $field-sizes: xnarrow, narrow, normal, wide, xwide, xxwide; -------------------------------------------------------------------------------- /public/sass/var/_settings.scss: -------------------------------------------------------------------------------- 1 | // Welcome to Gumby 2.0 Settings. 2 | // Happy Tinkering! 3 | 4 | 5 | // Grid Settings 6 | $row-max-width: 940px !default; // 940px 7 | $gutter-in-px: 20px !default; // 20px 8 | $cols: 12 !default; // 12 Column Default Grid 9 | $hybrid: 16 !default; // 16 Column Default Hybrid Grid 10 | 11 | // Responsiveness Settings 12 | $min-device-width: 320px; // iPhone Portrait 13 | $tablet-device-width: 768px; // iPad Portrait 14 | $document-width: $row-max-width; // Default Document 15 | $max-device-width: 2880px; // Max Document Size 16 | 17 | // Spacing 18 | $nav-distance: 0px; // Navigation distance from the top of the viewport 19 | 20 | // Typography 21 | $font-family: "Open Sans"; 22 | $font-style-italic: italic; 23 | $icons: entypo; 24 | $font-smoothing: antialiased; 25 | 26 | // Font Weights 27 | $font-weight-bold: 700; 28 | $font-weight-semibold: 600; 29 | $font-weight-medium: 400; 30 | $font-weight-regular: 400; 31 | $font-weight-light: 300; 32 | $font-weight-thin: 300; 33 | 34 | $header-font-weight: $font-weight-thin; 35 | $body-font-weight: $font-weight-regular; 36 | $type-font-weight: $font-weight-regular; 37 | $link-font-weight: $font-weight-regular; 38 | $button-font-weight: $font-weight-semibold; 39 | $tabs-font-weight: $font-weight-semibold; 40 | 41 | // Vertical Rhythm Spacing 42 | $base-line-height: ms(1) !default; 43 | $rhythm-spacing: .168; 44 | $rhythm-height: .711; 45 | 46 | // Modular Scale Settings 47 | // http://www.modularscale.com by Tim Brown 48 | // https://github.com/scottkellum/modular-scale 49 | $ratio: golden(); // Ratio for Modular Scale 50 | $base-font-size: 16px !default; 51 | $importantNum: 78px !default; 52 | $base-size: $base-font-size $importantNum; 53 | // Gumby Default Scale Values: 16, 18, 26, 30, 42, 48, 68, 78, 110, 126; 54 | 55 | // Sizing 56 | $xsmall: ms(-2); 57 | $small: ms(-1); 58 | $norm: ms(0); // $base-font-size (16px == default) 59 | $med: ms(1); 60 | $large: ms(2); 61 | $larger: ms(3); 62 | $xlarge: ms(4); 63 | $xxlarge: ms(5); 64 | $xxxlarge: ms(6); 65 | $reallybig: ms(8); 66 | $tremendous: ms(9); 67 | $absurd: ms(10); 68 | 69 | // Typography Colors 70 | $header-font-color: #444444 !default; 71 | $header-link-color: #d04526 !default; 72 | $header-link-hover-color: #c03d20 !default; 73 | $body-font-color: #555555 !default; 74 | $body-link-color: #d04526 !default; 75 | $body-link-hover-color: #c03d20 !default; 76 | 77 | // User Interface Colors 78 | $global-bg-color: #fff; 79 | $navbar-color: #4a4d50; 80 | $navbar-link-color: #fff; 81 | 82 | $primary-color: #3085d6; 83 | $secondary-color: #42a35a; 84 | $default-color: #f2f2f2; 85 | $info-color: #4a4d50; 86 | $danger-color: #ca3838; 87 | $warning-color: #f6b83f; 88 | $success-color: #58c026; 89 | 90 | $primary-hover-color: #58b2fa; 91 | $secondary-hover-color: #6dbb80; 92 | $default-hover-color: #ffffff; 93 | $info-hover-color: #868d92; 94 | $danger-hover-color: #f14f4f; 95 | $warning-hover-color: #fdd27f; 96 | $success-hover-color: #66d92f; 97 | 98 | $horizontal-rule-color: #ccc !default; 99 | 100 | $black: #000; 101 | $white: #fff; 102 | 103 | // Borders 104 | $button-radius: 4px !default; 105 | $metro-radius: 0 !default; 106 | $bigger-radius: 8px; 107 | 108 | // Buttons 109 | // Font Sizing 110 | $xlarge-button-font-size: $larger; 111 | $large-button-font-size: $large; 112 | $medium-button-font-size: $norm; 113 | $small-button-font-size: $small; 114 | // Padding 115 | $default-button-padding: $med; 116 | // Height 117 | $default-button-height: 36px; 118 | 119 | // Tabs 120 | $tab-height: 42px; 121 | 122 | // Drawers & Modals 123 | $modal-overlay-color: rgba(0, 0, 0, 0.8); 124 | $modal-window-color: $white; 125 | $drawer-background-color: #3e4144; 126 | $drawer-inner-shadow-x-offset: 0; 127 | $drawer-inner-shadow-y-offset: 2px; 128 | $drawer-inner-shadow-blur: 5px; 129 | $drawer-inner-shadow-color: #313436; 130 | 131 | // Tables 132 | $table-bgcolor: #fff; 133 | $table-thead-bgcolor: $primary-color; 134 | $table-row-first-cell-font-weight: bold; 135 | $table-border-size: 1px; 136 | $table-border-style: solid; 137 | $table-border-color: #e5e5e5; 138 | $table-cell-border-size: 1px; 139 | $table-cell-border-color: #e5e5e5; 140 | $table-cell-border-style: solid; 141 | // .rounded 142 | $table-border-radius: 4px; 143 | // .striped 144 | $table-stripe-bgcolor: #e5e5e5; 145 | 146 | // Floats 147 | $default-float: left; 148 | $switch-float: right; 149 | 150 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Implementing Laravel 2 | 3 | The companion application code to my e-book [Implementing Laravel](https://leanpub.com/implementinglaravel). 4 | 5 | ## Setting Up 6 | 7 | See files `public/.htaccess` and `bootstrap/start`. 8 | 9 | See `composer.json` for PSR-0 autoloading and `app/Impl` for application library. 10 | 11 | ## Repository Pattern 12 | 13 | See files under `app/Impl/Repo` 14 | 15 | ## Repository Pattern + Cache Layer 16 | 17 | See files under `app/Impl/Repo` and `app/Impl/Service/Cache` 18 | 19 | ## Validation as a Service 20 | 21 | See files under `app/Impl/Service/Valiation`. 22 | 23 | ## Form Processing 24 | 25 | See files under `app/Impl/Service/Form`. 26 | 27 | ## Error Handling 28 | 29 | See files under `app/Impl/Exception`. 30 | 31 | ## Using Packages 32 | 33 | ### Notification 34 | 35 | See files under `app/Impl/Service/Notification`. -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 |