From b524121f8a7f76d30c684c2eeedb3373b85c8ebe Mon Sep 17 00:00:00 2001 From: Tom Fulton Date: Fri, 10 Jan 2014 17:20:35 -0700 Subject: [PATCH] Initial commit --- .gitignore | 14 +++ Gruntfile.js | 84 +++++++++++++ app/controllers/archetype_controller.js | 159 ++++++++++++++++++++++++ app/directives/content_item.js | 47 +++++++ app/less/archetype.less | 40 ++++++ app/package.manifest | 92 ++++++++++++++ app/views/archetype.html | 17 +++ package.json | 16 +++ 8 files changed, 469 insertions(+) create mode 100644 .gitignore create mode 100644 Gruntfile.js create mode 100644 app/controllers/archetype_controller.js create mode 100644 app/directives/content_item.js create mode 100644 app/less/archetype.less create mode 100644 app/package.manifest create mode 100644 app/views/archetype.html create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a02f08f --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +.AppleDouble +.LSOverride +Icon +._* +.Spotlight-V100 +.Trashes +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +dist/ +node_modules/ \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..b727420 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,84 @@ +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + dest: 'dist', + + watch: { + less: { + files: ['app/less/*.less', 'lib/**/*.less'], + tasks: ['less:build'], + options: { + spawn: false, + } + }, + + js: { + files: ['app/**/*.js', 'lib/**/*.js'], + tasks: ['concat', 'jshint'], + options: { + spawn: false, + } + } + }, + + jshint: { + options: { + jshintrc: '.jshintrc' + }, + src: { + src: ['app/**/*.js', 'lib/**/*.js'] + } + }, + + + copy: { + main: { + files: [ + {expand: true, src: ['app/package.manifest'], dest: '<%= dest %>', flatten: true}, + {expand: true, src: ['app/views/archetype.html'], dest: '<%= dest %>/views', flatten: true} + ] + } + }, + + less: { + build: { + options: { + paths: ["app/less", "lib/less", "vendor"], + }, + files: { + '<%= dest %>/css/archetype.css': 'app/less/archetype.less', + } + } + }, + + concat: { + options: { + stripBanners: false + }, + application: { + src: [ + 'app/controllers/archetype_controller.js', + 'app/directives/content_item.js' + ], + dest: '<%= dest %>/js/archetype.js' + } + }, + + clean: ['<%= dest %>'] + + }); + + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-less'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + + + grunt.registerTask('css:build', ['less']); + grunt.registerTask('js:build', ['concat']); + grunt.registerTask('default', ['clean', 'css:build', 'js:build', 'copy']); +}; + diff --git a/app/controllers/archetype_controller.js b/app/controllers/archetype_controller.js new file mode 100644 index 0000000..9926976 --- /dev/null +++ b/app/controllers/archetype_controller.js @@ -0,0 +1,159 @@ +angular.module("umbraco").controller("Imulus.ArchetypeController", function ($scope, $http, assetsService) { + + //$scope.model.value = ""; + + //test config/model + //$scope.model.config.emptyFieldSetModel = '{ remove: false, properties:[{ type: "textbox", options: { label: "Name" }, data: "" }, { type: "contentPicker", options: { label: "Pick some content" }, data: "" }]}'; + + //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 + var validDefaultModel = getValidJson("$scope.model.config.defaultModel", $scope.model.config.defaultModel); + var validEmptyFieldsetModel = getValidJson("$scope.model.config.emptyFieldsetModel", $scope.model.config.emptyFieldsetModel); + + $scope.model.value = $scope.model.value || (validDefaultModel || { fieldsets: [validEmptyFieldsetModel] }); + + //ini + $scope.archetypeRenderModel = {}; + initArchetypeRenderModel(); + + /* add/remove/sort */ + + //defines the options for the jquery sortable + //i used an ng-model="archetypeRenderModel" so the sort updates the right model + $scope.sortableOptions = { + axis: 'y', + cursor: "move", + handle: ".handle", + update: function (ev, ui) { + + }, + stop: function (ev, ui) { + console.log($scope.archetypeRenderModel); + } + }; + + $scope.addRow = function ($index) { + if (true) + { + var validJson = getValidJson("$scope.model.config.emptyFieldsetModel", $scope.model.config.emptyFieldsetModel); + + if (validJson) + { + $scope.archetypeRenderModel.fieldsets.splice($index + 1, 0, validJson); + } + } + } + + //rather than splice the archetypeRenderModel, we're hiding this and cleaning onFormSubmitting + $scope.removeRow = function ($index) { + if ($scope.canRemove()) { + if (confirm('Are you sure you want to remove this?')) { + $scope.archetypeRenderModel.fieldsets[$index].remove = true; + } + } + } + + //helpers for determining if a user can do something + $scope.canAdd = function () + { + if ($scope.model.config.maxProperties) + { + return countVisible() < $scope.model.config.maxProperties; + } + + return true; + } + + $scope.canRemove = function () + { + return countVisible() > 1; + } + + $scope.canSort = function () + { + return countVisible() > 1; + } + + //helper, ini the render model from the server (model.value) + function initArchetypeRenderModel() { + $scope.archetypeRenderModel = $scope.model.value; + } + + //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; + + function stringify() { + return JSON.stringify(this); + } + + //watch for changes + $scope.$watch('archetypeRenderModel', function (v) { + if ($scope.model.config.developerMode) { + console.log(v); + if (typeof v === 'string') { + $scope.archetypeRenderModel = JSON.parse(v); + $scope.archetypeRenderModel.toString = stringify; + } + } + }); + + //helper to count what is visible + function countVisible() + { + var count = 0; + + for (var i in $scope.archetypeRenderModel.fieldsets) { + if ($scope.archetypeRenderModel.fieldsets[i].remove == false) { + count++; + } + } + + return count; + } + + //helper to sync the model to the renderModel + function syncModelToRenderModel() + { + $scope.model.value = { fieldsets: [] }; + + for (var i in $scope.archetypeRenderModel.fieldsets) { + if (!$scope.archetypeRenderModel.fieldsets[i].remove) { + $scope.model.value.fieldsets.push($scope.archetypeRenderModel.fieldsets[i]); + } + } + } + + //sync things up on save + $scope.$on("formSubmitting", function (ev, args) { + syncModelToRenderModel(); + }); + + //custom js + if ($scope.model.config.customJsPath) { + assetsService.loadJs($scope.model.config.customJsPath); + } + + //archetype css + assetsService.loadCss("/App_Plugins/Archetype/css/archetype.css"); + + //custom css + if($scope.model.config.customCssPath) + { + assetsService.loadCss($scope.model.config.customCssPath); + } +}); diff --git a/app/directives/content_item.js b/app/directives/content_item.js new file mode 100644 index 0000000..00dc566 --- /dev/null +++ b/app/directives/content_item.js @@ -0,0 +1,47 @@ +angular.module("umbraco").directive('contentItem', function ($compile, $http) { + + var linker = function (scope, element, attrs) { + + if (scope.content.view) + { + $http.get(scope.content.view).success(function (data) { + if (data) { + var rawTemplate = data; + + //define the initial model and config + scope.model = {}; + scope.model.config = {}; + + //pull these from the content + scope.model.value = scope.content.value; + scope.model.config = scope.content.config; + + //some items need an alias + scope.model.alias = "scope-" + scope.$id; + + //watch for changes since there is no two-way binding with the child values + scope.$watch('model.value', function (newValue, oldValue) { + scope.content.value = newValue; + }); + + //add label + if (true) { + rawTemplate = '' + rawTemplate; + } + + element.html(rawTemplate).show(); + $compile(element.contents())(scope); + } + }); + } + } + + return { + restrict: "E", + rep1ace: true, + link: linker, + scope: { + content: '=' + } + } +}); \ No newline at end of file diff --git a/app/less/archetype.less b/app/less/archetype.less new file mode 100644 index 0000000..cf86d0d --- /dev/null +++ b/app/less/archetype.less @@ -0,0 +1,40 @@ +.archetypeEditor fieldset{ + border: 1px solid #666; + padding: 5px; + margin-bottom: 5px; + background-color: #fff; +} + +.archetypeEditor label{ + width: 75px; + float: left; +} + +.archetypeEditor .multiPropertyTextbox{ + margin-bottom: 3px; +} + +.archetypeEditor ul{ + list-style: none; +} + +.archetypeEditor .archetypeEditorControls{ + float: right; +} + +.archetypeEditor .archetypeProperty{ + clear: both; +} + +.archetypeEditor .umb-contentpicker{ + margin-left: 60px; + padding-left: 25px; +} + +.archetypeEditor .archetypeDeveloperModel +{ + width: 98%; + min-height: 100px; + margin-bottom: 10px; + border: 1px solid red; +} \ No newline at end of file diff --git a/app/package.manifest b/app/package.manifest new file mode 100644 index 0000000..2b33ac1 --- /dev/null +++ b/app/package.manifest @@ -0,0 +1,92 @@ +{ + propertyEditors: [ + { + alias: "Archetype", + name: "Archetype", + editor: { + view: "~/App_Plugins/Archetype/views/archetype.html", + valueType: "JSON" + }, + prevalues: { + fields: [ + { + label: "Empty Fieldset Model", + description: "(Required) What would you like a new fieldset model to be?", + key: "emptyFieldsetModel", + view: "textarea", + validation: [ + + ] + }, + { + label: "Hide editor controls?", + description: "Hide the add/remove/sort controls", + key: "hideControls", + view: "boolean", + validation: [ + + ] + }, + { + label: "Default Model", + description: "(Optional) What would you the default model to be? Otherwise the model will defer to the Empty Fieldset Model for new items.", + key: "defaultModel", + view: "textarea", + validation: [ + + ] + }, + { + label: "Max Fieldsets", + description: "(Optional) How many Fieldsets are allowed? Entering '1' will disable the controls. Default is unlimited.", + key: "maxProperties", + view: "number", + validation: [ + + ] + }, + { + label: "Custom Class", + description: "(Optional) Enter a custom CSS class.", + key: "customCssClass", + view: "textstring", + validation: [ + + ] + }, + { + label: "CSS File", + description: "(Optional) Enter a path for a custom CSS file to be included on the page.", + key: "customCssPath", + view: "textstring", + validation: [ + + ] + }, + { + label: "JS File", + description: "(Optional) Enter a path for a custom JS file to be included on the page.", + key: "customJsPath", + view: "textstring", + validation: [ + + ] + }, + { + label: "Developer Mode", + description: "Turn on for developer options.", + key: "developerMode", + view: "boolean", + validation: [ + + ] + } + ] + } + } + ] + , + javascript: [ + '~/App_Plugins/Archetype/js/archetype.js' + ] +} \ No newline at end of file diff --git a/app/views/archetype.html b/app/views/archetype.html new file mode 100644 index 0000000..939f480 --- /dev/null +++ b/app/views/archetype.html @@ -0,0 +1,17 @@ +
+ + +
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..209ff87 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "archetype", + "version": "0.0.0", + "devDependencies": { + "grunt": "~0.4.2", + "bower": "~1.2.8", + "grunt-contrib-copy": "~0.5.0", + "grunt-contrib-less": "~0.8.3", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-uglify": "~0.2.7", + "grunt-contrib-watch": "~0.5.3", + "grunt-contrib-jshint": "~0.7.2", + "grunt-cli": "~0.1.11", + "grunt-contrib-clean": "~0.5.0" + } +}