diff --git a/.gitignore b/.gitignore index 3620798..8e469a5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ tmp/** docs** .idea node_modules +testee.log \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +.idea/ diff --git a/.travis.yml b/.travis.yml index c2ba3f9..20fd86b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,3 @@ language: node_js node_js: - - 0.8 \ No newline at end of file + - 0.10 diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..dfaf0a6 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,44 @@ +module.exports = function (grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + connect: { + server: { + options: { + port: 9001, + base: '.' + } + } + }, + qunit: { + all: { + options: { + urls: ['http://localhost:9001/test/index.html'] + } + } + }, + concat: { + dist: { + src: ['src/dform.js', 'src/dform.core.js', 'src/dform.extensions.js'], + dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js' + } + }, + uglify: { + options: { + banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' + }, + build: { + src: ['dist/<%= pkg.name %>-<%= pkg.version %>.js'], + dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.min.js' + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-contrib-connect'); + + + grunt.registerTask('test', ['connect', 'qunit']); + grunt.registerTask('default', ['concat', 'uglify']); +}; diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..c0384b6 --- /dev/null +++ b/bower.json @@ -0,0 +1,12 @@ +{ + "name": "jquery.dform", + "version": "1.1.0", + "main": "dist/jquery.dform-1.1.0.js", + "ignore": [ + ".jshintrc", + "**/*.txt" + ], + "dependencies": { + "jquery": ">= 1.7.1" + } +} \ No newline at end of file diff --git a/dist/jquery.dform-1.1.0.js b/dist/jquery.dform-1.1.0.js new file mode 100644 index 0000000..2d9adbf --- /dev/null +++ b/dist/jquery.dform-1.1.0.js @@ -0,0 +1,865 @@ +/* + * jQuery dform plugin + * Copyright (C) 2012 David Luecke , [http://daffl.github.com/jquery.dform] + * + * Licensed under the MIT license + */ +(function ($) { + var _subscriptions = {}, + _types = {}, + each = $.each, + addToObject = function (obj) { + var result = function (data, fn, condition) { + if (typeof data === 'object') { + $.each(data, function (name, val) { + result(name, val, condition); + }); + } else if (condition === undefined || condition === true) { + if (!obj[data]) { + obj[data] = []; + } + obj[data].push(fn); + } + } + return result; + }, + isArray = $.isArray, + /** + * Returns an array of keys (properties) contained in the given object. + * + * @param {Object} object The object to use + * @return {Array} An array containing all properties in the object + */ + keyset = function (object) { + return $.map(object, function (val, key) { + return key; + }); + }, + /** + * Returns an object that contains all values from the given + * object that have a key which is also in the array keys. + * + * @param {Object} object The object to traverse + * @param {Array} keys The keys the new object should contain + * @return {Object} A new object containing only the properties + * with names given in keys + */ + withKeys = function (object, keys) { + var result = {}; + each(keys, function (index, value) { + if (object[value]) { + result[value] = object[value]; + } + }); + return result; + }, + /** + * Returns an object that contains all value from the given + * object that do not have a key which is also in the array keys. + * + * @param {Object} object The object to traverse + * @param {Array} keys A list of keys that should not be contained in the new object + * @return {Object} A new object with all properties of the given object, except + * for the ones given in the list of keys + */ + withoutKeys = function (object, keys) { + var result = {}; + each(object, function (index, value) { + if (!~$.inArray(index, keys)) { + result[index] = value; + } + }); + return result; + }, + /** + * Run all subscriptions with the given name and options + * on an element. + * + * @param {String} name The name of the subscriber function + * @param {Object} options ptions for the function + * @param {String} type The type of the current element as in the registered types + * @return {Object} The jQuery object + */ + runSubscription = function (name, options, type) { + if ($.dform.hasSubscription(name)) { + this.each(function () { + var element = $(this); + each(_subscriptions[name], function (i, sfn) { + // run subscriber function with options + sfn.call(element, options, type); + }); + }); + } + return this; + }, + /** + * Run all subscription functions with given options. + * + * @param {Object} options The options to use + * @return {Object} The jQuery element this function has been called on + */ + runAll = function (options) { + var type = options.type, self = this; + // Run preprocessing subscribers + this.dform('run', '[pre]', options, type); + each(options, function (name, sopts) { + self.dform('run', name, sopts, type); + }); + // Run post processing subscribers + this.dform('run', '[post]', options, type); + return this; + }; + + /** + * Globals added directly to the jQuery object + */ + $.extend($, { + keyset : keyset, + withKeys : withKeys, + withoutKeys : withoutKeys, + dform : { + /** + * Default options the plugin is initialized with: + * + * ## prefix + * + * The Default prefix used for element classnames generated by the dform plugin. + * Defaults to _ui-dform-_ + * E.g. an element with type text will have the class ui-dform-text + * + */ + options : { + prefix : "ui-dform-" + }, + + /** + * A function that is called, when no registered type has been found. + * The default behaviour returns an HTML element with the tag + * as specified in type and the HTML attributes given in options + * (without subscriber options). + * + * @param {Object} options + * @return {Object} The created object + */ + defaultType : function (options) { + return $("<" + options.type + ">").dform('attr', options); + }, + /** + * Return all types. + * + * @params {String} name (optional) If passed return + * all type generators for a given name. + * @return {Object} Mapping from type name to + * an array of generator functions. + */ + types : function (name) { + return name ? _types[name ] : _types; + }, + /** + * Register an element type function. + * + * @param {String|Array} data Can either be the name of the type + * function or an object that contains name : type function pairs + * @param {Function} fn The function that creates a new type element + */ + addType : addToObject(_types), + /** + * Returns all subscribers or all subscribers for a given name. + * + * @params {String} name (optional) If passed return all + * subscribers for a given name + * @return {Object} Mapping from subscriber names + * to an array of subscriber functions. + */ + subscribers : function (name) { + return name ? _subscriptions[name] : _subscriptions; + }, + /** + * Register a subscriber function. + * + * @param {String|Object} data Can either be the name of the subscriber + * function or an object that contains name : subscriber function pairs + * @param {Function} fn The function to subscribe or nothing if an object is passed for data + * @param {Array} deps An optional list of dependencies + */ + subscribe : addToObject(_subscriptions), + /** + * Returns if a subscriber function with the given name + * has been registered. + * + * @param {String} name The subscriber name + * @return {Boolean} True if the given name has at least one subscriber registered, + * false otherwise + */ + hasSubscription : function (name) { + return _subscriptions[name] ? true : false; + }, + /** + * Create a new element. + * + * @param {Object} options - The options to use + * @return {Object} The element as created by the builder function specified + * or returned by the defaultType function. + */ + createElement : function (options) { + if (!options.type) { + throw "No element type given! Must always exist."; + } + var type = options.type, + element = null, + // We don't need the type key in the options + opts = $.withoutKeys(options, ["type"]); + + if (_types[type]) { + // Run all type element builder functions called typename + each(_types[type], function (i, sfn) { + element = sfn.call(element, opts); + }); + } else { + // Call defaultType function if no type was found + element = $.dform.defaultType(options); + } + return $(element); + }, + methods : { + /** + * Run all subscriptions with the given name and options + * on an element. + * + * @param {String} name The name of the subscriber function + * @param {Object} options ptions for the function + * @param {String} type The type of the current element as in the registered types + * @return {Object} The jQuery object + */ + run : function (name, options, type) { + if (typeof name !== 'string') { + return runAll.call(this, name); + } + return runSubscription.call(this, name, options, type); + }, + /** + * Creates a form element on an element with given options + * + * @param {Object} options The options to use + * @return {Object} The jQuery element this function has been called on + */ + append : function (options, converter) { + if (converter && $.dform.converters && $.isFunction($.dform.converters[converter])) { + options = $.dform.converters[converter](options); + } + // Create element (run builder function for type) + var element = $.dform.createElement(options); + this.append(element); + // Run all subscriptions + element.dform('run', options); + }, + /** + * Adds HTML attributes to the current element from the given options. + * Any subscriber will be omitted so that the attributes will contain any + * key value pair where the key is not the name of a subscriber function + * and is not in the string array excludes. + * + * @param {Object} object The attribute object + * @param {Array} excludes A list of keys that should also be excluded + * @return {Object} The jQuery object of the this reference + */ + attr : function (object, excludes) { + // Ignore any subscriber name and the objects given in excludes + var ignores = $.keyset(_subscriptions); + isArray(excludes) && $.merge(ignores, excludes); + this.attr($.withoutKeys(object, ignores)); + }, + /** + * + * + * @param params + * @param success + * @param error + */ + ajax : function (params, success, error) { + var options = { + error : error, + url : params + }, self = this; + if (typeof params !== 'string') { + $.extend(options, params); + } + options.success = function (data) { + var callback = success || params.success; + self.dform(data); + if(callback) { + callback.call(self, data); + } + } + $.ajax(options); + }, + /** + * + * + * @param options + */ + init : function (options, converter) { + var opts = options.type ? options : $.extend({ "type" : "form" }, options); + if (converter && $.dform.converters && $.isFunction($.dform.converters[converter])) { + opts = $.dform.converters[converter](opts); + } + if (this.is(opts.type)) { + this.dform('attr', opts); + this.dform('run', opts); + } else { + this.dform('append', opts); + } + } + } + } + }); + + /** + * The jQuery plugin function + * + * @param options The form options + * @param {String} converter The name of the converter in $.dform.converters + * that will be used to convert the options + */ + $.fn.dform = function (options, converter, error) { + var self = $(this); + if ($.dform.methods[options]) { + $.dform.methods[options].apply(self, Array.prototype.slice.call(arguments, 1)); + } else { + if (typeof options === 'string') { + $.dform.methods.ajax.call(self, { + url : options, + dataType : 'json' + }, converter, error); + } else { + $.dform.methods.init.apply(self, arguments); + } + } + return this; + } +})(jQuery); + +/* + * jQuery dform plugin + * Copyright (C) 2012 David Luecke , [http://daffl.github.com/jquery.dform] + * + * Licensed under the MIT license + */ +(function ($) { + var each = $.each, + _element = function (tag, excludes) { + return function (ops) { + return $(tag).dform('attr', ops, excludes); + }; + }, + _html = function (options, type) { + var self = this; + if ($.isPlainObject(options)) { + self.dform('append', options); + } else if ($.isArray(options)) { + each(options, function (index, nested) { + self.dform('append', nested); + }); + } else { + self.html(options); + } + }; + + $.dform.addType({ + container : _element("
"), + text : _element(''), + password : _element(''), + submit : _element(''), + reset : _element(''), + hidden : _element(''), + radio : _element(''), + checkbox : _element(''), + file : _element(''), + number : _element(''), + url : _element(''), + tel : _element(''), + email : _element(''), + checkboxes : _element("
", ["name"]), + radiobuttons : _element("
", ["name"]) + }); + + $.dform.subscribe({ + /** + * Adds a class to the current element. + * Ovverrides the default behaviour which would be replacing the class attribute. + * + * @param options A list of whitespace separated classnames + * @param type The type of the *this* element + */ + "class" : function (options, type) { + this.addClass(options); + }, + + /** + * Sets html content of the current element + * + * @param options The html content to set as a string + * @param type The type of the *this* element + */ + "html" : _html, + + /** + * Recursively appends subelements to the current form element. + * + * @param options Either an object with key value pairs + * where the key is the element name and the value the + * subelement options or an array of objects where each object + * is the options for a subelement + * @param type The type of the *this* element + */ + "elements" : _html, + + /** + * Sets the value of the current element. + * + * @param options The value to set + * @param type The type of the *this* element + */ + "value" : function (options) { + this.val(options); + }, + + /** + * Set CSS styles for the current element + * + * @param options The Styles to set + * @param type The type of the *this* element + */ + "css" : function (options) { + this.css(options); + }, + + /** + * Adds options to select type elements or radio and checkbox list elements. + * + * @param options A key value pair where the key is the + * option value and the value the options text or the settings for the element. + * @param type The type of the *this* element + */ + "options" : function (options, type) { + var self = this; + // Options for select elements + if ((type === "select" || type === "optgroup") && typeof options !== 'string') + { + each(options, function (value, content) { + var option = { type : 'option', value : value }; + if (typeof (content) === "string") { + option.html = content; + } + if (typeof (content) === "object") { + option = $.extend(option, content); + } + self.dform('append', option); + }); + } + else if (type === "checkboxes" || type === "radiobuttons") { + // Options for checkbox and radiobutton lists + each(options, function (value, content) { + var boxoptions = ((type === "radiobuttons") ? { "type" : "radio" } : { "type" : "checkbox" }); + if (typeof(content) === "string") { + boxoptions["caption"] = content; + } else { + $.extend(boxoptions, content); + } + boxoptions["value"] = value; + self.dform('append', boxoptions); + }); + } + }, + + /** + * Adds caption to elements. + * + * Depending on the element type the following elements will + * be used: + * - A legend for
elements + * - A
+**url** *{String|Object}*
+The `url` subscriber issues a `$(element).dform('ajax', options)` request to load content from remote files. + + { + "type" : "div", + "url": "form.json" + } + **type** *{String}*
Besides looking up the correct Type Generator it also adds a dform specific class to the element using `$.dform.options.prefix` (*ui-dform-* by default) and the type name. @@ -436,9 +463,9 @@ Append the dForm object to each selected element. If the element is of the same a `type : 'form'` on a `
`) or if no type has been given run the subscribers and add the attributes on the current element. Optionally use a converter with a given name. -**$(form).dform(url \[, success\])** *{String}* *{Function}*
-Load a JSON form definition using GET from a given URL and execute a success handler when it returns. -The handler gets the data passed and has `this` refer to the form element. +**$(form).dform(url \[, success\], \[, error\])** *{String}* *{Function}* *{Function}*
+Load a JSON form definition using GET from a given URL and execute a success handler when it returns +or an error handler if the request faiuls. The handler gets the data passed and has `this` refer to the form element. **$(form).dform('run', options)** *{Object}*
Run all subscribers from a given dForm object on the selected element(s). @@ -569,7 +596,7 @@ The caption in each entries element will be used as the tab heading. You can eit of entries and set the *id* attribute individually or an object which will use the key name as the id: { - "type" : "accordion", + "type" : "tabs", "entries" : [ { "caption" : "Tab 1", @@ -587,7 +614,7 @@ of entries and set the *id* attribute individually or an object which will use t Which is equivalent to: { - "type" : "accordion", + "type" : "tabs", "entries" : { "first": { "caption" : "Tab 1", @@ -597,7 +624,7 @@ Which is equivalent to: "caption" : "Tab 2", "html" : "Content 2" } - ] + } } ### Subscribers @@ -698,6 +725,20 @@ Generates: ## Changelog +__1.1.0__ + +* Added `url` subscriber ([#22](https://github.com/daffl/jquery.dform/issues/22)) +* Better Ajax support ([#35](https://github.com/daffl/jquery.dform/issues/35)) +* Added dist files to GitHub pages for better CDN support +* Registered bower package: `bower install jquery.dform` + +__1.0.1__ + +* Updated and fixed documentation +* Added [jQuery plugin](http://plugins.jquery.com) deploy information +* Moved away from GitHub downloads +* Verified jQuery 1.9.0 compatibility + __1.0.0__ * Improved documentation using DocumentUp @@ -722,7 +763,6 @@ support to set up a validate options for validate() in "form" type __0.1.3__ * Created some public [JSFiddles for trying the plugin](http://jsfiddle.net/user/Daff/fiddles) -* Created [jQuery.dForm Google Group](http://groups.google.com/group/jquery-dform) * Added *form* type, unified *buildForm* usage * Fixed [issue #14](https://github.com/daffl/jquery.dform/issues/closed#issue/14), setting type attribute properly in IE * Added *getValueAt* @@ -754,7 +794,7 @@ __0.1__ ## License -Copyright (C) 2012 [David Luecke](http://daffl.github.com), [http://daffl.github.com/jquery.dform] +Copyright (C) 2013 [David Luecke](http://daffl.github.com), [http://daffl.github.com/jquery.dform] The MIT license: diff --git a/src/dform.core.js b/src/dform.core.js index 35fe916..4f788cb 100644 --- a/src/dform.core.js +++ b/src/dform.core.js @@ -195,10 +195,9 @@ * @param options * @param type */ - "url" : function (options, type) { - // TODO this.buildForm(options); + "url" : function (options) { + this.dform('ajax', options); }, - /** * Post processing function, that will run whenever all other subscribers are finished. * diff --git a/src/dform.js b/src/dform.js index 77f85f6..5d4d789 100644 --- a/src/dform.js +++ b/src/dform.js @@ -30,7 +30,7 @@ * @param {Object} object The object to use * @return {Array} An array containing all properties in the object */ - keyset = function (object) { + keyset = function (object) { return $.map(object, function (val, key) { return key; }); @@ -44,7 +44,7 @@ * @return {Object} A new object containing only the properties * with names given in keys */ - withKeys = function (object, keys) { + withKeys = function (object, keys) { var result = {}; each(keys, function (index, value) { if (object[value]) { @@ -62,7 +62,7 @@ * @return {Object} A new object with all properties of the given object, except * for the ones given in the list of keys */ - withoutKeys = function (object, keys) { + withoutKeys = function (object, keys) { var result = {}; each(object, function (index, value) { if (!~$.inArray(index, keys)) { @@ -80,7 +80,7 @@ * @param {String} type The type of the current element as in the registered types * @return {Object} The jQuery object */ - runSubscription = function (name, options, type) { + runSubscription = function (name, options, type) { if ($.dform.hasSubscription(name)) { this.each(function () { var element = $(this); @@ -98,7 +98,7 @@ * @param {Object} options The options to use * @return {Object} The jQuery element this function has been called on */ - runAll = function (options) { + runAll = function (options) { var type = options.type, self = this; // Run preprocessing subscribers this.dform('run', '[pre]', options, type); @@ -285,9 +285,10 @@ $.extend(options, params); } options.success = function (data) { + var callback = success || params.success; self.dform(data); - if(success) { - success.call(self, data); + if(callback) { + callback.call(self, data); } } $.ajax(options); diff --git a/start.html b/start.html index cacc42b..2436b58 100644 --- a/start.html +++ b/start.html @@ -18,8 +18,8 @@

 
 
-
-
+
+
 
 
-	
-  
+	
+  
 
   
-  
+  
 
-	
+	
 	
 	
 	
diff --git a/test/test.js b/test/test.js
index 9c2a354..5f993bb 100644
--- a/test/test.js
+++ b/test/test.js
@@ -56,12 +56,15 @@ $(document).ready(function () {
 		equal(created.data('test'), 'Test data attribute', 'Added test data attribute');
 	});
 
-	test("Ajax", 2, function() {
+	test("Ajax", 3, function() {
 		stop();
 		$('
').dform('test.json', function(data) { equal(data.html, 'The test', 'Data passed to success callback'); equal(this.html(), 'The test', 'Form got created'); - start(); + $('
').dform('missing.json', function() {}, function() { + ok(true, 'Error handler called'); + start(); + }); }); }) }); diff --git a/test/test_subscribers.js b/test/test_subscribers.js index 78368b2..f91bafa 100644 --- a/test/test_subscribers.js +++ b/test/test_subscribers.js @@ -36,7 +36,7 @@ $(document).ready(function () { ok(div.hasClass('test'), 'Class set'); ok(div.hasClass($.dform.options.prefix + 'container'), 'Class set'); equal(text.val(), 'Test value', 'Value set'); - equal(text.css('background-color'), 'red', 'Css set'); + ok(text.css('background-color'), 'Css set'); }); test("elements and html", function () { @@ -70,9 +70,9 @@ $(document).ready(function () { test("caption", function () { var simple = $('
').dform({ - type : 'text', - caption : 'The test' - }).find('label'), + type : 'text', + caption : 'The test' + }).find('label'), asElement = $('
').dform({ type : 'text', caption : { @@ -89,9 +89,9 @@ $(document).ready(function () { test("options", function () { var options = { - test1 : 'Test 1', - test2 : 'Test 2' - }, + test1 : 'Test 1', + test2 : 'Test 2' + }, select = $('