diff --git a/.gitignore b/.gitignore index 625f205..fa14c32 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,9 @@ $RECYCLE.BIN/ dist/ node_modules/ -pkg/ +pkg/tmp/ +pkg/*.nupkg +pkg/*.zip [Dd]ebug/ [Bb]uild/ diff --git a/Gruntfile.js b/Gruntfile.js index 4552698..df05808 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,7 +1,11 @@ module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), + pkgMeta: grunt.file.readJSON('pkg/meta.json'), dest: 'dist', + package_dir: 'pkg', + package_temp_dir: '<%= package_dir %>/tmp/', + watch: { less: { @@ -58,26 +62,71 @@ module.exports = function(grunt) { 'app/controllers/controller.js', 'app/controllers/config.controller.js', 'app/directives/archetypeproperty.js', - 'app/services/propertyeditor.js' + 'app/resources/propertyeditor.js' ], dest: '<%= dest %>/js/archetype.js' } }, + nugetpack: { + dist: { + src: '<%= package_temp_dir %>/nuget/package.nuspec', + dest: '<%= package_dir %>' + } + }, + + template: { + 'nuget_manifest': { + 'options': { + 'data': { + name: '<%= pkgMeta.name %>', + version: '<%= pkgMeta.version %>', + url: '<%= pkgMeta.url %>', + license: '<%= pkgMeta.license %>', + licenseUrl: '<%= pkgMeta.licenseUrl %>', + author: '<%= pkgMeta.author %>', + authorUrl: '<%= pkgMeta.authorUrl %>', + + files: [{ path: '..\\..\\..\\<%= dest %>\\**', target: 'content\\App_Plugins\\Archetype'}] + } + }, + 'files': { + '<%= package_temp_dir %>/nuget/package.nuspec': ['<%= package_dir %>/nuget/package.nuspec'] + } + } + }, + + clean: { + build: ['<%= dest %>'] + }, + copy: { build: { files: [ {expand: true, cwd: 'app/', src: ['package.manifest'], dest: '<%= dest %>', flatten: true}, {expand: true, cwd: 'app/config/', src: ['config.views.js'], dest: '<%= dest %>/js', flatten: true}, - {expand: true, cwd: 'app/views/', src: ['archetype.html', 'archetype.config.html'], dest: '<%= dest %>/views', flatten: true} + {expand: true, cwd: 'app/views/', src: ['archetype.html', 'archetype.config.html'], dest: '<%= dest %>/views', flatten: true}, + {expand: true, cwd: 'app/Umbraco/Umbraco.Archetype/bin/Debug/', src: ['Archetype.dll'], dest: '<%= dest %>/bin', flatten: true} ] }, deploy: { files: [ - {expand: true, cwd: '<%= dest %>/', src: ['**'], dest: '<%= grunt.option("target") %>\\App_Plugins\\Archetype', flatten: false}, + {expand: true, cwd: '<%= dest %>/', src: ['**/*', '!bin/*'], dest: '<%= grunt.option("target") %>\\App_Plugins\\Archetype', flatten: false}, + {expand: true, cwd: '<%= dest %>/', src: ['bin/*'], dest: '<%= grunt.option("target") %>\\bin', flatten: false}, + ] + }, + nuget_prepare: { + files: [ + {expand: true, cwd: '<%= dest %>/', src: ['**/*', '!bin/*'], dest: '<%= package_temp_dir %>/nuget/content/', flatten: false}, + {expand: true, cwd: '<%= dest %>/', src: ['bin/*'], dest: '<%= package_temp_dir %>/nuget/lib/net40/', flatten: false} + ] + }, + umbracopackage: { + files: [ + {expand: true, cwd: '<%= dest %>/', src: ['**/*', "!bin/*"], dest: 'pkg/tmp/umbraco/App_Plugins/Archetype', flatten: false}, + {expand: true, cwd: '<%= dest %>/', src: ['bin/*'], dest: 'pkg/tmp/umbraco', flatten: false} ] } - }, touch: { @@ -87,7 +136,42 @@ module.exports = function(grunt) { } }, - clean: ['<%= dest %>'] + umbracoPackage: { + options: { + name: "<%= pkgMeta.name %>", + version: '<%= pkgMeta.version %>', + url: '<%= pkgMeta.url %>', + license: '<%= pkgMeta.license %>', + licenseUrl: '<%= pkgMeta.licenseUrl %>', + author: '<%= pkgMeta.author %>', + authorUrl: '<%= pkgMeta.authorUrl %>', + manifest: 'pkg/umbraco/package.xml', + readme: 'pkg/umbraco/readme.txt', + sourceDir: 'pkg/tmp/umbraco', + outputDir: 'pkg', + } + }, + + clean: { + build: ['<%= dest %>'], + package_temp: ['pkg/tmp'], + }, + + msbuild: { + dev: { + src: ['app/Umbraco/Umbraco.Archetype/Archetype.Umbraco.csproj'], + options: { + projectConfiguration: 'Debug', + targets: ['Clean', 'Rebuild'], + stdout: true, + maxCpuCount: 4, + buildParameters: { + WarningLevel: 2 + }, + verbosity: 'quiet' + } + } + } }); @@ -98,12 +182,20 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-nuget'); + grunt.loadNpmTasks('grunt-template'); grunt.loadNpmTasks('grunt-touch'); + grunt.loadNpmTasks('grunt-msbuild'); + grunt.loadTasks('tasks'); + + grunt.registerTask('package', ['package:nuget', 'package:umbraco']); + grunt.registerTask('package:nuget', ['copy:nuget_prepare', 'template:nuget_manifest', 'nugetpack', 'clean:package_temp']); + grunt.registerTask('package:umbraco', ['copy:umbracopackage', 'umbracoPackage', 'clean:package_temp']); grunt.registerTask('touchwebconfigifenabled', function() { if (grunt.option("touch")) grunt.task.run("touch:webconfig") }); grunt.registerTask('deploy', ['default', 'copy:deploy', 'touchwebconfigifenabled']); grunt.registerTask('css:build', ['less']); grunt.registerTask('js:build', ['concat']); - grunt.registerTask('default', ['clean', 'css:build', 'js:build', 'copy:build']); + grunt.registerTask('default', ['clean', 'css:build', 'js:build', 'copy:build', 'msbuild:dev']); }; diff --git a/README.md b/README.md index a6e5a9e..9f4208f 100644 --- a/README.md +++ b/README.md @@ -17,26 +17,31 @@ Archetype Add `--touch` to either command to automatically touch the web.config on a deploy -## Installation ## +## Installation +1) Install the package through the Umbraco package installer. Recommended if you want to just see the packaged version. -To use this package right from this repo you will need to manually download and copy the files into the appropriate spot: +OR -###/App_Plugins### +2) Deploy with Grunt (see above). Recommended if you wish to extend this repo as this will inject this project into an existing v7 Umbraco install. -Your /App_Plugins should contain this structure: +OR + +3) Not recommended but a down and dirty way is to manually download and copy the files into the appropriate spot: + +Your /App_Plugins and /bin should contain this structure: /App_Plugins - package.manifest - /css -- archetype.css - /js - -- archetype.js (our Grunt script concats the controller.js and all directives into one file, you may have to do so manually) + -- archetype.js (our Grunt script concats the controller.js/config.controller.js, services and all directives into one file, you will have to do so manually) + -- config.views.js - /views -- archetype.html - -###/bin### -- Drop the Imulus.Archetype.dll in the /bin of your Umbraco install (this dll only handles template helpers, PVC's at present) -- You will have to build this yourself or download it here. + -- archetype.config.html + /bin + -- archetype.dll (you'll have to build it) ## Prevalue Configs ## diff --git a/app/Umbraco/Umbraco.Archetype/Properties/AssemblyInfo.cs b/app/Umbraco/Umbraco.Archetype/Properties/AssemblyInfo.cs index 20fd3a3..73447cf 100644 --- a/app/Umbraco/Umbraco.Archetype/Properties/AssemblyInfo.cs +++ b/app/Umbraco/Umbraco.Archetype/Properties/AssemblyInfo.cs @@ -32,5 +32,6 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("0.1.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] +[assembly: AssemblyInformationalVersion("0.1.0-alpha")] \ No newline at end of file diff --git a/app/controllers/config.controller.js b/app/controllers/config.controller.js index 8a6734f..8bd5bd9 100644 --- a/app/controllers/config.controller.js +++ b/app/controllers/config.controller.js @@ -1,21 +1,25 @@ -angular.module("umbraco").controller("Imulus.ArchetypeConfigController", function ($scope, $http, assetsService, propertyEditorService) { +angular.module("umbraco").controller("Imulus.ArchetypeConfigController", function ($scope, $http, assetsService, propertyEditorResource) { //$scope.model.value = ""; //console.log($scope.model.value); - var newPropertyModel = '{alias: "", remove: false, collapse: false, label: "", helpText: "", view: "", value: "", config: ""}'; - var newFieldsetModel = '{alias: "", remove: false, collapse: false, labelTemplate: "", tooltip: "", icon: "", label: "", headerText: "", footerText: "", properties:[' + newPropertyModel + ']}'; - var defaultFieldsetConfigModel = eval("({showAdvancedOptions: false, hideFieldsetToolbar: false, enableMultipleFieldsets: false, hideFieldsetControls: false, hideFieldsetLabels: false, hidePropertyLabel: false, maxFieldsets: null, fieldsets: [" + newFieldsetModel + "]})"); + //define empty items + var newPropertyModel = '{"alias": "", "remove": false, "collapse": false, "label": "", "helpText": "", "view": "", "value": "", "config": ""}'; + var newFieldsetModel = '{"alias": "", "remove": false, "collapse": false, "labelTemplate": "", "tooltip": "", "icon": "", "label": "", "headerText": "", "footerText": "", "properties": [' + newPropertyModel + ']}'; + var defaultFieldsetConfigModel = JSON.parse('{"showAdvancedOptions": false, "hideFieldsetToolbar": false, "enableMultipleFieldsets": false, "hideFieldsetControls": false, "hideFieldsetLabels": false, "hidePropertyLabel": false, "maxFieldsets": null, "fieldsets": [' + newFieldsetModel + ']}'); + //ini the model $scope.model.value = $scope.model.value || defaultFieldsetConfigModel; + //ini the render model initConfigRenderModel(); - + //get the available views - propertyEditorService.getViews().then(function(data){ + propertyEditorResource.getViews().then(function(data){ $scope.availableViews = data; }); - + + //config for the sorting $scope.sortableOptions = { axis: 'y', cursor: "move", @@ -28,6 +32,7 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio } }; + //function that determines how to manage expanding/collapsing fieldsets $scope.focusFieldset = function(fieldset){ var iniState; @@ -58,9 +63,11 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio fieldset.collapse = !iniState; } } - //ini + + //ini the fieldsets $scope.focusFieldset(); + //function that determines how to manage expanding/collapsing properties $scope.focusProperty = function(properties, property){ var iniState; @@ -101,7 +108,7 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio $scope.archetypeConfigRenderModel = JSON.parse(v); $scope.archetypeConfigRenderModel.toString = stringify; } - }, true); + }); //helper that returns if an item can be removed $scope.canRemoveFieldset = function () @@ -141,6 +148,7 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio return count; } + //determines how many properties are visible function countVisibleProperty(fieldset) { var count = 0; @@ -156,7 +164,7 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio //handles a fieldset add $scope.addFieldsetRow = function ($index, $event) { - $scope.archetypeConfigRenderModel.fieldsets.splice($index + 1, 0, eval("(" + newFieldsetModel + ")")); + $scope.archetypeConfigRenderModel.fieldsets.splice($index + 1, 0, JSON.parse(newFieldsetModel)); $scope.focusFieldset(); } @@ -171,7 +179,7 @@ angular.module("umbraco").controller("Imulus.ArchetypeConfigController", functio //handles a property add $scope.addPropertyRow = function (fieldset, $index) { - fieldset.properties.splice($index + 1, 0, eval("(" + newPropertyModel + ")")); + fieldset.properties.splice($index + 1, 0, JSON.parse(newPropertyModel)); } //rather than splice the archetypeConfigRenderModel, we're hiding this and cleaning onFormSubmitting diff --git a/app/controllers/controller.js b/app/controllers/controller.js index 39beb32..e185504 100644 --- a/app/controllers/controller.js +++ b/app/controllers/controller.js @@ -1,16 +1,17 @@ angular.module("umbraco").controller("Imulus.ArchetypeController", function ($scope, $http, $interpolate, assetsService, angularHelper, notificationsService) { //$scope.model.value = ""; - //set default value of the model - //this works by checking to see if there is a model; then cascades to the default model then to an empty fieldset + //get a reference to the current form var form = angularHelper.getCurrentForm($scope); + //set the config equal to our prevalue config $scope.model.config = $scope.model.config.archetypeConfig; + //ini the model $scope.model.value = $scope.model.value || { fieldsets: [getEmptyRenderFieldset($scope.model.config.fieldsets[0])] }; - //ini + //ini the render model $scope.archetypeRenderModel = {}; initArchetypeRenderModel(); @@ -31,10 +32,7 @@ return parsedTemplate; }; - /* add/remove/sort */ - - //defines the options for the jquery sortable - //i used an ng-model="archetypeRenderModel" so the sort updates the right model + //sort config $scope.sortableOptions = { axis: 'y', cursor: "move", @@ -128,7 +126,7 @@ return (typeof property !== 'undefined') ? property.value : ''; }; - //helper for collapsing + //helper for expanding/collapsing fieldsets $scope.focusFieldset = function(fieldset){ var iniState; @@ -154,24 +152,9 @@ } } - //ini + //ini the fieldset expand/collapse $scope.focusFieldset(); - //helper returns valid JS or null - function getValidJson(variable, json) - { - if(!json) return null; - - try { - return eval("(" + json + ")"); - } - catch (e) { - console.log("There was an error while using 'eval' on " + variable); - console.log(json); - return null; - } - } - //developerMode helpers $scope.archetypeRenderModel.toString = stringify; @@ -228,10 +211,10 @@ }); } - //helper to add an empty fieldset + //helper to add an empty fieldset to the render model function getEmptyRenderFieldset (fieldsetModel) { - return eval("({ alias: '" + fieldsetModel.alias + "', remove: false, properties: []})"); + return JSON.parse('{"alias": "' + fieldsetModel.alias + '", "remove": false, "properties": []}'); } //helper for validation @@ -288,6 +271,7 @@ return validation; } + //helper to lookup validity when given a fieldsetIndex and property alias $scope.getPropertyValidity = function(fieldsetIndex, alias) { if($scope.archetypeRenderModel.fieldsets[fieldsetIndex]) diff --git a/app/directives/archetypeproperty.js b/app/directives/archetypeproperty.js index 06dc6da..3202746 100644 --- a/app/directives/archetypeproperty.js +++ b/app/directives/archetypeproperty.js @@ -2,16 +2,12 @@ function getFieldsetByAlias(fieldsets, alias) { - for (var i in fieldsets) - { - if (fieldsets[i].alias == alias) - { - return fieldsets[i]; - } - } + return _.find(fieldsets, function(fieldset){ + return fieldset.alias == alias; + }); } - function getPropertyIdByAlias(properties, alias) + function getPropertyIndexByAlias(properties, alias) { for (var i in properties) { @@ -21,6 +17,31 @@ } } + //helper that returns a JS ojbect from 'value' string or the original string + function jsonOrString(value, developerMode, debugLabel){ + if(value && typeof value == 'string'){ + try{ + if(developerMode == '1'){ + console.log("Trying to parse " + debugLabel + ": " + value); + } + value = JSON.parse(value); + } + catch(exception) + { + if(developerMode == '1'){ + console.log("Failed to parse " + debugLabel + "."); + } + } + } + + if(value && developerMode == '1'){ + console.log(debugLabel + " post-parsing: "); + console.log(value); + } + + return value; + } + var linker = function (scope, element, attrs) { var configFieldsetModel = getFieldsetByAlias(scope.archetypeConfig.fieldsets, scope.fieldset.alias); @@ -32,44 +53,10 @@ var defaultValue = configFieldsetModel.properties[scope.propertyConfigIndex].value; //try to convert the config to a JS object - if(config && typeof config == 'string'){ - try{ - if(scope.archetypeConfig.developerMode == '1'){ - console.log("Trying to eval config: " + config); - } - config = eval("(" + config + ")"); - } - catch(exception) - { - if(scope.archetypeConfig.developerMode == '1'){ - console.log("Failed to eval config."); - } - } - } - - if(config && scope.archetypeConfig.developerMode == '1'){ - console.log("Config post-eval: " + config); - } + config = jsonOrString(config, scope.archetypeConfig.developerMode, "config"); //try to convert the defaultValue to a JS object - if(defaultValue && typeof defaultValue == 'string'){ - try{ - if(scope.archetypeConfig.developerMode == '1'){ - console.log("Trying to eval default value: " + defaultValue); - } - defaultValue = eval("(" + defaultValue + ")"); - } - catch(exception) - { - if(scope.archetypeConfig.developerMode == '1'){ - console.log("Failed to eval defaultValue."); - } - } - } - - if(defaultValue && scope.archetypeConfig.developerMode == '1'){ - console.log("Default value post-eval: " + defaultValue); - } + defaultValue = jsonOrString(defaultValue, scope.archetypeConfig.developerMode, "defaultValue"); if (view) { @@ -87,12 +74,12 @@ scope.model.config = {}; //ini the property value after test to make sure a prop exists in the renderModel - var renderModelPropertyIndex = getPropertyIdByAlias(scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties, alias); + var renderModelPropertyIndex = getPropertyIndexByAlias(scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties, alias); if (!renderModelPropertyIndex) { - scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties.push(eval("({alias: '" + alias + "', value:'" + defaultValue + "'})")); - renderModelPropertyIndex = getPropertyIdByAlias(scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties, alias); + scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties.push(JSON.parse('{"alias": "' + alias + '", "value": "' + defaultValue + '"}')); + renderModelPropertyIndex = getPropertyIndexByAlias(scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties, alias); } scope.model.value = scope.archetypeRenderModel.fieldsets[scope.fieldsetIndex].properties[renderModelPropertyIndex].value; diff --git a/app/less/archetype.less b/app/less/archetype.less index 9595008..eb06cfc 100644 --- a/app/less/archetype.less +++ b/app/less/archetype.less @@ -1,20 +1,63 @@ .archetypeEditor fieldset{ - border: 1px solid #666; - padding: 5px; + border: 1px solid #dddddd; + padding: 0; margin-bottom: 5px; - background-color: #fff; + background-color: #f1f1f1; clear: both; + display: inline-block; + width: 100%; } -.archetypeEditor label{ - width: 75px; +.archetypeEditor .archetypeFieldsetLabel { + padding: 8px 25px 8px 3px; float: left; + width: 98%; + + span { + font-weight: bold; + } + + &:hover { + cursor: pointer; + + label span { + text-decoration: underline; + } + } + + .caret { + float:left; + margin: 8px 1px 0 7px; + &.caret-right { + border-bottom: 4px solid transparent; + border-top: 4px solid transparent; + border-left: 4px solid #000000; + content: ""; + margin-top: 6px; + } + } + + label{ + width: 75px; + float: left; + } } -.archetypeEditor fieldset>label{ +.archetypeEditor .arechetypeCollapser{ + clear: both; + height: 100%; + margin-top: 10px; + padding-top: 12px; + background-color: #fff; + border-top: 1px solid silver; + border-radius: 0 0 0 5px; +} + +.archetypeEditor fieldset .archetypeFieldsetLabel label{ float: left; - width: 200px; - padding-left: 0; + width: auto; + margin-bottom: 0; + padding: 0; } .archetypeEditor .multiPropertyTextbox{ @@ -23,10 +66,22 @@ .archetypeEditor ul{ list-style: none; + margin-left: 0; } .archetypeEditor .archetypeEditorControls{ float: right; + i:hover { + cursor: pointer; + } +} + +.archetypeEditor form { + margin-left:20px; + + .control-label { + padding-top: 0 !important; + } } .archetypeEditor .archetypeProperty{ @@ -146,10 +201,6 @@ overflow: hidden; } -.archetypeConfig .archetypeFieldsetCollapser{ - margin-top: 10px; -} - .archetypeConfig .archetypeOptions label{ width: 200px; } @@ -187,4 +238,9 @@ margin-bottom: 5px; padding-top: 3px; padding-bottom: 3px; +} + +.ui-sortable-placeholder { + visibility: visible !important; + background-color: #d0e7f1 !important; } \ No newline at end of file diff --git a/app/services/propertyeditor.js b/app/resources/propertyeditor.js similarity index 67% rename from app/services/propertyeditor.js rename to app/resources/propertyeditor.js index 57bf02b..d24a34c 100644 --- a/app/services/propertyeditor.js +++ b/app/resources/propertyeditor.js @@ -1,5 +1,5 @@ -angular.module('umbraco').factory('propertyEditorService', function($q, $http, umbRequestHelper){ - return { +angular.module('umbraco').factory('propertyEditorResource', function($q, $http, umbRequestHelper){ + return { getViews: function() { return umbRequestHelper.resourcePromise( $http.get("/App_Plugins/Archetype/js/config.views.js"), 'Failed to retreive config for views.' diff --git a/app/views/archetype.html b/app/views/archetype.html index c48ff8d..1cba82e 100644 --- a/app/views/archetype.html +++ b/app/views/archetype.html @@ -8,35 +8,51 @@ +