"angular cms editor"
Bootstrap 3.0.0 Snippet by evarevirus

<!DOCTYPE html><html lang='en' class=''> <head><script src='//production-assets.codepen.io/assets/editor/live/console_runner-079c09a0e3b9ff743e39ee2d5637b9216b3545af0de366d4b9aad9dc87e26bfd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/events_runner-73716630c22bbc8cff4bd0f07b135f00a0bdc5d14629260c3ec49e5606f98fdd.js'></script><script src='//production-assets.codepen.io/assets/editor/live/css_live_reload_init-2c0dc5167d60a5af3ee189d570b1835129687ea2a61bee3513dee3a50c115a77.js'></script><meta charset='UTF-8'><meta name="robots" content="noindex"><link rel="shortcut icon" type="image/x-icon" href="//production-assets.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" /><link rel="mask-icon" type="" href="//production-assets.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" /><link rel="canonical" href="https://codepen.io/intellix/pen/KpjNrJ?limit=all&page=15&q=editor" /> <link rel='stylesheet prefetch' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css'><link rel='stylesheet prefetch' href='//cdnjs.cloudflare.com/ajax/libs/textAngular/1.5.0/textAngular.css'><link rel='stylesheet prefetch' href='//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css'> <style class="cp-pen-styles">body { padding: 0 50px 50px; } .table > tbody > tr > td { line-height: 35px; } .table > tfoot form { padding: 20px; } .table .label { margin-right: 2px; margin-bottom: 2px; } .btn-tabs { z-index: 1; position: absolute; top: 10px; right: 0; transition: transform .1s; transform: translateX(8px); } .btn-tab { display: block; width: 100%; margin-bottom: 2px; padding: 5px 8px; border: 0; border-radius: 0 3px 3px 0; text-transform: uppercase; transition: color .1s, transform .1s; } .btn-tab:hover { transform: translateX(5%); } .field { position: relative; margin: 0 0 3px; transition: opacity .2s, transform .2s; } .field > .inner { z-index: 2; position: relative; padding: 10px 10px 1px; min-height: 80px; border-radius: 0; background: #f4f4f4; } .field.ng-enter { opacity: 0; transform: translateY(-50px); } .field.ng-enter.ng-enter-active { opacity: 1; transform: translateY(0); } .field.ng-leave { opacity: 1; transform: translateY(0); } .field.ng-leave.ng-leave-active { opacity: 0; transform: translateY(-50px); } .field:hover .btn-tabs { transform: translateX(95%); } .form-group { margin-bottom: 10px; } fieldset { margin-bottom: 10px; } .fieldset-info { margin: -15px 0 20px; color: #9c9c9c; } field-data { display: block; } .strong { font-weight: bold; } .btn-group { box-shadow: 1px 0 5px rgba(0, 0, 0, 0.2); border-radius: 5px; } template-space { display: block; } .carousel.banner .item { height: 250px; padding: 10px; background: #eee; color: #fff; } .carousel.banner h1, .carousel.banner h2, .carousel.banner h3, .carousel.banner h4 { margin: 0 0 5px; } .carousel.banner img { z-index: -1; position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .carousel.banner .bottom { position: absolute; bottom: 0; left: 0; right: 0; padding: 10px; text-align: right; background: rgba(0, 0, 0, 0.3); } .carousel .btn { margin-left: 5px; } .carousel .flip-content { position: absolute; top: 0; right: 0; bottom: 54px; left: 0; padding: 10px; background: rgba(0, 0, 0, 0.8); color: #fff; opacity: 0; transition: opacity .1s; } .carousel:hover .flip-content { opacity: 1; } .draggable { border: 5px dashed #ccc; padding: 15px; text-align: center; } .draggable.drag-over { border-color: #000; } .draggable.drag-over .drop-txt.off { display: none; } .draggable.drag-over .drop-txt.on { display: block; } .draggable .drop-txt { font-weight: bold; } .draggable .drop-txt.on { display: none; } .draggable .or { margin: 5px 0; color: #333; } </style></head><body> <body ng-app="backoffice" ng-controller="AppCtrl as app"> <div class="container"> <!-- Info --> <h1>Angular CMS/Editor</h1> <p><b>Problem:</b> Currently, we create PHP constants, spend some time to create a new space and then deploy it. It's just a block of HTML content and the frontend developer has to try and put that into different places on the page.</p> <p><b>Solution:</b> Make templates within the CMS. The frontend developer can creates templates for the content team to populate, easily. No PHP constants, deployments or dev time needed.</p> <ol> <li ng-class="{ strong: !app.space }">Frontend Developer creates a template</li> <li ng-class="{ strong: app.space }">Content team create a space by populating space previously defined</li> </ol> <hr /> <!-- List of templates --> <div ng-if="!app.space.template"> <h2>Templates</h2> <table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>Name (slug)</th> <th>Created</th> <th>Updated</th> <th>Fields</th> <th class="text-right">Actions</th> </tr> </thead> <tbody> <tr ng-if="!app.templates.length"> <td colspan="6">There are no templates defined</td> </tr> <tr ng-repeat="template in app.templates"> <td>{{ template.id }}</td> <td>{{ template.name }} ({{template.slug}})</td> <td>{{ template.createdOn | date }}</td> <td>{{ template.updatedOn | date }}</td> <td> <span class="label label-default" title="{{field.type}} - {{field.description}}" ng-repeat="field in template.fields">{{ field.label }}</span> </td> <td class="text-right"> <button ng-click="app.templates.splice($index, 1)" class="btn btn-danger"><i class="fa fa-remove"></i> Delete</button> <button ng-click="app.template = template; app.creatingTemplate = true" class="btn btn-primary"><i class="fa fa-pencil"></i> Edit</button> <button ng-click="app.createSpace(template)" class="btn btn-primary"><i class="fa fa-plus"></i> New space</button> </td> </tr> </tbody> <tfoot> <tr> <td colspan="6"> <div ng-if="app.creatingTemplate"> <form name="tForm" ng-submit="app.saveTemplate(tForm)" novalidate> <!-- Mandatory identifiers --> <fieldset> <legend>Identifiers</legend> <p class="fieldset-info">Fields required for identification and deep-linking within system</p> <div class="field"> <div class="inner"> <div class="row"> <div class="col-xs-3"> <div class="form-group" ng-class="{ 'has-error': (tForm.name.$invalid && tForm.$submitted) || (tForm.name.$invalid && tForm.name.$dirty) }"> <label class="control-label">Name</label> <input type="text" class="form-control" name="name" placeholder="My Template" ng-model="app.template.name" required /> </div> </div> <div class="col-xs-3"> <div class="form-group" ng-class="{ 'has-error': (tForm.slug.$invalid && tForm.$submitted) || (tForm.slug.$invalid && tForm.slug.$dirty) }"> <label class="control-label">Slug</label> <input type="text" class="form-control" name="slug" placeholder="my-template" ng-model="app.template.slug" required /> </div> </div> </div> </div> </div> </fieldset> <!-- Fields --> <fieldset> <legend>Fields</legend> <div class="field" ng-repeat="field in app.template.fields"> <div class="btn-tabs"> <button type="button" class="btn-tab btn-danger" title="Remove" ng-if="app.template.fields.length > 1" ng-click="app.template.fields.splice($index, 1)"> <i class="fa fa-remove"></i> Kill </button> <button type="button" class="btn-tab btn-primary" title="Add" ng-if="$last" ng-click="app.template.fields.push({ type: 'text', required: true, multiple: false })"> <i class="fa fa-plus"></i> Add </button> </div> <div class="inner"> <div class="row"> <div class="col-xs-3"> <div class="form-group" ng-class="{ 'has-error': (tForm['field-label'].$invalid && tForm.$submitted) || (tForm['field-label'].$invalid && tForm['field-label'].$dirty) }"> <label class="control-label">Label</label> <input type="text" class="form-control" name="field-label" placeholder="My Field" ng-model="app.template.fields[$index].label" required /> </div> </div> <div class="col-xs-3"> <div class="form-group" ng-class="{ 'has-error': (tForm['field-key'].$invalid && tForm.$submitted) || (tForm['field-key'].$invalid && tForm['field-key'].$dirty) }"> <label class="control-label">Key</label> <input type="text" class="form-control" name="field-key" placeholder="my-field" ng-model="app.template.fields[$index].key" required /> </div> </div> <div class="col-xs-2"> <div class="form-group" ng-class="{ 'has-error': (tForm['field-type'].$invalid && tForm.$submitted) || (tForm['field-type'].$invalid && tForm['field-type'].$dirty) }"> <label class="control-label">Type</label> <select class="form-control" name="field-type" ng-model="app.template.fields[$index].type" ng-options="o.value as o.label for o in app.options.types" required></select> </div> </div> <div class="col-xs-2"> <div class="form-group" ng-class="{ 'has-error': (tForm['field-required'].$invalid && tForm.$submitted) || (tForm['field-required'].$invalid && tForm['field-required'].$dirty) }"> <label class="control-label">Required</label> <select class="form-control" name="field-required" ng-model="app.template.fields[$index].required" ng-options="o.value as o.label for o in app.options.booleans" required></select> </div> </div> <div class="col-xs-2"> <div class="form-group" ng-class="{ 'has-error': (tForm['field-multiple'].$invalid && tForm.$submitted) || (tForm['field-multiple'].$invalid && tForm['field-multiple'].$dirty) }"> <label class="control-label">Multiple</label> <select class="form-control" name="field-multiple" ng-model="app.template.fields[$index].multiple" ng-options="o.value as o.label for o in app.options.booleans" required></select> </div> </div> </div> </div> </div> </fieldset> <hr /> <div class="text-right"> <button type="button" class="btn btn-danger" ng-click="app.creatingTemplate = false">Cancel</button> <button type="submit" class="btn btn-primary" ng-disabled="dForm.$invalid"><i class="fa fa-check"></i> Save template</button> </div> </form> </div> <div class="text-right" ng-if="!app.creatingTemplate"> <button type="button" class="btn btn-primary" ng-click="app.creatingTemplate = !app.creatingTemplate"><i class="fa fa-plus"></i> New template</button> </div> </td> </tr> </tfoot> </table> <!-- List of spaces --> <div ng-if="app.spaces.length"> <hr /> <h2>Spaces</h2> <table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>Name (slug)</th> <th>Template (slug)</th> <th>Created</th> <th>Updated</th> <th class="text-right">Actions</th> </tr> </thead> <tbody> <tr ng-repeat="space in app.spaces"> <td>{{ space.id }}</td> <td>{{ space.name }} ({{ space.slug }})</td> <td>{{ space.template.name }} ({{ space.template.slug }})</td> <td>{{ space.createdOn | date }}</td> <td>{{ space.updatedOn | date }}</td> <td class="text-right"> <button ng-click="app.spaces.splice($index, 1)" class="btn btn-danger"><i class="fa fa-remove"></i> Delete</button> <button ng-click="app.space = space" class="btn btn-primary"><i class="fa fa-pencil"></i> Edit</button> </td> </tr> </tbody> </table> </div> </div> <!-- Space creation/editing --> <div class="row" ng-if="app.space.template"> <div class="col-md-6"> <h2>{{app.space.template.name}} ({{app.space.template.slug}})</h2> <form name="sForm" ng-submit="app.saveSpace(sForm)" novalidate> <!-- Mandatory fields for identification --> <fieldset> <legend> Identifiers <span class="text-danger" title="Required">*</span> </legend> <p class="fieldset-info">Fields required for identification and deep-linking within system</p> <div class="field"> <div class="inner"> <div class="row"> <div class="col-xs-6"> <div class="form-group" ng-class="{ 'has-error': (sForm.name.$invalid && sForm.$submitted) || (sForm.name.$invalid && sForm.name.$dirty) }"> <label class="control-label">Name</label> <input type="text" class="form-control" name="name" placeholder="My Space" ng-model="app.space.name" required /> </div> </div> <div class="col-xs-6"> <div class="form-group" ng-class="{ 'has-error': (sForm.slug.$invalid && sForm.$submitted) || (sForm.slug.$invalid && sForm.slug.$dirty) }"> <label class="control-label">Slug</label> <input type="text" class="form-control" name="slug" placeholder="my-slug" ng-model="app.space.slug" required /> </div> </div> </div> </div> </div> </fieldset> <!-- Template data fields --> <fieldset ng-repeat="definition in app.space.template.fields" ng-class="{ 'has-error': (sForm['data-' + definition.key].$invalid && sForm.$submitted) || (sForm['data-' + definition.key].$invalid && sForm['data-' + definition.key].$dirty) }"> <legend> {{ definition.label }} <span class="text-danger" title="Required" ng-if="definition.required !== false">*</span> </legend> <!-- Singular --> <field-data class="field" ng-if="!definition.multiple" data="app.space.data[definition.key]" definition="definition"></field-data> <!-- Multiple: have to pass in data like this or else ng-model doesn't trigger changes: data="app.space.data[definition.key][$index]" --> <field-data class="field" ng-if="definition.multiple" ng-repeat="d in app.space.data[definition.key] track by $index" data="app.space.data[definition.key][$index]" definition="definition" on-add="app.addData(definition, app.space.data[definition.key])" on-remove="app.removeData(definition, $index)" item-length="app.space.data[definition.key].length" last="$last"></field-data> </fieldset> <div class="text-right"> <button type="button" ng-click="app.space = null" class="btn btn-danger">Cancel</button> <button type="submit" class="btn btn-primary"><i class="fa fa-check"></i> Save space</button> </div> </form> <br /> </div> <!-- Debug --> <div class="col-md-6"> <h2>Live Preview</h2> <p>Template HTML/CSS is shared between frontend and CMS for live-previews and only need to be coded once.</p> <template-space data="app.space.data" slug="{{app.space.template.slug}}"></template-space> <h2>Raw JSON</h2> <pre>{{app.space | json}}</pre> </div> </div> </div> </body> <!-- Imported from FE/Common --> <script type="text/ng-template" id="components/template-space/banner.tpl.html"> <div class="carousel banner"> <div class="carousel-inner"> <div class="item active"> <img ng-src="{{tSpace.data.image}}"> <h3 ng-repeat="title in tSpace.data.titles">{{title}}</h3> <div class="bottom"> <button ng-repeat="cta in tSpace.data.ctas" ng-href="{{cta.url}}" class="btn btn-{{cta.size}} btn-{{cta.style}}">{{cta.text}}</button> {{tSpace.title}} </div> <div class="flip-content" ng-bind-html="tSpace.data.flipContent"></div> </div> </div> </div> </script> <script src='//production-assets.codepen.io/assets/common/stopExecutionOnTimeout-b2a7b3fe212eaa732349046d8416e00a9dec26eb7fd347590fbced3ab38af52e.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular-animate.min.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/textAngular/1.5.0/textAngular-rangy.min.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/textAngular/1.5.0/textAngular-sanitize.min.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/textAngular/1.5.0/textAngular.min.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/12.0.1/ng-file-upload-shim.min.js'></script><script src='//cdnjs.cloudflare.com/ajax/libs/danialfarid-angular-file-upload/12.0.1/ng-file-upload.min.js'></script> <script >angular .module('backoffice', [ 'ngAnimate', 'textAngular', 'ngFileUpload' ]) .controller('AppCtrl', AppCtrl) .directive('fieldData', fieldData) // Imported from FE/Common .directive('templateSpace', templateSpace); function AppCtrl() { var vm = this; vm.creatingTemplate = false; vm.template = { slug: '', name: '', fields: [ { key: '', label: '', type: 'text', required: true, multiple: false } ] }; vm.templates = [ { "id": 1, "name": "HTML Space", "slug": "html-space", "createdOn": new Date("Sun Feb 07 2016 10:40:38 GMT+0100 (CET)"), "updatedOn": new Date("Sun Feb 08 2016 10:40:38 GMT+0100 (CET)"), "fields": [ { "key": "title", "type": "text", "label": "Title", "multiple": false, "required": true }, { "key": "content", "type": "html", "label": "Content", "multiple": false, "required": true }, { "key": "ctas", "type": "button", "label": "Calls to action", "multiple": true, "required": true } ] }, { "id": 2, "name": "Banner", "slug": "banner", "createdOn": new Date("Friday Feb 12 2016 10:40:38 GMT+0100 (CET)"), "updatedOn": new Date("Saturday Feb 13 2016 10:40:38 GMT+0100 (CET)"), "fields": [ { "key": "titles", "type": "text", "label": "Titles", "multiple": true, "required": true }, { "key": "image", "type": "text", "label": "Image URL", "default": "https://i.ytimg.com/vi/tntOCGkgt98/maxresdefault.jpg", "multiple": false, "required": true }, { "key": "flipContent", "type": "html", "label": "Flip Content", "multiple": false, "required": true }, { "key": "ctas", "type": "button", "label": "Calls to action", "multiple": true, "required": true } ] } ]; vm.options = { types: [ { value: 'text', label: 'Text' }, { value: 'button', label: 'Button' }, { value: 'html', label: 'HTML' }, { value: 'file', label: 'File' } ], booleans: [ { value: true, label: 'Yes' }, { value: false, label: 'No' } ] } vm.spaces = []; vm.space = null; vm.saveTemplate = saveTemplate; vm.createSpace = createSpace; vm.saveSpace = saveSpace; // add/remove of field data vm.addData = addData; vm.removeData = removeData; function saveTemplate(form) { if (form.$valid) { var now = new Date; vm.template.updatedOn = now; if (!vm.template.id) { vm.template.id = vm.templates.length + 1; vm.template.createdOn = new Date; vm.templates.push(vm.template); } vm.creatingTemplate = false; } } function createSpace(template) { // init field data var spaceData = {}; template.fields.forEach(function(definition) { var data = getDefaultData(definition); if (definition.multiple) { spaceData[definition.key] = [data]; } else { spaceData[definition.key] = data; } }); vm.space = { template: template, data: spaceData }; } function saveSpace(form) { if (form.$valid) { var now = new Date; vm.space.updatedOn = now; if (!vm.space.id) { vm.space.id = vm.spaces.length + 1; vm.space.createdOn = now; vm.spaces.push(vm.space); } vm.space = null; } } function getDefaultData(definition) { if (definition.type === 'button') { return { text: definition.default || 'Submit', href: '', size: 'md', style: 'default' }; } return definition.default || ''; } function addData(definition, spaceData) { var defaultData = getDefaultData(definition); spaceData.push(defaultData); } function removeData(definition, index) { vm.space.data[definition.key].splice(index, 1); } } function fieldData() { return { restrict: 'E', bindToController: true, scope: { definition: '=', data: '=', last: '=', itemLength: '=', onAdd: '&', onRemove: '&' }, controllerAs: 'field', controller: SpaceFieldController, template: ` <div class="btn-tabs"> <button type="button" class="btn-tab btn-danger" title="Remove" ng-if="field.itemLength > 1" ng-click="field.onRemove()"> <i class="fa fa-remove"></i> Kill </button> <button type="button" class="btn-tab btn-primary" title="Add" ng-if="field.last" ng-click="field.onAdd()"> <i class="fa fa-plus"></i> Add </button> </div> <div class="inner" ng-switch on="field.definition.type"> <div class="form-group" ng-switch-when="text"> <input type="text" class="form-control" name="data-{{field.definition.key}}" placeholder="Some text content" ng-required="field.definition.required !== false" ng-model="field.data" /> </div> <div class="form-group" ng-switch-when="file"> <section class="draggable" ngf-max-size="{{field.maxUploadSize}}" ngf-pattern="'{{field.allowedFiletypes}}'" ng-model="field.data" ngf-drop="field.uploadFiles($files);" ngf-drag-over-class="'drag-over'"> <div class="drop-txt off" translate>Drag file here to upload</div> <div class="drop-txt on" translate>Drop file to upload</div> <div class="or">or</div> <div> <button type="button" name="data-{{field.definition.key}}" class="btn btn-primary" ngf-select="field.uploadFiles($files);" ng-required="field.definition.required !== false">Select files on your computer</button> </div> </section> <section class="uploading" ng-show="field.thinking"> <div class="col-md-3"> <i class="fa fa-2x fa-file-image-o"></i> </div> <div class="col-md-6 text-center"> <i class="fa fa-2x fa-circle-o-notch fa-spin"></i> <span>uploading</span> </div> <div class="col-md-3"> <button class="btn btn-sm btn-block btn-default">Cancel</button> </div> </section> </div> <div class="form-group" ng-switch-when="html"> <text-angular name="data-{{field.definition.key}}" ng-model="field.data" ng-required="field.definition.required !== false"></text-angular> </div> <div ng-switch-when="button"> <!-- Text and URL --> <div class="row"> <div class="col-xs-4"> <div class="form-group"> <label>Text</label> <input type="text" class="form-control" name="data-{{field.definition.key}}" ng-model="field.data.text" ng-required="field.definition.required !== false"> </div> </div> <div class="col-xs-8"> <div class="form-group"> <label>URL</label> <input type="url" class="form-control" name="data-{{field.definition.key}}" ng-model="field.data.href" placeholder="https://www.url.com" ng-required="field.definition.required !== false"> </div> </div> </div> <!-- Style and size --> <div class="row"> <div class="col-xs-4"> <div class="form-group"> <label>Size</label> <div> <div class="btn-group"> <button type="button" ng-repeat="size in field.sizes" ng-click="field.data.size = size.key" ng-class="{ active: field.data.size == size.key }" title="{{size.title}}" class="btn btn-sm btn-default">{{size.name}}</button> </div> </div> </div> </div> <div class="col-xs-8"> <div class="form-group"> <label>Style</label> <div> <div class="btn-group"> <button type="button" ng-repeat="style in field.styles" ng-click="field.data.style = style.key" ng-class="{ active: field.data.style == style.key }" title="{{style.title}}" class="btn btn-sm btn-{{style.key}}">{{style.name}}</button> </div> </div> </div> </div> </div> </div> </div> ` }; } function SpaceFieldController($scope, $timeout) { var vm = this; vm.maxUploadSize = 5000; vm.allowedFiletypes = '.jpg, .jpeg, .png, .gif'; vm.sizes = [ { key: 'xs', title: 'Extra Small', name: 'XS' }, { key: 'sm', title: 'Small', name: 'SM' }, { key: 'md', title: 'Medium', name: 'MD' }, { key: 'lg', title: 'Large', name: 'LG' } ]; vm.styles = [ { key: 'default', name: 'Default', title: 'Default' }, { key: 'primary', name: 'Pri', title: 'Primary' }, { key: 'success', name: 'Suc', title: 'Success' }, { key: 'info', name: 'Info', title: 'Info' }, { key: 'warning', name: 'Warn', title: 'Warning' }, { key: 'danger', name: 'Dan', title: 'Danger' }, { key: 'link', name: 'Link' } ]; vm.uploadFiles = uploadFiles; function uploadFiles($files) { console.log('TODO: Upload', $files); } } function templateSpace() { return { restrict: 'E', templateUrl: function(elem, attrs) { // Harcoded for now... attrs.slug doesn't get compiled yet :S return 'components/template-space/banner.tpl.html'; // return 'components/template-space/' + attrs.slug + '.tpl.html'; }, scope: { slug: '@', data: '=?' }, controller: TemplateSpaceCtrl, controllerAs: 'tSpace', bindToController: true }; } /* @ngInject */ function TemplateSpaceCtrl() { var vm = this; } //# sourceURL=pen.js </script> </body></html>

Questions / Comments:

Related: See More