├── .bowerrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── app
├── .buildignore
├── .htaccess
├── 404.html
├── api
│ ├── composer.json
│ ├── composer.lock
│ └── index.php
├── favicon.ico
├── images
│ └── yeoman.png
├── index.html
├── robots.txt
├── scripts
│ ├── app.js
│ └── controllers
│ │ ├── about.js
│ │ ├── cars.js
│ │ └── main.js
├── styles
│ └── main.css
└── views
│ ├── about.html
│ ├── cars.html
│ └── main.html
├── bower.json
├── package.json
└── test
├── .jshintrc
├── karma.conf.js
└── spec
└── controllers
├── about.js
├── cars.js
└── main.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .tmp
4 | .sass-cache
5 | bower_components
6 | composer.phar
7 | vendor/
8 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "angular": false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | before_script:
5 | - 'npm install -g bower grunt-cli'
6 | - 'bower install'
7 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Generated on 2014-10-11 using generator-angular 0.9.5
2 | 'use strict';
3 |
4 | // # Globbing
5 | // for performance reasons we're only matching one level down:
6 | // 'test/spec/{,*/}*.js'
7 | // use this if you want to recursively match all subfolders:
8 | // 'test/spec/**/*.js'
9 |
10 | module.exports = function (grunt) {
11 |
12 | // Load grunt tasks automatically
13 | require('load-grunt-tasks')(grunt);
14 |
15 | // Time how long tasks take. Can help when optimizing build times
16 | require('time-grunt')(grunt);
17 |
18 | // Configurable paths for the application
19 | var appConfig = {
20 | app: require('./bower.json').appPath || 'app',
21 | dist: 'dist'
22 | };
23 |
24 | // Define the configuration for all the tasks
25 | grunt.initConfig({
26 |
27 | // Project settings
28 | yeoman: appConfig,
29 |
30 | // Watches files for changes and runs tasks based on the changed files
31 | watch: {
32 | bower: {
33 | files: ['bower.json'],
34 | tasks: ['wiredep']
35 | },
36 | js: {
37 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
38 | tasks: ['newer:jshint:all'],
39 | options: {
40 | livereload: '<%= php.options.livereload %>'
41 | }
42 | },
43 | jsTest: {
44 | files: ['test/spec/{,*/}*.js'],
45 | tasks: ['newer:jshint:test', 'karma']
46 | },
47 | styles: {
48 | files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
49 | tasks: ['newer:copy:styles', 'autoprefixer']
50 | },
51 | gruntfile: {
52 | files: ['Gruntfile.js']
53 | },
54 | livereload: {
55 | options: {
56 | livereload: '<%= php.options.livereload %>'
57 | },
58 | files: [
59 | '<%= yeoman.app %>/{,*/}*.html',
60 | '.tmp/styles/{,*/}*.css',
61 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
62 | ]
63 | }
64 | },
65 |
66 | // The actual grunt server settings
67 | php: {
68 | options: {
69 | port: 9000,
70 | // Change this to '0.0.0.0' to access the server from outside.
71 | hostname: 'localhost',
72 | livereload: 35729,
73 | base: '<%= yeoman.app %>'
74 | },
75 | livereload: {
76 | options: {
77 | open: true,
78 | middleware: function (php) {
79 | return [
80 | php.static('.tmp'),
81 | php().use(
82 | '/bower_components',
83 | php.static('./bower_components')
84 | ),
85 | php.static(appConfig.app)
86 | ];
87 | }
88 | }
89 | },
90 | test: {
91 | options: {
92 | port: 9001,
93 | middleware: function (php) {
94 | return [
95 | php.static('.tmp'),
96 | php.static('test'),
97 | php().use(
98 | '/bower_components',
99 | php.static('./bower_components')
100 | ),
101 | php.static(appConfig.app)
102 | ];
103 | }
104 | }
105 | },
106 | dist: {
107 | options: {
108 | open: true,
109 | base: '<%= yeoman.dist %>'
110 | }
111 | }
112 | },
113 |
114 | // Make sure code styles are up to par and there are no obvious mistakes
115 | jshint: {
116 | options: {
117 | jshintrc: '.jshintrc',
118 | reporter: require('jshint-stylish')
119 | },
120 | all: {
121 | src: [
122 | 'Gruntfile.js',
123 | '<%= yeoman.app %>/scripts/{,*/}*.js'
124 | ]
125 | },
126 | test: {
127 | options: {
128 | jshintrc: 'test/.jshintrc'
129 | },
130 | src: ['test/spec/{,*/}*.js']
131 | }
132 | },
133 |
134 | // Empties folders to start fresh
135 | clean: {
136 | dist: {
137 | files: [{
138 | dot: true,
139 | src: [
140 | '.tmp',
141 | '<%= yeoman.dist %>/{,*/}*',
142 | '!<%= yeoman.dist %>/.git*'
143 | ]
144 | }]
145 | },
146 | server: '.tmp'
147 | },
148 |
149 | // Add vendor prefixed styles
150 | autoprefixer: {
151 | options: {
152 | browsers: ['last 1 version']
153 | },
154 | dist: {
155 | files: [{
156 | expand: true,
157 | cwd: '.tmp/styles/',
158 | src: '{,*/}*.css',
159 | dest: '.tmp/styles/'
160 | }]
161 | }
162 | },
163 |
164 | // Automatically inject Bower components into the app
165 | wiredep: {
166 | app: {
167 | src: ['<%= yeoman.app %>/index.html'],
168 | ignorePath: /\.\.\//
169 | }
170 | },
171 |
172 | // Renames files for browser caching purposes
173 | filerev: {
174 | dist: {
175 | src: [
176 | '<%= yeoman.dist %>/scripts/{,*/}*.js',
177 | '<%= yeoman.dist %>/styles/{,*/}*.css',
178 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
179 | '<%= yeoman.dist %>/styles/fonts/*'
180 | ]
181 | }
182 | },
183 |
184 | // Reads HTML for usemin blocks to enable smart builds that automatically
185 | // concat, minify and revision files. Creates configurations in memory so
186 | // additional tasks can operate on them
187 | useminPrepare: {
188 | html: '<%= yeoman.app %>/index.html',
189 | options: {
190 | dest: '<%= yeoman.dist %>',
191 | flow: {
192 | html: {
193 | steps: {
194 | js: ['concat', 'uglifyjs'],
195 | css: ['cssmin']
196 | },
197 | post: {}
198 | }
199 | }
200 | }
201 | },
202 |
203 | // Performs rewrites based on filerev and the useminPrepare configuration
204 | usemin: {
205 | html: ['<%= yeoman.dist %>/{,*/}*.html'],
206 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
207 | options: {
208 | assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images']
209 | }
210 | },
211 |
212 | // The following *-min tasks will produce minified files in the dist folder
213 | // By default, your `index.html`'s will take care of
214 | // minification. These next options are pre-configured if you do not wish
215 | // to use the Usemin blocks.
216 | // cssmin: {
217 | // dist: {
218 | // files: {
219 | // '<%= yeoman.dist %>/styles/main.css': [
220 | // '.tmp/styles/{,*/}*.css'
221 | // ]
222 | // }
223 | // }
224 | // },
225 | // uglify: {
226 | // dist: {
227 | // files: {
228 | // '<%= yeoman.dist %>/scripts/scripts.js': [
229 | // '<%= yeoman.dist %>/scripts/scripts.js'
230 | // ]
231 | // }
232 | // }
233 | // },
234 | // concat: {
235 | // dist: {}
236 | // },
237 |
238 | imagemin: {
239 | dist: {
240 | files: [{
241 | expand: true,
242 | cwd: '<%= yeoman.app %>/images',
243 | src: '{,*/}*.{png,jpg,jpeg,gif}',
244 | dest: '<%= yeoman.dist %>/images'
245 | }]
246 | }
247 | },
248 |
249 | svgmin: {
250 | dist: {
251 | files: [{
252 | expand: true,
253 | cwd: '<%= yeoman.app %>/images',
254 | src: '{,*/}*.svg',
255 | dest: '<%= yeoman.dist %>/images'
256 | }]
257 | }
258 | },
259 |
260 | htmlmin: {
261 | dist: {
262 | options: {
263 | collapseWhitespace: true,
264 | conservativeCollapse: true,
265 | collapseBooleanAttributes: true,
266 | removeCommentsFromCDATA: true,
267 | removeOptionalTags: true
268 | },
269 | files: [{
270 | expand: true,
271 | cwd: '<%= yeoman.dist %>',
272 | src: ['*.html', 'views/{,*/}*.html'],
273 | dest: '<%= yeoman.dist %>'
274 | }]
275 | }
276 | },
277 |
278 | // ngmin tries to make the code safe for minification automatically by
279 | // using the Angular long form for dependency injection. It doesn't work on
280 | // things like resolve or inject so those have to be done manually.
281 | ngmin: {
282 | dist: {
283 | files: [{
284 | expand: true,
285 | cwd: '.tmp/concat/scripts',
286 | src: '*.js',
287 | dest: '.tmp/concat/scripts'
288 | }]
289 | }
290 | },
291 |
292 | // Replace Google CDN references
293 | cdnify: {
294 | dist: {
295 | html: ['<%= yeoman.dist %>/*.html']
296 | }
297 | },
298 |
299 | // Copies remaining files to places other tasks can use
300 | copy: {
301 | dist: {
302 | files: [{
303 | expand: true,
304 | dot: true,
305 | cwd: '<%= yeoman.app %>',
306 | dest: '<%= yeoman.dist %>',
307 | src: [
308 | '*.{ico,png,txt}',
309 | '.htaccess',
310 | '*.html',
311 | 'views/{,*/}*.html',
312 | 'images/{,*/}*.{webp}',
313 | 'fonts/*',
314 | 'api/**'
315 | ]
316 | }, {
317 | expand: true,
318 | cwd: '.tmp/images',
319 | dest: '<%= yeoman.dist %>/images',
320 | src: ['generated/*']
321 | }, {
322 | expand: true,
323 | cwd: 'bower_components/bootstrap/dist',
324 | src: 'fonts/*',
325 | dest: '<%= yeoman.dist %>'
326 | }]
327 | },
328 | styles: {
329 | expand: true,
330 | cwd: '<%= yeoman.app %>/styles',
331 | dest: '.tmp/styles/',
332 | src: '{,*/}*.css'
333 | }
334 | },
335 |
336 | // Run some tasks in parallel to speed up the build process
337 | concurrent: {
338 | server: [
339 | 'copy:styles'
340 | ],
341 | test: [
342 | 'copy:styles'
343 | ],
344 | dist: [
345 | 'copy:styles',
346 | 'imagemin',
347 | 'svgmin'
348 | ]
349 | },
350 |
351 | // Test settings
352 | karma: {
353 | unit: {
354 | configFile: 'test/karma.conf.js',
355 | singleRun: true
356 | }
357 | }
358 | });
359 |
360 |
361 | grunt.registerTask('serve', 'Compile then start a php web server', function (target) {
362 | if (target === 'dist') {
363 | return grunt.task.run(['build', 'php:dist:keepalive']);
364 | }
365 |
366 | grunt.task.run([
367 | 'clean:server',
368 | 'wiredep',
369 | 'concurrent:server',
370 | 'autoprefixer',
371 | 'php:livereload',
372 | 'watch'
373 | ]);
374 | });
375 |
376 | grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
377 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
378 | grunt.task.run(['serve:' + target]);
379 | });
380 |
381 | grunt.registerTask('test', [
382 | 'clean:server',
383 | 'concurrent:test',
384 | 'autoprefixer',
385 | 'php:test',
386 | 'karma'
387 | ]);
388 |
389 | grunt.registerTask('build', [
390 | 'clean:dist',
391 | 'wiredep',
392 | 'useminPrepare',
393 | 'concurrent:dist',
394 | 'autoprefixer',
395 | 'concat',
396 | 'ngmin',
397 | 'copy:dist',
398 | 'cdnify',
399 | 'cssmin',
400 | 'uglify',
401 | 'filerev',
402 | 'usemin',
403 | 'htmlmin'
404 | ]);
405 |
406 | grunt.registerTask('default', [
407 | 'newer:jshint',
408 | 'test',
409 | 'build'
410 | ]);
411 | };
412 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Adam Merrifield
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | yo-angular-php-crud
2 | ===================
3 |
4 | A sample Yeoman application with an integrated Slim PHP CRUD API.
5 |
6 | ## Generate some data
7 |
8 | It's assumed in this tutorial that you have a MySQL database called `cars_demo` with a table called `cars`, with 5 columns -- `id`, `make`, `model`, `package`, `year`. To give yourself something to work with, add at least one entry right now. Perhaps your favorite car, your dream car and the one you own today.
9 |
10 | 1. Create the table:
11 |
12 | ```sql
13 | CREATE TABLE IF NOT EXISTS `cars` (
14 | `id` int(11) NOT NULL AUTO_INCREMENT,
15 | `make` varchar(50) NOT NULL,
16 | `model` varchar(50) NOT NULL,
17 | `pkg` varchar(50) NOT NULL,
18 | `year` varchar(50) NOT NULL,
19 | PRIMARY KEY (`id`)
20 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
21 | ```
22 |
23 | 1. Insert some samples:
24 |
25 | ```sql
26 | INSERT INTO `cars` (`make`, `model`, `pkg`, `year`) VALUES
27 | ('Ford', 'Escape', 'xlt', '2009'),
28 | ('Porsche', '918', 'Spyder', '2013');
29 | ```
30 |
31 | ## Setting up the app
32 |
33 | 1. Read more about [yo](http://yeoman.io/) and [AngularJS](https://angularjs.org/).
34 |
35 | 1. Create your yo application (any will do, I just went with AngularJS).
36 |
37 | ```sh
38 | yo angular:app myWebApp
39 | ```
40 |
41 | 1. Open the generated .gitignore and add the following after all the rest (we'll be using [composer](https://getcomposer.org/) for our PHP dependencies):
42 |
43 | ```sh
44 | composer.phar
45 | vendor/
46 | ```
47 |
48 | 1. Add a cars route:
49 |
50 | ```sh
51 | yo angular:route cars
52 | ```
53 |
54 | 1. Because we're hosting a php api we're going to need a php server instead of the [Connect](https://github.com/senchalabs/connect) node server that comes bundled with Yeoman. To do this we're going to install [grunt-php](https://github.com/sindresorhus/grunt-php):
55 |
56 | ```sh
57 | npm install --save-dev grunt-php
58 | ```
59 |
60 | 1. Grunt-php (above) claims to be a drop in replacement for grunt-contrib-connect, so with that in mind, I just replaced all instances of the word `connect` in my `Gruntfile.js` with the word `php` and it works (for the most part).
61 |
62 | 1. While in the `Gruntfile.js`, we'll add our soon-to-be created `api` folder to `copy.dist.files[0].src`:
63 |
64 | ```js
65 | src: [
66 | '*.{ico,png,txt}',
67 | '.htaccess',
68 |
69 | ...
70 |
71 | 'fonts/*',
72 | // add the api folder here
73 | 'api/**'
74 | ]
75 | ```
76 |
77 | 1. Also, at `php.options`, add the base folder for our future php script to run from:
78 |
79 | ```js
80 | options: {
81 | port: 9000,
82 |
83 | ...
84 |
85 | // add the base folder to run php scripts from
86 | base:'<%= yeoman.app %>'
87 | },
88 | ```
89 |
90 | 1. Now edit the `.bowerrc` file and change the bower components path to point to `app/`:
91 |
92 | ```js
93 | {
94 | "directory": "app/bower_components"
95 | }
96 | ```
97 |
98 | 1. And move the bower folder to the app folder:
99 |
100 | ```sh
101 | mv bower_components app/
102 | ```
103 |
104 | 1. Run `grunt serve` to ensure the PHP server is working (be mindful of [this issue](https://github.com/yeoman/generator-angular/issues/841))
105 |
106 | 1. Go to `http://localhost:9000/#/cars` to check that the route renders.
107 |
108 | 1. We'll come back to the front-end later.
109 |
110 | ## Installing the Slim Framework
111 |
112 | 1. Read more about [Slim](http://www.slimframework.com/) and [composer](https://getcomposer.org/).
113 |
114 | 1. In the root of the `app` directory, make a directory called `api`:
115 |
116 | ```sh
117 | mkdir app/api
118 | ```
119 |
120 | 1. Change over the `api` directory install composer locally (if it's not installed globally already):
121 |
122 | ```sh
123 | curl -s https://getcomposer.org/installer | php
124 | ```
125 |
126 | 1. Make a new `composer.json` file and include the Slim dependency:
127 |
128 | ```sh
129 | {
130 | "require": {
131 | "slim/slim": "2.*"
132 | }
133 | }
134 | ```
135 |
136 | 1. Install Slim via composer:
137 |
138 | ```sh
139 | php composer.phar install
140 | ```
141 |
142 | ## Setting up the Slim PHP API
143 |
144 | 1. In the `app/api` directory, make a new `index.php` file and add the following:
145 |
146 | ```php
147 | get('/cars', 'getCars');
163 | $app->get('/cars/:id', 'getCar');
164 | $app->post('/cars', 'addCar');
165 | $app->put('/cars/:id', 'updateCar');
166 | $app->delete('/cars/:id', 'deleteCar');
167 |
168 | $app->run();
169 | ```
170 |
171 | 1. Define a connection function:
172 |
173 | ```php
174 | function getConnection() {
175 | $dbhost="localhost";
176 | $dbport="8889";
177 | $dbuser="root";
178 | $dbpass="root";
179 | $dbname="cars_demo";
180 | $dbh = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
181 | $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
182 | return $dbh;
183 | }
184 | ```
185 |
186 | 1. Define each of the restful functions:
187 |
188 | Create a car...
189 | ```php
190 | function addCar() {
191 | $app = \Slim\Slim::getInstance();
192 | $req = $app->request();
193 | $car = json_decode($req->getBody());
194 | $sql = "INSERT INTO cars (make, model, year, pkg) VALUES (:make, :model, :year, :pkg)";
195 | try {
196 | $db = getConnection();
197 | $stmt = $db->prepare($sql);
198 | $stmt->bindParam("make", $car->make);
199 | $stmt->bindParam("model", $car->model);
200 | $stmt->bindParam("year", $car->year);
201 | $stmt->bindParam("pkg", $car->pkg);
202 | $stmt->execute();
203 | $car->id = $db->lastInsertId();
204 | $db = null;
205 | echo json_encode($car);
206 | } catch(PDOException $e) {
207 | echo '{"error":{"text":'. $e->getMessage() .'}}';
208 | }
209 | }
210 | ```
211 |
212 | Read all cars...
213 | ```php
214 | function getCars() {
215 | $sql = "select * FROM cars ORDER BY id";
216 | try {
217 | $db = getConnection();
218 | $stmt = $db->query($sql);
219 | $cars = $stmt->fetchAll(PDO::FETCH_OBJ);
220 | $db = null;
221 | echo json_encode($cars);
222 | } catch(PDOException $e) {
223 | echo '{"error":{"text":'. $e->getMessage() .'}}';
224 | }
225 | }
226 | ```
227 |
228 | Read one car...
229 | ```php
230 | function getCar($id) {
231 | $sql = "select * FROM cars WHERE id=".$id." ORDER BY id";
232 | try {
233 | $db = getConnection();
234 | $stmt = $db->query($sql);
235 | $cars = $stmt->fetchAll(PDO::FETCH_OBJ);
236 | $db = null;
237 | echo json_encode($cars);
238 | } catch(PDOException $e) {
239 | echo '{"error":{"text":'. $e->getMessage() .'}}';
240 | }
241 | }
242 | ```
243 |
244 | Update a car...
245 | ```php
246 | function updateCar($id) {
247 | $app = \Slim\Slim::getInstance();
248 | $req = $app->request();
249 | $car = json_decode($req->getBody());
250 | $sql = "UPDATE cars SET make=:make, model=:model, year=:year, pkg=:pkg WHERE id=:id";
251 |
252 | try {
253 | $db = getConnection();
254 | $stmt = $db->prepare($sql);
255 | $stmt->bindParam("make", $car->make);
256 | $stmt->bindParam("model", $car->model);
257 | $stmt->bindParam("year", $car->year);
258 | $stmt->bindParam("pkg", $car->pkg);
259 | $stmt->bindParam("id", $id);
260 | $stmt->execute();
261 | $db = null;
262 | echo json_encode($car);
263 | } catch(PDOException $e) {
264 | echo '{"error":{"text":'. $e->getMessage() .'}}';
265 | }
266 | }
267 | ```
268 |
269 | Delete a car...
270 | ```php
271 | function deleteCar($id) {
272 | $sql = "DELETE FROM cars WHERE id=:id";
273 | try {
274 | $db = getConnection();
275 | $stmt = $db->prepare($sql);
276 | $stmt->bindParam("id", $id);
277 | $stmt->execute();
278 | $db = null;
279 | } catch(PDOException $e) {
280 | echo '{"error":{"text":'. $e->getMessage() .'}}';
281 | }
282 | }
283 | ```
284 |
285 | 1. Test that your api works. I use [Postman](http://www.getpostman.com/).
286 |
287 | ## Creating our Interface in AngularJS
288 |
289 | #### Installing and applying Restangular
290 |
291 | 1. To consume our restful endpoints I was going to create a factory that added some syntactic sugar to either `$http` or `$resoure`, but then I found [Restangular](https://github.com/mgonto/restangular) and figured there was no point reinventing the reinvented wheel. We're going to use Restangular, which depends on lodash, so we need to include both in our project.
292 |
293 | Install...
294 | ```sh
295 | bower install restangular lodash --save
296 | ```
297 |
298 | ... and wire them into your app...
299 | ```sh
300 | grunt wiredep
301 | ```
302 |
303 | 1. We need to include Restangular as a dependency in our app module. Edit your `app/scripts/app.js` and add `'restangular'` to the list.:
304 |
305 | ```js
306 | .module('yoAngularPhpCrudApp', [
307 | 'ngAnimate',
308 | 'ngCookies',
309 | ...
310 | 'ngTouch',
311 | 'restangular'
312 | ])
313 | ```
314 |
315 | 1. We'll set a global configuration for Restangular in the same file, after our $routeProvider config. This isn't necessary, but we're making the assumption that all our restful calls will be made from the `api` route, therefore saving us a few bytes later on:
316 |
317 | ```js
318 | .config(function (RestangularProvider) {
319 | RestangularProvider.setBaseUrl('/api/');
320 | })
321 | ```
322 |
323 | 1. We now have the `Restangular` service available for injecting into our Angular objects. Let's inject Restangular into our cars controller. Edit `app/scripts/controllers/cars.js`:
324 |
325 | ```js
326 | .controller('CarsCtrl', function ($scope, Restangular) {
327 | ```
328 |
329 | #### Creating handlers in our Cars Controller
330 |
331 | 1. Let's create our first Restangular object. In the body of our `CarsCtrl`, add the following:
332 |
333 | ```js
334 | var url = 'cars';
335 | var Cars = Restangular.all(url);
336 | ```
337 |
338 | 1. Next we'll add a `blankCar` object that will help us clear out our model from time to time.
339 |
340 | ```js
341 | var blankCar = {
342 | make:"",
343 | model:"",
344 | pkg:"",
345 | year:""
346 | };
347 | ```
348 |
349 | 1. And then a function expression that will get our initial data for us, using Restangular convenience methods, and serve to refresh our data with each digest cycle:
350 |
351 | ```js
352 | var refreshCars = function() {
353 | $scope.isEditVisible = false;
354 | $scope.isAddNewCar = false;
355 | Cars.getList().then(function(data) {
356 | console.log('--> api/cars called from refreshCars()');
357 | $scope.cars = data;
358 | });
359 | };
360 | ```
361 |
362 | 1. Now let's get to creating our `$scope` objects. We'll start with some function expressions that set a few flags for us to help set and determine various states:
363 |
364 | ```js
365 | $scope.showAdd = function () {
366 | if ($scope.isEditVisible === false) {
367 | $scope.isAddNewCar = true;
368 | }
369 | };
370 |
371 | $scope.hideAdd = function () {
372 | $scope.isAddNewCar = false;
373 | $scope.newCar = angular.copy($scope.blankCar);
374 | };
375 |
376 | $scope.showEdit = function (idx) {
377 | $scope.isEditVisible = idx;
378 | $scope.newCar = angular.copy(blankCar);
379 | };
380 | ```
381 |
382 | 1. Now we'll add the methods for our remaining HTTP verbs, post, put and delete. Again, we'll use Restangular awesomeness:
383 |
384 | ```sh
385 | $scope.saveCar = function() {
386 | $scope.isEditVisible = false;
387 | $scope.isAddNewCar = false;
388 | Cars.post($scope.newCar).then(function (data) {
389 | $scope.cars.push(data);
390 | console.log('--> newCar saved!');
391 | },
392 | function () {
393 | console.log('--> Could not save newCar!');
394 | });
395 | };
396 |
397 | $scope.updateCar = function(idx) {
398 | $scope.isAddNewCar = false;
399 | Cars.getList().then(function(data) {
400 | console.log('--> api/cars called from updateCar()');
401 | $scope.cars = data;
402 | var carWithId = _.find($scope.cars, function(car) {
403 | return car.id === $scope.cars[idx].id;
404 | });
405 |
406 | carWithId.make = $scope.newCar.make || carWithId.make;
407 | carWithId.model = $scope.newCar.model || carWithId.model;
408 | carWithId.pkg = $scope.newCar.pkg || carWithId.pkg;
409 | carWithId.year = $scope.newCar.year || carWithId.year;
410 | carWithId.put();
411 | $scope.isEditVisible = false;
412 | });
413 | };
414 |
415 | $scope.deleteCar = function(idx) {
416 | var car = Restangular.one(url, $scope.cars[idx].id);
417 | car.remove();
418 | $scope.cars.splice(idx, 1);
419 | };
420 | ```
421 |
422 | 1. Finally, we'll run our `refreshCars()` method:
423 |
424 | ```js
425 | refreshCars();
426 | ```
427 |
428 | #### Creating our View
429 |
430 | I'm not going to go into a whole lot of detail here about the various things that are happening, but I will point out a few of the basic ideas.
431 |
432 | In addition to adding new cars within the view, we're employing an edit-in-place methodology so the cars we do create can be edited within that same view as well. This will allow for a better user experience than shuffling the user off to different views to add and edit entries.
433 |
434 | However, there are a lot of things to consider when using one view for multiple tasks, namely preventing users from being able to add a new car and edit another one at the same time. Or preventing users from performing actions on any of the other entries while editing another.
435 |
436 | So as you read through the code, have a look at some of the directives in use and some of the logic used to show and hide various components and disable others, depending on the current actions being performed.
437 |
438 | ```html
439 |
440 |
441 |
516 |
517 | ```
518 |
--------------------------------------------------------------------------------
/app/.buildignore:
--------------------------------------------------------------------------------
1 | *.coffee
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache Configuration File
2 |
3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access
4 | # to the main server config file (usually called `httpd.conf`), you should add
5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
6 |
7 | # ##############################################################################
8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) #
9 | # ##############################################################################
10 |
11 | # ------------------------------------------------------------------------------
12 | # | Cross-domain AJAX requests |
13 | # ------------------------------------------------------------------------------
14 |
15 | # Enable cross-origin AJAX requests.
16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
17 | # http://enable-cors.org/
18 |
19 | #
20 | # Header set Access-Control-Allow-Origin "*"
21 | #
22 |
23 | # ------------------------------------------------------------------------------
24 | # | CORS-enabled images |
25 | # ------------------------------------------------------------------------------
26 |
27 | # Send the CORS header for images when browsers request it.
28 | # https://developer.mozilla.org/en/CORS_Enabled_Image
29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
31 |
32 |
33 |
34 |
35 | SetEnvIf Origin ":" IS_CORS
36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS
37 |
38 |
39 |
40 |
41 | # ------------------------------------------------------------------------------
42 | # | Web fonts access |
43 | # ------------------------------------------------------------------------------
44 |
45 | # Allow access from all domains for web fonts
46 |
47 |
48 |
49 | Header set Access-Control-Allow-Origin "*"
50 |
51 |
52 |
53 |
54 | # ##############################################################################
55 | # # ERRORS #
56 | # ##############################################################################
57 |
58 | # ------------------------------------------------------------------------------
59 | # | 404 error prevention for non-existing redirected folders |
60 | # ------------------------------------------------------------------------------
61 |
62 | # Prevent Apache from returning a 404 error for a rewrite if a directory
63 | # with the same name does not exist.
64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
65 | # http://www.webmasterworld.com/apache/3808792.htm
66 |
67 | Options -MultiViews
68 |
69 | # ------------------------------------------------------------------------------
70 | # | Custom error messages / pages |
71 | # ------------------------------------------------------------------------------
72 |
73 | # You can customize what Apache returns to the client in case of an error (see
74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
75 |
76 | ErrorDocument 404 /404.html
77 |
78 |
79 | # ##############################################################################
80 | # # INTERNET EXPLORER #
81 | # ##############################################################################
82 |
83 | # ------------------------------------------------------------------------------
84 | # | Better website experience |
85 | # ------------------------------------------------------------------------------
86 |
87 | # Force IE to render pages in the highest available mode in the various
88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
89 |
90 |
91 | Header set X-UA-Compatible "IE=edge"
92 | # `mod_headers` can't match based on the content-type, however, we only
93 | # want to send this header for HTML pages and not for the other resources
94 |
95 | Header unset X-UA-Compatible
96 |
97 |
98 |
99 | # ------------------------------------------------------------------------------
100 | # | Cookie setting from iframes |
101 | # ------------------------------------------------------------------------------
102 |
103 | # Allow cookies to be set from iframes in IE.
104 |
105 | #
106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
107 | #
108 |
109 | # ------------------------------------------------------------------------------
110 | # | Screen flicker |
111 | # ------------------------------------------------------------------------------
112 |
113 | # Stop screen flicker in IE on CSS rollovers (this only works in
114 | # combination with the `ExpiresByType` directives for images from below).
115 |
116 | # BrowserMatch "MSIE" brokenvary=1
117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
118 | # BrowserMatch "Opera" !brokenvary
119 | # SetEnvIf brokenvary 1 force-no-vary
120 |
121 |
122 | # ##############################################################################
123 | # # MIME TYPES AND ENCODING #
124 | # ##############################################################################
125 |
126 | # ------------------------------------------------------------------------------
127 | # | Proper MIME types for all files |
128 | # ------------------------------------------------------------------------------
129 |
130 |
131 |
132 | # Audio
133 | AddType audio/mp4 m4a f4a f4b
134 | AddType audio/ogg oga ogg
135 |
136 | # JavaScript
137 | # Normalize to standard type (it's sniffed in IE anyways):
138 | # http://tools.ietf.org/html/rfc4329#section-7.2
139 | AddType application/javascript js jsonp
140 | AddType application/json json
141 |
142 | # Video
143 | AddType video/mp4 mp4 m4v f4v f4p
144 | AddType video/ogg ogv
145 | AddType video/webm webm
146 | AddType video/x-flv flv
147 |
148 | # Web fonts
149 | AddType application/font-woff woff
150 | AddType application/vnd.ms-fontobject eot
151 |
152 | # Browsers usually ignore the font MIME types and sniff the content,
153 | # however, Chrome shows a warning if other MIME types are used for the
154 | # following fonts.
155 | AddType application/x-font-ttf ttc ttf
156 | AddType font/opentype otf
157 |
158 | # Make SVGZ fonts work on iPad:
159 | # https://twitter.com/FontSquirrel/status/14855840545
160 | AddType image/svg+xml svg svgz
161 | AddEncoding gzip svgz
162 |
163 | # Other
164 | AddType application/octet-stream safariextz
165 | AddType application/x-chrome-extension crx
166 | AddType application/x-opera-extension oex
167 | AddType application/x-shockwave-flash swf
168 | AddType application/x-web-app-manifest+json webapp
169 | AddType application/x-xpinstall xpi
170 | AddType application/xml atom rdf rss xml
171 | AddType image/webp webp
172 | AddType image/x-icon ico
173 | AddType text/cache-manifest appcache manifest
174 | AddType text/vtt vtt
175 | AddType text/x-component htc
176 | AddType text/x-vcard vcf
177 |
178 |
179 |
180 | # ------------------------------------------------------------------------------
181 | # | UTF-8 encoding |
182 | # ------------------------------------------------------------------------------
183 |
184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
185 | AddDefaultCharset utf-8
186 |
187 | # Force UTF-8 for certain file formats.
188 |
189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
190 |
191 |
192 |
193 | # ##############################################################################
194 | # # URL REWRITES #
195 | # ##############################################################################
196 |
197 | # ------------------------------------------------------------------------------
198 | # | Rewrite engine |
199 | # ------------------------------------------------------------------------------
200 |
201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is
202 | # necessary for the following directives to work.
203 |
204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to
205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
207 |
208 | # Also, some cloud hosting services require `RewriteBase` to be set:
209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
210 |
211 |
212 | Options +FollowSymlinks
213 | # Options +SymLinksIfOwnerMatch
214 | RewriteEngine On
215 | # RewriteBase /
216 |
217 |
218 | # ------------------------------------------------------------------------------
219 | # | Suppressing / Forcing the "www." at the beginning of URLs |
220 | # ------------------------------------------------------------------------------
221 |
222 | # The same content should never be available under two different URLs especially
223 | # not with and without "www." at the beginning. This can cause SEO problems
224 | # (duplicate content), therefore, you should choose one of the alternatives and
225 | # redirect the other one.
226 |
227 | # By default option 1 (no "www.") is activated:
228 | # http://no-www.org/faq.php?q=class_b
229 |
230 | # If you'd prefer to use option 2, just comment out all the lines from option 1
231 | # and uncomment the ones from option 2.
232 |
233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
234 |
235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
236 |
237 | # Option 1: rewrite www.example.com → example.com
238 |
239 |
240 | RewriteCond %{HTTPS} !=on
241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
243 |
244 |
245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
246 |
247 | # Option 2: rewrite example.com → www.example.com
248 |
249 | # Be aware that the following might not be a good idea if you use "real"
250 | # subdomains for certain parts of your website.
251 |
252 | #
253 | # RewriteCond %{HTTPS} !=on
254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
256 | #
257 |
258 |
259 | # ##############################################################################
260 | # # SECURITY #
261 | # ##############################################################################
262 |
263 | # ------------------------------------------------------------------------------
264 | # | Content Security Policy (CSP) |
265 | # ------------------------------------------------------------------------------
266 |
267 | # You can mitigate the risk of cross-site scripting and other content-injection
268 | # attacks by setting a Content Security Policy which whitelists trusted sources
269 | # of content for your site.
270 |
271 | # The example header below allows ONLY scripts that are loaded from the current
272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't
273 | # work as-is for your site!
274 |
275 | # To get all the details you'll need to craft a reasonable policy for your site,
276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
277 | # see the specification: http://w3.org/TR/CSP).
278 |
279 | #
280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
281 | #
282 | # Header unset Content-Security-Policy
283 | #
284 | #
285 |
286 | # ------------------------------------------------------------------------------
287 | # | File access |
288 | # ------------------------------------------------------------------------------
289 |
290 | # Block access to directories without a default document.
291 | # Usually you should leave this uncommented because you shouldn't allow anyone
292 | # to surf through every directory on your server (which may includes rather
293 | # private places like the CMS's directories).
294 |
295 |
296 | Options -Indexes
297 |
298 |
299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
300 |
301 | # Block access to hidden files and directories.
302 | # This includes directories used by version control systems such as Git and SVN.
303 |
304 |
305 | RewriteCond %{SCRIPT_FILENAME} -d [OR]
306 | RewriteCond %{SCRIPT_FILENAME} -f
307 | RewriteRule "(^|/)\." - [F]
308 |
309 |
310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
311 |
312 | # Block access to backup and source files.
313 | # These files may be left by some text editors and can pose a great security
314 | # danger when anyone has access to them.
315 |
316 |
317 | Order allow,deny
318 | Deny from all
319 | Satisfy All
320 |
321 |
322 | # ------------------------------------------------------------------------------
323 | # | Secure Sockets Layer (SSL) |
324 | # ------------------------------------------------------------------------------
325 |
326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
327 | # prevent `https://www.example.com` when your certificate only allows
328 | # `https://secure.example.com`.
329 |
330 | #
331 | # RewriteCond %{SERVER_PORT} !^443
332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
333 | #
334 |
335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
336 |
337 | # Force client-side SSL redirection.
338 |
339 | # If a user types "example.com" in his browser, the above rule will redirect him
340 | # to the secure version of the site. That still leaves a window of opportunity
341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the
342 | # request. The following header ensures that browser will ONLY connect to your
343 | # server via HTTPS, regardless of what the users type in the address bar.
344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
345 |
346 | #
347 | # Header set Strict-Transport-Security max-age=16070400;
348 | #
349 |
350 | # ------------------------------------------------------------------------------
351 | # | Server software information |
352 | # ------------------------------------------------------------------------------
353 |
354 | # Avoid displaying the exact Apache version number, the description of the
355 | # generic OS-type and the information about Apache's compiled-in modules.
356 |
357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
358 |
359 | # ServerTokens Prod
360 |
361 |
362 | # ##############################################################################
363 | # # WEB PERFORMANCE #
364 | # ##############################################################################
365 |
366 | # ------------------------------------------------------------------------------
367 | # | Compression |
368 | # ------------------------------------------------------------------------------
369 |
370 |
371 |
372 | # Force compression for mangled headers.
373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
374 |
375 |
376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
378 |
379 |
380 |
381 | # Compress all output labeled with one of the following MIME-types
382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
383 | # and can remove the `` and `` lines
384 | # as `AddOutputFilterByType` is still in the core directives).
385 |
386 | AddOutputFilterByType DEFLATE application/atom+xml \
387 | application/javascript \
388 | application/json \
389 | application/rss+xml \
390 | application/vnd.ms-fontobject \
391 | application/x-font-ttf \
392 | application/x-web-app-manifest+json \
393 | application/xhtml+xml \
394 | application/xml \
395 | font/opentype \
396 | image/svg+xml \
397 | image/x-icon \
398 | text/css \
399 | text/html \
400 | text/plain \
401 | text/x-component \
402 | text/xml
403 |
404 |
405 |
406 |
407 | # ------------------------------------------------------------------------------
408 | # | Content transformations |
409 | # ------------------------------------------------------------------------------
410 |
411 | # Prevent some of the mobile network providers from modifying the content of
412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
413 |
414 | #
415 | # Header set Cache-Control "no-transform"
416 | #
417 |
418 | # ------------------------------------------------------------------------------
419 | # | ETag removal |
420 | # ------------------------------------------------------------------------------
421 |
422 | # Since we're sending far-future expires headers (see below), ETags can
423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags.
424 |
425 | # `FileETag None` is not enough for every server.
426 |
427 | Header unset ETag
428 |
429 |
430 | FileETag None
431 |
432 | # ------------------------------------------------------------------------------
433 | # | Expires headers (for better cache control) |
434 | # ------------------------------------------------------------------------------
435 |
436 | # The following expires headers are set pretty far in the future. If you don't
437 | # control versioning with filename-based cache busting, consider lowering the
438 | # cache time for resources like CSS and JS to something like 1 week.
439 |
440 |
441 |
442 | ExpiresActive on
443 | ExpiresDefault "access plus 1 month"
444 |
445 | # CSS
446 | ExpiresByType text/css "access plus 1 year"
447 |
448 | # Data interchange
449 | ExpiresByType application/json "access plus 0 seconds"
450 | ExpiresByType application/xml "access plus 0 seconds"
451 | ExpiresByType text/xml "access plus 0 seconds"
452 |
453 | # Favicon (cannot be renamed!)
454 | ExpiresByType image/x-icon "access plus 1 week"
455 |
456 | # HTML components (HTCs)
457 | ExpiresByType text/x-component "access plus 1 month"
458 |
459 | # HTML
460 | ExpiresByType text/html "access plus 0 seconds"
461 |
462 | # JavaScript
463 | ExpiresByType application/javascript "access plus 1 year"
464 |
465 | # Manifest files
466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
467 | ExpiresByType text/cache-manifest "access plus 0 seconds"
468 |
469 | # Media
470 | ExpiresByType audio/ogg "access plus 1 month"
471 | ExpiresByType image/gif "access plus 1 month"
472 | ExpiresByType image/jpeg "access plus 1 month"
473 | ExpiresByType image/png "access plus 1 month"
474 | ExpiresByType video/mp4 "access plus 1 month"
475 | ExpiresByType video/ogg "access plus 1 month"
476 | ExpiresByType video/webm "access plus 1 month"
477 |
478 | # Web feeds
479 | ExpiresByType application/atom+xml "access plus 1 hour"
480 | ExpiresByType application/rss+xml "access plus 1 hour"
481 |
482 | # Web fonts
483 | ExpiresByType application/font-woff "access plus 1 month"
484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
485 | ExpiresByType application/x-font-ttf "access plus 1 month"
486 | ExpiresByType font/opentype "access plus 1 month"
487 | ExpiresByType image/svg+xml "access plus 1 month"
488 |
489 |
490 |
491 | # ------------------------------------------------------------------------------
492 | # | Filename-based cache busting |
493 | # ------------------------------------------------------------------------------
494 |
495 | # If you're not using a build process to manage your filename version revving,
496 | # you might want to consider enabling the following directives to route all
497 | # requests such as `/css/style.12345.css` to `/css/style.css`.
498 |
499 | # To understand why this is important and a better idea than `*.css?v231`, read:
500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
501 |
502 | #
503 | # RewriteCond %{REQUEST_FILENAME} !-f
504 | # RewriteCond %{REQUEST_FILENAME} !-d
505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
506 | #
507 |
508 | # ------------------------------------------------------------------------------
509 | # | File concatenation |
510 | # ------------------------------------------------------------------------------
511 |
512 | # Allow concatenation from within specific CSS and JS files, e.g.:
513 | # Inside of `script.combined.js` you could have
514 | #
515 | #
516 | # and they would be included into this single file.
517 |
518 | #
519 | #
520 | # Options +Includes
521 | # AddOutputFilterByType INCLUDES application/javascript application/json
522 | # SetOutputFilter INCLUDES
523 | #
524 | #
525 | # Options +Includes
526 | # AddOutputFilterByType INCLUDES text/css
527 | # SetOutputFilter INCLUDES
528 | #
529 | #
530 |
531 | # ------------------------------------------------------------------------------
532 | # | Persistent connections |
533 | # ------------------------------------------------------------------------------
534 |
535 | # Allow multiple requests to be sent over the same TCP connection:
536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
537 |
538 | # Enable if you serve a lot of static content but, be aware of the
539 | # possible disadvantages!
540 |
541 | #
542 | # Header set Connection Keep-Alive
543 | #
544 |
--------------------------------------------------------------------------------
/app/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page Not Found :(
6 |
141 |
142 |
143 |
144 |
Not found :(
145 |
Sorry, but the page you were trying to view does not exist.
146 |
It looks like this was the result of either:
147 |
148 | - a mistyped address
149 | - an out-of-date link
150 |
151 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/app/api/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "slim/slim": "2.*"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/app/api/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "hash": "3572ef5486e01be41b96a1fdace500a6",
8 | "packages": [
9 | {
10 | "name": "slim/slim",
11 | "version": "2.4.3",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/codeguy/Slim.git",
15 | "reference": "4906b77a07c7bd6ff1a99aea903e940a2d4fa106"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/codeguy/Slim/zipball/4906b77a07c7bd6ff1a99aea903e940a2d4fa106",
20 | "reference": "4906b77a07c7bd6ff1a99aea903e940a2d4fa106",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "php": ">=5.3.0"
25 | },
26 | "suggest": {
27 | "ext-mcrypt": "Required for HTTP cookie encryption"
28 | },
29 | "type": "library",
30 | "autoload": {
31 | "psr-0": {
32 | "Slim": "."
33 | }
34 | },
35 | "notification-url": "https://packagist.org/downloads/",
36 | "license": [
37 | "MIT"
38 | ],
39 | "authors": [
40 | {
41 | "name": "Josh Lockhart",
42 | "email": "info@joshlockhart.com",
43 | "homepage": "http://www.joshlockhart.com/"
44 | }
45 | ],
46 | "description": "Slim Framework, a PHP micro framework",
47 | "homepage": "http://github.com/codeguy/Slim",
48 | "keywords": [
49 | "microframework",
50 | "rest",
51 | "router"
52 | ],
53 | "time": "2014-04-05 18:33:59"
54 | }
55 | ],
56 | "packages-dev": [],
57 | "aliases": [],
58 | "minimum-stability": "stable",
59 | "stability-flags": [],
60 | "prefer-stable": false,
61 | "platform": [],
62 | "platform-dev": []
63 | }
64 |
--------------------------------------------------------------------------------
/app/api/index.php:
--------------------------------------------------------------------------------
1 |
2 | get('/cars', 'getCars');
8 | $app->get('/cars/:id', 'getCar');
9 | $app->post('/cars', 'addCar');
10 | $app->put('/cars/:id', 'updateCar');
11 | $app->delete('/cars/:id', 'deleteCar');
12 |
13 | $app->run();
14 |
15 | function getConnection() {
16 | $dbhost="localhost";
17 | $dbport="8889";
18 | $dbuser="root";
19 | $dbpass="root";
20 | $dbname="cars_demo";
21 | $dbh = new PDO("mysql:host=$dbhost;dbname=$dbname", $dbuser, $dbpass);
22 | $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
23 | return $dbh;
24 | }
25 |
26 | function getCars() {
27 | $sql = "select * FROM cars ORDER BY id";
28 | try {
29 | $db = getConnection();
30 | $stmt = $db->query($sql);
31 | $cars = $stmt->fetchAll(PDO::FETCH_OBJ);
32 | $db = null;
33 | echo json_encode($cars);
34 | } catch(PDOException $e) {
35 | echo '{"error":{"text":'. $e->getMessage() .'}}';
36 | }
37 | }
38 |
39 | function getCar($id) {
40 | $sql = "select * FROM cars WHERE id=".$id." ORDER BY id";
41 | try {
42 | $db = getConnection();
43 | $stmt = $db->query($sql);
44 | $cars = $stmt->fetchAll(PDO::FETCH_OBJ);
45 | $db = null;
46 | echo json_encode($cars);
47 | } catch(PDOException $e) {
48 | echo '{"error":{"text":'. $e->getMessage() .'}}';
49 | }
50 | }
51 |
52 | function addCar() {
53 | $app = \Slim\Slim::getInstance();
54 | $req = $app->request();
55 | $car = json_decode($req->getBody());
56 |
57 | $sql = "INSERT INTO cars (make, model, year, pkg) VALUES (:make, :model, :year, :pkg)";
58 |
59 | try {
60 | $db = getConnection();
61 | $stmt = $db->prepare($sql);
62 | $stmt->bindParam("make", $car->make);
63 | $stmt->bindParam("model", $car->model);
64 | $stmt->bindParam("year", $car->year);
65 | $stmt->bindParam("pkg", $car->pkg);
66 | $stmt->execute();
67 | $car->id = $db->lastInsertId();
68 | $db = null;
69 | echo json_encode($car);
70 | } catch(PDOException $e) {
71 | echo '{"error":{"text":'. $e->getMessage() .'}}';
72 | }
73 | }
74 |
75 | function updateCar($id) {
76 | $app = \Slim\Slim::getInstance();
77 | $req = $app->request();
78 | $car = json_decode($req->getBody());
79 |
80 | $sql = "UPDATE cars SET make=:make, model=:model, year=:year, pkg=:pkg WHERE id=:id";
81 |
82 | try {
83 | $db = getConnection();
84 | $stmt = $db->prepare($sql);
85 | $stmt->bindParam("make", $car->make);
86 | $stmt->bindParam("model", $car->model);
87 | $stmt->bindParam("year", $car->year);
88 | $stmt->bindParam("pkg", $car->pkg);
89 | $stmt->bindParam("id", $id);
90 | $stmt->execute();
91 | $db = null;
92 | echo json_encode($car);
93 | } catch(PDOException $e) {
94 | echo '{"error":{"text":'. $e->getMessage() .'}}';
95 | }
96 | }
97 |
98 | function deleteCar($id) {
99 | $sql = "DELETE FROM cars WHERE id=:id";
100 | try {
101 | $db = getConnection();
102 | $stmt = $db->prepare($sql);
103 | $stmt->bindParam("id", $id);
104 | $stmt->execute();
105 | $db = null;
106 | } catch(PDOException $e) {
107 | echo '{"error":{"text":'. $e->getMessage() .'}}';
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seyDoggy/yo-angular-php-crud/a91e8e12fb3a5f8a9c719992f2fd17c3e7859476/app/favicon.ico
--------------------------------------------------------------------------------
/app/images/yeoman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seyDoggy/yo-angular-php-crud/a91e8e12fb3a5f8a9c719992f2fd17c3e7859476/app/images/yeoman.png
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/app/robots.txt:
--------------------------------------------------------------------------------
1 | # robotstxt.org
2 |
3 | User-agent: *
4 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc overview
5 | * @name myWebAppApp
6 | * @description
7 | * # myWebAppApp
8 | *
9 | * Main module of the application.
10 | */
11 | angular
12 | .module('myWebAppApp', [
13 | 'ngAnimate',
14 | 'ngCookies',
15 | 'ngResource',
16 | 'ngRoute',
17 | 'ngSanitize',
18 | 'ngTouch',
19 | 'restangular'
20 | ])
21 | .config(function ($routeProvider) {
22 | $routeProvider
23 | .when('/', {
24 | templateUrl: 'views/main.html',
25 | controller: 'MainCtrl'
26 | })
27 | .when('/about', {
28 | templateUrl: 'views/about.html',
29 | controller: 'AboutCtrl'
30 | })
31 | .when('/cars', {
32 | templateUrl: 'views/cars.html',
33 | controller: 'CarsCtrl'
34 | })
35 | .otherwise({
36 | redirectTo: '/'
37 | });
38 | })
39 | .config(function (RestangularProvider) {
40 | RestangularProvider.setBaseUrl('/api/');
41 | });
42 |
--------------------------------------------------------------------------------
/app/scripts/controllers/about.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name myWebAppApp.controller:AboutCtrl
6 | * @description
7 | * # AboutCtrl
8 | * Controller of the myWebAppApp
9 | */
10 | angular.module('myWebAppApp')
11 | .controller('AboutCtrl', function ($scope) {
12 | $scope.awesomeThings = [
13 | 'HTML5 Boilerplate',
14 | 'AngularJS',
15 | 'Karma'
16 | ];
17 | });
18 |
--------------------------------------------------------------------------------
/app/scripts/controllers/cars.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name myWebAppApp.controller:CarsCtrl
6 | * @description
7 | * # CarsCtrl
8 | * Controller of the myWebAppApp
9 | */
10 | angular.module('myWebAppApp')
11 | .controller('CarsCtrl', function ($scope, Restangular) {
12 | var url = 'cars';
13 | var Cars = Restangular.all(url);
14 | var blankCar = {
15 | make:"",
16 | model:"",
17 | pkg:"",
18 | year:""
19 | };
20 | var refreshCars = function() {
21 | $scope.isEditVisible = false;
22 | $scope.isAddNewCar = false;
23 | Cars.getList().then(function(data) {
24 | console.log('--> api/cars called from refreshCars()');
25 | $scope.cars = data;
26 | });
27 | };
28 |
29 | $scope.showAdd = function () {
30 | if ($scope.isEditVisible === false) {
31 | $scope.isAddNewCar = true;
32 | }
33 | };
34 |
35 | $scope.hideAdd = function () {
36 | $scope.isAddNewCar = false;
37 | $scope.newCar = angular.copy($scope.blankCar);
38 | };
39 |
40 | $scope.showEdit = function (idx) {
41 | $scope.isEditVisible = idx;
42 | $scope.newCar = angular.copy(blankCar);
43 | };
44 |
45 | $scope.saveCar = function() {
46 | $scope.isEditVisible = false;
47 | $scope.isAddNewCar = false;
48 | Cars.post($scope.newCar).then(function (data) {
49 | $scope.cars.push(data);
50 | console.log('--> newCar saved!');
51 | },
52 | function () {
53 | console.log('--> Could not save newCar!');
54 | });
55 | };
56 |
57 | $scope.updateCar = function(idx) {
58 | $scope.isAddNewCar = false;
59 | Cars.getList().then(function(data) {
60 | console.log('--> api/cars called from updateCar()');
61 | $scope.cars = data;
62 | var carWithId = _.find($scope.cars, function(car) {
63 | return car.id === $scope.cars[idx].id;
64 | });
65 |
66 | carWithId.make = $scope.newCar.make || carWithId.make;
67 | carWithId.model = $scope.newCar.model || carWithId.model;
68 | carWithId.pkg = $scope.newCar.pkg || carWithId.pkg;
69 | carWithId.year = $scope.newCar.year || carWithId.year;
70 | carWithId.put();
71 | $scope.isEditVisible = false;
72 | });
73 | };
74 |
75 | $scope.deleteCar = function(idx) {
76 | var car = Restangular.one(url, $scope.cars[idx].id);
77 | car.remove();
78 | $scope.cars.splice(idx, 1);
79 | };
80 |
81 | refreshCars();
82 | });
83 |
--------------------------------------------------------------------------------
/app/scripts/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc function
5 | * @name myWebAppApp.controller:MainCtrl
6 | * @description
7 | * # MainCtrl
8 | * Controller of the myWebAppApp
9 | */
10 | angular.module('myWebAppApp')
11 | .controller('MainCtrl', function ($scope) {
12 | $scope.awesomeThings = [
13 | 'HTML5 Boilerplate',
14 | 'AngularJS',
15 | 'Karma'
16 | ];
17 | });
18 |
--------------------------------------------------------------------------------
/app/styles/main.css:
--------------------------------------------------------------------------------
1 | /* Space out content a bit */
2 | body {
3 | padding-top: 20px;
4 | padding-bottom: 20px;
5 | }
6 |
7 | /* Everything but the jumbotron gets side spacing for mobile first views */
8 | .header,
9 | .marketing,
10 | .footer {
11 | padding-left: 15px;
12 | padding-right: 15px;
13 | }
14 |
15 | /* Custom page header */
16 | .header {
17 | border-bottom: 1px solid #e5e5e5;
18 | }
19 | /* Make the masthead heading the same height as the navigation */
20 | .header h3 {
21 | margin-top: 0;
22 | margin-bottom: 0;
23 | line-height: 40px;
24 | padding-bottom: 19px;
25 | }
26 |
27 | /* Custom page footer */
28 | .footer {
29 | padding-top: 19px;
30 | color: #777;
31 | border-top: 1px solid #e5e5e5;
32 | }
33 |
34 | /* Customize container */
35 | @media (min-width: 768px) {
36 | .container {
37 | max-width: 730px;
38 | }
39 | }
40 | .container-narrow > hr {
41 | margin: 30px 0;
42 | }
43 |
44 | /* Main marketing message and sign up button */
45 | .jumbotron {
46 | text-align: center;
47 | border-bottom: 1px solid #e5e5e5;
48 | }
49 | .jumbotron .btn {
50 | font-size: 21px;
51 | padding: 14px 24px;
52 | }
53 |
54 | /* Supporting marketing content */
55 | .marketing {
56 | margin: 40px 0;
57 | }
58 | .marketing p + h4 {
59 | margin-top: 28px;
60 | }
61 |
62 | /* Responsive: Portrait tablets and up */
63 | @media screen and (min-width: 768px) {
64 | /* Remove the padding we set earlier */
65 | .header,
66 | .marketing,
67 | .footer {
68 | padding-left: 0;
69 | padding-right: 0;
70 | }
71 | /* Space out the masthead */
72 | .header {
73 | margin-bottom: 30px;
74 | }
75 | /* Remove the bottom border on the jumbotron for visual effect */
76 | .jumbotron {
77 | border-bottom: 0;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/views/about.html:
--------------------------------------------------------------------------------
1 | This is the about view.
2 |
--------------------------------------------------------------------------------
/app/views/cars.html:
--------------------------------------------------------------------------------
1 |
73 |
--------------------------------------------------------------------------------
/app/views/main.html:
--------------------------------------------------------------------------------
1 |
2 |
'Allo, 'Allo!
3 |
4 | 
5 | Always a pleasure scaffolding your apps.
6 |
7 |
Splendid!
8 |
9 |
10 |
11 |
HTML5 Boilerplate
12 |
13 | HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites.
14 |
15 |
16 |
Angular
17 |
18 | AngularJS is a toolset for building the framework most suited to your application development.
19 |
20 |
21 |
Karma
22 |
Spectacular Test Runner for JavaScript.
23 |
24 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-web-app",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "angular": "1.2.16",
6 | "json3": "~3.3.1",
7 | "es5-shim": "~3.1.0",
8 | "bootstrap": "~3.2.0",
9 | "angular-resource": "1.2.16",
10 | "angular-cookies": "1.2.16",
11 | "angular-sanitize": "1.2.16",
12 | "angular-animate": "1.2.16",
13 | "angular-touch": "1.2.16",
14 | "angular-route": "1.2.16",
15 | "restangular": "~1.4.0",
16 | "lodash": "~2.4.1"
17 | },
18 | "devDependencies": {
19 | "angular-mocks": "1.2.16",
20 | "angular-scenario": "1.2.16"
21 | },
22 | "appPath": "app"
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mywebapp",
3 | "version": "0.0.0",
4 | "dependencies": {},
5 | "devDependencies": {
6 | "grunt": "^0.4.1",
7 | "grunt-autoprefixer": "^0.7.3",
8 | "grunt-concurrent": "^0.5.0",
9 | "grunt-contrib-clean": "^0.5.0",
10 | "grunt-contrib-concat": "^0.4.0",
11 | "grunt-contrib-connect": "^0.7.1",
12 | "grunt-contrib-copy": "^0.5.0",
13 | "grunt-contrib-cssmin": "^0.9.0",
14 | "grunt-contrib-htmlmin": "^0.3.0",
15 | "grunt-contrib-imagemin": "^0.7.0",
16 | "grunt-contrib-jshint": "^0.10.0",
17 | "grunt-contrib-uglify": "^0.4.0",
18 | "grunt-contrib-watch": "^0.6.1",
19 | "grunt-filerev": "^0.2.1",
20 | "grunt-google-cdn": "^0.4.0",
21 | "grunt-karma": "^0.9.0",
22 | "grunt-newer": "^0.7.0",
23 | "grunt-ngmin": "^0.0.3",
24 | "grunt-php": "^1.1.0",
25 | "grunt-svgmin": "^0.4.0",
26 | "grunt-usemin": "^2.1.1",
27 | "grunt-wiredep": "^1.7.0",
28 | "jshint-stylish": "^0.2.0",
29 | "karma": "^0.12.24",
30 | "karma-jasmine": "^0.1.5",
31 | "karma-phantomjs-launcher": "^0.1.4",
32 | "load-grunt-tasks": "^0.4.0",
33 | "time-grunt": "^0.3.1"
34 | },
35 | "engines": {
36 | "node": ">=0.10.0"
37 | },
38 | "scripts": {
39 | "test": "grunt test"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "bitwise": true,
6 | "camelcase": true,
7 | "curly": true,
8 | "eqeqeq": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": true,
12 | "newcap": true,
13 | "noarg": true,
14 | "quotmark": "single",
15 | "regexp": true,
16 | "undef": true,
17 | "unused": true,
18 | "strict": true,
19 | "trailing": true,
20 | "smarttabs": true,
21 | "globals": {
22 | "after": false,
23 | "afterEach": false,
24 | "angular": false,
25 | "before": false,
26 | "beforeEach": false,
27 | "browser": false,
28 | "describe": false,
29 | "expect": false,
30 | "inject": false,
31 | "it": false,
32 | "jasmine": false,
33 | "spyOn": false
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // http://karma-runner.github.io/0.12/config/configuration-file.html
3 | // Generated on 2014-10-11 using
4 | // generator-karma 0.8.3
5 |
6 | module.exports = function(config) {
7 | 'use strict';
8 |
9 | config.set({
10 | // enable / disable watching file and executing tests whenever any file changes
11 | autoWatch: true,
12 |
13 | // base path, that will be used to resolve files and exclude
14 | basePath: '../',
15 |
16 | // testing framework to use (jasmine/mocha/qunit/...)
17 | frameworks: ['jasmine'],
18 |
19 | // list of files / patterns to load in the browser
20 | files: [
21 | 'bower_components/angular/angular.js',
22 | 'bower_components/angular-mocks/angular-mocks.js',
23 | 'bower_components/angular-animate/angular-animate.js',
24 | 'bower_components/angular-cookies/angular-cookies.js',
25 | 'bower_components/angular-resource/angular-resource.js',
26 | 'bower_components/angular-route/angular-route.js',
27 | 'bower_components/angular-sanitize/angular-sanitize.js',
28 | 'bower_components/angular-touch/angular-touch.js',
29 | 'app/scripts/**/*.js',
30 | 'test/mock/**/*.js',
31 | 'test/spec/**/*.js'
32 | ],
33 |
34 | // list of files / patterns to exclude
35 | exclude: [],
36 |
37 | // web server port
38 | port: 8080,
39 |
40 | // Start these browsers, currently available:
41 | // - Chrome
42 | // - ChromeCanary
43 | // - Firefox
44 | // - Opera
45 | // - Safari (only Mac)
46 | // - PhantomJS
47 | // - IE (only Windows)
48 | browsers: [
49 | 'PhantomJS'
50 | ],
51 |
52 | // Which plugins to enable
53 | plugins: [
54 | 'karma-phantomjs-launcher',
55 | 'karma-jasmine'
56 | ],
57 |
58 | // Continuous Integration mode
59 | // if true, it capture browsers, run tests and exit
60 | singleRun: false,
61 |
62 | colors: true,
63 |
64 | // level of logging
65 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
66 | logLevel: config.LOG_INFO,
67 |
68 | // Uncomment the following lines if you are using grunt's server to run the tests
69 | // proxies: {
70 | // '/': 'http://localhost:9000/'
71 | // },
72 | // URL root prevent conflicts with the site root
73 | // urlRoot: '_karma_'
74 | });
75 | };
76 |
--------------------------------------------------------------------------------
/test/spec/controllers/about.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Controller: AboutCtrl', function () {
4 |
5 | // load the controller's module
6 | beforeEach(module('myWebAppApp'));
7 |
8 | var AboutCtrl,
9 | scope;
10 |
11 | // Initialize the controller and a mock scope
12 | beforeEach(inject(function ($controller, $rootScope) {
13 | scope = $rootScope.$new();
14 | AboutCtrl = $controller('AboutCtrl', {
15 | $scope: scope
16 | });
17 | }));
18 |
19 | it('should attach a list of awesomeThings to the scope', function () {
20 | expect(scope.awesomeThings.length).toBe(3);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/spec/controllers/cars.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Controller: CarsCtrl', function () {
4 |
5 | // load the controller's module
6 | beforeEach(module('myWebAppApp'));
7 |
8 | var CarsCtrl,
9 | scope;
10 |
11 | // Initialize the controller and a mock scope
12 | beforeEach(inject(function ($controller, $rootScope) {
13 | scope = $rootScope.$new();
14 | CarsCtrl = $controller('CarsCtrl', {
15 | $scope: scope
16 | });
17 | }));
18 |
19 | it('should attach a list of awesomeThings to the scope', function () {
20 | expect(scope.awesomeThings.length).toBe(3);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/spec/controllers/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('Controller: MainCtrl', function () {
4 |
5 | // load the controller's module
6 | beforeEach(module('myWebAppApp'));
7 |
8 | var MainCtrl,
9 | scope;
10 |
11 | // Initialize the controller and a mock scope
12 | beforeEach(inject(function ($controller, $rootScope) {
13 | scope = $rootScope.$new();
14 | MainCtrl = $controller('MainCtrl', {
15 | $scope: scope
16 | });
17 | }));
18 |
19 | it('should attach a list of awesomeThings to the scope', function () {
20 | expect(scope.awesomeThings.length).toBe(3);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------