├── VERSION ├── packages └── ember-forms │ ├── lib │ ├── controls.js │ ├── helpers.js │ ├── fields │ │ ├── textarea.js │ │ ├── text.js │ │ ├── password.js │ │ ├── select.js │ │ ├── base.js │ │ └── date.js │ ├── main.js │ ├── fields.js │ ├── labels.js │ ├── buttons.js │ ├── helpers │ │ ├── buttons.js │ │ └── field.js │ ├── label.js │ ├── core.js │ ├── controls │ │ └── unbound_select.js │ └── form.js │ ├── tests │ ├── controls │ │ └── unbound_select.js │ ├── fields │ │ ├── select.js │ │ └── date.js │ └── integration.js │ └── package.json ├── .gitignore ├── .travis.yml ├── project.json ├── generators └── license.js ├── Gemfile ├── config.ru ├── LICENSE ├── .jshintrc ├── tests ├── minispade.js ├── qunit │ ├── run-qunit.js │ ├── qunit.css │ └── qunit.js ├── index.html └── handlebars.js ├── Gemfile.lock ├── README.md ├── lib └── github_uploader.rb ├── Assetfile └── Rakefile /VERSION: -------------------------------------------------------------------------------- 1 | 0.5.0.pre 2 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/controls.js: -------------------------------------------------------------------------------- 1 | require('ember-forms/controls/unbound_select'); 2 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/helpers.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/helpers/buttons"); 2 | require("ember-forms/helpers/field"); 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .bundle 3 | tmp/ 4 | tests/source/ 5 | dist/ 6 | 7 | .DS_Store 8 | .project 9 | 10 | tests/ember-forms-tests.js 11 | .github-upload-token 12 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/fields/textarea.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/fields/base"); 2 | 3 | EF.TextareaField = EF.BaseField.extend({ 4 | InputView: Ember.TextArea 5 | }); 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.3 3 | bundler_args: --without development 4 | before_script: 5 | - "export DISPLAY=:99.0" 6 | - "sh -e /etc/init.d/xvfb start" 7 | - "rake clean" 8 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/fields/text.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/fields/base"); 2 | 3 | EF.TextField = EF.BaseField.extend({ 4 | InputView: Ember.TextField.extend({ 5 | attributeBindings: ['name', 'placeholder'] 6 | }) 7 | }); 8 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/main.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/core"); 2 | require("ember-forms/labels"); 3 | require("ember-forms/controls"); 4 | require("ember-forms/fields"); 5 | require("ember-forms/form"); 6 | require("ember-forms/helpers"); 7 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/fields.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/fields/text"); 2 | require("ember-forms/fields/textarea"); 3 | require("ember-forms/fields/select"); 4 | require("ember-forms/fields/date"); 5 | require("ember-forms/fields/password"); 6 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/fields/password.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/fields/base"); 2 | 3 | EF.PasswordField = EF.BaseField.extend({ 4 | InputView: Ember.TextField.extend({ 5 | attributeBindings: ['name', 'placeholder'], 6 | type: 'password' 7 | }) 8 | }); 9 | -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-forms-project", 3 | "bpm": "1.0.0", 4 | "dependencies": { 5 | "ember-forms": ">= 0", 6 | "ember": ">= 0" 7 | }, 8 | 9 | "bpm:build": { 10 | "bpm_libs.js": { 11 | "minifier": "uglify-js" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /generators/license.js: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Project: Ember Form 3 | // Copyright: ©2012 Josep Jaume Rey and Contributors 4 | // License: Licensed under MIT license (see license.js) 5 | // ========================================================================== 6 | 7 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/labels.js: -------------------------------------------------------------------------------- 1 | EF.Labels = Ember.Object.create({ 2 | months: Ember.A(["January", "February", "March", "April", "May", 3 | "June", "July", "August", "September", "October", "November", 4 | "December"]), 5 | dayPrompt: '- Day -', 6 | monthPrompt: '- Month -', 7 | yearPrompt: '- Year -', 8 | }); 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "rake-pipeline", :git => "https://github.com/livingsocial/rake-pipeline.git" 4 | gem "rake-pipeline-web-filters", :git => "https://github.com/wycats/rake-pipeline-web-filters.git" 5 | gem "colored" 6 | gem "uglifier", "~> 1.0.3" 7 | 8 | group :development do 9 | gem "rack" 10 | gem "kicker" 11 | gem 'rest-client' 12 | gem 'github_api' 13 | end 14 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'rake-pipeline' 3 | require 'rake-pipeline/middleware' 4 | 5 | class NoCache 6 | def initialize(app) 7 | @app = app 8 | end 9 | 10 | def call(env) 11 | @app.call(env).tap do |status, headers, body| 12 | headers["Cache-Control"] = "no-store" 13 | end 14 | end 15 | end 16 | 17 | use NoCache 18 | use Rake::Pipeline::Middleware, "Assetfile" 19 | run Rack::Directory.new('.') 20 | -------------------------------------------------------------------------------- /packages/ember-forms/tests/controls/unbound_select.js: -------------------------------------------------------------------------------- 1 | test("adds the proper options to an unbound select", function(){ 2 | 3 | var unboundSelect = EF.UnboundSelect.create({ 4 | prompt: 'Hola', 5 | content: [1, 2, 3] 6 | }); 7 | 8 | Ember.run(function(){ 9 | unboundSelect.appendTo("#qunit-fixture"); 10 | unboundSelect.set('value', 2); 11 | }); 12 | 13 | equal(unboundSelect.$("option").length, 4, "renders three options"); 14 | var selectedOption = unboundSelect.$("option[selected]"); 15 | equal(selectedOption.text(), "2", "it selects an element"); 16 | 17 | Ember.run(function(){ 18 | unboundSelect.destroy(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/ember-forms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-forms", 3 | "summary": "Short package description", 4 | "description": "", 5 | "homepage": "https://github.com/josepjaume/ember-forms", 6 | "author": "Josep Jaume", 7 | "version": "0.0.1.pre", 8 | 9 | "directories": { 10 | "lib": "lib" 11 | }, 12 | 13 | "dependencies": { 14 | "spade": "~> 1.0", 15 | "ember-runtime": ">= 0" 16 | }, 17 | "dependencies:development": { 18 | "spade-qunit": "~> 1.0.0" 19 | }, 20 | "bpm:build": { 21 | "bpm_libs.js": { 22 | "files": ["lib"], 23 | "modes": "*" 24 | }, 25 | "ember-forms/bpm_tests.js": { 26 | "files": ["tests"], 27 | "modes": ["debug"] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/buttons.js: -------------------------------------------------------------------------------- 1 | var findFormRecursively = EF.findFormRecursively; 2 | 3 | /** 4 | @class 5 | Represents a submit button. 6 | 7 | * name: The button text 8 | */ 9 | EF.SubmitButton = Ember.View.extend({ 10 | tagName: 'button', 11 | attributeBindings: ['type'], 12 | type: 'submit', 13 | name: Ember.computed(function(){ return this.get('parentView.submitName'); }), 14 | template: Ember.Handlebars.compile("{{view.name}}") 15 | }); 16 | 17 | EF.Buttons = Ember.ContainerView.extend({ 18 | classNames: ['buttons'], 19 | childViews: [EF.SubmitButton], 20 | form: Ember.computed(function(){ return findFormRecursively(this); }), 21 | submitName: Ember.computed(function(){ return this.get('form.submitName'); }) 22 | }); 23 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/fields/select.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/fields/base"); 2 | 3 | EF.SelectField = EF.BaseField.extend({ 4 | InputView: Ember.Select.extend({ 5 | init: function(){ 6 | var labelPath = this.get('field.optionLabelPath'), 7 | promptValue = this.get('field.prompt'), 8 | valuePath = this.get('field.optionValuePath'); 9 | 10 | if(labelPath){ this.set('optionLabelPath', 'content.' + labelPath); } 11 | if(valuePath){ this.set('optionValuePath', 'content.' + valuePath); } 12 | if(promptValue){ this.set('prompt', this.get('field.prompt')); } 13 | 14 | this._super(); 15 | }, 16 | content: Ember.computed(function(){ 17 | return this.get('field.content') || Ember.A([]); 18 | }).property('field.content') 19 | }) 20 | }); 21 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/helpers/buttons.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/buttons"); 2 | 3 | var findFormRecursively = EF.findFormRecursively, 4 | findField = EF.findField; 5 | 6 | EF.ButtonHelper = Ember.Object.create({ 7 | helper: function(view, options){ 8 | var buttonView = EF.Buttons; 9 | var currentView = options.data.view; 10 | currentView.appendChild(buttonView, options.hash); 11 | } 12 | }); 13 | 14 | /** 15 | A helper to be used in a handlebars template. Will generate a submit button 16 | that intented to trigger the form submission. Usage: 17 | 18 | {{ form buttons }} 19 | 20 | It accepts the following options: 21 | 22 | * name: The button's text 23 | */ 24 | Ember.Handlebars.registerHelper('form', function(name, options){ 25 | if(name === 'buttons'){ 26 | EF.ButtonHelper.helper(this, options); 27 | }else{ 28 | throw "Unknown " + name + " in form helper"; 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/label.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/core"); 2 | 3 | var findFieldRecursively = EF.findFieldRecursively, 4 | findFormRecursively = EF.findFormRecursively; 5 | 6 | /** 7 | @class 8 | @private 9 | 10 | Represents an input's label. Depends on the following attributes: 11 | 12 | * name: The label name. Will fallback to the raw field name 13 | 14 | @extends Ember.View 15 | */ 16 | EF.Label = Ember.View.extend({ 17 | tagName: 'label', 18 | attributeBindings: ['for'], 19 | template: Ember.Handlebars.compile("{{view.name}}"), 20 | field: Ember.computed(function(){ return findFieldRecursively(this); }), 21 | form: Ember.computed(function(){ return this.get('field.formView'); }), 22 | name: Ember.computed(function(){ 23 | return this.get('field.label') || this.get('field.name'); 24 | }), 25 | didInsertElement: function(){ 26 | // We bind it here to avoid re-rendering before the element was inserted 27 | Ember.bind(this, 'for', 'component.inputView.elementId'); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/helpers/field.js: -------------------------------------------------------------------------------- 1 | var findFormRecursively = EF.findFormRecursively, 2 | findField = EF.findField; 3 | 4 | EF.FieldHelper = Ember.Object.create({ 5 | helper: function(view, name, options){ 6 | var optionsHash = options.hash, 7 | type = optionsHash.as, 8 | currentView = options.data.view, 9 | fieldView = findField(type); 10 | 11 | if(Ember.isEmpty(optionsHash.name)){ optionsHash.name = name; } 12 | delete(optionsHash.as); 13 | currentView.appendChild(fieldView, optionsHash); 14 | } 15 | }); 16 | 17 | /** 18 | A handlebars helper that will render a field with its input and label. 19 | 20 | {{ field name }} 21 | 22 | It accepts the following options: 23 | 24 | * as: The field type. Defaults to `text`. Can be `text`, `textarea` and 25 | `select`. 26 | 27 | Any other options will be passed to the particular field instance and may 28 | modify its behavior. 29 | */ 30 | Ember.Handlebars.registerHelper('field', function(name, options){ 31 | EF.FieldHelper.helper(this, name, options); 32 | }); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by LivingSocial, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "console", 4 | "Ember", 5 | "EF", 6 | "Handlebars", 7 | "Metamorph", 8 | "ember_assert", 9 | "ember_warn", 10 | "ember_deprecate", 11 | "ember_deprecateFunc", 12 | "require", 13 | "equal", 14 | "test", 15 | "testBoth", 16 | "testWithDefault", 17 | "raises", 18 | "deepEqual", 19 | "start", 20 | "stop", 21 | "ok", 22 | "strictEqual", 23 | "module", 24 | "expect", 25 | "minispade" 26 | ], 27 | 28 | "node" : false, 29 | "es5" : true, 30 | "browser" : true, 31 | 32 | "boss" : true, 33 | "curly": false, 34 | "debug": false, 35 | "devel": false, 36 | "eqeqeq": true, 37 | "evil": true, 38 | "forin": false, 39 | "immed": false, 40 | "laxbreak": false, 41 | "newcap": true, 42 | "noarg": true, 43 | "noempty": false, 44 | "nonew": false, 45 | "nomen": false, 46 | "onevar": false, 47 | "plusplus": false, 48 | "regexp": false, 49 | "undef": true, 50 | "sub": true, 51 | "strict": false, 52 | "white": false 53 | } 54 | -------------------------------------------------------------------------------- /tests/minispade.js: -------------------------------------------------------------------------------- 1 | if (typeof document !== "undefined") { 2 | (function() { 3 | minispade = { 4 | root: null, 5 | modules: {}, 6 | loaded: {}, 7 | 8 | globalEval: function(data) { 9 | if ( data ) { 10 | var ev = "ev"; 11 | var execScript = "execScript"; 12 | 13 | // We use execScript on Internet Explorer 14 | // We use an anonymous function so that context is window 15 | // rather than jQuery in Firefox 16 | ( window[execScript] || function( data ) { 17 | window[ ev+"al" ].call( window, data ); 18 | } )( data ); 19 | } 20 | }, 21 | 22 | require: function(name) { 23 | var loaded = minispade.loaded[name]; 24 | var mod = minispade.modules[name]; 25 | 26 | if (!loaded) { 27 | if (mod) { 28 | minispade.loaded[name] = true; 29 | 30 | if (typeof mod === "string") { 31 | this.globalEval(mod); 32 | } else { 33 | mod(); 34 | } 35 | } else { 36 | if (minispade.root && name.substr(0,minispade.root.length) !== minispade.root) { 37 | return minispade.require(minispade.root+name); 38 | } else { 39 | throw "The module '" + name + "' could not be found"; 40 | } 41 | } 42 | } 43 | 44 | return loaded; 45 | }, 46 | 47 | register: function(name, callback) { 48 | minispade.modules[name] = callback; 49 | } 50 | }; 51 | })(); 52 | } 53 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/livingsocial/rake-pipeline.git 3 | revision: 543f4322fe70facee9572d29ddabf7f090dad68a 4 | specs: 5 | rake-pipeline (0.6.0) 6 | rake (~> 0.9.0) 7 | thor 8 | 9 | GIT 10 | remote: https://github.com/wycats/rake-pipeline-web-filters.git 11 | revision: 81a22fb0808dfdeab8ed92d5d8c898ad198b9938 12 | specs: 13 | rake-pipeline-web-filters (0.6.0) 14 | rack 15 | rake-pipeline (~> 0.6) 16 | 17 | GEM 18 | remote: http://rubygems.org/ 19 | specs: 20 | colored (1.2) 21 | execjs (1.4.0) 22 | multi_json (~> 1.0) 23 | faraday (0.8.1) 24 | multipart-post (~> 1.1) 25 | github_api (0.5.4) 26 | faraday (~> 0.8.0) 27 | hashie (~> 1.2.0) 28 | multi_json (~> 1.3) 29 | nokogiri (~> 1.5.2) 30 | oauth2 (~> 0.7) 31 | hashie (1.2.0) 32 | httpauth (0.1) 33 | kicker (2.5.0) 34 | rb-fsevent 35 | mime-types (1.18) 36 | multi_json (1.3.6) 37 | multipart-post (1.1.5) 38 | nokogiri (1.5.4) 39 | oauth2 (0.7.1) 40 | faraday (~> 0.8) 41 | httpauth (~> 0.1) 42 | multi_json (~> 1.0) 43 | rack (~> 1.4) 44 | rack (1.4.1) 45 | rake (0.9.2.2) 46 | rb-fsevent (0.9.1) 47 | rest-client (1.6.7) 48 | mime-types (>= 1.16) 49 | thor (0.15.2) 50 | uglifier (1.0.4) 51 | execjs (>= 0.3.0) 52 | multi_json (>= 1.0.2) 53 | 54 | PLATFORMS 55 | ruby 56 | 57 | DEPENDENCIES 58 | colored 59 | github_api 60 | kicker 61 | rack 62 | rake-pipeline! 63 | rake-pipeline-web-filters! 64 | rest-client 65 | uglifier (~> 1.0.3) 66 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/core.js: -------------------------------------------------------------------------------- 1 | /** 2 | Default namespace. Here's where you'll find everything related 3 | to ember-forms. 4 | */ 5 | window.EF = Ember.Namespace.create({ 6 | 7 | /** 8 | Will find the container form recursively through the view hierarchy. Since 9 | forms cannot contain other forms (http://www.w3.org/TR/xhtml1/#prohibitions) 10 | this will resolve to a single EF.Form or undefined otherwise. 11 | 12 | @type EF.Form 13 | */ 14 | findFormRecursively: function(view){ 15 | var currentView = view; 16 | do{ 17 | if(currentView.get('isForm') === true){ return currentView; } 18 | }while(currentView = view.get('parentView')); 19 | }, 20 | 21 | /** 22 | Will find the first EF.BaseField in line looking recursively through the 23 | view hierarchy. 24 | 25 | @type EF.BaseField 26 | */ 27 | findFieldRecursively: function(view){ 28 | var currentView = view; 29 | do{ 30 | if(currentView.get('isField') === true){ return currentView; } 31 | }while(currentView = view.get('parentView')); 32 | }, 33 | 34 | /** 35 | Returns a field class given a particular name. For example, 36 | `findField("text")` will return `EF.TextField`. 37 | 38 | @type String 39 | */ 40 | findField: function(name){ 41 | name = name || 'text'; 42 | var fieldName = Ember.String.camelize(name); 43 | fieldName = fieldName.replace(/^./, fieldName[0].toUpperCase()); 44 | fieldName = fieldName + 'Field'; 45 | var field = EF[fieldName]; 46 | if(field){ 47 | return field; 48 | }else{ 49 | throw 'Field ' + name + ' cannot be found'; 50 | } 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/fields/base.js: -------------------------------------------------------------------------------- 1 | require("ember-forms/label"); 2 | 3 | var findFormRecursively = EF.findFormRecursively; 4 | 5 | EF.BaseField = Ember.ContainerView.extend({ 6 | name: null, 7 | formView: null, 8 | tagName: 'div', 9 | classNames: ['input'], 10 | InputView: null, 11 | value: null, 12 | isField: true, 13 | 14 | setFormView: function(){ 15 | var parentView, formView; 16 | 17 | if(parentView = this.get('parentView')){ 18 | formView = findFormRecursively(parentView); 19 | } 20 | if(formView){ 21 | formView.get('fieldViews').pushObject(this); 22 | this.set('formView', formView); 23 | this.set('content', formView.get('content')); 24 | } 25 | }, 26 | 27 | bindValue: function(){ 28 | var name = this.get('name'); 29 | var path = 'content.' + name; 30 | var value = this.get(path); 31 | this.set('value', this.get(path)); 32 | }, 33 | 34 | data: Ember.computed(function(){ 35 | var data = {}; 36 | data[this.get('name')] = this.get('inputView.value'); 37 | return data; 38 | }).volatile(), 39 | 40 | init: function(){ 41 | this._super(); 42 | var labelView = EF.Label.create(), 43 | inputView = this.get('InputView').create({ 44 | field: this, 45 | valueBinding: 'field.value', 46 | name: this.get('name'), 47 | placeholder: this.get('placeholder') 48 | }); 49 | 50 | this.set('labelView', labelView); 51 | this.set('inputView', inputView); 52 | this.pushObject(labelView); 53 | this.pushObject(inputView); 54 | this.setFormView(); 55 | this.bindValue(); 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /packages/ember-forms/tests/fields/select.js: -------------------------------------------------------------------------------- 1 | test("it creates a select", function() { 2 | var select = EF.SelectField.create({ 3 | content: Ember.A([ 4 | {id: 1, name: 'James'}, 5 | {id: 2, name: 'John'} 6 | ]), 7 | optionValuePath: 'id', 8 | optionLabelPath: 'name' 9 | }); 10 | 11 | Ember.run(function(){ 12 | select.appendTo("#qunit-fixture"); 13 | }); 14 | 15 | equal(select.$("option").length, 2, "it is populated with two options"); 16 | var firstOption = select.$("option:first"); 17 | equal(firstOption.attr('value'), "1", "it assigns a value"); 18 | equal(firstOption.text(), "James", "it assigns a name"); 19 | 20 | Ember.run(function(){ 21 | select.destroy(); 22 | }); 23 | }); 24 | 25 | test("it uses an array as both value and label", function() { 26 | var select = EF.SelectField.create({ 27 | content: Ember.A([1, 2, 3]) 28 | }); 29 | 30 | Ember.run(function(){ 31 | select.appendTo("#qunit-fixture"); 32 | }); 33 | 34 | equal(select.$("option").length, 3, "it is populated with three options"); 35 | var firstOption = select.$("option:first"); 36 | equal(firstOption.attr('value'), "1", "it assigns a value"); 37 | equal(firstOption.text(), "1", "it assigns a name"); 38 | 39 | Ember.run(function(){ 40 | select.destroy(); 41 | }); 42 | }); 43 | 44 | test("renders the prompt", function() { 45 | var select = EF.SelectField.create({ 46 | content: Ember.A([1, 2, 3]), 47 | prompt: 'Select a value...' 48 | }); 49 | 50 | Ember.run(function(){ 51 | select.appendTo("#qunit-fixture"); 52 | }); 53 | 54 | var firstOption = select.$("option:first"); 55 | equal(firstOption.text(), "Select a value...", "it assigns a prompt"); 56 | equal(firstOption.attr('value'), ""); 57 | 58 | Ember.run(function(){ 59 | select.destroy(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/ember-forms/tests/fields/date.js: -------------------------------------------------------------------------------- 1 | test("it creates a date field", function() { 2 | var dateField = EF.DateField.create({}); 3 | 4 | Ember.run(function(){ 5 | dateField.appendTo("#qunit-fixture"); 6 | }); 7 | 8 | equal(dateField.get('value'), undefined, "it is undefined by default"); 9 | 10 | var date = new Date(), 11 | day = date.getUTCDate(), 12 | month = date.getUTCMonth(), 13 | year = date.getUTCFullYear(); 14 | 15 | Ember.run(function(){ 16 | dateField.set('value', date); 17 | }); 18 | 19 | equal(dateField.$('select:first option[selected]').val(), day + ''); 20 | equal(dateField.$('select:nth-child(2) option[selected]').val(), month + ''); 21 | equal(dateField.$('select:nth-child(3) option[selected]').val(), year + ''); 22 | 23 | Ember.run(function(){ 24 | dateField.destroy(); 25 | }); 26 | }); 27 | 28 | test("it adds some appropiate field names", function() { 29 | var dateField = EF.DateField.create({ 30 | name: 'test' 31 | }); 32 | 33 | Ember.run(function(){ 34 | dateField.appendTo("#qunit-fixture"); 35 | }); 36 | 37 | equal(dateField.$('select:first').attr('name'), 'test_day'); 38 | equal(dateField.$('select:nth-child(2)').attr('name'), 'test_month'); 39 | equal(dateField.$('select:nth-child(3)').attr('name'), 'test_year'); 40 | 41 | Ember.run(function(){ 42 | dateField.destroy(); 43 | }); 44 | }); 45 | 46 | test("options", function() { 47 | var dateField = EF.DateField.create({ 48 | startYear: 2050, 49 | endYear: 2012 50 | }); 51 | 52 | Ember.run(function(){ 53 | dateField.appendTo("#qunit-fixture"); 54 | }); 55 | 56 | equal(dateField.$('select:nth-child(3) option:nth-child(2)').val(), '2050', 'it sets the startYear'); 57 | equal(dateField.$('select:nth-child(3) option:last').val(), '2012', 'it sets the endYear'); 58 | 59 | Ember.run(function(){ 60 | dateField.destroy(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/controls/unbound_select.js: -------------------------------------------------------------------------------- 1 | var fmt = Ember.String.fmt; 2 | 3 | /** 4 | `Ember.Select` is meant to be bound to collections with a changing nature, 5 | but with big collections, it comes with a big performance penalty. In order 6 | to address this issue, we've created a "static" one - meaning that won't 7 | change as the associated collection changes. This makes it perfect for things 8 | like date selectors, gender, numerical... 9 | 10 | The collection can be an array of values, or an array of Javascript objects 11 | with `value` and `label` keys. 12 | */ 13 | EF.UnboundSelect = Ember.View.extend({ 14 | tagName: 'select', 15 | template: Ember.Handlebars.compile("{{unbound view.options}}"), 16 | 17 | /** 18 | @private 19 | 20 | Renders the options from the collection. 21 | */ 22 | options: Ember.computed(function(){ 23 | var output; 24 | if(!Ember.isEmpty(this.get('prompt'))){ 25 | output = ''; 26 | } 27 | this.get('content').forEach(function(item){ 28 | var value, label; 29 | if(!Ember.isEmpty(item.value)){ 30 | value = item.value; 31 | label = item.label; 32 | }else{ value = item; label = item; } 33 | var selected = ""; 34 | if(value === this.get('value')){ 35 | selected = ' selected="selected"'; 36 | } 37 | output += fmt(''); 38 | }, this); 39 | return (new Handlebars.SafeString(output)); 40 | }).property('value'), 41 | 42 | setValue: Ember.observer(function(){ 43 | if(!this.$()) return; 44 | var value = this.get('value'); 45 | var option = this.$('option[value=' + value + ']'); 46 | option.siblings().attr('selected', null); 47 | option.attr('selected', 'selected'); 48 | }, 'value'), 49 | 50 | didInsertElement: function(){ 51 | var view = this; 52 | this.$().change(function(){ 53 | view.change(); 54 | }); 55 | }, 56 | 57 | change: function(){ 58 | var value = this.$().val(); 59 | this.set('value', value); 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /packages/ember-forms/lib/form.js: -------------------------------------------------------------------------------- 1 | /** 2 | @class 3 | 4 | EF.Form is a view that contains several fields and can respond to its events, 5 | providing the field's normalized data. 6 | 7 | It will automatically bind to the values of an object, if provided. 8 | 9 | myForm = EF.Form.create({ 10 | objectBinding: 'App.someObject', 11 | template: Ember.Handlebars.compile( 12 | '{{field title }}' + 13 | '{{field body as="textarea"}}' + 14 | '{{form buttons}}' 15 | ), 16 | save: function(data){ this.get('object').setProperties(data); } 17 | }); 18 | 19 | @extends Ember.View 20 | */ 21 | EF.Form = Ember.View.extend({ 22 | tagName: 'form', 23 | classNameBindings: ['name'], 24 | classNames: ['ember-form'], 25 | attributeBindings: ['action'], 26 | fieldViews: Ember.A(), 27 | buttons: ['submit'], 28 | content: null, 29 | isForm: true, 30 | submitName: 'Save', 31 | name: Ember.computed(function(){ 32 | var constructor = this.get('content.constructor'); 33 | if(constructor && constructor.isClass){ 34 | var className = constructor.toString().split('.').pop(); 35 | return Ember.String.decamelize(className); 36 | } 37 | }).property('content'), 38 | 39 | /** 40 | It returns this form fields data in an object. 41 | 42 | myForm.data(); 43 | 44 | Would return: 45 | 46 | { 47 | title: 'Some post title', 48 | content: 'The post content' 49 | } 50 | */ 51 | data: Ember.computed(function(){ 52 | var data = {}; 53 | this.get('fieldViews').forEach(function(field){ 54 | var fieldData = field.get('data'); 55 | for(var key in fieldData){ 56 | data[key] = fieldData[key]; 57 | } 58 | }); 59 | return data; 60 | }).volatile(), 61 | 62 | submit: function(){ 63 | this.save(this.get('data')); 64 | return false; 65 | }, 66 | 67 | /** 68 | This event will be fired when the form is sent, and will receive the form 69 | data as argument. Override it to perform some operation like setting the 70 | properties of an object. 71 | 72 | myForm = EF.Form.create({ 73 | [...] 74 | save: function(data){ 75 | this.get('object').setProperties(data); 76 | } 77 | }); 78 | */ 79 | save: function(data){ } 80 | }); 81 | -------------------------------------------------------------------------------- /tests/qunit/run-qunit.js: -------------------------------------------------------------------------------- 1 | // PhantomJS QUnit Test Runner 2 | 3 | /*globals QUnit phantom*/ 4 | 5 | var args = phantom.args; 6 | if (args.length < 1 || args.length > 2) { 7 | console.log("Usage: " + phantom.scriptName + " "); 8 | phantom.exit(1); 9 | } 10 | 11 | var page = require('webpage').create(); 12 | 13 | page.onConsoleMessage = function(msg) { 14 | if (msg.slice(0,8) === 'WARNING:') { return; } 15 | console.log(msg); 16 | }; 17 | 18 | page.open(args[0], function(status) { 19 | if (status !== 'success') { 20 | console.error("Unable to access network"); 21 | phantom.exit(1); 22 | } else { 23 | page.evaluate(logQUnit); 24 | 25 | var timeout = parseInt(args[1] || 60000, 10); 26 | var start = Date.now(); 27 | var interval = setInterval(function() { 28 | if (Date.now() > start + timeout) { 29 | console.error("Tests timed out"); 30 | phantom.exit(124); 31 | } else { 32 | var qunitDone = page.evaluate(function() { 33 | return window.qunitDone; 34 | }); 35 | 36 | if (qunitDone) { 37 | clearInterval(interval); 38 | if (qunitDone.failed > 0) { 39 | phantom.exit(1); 40 | } else { 41 | phantom.exit(); 42 | } 43 | } 44 | } 45 | }, 500); 46 | } 47 | }); 48 | 49 | function logQUnit() { 50 | var testErrors = []; 51 | var assertionErrors = []; 52 | 53 | console.log("Running: " + JSON.stringify(QUnit.urlParams)); 54 | 55 | QUnit.moduleDone(function(context) { 56 | if (context.failed) { 57 | var msg = "Module Failed: " + context.name + "\n" + testErrors.join("\n"); 58 | console.error(msg); 59 | testErrors = []; 60 | } 61 | }); 62 | 63 | QUnit.testDone(function(context) { 64 | if (context.failed) { 65 | var msg = " Test Failed: " + context.name + assertionErrors.join(" "); 66 | testErrors.push(msg); 67 | assertionErrors = []; 68 | } 69 | }); 70 | 71 | QUnit.log(function(context) { 72 | if (context.result) return; 73 | 74 | var msg = "\n Assertion Failed:"; 75 | if (context.message) { 76 | msg += " " + context.message; 77 | } 78 | 79 | if (context.expected) { 80 | msg += "\n Expected: " + context.expected + ", Actual: " + context.actual; 81 | } 82 | 83 | assertionErrors.push(msg); 84 | }); 85 | 86 | QUnit.done(function(context) { 87 | var stats = [ 88 | "Time: " + context.runtime + "ms", 89 | "Total: " + context.total, 90 | "Passed: " + context.passed, 91 | "Failed: " + context.failed 92 | ]; 93 | console.log(stats.join(", ")); 94 | window.qunitDone = context; 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember Forms [![Build Status](https://secure.travis-ci.org/codegram/ember-forms.png?branch=master)](http://travis-ci.org/codegram/ember-forms) 2 | 3 | Ember forms is a library for Ember.js to assist in the creation of forms, 4 | binding them to objects and extracting their data. 5 | 6 | ## Usage 7 | 8 | ### Simple example 9 | 10 | Just declare your form as a view extending EF.Form: 11 | 12 | ```Javascript 13 | App.PostForm = EF.Form.extend({ 14 | content: person, 15 | gender: Ember.A([{id: 'm', name: 'Male'}, {id: 'f', name: 'Female'}]), 16 | template: Ember.Handlebars.compile( 17 | '{{field name label="Post title"}}' + 18 | '{{field interests as="textarea"}}' + 19 | '{{field birthday as="date"}}' + 20 | '{{field gender as="select" optionsBinding="formView.gender"}}' + 21 | '{{form buttons name="Save post"}}' 22 | ), 23 | save: function(data){ 24 | this.get('content').setProperties(data); 25 | } 26 | }); 27 | ``` 28 | 29 | ### More complex example 30 | 31 | Just declare your form as a view extending EF.Form: 32 | 33 | ```Javascript 34 | App.PostForm = EF.Form.extend({ 35 | contentBinding: 'App.someObject', 36 | save: function(data){ 37 | this.get('content').setProperties(data); 38 | } 39 | }); 40 | ``` 41 | 42 | Then create a handlebars layout more complex than that: 43 | 44 | ```Handlebars 45 |
46 | User data 47 | {{field name}} 48 | {{field gender}} 49 |
50 |
51 | Other data 52 | {{field comments as="textarea"}} 53 |
54 | {{form buttons}} 55 | ``` 56 | 57 | ## Field types 58 | 59 | Right now only three field types are supported: 60 | 61 | ### text 62 | `` 63 | 64 | ### textarea 65 | `