'use strict';
angular.module('JSONedit', ['ui.sortable']) .directive('ngModelOnblur', function() {
// override the default input to update on blur // from http://jsfiddle.net/cn8VF/ return { restrict: 'A', require: 'ngModel', link: function(scope, elm, attr, ngModelCtrl) { if (attr.type === 'radio' || attr.type === 'checkbox') return; elm.unbind('input').unbind('keydown').unbind('change'); elm.bind('blur', function() { scope.$apply(function() { ngModelCtrl.$setViewValue(elm.val()); }); }); } };
}) .directive('json', function($compile) {
return { restrict: 'E', scope: { child: '=', type: '@', defaultCollapsed: '=' }, link: function(scope, element, attributes) { var stringName = "Text"; var objectName = "Object"; var arrayName = "Array"; var refName = "Reference"; scope.valueTypes = [stringName, objectName, arrayName, refName]; scope.sortableOptions = { axis: 'y' }; if (scope.$parent.defaultCollapsed === undefined) { scope.collapsed = false; } else { scope.collapsed = scope.defaultCollapsed; } if (scope.collapsed) { scope.chevron = "glyphicon-chevron-right"; } else { scope.chevron = "glyphicon-chevron-down"; } ////// // Helper functions ////// var getType = function(obj) { var type = Object.prototype.toString.call(obj); if (type === "[object Object]") { return "Object"; } else if(type === "[object Array]"){ return "Array"; } else { return "Literal"; } }; var isNumber = function(n) { return !isNaN(parseFloat(n)) && isFinite(n); }; scope.getType = function(obj) { return getType(obj); }; scope.toggleCollapse = function() { if (scope.collapsed) { scope.collapsed = false; scope.chevron = "glyphicon-chevron-down"; } else { scope.collapsed = true; scope.chevron = "glyphicon-chevron-right"; } }; scope.moveKey = function(obj, key, newkey) { //moves key to newkey in obj if (key !== newkey) { obj[newkey] = obj[key]; delete obj[key]; } }; scope.deleteKey = function(obj, key) { if (getType(obj) == "Object") { if( confirm('Delete "'+key+'" and all it contains?') ) { delete obj[key]; } } else if (getType(obj) == "Array") { if( confirm('Delete "'+obj[key]+'"?') ) { obj.splice(key, 1); } } else { console.error("object to delete from was " + obj); } }; scope.addItem = function(obj) { if (getType(obj) == "Object") { // check input for key if (scope.keyName == undefined || scope.keyName.length == 0){ alert("Please fill in a name"); } else if (scope.keyName.indexOf("$") == 0){ alert("The name may not start with $ (the dollar sign)"); } else if (scope.keyName.indexOf("_") == 0){ alert("The name may not start with _ (the underscore)"); } else { if (obj[scope.keyName]) { if( !confirm('An item with the name "'+scope.keyName +'" exists already. Do you really want to replace it?') ) { return; } } // add item to object switch(scope.valueType) { case stringName: obj[scope.keyName] = scope.valueName ? scope.possibleNumber(scope.valueName) : ""; break; case objectName: obj[scope.keyName] = {}; break; case arrayName: obj[scope.keyName] = []; break; case refName: obj[scope.keyName] = {"Reference!!!!": "todo"}; break; } //clean-up scope.keyName = ""; scope.valueName = ""; scope.showAddKey = false; } } else if (getType(obj) == "Array") { // add item to array switch(scope.valueType) { case stringName: obj.push(scope.valueName ? scope.valueName : ""); break; case objectName: obj.push({}); break; case arrayName: obj.push([]); break; case refName: obj.push({"Reference!!!!": "todo"}); break; } scope.valueName = ""; scope.showAddKey = false; } else { console.error("object to add to was " + obj); } }; scope.possibleNumber = function(val) { return isNumber(val) ? parseFloat(val) : val; }; ////// // Template Generation ////// // Note: // sometimes having a different ng-model and then saving it on ng-change // into the object or array is necesarry for all updates to work // recursion var switchTemplate = '<span ng-switch on="getType(val)" >' + '<json ng-switch-when="Object" child="val" type="object" default-collapsed="defaultCollapsed"></json>' + '<json ng-switch-when="Array" child="val" type="array" default-collapsed="defaultCollapsed"></json>' + '<span ng-switch-default class="jsonLiteral"><input type="text" ng-model="val" ' + 'placeholder="Empty" ng-model-onblur ng-change="child[key] = possibleNumber(val)"/>' + '</span>' + '</span>'; // display either "plus button" or "key-value inputs" var addItemTemplate = '<div ng-switch on="showAddKey" class="block" >' + '<span ng-switch-when="true">'; if (scope.type == "object"){ // input key addItemTemplate += '<input placeholder="Name" type="text" ui-keyup="{\'enter\':\'addItem(child)\'}" ' + 'class="form-control input-sm addItemKeyInput" ng-model="$parent.keyName" /> '; } addItemTemplate += // value type dropdown '<select ng-model="$parent.valueType" ng-options="option for option in valueTypes" class="form-control input-sm"' + 'ng-init="$parent.valueType=\''+stringName+'\'" ui-keydown="{\'enter\':\'addItem(child)\'}"></select>' // input value + '<span ng-show="$parent.valueType == \''+stringName+'\'"> : <input type="text" placeholder="Value" ' + 'class="form-control input-sm addItemValueInput" ng-model="$parent.valueName" ui-keyup="{\'enter\':\'addItem(child)\'}"/></span> ' // Add button + '<button class="btn btn-primary btn-sm" ng-click="addItem(child)">Add</button> ' + '<button class="btn btn-default btn-sm" ng-click="$parent.showAddKey=false">Cancel</button>' + '</span>' + '<span ng-switch-default>' // plus button + '<button class="addObjectItemBtn" ng-click="$parent.showAddKey = true"><i class="glyphicon glyphicon-plus"></i></button>' + '</span>' + '</div>'; // start template if (scope.type == "object"){ var template = '<i ng-click="toggleCollapse()" class="glyphicon" ng-class="chevron"></i>' + '<span class="jsonItemDesc">'+objectName+'</span>' + '<div class="jsonContents" ng-hide="collapsed">' // repeat + '<span class="block" ng-hide="key.indexOf(\'_\') == 0" ng-repeat="(key, val) in child">' // object key + '<span class="jsonObjectKey">' + '<input class="keyinput" type="text" ng-model="newkey" ng-init="newkey=key" ' + 'ng-blur="moveKey(child, key, newkey)"/>' // delete button + '<i class="deleteKeyBtn glyphicon glyphicon-trash" ng-click="deleteKey(child, key)"></i>' + '</span>' // object value + '<span class="jsonObjectValue">' + switchTemplate + '</span>' + '</span>' // repeat end + addItemTemplate + '</div>'; } else if (scope.type == "array") { var template = '<i ng-click="toggleCollapse()" class="glyphicon"' + 'ng-class="chevron"></i>' + '<span class="jsonItemDesc">'+arrayName+'</span>' + '<div class="jsonContents" ng-hide="collapsed">' + '<ol class="arrayOl" ui-sortable="sortableOptions" ng-model="child">' // repeat + '<li class="arrayItem" ng-repeat="val in child">' // delete button + '<i class="deleteKeyBtn glyphicon glyphicon-trash" ng-click="deleteKey(child, $index)"></i>' + '<i class="moveArrayItemBtn glyphicon glyphicon-align-justify"></i>' + '<span>' + switchTemplate + '</span>' + '</li>' // repeat end + '</ol>' + addItemTemplate + '</div>'; } else { console.error("scope.type was "+ scope.type); } var newElement = angular.element(template); $compile(newElement)(scope); element.replaceWith ( newElement ); } };
});