├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── LICENSE ├── README.md ├── angular-object-diff.js ├── angular-object-diff.less ├── bower.json ├── demo.js ├── dist ├── angular-object-diff.css ├── angular-object-diff.css.map ├── angular-object-diff.js ├── angular-object-diff.js.tar.gz ├── angular-object-diff.min.js └── angular-object-diff.min.js.map ├── gulpfile.js ├── index.html ├── package.json └── screenshot.png /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Tabs in JS unless otherwise specified 13 | [**.js] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | test/fixtures/shots/ 4 | bower_components/ 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": [ 3 | "try", 4 | "catch", 5 | "do" 6 | ], 7 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 8 | "requireCapitalizedConstructors": true, 9 | "validateIndentation": 2, 10 | "validateQuoteMarks": "'", 11 | 12 | "disallowQuotedKeysInObjects": true, 13 | "disallowSpaceAfterObjectKeys": true, 14 | 15 | "requireSpaceBeforeBinaryOperators": [ 16 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 17 | "&=", "|=", "^=", "+=", 18 | 19 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 20 | "|", "^", "&&", "||", "===", "==", ">=", 21 | "<=", "<", ">", "!=", "!==" 22 | ], 23 | "requireSpaceAfterBinaryOperators": true, 24 | "requireSpacesInConditionalExpression": true, 25 | "requireSpaceBeforeBlockStatements": true, 26 | "requireSpacesInForStatement": true, 27 | "requireLineFeedAtFileEnd": true, 28 | "requireSpacesInFunctionExpression": { 29 | "beforeOpeningCurlyBrace": true 30 | }, 31 | "requireDotNotation": true, 32 | "disallowSpacesInsideArrayBrackets": "all", 33 | "disallowSpacesInsideParentheses": true, 34 | 35 | 36 | "validateJSDoc": { 37 | "checkParamNames": true, 38 | "requireParamTypes": true 39 | }, 40 | 41 | "disallowMultipleLineBreaks": true, 42 | "disallowNewlineBeforeBlockStatements": true, 43 | "disallowKeywords": [ "with" ], 44 | 45 | "excludeFiles": [ 46 | "bower_components/**", 47 | "node_modules/**", 48 | "dist/**", 49 | "test/coverage/**", 50 | "examples/smoothscroll.min.js" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | dist/ 4 | tmp/ 5 | examples/ 6 | 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 3 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 4 | "unused" : true, // true: Require all defined variables be used 5 | "noempty" : true, // Prohibit use of empty blocks 6 | "trailing" : true, // Prohibit trailing whitespaces. 7 | "white" : false, // Check against strict whitespace and indentation rules. 8 | "indent" : 2, // {int} Number of spaces to use for indentation 9 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 10 | "quotmark" : "single", // Quotation mark consistency 11 | "-W058" : true, // Missing '()' invoking a constructor 12 | "browser" : true, // Standard browser globals e.g. `window`, `document`. 13 | "predef" : [ // Custom globals. 14 | "angular", 15 | "G_vmlCanvasManager", 16 | "require", 17 | "console" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Deepu k Sasidharan 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 | # angular-diff 2 | An Angular JS plugin to compare and show object differences in JSON format. [Demo](http://hipster-labs.github.io/angular-object-diff/) 3 | 4 |  5 | # Installation 6 | 7 | with bower 8 | ``` 9 | bower install angular-object-diff --save 10 | ``` 11 | 12 | ``` 13 | 14 | 15 | ``` 16 | 17 | or with npm 18 | ``` 19 | npm i angular-object-diff 20 | ``` 21 | 22 | # Available methods on `ObjectDiff` service 23 | 24 | 25 | `setOpenChar`: set the opening character for the view, default is `{` 26 | 27 | `setCloseChar`: set the closing character for the view, default is `}` 28 | 29 | `diff`: compare and build all the difference of two objects including prototype properties 30 | 31 | `diffOwnProperties`: compare and build the difference of two objects taking only its own properties into account 32 | 33 | `toJsonView`: format a diff object to a full JSON formatted object view 34 | 35 | `toJsonDiffView`: format a diff object to a JSON formatted view with only changes 36 | 37 | `objToJsonView`: format any javascript object to a JSON formatted view 38 | 39 | 40 | # Available filters 41 | 42 | `toJsonView`: format a diff object to a full JSON formatted object view 43 | 44 | `toJsonDiffView`: format a diff object to a JSON formatted view with only changes 45 | 46 | `objToJsonView`: format any javascript object to a JSON formatted view 47 | 48 | 49 | # Usage 50 | 51 | Declare the dependency 52 | ``` 53 | angular.module('myModule', ['ds.objectDiff']); 54 | 55 | ``` 56 | 57 | Inject the service 58 | 59 | ```javascript 60 | angular.module('myModule') 61 | .controller('MyController', ['$scope', 'ObjectDiff', function($scope, ObjectDiff){ 62 | $scope.yourObjectOne = {//all your object attributes and values here}; 63 | $scope.yourObjectTwo = {//all your object attributes and values here}; 64 | 65 | // This is required only if you want to show a JSON formatted view of your object without using a filter 66 | $scope.yourObjectOneJsonView = ObjectDiff.objToJsonView($scope.yourObjectOne); 67 | $scope.yourObjectTwoJsonView = ObjectDiff.objToJsonView($scope.yourObjectTwo); 68 | 69 | // you can directly diff your objects js now or parse a Json to object and diff 70 | var diff = ObjectDiff.diffOwnProperties($scope.yourObjectOne, $scope.yourObjectTwo); 71 | 72 | // you can directly diff your objects including prototype properties and inherited properties using `diff` method 73 | var diffAll = ObjectDiff.diff($scope.yourObjectOne, $scope.yourObjectTwo); 74 | 75 | // gives a full object view with Diff highlighted 76 | $scope.diffValue = ObjectDiff.toJsonView(diff); 77 | 78 | // gives object view with onlys Diff highlighted 79 | $scope.diffValueChanges = ObjectDiff.toJsonDiffView(diff); 80 | 81 | }]); 82 | ``` 83 | 84 | Bind the variables directly in your html using the `ng-bind-html` angular directive. 85 | Use a `
` element for better results 86 | 87 | ```html 88 | 89 | 90 | 91 | 92 | ``` 93 | 94 | The same can be done with filters as well 95 | 96 | ```javascript 97 | angular.module('myModule') 98 | .controller('MyController', ['$scope', 'ObjectDiff', function($scope, ObjectDiff){ 99 | $scope.yourObjectOne = {//all your object attributes and values here}; 100 | $scope.yourObjectTwo = {//all your object attributes and values here}; 101 | 102 | // you can directly diff your objects js now or parse a Json to object and diff 103 | var diff = ObjectDiff.diffOwnProperties($scope.yourObjectOne, $scope.yourObjectTwo); 104 | 105 | // you can directly diff your objects including prototype properties and inherited properties using `diff` method 106 | var diffAll = ObjectDiff.diff($scope.yourObjectOne, $scope.yourObjectTwo); 107 | 108 | }]); 109 | ``` 110 | 111 | Bind the variables directly in your html using the `ng-bind-html` angular directive. 112 | Use a `` element for better results 113 | 114 | ```html 115 | 116 | 117 | 118 | 119 | ``` 120 | 121 | Inspired from https://github.com/NV/objectDiff.js 122 | -------------------------------------------------------------------------------- /angular-object-diff.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ds.objectDiff', []) 6 | .factory('ObjectDiff', objectDiff) 7 | .filter('toJsonView', toJsonViewFilter) 8 | .filter('toJsonDiffView', toJsonDiffViewFilter) 9 | .filter('objToJsonView', objToJsonViewFilter); 10 | 11 | objectDiff.$inject = ['$sce']; 12 | toJsonViewFilter.$inject = ['ObjectDiff']; 13 | toJsonDiffViewFilter.$inject = ['ObjectDiff']; 14 | objToJsonViewFilter.$inject = ['ObjectDiff']; 15 | 16 | /* service implementation */ 17 | function objectDiff($sce) { 18 | 19 | var openChar = '{', 20 | closeChar = '}', 21 | service = { 22 | setOpenChar: setOpenChar, 23 | setCloseChar: setCloseChar, 24 | diff: diff, 25 | diffOwnProperties: diffOwnProperties, 26 | toJsonView: formatToJsonXMLString, 27 | objToJsonView: formatObjToJsonXMLString, 28 | toJsonDiffView: formatChangesToXMLString 29 | }; 30 | 31 | return service; 32 | 33 | 34 | /* service methods */ 35 | 36 | /** 37 | * @param char 38 | */ 39 | function setOpenChar(char) { 40 | openChar = char; 41 | } 42 | 43 | /** 44 | * @param char 45 | */ 46 | function setCloseChar(char) { 47 | closeChar = char; 48 | } 49 | 50 | /** 51 | * diff between object a and b 52 | * @param {Object} a 53 | * @param {Object} b 54 | * @param shallow 55 | * @param isOwn 56 | * @return {Object} 57 | */ 58 | function diff(a, b, shallow, isOwn) { 59 | 60 | if (a === b) { 61 | return equalObj(a); 62 | } 63 | 64 | var diffValue = {}; 65 | var equal = true; 66 | 67 | for (var key in a) { 68 | if ((!isOwn && key in b) || (isOwn && typeof b != 'undefined' && b.hasOwnProperty(key))) { 69 | if (a[key] === b[key]) { 70 | diffValue[key] = equalObj(a[key]); 71 | } else { 72 | if (!shallow && isValidAttr(a[key], b[key])) { 73 | var valueDiff = diff(a[key], b[key], shallow, isOwn); 74 | if (valueDiff.changed == 'equal') { 75 | diffValue[key] = equalObj(a[key]); 76 | } else { 77 | equal = false; 78 | diffValue[key] = valueDiff; 79 | } 80 | } else { 81 | equal = false; 82 | diffValue[key] = { 83 | changed: 'primitive change', 84 | removed: a[key], 85 | added: b[key] 86 | } 87 | } 88 | } 89 | } else { 90 | equal = false; 91 | diffValue[key] = { 92 | changed: 'removed', 93 | value: a[key] 94 | } 95 | } 96 | } 97 | 98 | for (key in b) { 99 | if ((!isOwn && !(key in a)) || (isOwn && typeof a != 'undefined' && !a.hasOwnProperty(key))) { 100 | equal = false; 101 | diffValue[key] = { 102 | changed: 'added', 103 | value: b[key] 104 | } 105 | } 106 | } 107 | 108 | if (equal) { 109 | return equalObj(a); 110 | } else { 111 | return { 112 | changed: 'object change', 113 | value: diffValue 114 | } 115 | } 116 | } 117 | 118 | 119 | /** 120 | * diff between object a and b own properties only 121 | * @param {Object} a 122 | * @param {Object} b 123 | * @return {Object} 124 | * @param deep 125 | */ 126 | function diffOwnProperties(a, b, shallow) { 127 | return diff(a, b, shallow, true); 128 | } 129 | 130 | /** 131 | * Convert to a readable xml/html Json structure 132 | * @param {Object} changes 133 | * @return {string} 134 | * @param shallow 135 | */ 136 | function formatToJsonXMLString(changes, shallow) { 137 | var properties = []; 138 | 139 | var diff = changes.value; 140 | if (changes.changed == 'equal') { 141 | return $sce.trustAsHtml(inspect(diff, shallow)); 142 | } 143 | 144 | for (var key in diff) { 145 | properties.push(formatChange(key, diff[key], shallow)); 146 | } 147 | 148 | return $sce.trustAsHtml('' + openChar + '\n' + properties.join(',\n') + '\n' + closeChar + ''); 149 | 150 | } 151 | 152 | /** 153 | * Convert to a readable xml/html Json structure 154 | * @return {string} 155 | * @param obj 156 | * @param shallow 157 | */ 158 | function formatObjToJsonXMLString(obj, shallow) { 159 | return $sce.trustAsHtml(inspect(obj, shallow)); 160 | } 161 | 162 | /** 163 | * Convert to a readable xml/html Json structure 164 | * @param {Object} changes 165 | * @return {string} 166 | * @param shallow 167 | */ 168 | function formatChangesToXMLString(changes, shallow) { 169 | var properties = []; 170 | 171 | if (changes.changed == 'equal') { 172 | return ''; 173 | } 174 | 175 | var diff = changes.value; 176 | 177 | for (var key in diff) { 178 | var changed = diff[key].changed; 179 | if (changed !== 'equal') 180 | properties.push(formatChange(key, diff[key], shallow, true)); 181 | } 182 | 183 | return $sce.trustAsHtml('' + openChar + '\n' + properties.join(',\n') + '\n' + closeChar + ''); 184 | 185 | } 186 | 187 | /** 188 | * @param obj 189 | * @returns {{changed: string, value: *}} 190 | */ 191 | function equalObj(obj) { 192 | return { 193 | changed: 'equal', 194 | value: obj 195 | } 196 | } 197 | 198 | /** 199 | * @param a 200 | * @param b 201 | * @returns {*|boolean} 202 | */ 203 | function isValidAttr(a, b) { 204 | var typeA = typeof a; 205 | var typeB = typeof b; 206 | return (a && b && (typeA == 'object' || typeA == 'function') && (typeB == 'object' || typeB == 'function')); 207 | } 208 | 209 | /** 210 | * @param key 211 | * @param diffItem 212 | * @returns {*} 213 | * @param shallow 214 | * @param diffOnly 215 | */ 216 | function formatChange(key, diffItem, shallow, diffOnly) { 217 | var changed = diffItem.changed; 218 | var property; 219 | switch (changed) { 220 | case 'equal': 221 | property = (stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value)); 222 | break; 223 | 224 | case 'removed': 225 | property = ('' + stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value) + ''); 226 | break; 227 | 228 | case 'added': 229 | property = ('' + stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value) + ''); 230 | break; 231 | 232 | case 'primitive change': 233 | var prefix = stringifyObjectKey(escapeHTML(key)) + ': '; 234 | property = ( 235 | '' + prefix + inspect(diffItem.removed) + ',\n' + 236 | '' + prefix + inspect(diffItem.added) + ''); 237 | break; 238 | 239 | case 'object change': 240 | property = shallow ? '' : (stringifyObjectKey(key) + ': ' + ( diffOnly ? formatChangesToXMLString(diffItem) : formatToJsonXMLString(diffItem))); 241 | break; 242 | } 243 | 244 | return property; 245 | } 246 | 247 | /** 248 | * @param {string} key 249 | * @return {string} 250 | */ 251 | function stringifyObjectKey(key) { 252 | return /^[a-z0-9_$]*$/i.test(key) ? 253 | key : 254 | JSON.stringify(key); 255 | } 256 | 257 | /** 258 | * @param {string} string 259 | * @return {string} 260 | */ 261 | function escapeHTML(string) { 262 | return string.replace(/&/g, '&').replace(//g, '>'); 263 | } 264 | 265 | /** 266 | * @param {Object} obj 267 | * @return {string} 268 | * @param shallow 269 | */ 270 | function inspect(obj, shallow) { 271 | 272 | return _inspect('', obj, shallow); 273 | 274 | /** 275 | * @param {string} accumulator 276 | * @param {object} obj 277 | * @see http://jsperf.com/continuation-passing-style/3 278 | * @return {string} 279 | * @param shallow 280 | */ 281 | function _inspect(accumulator, obj, shallow) { 282 | switch (typeof obj) { 283 | case 'object': 284 | if (!obj) { 285 | accumulator += 'null'; 286 | break; 287 | } 288 | if (shallow) { 289 | accumulator += '[object]'; 290 | break; 291 | } 292 | var keys = Object.keys(obj); 293 | var length = keys.length; 294 | if (length === 0) { 295 | accumulator += '' + openChar + closeChar + ''; 296 | } else { 297 | accumulator += '' + openChar + '\n'; 298 | for (var i = 0; i < length; i++) { 299 | var key = keys[i]; 300 | accumulator = _inspect(accumulator + stringifyObjectKey(escapeHTML(key)) + ': ', obj[key]); 301 | if (i < length - 1) { 302 | accumulator += ',\n'; 303 | } 304 | } 305 | accumulator += '\n' + closeChar + '' 306 | } 307 | break; 308 | 309 | case 'string': 310 | accumulator += JSON.stringify(escapeHTML(obj)); 311 | break; 312 | 313 | case 'undefined': 314 | accumulator += 'undefined'; 315 | break; 316 | 317 | default: 318 | accumulator += escapeHTML(String(obj)); 319 | break; 320 | } 321 | return accumulator; 322 | } 323 | } 324 | } 325 | 326 | /* filter implementation */ 327 | function toJsonViewFilter(ObjectDiff) { 328 | return function (value) { 329 | return ObjectDiff.toJsonView(value); 330 | }; 331 | } 332 | 333 | function toJsonDiffViewFilter(ObjectDiff) { 334 | return function (value) { 335 | return ObjectDiff.toJsonDiffView(value); 336 | }; 337 | } 338 | 339 | function objToJsonViewFilter(ObjectDiff) { 340 | return function (value) { 341 | return ObjectDiff.objToJsonView(value); 342 | }; 343 | } 344 | })(); 345 | -------------------------------------------------------------------------------- /angular-object-diff.less: -------------------------------------------------------------------------------- 1 | .diff { 2 | display: inline-block; 3 | } 4 | .diff-level { 5 | margin-left: 1.6em; 6 | } 7 | .diff-holder { 8 | color: #666; 9 | margin: 0; 10 | } 11 | .diff-holder span { 12 | color: #AAA; 13 | } 14 | del.diff { 15 | text-decoration: none; 16 | color: #b30000; 17 | background: #fadad7; 18 | } 19 | ins.diff { 20 | background: #eaf2c2; 21 | color: #406619; 22 | text-decoration: none; 23 | } 24 | del.diff-key { 25 | border: 1px solid #f8a4a4; 26 | } 27 | ins.diff-key { 28 | border: 1px solid #a3ce4c; 29 | margin-top: -1px; 30 | position: relative; 31 | } 32 | ins.diff span { 33 | color: #AABF40; 34 | } 35 | del.diff span { 36 | color: #EE8177; 37 | } 38 | .audit-obj { 39 | max-height: 300px; 40 | max-width: 300px; 41 | overflow: auto; 42 | } 43 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-object-diff", 3 | "version": "1.0.3", 4 | "main": [ 5 | "./dist/angular-object-diff.js", 6 | "./dist/angular-object-diff.css" 7 | ], 8 | "authors": [ 9 | "Deepu K Sasidharan" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/hipster-labs/angular-object-diff.git" 14 | }, 15 | "description": "An Angular JS plugin to compare and show object differences.", 16 | "moduleType": [ 17 | "globals" 18 | ], 19 | "keywords": [ 20 | "angular", 21 | "angular.js", 22 | "object", 23 | "diff", 24 | "json", 25 | "json-diff", 26 | "object-diff", 27 | "compare" 28 | ], 29 | "license": "MIT", 30 | "ignore": [ 31 | "**/.*", 32 | "node_modules", 33 | "bower_components", 34 | "test", 35 | "tests" 36 | ], 37 | "dependencies": { 38 | "angular": "1.x" 39 | }, 40 | "devDependencies": { 41 | "angular-bootstrap": "~0.11.0", 42 | "font-awesome": "~4.1.0", 43 | "rainbow": "~1.1.9", 44 | "angular-mocks": "~1.3.10" 45 | }, 46 | "resolutions": { 47 | "angular": "1.x", 48 | "angular-mocks": "1.3.10" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /demo.js: -------------------------------------------------------------------------------- 1 | // Angular JS code 2 | (function () { 3 | 'use-strict'; 4 | 5 | angular.module('demoApp', ['ds.objectDiff']) 6 | .config([ 7 | '$interpolateProvider', 8 | function ($interpolateProvider) { 9 | return $interpolateProvider.startSymbol('{(').endSymbol(')}'); 10 | } 11 | ]) 12 | .controller('DemoController', DemoController); 13 | 14 | DemoController.$inject = ['$scope', 'ObjectDiff']; 15 | 16 | function DemoController($scope, ObjectDiff) { 17 | $scope.objectOne = "{\n" + 18 | " a: {\n" + 19 | " b: 1,\n" + 20 | " c: [1, 2]\n" + 21 | " },\n" + 22 | " \"2b\": {\n" + 23 | " foo: 'bar'\n" + 24 | " }\n" + 25 | " }"; 26 | $scope.objectTwo = "{\n" + 27 | " a: { \n" + 28 | " b: 2,\n" + 29 | " c: [1, 2, 3]\n" + 30 | " },\n" + 31 | " x: 1\n" + 32 | " }"; 33 | 34 | 35 | function makeDiff() { 36 | var objectOne, objectTwo, diff; 37 | try { 38 | $scope.errorA = false; 39 | objectOne = eval('(' + $scope.objectOne + ')'); //JSON.parse($scope.objectOne); 40 | } catch (err) { 41 | $scope.errorA = true; 42 | } 43 | try { 44 | $scope.errorB = false; 45 | objectTwo = eval('(' + $scope.objectTwo + ')'); //JSON.parse($scope.objectTwo); 46 | } catch (err) { 47 | $scope.errorB = true; 48 | } 49 | 50 | // you can directly diff your objects if they are not string 51 | diff = ObjectDiff.diffOwnProperties(objectOne, objectTwo); 52 | 53 | $scope.diffValue = ObjectDiff.toJsonView(diff); 54 | $scope.diffValueChanges = ObjectDiff.toJsonDiffView(diff); 55 | 56 | $scope.yourObjectOne = objectOne; 57 | $scope.yourObjectTwo = objectTwo; 58 | } 59 | 60 | $scope.$watch('objectOne', function (newValue, oldValue) { 61 | makeDiff(); 62 | }); 63 | $scope.$watch('objectTwo', function (newValue, oldValue) { 64 | makeDiff(); 65 | }); 66 | } 67 | 68 | })(); 69 | -------------------------------------------------------------------------------- /dist/angular-object-diff.css: -------------------------------------------------------------------------------- 1 | .diff{display:inline-block}.diff-level{margin-left:1.6em}.diff-holder{color:#666;margin:0}.diff-holder span{color:#AAA}del.diff{text-decoration:none;color:#b30000;background:#fadad7}ins.diff{background:#eaf2c2;color:#406619;text-decoration:none}del.diff-key{border:1px solid #f8a4a4}ins.diff-key{border:1px solid #a3ce4c;margin-top:-1px;position:relative}ins.diff span{color:#AABF40}del.diff span{color:#EE8177}.audit-obj{max-height:300px;max-width:300px;overflow:auto} 2 | /*# sourceMappingURL=angular-object-diff.css.map */ 3 | -------------------------------------------------------------------------------- /dist/angular-object-diff.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["angular-object-diff.less"],"names":[],"mappings":"AAAA;EACC,qBAAA;;AAED;EACC,kBAAA;;AAED;EACC,WAAA;EACA,SAAA;;AAED,YAAa;EACZ,WAAA;;AAED,GAAG;EACF,qBAAA;EACA,cAAA;EACA,mBAAA;;AAED,GAAG;EACF,mBAAA;EACA,cAAA;EACA,qBAAA;;AAED,GAAG;EACF,yBAAA;;AAED,GAAG;EACF,yBAAA;EACA,gBAAA;EACA,kBAAA;;AAED,GAAG,KAAM;EACR,cAAA;;AAED,GAAG,KAAM;EACR,cAAA;;AAED;EACI,iBAAA;EACA,gBAAA;EACA,cAAA","file":"angular-object-diff.css","sourcesContent":[".diff {\r\n\tdisplay: inline-block;\r\n}\r\n.diff-level {\r\n\tmargin-left: 1.6em;\r\n}\r\n.diff-holder {\r\n\tcolor: #666;\r\n\tmargin: 0;\r\n}\r\n.diff-holder span {\r\n\tcolor: #AAA;\r\n}\r\ndel.diff {\r\n\ttext-decoration: none;\r\n\tcolor: #b30000;\r\n\tbackground: #fadad7;\r\n}\r\nins.diff {\r\n\tbackground: #eaf2c2;\r\n\tcolor: #406619;\r\n\ttext-decoration: none;\r\n}\r\ndel.diff-key {\r\n\tborder: 1px solid #f8a4a4;\r\n}\r\nins.diff-key {\r\n\tborder: 1px solid #a3ce4c;\r\n\tmargin-top: -1px;\r\n\tposition: relative;\r\n}\r\nins.diff span {\r\n\tcolor: #AABF40;\r\n}\r\ndel.diff span {\r\n\tcolor: #EE8177;\r\n}\r\n.audit-obj {\r\n max-height: 300px;\r\n max-width: 300px;\r\n overflow: auto;\r\n}\r\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /dist/angular-object-diff.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ds.objectDiff', []) 6 | .factory('ObjectDiff', objectDiff) 7 | .filter('toJsonView', toJsonViewFilter) 8 | .filter('toJsonDiffView', toJsonDiffViewFilter) 9 | .filter('objToJsonView', objToJsonViewFilter); 10 | 11 | objectDiff.$inject = ['$sce']; 12 | toJsonViewFilter.$inject = ['ObjectDiff']; 13 | toJsonDiffViewFilter.$inject = ['ObjectDiff']; 14 | objToJsonViewFilter.$inject = ['ObjectDiff']; 15 | 16 | /* service implementation */ 17 | function objectDiff($sce) { 18 | 19 | var openChar = '{', 20 | closeChar = '}', 21 | service = { 22 | setOpenChar: setOpenChar, 23 | setCloseChar: setCloseChar, 24 | diff: diff, 25 | diffOwnProperties: diffOwnProperties, 26 | toJsonView: formatToJsonXMLString, 27 | objToJsonView: formatObjToJsonXMLString, 28 | toJsonDiffView: formatChangesToXMLString 29 | }; 30 | 31 | return service; 32 | 33 | 34 | /* service methods */ 35 | 36 | /** 37 | * @param char 38 | */ 39 | function setOpenChar(char) { 40 | openChar = char; 41 | } 42 | 43 | /** 44 | * @param char 45 | */ 46 | function setCloseChar(char) { 47 | closeChar = char; 48 | } 49 | 50 | /** 51 | * diff between object a and b 52 | * @param {Object} a 53 | * @param {Object} b 54 | * @param shallow 55 | * @param isOwn 56 | * @return {Object} 57 | */ 58 | function diff(a, b, shallow, isOwn) { 59 | 60 | if (a === b) { 61 | return equalObj(a); 62 | } 63 | 64 | var diffValue = {}; 65 | var equal = true; 66 | 67 | for (var key in a) { 68 | if ((!isOwn && key in b) || (isOwn && typeof b != 'undefined' && b.hasOwnProperty(key))) { 69 | if (a[key] === b[key]) { 70 | diffValue[key] = equalObj(a[key]); 71 | } else { 72 | if (!shallow && isValidAttr(a[key], b[key])) { 73 | var valueDiff = diff(a[key], b[key], shallow, isOwn); 74 | if (valueDiff.changed == 'equal') { 75 | diffValue[key] = equalObj(a[key]); 76 | } else { 77 | equal = false; 78 | diffValue[key] = valueDiff; 79 | } 80 | } else { 81 | equal = false; 82 | diffValue[key] = { 83 | changed: 'primitive change', 84 | removed: a[key], 85 | added: b[key] 86 | } 87 | } 88 | } 89 | } else { 90 | equal = false; 91 | diffValue[key] = { 92 | changed: 'removed', 93 | value: a[key] 94 | } 95 | } 96 | } 97 | 98 | for (key in b) { 99 | if ((!isOwn && !(key in a)) || (isOwn && typeof a != 'undefined' && !a.hasOwnProperty(key))) { 100 | equal = false; 101 | diffValue[key] = { 102 | changed: 'added', 103 | value: b[key] 104 | } 105 | } 106 | } 107 | 108 | if (equal) { 109 | return equalObj(a); 110 | } else { 111 | return { 112 | changed: 'object change', 113 | value: diffValue 114 | } 115 | } 116 | } 117 | 118 | 119 | /** 120 | * diff between object a and b own properties only 121 | * @param {Object} a 122 | * @param {Object} b 123 | * @return {Object} 124 | * @param deep 125 | */ 126 | function diffOwnProperties(a, b, shallow) { 127 | return diff(a, b, shallow, true); 128 | } 129 | 130 | /** 131 | * Convert to a readable xml/html Json structure 132 | * @param {Object} changes 133 | * @return {string} 134 | * @param shallow 135 | */ 136 | function formatToJsonXMLString(changes, shallow) { 137 | var properties = []; 138 | 139 | var diff = changes.value; 140 | if (changes.changed == 'equal') { 141 | return $sce.trustAsHtml(inspect(diff, shallow)); 142 | } 143 | 144 | for (var key in diff) { 145 | properties.push(formatChange(key, diff[key], shallow)); 146 | } 147 | 148 | return $sce.trustAsHtml('' + openChar + '\n ' + properties.join(',\n') + '\n' + closeChar + ''); 149 | 150 | } 151 | 152 | /** 153 | * Convert to a readable xml/html Json structure 154 | * @return {string} 155 | * @param obj 156 | * @param shallow 157 | */ 158 | function formatObjToJsonXMLString(obj, shallow) { 159 | return $sce.trustAsHtml(inspect(obj, shallow)); 160 | } 161 | 162 | /** 163 | * Convert to a readable xml/html Json structure 164 | * @param {Object} changes 165 | * @return {string} 166 | * @param shallow 167 | */ 168 | function formatChangesToXMLString(changes, shallow) { 169 | var properties = []; 170 | 171 | if (changes.changed == 'equal') { 172 | return ''; 173 | } 174 | 175 | var diff = changes.value; 176 | 177 | for (var key in diff) { 178 | var changed = diff[key].changed; 179 | if (changed !== 'equal') 180 | properties.push(formatChange(key, diff[key], shallow, true)); 181 | } 182 | 183 | return $sce.trustAsHtml('' + openChar + '\n' + properties.join(',\n') + '\n' + closeChar + ''); 184 | 185 | } 186 | 187 | /** 188 | * @param obj 189 | * @returns {{changed: string, value: *}} 190 | */ 191 | function equalObj(obj) { 192 | return { 193 | changed: 'equal', 194 | value: obj 195 | } 196 | } 197 | 198 | /** 199 | * @param a 200 | * @param b 201 | * @returns {*|boolean} 202 | */ 203 | function isValidAttr(a, b) { 204 | var typeA = typeof a; 205 | var typeB = typeof b; 206 | return (a && b && (typeA == 'object' || typeA == 'function') && (typeB == 'object' || typeB == 'function')); 207 | } 208 | 209 | /** 210 | * @param key 211 | * @param diffItem 212 | * @returns {*} 213 | * @param shallow 214 | * @param diffOnly 215 | */ 216 | function formatChange(key, diffItem, shallow, diffOnly) { 217 | var changed = diffItem.changed; 218 | var property; 219 | switch (changed) { 220 | case 'equal': 221 | property = (stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value)); 222 | break; 223 | 224 | case 'removed': 225 | property = ('' + stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value) + ''); 226 | break; 227 | 228 | case 'added': 229 | property = ('' + stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value) + ''); 230 | break; 231 | 232 | case 'primitive change': 233 | var prefix = stringifyObjectKey(escapeHTML(key)) + ': '; 234 | property = ( 235 | '' + prefix + inspect(diffItem.removed) + ',\n' + 236 | '' + prefix + inspect(diffItem.added) + ''); 237 | break; 238 | 239 | case 'object change': 240 | property = shallow ? '' : (stringifyObjectKey(key) + ': ' + ( diffOnly ? formatChangesToXMLString(diffItem) : formatToJsonXMLString(diffItem))); 241 | break; 242 | } 243 | 244 | return property; 245 | } 246 | 247 | /** 248 | * @param {string} key 249 | * @return {string} 250 | */ 251 | function stringifyObjectKey(key) { 252 | return /^[a-z0-9_$]*$/i.test(key) ? 253 | key : 254 | JSON.stringify(key); 255 | } 256 | 257 | /** 258 | * @param {string} string 259 | * @return {string} 260 | */ 261 | function escapeHTML(string) { 262 | return string.replace(/&/g, '&').replace(//g, '>'); 263 | } 264 | 265 | /** 266 | * @param {Object} obj 267 | * @return {string} 268 | * @param shallow 269 | */ 270 | function inspect(obj, shallow) { 271 | 272 | return _inspect('', obj, shallow); 273 | 274 | /** 275 | * @param {string} accumulator 276 | * @param {object} obj 277 | * @see http://jsperf.com/continuation-passing-style/3 278 | * @return {string} 279 | * @param shallow 280 | */ 281 | function _inspect(accumulator, obj, shallow) { 282 | switch (typeof obj) { 283 | case 'object': 284 | if (!obj) { 285 | accumulator += 'null'; 286 | break; 287 | } 288 | if (shallow) { 289 | accumulator += '[object]'; 290 | break; 291 | } 292 | var keys = Object.keys(obj); 293 | var length = keys.length; 294 | if (length === 0) { 295 | accumulator += '' + openChar + closeChar + ''; 296 | } else { 297 | accumulator += '' + openChar + '\n'; 298 | for (var i = 0; i < length; i++) { 299 | var key = keys[i]; 300 | accumulator = _inspect(accumulator + stringifyObjectKey(escapeHTML(key)) + ': ', obj[key]); 301 | if (i < length - 1) { 302 | accumulator += ',\n'; 303 | } 304 | } 305 | accumulator += '\n' + closeChar + '' 306 | } 307 | break; 308 | 309 | case 'string': 310 | accumulator += JSON.stringify(escapeHTML(obj)); 311 | break; 312 | 313 | case 'undefined': 314 | accumulator += 'undefined'; 315 | break; 316 | 317 | default: 318 | accumulator += escapeHTML(String(obj)); 319 | break; 320 | } 321 | return accumulator; 322 | } 323 | } 324 | } 325 | 326 | /* filter implementation */ 327 | function toJsonViewFilter(ObjectDiff) { 328 | return function (value) { 329 | return ObjectDiff.toJsonView(value); 330 | }; 331 | } 332 | 333 | function toJsonDiffViewFilter(ObjectDiff) { 334 | return function (value) { 335 | return ObjectDiff.toJsonDiffView(value); 336 | }; 337 | } 338 | 339 | function objToJsonViewFilter(ObjectDiff) { 340 | return function (value) { 341 | return ObjectDiff.objToJsonView(value); 342 | }; 343 | } 344 | })(); 345 | -------------------------------------------------------------------------------- /dist/angular-object-diff.js.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hipster-labs/angular-object-diff/4299d0680d2aa276ab52cc7e3654df8b2689c521/dist/angular-object-diff.js.tar.gz -------------------------------------------------------------------------------- /dist/angular-object-diff.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function n(n){function e(n){v=n}function a(n){b=n}function t(n,e,a,i){if(n===e)return c(n);var r={},s=!0;for(var f in n)if(!i&&f in e||i&&"undefined"!=typeof e&&e.hasOwnProperty(f))if(n[f]===e[f])r[f]=c(n[f]);else if(!a&&u(n[f],e[f])){var o=t(n[f],e[f],a,i);"equal"==o.changed?r[f]=c(n[f]):(s=!1,r[f]=o)}else s=!1,r[f]={changed:"primitive change",removed:n[f],added:e[f]};else s=!1,r[f]={changed:"removed",value:n[f]};for(f in e)(!i&&!(f in n)||i&&"undefined"!=typeof n&&!n.hasOwnProperty(f))&&(s=!1,r[f]={changed:"added",value:e[f]});return s?c(n):{changed:"object change",value:r}}function i(n,e,a){return t(n,e,a,!0)}function r(e,a){var t=[],i=e.value;if("equal"==e.changed)return n.trustAsHtml(p(i,a));for(var r in i)t.push(o(r,i[r],a));return n.trustAsHtml(""+v+'\n'+t.join(",\n")+"\n"+b+"")}function s(e,a){return n.trustAsHtml(p(e,a))}function f(e,a){var t=[];if("equal"==e.changed)return"";var i=e.value;for(var r in i){var s=i[r].changed;"equal"!==s&&t.push(o(r,i[r],a,!0))}return n.trustAsHtml(""+v+'\n'+t.join(",\n")+"\n"+b+"")}function c(n){return{changed:"equal",value:n}}function u(n,e){var a=typeof n,t=typeof e;return n&&e&&("object"==a||"function"==a)&&("object"==t||"function"==t)}function o(n,e,a,t){var i,s=e.changed;switch(s){case"equal":i=d(l(n))+": "+p(e.value);break;case"removed":i=''+d(l(n))+": "+p(e.value)+"";break;case"added":i=''+d(l(n))+": "+p(e.value)+"";break;case"primitive change":var c=d(l(n))+": ";i=''+c+p(e.removed)+',\n'+c+p(e.added)+"";break;case"object change":i=a?"":d(n)+": "+(t?f(e):r(e))}return i}function d(n){return/^[a-z0-9_$]*$/i.test(n)?n:JSON.stringify(n)}function l(n){return n.replace(/&/g,"&").replace(//g,">")}function p(n,e){function a(n,e,t){switch(typeof e){case"object":if(!e){n+="null";break}if(t){n+="[object]";break}var i=Object.keys(e),r=i.length;if(0===r)n+=""+v+b+"";else{n+=""+v+'\n';for(var s=0;r>s;s++){var f=i[s];n=a(n+d(l(f))+": ",e[f]),r-1>s&&(n+=",\n")}n+="\n"+b+""}break;case"string":n+=JSON.stringify(l(e));break;case"undefined":n+="undefined";break;default:n+=l(String(e))}return n}return a("",n,e)}var v="{",b="}",g={setOpenChar:e,setCloseChar:a,diff:t,diffOwnProperties:i,toJsonView:r,objToJsonView:s,toJsonDiffView:f};return g}function e(n){return function(e){return n.toJsonView(e)}}function a(n){return function(e){return n.toJsonDiffView(e)}}function t(n){return function(e){return n.objToJsonView(e)}}angular.module("ds.objectDiff",[]).factory("ObjectDiff",n).filter("toJsonView",e).filter("toJsonDiffView",a).filter("objToJsonView",t),n.$inject=["$sce"],e.$inject=["ObjectDiff"],a.$inject=["ObjectDiff"],t.$inject=["ObjectDiff"]}(); 2 | //# sourceMappingURL=angular-object-diff.min.js.map 3 | -------------------------------------------------------------------------------- /dist/angular-object-diff.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["angular-object-diff.min.js"],"names":["objectDiff","$sce","setOpenChar","char","openChar","setCloseChar","closeChar","diff","a","b","shallow","isOwn","equalObj","diffValue","equal","key","hasOwnProperty","isValidAttr","valueDiff","changed","removed","added","value","diffOwnProperties","formatToJsonXMLString","changes","properties","trustAsHtml","inspect","push","formatChange","join","formatObjToJsonXMLString","obj","formatChangesToXMLString","typeA","typeB","diffItem","diffOnly","property","stringifyObjectKey","escapeHTML","prefix","test","JSON","stringify","string","replace","_inspect","accumulator","keys","Object","length","i","String","service","toJsonView","objToJsonView","toJsonDiffView","toJsonViewFilter","ObjectDiff","toJsonDiffViewFilter","objToJsonViewFilter","angular","module","factory","filter","$inject"],"mappings":"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","file":"angular-object-diff.min.js","sourcesContent":["(function () {\n 'use strict';\n\n angular\n .module('ds.objectDiff', [])\n .factory('ObjectDiff', objectDiff)\n .filter('toJsonView', toJsonViewFilter)\n .filter('toJsonDiffView', toJsonDiffViewFilter)\n .filter('objToJsonView', objToJsonViewFilter);\n\n objectDiff.$inject = ['$sce'];\n toJsonViewFilter.$inject = ['ObjectDiff'];\n toJsonDiffViewFilter.$inject = ['ObjectDiff'];\n objToJsonViewFilter.$inject = ['ObjectDiff'];\n\n /* service implementation */\n function objectDiff($sce) {\n\n var openChar = '{',\n closeChar = '}',\n service = {\n setOpenChar: setOpenChar,\n setCloseChar: setCloseChar,\n diff: diff,\n diffOwnProperties: diffOwnProperties,\n toJsonView: formatToJsonXMLString,\n objToJsonView: formatObjToJsonXMLString,\n toJsonDiffView: formatChangesToXMLString\n };\n\n return service;\n\n\n /* service methods */\n\n /**\n * @param char\n */\n function setOpenChar(char) {\n openChar = char;\n }\n\n /**\n * @param char\n */\n function setCloseChar(char) {\n closeChar = char;\n }\n\n /**\n * diff between object a and b\n * @param {Object} a\n * @param {Object} b\n * @param shallow\n * @param isOwn\n * @return {Object}\n */\n function diff(a, b, shallow, isOwn) {\n\n if (a === b) {\n return equalObj(a);\n }\n\n var diffValue = {};\n var equal = true;\n\n for (var key in a) {\n if ((!isOwn && key in b) || (isOwn && typeof b != 'undefined' && b.hasOwnProperty(key))) {\n if (a[key] === b[key]) {\n diffValue[key] = equalObj(a[key]);\n } else {\n if (!shallow && isValidAttr(a[key], b[key])) {\n var valueDiff = diff(a[key], b[key], shallow, isOwn);\n if (valueDiff.changed == 'equal') {\n diffValue[key] = equalObj(a[key]);\n } else {\n equal = false;\n diffValue[key] = valueDiff;\n }\n } else {\n equal = false;\n diffValue[key] = {\n changed: 'primitive change',\n removed: a[key],\n added: b[key]\n }\n }\n }\n } else {\n equal = false;\n diffValue[key] = {\n changed: 'removed',\n value: a[key]\n }\n }\n }\n\n for (key in b) {\n if ((!isOwn && !(key in a)) || (isOwn && typeof a != 'undefined' && !a.hasOwnProperty(key))) {\n equal = false;\n diffValue[key] = {\n changed: 'added',\n value: b[key]\n }\n }\n }\n\n if (equal) {\n return equalObj(a);\n } else {\n return {\n changed: 'object change',\n value: diffValue\n }\n }\n }\n\n\n /**\n * diff between object a and b own properties only\n * @param {Object} a\n * @param {Object} b\n * @return {Object}\n * @param deep\n */\n function diffOwnProperties(a, b, shallow) {\n return diff(a, b, shallow, true);\n }\n\n /**\n * Convert to a readable xml/html Json structure\n * @param {Object} changes\n * @return {string}\n * @param shallow\n */\n function formatToJsonXMLString(changes, shallow) {\n var properties = [];\n\n var diff = changes.value;\n if (changes.changed == 'equal') {\n return $sce.trustAsHtml(inspect(diff, shallow));\n }\n\n for (var key in diff) {\n properties.push(formatChange(key, diff[key], shallow));\n }\n\n return $sce.trustAsHtml('' + openChar + '\\n' + properties.join(',\\n') + '\\n' + closeChar + '');\n\n }\n\n /**\n * Convert to a readable xml/html Json structure\n * @return {string}\n * @param obj\n * @param shallow\n */\n function formatObjToJsonXMLString(obj, shallow) {\n return $sce.trustAsHtml(inspect(obj, shallow));\n }\n\n /**\n * Convert to a readable xml/html Json structure\n * @param {Object} changes\n * @return {string}\n * @param shallow\n */\n function formatChangesToXMLString(changes, shallow) {\n var properties = [];\n\n if (changes.changed == 'equal') {\n return '';\n }\n\n var diff = changes.value;\n\n for (var key in diff) {\n var changed = diff[key].changed;\n if (changed !== 'equal')\n properties.push(formatChange(key, diff[key], shallow, true));\n }\n\n return $sce.trustAsHtml('' + openChar + '\\n' + properties.join(',\\n') + '\\n' + closeChar + '');\n\n }\n\n /**\n * @param obj\n * @returns {{changed: string, value: *}}\n */\n function equalObj(obj) {\n return {\n changed: 'equal',\n value: obj\n }\n }\n\n /**\n * @param a\n * @param b\n * @returns {*|boolean}\n */\n function isValidAttr(a, b) {\n var typeA = typeof a;\n var typeB = typeof b;\n return (a && b && (typeA == 'object' || typeA == 'function') && (typeB == 'object' || typeB == 'function'));\n }\n\n /**\n * @param key\n * @param diffItem\n * @returns {*}\n * @param shallow\n * @param diffOnly\n */\n function formatChange(key, diffItem, shallow, diffOnly) {\n var changed = diffItem.changed;\n var property;\n switch (changed) {\n case 'equal':\n property = (stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value));\n break;\n\n case 'removed':\n property = ('' + stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value) + '');\n break;\n\n case 'added':\n property = ('' + stringifyObjectKey(escapeHTML(key)) + ': ' + inspect(diffItem.value) + '');\n break;\n\n case 'primitive change':\n var prefix = stringifyObjectKey(escapeHTML(key)) + ': ';\n property = (\n '' + prefix + inspect(diffItem.removed) + ',\\n' +\n '' + prefix + inspect(diffItem.added) + '');\n break;\n\n case 'object change':\n property = shallow ? '' : (stringifyObjectKey(key) + ': ' + ( diffOnly ? formatChangesToXMLString(diffItem) : formatToJsonXMLString(diffItem)));\n break;\n }\n\n return property;\n }\n\n /**\n * @param {string} key\n * @return {string}\n */\n function stringifyObjectKey(key) {\n return /^[a-z0-9_$]*$/i.test(key) ?\n key :\n JSON.stringify(key);\n }\n\n /**\n * @param {string} string\n * @return {string}\n */\n function escapeHTML(string) {\n return string.replace(/&/g, '&').replace(//g, '>');\n }\n\n /**\n * @param {Object} obj\n * @return {string}\n * @param shallow\n */\n function inspect(obj, shallow) {\n\n return _inspect('', obj, shallow);\n\n /**\n * @param {string} accumulator\n * @param {object} obj\n * @see http://jsperf.com/continuation-passing-style/3\n * @return {string}\n * @param shallow\n */\n function _inspect(accumulator, obj, shallow) {\n switch (typeof obj) {\n case 'object':\n if (!obj) {\n accumulator += 'null';\n break;\n }\n if (shallow) {\n accumulator += '[object]';\n break;\n }\n var keys = Object.keys(obj);\n var length = keys.length;\n if (length === 0) {\n accumulator += '' + openChar + closeChar + '';\n } else {\n accumulator += '' + openChar + '\\n';\n for (var i = 0; i < length; i++) {\n var key = keys[i];\n accumulator = _inspect(accumulator + stringifyObjectKey(escapeHTML(key)) + ': ', obj[key]);\n if (i < length - 1) {\n accumulator += ',\\n';\n }\n }\n accumulator += '\\n' + closeChar + ''\n }\n break;\n\n case 'string':\n accumulator += JSON.stringify(escapeHTML(obj));\n break;\n\n case 'undefined':\n accumulator += 'undefined';\n break;\n\n default:\n accumulator += escapeHTML(String(obj));\n break;\n }\n return accumulator;\n }\n }\n }\n\n /* filter implementation */\n function toJsonViewFilter(ObjectDiff) {\n return function (value) {\n return ObjectDiff.toJsonView(value);\n };\n }\n\n function toJsonDiffViewFilter(ObjectDiff) {\n return function (value) {\n return ObjectDiff.toJsonDiffView(value);\n };\n }\n\n function objToJsonViewFilter(ObjectDiff) {\n return function (value) {\n return ObjectDiff.objToJsonView(value);\n };\n }\n})();\n"],"sourceRoot":"/source/"} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var gulp = require('gulp'); 5 | var less = require('gulp-less'); 6 | var sourcemaps = require('gulp-sourcemaps'); 7 | var uglify = require('gulp-uglify'); 8 | var csso = require('gulp-csso'); 9 | var jshint = require('gulp-jshint'); 10 | var stylish = require('jshint-stylish'); 11 | var jscs = require('gulp-jscs'); 12 | var mocha = require('gulp-spawn-mocha'); 13 | var mochaPhantomJS = require('gulp-mocha-phantomjs'); 14 | var tar = require('gulp-tar'); 15 | var gzip = require('gulp-gzip'); 16 | var bumper = require('gulp-bump'); 17 | var git = require('gulp-git'); 18 | var shell = require('gulp-shell'); 19 | var rename = require('gulp-rename'); 20 | var fs = require('fs'); 21 | var sequence = require('gulp-sequence'); 22 | 23 | gulp.task('less', function () { 24 | return gulp.src('./*.less') 25 | .pipe(sourcemaps.init()) 26 | .pipe(less()) 27 | .pipe(csso()) 28 | .pipe(sourcemaps.write('./')) 29 | .pipe(gulp.dest('./dist')); 30 | }); 31 | 32 | gulp.task('lint', function () { 33 | return gulp.src('**/*.js') 34 | .pipe(jshint()) 35 | .pipe(jshint.reporter(stylish)); 36 | }); 37 | 38 | /* gulp.task('style', function() { 39 | return gulp.src('**/ 40 | /*.js') 41 | .pipe(jscs()); 42 | });*/ 43 | 44 | gulp.task('unit', function () { 45 | return gulp 46 | .src('test/index.html') 47 | .pipe(mochaPhantomJS()); 48 | }); 49 | 50 | gulp.task('bump-patch', bump('patch')); 51 | gulp.task('bump-minor', bump('minor')); 52 | gulp.task('bump-major', bump('major')); 53 | 54 | gulp.task('bower', function () { 55 | return gulp.src('./angular-object-diff.js') 56 | .pipe(gulp.dest('./dist')); 57 | }); 58 | 59 | gulp.task('js', ['lint' /*, 'style'*/ , 'bower'], function () { 60 | return gulp.src('./angular-object-diff.js') 61 | .pipe(rename('angular-object-diff.min.js')) 62 | .pipe(sourcemaps.init()) 63 | .pipe(uglify()) 64 | .pipe(sourcemaps.write('./')) 65 | .pipe(gulp.dest('./dist')); 66 | }); 67 | 68 | gulp.task('build', function () { 69 | return gulp.src(['dist/*', '!./dist/*.tar.gz']) 70 | .pipe(tar('angular-object-diff.js.tar')) 71 | .pipe(gzip({ 72 | gzipOptions: { 73 | level: 9 74 | } 75 | })) 76 | .pipe(gulp.dest('dist/')); 77 | }); 78 | 79 | gulp.task('update', function (cb) { 80 | fs.readFile('./examples/index.template.html', 'utf8', function (err, file) { 81 | if (err) return cb(err); 82 | file = file.replace('', version()); 83 | fs.writeFile('./examples/index.html', file, cb); 84 | }); 85 | }); 86 | 87 | gulp.task('git-commit', function () { 88 | var v = 'update to version ' + version(); 89 | gulp.src(['./dist/*', './examples/*', './test/*', './package.json', './bower.json', './angular-object-diff.js', './angular-object-diff.less']) 90 | .pipe(git.add()) 91 | .pipe(git.commit(v)); 92 | }); 93 | 94 | gulp.task('git-push', function (cb) { 95 | var v = 'v' + version(); 96 | git.push('origin', 'master', function (err) { 97 | if (err) return cb(err); 98 | git.tag(v, v, function (err) { 99 | if (err) return cb(err); 100 | git.push('origin', 'master', { 101 | args: '--tags' 102 | }, function(err){ 103 | if (err) return cb(err); 104 | git.checkout('gh-pages', function (err) { 105 | if (err) return cb(err); 106 | git.reset('master', { 107 | args: '--hard' 108 | }, function (err) { 109 | if (err) return cb(err); 110 | git.push('origin', 'gh-pages', function (err) { 111 | if (err) return cb(err); 112 | git.checkout('master', cb); 113 | }); 114 | }); 115 | }); 116 | }); 117 | }); 118 | }); 119 | }); 120 | 121 | gulp.task('git-demo', function (cb) { 122 | var v = 'v' + version(); 123 | git.checkout('gh-pages', function (err) { 124 | if (err) return cb(err); 125 | git.reset('master', { 126 | args: '--hard' 127 | }, function (err) { 128 | if (err) return cb(err); 129 | git.push('origin', 'gh-pages', function (err) { 130 | if (err) return cb(err); 131 | git.checkout('master', cb); 132 | }); 133 | }); 134 | }); 135 | }); 136 | 137 | gulp.task('npm', shell.task([ 138 | 'npm publish' 139 | ])); 140 | 141 | gulp.task('watch', function () { 142 | gulp.watch('./*.js', ['js']); 143 | gulp.watch('./*.less', ['less']); 144 | return true; 145 | }); 146 | 147 | function bump(level) { 148 | return function () { 149 | return gulp.src(['./package.json', './bower.json']) 150 | .pipe(bumper({ 151 | type: level 152 | })) 153 | .pipe(gulp.dest('./')); 154 | }; 155 | } 156 | 157 | function version() { 158 | return JSON.parse(fs.readFileSync('package.json', 'utf8')).version; 159 | } 160 | 161 | gulp.task('default', sequence('check', ['less', 'js'], 'build')); 162 | gulp.task('test', sequence('unit' /*, 'integration'*/ )); 163 | gulp.task('check', sequence(['lint' /*, 'style'*/ ], 'test')); 164 | gulp.task('publish', sequence(['git-commit', 'git-push', 'npm'])); 165 | gulp.task('deploy-patch', sequence('default', 'bump-patch', /*'update',*/ 'publish')); 166 | gulp.task('deploy-minor', sequence('default', 'bump-minor', /*'update',*/ 'publish')); 167 | gulp.task('deploy-major', sequence('default', 'bump-major', /*'update',*/ 'publish')); 168 | 169 | })(); 170 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |Angular Object Diff Demo 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 41 | 42 | 43 |44 | 45 | 46 |78 | 79 | 80 | 81 | 82 | 83 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-object-diff", 3 | "version": "1.0.3", 4 | "description": "An Angular JS plugin to compare and show object differences.", 5 | "main": "angular-object-diff.js", 6 | "directories": { 7 | "example": "examples" 8 | }, 9 | "scripts": { 10 | "test": "gulp check" 11 | }, 12 | "author": "Deepu K Sasidharan47 |76 | 77 |48 |61 |49 | 50 | 51 |52 |53 | 54 | 55 |56 |57 | 58 | 59 |60 |62 |75 |63 | 64 | 65 |66 |67 | 68 | 69 |70 |71 | 72 | 73 |74 |", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/hipster-labs/angular-object-diff.git" 16 | }, 17 | "license": "MIT", 18 | "devDependencies": { 19 | "chai": "^1.10.0", 20 | "chai-string": "^1.1.1", 21 | "cp": "^0.2.0", 22 | "gm": "^1.17.0", 23 | "gulp": "^3.8.6", 24 | "gulp-bump": "^0.1.11", 25 | "gulp-csso": "^0.2.9", 26 | "gulp-git": "^0.5.6", 27 | "gulp-gzip": "0.0.8", 28 | "gulp-jscs": "^1.4.0", 29 | "gulp-jshint": "^1.9.2", 30 | "gulp-less": "^1.3.1", 31 | "gulp-mocha-phantomjs": "^0.6.1", 32 | "gulp-rename": "^1.2.0", 33 | "gulp-sequence": "^0.3.1", 34 | "gulp-shell": "^0.2.11", 35 | "gulp-sourcemaps": "^1.0.0", 36 | "gulp-spawn-mocha": "^2.0.1", 37 | "gulp-tar": "^0.5.0", 38 | "gulp-uglify": "^0.3.1", 39 | "imgur-node-api": "^0.1.0", 40 | "jshint-stylish": "^1.0.0", 41 | "less": "^1.7.3", 42 | "mkdirp": "^0.5.0", 43 | "mocha": "^2.1.0", 44 | "mocha-phantomjs": "^3.5.3", 45 | "sinon": "^1.12.2", 46 | "sinon-chai": "^2.7.0", 47 | "testatic": "^0.1.0", 48 | "tmp-sync": "jtblin/node-tmp-sync", 49 | "webshot": "^0.15.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hipster-labs/angular-object-diff/4299d0680d2aa276ab52cc7e3654df8b2689c521/screenshot.png --------------------------------------------------------------------------------