From 1707f4d19fd0099b92a6b007c8714989a3dde67d Mon Sep 17 00:00:00 2001 From: Benjamin ter Kuile Date: Mon, 24 Nov 2014 18:20:10 +0100 Subject: [PATCH] supplier menu progress --- .../app/controllers/menu_controller.js.coffee | 13 + .../app/controllers/menu_controller.js.em | 3 - .../modals/add_section_controller.js.coffee | 1 - .../controllers/modals/base_controller.js.em | 11 +- ...product_category_edit_controller.js.coffee | 2 + ...product_category_move_controller.js.coffee | 29 + ...ection_arrange_tables_controller.js.coffee | 1 - .../app/helpers/select-hour.js.coffee | 6 + .../app/helpers/select-minute.js.coffee | 5 + .../app/models/product_category.js.coffee | 22 + .../supplier/app/models/supplier.js.coffee | 2 + .../form_elements/boolean_switch.emblem | 2 + .../form_elements/select_minute_of_day.emblem | 6 + .../supplier/app/templates/menu.emblem | 8 +- .../modals/product_category_edit.emblem | 45 + .../modals/product_category_move.emblem | 13 + .../boolean_button_view.js.coffee | 17 + .../boolean_switch_view.js.coffee | 8 + .../select_minute_of_day_view.js.coffee | 13 + .../javascripts/translations.js.coffee.erb | 7 + .../supplier/foundation1/application.css.sass | 1 - .../foundation1/components/_forms.css.sass | 4 + .../_modal.css.sass} | 4 +- .../components/_products_menu.css.sass | 29 + .../product_categories_controller.rb | 6 +- app/models/product_category.rb | 9 +- app/models/supplier.rb | 5 +- .../product_category_serializer.rb | 4 +- .../supplier_supplier_serializer.rb | 2 +- config/environments/development.rb | 2 +- config/environments/test.rb | 2 +- config/locales/supplier.en.yml | 14 + config/locales/supplier.nl.yml | 13 + vendor/assets/ember/development/ember-data.js | 2815 +++-- vendor/assets/ember/development/ember.js | 10233 +++++++++------- vendor/assets/ember/production/ember-data.js | 10 +- vendor/assets/ember/production/ember.js | 35 +- 37 files changed, 7356 insertions(+), 6046 deletions(-) create mode 100644 app/assets/javascripts/supplier/app/controllers/menu_controller.js.coffee delete mode 100644 app/assets/javascripts/supplier/app/controllers/menu_controller.js.em create mode 100644 app/assets/javascripts/supplier/app/controllers/modals/product_category_edit_controller.js.coffee create mode 100644 app/assets/javascripts/supplier/app/controllers/modals/product_category_move_controller.js.coffee create mode 100644 app/assets/javascripts/supplier/app/helpers/select-hour.js.coffee create mode 100644 app/assets/javascripts/supplier/app/helpers/select-minute.js.coffee create mode 100644 app/assets/javascripts/supplier/app/templates/form_elements/boolean_switch.emblem create mode 100644 app/assets/javascripts/supplier/app/templates/form_elements/select_minute_of_day.emblem create mode 100644 app/assets/javascripts/supplier/app/templates/modals/product_category_edit.emblem create mode 100644 app/assets/javascripts/supplier/app/templates/modals/product_category_move.emblem create mode 100644 app/assets/javascripts/supplier/app/views/form_elements/boolean_button_view.js.coffee create mode 100644 app/assets/javascripts/supplier/app/views/form_elements/boolean_switch_view.js.coffee create mode 100644 app/assets/javascripts/supplier/app/views/form_elements/select_minute_of_day_view.js.coffee rename app/assets/stylesheets/supplier/foundation1/{_qmodal.css.sass => components/_modal.css.sass} (79%) create mode 100644 app/assets/stylesheets/supplier/foundation1/components/_products_menu.css.sass diff --git a/app/assets/javascripts/supplier/app/controllers/menu_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/menu_controller.js.coffee new file mode 100644 index 00000000..367aaa51 --- /dev/null +++ b/app/assets/javascripts/supplier/app/controllers/menu_controller.js.coffee @@ -0,0 +1,13 @@ +App.MenuController = Ember.ObjectController.extend + needs: ['application'] + product_categories: (-> @store.all('product_category')).property() + sorted_product_categories: (-> @get('product_categories').sortBy('position')).property('product_categories.@each', 'product_categories.@each.position') + actions: + editProductCategory: (product_category)-> + @modal 'product_category_edit', + model: product_category, + close: -> product_category.rollback() + + moveProductCategory: (product_category)-> + @modal 'product_category_move', + model: product_category diff --git a/app/assets/javascripts/supplier/app/controllers/menu_controller.js.em b/app/assets/javascripts/supplier/app/controllers/menu_controller.js.em deleted file mode 100644 index 38fb4fc2..00000000 --- a/app/assets/javascripts/supplier/app/controllers/menu_controller.js.em +++ /dev/null @@ -1,3 +0,0 @@ -App.MenuController = Ember.ObjectController.extend - needs: ['application'] - product_categories: ~> @store.all('product_category') diff --git a/app/assets/javascripts/supplier/app/controllers/modals/add_section_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/modals/add_section_controller.js.coffee index 6458b2b4..0377c18a 100644 --- a/app/assets/javascripts/supplier/app/controllers/modals/add_section_controller.js.coffee +++ b/app/assets/javascripts/supplier/app/controllers/modals/add_section_controller.js.coffee @@ -1,5 +1,4 @@ App.modals.AddSectionController = @App.modals.BaseController.extend - alert_message: null section_title: '' section_width: 15 section_height: 8 diff --git a/app/assets/javascripts/supplier/app/controllers/modals/base_controller.js.em b/app/assets/javascripts/supplier/app/controllers/modals/base_controller.js.em index 09eb2bb0..2106cd56 100644 --- a/app/assets/javascripts/supplier/app/controllers/modals/base_controller.js.em +++ b/app/assets/javascripts/supplier/app/controllers/modals/base_controller.js.em @@ -1,13 +1,19 @@ @App.modals.BaseController = Ember.ObjectController.extend + alert_message: "" title: ~> + # return title if directly set by options return @get('modal_options.title') if @get('modal_options.title') + # return translated title_path if directly set by controller return t(@title_path) if @title_path + # return translated title_path if directly set by options return t(@get('modal_options.title_path')) if @get('modal_options.title_path') + # infer title path based on controller name App.modals.AddSectionController => add_section underscored = `this.constructor.toString().substr(11).replace(/Controller$/, '').underscore()` params = {} if model = @get('model') params = model.serialize() if model.serialize - @get('modal_options.title') or ttry("modal.#{underscored}.title", params) or underscored.capitalize().replace(/_/, ' ') + # find translated title or humanize the controller name + ttry("modal.#{underscored}.title", params) or underscored.capitalize().replace(/_/, ' ') actions: close: -> if close = @get('modal_options.close') @@ -24,3 +30,6 @@ ok.apply(@) @send 'closeModal' unless @preventClose confirm: -> @send('ok') + save: -> + @get('model').save() + @send 'closeModal' unless @preventClose diff --git a/app/assets/javascripts/supplier/app/controllers/modals/product_category_edit_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/modals/product_category_edit_controller.js.coffee new file mode 100644 index 00000000..cd87a881 --- /dev/null +++ b/app/assets/javascripts/supplier/app/controllers/modals/product_category_edit_controller.js.coffee @@ -0,0 +1,2 @@ +App.modals.ProductCategoryEditController = App.modals.BaseController.extend + title_path: 'product_category.edit.modal.title' diff --git a/app/assets/javascripts/supplier/app/controllers/modals/product_category_move_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/modals/product_category_move_controller.js.coffee new file mode 100644 index 00000000..313a4e15 --- /dev/null +++ b/app/assets/javascripts/supplier/app/controllers/modals/product_category_move_controller.js.coffee @@ -0,0 +1,29 @@ +App.modals.ProductCategoryMoveController = App.modals.BaseController.extend + title_path: 'product_category.move.modal.title' + actions: + moveBelow: (below_product_category)-> + position = 0 + unless below_product_category + @set 'model.position', position + @get('model').save() + position += 1 + for product_category in @get('product_categories') + product_category.set('position', position) + product_category.save() + if below_product_category and product_category.id is below_product_category.id + @set 'model.position', position += 1 + @get('model').save() + + position += 1 + @send 'close' + # moveBelow: (-> + # debugger + # ).observes('move_below_selection') + # change: -> + # debugger + + product_categories: (-> + @store.all('product_category').filter( (pc) => pc.id isnt @get('model.id')).sortBy('position') + ).property('model.id') + + # setProductCategories: (-> @set 'product_categories', @store.all('product_category')).on('init') diff --git a/app/assets/javascripts/supplier/app/controllers/modals/section_arrange_tables_controller.js.coffee b/app/assets/javascripts/supplier/app/controllers/modals/section_arrange_tables_controller.js.coffee index 59640b79..cd2ce74d 100644 --- a/app/assets/javascripts/supplier/app/controllers/modals/section_arrange_tables_controller.js.coffee +++ b/app/assets/javascripts/supplier/app/controllers/modals/section_arrange_tables_controller.js.coffee @@ -1,5 +1,4 @@ App.modals.SectionArrangeTablesController = App.modals.BaseController.extend - alert_message: null arrange_type: 'distributed' # can be distributed, by_row or by_column row_count: 2 column_count: 2 diff --git a/app/assets/javascripts/supplier/app/helpers/select-hour.js.coffee b/app/assets/javascripts/supplier/app/helpers/select-hour.js.coffee new file mode 100644 index 00000000..7ef849df --- /dev/null +++ b/app/assets/javascripts/supplier/app/helpers/select-hour.js.coffee @@ -0,0 +1,6 @@ +Ember.Handlebars.helper 'select-hour', (params..., options)-> + debugger + result = "" + new Handlebars.SafeString result diff --git a/app/assets/javascripts/supplier/app/helpers/select-minute.js.coffee b/app/assets/javascripts/supplier/app/helpers/select-minute.js.coffee new file mode 100644 index 00000000..be1d8214 --- /dev/null +++ b/app/assets/javascripts/supplier/app/helpers/select-minute.js.coffee @@ -0,0 +1,5 @@ +Ember.Handlebars.helper 'select-minute', (params..., options)-> + result = "" + new Handlebars.SafeString result diff --git a/app/assets/javascripts/supplier/app/models/product_category.js.coffee b/app/assets/javascripts/supplier/app/models/product_category.js.coffee index 096bf301..9da80d20 100644 --- a/app/assets/javascripts/supplier/app/models/product_category.js.coffee +++ b/app/assets/javascripts/supplier/app/models/product_category.js.coffee @@ -2,3 +2,25 @@ attr = DS.attr App.ProductCategory = DS.Model.extend name: attr('string') products: DS.hasMany('product') + supplier: DS.belongsTo 'supplier' + active_on_sunday: attr('boolean') + active_on_monday: attr('boolean') + active_on_tuesday: attr('boolean') + active_on_wednesday: attr('boolean') + active_on_thursday: attr('boolean') + active_on_friday: attr('boolean') + active_on_saturday: attr('boolean') + full_day: attr('boolean') + start_from: attr('number') + end_on: attr('number') + position: attr('number') + + availability_text: Ember.computed 'active_on_sunday', 'active_on_monday', 'active_on_tuesday', 'active_on_wednesday', 'active_on_thursday', 'active_on_friday', 'active_on_saturday', 'full_day', 'start_from', 'end_on', -> + days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] + active_days = days.filter (day)=>@get("active_on_#{day}") + result = "" + if active_days.length < 7 + result += "#{active_days.map((day) -> t("date.day_name.#{day}")).join(", ")} " + unless @get('full_day') + result += "#{day_minutes_to_time @get('start_from')} - #{day_minutes_to_time @get('end_on')}" + new Ember.Handlebars.SafeString result diff --git a/app/assets/javascripts/supplier/app/models/supplier.js.coffee b/app/assets/javascripts/supplier/app/models/supplier.js.coffee index 6b2c99f6..7c97b0c2 100644 --- a/app/assets/javascripts/supplier/app/models/supplier.js.coffee +++ b/app/assets/javascripts/supplier/app/models/supplier.js.coffee @@ -13,3 +13,5 @@ App.Supplier = DS.Model.extend iens_profile: attr 'string' lat: attr 'number' lng: attr 'number' + week_starts_on_monday: attr 'boolean' + product_categories: DS.hasMany 'product_category' diff --git a/app/assets/javascripts/supplier/app/templates/form_elements/boolean_switch.emblem b/app/assets/javascripts/supplier/app/templates/form_elements/boolean_switch.emblem new file mode 100644 index 00000000..2eb5f2f5 --- /dev/null +++ b/app/assets/javascripts/supplier/app/templates/form_elements/boolean_switch.emblem @@ -0,0 +1,2 @@ +Ember.Checkbox id=view.switchId checkedBinding="view.value" +label for=view.switchId diff --git a/app/assets/javascripts/supplier/app/templates/form_elements/select_minute_of_day.emblem b/app/assets/javascripts/supplier/app/templates/form_elements/select_minute_of_day.emblem new file mode 100644 index 00000000..f0534964 --- /dev/null +++ b/app/assets/javascripts/supplier/app/templates/form_elements/select_minute_of_day.emblem @@ -0,0 +1,6 @@ +.row.select-minute-of-day + .small-5.columns + view "select" content=view.hours_list value=view.hour + .small-1.columns: span : + .small-5.columns.end + view "select" content=view.minutes_list value=view.minute diff --git a/app/assets/javascripts/supplier/app/templates/menu.emblem b/app/assets/javascripts/supplier/app/templates/menu.emblem index 6d7d832d..b8a94662 100644 --- a/app/assets/javascripts/supplier/app/templates/menu.emblem +++ b/app/assets/javascripts/supplier/app/templates/menu.emblem @@ -1,7 +1,11 @@ h2 Menu -each product_category in product_categories +each product_category in sorted_product_categories .row: .small-12.columns - h3= product_category.name + .product_category-header + a.move{action "moveProductCategory" product_category} href="#" + span.title= product_category.name + span.availability= product_category.availability_text + a.edit-product-category-button{action "editProductCategory" product_category} href="#" each product in product_category.products .row .small-4.columns= product.name diff --git a/app/assets/javascripts/supplier/app/templates/modals/product_category_edit.emblem b/app/assets/javascripts/supplier/app/templates/modals/product_category_edit.emblem new file mode 100644 index 00000000..b86d16b1 --- /dev/null +++ b/app/assets/javascripts/supplier/app/templates/modals/product_category_edit.emblem @@ -0,0 +1,45 @@ +p=t 'product_category.edit.modal.body_header' +.form-row + .form-label.half=t 'attributes.product_category.name' + .form-field.half= input valueBinding="model.name" +.row + .small-6.columns + unless model.supplier.week_starts_on_monday + .form-row + .form-label.half= t 'date.day_name.sunday' + .form-field.half: App.BooleanSwitchView value=model.active_on_sunday + .form-row + .form-label.half= t 'date.day_name.monday' + .form-field.half: App.BooleanSwitchView value=model.active_on_monday + .form-row + .form-label.half= t 'date.day_name.tuesday' + .form-field.half: App.BooleanSwitchView value=model.active_on_tuesday + .form-row + .form-label.half= t 'date.day_name.wednesday' + .form-field.half: App.BooleanSwitchView value=model.active_on_wednesday + .form-row + .form-label.half= t 'date.day_name.thursday' + .form-field.half: App.BooleanSwitchView value=model.active_on_thursday + .form-row + .form-label.half= t 'date.day_name.friday' + .form-field.half: App.BooleanSwitchView value=model.active_on_friday + .form-row + .form-label.half= t 'date.day_name.saturday' + .form-field.half: App.BooleanSwitchView value=model.active_on_saturday + if model.supplier.week_starts_on_monday + .form-row + .form-label.half= t 'date.day_name.sunday' + .form-field.half: App.BooleanSwitchView valueBinding=model.active_on_sunday + .small-6.columns + .row + .small-12.columns.text-center: App.BooleanButtonView value=model.full_day reverse=true text_path="product_category.edit.modal.active_between.top" + unless model.full_day + .row + .small-12.columns= view "select-minute-of-day" value=model.start_from + .row + .small-12.columns.text-center= t 'product_category.edit.modal.active_between.middle' + .row + .small-12.columns= view "select-minute-of-day" value=model.end_on +hr +button.confirm-cancel{action "close"}=t 'section.add_tables.modal.close_button' +button.confirm-ok.right{action "save"}=t 'section.add_tables.modal.add_button' diff --git a/app/assets/javascripts/supplier/app/templates/modals/product_category_move.emblem b/app/assets/javascripts/supplier/app/templates/modals/product_category_move.emblem new file mode 100644 index 00000000..68fb1038 --- /dev/null +++ b/app/assets/javascripts/supplier/app/templates/modals/product_category_move.emblem @@ -0,0 +1,13 @@ +p=t 'product_category.move.modal.body_header' +.row + .small-11.small-offset-1.columns + a{action "moveBelow"} href="#" = t 'product_category.move.modal.move_to_top' +h4=t 'product_category.move.modal.move_below_label' +each product_category in product_categories + .row.product_category-move-row + .small-11.small-offset-1.columns + a{action "moveBelow" product_category} href="#" + span.title= product_category.name + span.availability= product_category.availability_text +hr +button.confirm-cancel{action "close"}=t 'section.add_tables.modal.close_button' diff --git a/app/assets/javascripts/supplier/app/views/form_elements/boolean_button_view.js.coffee b/app/assets/javascripts/supplier/app/views/form_elements/boolean_button_view.js.coffee new file mode 100644 index 00000000..830b6a32 --- /dev/null +++ b/app/assets/javascripts/supplier/app/views/form_elements/boolean_button_view.js.coffee @@ -0,0 +1,17 @@ +App.BooleanButtonView = Ember.View.extend + tagName: 'a' + href: '#' + classNames: "button" + # templateName: "form_elements/boolean_switch" + template: Ember.Handlebars.compile "{{view.text}}" + classNameBindings: ['rounded:round', 'active:active:disabled'] + + text: Ember.computed 'text_path', -> + t @text_path + + active: Ember.computed 'value', -> + if @reverse then !@get('value') else !!@get('value') + + click: -> + @set 'value', !@get('value') + # setUniqueId: (->@set 'switchId', "switch-#{Math.round(Math.random()*1000)}").on('init') diff --git a/app/assets/javascripts/supplier/app/views/form_elements/boolean_switch_view.js.coffee b/app/assets/javascripts/supplier/app/views/form_elements/boolean_switch_view.js.coffee new file mode 100644 index 00000000..705720a0 --- /dev/null +++ b/app/assets/javascripts/supplier/app/views/form_elements/boolean_switch_view.js.coffee @@ -0,0 +1,8 @@ +App.BooleanSwitchView = Ember.View.extend + classNames: "switch" + templateName: "form_elements/boolean_switch" + classNameBindings: ['rounded:round'] + + click: -> + @set 'value', !@get('value') + setUniqueId: (->@set 'switchId', "switch-#{Math.round(Math.random()*1000)}").on('init') diff --git a/app/assets/javascripts/supplier/app/views/form_elements/select_minute_of_day_view.js.coffee b/app/assets/javascripts/supplier/app/views/form_elements/select_minute_of_day_view.js.coffee new file mode 100644 index 00000000..dc5731a1 --- /dev/null +++ b/app/assets/javascripts/supplier/app/views/form_elements/select_minute_of_day_view.js.coffee @@ -0,0 +1,13 @@ +App.SelectMinuteOfDayView = Ember.View.extend + templateName: "form_elements/select_minute_of_day" + classNameBindings: ['rounded:round'] + hours_list: [0..24] + minutes_list: [0..60] + hour: Ember.computed 'value', (a,b,c)-> + if arguments.length > 1 + @set 'value', 60*b + (@get('minute') || 0) + Math.floor (@get('value') || 0)/60 + minute: Ember.computed 'value', (a,b,c)-> + if arguments.length > 1 + @set 'value', 60*(@get('hour')||0) + b + Math.floor @get('value')%60 diff --git a/app/assets/javascripts/translations.js.coffee.erb b/app/assets/javascripts/translations.js.coffee.erb index 35e2f66c..0ed1cde3 100644 --- a/app/assets/javascripts/translations.js.coffee.erb +++ b/app/assets/javascripts/translations.js.coffee.erb @@ -5,12 +5,19 @@ helpers: <%= I18n.t('helpers', locale: :en).to_json %> pagination: <%= I18n.t('views.pagination', locale: :en).to_json %> errors: <%= I18n.t('errors', locale: :en).to_json %> + date: <%= {day_name: Hash[%w[sunday monday tuesday wednesday thursday friday saturday].zip(I18n.t('date.day_names', locale: :en))]}.to_json %> nl: models: <%= I18n.t('activemodel.models', locale: :nl).to_json %> attributes: <%= I18n.t('activemodel.attributes', locale: :nl).to_json %> helpers: <%= I18n.t('helpers', locale: :nl).to_json %> pagination: <%= I18n.t('views.pagination', locale: :nl).to_json %> errors: <%= I18n.t('errors', locale: :nl).to_json %> + date: <%= {day_name: Hash[%w[sunday monday tuesday wednesday thursday friday saturday].zip(I18n.t('date.day_names', locale: :nl))]}.to_json %> + +@day_minutes_to_time = (minutes)-> + return "" unless minutes + [("0" + Math.floor(minutes/60)).substr(-2,2), ("0" + Math.floor(minutes%60)).substr(-2,2)].join(":") + @ttry = (path, vars={})-> @t(path, $.extend(vars, emptyWhenNotFound: true)) diff --git a/app/assets/stylesheets/supplier/foundation1/application.css.sass b/app/assets/stylesheets/supplier/foundation1/application.css.sass index 77c82c31..55a68a2c 100644 --- a/app/assets/stylesheets/supplier/foundation1/application.css.sass +++ b/app/assets/stylesheets/supplier/foundation1/application.css.sass @@ -13,5 +13,4 @@ @import ./qsections @import ./section_tab_headers @import ./qlists -@import ./qmodal @import ./ember_dropdown diff --git a/app/assets/stylesheets/supplier/foundation1/components/_forms.css.sass b/app/assets/stylesheets/supplier/foundation1/components/_forms.css.sass index fc5d4e4f..f6ad23bc 100644 --- a/app/assets/stylesheets/supplier/foundation1/components/_forms.css.sass +++ b/app/assets/stylesheets/supplier/foundation1/components/_forms.css.sass @@ -7,6 +7,8 @@ +grid-column($columns:4, $offset:1) @media #{$large-up} +grid-column(3) + &.half + +grid-column(6) .form-field @media #{$small-only} +grid-column($columns:10, $center:true, $last-column:true) @@ -21,6 +23,8 @@ +grid-column($columns:6, $last-column:true) @media #{$large-up} +grid-column($columns: 9, $last-column:true) + &.half + +grid-column(6) &.form-actions padding-top: 12px body diff --git a/app/assets/stylesheets/supplier/foundation1/_qmodal.css.sass b/app/assets/stylesheets/supplier/foundation1/components/_modal.css.sass similarity index 79% rename from app/assets/stylesheets/supplier/foundation1/_qmodal.css.sass rename to app/assets/stylesheets/supplier/foundation1/components/_modal.css.sass index 0b24b11f..8536f201 100644 --- a/app/assets/stylesheets/supplier/foundation1/_qmodal.css.sass +++ b/app/assets/stylesheets/supplier/foundation1/components/_modal.css.sass @@ -1,6 +1,6 @@ .modal margin: 10px auto - width: 500px + width: 600px background-color: #fff padding: 1em .modal-alert @@ -12,7 +12,7 @@ position: fixed top: 0 left: 0 - background-color: rgba(0, 0, 0, 0.2) + background-color: rgba(0, 0, 0, 0.5) .flush--top margin-top: 0 diff --git a/app/assets/stylesheets/supplier/foundation1/components/_products_menu.css.sass b/app/assets/stylesheets/supplier/foundation1/components/_products_menu.css.sass new file mode 100644 index 00000000..938a6d36 --- /dev/null +++ b/app/assets/stylesheets/supplier/foundation1/components/_products_menu.css.sass @@ -0,0 +1,29 @@ +.product_category-header + border-top: 1px solid #ccc + border-bottom: 1px solid #ccc + .move + @extend .fa + // @extend .fa-lg + @extend .fa-arrows + .title + font-weight: bold + font-size: 1.3em + .availability + font-size: 0.8em + padding-left: 1em + padding-right: 1em + .day-names + color: #444 + .time-range + color: rgb(39, 6, 121) + .edit-product-category-button + @extend .fa + @extend .fa-lg + @extend .fa-edit +.product_category-move-row + .availability + font-size: 0.8em + .day-names + color: #444 + .time-range + color: rgb(39, 6, 121) diff --git a/app/controllers/suppliers/product_categories_controller.rb b/app/controllers/suppliers/product_categories_controller.rb index b9d97ef8..6eed6551 100644 --- a/app/controllers/suppliers/product_categories_controller.rb +++ b/app/controllers/suppliers/product_categories_controller.rb @@ -92,7 +92,7 @@ module Suppliers respond_to do |format| if @product_category.update_attributes(product_category_params) format.html { redirect_to [:suppliers, :product_categories], notice: t('action.update.successfull', model: ProductCategory.model_name.human) } - format.json { head :no_content } + format.json { render json: @product_category } else format.html { render action: "edit" } format.json { render json: @product_category.errors, status: :unprocessable_entity } @@ -124,7 +124,9 @@ module Suppliers private def product_category_params - params.require(:product_category).permit(:name, :start_from, :end_on, :full_day, product_ids: [], week_days: []) + params.require(:product_category).permit(:name, :start_from, :end_on, :full_day, :position, + :active_on_sunday, :active_on_monday, :active_on_tuesday, :active_on_wednesday, :active_on_thursday, :active_on_friday, :active_on_saturday, + product_ids: []) end end end diff --git a/app/models/product_category.rb b/app/models/product_category.rb index 35728b81..92a44fad 100644 --- a/app/models/product_category.rb +++ b/app/models/product_category.rb @@ -8,6 +8,13 @@ class ProductCategory property :full_day, type: :boolean, default: true property :start_from, type: Fixnum, default: 1320 # 22:00 property :end_on, type: Fixnum, default: 1380 # 23:00 + property :active_on_sunday, type: :boolean, default: true + property :active_on_monday, type: :boolean, default: true + property :active_on_tuesday, type: :boolean, default: true + property :active_on_wednesday, type: :boolean, default: true + property :active_on_thursday, type: :boolean, default: true + property :active_on_friday, type: :boolean, default: true + property :active_on_saturday, type: :boolean, default: true belongs_to :supplier has_and_belongs_to_many :products, storing_keys: true @@ -40,7 +47,7 @@ class ProductCategory def week_days=(ary) #write_attribute(:week_days, ary.map(&:to_i)) typecasted_value = ary.map(&:to_i) - #return typecasted_value if @week_days == typecasted_value + #return typecasted_value if @week_days == typecasted_value week_days_will_change! unless @skip_dirty_tracking || typecasted_value == week_days @week_days = typecasted_value end diff --git a/app/models/supplier.rb b/app/models/supplier.rb index 28f5a9ee..92e6736d 100644 --- a/app/models/supplier.rb +++ b/app/models/supplier.rb @@ -18,6 +18,7 @@ class Supplier property :city property :country, default: 'Netherlands' property :facebook_promotion_url + property :week_starts_on_monday, type: :boolean, default: true #LOCATION property :lat, type: Float #, default: 52.08062426379751 @@ -148,10 +149,6 @@ class Supplier SupplierMailer.creation(self).deliver_now end - def week_starts_on_monday? - true - end - private def add_section_on_create diff --git a/app/serializers/product_category_serializer.rb b/app/serializers/product_category_serializer.rb index e4913e20..c698a13b 100644 --- a/app/serializers/product_category_serializer.rb +++ b/app/serializers/product_category_serializer.rb @@ -1,5 +1,7 @@ class ProductCategorySerializer < Qwaiter::Serializer embed :ids, include: true - attributes :name + attributes :name, :supplier_id, :active_on_sunday, :active_on_monday, :active_on_tuesday, :active_on_wednesday, + :active_on_thursday, :active_on_friday, :active_on_saturday, :full_day, :start_from, :end_on, + :position has_many :products end diff --git a/app/serializers/supplier_supplier_serializer.rb b/app/serializers/supplier_supplier_serializer.rb index b129c95f..cf1f31dc 100644 --- a/app/serializers/supplier_supplier_serializer.rb +++ b/app/serializers/supplier_supplier_serializer.rb @@ -1,7 +1,7 @@ class SupplierSupplierSerializer < Qwaiter::Serializer self.root = :supplier attributes :extended_version, :open, :name, :lat, :lng, :email, :time_zone, :address, :house_number, :house_number_addition, :postal_code, :city, :country, - :facebook_promotion_url, :iens_profile + :facebook_promotion_url, :iens_profile, :week_starts_on_monday def extended_version false diff --git a/config/environments/development.rb b/config/environments/development.rb index 0c7ec2b4..980596e5 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -12,7 +12,7 @@ Qwaiter::Application.configure do # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false - config.action_controller.action_on_unpermitted_parameters = :raise + config.action_controller.action_on_unpermitted_parameters = :log config.ember.variant = :development diff --git a/config/environments/test.rb b/config/environments/test.rb index 2faaf24c..8fb7df32 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -24,7 +24,7 @@ Qwaiter::Application.configure do # Show full error reports and disable caching config.consider_all_requests_local = true config.action_controller.perform_caching = false - config.action_controller.action_on_unpermitted_parameters = :raise + config.action_controller.action_on_unpermitted_parameters = :log # Raise exceptions instead of rendering exception templates config.action_dispatch.show_exceptions = false diff --git a/config/locales/supplier.en.yml b/config/locales/supplier.en.yml index fcd545c8..1f064647 100644 --- a/config/locales/supplier.en.yml +++ b/config/locales/supplier.en.yml @@ -120,6 +120,7 @@ en: title: Reviews explanation: Fill in your Iens id. You can find this id in the web location of your page product_category: + # week days depricated week_days: abbreviation: sunday: S @@ -129,6 +130,19 @@ en: thursday: T friday: F saturday: S + edit: + modal: + title: Edit ${models.product_category} + body_header: '' + active_between: + top: Active between + middle: and + move: + modal: + title: Move ${models.product_category|downcase} + body_header: '' + move_to_top: Move to top + move_below_label: "Place below ${product_category|downcase}" product: new: 'New ${model.product|downcase}' preview: diff --git a/config/locales/supplier.nl.yml b/config/locales/supplier.nl.yml index 4aa7cc2e..8bffd4a1 100644 --- a/config/locales/supplier.nl.yml +++ b/config/locales/supplier.nl.yml @@ -128,6 +128,19 @@ nl: thursday: D friday: V saturday: Z + edit: + modal: + title: Bewerk ${models.product_category} + body_header: '' + active_between: + top: Actief tussen + middle: en + move: + modal: + title: Verplaats ${models.product_category|downcase} + body_header: '' + move_to_top: Plaats bovenaan + move_below_label: "Plaats onder ${product_category|downcase}" product: new: 'Nieuw ${model.product|downcase}' preview: diff --git a/vendor/assets/ember/development/ember-data.js b/vendor/assets/ember/development/ember-data.js index d6dab7b6..e8433e42 100644 --- a/vendor/assets/ember/development/ember-data.js +++ b/vendor/assets/ember/development/ember-data.js @@ -1,5 +1,5 @@ -// Fetched from channel: canary, with url http://builds.emberjs.com/canary/ember-data.js -// Fetched on: 2014-08-07T09:19:38Z +// Fetched from channel: release, with url http://builds.emberjs.com/beta/ember-data.js +// Fetched on: 2014-11-24T13:02:13Z (function(global){ var define, requireModule, require, requirejs; @@ -152,13 +152,12 @@ define("activemodel-adapter/system", __exports__.ActiveModelSerializer = ActiveModelSerializer; }); define("activemodel-adapter/system/active_model_adapter", - ["ember-data/adapters","ember-data/system/adapter","ember-inflector","activemodel-adapter/system/active_model_serializer","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + ["ember-data/adapters","ember-data/system/adapter","ember-inflector","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { "use strict"; var RESTAdapter = __dependency1__.RESTAdapter; var InvalidError = __dependency2__.InvalidError; var pluralize = __dependency3__.pluralize; - var ActiveModelSerializer = __dependency4__["default"]; /** @module ember-data @@ -293,7 +292,7 @@ define("activemodel-adapter/system/active_model_adapter", https://tools.ietf.org/html/rfc4918#section-11.2 @method ajaxError - @param jqXHR + @param {Object} jqXHR @return error */ ajaxError: function(jqXHR) { @@ -482,15 +481,17 @@ define("activemodel-adapter/system/active_model_serializer", @method serializePolymorphicType @param {DS.Model} record @param {Object} json - @param relationship + @param {Object} relationship */ serializePolymorphicType: function(record, json, relationship) { var key = relationship.key; var belongsTo = get(record, key); + var jsonKey = underscore(key + "_type"); - if (belongsTo) { - key = this.keyForAttribute(key); - json[key + "_type"] = capitalize(belongsTo.constructor.typeKey); + if (Ember.isNone(belongsTo)) { + json[jsonKey] = null; + } else { + json[jsonKey] = capitalize(camelize(belongsTo.constructor.typeKey)); } }, @@ -615,8 +616,8 @@ define("activemodel-adapter/system/active_model_serializer", __exports__["default"] = ActiveModelSerializer; }); define("ember-data", - ["ember-data/core","ember-data/ext/date","ember-data/system/store","ember-data/system/model","ember-data/system/changes","ember-data/system/adapter","ember-data/system/debug","ember-data/system/record_arrays","ember-data/system/record_array_manager","ember-data/adapters","ember-data/serializers/json_serializer","ember-data/serializers/rest_serializer","ember-inflector","ember-data/serializers/embedded_records_mixin","activemodel-adapter","ember-data/transforms","ember-data/system/relationships","ember-data/ember-initializer","ember-data/setup-container","ember-data/system/container_proxy","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __exports__) { + ["ember-data/core","ember-data/ext/date","ember-data/system/promise_proxies","ember-data/system/store","ember-data/system/model","ember-data/system/adapter","ember-data/system/debug","ember-data/system/record_arrays","ember-data/system/record_array_manager","ember-data/adapters","ember-data/serializers/json_serializer","ember-data/serializers/rest_serializer","ember-inflector","ember-data/serializers/embedded_records_mixin","activemodel-adapter","ember-data/transforms","ember-data/system/relationships","ember-data/ember-initializer","ember-data/setup-container","ember-data/system/container_proxy","ember-data/system/relationships/relationship","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __dependency17__, __dependency18__, __dependency19__, __dependency20__, __dependency21__, __exports__) { "use strict"; /** Ember Data @@ -630,21 +631,13 @@ define("ember-data", var DS = __dependency1__["default"]; - var Store = __dependency3__.Store; var PromiseArray = __dependency3__.PromiseArray; var PromiseObject = __dependency3__.PromiseObject; - var Model = __dependency4__.Model; - var Errors = __dependency4__.Errors; - var RootState = __dependency4__.RootState; - var attr = __dependency4__.attr; - var AttributeChange = __dependency5__.AttributeChange; - var RelationshipChange = __dependency5__.RelationshipChange; - var RelationshipChangeAdd = __dependency5__.RelationshipChangeAdd; - var RelationshipChangeRemove = __dependency5__.RelationshipChangeRemove; - var OneToManyChange = __dependency5__.OneToManyChange; - var ManyToNoneChange = __dependency5__.ManyToNoneChange; - var OneToOneChange = __dependency5__.OneToOneChange; - var ManyToManyChange = __dependency5__.ManyToManyChange; + var Store = __dependency4__.Store; + var Model = __dependency5__.Model; + var Errors = __dependency5__.Errors; + var RootState = __dependency5__.RootState; + var attr = __dependency5__.attr; var InvalidError = __dependency6__.InvalidError; var Adapter = __dependency6__.Adapter; var DebugAdapter = __dependency7__["default"]; @@ -672,6 +665,7 @@ define("ember-data", var setupContainer = __dependency19__["default"]; var ContainerProxy = __dependency20__["default"]; + var Relationship = __dependency21__.Relationship; DS.Store = Store; DS.PromiseArray = PromiseArray; @@ -682,14 +676,6 @@ define("ember-data", DS.attr = attr; DS.Errors = Errors; - DS.AttributeChange = AttributeChange; - DS.RelationshipChange = RelationshipChange; - DS.RelationshipChangeAdd = RelationshipChangeAdd; - DS.OneToManyChange = OneToManyChange; - DS.ManyToNoneChange = OneToManyChange; - DS.OneToOneChange = OneToOneChange; - DS.ManyToManyChange = ManyToManyChange; - DS.Adapter = Adapter; DS.InvalidError = InvalidError; @@ -721,6 +707,8 @@ define("ember-data", DS.belongsTo = belongsTo; DS.hasMany = hasMany; + DS.Relationship = Relationship; + DS.ContainerProxy = ContainerProxy; DS._setupContainer = setupContainer; @@ -1072,15 +1060,16 @@ define("ember-data/adapters/fixture_adapter", var adapter = this; return new Ember.RSVP.Promise(function(resolve) { + var value = Ember.copy(callback.call(context), true); if (get(adapter, 'simulateRemoteResponse')) { // Schedule with setTimeout Ember.run.later(function() { - resolve(callback.call(context)); + resolve(value); }, get(adapter, 'latency')); } else { // Asynchronous, but at the of the runloop with zero latency Ember.run.schedule('actions', null, function() { - resolve(callback.call(context)); + resolve(value); }); } }, "DS: FixtureAdapter#simulateRemoteCall"); @@ -1088,14 +1077,16 @@ define("ember-data/adapters/fixture_adapter", }); }); define("ember-data/adapters/rest_adapter", - ["ember-data/system/adapter","exports"], - function(__dependency1__, __exports__) { + ["ember-data/system/adapter","ember-data/system/map","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; /** @module ember-data */ - var Adapter = __dependency1__["default"]; + var Adapter = __dependency1__.Adapter; + var InvalidError = __dependency1__.InvalidError; + var MapWithDefault = __dependency2__.MapWithDefault; var get = Ember.get; var forEach = Ember.ArrayPolyfills.forEach; @@ -1121,12 +1112,33 @@ define("ember-data/adapters/rest_adapter", ```js { "post": { + "id": 1, "title": "I'm Running to Reform the W3C's Tag", "author": "Yehuda Katz" } } ``` + Similarly, in response to a `GET` request for `/posts`, the JSON should + look like this: + + ```js + { + "posts": [ + { + "id": 1, + "title": "I'm Running to Reform the W3C's Tag", + "author": "Yehuda Katz" + }, + { + "id": 2, + "title": "Rails is omakase", + "author": "D2H" + } + ] + } + ``` + ### Conventional Names Attribute names in your JSON payload should be the camelCased versions of @@ -1147,6 +1159,7 @@ define("ember-data/adapters/rest_adapter", ```js { "person": { + "id": 5, "firstName": "Barack", "lastName": "Obama", "occupation": "President" @@ -1273,6 +1286,9 @@ define("ember-data/adapters/rest_adapter", will also send a request to: `GET /comments?ids[]=1&ids[]=2` + Note: Requests coalescing rely on URL building strategy. So if you override `buildUrl` in your app + `groupRecordsForFindMany` more likely should be overriden as well in order for coalescing to work. + @property coalesceFindRequests @type {boolean} */ @@ -1460,7 +1476,7 @@ define("ember-data/adapters/rest_adapter", @param {String} url @return {Promise} promise */ - findHasMany: function(store, record, url) { + findHasMany: function(store, record, url, relationship) { var host = get(this, 'host'); var id = get(record, 'id'); var type = record.constructor.typeKey; @@ -1499,7 +1515,7 @@ define("ember-data/adapters/rest_adapter", @param {String} url @return {Promise} promise */ - findBelongsTo: function(store, record, url) { + findBelongsTo: function(store, record, url, relationship) { var id = get(record, 'id'); var type = record.constructor.typeKey; @@ -1601,7 +1617,7 @@ define("ember-data/adapters/rest_adapter", //We might get passed in an array of ids from findMany //in which case we don't want to modify the url, as the //ids will be passed in through a query param - if (id && !Ember.isArray(id)) { url.push(id); } + if (id && !Ember.isArray(id)) { url.push(encodeURIComponent(id)); } if (prefix) { url.unshift(prefix); } @@ -1686,15 +1702,44 @@ define("ember-data/adapters/rest_adapter", loaded separately by `findMany`. */ groupRecordsForFindMany: function (store, records) { - var groups = Ember.MapWithDefault.create({defaultValue: function(){return [];}}); + var groups = MapWithDefault.create({defaultValue: function(){return [];}}); var adapter = this; + forEach.call(records, function(record){ var baseUrl = adapter._stripIDFromURL(store, record); groups.get(baseUrl).push(record); }); + + function splitGroupToFitInUrl(group, maxUrlLength) { + var baseUrl = adapter._stripIDFromURL(store, group[0]); + var idsSize = 0; + var splitGroups = [[]]; + + forEach.call(group, function(record) { + var additionalLength = '&ids[]='.length + record.get('id.length'); + if (baseUrl.length + idsSize + additionalLength >= maxUrlLength) { + idsSize = 0; + splitGroups.push([]); + } + + idsSize += additionalLength; + + var lastGroupIndex = splitGroups.length - 1; + splitGroups[lastGroupIndex].push(record); + }); + + return splitGroups; + } + var groupsArray = []; - groups.forEach(function(key, group){ - groupsArray.push(group); + groups.forEach(function(group, key){ + // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers + var maxUrlLength = 2048; + var splitGroups = splitGroupToFitInUrl(group, maxUrlLength); + + forEach.call(splitGroups, function(splitGroup) { + groupsArray.push(splitGroup); + }); }); return groupsArray; @@ -1761,9 +1806,10 @@ define("ember-data/adapters/rest_adapter", @method ajaxError @param {Object} jqXHR + @param {Object} responseText @return {Object} jqXHR */ - ajaxError: function(jqXHR) { + ajaxError: function(jqXHR, responseText) { if (jqXHR && typeof jqXHR === 'object') { jqXHR.then = null; } @@ -1771,6 +1817,32 @@ define("ember-data/adapters/rest_adapter", return jqXHR; }, + /** + Takes an ajax response, and returns the json payload. + + By default this hook just returns the jsonPayload passed to it. + You might want to override it in two cases: + + 1. Your API might return useful results in the request headers. + If you need to access these, you can override this hook to copy them + from jqXHR to the payload object so they can be processed in you serializer. + + + 2. Your API might return errors as successful responses with status code + 200 and an Errors text or object. You can return a DS.InvalidError from + this hook and it will automatically reject the promise and put your record + into the invald state. + + @method ajaxError + @param {Object} jqXHR + @param {Object} jsonPayload + @return {Object} jqXHR + */ + + ajaxSuccess: function(jqXHR, jsonPayload) { + return jsonPayload; + }, + /** Takes a URL, an HTTP method and a hash of data, and makes an HTTP request. @@ -1801,12 +1873,17 @@ define("ember-data/adapters/rest_adapter", return new Ember.RSVP.Promise(function(resolve, reject) { var hash = adapter.ajaxOptions(url, type, options); - hash.success = function(json) { - Ember.run(null, resolve, json); + hash.success = function(json, textStatus, jqXHR) { + json = adapter.ajaxSuccess(jqXHR, json); + if (json instanceof InvalidError) { + Ember.run(null, reject, json); + } else { + Ember.run(null, resolve, json); + } }; hash.error = function(jqXHR, textStatus, errorThrown) { - Ember.run(null, reject, adapter.ajaxError(jqXHR)); + Ember.run(null, reject, adapter.ajaxError(jqXHR, jqXHR.responseText)); }; Ember.$.ajax(hash); @@ -1874,11 +1951,11 @@ define("ember-data/core", /** @property VERSION @type String - @default '1.0.0-beta.9+canary.20c9acf5d4' + @default '1.0.0-beta.11' @static */ DS = Ember.Namespace.create({ - VERSION: '1.0.0-beta.9+canary.20c9acf5d4' + VERSION: '1.0.0-beta.11' }); if (Ember.libraries) { @@ -1997,7 +2074,8 @@ define("ember-data/ext/date", /** @method parse - @param date + @param {Date} date + @return {Number} timestamp */ Ember.Date.parse = function (date) { var timestamp, struct, minutesOffset = 0; @@ -2149,15 +2227,13 @@ define("ember-data/serializers", __exports__.RESTSerializer = RESTSerializer; }); define("ember-data/serializers/embedded_records_mixin", - ["ember-inflector","exports"], - function(__dependency1__, __exports__) { + ["exports"], + function(__exports__) { "use strict"; var get = Ember.get; var forEach = Ember.EnumerableUtils.forEach; var camelize = Ember.String.camelize; - var pluralize = __dependency1__.pluralize; - /** ## Using Embedded Records @@ -2171,21 +2247,29 @@ define("ember-data/serializers/embedded_records_mixin", ```js App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, { attrs: { - author: {embedded: 'always'}, - comments: {serialize: 'ids'} + author: { embedded: 'always' }, + comments: { serialize: 'ids' } } - }) + }); ``` + Note that this use of `{ embedded: 'always' }` is unrelated to + the `{ embedded: 'always' }` that is defined as an option on `DS.attr` as part of + defining a model while working with the ActiveModelSerializer. Nevertheless, + using `{ embedded: 'always' }` as an option to DS.attr is not a valid way to setup + embedded records. - The `attrs` option for a resource `{embedded: 'always'}` is shorthand for: + The `attrs` option for a resource `{ embedded: 'always' }` is shorthand for: ```js - {serialize: 'records', deserialize: 'records'} + { + serialize: 'records', + deserialize: 'records' + } ``` ### Configuring Attrs - A resource's `attrs` option may be set to use `ids`, `records` or `no` for the + A resource's `attrs` option may be set to use `ids`, `records` or false for the `serialize` and `deserialize` settings. The `attrs` property can be set on the ApplicationSerializer or a per-type @@ -2197,19 +2281,23 @@ define("ember-data/serializers/embedded_records_mixin", the vanilla EmbeddedRecordsMixin. Likewise, to embed JSON in the payload while serializing `serialize: 'records'` is the setting to use. There is an option of not embedding JSON in the serialized payload by using `serialize: 'ids'`. If you - do not want the relationship sent at all, you can use `serialize: 'no'`. + do not want the relationship sent at all, you can use `serialize: false`. ### EmbeddedRecordsMixin defaults If you do not overwrite `attrs` for a specific relationship, the `EmbeddedRecordsMixin` will behave in the following way: - BelongsTo: `{serialize:'id', deserialize:'id'}` - HasMany: `{serialize:'no', deserialize:'ids'}` + BelongsTo: `{ serialize: 'id', deserialize: 'id' }` + HasMany: `{ serialize: false, deserialize: 'ids' }` ### Model Relationships - Embedded records must have a model defined to be extracted and serialized. + Embedded records must have a model defined to be extracted and serialized. Note that + when defining any relationships on your model such as `belongsTo` and `hasMany`, you + should not both specify `async:true` and also indicate through the serializer's + `attrs` attribute that the related model should be embedded. If a model is + declared embedded, then do not use `async:true`. To successfully extract and serialize embedded records the model relationships must be setup correcty See the @@ -2221,9 +2309,9 @@ define("ember-data/serializers/embedded_records_mixin", ### Example JSON payloads, Models and Serializers - **When customizing a serializer it is imporant to grok what the cusomizations - are, please read the docs for the methods this mixin provides, in case you need - to modify to fit your specific needs.** + **When customizing a serializer it is important to grok what the customizations + are. Please read the docs for the methods this mixin provides, in case you need + to modify it to fit your specific needs.** For example review the docs for each method of this mixin: * [normalize](/api/data/classes/DS.EmbeddedRecordsMixin.html#method_normalize) @@ -2326,7 +2414,6 @@ define("ember-data/serializers/embedded_records_mixin", */ serializeBelongsTo: function(record, json, relationship) { var attr = relationship.key; - var attrs = this.get('attrs'); if (this.noSerializeOptionSpecified(attr)) { this._super(record, json, relationship); return; @@ -2436,7 +2523,6 @@ define("ember-data/serializers/embedded_records_mixin", */ serializeHasMany: function(record, json, relationship) { var attr = relationship.key; - var attrs = this.get('attrs'); if (this.noSerializeOptionSpecified(attr)) { this._super(record, json, relationship); return; @@ -2511,8 +2597,6 @@ define("ember-data/serializers/embedded_records_mixin", // checks config for attrs option to serialize records noSerializeOptionSpecified: function(attr) { var option = this.attrsOption(attr); - var serializeRecords = this.hasSerializeRecordsOption(attr); - var serializeIds = this.hasSerializeIdsOption(attr); return !(option && (option.serialize || option.embedded)); }, @@ -2539,7 +2623,12 @@ define("ember-data/serializers/embedded_records_mixin", if (serializer.hasDeserializeRecordsOption(key)) { var embeddedType = store.modelFor(relationship.type.typeKey); if (relationship.kind === "hasMany") { - extractEmbeddedHasMany(store, key, embeddedType, partial); + if (relationship.options.polymorphic) { + extractEmbeddedHasManyPolymorphic(store, key, partial); + } + else { + extractEmbeddedHasMany(store, key, embeddedType, partial); + } } if (relationship.kind === "belongsTo") { extractEmbeddedBelongsTo(store, key, embeddedType, partial); @@ -2569,6 +2658,28 @@ define("ember-data/serializers/embedded_records_mixin", return hash; } + function extractEmbeddedHasManyPolymorphic(store, key, hash) { + if (!hash[key]) { + return hash; + } + + var ids = []; + + forEach(hash[key], function(data) { + var typeKey = data.type; + var embeddedSerializer = store.serializerFor(typeKey); + var embeddedType = store.modelFor(typeKey); + var primaryKey = get(embeddedSerializer, 'primaryKey'); + + var embeddedRecord = embeddedSerializer.normalize(embeddedType, data, null); + store.push(embeddedType, embeddedRecord); + ids.push({ id: embeddedRecord[primaryKey], type: typeKey }); + }); + + hash[key] = ids; + return hash; + } + function extractEmbeddedBelongsTo(store, key, embeddedType, hash) { if (!hash[key]) { return hash; @@ -2586,12 +2697,10 @@ define("ember-data/serializers/embedded_records_mixin", __exports__["default"] = EmbeddedRecordsMixin; }); define("ember-data/serializers/json_serializer", - ["ember-data/system/changes","exports"], - function(__dependency1__, __exports__) { + ["exports"], + function(__exports__) { "use strict"; - var RelationshipChange = __dependency1__.RelationshipChange; var get = Ember.get; - var set = Ember.set; var isNone = Ember.isNone; var map = Ember.ArrayPolyfills.map; var merge = Ember.merge; @@ -2701,7 +2810,7 @@ define("ember-data/serializers/json_serializer", @return {Object} data The transformed data object */ applyTransforms: function(type, data) { - type.eachTransformedAttribute(function(key, type) { + type.eachTransformedAttribute(function applyTransform(key, type) { if (!data.hasOwnProperty(key)) { return; } var transform = this.transformFor(type); @@ -2787,7 +2896,7 @@ define("ember-data/serializers/json_serializer", @private */ normalizeAttributes: function(type, hash) { - var payloadKey, key; + var payloadKey; if (this.keyForAttribute) { type.eachAttribute(function(key) { @@ -2806,7 +2915,7 @@ define("ember-data/serializers/json_serializer", @private */ normalizeRelationships: function(type, hash) { - var payloadKey, key; + var payloadKey; if (this.keyForRelationship) { type.eachRelationship(function(key, relationship) { @@ -2880,6 +2989,21 @@ define("ember-data/serializers/json_serializer", return key; }, + /** + Check attrs.key.serialize property to inform if the `key` + can be serialized + + @method _canSerialize + @private + @param {String} key + @return {boolean} true if the key can be serialized + */ + _canSerialize: function(key) { + var attrs = get(this, 'attrs'); + + return !attrs || !attrs[key] || attrs[key].serialize !== false; + }, + // SERIALIZE /** Called when a record is saved in order to convert the @@ -3102,29 +3226,25 @@ define("ember-data/serializers/json_serializer", @param {Object} attribute */ serializeAttribute: function(record, json, key, attribute) { - var attrs = get(this, 'attrs'); - var value = get(record, key); var type = attribute.type; - if (type) { - var transform = this.transformFor(type); - value = transform.serialize(value); - } + if (this._canSerialize(key)) { + var value = get(record, key); + if (type) { + var transform = this.transformFor(type); + value = transform.serialize(value); + } - // If attrs.key.serialize is false, do not include the value in the - // response to the server at all. - if (attrs && attrs[key] && attrs[key].serialize === false) { - return; - } - // if provided, use the mapping provided by `attrs` in - // the serializer - var payloadKey = this._getMappedKey(key); + // if provided, use the mapping provided by `attrs` in + // the serializer + var payloadKey = this._getMappedKey(key); - if (payloadKey === key && this.keyForAttribute) { - payloadKey = this.keyForAttribute(key); - } + if (payloadKey === key && this.keyForAttribute) { + payloadKey = this.keyForAttribute(key); + } - json[payloadKey] = value; + json[payloadKey] = value; + } }, /** @@ -3153,25 +3273,28 @@ define("ember-data/serializers/json_serializer", @param {Object} relationship */ serializeBelongsTo: function(record, json, relationship) { - var attrs = get(this, 'attrs'); var key = relationship.key; - var belongsTo = get(record, key); - // if provided, use the mapping provided by `attrs` in - // the serializer - var payloadKey = this._getMappedKey(key); - if (payloadKey === key && this.keyForRelationship) { - payloadKey = this.keyForRelationship(key, "belongsTo"); - } + if (this._canSerialize(key)) { + var belongsTo = get(record, key); - if (isNone(belongsTo)) { - json[payloadKey] = belongsTo; - } else { - json[payloadKey] = get(belongsTo, 'id'); - } + // if provided, use the mapping provided by `attrs` in + // the serializer + var payloadKey = this._getMappedKey(key); + if (payloadKey === key && this.keyForRelationship) { + payloadKey = this.keyForRelationship(key, "belongsTo"); + } - if (relationship.options.polymorphic) { - this.serializePolymorphicType(record, json, relationship); + //Need to check whether the id is there for new&async records + if (isNone(belongsTo) || isNone(get(belongsTo, 'id'))) { + json[payloadKey] = null; + } else { + json[payloadKey] = get(belongsTo, 'id'); + } + + if (relationship.options.polymorphic) { + this.serializePolymorphicType(record, json, relationship); + } } }, @@ -3200,22 +3323,24 @@ define("ember-data/serializers/json_serializer", @param {Object} relationship */ serializeHasMany: function(record, json, relationship) { - var attrs = get(this, 'attrs'); var key = relationship.key; - var payloadKey; - // if provided, use the mapping provided by `attrs` in - // the serializer - payloadKey = this._getMappedKey(key); - if (payloadKey === key && this.keyForRelationship) { - payloadKey = this.keyForRelationship(key, "hasMany"); - } + if (this._canSerialize(key)) { + var payloadKey; - var relationshipType = RelationshipChange.determineRelationshipType(record.constructor, relationship); + // if provided, use the mapping provided by `attrs` in + // the serializer + payloadKey = this._getMappedKey(key); + if (payloadKey === key && this.keyForRelationship) { + payloadKey = this.keyForRelationship(key, "hasMany"); + } - if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { - json[payloadKey] = get(record, key).mapBy('id'); - // TODO support for polymorphic manyToNone and manyToMany relationships + var relationshipType = record.constructor.determineRelationshipType(relationship); + + if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') { + json[payloadKey] = get(record, key).mapBy('id'); + // TODO support for polymorphic manyToNone and manyToMany relationships + } } }, @@ -3233,7 +3358,12 @@ define("ember-data/serializers/json_serializer", var key = relationship.key, belongsTo = get(record, key); key = this.keyForAttribute ? this.keyForAttribute(key) : key; - json[key + "_type"] = belongsTo.constructor.typeKey; + + if (Ember.isNone(belongsTo)) { + json[key + "_type"] = null; + } else { + json[key + "_type"] = belongsTo.constructor.typeKey; + } } }); ``` @@ -3615,7 +3745,6 @@ define("ember-data/serializers/rest_serializer", var JSONSerializer = __dependency1__["default"]; var get = Ember.get; - var set = Ember.set; var forEach = Ember.ArrayPolyfills.forEach; var map = Ember.ArrayPolyfills.map; var camelize = Ember.String.camelize; @@ -3666,7 +3795,7 @@ define("ember-data/serializers/rest_serializer", @namespace DS @extends DS.JSONSerializer */ - __exports__["default"] = JSONSerializer.extend({ + var RESTSerializer = JSONSerializer.extend({ /** If you want to do normalizations specific to some part of the payload, you can specify those under `normalizeHash`. @@ -3879,6 +4008,10 @@ define("ember-data/serializers/rest_serializer", for (var prop in payload) { var typeName = this.typeForRoot(prop); + if (!store.modelFactoryFor(typeName)){ + Ember.warn(this.warnMessageNoModelForKey(prop, typeName), false); + continue; + } var type = store.modelFor(typeName); var isPrimary = type.typeKey === primaryTypeName; var value = payload[prop]; @@ -4032,6 +4165,10 @@ define("ember-data/serializers/rest_serializer", } var typeName = this.typeForRoot(typeKey); + if (!store.modelFactoryFor(typeName)) { + Ember.warn(this.warnMessageNoModelForKey(prop, typeName), false); + continue; + } var type = store.modelFor(typeName); var typeSerializer = store.serializerFor(type); var isPrimary = (!forcedSecondary && (type.typeKey === primaryTypeName)); @@ -4087,6 +4224,10 @@ define("ember-data/serializers/rest_serializer", for (var prop in payload) { var typeName = this.typeForRoot(prop); + if (!store.modelFactoryFor(typeName, prop)){ + Ember.warn(this.warnMessageNoModelForKey(prop, typeName), false); + continue; + } var type = store.modelFor(typeName); var typeSerializer = store.serializerFor(type); @@ -4287,8 +4428,9 @@ define("ember-data/serializers/rest_serializer", ``` @method serialize - @param record - @param options + @param {subclass of DS.Model} record + @param {Object} options + @return {Object} json */ serialize: function(record, options) { return this._super.apply(this, arguments); @@ -4296,7 +4438,7 @@ define("ember-data/serializers/rest_serializer", /** You can use this method to customize the root keys serialized into the JSON. - By default the REST Serializer sends the typeKey of a model, whih is a camelized + By default the REST Serializer sends the typeKey of a model, which is a camelized version of the name. For example, your server may expect underscored root objects. @@ -4334,9 +4476,23 @@ define("ember-data/serializers/rest_serializer", var key = relationship.key; var belongsTo = get(record, key); key = this.keyForAttribute ? this.keyForAttribute(key) : key; - json[key + "Type"] = belongsTo.constructor.typeKey; + if (Ember.isNone(belongsTo)) { + json[key + "Type"] = null; + } else { + json[key + "Type"] = Ember.String.camelize(belongsTo.constructor.typeKey); + } } }); + + Ember.runInDebug(function(){ + RESTSerializer.reopen({ + warnMessageNoModelForKey: function(prop, typeKey){ + return 'Encountered "' + prop + '" in payload, but no model was found for model name "' + typeKey + '" (resolved model name using ' + this.constructor.toString() + '.typeForRoot("' + prop + '"))'; + } + }); + }); + + __exports__["default"] = RESTSerializer; }); define("ember-data/setup-container", ["ember-data/initializers/store","ember-data/initializers/transforms","ember-data/initializers/store_injections","ember-data/initializers/data_adapter","activemodel-adapter/setup-container","exports"], @@ -4369,8 +4525,6 @@ define("ember-data/system/adapter", */ var get = Ember.get; - var set = Ember.set; - var map = Ember.ArrayPolyfills.map; var errorProps = [ 'description', @@ -4827,465 +4981,6 @@ define("ember-data/system/adapter", __exports__.Adapter = Adapter; __exports__["default"] = Adapter; }); -define("ember-data/system/changes", - ["ember-data/system/changes/relationship_change","exports"], - function(__dependency1__, __exports__) { - "use strict"; - /** - @module ember-data - */ - - var RelationshipChange = __dependency1__.RelationshipChange; - var RelationshipChangeAdd = __dependency1__.RelationshipChangeAdd; - var RelationshipChangeRemove = __dependency1__.RelationshipChangeRemove; - var OneToManyChange = __dependency1__.OneToManyChange; - var ManyToNoneChange = __dependency1__.ManyToNoneChange; - var OneToOneChange = __dependency1__.OneToOneChange; - var ManyToManyChange = __dependency1__.ManyToManyChange; - - __exports__.RelationshipChange = RelationshipChange; - __exports__.RelationshipChangeAdd = RelationshipChangeAdd; - __exports__.RelationshipChangeRemove = RelationshipChangeRemove; - __exports__.OneToManyChange = OneToManyChange; - __exports__.ManyToNoneChange = ManyToNoneChange; - __exports__.OneToOneChange = OneToOneChange; - __exports__.ManyToManyChange = ManyToManyChange; - }); -define("ember-data/system/changes/relationship_change", - ["ember-data/system/model/model","ember-data/system/relationship-meta","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - /** - @module ember-data - */ - - var Model = __dependency1__["default"]; - var isSyncRelationship = __dependency2__.isSyncRelationship; - - var get = Ember.get; - var set = Ember.set; - var forEach = Ember.EnumerableUtils.forEach; - - /** - @class RelationshipChange - @namespace DS - @private - @constructor - */ - var RelationshipChange = function(options) { - this.parentRecord = options.parentRecord; - this.childRecord = options.childRecord; - this.firstRecord = options.firstRecord; - this.firstRecordKind = options.firstRecordKind; - this.firstRecordName = options.firstRecordName; - this.secondRecord = options.secondRecord; - this.secondRecordKind = options.secondRecordKind; - this.secondRecordName = options.secondRecordName; - this.changeType = options.changeType; - this.store = options.store; - - this.committed = {}; - }; - - /** - @class RelationshipChangeAdd - @namespace DS - @private - @constructor - */ - function RelationshipChangeAdd(options){ - RelationshipChange.call(this, options); - } - - /** - @class RelationshipChangeRemove - @namespace DS - @private - @constructor - */ - function RelationshipChangeRemove(options){ - RelationshipChange.call(this, options); - } - - RelationshipChange.create = function(options) { - return new RelationshipChange(options); - }; - - RelationshipChangeAdd.create = function(options) { - return new RelationshipChangeAdd(options); - }; - - RelationshipChangeRemove.create = function(options) { - return new RelationshipChangeRemove(options); - }; - - var OneToManyChange = {}; - var OneToNoneChange = {}; - var ManyToNoneChange = {}; - var OneToOneChange = {}; - var ManyToManyChange = {}; - - RelationshipChange._createChange = function(options){ - if (options.changeType === 'add') { - return RelationshipChangeAdd.create(options); - } - if (options.changeType === 'remove') { - return RelationshipChangeRemove.create(options); - } - }; - - RelationshipChange.determineRelationshipType = function(recordType, knownSide){ - var knownKey = knownSide.key, key, otherKind; - var knownKind = knownSide.kind; - - var inverse = recordType.inverseFor(knownKey); - - if (inverse) { - key = inverse.name; - otherKind = inverse.kind; - } - - if (!inverse) { - return knownKind === 'belongsTo' ? 'oneToNone' : 'manyToNone'; - } else { - if (otherKind === 'belongsTo') { - return knownKind === 'belongsTo' ? 'oneToOne' : 'manyToOne'; - } else { - return knownKind === 'belongsTo' ? 'oneToMany' : 'manyToMany'; - } - } - }; - - RelationshipChange.createChange = function(firstRecord, secondRecord, store, options){ - // Get the type of the child based on the child's client ID - var firstRecordType = firstRecord.constructor, changeType; - changeType = RelationshipChange.determineRelationshipType(firstRecordType, options); - if (changeType === 'oneToMany') { - return OneToManyChange.createChange(firstRecord, secondRecord, store, options); - } else if (changeType === 'manyToOne') { - return OneToManyChange.createChange(secondRecord, firstRecord, store, options); - } else if (changeType === 'oneToNone') { - return OneToNoneChange.createChange(firstRecord, secondRecord, store, options); - } else if (changeType === 'manyToNone') { - return ManyToNoneChange.createChange(firstRecord, secondRecord, store, options); - } else if (changeType === 'oneToOne') { - return OneToOneChange.createChange(firstRecord, secondRecord, store, options); - } else if (changeType === 'manyToMany') { - return ManyToManyChange.createChange(firstRecord, secondRecord, store, options); - } - }; - - OneToNoneChange.createChange = function(childRecord, parentRecord, store, options) { - var key = options.key; - var change = RelationshipChange._createChange({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - store: store, - changeType: options.changeType, - firstRecordName: key, - firstRecordKind: 'belongsTo' - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - - return change; - }; - - ManyToNoneChange.createChange = function(childRecord, parentRecord, store, options) { - var key = options.key; - var change = RelationshipChange._createChange({ - parentRecord: childRecord, - childRecord: parentRecord, - secondRecord: childRecord, - store: store, - changeType: options.changeType, - secondRecordName: options.key, - secondRecordKind: 'hasMany' - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - return change; - }; - - - ManyToManyChange.createChange = function(childRecord, parentRecord, store, options) { - // If the name of the belongsTo side of the relationship is specified, - // use that - // If the type of the parent is specified, look it up on the child's type - // definition. - var key = options.key; - - var change = RelationshipChange._createChange({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - secondRecord: parentRecord, - firstRecordKind: 'hasMany', - secondRecordKind: 'hasMany', - store: store, - changeType: options.changeType, - firstRecordName: key - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - - return change; - }; - - OneToOneChange.createChange = function(childRecord, parentRecord, store, options) { - var key; - - // If the name of the belongsTo side of the relationship is specified, - // use that - // If the type of the parent is specified, look it up on the child's type - // definition. - if (options.parentType) { - key = options.parentType.inverseFor(options.key).name; - } else if (options.key) { - key = options.key; - } else { - Ember.assert('You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent', false); - } - - var change = RelationshipChange._createChange({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - secondRecord: parentRecord, - firstRecordKind: 'belongsTo', - secondRecordKind: 'belongsTo', - store: store, - changeType: options.changeType, - firstRecordName: key - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, null, change); - - return change; - }; - - OneToOneChange.maintainInvariant = function(options, store, childRecord, key){ - if (options.changeType === 'add' && store.recordIsMaterialized(childRecord)) { - var oldParent = get(childRecord, key); - if (oldParent) { - var correspondingChange = OneToOneChange.createChange(childRecord, oldParent, store, { - parentType: options.parentType, - hasManyName: options.hasManyName, - changeType: 'remove', - key: options.key - }); - store.addRelationshipChangeFor(childRecord, key, options.parentRecord , null, correspondingChange); - correspondingChange.sync(); - } - } - }; - - OneToManyChange.createChange = function(childRecord, parentRecord, store, options) { - var key; - - // If the name of the belongsTo side of the relationship is specified, - // use that - // If the type of the parent is specified, look it up on the child's type - // definition. - if (options.parentType) { - key = options.parentType.inverseFor(options.key).name; - OneToManyChange.maintainInvariant( options, store, childRecord, key ); - } else if (options.key) { - key = options.key; - } else { - Ember.assert('You must pass either a parentType or belongsToName option to OneToManyChange.forChildAndParent', false); - } - - var change = RelationshipChange._createChange({ - parentRecord: parentRecord, - childRecord: childRecord, - firstRecord: childRecord, - secondRecord: parentRecord, - firstRecordKind: 'belongsTo', - secondRecordKind: 'hasMany', - store: store, - changeType: options.changeType, - firstRecordName: key - }); - - store.addRelationshipChangeFor(childRecord, key, parentRecord, change.getSecondRecordName(), change); - - return change; - }; - - OneToManyChange.maintainInvariant = function(options, store, childRecord, key){ - if (options.changeType === 'add' && childRecord) { - var oldParent = get(childRecord, key); - if (oldParent) { - var correspondingChange = OneToManyChange.createChange(childRecord, oldParent, store, { - parentType: options.parentType, - hasManyName: options.hasManyName, - changeType: 'remove', - key: options.key - }); - store.addRelationshipChangeFor(childRecord, key, options.parentRecord, correspondingChange.getSecondRecordName(), correspondingChange); - correspondingChange.sync(); - } - } - }; - - /** - @class RelationshipChange - @namespace DS - */ - RelationshipChange.prototype = { - getSecondRecordName: function() { - var name = this.secondRecordName, parent; - - if (!name) { - parent = this.secondRecord; - if (!parent) { return; } - - var childType = this.firstRecord.constructor; - var inverse = childType.inverseFor(this.firstRecordName); - this.secondRecordName = inverse.name; - } - - return this.secondRecordName; - }, - - /** - Get the name of the relationship on the belongsTo side. - - @method getFirstRecordName - @return {String} - */ - getFirstRecordName: function() { - return this.firstRecordName; - }, - - /** - @method destroy - @private - */ - destroy: function() { - var childRecord = this.childRecord; - var belongsToName = this.getFirstRecordName(); - var hasManyName = this.getSecondRecordName(); - var store = this.store; - - store.removeRelationshipChangeFor(childRecord, belongsToName, this.parentRecord, hasManyName, this.changeType); - }, - - getSecondRecord: function(){ - return this.secondRecord; - }, - - /** - @method getFirstRecord - @private - */ - getFirstRecord: function() { - return this.firstRecord; - }, - - coalesce: function(){ - var relationshipPairs = this.store.relationshipChangePairsFor(this.firstRecord); - forEach(relationshipPairs, function(pair) { - var addedChange = pair['add']; - var removedChange = pair['remove']; - if (addedChange && removedChange) { - addedChange.destroy(); - removedChange.destroy(); - } - }); - } - }; - - RelationshipChangeAdd.prototype = Ember.create(RelationshipChange.create({})); - RelationshipChangeRemove.prototype = Ember.create(RelationshipChange.create({})); - - RelationshipChangeAdd.prototype.changeType = 'add'; - RelationshipChangeAdd.prototype.sync = function() { - var secondRecordName = this.getSecondRecordName(); - var firstRecordName = this.getFirstRecordName(); - var firstRecord = this.getFirstRecord(); - var secondRecord = this.getSecondRecord(); - - //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName); - //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName); - - if (secondRecord instanceof Model && firstRecord instanceof Model) { - if (this.secondRecordKind === 'belongsTo') { - secondRecord.suspendRelationshipObservers(function() { - set(secondRecord, secondRecordName, firstRecord); - }); - } else if (this.secondRecordKind === 'hasMany' && isSyncRelationship(secondRecord, secondRecordName)) { - secondRecord.suspendRelationshipObservers(function() { - var relationship = get(secondRecord, secondRecordName); - relationship.addObject(firstRecord); - }); - } - } - - if (firstRecord instanceof Model && secondRecord instanceof Model && get(firstRecord, firstRecordName) !== secondRecord) { - if (this.firstRecordKind === 'belongsTo') { - firstRecord.suspendRelationshipObservers(function() { - set(firstRecord, firstRecordName, secondRecord); - }); - } else if (this.firstRecordKind === 'hasMany' && isSyncRelationship(secondRecord, secondRecordName)) { - firstRecord.suspendRelationshipObservers(function() { - var relationship = get(firstRecord, firstRecordName); - relationship.addObject(secondRecord); - }); - } - } - this.coalesce(); - }; - - RelationshipChangeRemove.prototype.changeType = 'remove'; - RelationshipChangeRemove.prototype.sync = function() { - var secondRecordName = this.getSecondRecordName(); - var firstRecordName = this.getFirstRecordName(); - var firstRecord = this.getFirstRecord(); - var secondRecord = this.getSecondRecord(); - - //Ember.assert("You specified a hasMany (" + hasManyName + ") on " + (!belongsToName && (newParent || oldParent || this.lastParent).constructor) + " but did not specify an inverse belongsTo on " + child.constructor, belongsToName); - //Ember.assert("You specified a belongsTo (" + belongsToName + ") on " + child.constructor + " but did not specify an inverse hasMany on " + (!hasManyName && (newParent || oldParent || this.lastParentRecord).constructor), hasManyName); - - if (secondRecord instanceof Model && firstRecord instanceof Model) { - if (this.secondRecordKind === 'belongsTo') { - secondRecord.suspendRelationshipObservers(function() { - set(secondRecord, secondRecordName, null); - }); - } else if (this.secondRecordKind === 'hasMany' && isSyncRelationship(secondRecord, secondRecordName)) { - secondRecord.suspendRelationshipObservers(function() { - var relationship = get(secondRecord, secondRecordName); - relationship.removeObject(firstRecord); - }); - } - } - - if (firstRecord instanceof Model && get(firstRecord, firstRecordName)) { - if (this.firstRecordKind === 'belongsTo') { - firstRecord.suspendRelationshipObservers(function() { - set(firstRecord, firstRecordName, null); - }); - } else if (this.firstRecordKind === 'hasMany' && isSyncRelationship(firstRecord, firstRecordName)) { - firstRecord.suspendRelationshipObservers(function() { - var relationship = get(firstRecord, firstRecordName); - relationship.removeObject(secondRecord); - }); - } - } - - this.coalesce(); - }; - - __exports__.RelationshipChange = RelationshipChange; - __exports__.RelationshipChangeAdd = RelationshipChangeAdd; - __exports__.RelationshipChangeRemove = RelationshipChangeRemove; - __exports__.OneToManyChange = OneToManyChange; - __exports__.ManyToNoneChange = ManyToNoneChange; - __exports__.OneToOneChange = OneToOneChange; - __exports__.ManyToManyChange = ManyToManyChange; - }); define("ember-data/system/container_proxy", ["exports"], function(__exports__) { @@ -5328,7 +5023,7 @@ define("ember-data/system/container_proxy", }; ContainerProxy.prototype.registerDeprecations = function(proxyPairs) { - var i, proxyPair, deprecated, valid, proxy; + var i, proxyPair, deprecated, valid; for (i = proxyPairs.length; i > 0; i--) { proxyPair = proxyPairs[i - 1]; @@ -5393,7 +5088,7 @@ define("ember-data/system/debug/debug_adapter", }]; var count = 0; var self = this; - get(type, 'attributes').forEach(function(name, meta) { + get(type, 'attributes').forEach(function(meta, name) { if (count++ > self.attributeLimit) { return false; } var desc = capitalize(underscore(name).replace('_', ' ')); columns.push({ name: name, desc: desc }); @@ -5550,6 +5245,101 @@ define("ember-data/system/debug/debug_info", __exports__["default"] = Model; }); +define("ember-data/system/map", + ["exports"], + function(__exports__) { + "use strict"; + /** + * Polyfill Ember.Map behavior for Ember <= 1.7 + * This can probably be removed before 1.0 final + */ + var mapForEach, deleteFn; + + function OrderedSet(){ + Ember.OrderedSet.apply(this, arguments); + } + + function Map() { + Ember.Map.apply(this, arguments); + } + + function MapWithDefault(){ + Ember.MapWithDefault.apply(this, arguments); + } + + var testMap = Ember.Map.create(); + testMap.set('key', 'value'); + + var usesOldBehavior = false; + + testMap.forEach(function(value, key){ + usesOldBehavior = value === 'key' && key === 'value'; + }); + + Map.prototype = Object.create(Ember.Map.prototype); + MapWithDefault.prototype = Object.create(Ember.MapWithDefault.prototype); + OrderedSet.prototype = Object.create(Ember.OrderedSet.prototype); + + OrderedSet.create = function(){ + return new OrderedSet(); + }; + + /** + * returns a function that calls the original + * callback function in the correct order. + * if we are in pre-Ember.1.8 land, Map/MapWithDefault + * forEach calls with key, value, in that order. + * >= 1.8 forEach is called with the order value, key as per + * the ES6 spec. + */ + function translate(valueKeyOrderedCallback){ + return function(key, value){ + valueKeyOrderedCallback.call(this, value, key); + }; + } + + // old, non ES6 compliant behavior + if (usesOldBehavior){ + mapForEach = function(callback, thisArg){ + this.__super$forEach(translate(callback), thisArg); + }; + + /* alias to remove */ + deleteFn = function(thing){ + this.remove(thing); + }; + + Map.prototype.__super$forEach = Ember.Map.prototype.forEach; + Map.prototype.forEach = mapForEach; + Map.prototype["delete"] = deleteFn; + + MapWithDefault.prototype.forEach = mapForEach; + MapWithDefault.prototype.__super$forEach = Ember.MapWithDefault.prototype.forEach; + MapWithDefault.prototype["delete"] = deleteFn; + + OrderedSet.prototype["delete"] = deleteFn; + } + + MapWithDefault.constructor = MapWithDefault; + Map.constructor = Map; + + MapWithDefault.create = function(options){ + if (options) { + return new MapWithDefault(options); + } else { + return new Map(); + } + }; + + Map.create = function(){ + return new this.constructor(); + }; + + __exports__["default"] = Map; + __exports__.Map = Map; + __exports__.MapWithDefault = MapWithDefault; + __exports__.OrderedSet = OrderedSet; + }); define("ember-data/system/model", ["ember-data/system/model/model","ember-data/system/model/attributes","ember-data/system/model/states","ember-data/system/model/errors","exports"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { @@ -5569,10 +5359,11 @@ define("ember-data/system/model", __exports__.Errors = Errors; }); define("ember-data/system/model/attributes", - ["ember-data/system/model/model","exports"], - function(__dependency1__, __exports__) { + ["ember-data/system/model/model","ember-data/system/map","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; var Model = __dependency1__["default"]; + var Map = __dependency2__.Map; /** @module ember-data @@ -5618,7 +5409,7 @@ define("ember-data/system/model/attributes", @readOnly */ attributes: Ember.computed(function() { - var map = Ember.Map.create(); + var map = Map.create(); this.eachComputedProperty(function(name, meta) { if (meta.isAttribute) { @@ -5664,7 +5455,7 @@ define("ember-data/system/model/attributes", @readOnly */ transformedAttributes: Ember.computed(function() { - var map = Ember.Map.create(); + var map = Map.create(); this.eachAttribute(function(key, meta) { if (meta.type) { @@ -5717,7 +5508,7 @@ define("ember-data/system/model/attributes", @static */ eachAttribute: function(callback, binding) { - get(this, 'attributes').forEach(function(name, meta) { + get(this, 'attributes').forEach(function(meta, name) { callback.call(binding, name, meta); }, binding); }, @@ -5765,7 +5556,7 @@ define("ember-data/system/model/attributes", @static */ eachTransformedAttribute: function(callback, binding) { - get(this, 'transformedAttributes').forEach(function(name, type) { + get(this, 'transformedAttributes').forEach(function(type, name) { callback.call(binding, name, type); }); } @@ -5879,13 +5670,15 @@ define("ember-data/system/model/attributes", }; }); define("ember-data/system/model/errors", - ["exports"], - function(__exports__) { + ["ember-data/system/map","exports"], + function(__dependency1__, __exports__) { "use strict"; var get = Ember.get; var isEmpty = Ember.isEmpty; var map = Ember.EnumerableUtils.map; + var MapWithDefault = __dependency1__.MapWithDefault; + /** @module ember-data */ @@ -5982,7 +5775,7 @@ define("ember-data/system/model/errors", */ errorsByAttributeName: Ember.reduceComputed("content", { initialValue: function() { - return Ember.MapWithDefault.create({ + return MapWithDefault.create({ defaultValue: function() { return Ember.A(); } @@ -6224,12 +6017,14 @@ define("ember-data/system/model/errors", }); }); define("ember-data/system/model/model", - ["ember-data/system/model/states","ember-data/system/model/errors","ember-data/system/store","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __exports__) { + ["ember-data/system/model/states","ember-data/system/model/errors","ember-data/system/promise_proxies","ember-data/system/relationships/relationship","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { "use strict"; var RootState = __dependency1__["default"]; var Errors = __dependency2__["default"]; var PromiseObject = __dependency3__.PromiseObject; + var createRelationshipFor = __dependency4__.createRelationshipFor; + /** @module ember-data */ @@ -6239,6 +6034,7 @@ define("ember-data/system/model/model", var merge = Ember.merge; var Promise = Ember.RSVP.Promise; var forEach = Ember.ArrayPolyfills.forEach; + var map = Ember.ArrayPolyfills.map; var JSONSerializer; var retrieveFromCurrentState = Ember.computed('currentState', function(key, value) { @@ -6671,6 +6467,33 @@ define("ember-data/system/model/model", this._attributes = {}; this._inFlightAttributes = {}; this._relationships = {}; + /* + implicit relationships are relationship which have not been declared but the inverse side exists on + another record somewhere + For example if there was + ``` + App.Comment = DS.Model.extend({ + name: DS.attr() + }) + ``` + but there is also + ``` + App.Post = DS.Model.extend({ + name: DS.attr(), + comments: DS.hasMany('comment') + }) + ``` + + would have a implicit post relationship in order to be do things like remove ourselves from the post + when we are deleted + */ + this._implicitRelationships = Object.create(null); + var model = this; + //TODO Move into a getter for better perf + this.constructor.eachRelationship(function(key, descriptor) { + model._relationships[key] = createRelationshipFor(model, descriptor, model.store); + }); + }, /** @@ -6853,17 +6676,36 @@ define("ember-data/system/model/model", */ clearRelationships: function() { this.eachRelationship(function(name, relationship) { - if (relationship.kind === 'belongsTo') { - set(this, name, null); - } else if (relationship.kind === 'hasMany') { - var hasMany = this._relationships[name]; - if (hasMany) { // relationships are created lazily - hasMany.destroy(); - } + var rel = this._relationships[name]; + if (rel){ + //TODO(Igor) figure out whether we want to clear or disconnect + rel.clear(); + rel.destroy(); } }, this); }, + disconnectRelationships: function() { + this.eachRelationship(function(name, relationship) { + this._relationships[name].disconnect(); + }, this); + var model = this; + forEach.call(Ember.keys(this._implicitRelationships), function(key) { + model._implicitRelationships[key].disconnect(); + }); + }, + + reconnectRelationships: function() { + this.eachRelationship(function(name, relationship) { + this._relationships[name].reconnect(); + }, this); + var model = this; + forEach.call(Ember.keys(this._implicitRelationships), function(key) { + model._implicitRelationships[key].reconnect(); + }); + }, + + /** @method updateRecordArrays @private @@ -6874,9 +6716,9 @@ define("ember-data/system/model/model", }, /** - When a find request is triggered on the store, the user can optionally passed in + When a find request is triggered on the store, the user can optionally pass in attributes and relationships to be preloaded. These are meant to behave as if they - came back from the server, expect the user obtained them out of band and is informing + came back from the server, except the user obtained them out of band and is informing the store of their existence. The most common use case is for supporting client side nested URLs, such as `/posts/1/comments/2` so the user can do `store.find('comment', 2, {post:1})` without having to fetch the post. @@ -6916,15 +6758,20 @@ define("ember-data/system/model/model", Ember.assert("You need to pass in an array to set a hasMany property on a record", Ember.isArray(preloadValue)); var record = this; - forEach.call(preloadValue, function(recordToPush) { - recordToPush = record._convertStringOrNumberIntoRecord(recordToPush, type); - get(record, key).pushObject(recordToPush); + var recordsToSet = map.call(preloadValue, function(recordToPush) { + return record._convertStringOrNumberIntoRecord(recordToPush, type); }); + //We use the pathway of setting the hasMany as if it came from the adapter + //because the user told us that they know this relationships exists already + this._relationships[key].updateRecordsFromAdapter(recordsToSet); }, _preloadBelongsTo: function(key, preloadValue, type){ - var recordToPush = this._convertStringOrNumberIntoRecord(preloadValue, type); - set(this, key, recordToPush); + var recordToSet = this._convertStringOrNumberIntoRecord(preloadValue, type); + + //We use the pathway of setting the hasMany as if it came from the adapter + //because the user told us that they know this relationships exists already + this._relationships[key].setRecord(recordToSet); }, _convertStringOrNumberIntoRecord: function(value, type) { @@ -6999,9 +6846,7 @@ define("ember-data/system/model/model", if (!data) { return; } - this.suspendRelationshipObservers(function() { - this.notifyPropertyChange('data'); - }); + this.notifyPropertyChange('data'); }, /** @@ -7013,32 +6858,6 @@ define("ember-data/system/model/model", this.updateRecordArraysLater(); }, - dataDidChange: Ember.observer(function() { - this.reloadHasManys(); - }, 'data'), - - reloadHasManys: function() { - var relationships = get(this.constructor, 'relationshipsByName'); - this.updateRecordArraysLater(); - relationships.forEach(function(name, relationship) { - if (this._data.links && this._data.links[name]) { return; } - if (relationship.kind === 'hasMany') { - this.hasManyDidChange(relationship.key); - } - }, this); - }, - - hasManyDidChange: function(key) { - var hasMany = this._relationships[key]; - - if (hasMany) { - var records = this._data[key] || []; - - set(hasMany, 'content', Ember.A(records)); - set(hasMany, 'isLoaded', true); - hasMany.trigger('didLoad'); - } - }, /** @method updateRecordArraysLater @@ -7066,18 +6885,9 @@ define("ember-data/system/model/model", this._data = data; } - var relationships = this._relationships; - - this.eachRelationship(function(name, rel) { - if (data.links && data.links[name]) { return; } - if (rel.options.async) { relationships[name] = null; } - }); - if (data) { this.pushedData(); } - this.suspendRelationshipObservers(function() { - this.notifyPropertyChange('data'); - }); + this.notifyPropertyChange('data'); }, materializeId: function(id) { @@ -7093,27 +6903,6 @@ define("ember-data/system/model/model", this._data[name] = value; }, - /** - @method updateHasMany - @private - @param {String} name - @param {Array} records - */ - updateHasMany: function(name, records) { - this._data[name] = records; - this.hasManyDidChange(name); - }, - - /** - @method updateBelongsTo - @private - @param {String} name - @param {DS.Model} record - */ - updateBelongsTo: function(name, record) { - this._data[name] = record; - }, - /** If the model `isDirty` this function will discard any unsaved changes @@ -7138,57 +6927,29 @@ define("ember-data/system/model/model", set(this, 'isError', false); } + //Eventually rollback will always work for relationships + //For now we support it only out of deleted state, because we + //have an explicit way of knowing when the server acked the relationship change + if (get(this, 'isDeleted')) { + this.reconnectRelationships(); + } + if (!get(this, 'isValid')) { this._inFlightAttributes = {}; } this.send('rolledBack'); - this.suspendRelationshipObservers(function() { - this.notifyPropertyChange('data'); - }); + this.notifyPropertyChange('data'); }, toStringExtension: function() { return get(this, 'id'); }, - /** - The goal of this method is to temporarily disable specific observers - that take action in response to application changes. - - This allows the system to make changes (such as materialization and - rollback) that should not trigger secondary behavior (such as setting an - inverse relationship or marking records as dirty). - - The specific implementation will likely change as Ember proper provides - better infrastructure for suspending groups of observers, and if Array - observation becomes more unified with regular observers. - - @method suspendRelationshipObservers - @private - @param callback - @param binding - */ - suspendRelationshipObservers: function(callback, binding) { - var observers = get(this.constructor, 'relationshipNames').belongsTo; - var self = this; - - try { - this._suspendedRelationships = true; - Ember._suspendObservers(self, observers, null, 'belongsToDidChange', function() { - Ember._suspendBeforeObservers(self, observers, null, 'belongsToWillChange', function() { - callback.call(binding || self); - }); - }); - } finally { - this._suspendedRelationships = false; - } - }, - /** Save the record and persist any changes to the record to an - extenal source via the adapter. + external source via the adapter. Example @@ -7255,7 +7016,9 @@ define("ember-data/system/model/model", }, function(reason) { record.set('isError', true); throw reason; - }, "DS: Model#reload complete, update flags"); + }, "DS: Model#reload complete, update flags")['finally'](function () { + record.updateRecordArrays(); + }); return PromiseObject.create({ promise: promise @@ -7311,15 +7074,32 @@ define("ember-data/system/model/model", @method trigger @private - @param name + @param {String} name */ - trigger: function(name) { - Ember.tryInvoke(this, name, [].slice.call(arguments, 1)); + trigger: function() { + var length = arguments.length; + var args = new Array(length - 1); + var name = arguments[0]; + + for (var i = 1; i < length; i++ ){ + args[i - 1] = arguments[i]; + } + + Ember.tryInvoke(this, name, args); this._super.apply(this, arguments); }, triggerLater: function() { - if (this._deferredTriggers.push(arguments) !== 1) { return; } + var length = arguments.length; + var args = new Array(length); + + for (var i = 0; i < length; i++ ){ + args[i] = arguments[i]; + } + + if (this._deferredTriggers.push(args) !== 1) { + return; + } Ember.run.schedule('actions', this, '_triggerDeferredTriggers'); }, @@ -7552,18 +7332,6 @@ define("ember-data/system/model/states", @class RootState */ - function hasDefinedProperties(object) { - // Ignore internal property defined by simulated `Ember.create`. - var names = Ember.keys(object); - var i, l, name; - for (i = 0, l = names.length; i < l; i++ ) { - name = names[i]; - if (object.hasOwnProperty(name) && object[name]) { return true; } - } - - return false; - } - function didSetProperty(record, context) { if (context.value === context.originalValue) { delete record._attributes[context.name]; @@ -7638,12 +7406,8 @@ define("ember-data/system/model/states", loadingData: Ember.K, propertyWasReset: function(record, name) { - var stillDirty = false; - - for (var prop in record._attributes) { - stillDirty = true; - break; - } + var length = Ember.keys(record._attributes); + var stillDirty = length > 0; if (!stillDirty) { record.send('rolledBack'); } }, @@ -7719,7 +7483,7 @@ define("ember-data/system/model/states", // EVENTS deleteRecord: function(record) { record.transitionTo('deleted.uncommitted'); - record.clearRelationships(); + record.disconnectRelationships(); }, didSetProperty: function(record, context) { @@ -7800,7 +7564,7 @@ define("ember-data/system/model/states", }); createdState.uncommitted.deleteRecord = function(record) { - record.clearRelationships(); + record.disconnectRelationships(); record.transitionTo('deleted.saved'); }; @@ -7819,7 +7583,7 @@ define("ember-data/system/model/states", updatedState.uncommitted.deleteRecord = function(record) { record.transitionTo('deleted.uncommitted'); - record.clearRelationships(); + record.disconnectRelationships(); }; var RootState = { @@ -7868,10 +7632,7 @@ define("ember-data/system/model/states", loadedData: function(record) { record.transitionTo('loaded.created.uncommitted'); - - record.suspendRelationshipObservers(function() { - record.notifyPropertyChange('data'); - }); + record.notifyPropertyChange('data'); }, pushedData: function(record) { @@ -7963,7 +7724,7 @@ define("ember-data/system/model/states", deleteRecord: function(record) { record.transitionTo('deleted.uncommitted'); - record.clearRelationships(); + record.disconnectRelationships(); }, unloadRecord: function(record) { @@ -8115,9 +7876,119 @@ define("ember-data/system/model/states", __exports__["default"] = RootState; }); +define("ember-data/system/promise_proxies", + ["exports"], + function(__exports__) { + "use strict"; + var Promise = Ember.RSVP.Promise; + var get = Ember.get; + + /** + A `PromiseArray` is an object that acts like both an `Ember.Array` + and a promise. When the promise is resolved the resulting value + will be set to the `PromiseArray`'s `content` property. This makes + it easy to create data bindings with the `PromiseArray` that will be + updated when the promise resolves. + + For more information see the [Ember.PromiseProxyMixin + documentation](/api/classes/Ember.PromiseProxyMixin.html). + + Example + + ```javascript + var promiseArray = DS.PromiseArray.create({ + promise: $.getJSON('/some/remote/data.json') + }); + + promiseArray.get('length'); // 0 + + promiseArray.then(function() { + promiseArray.get('length'); // 100 + }); + ``` + + @class PromiseArray + @namespace DS + @extends Ember.ArrayProxy + @uses Ember.PromiseProxyMixin + */ + var PromiseArray = Ember.ArrayProxy.extend(Ember.PromiseProxyMixin); + + /** + A `PromiseObject` is an object that acts like both an `Ember.Object` + and a promise. When the promise is resolved, then the resulting value + will be set to the `PromiseObject`'s `content` property. This makes + it easy to create data bindings with the `PromiseObject` that will + be updated when the promise resolves. + + For more information see the [Ember.PromiseProxyMixin + documentation](/api/classes/Ember.PromiseProxyMixin.html). + + Example + + ```javascript + var promiseObject = DS.PromiseObject.create({ + promise: $.getJSON('/some/remote/data.json') + }); + + promiseObject.get('name'); // null + + promiseObject.then(function() { + promiseObject.get('name'); // 'Tomster' + }); + ``` + + @class PromiseObject + @namespace DS + @extends Ember.ObjectProxy + @uses Ember.PromiseProxyMixin + */ + var PromiseObject = Ember.ObjectProxy.extend(Ember.PromiseProxyMixin); + + var promiseObject = function(promise, label) { + return PromiseObject.create({ + promise: Promise.resolve(promise, label) + }); + }; + + var promiseArray = function(promise, label) { + return PromiseArray.create({ + promise: Promise.resolve(promise, label) + }); + }; + + /** + A PromiseManyArray is a PromiseArray that also proxies certain method calls + to the underlying manyArray. + Right now we proxy: + `reload()` + */ + + var PromiseManyArray = PromiseArray.extend({ + reload: function() { + //I don't think this should ever happen right now, but worth guarding if we refactor the async relationships + Ember.assert('You are trying to reload an async manyArray before it has been created', get(this, 'content')); + return get(this, 'content').reload(); + } + }); + + var promiseManyArray = function(promise, label) { + return PromiseManyArray.create({ + promise: Promise.resolve(promise, label) + }); + }; + + + __exports__.PromiseArray = PromiseArray; + __exports__.PromiseObject = PromiseObject; + __exports__.PromiseManyArray = PromiseManyArray; + __exports__.promiseArray = promiseArray; + __exports__.promiseObject = promiseObject; + __exports__.promiseManyArray = promiseManyArray; + }); define("ember-data/system/record_array_manager", - ["ember-data/system/record_arrays","exports"], - function(__dependency1__, __exports__) { + ["ember-data/system/record_arrays","ember-data/system/map","exports"], + function(__dependency1__, __dependency2__, __exports__) { "use strict"; /** @module ember-data @@ -8127,8 +7998,9 @@ define("ember-data/system/record_array_manager", var FilteredRecordArray = __dependency1__.FilteredRecordArray; var AdapterPopulatedRecordArray = __dependency1__.AdapterPopulatedRecordArray; var ManyArray = __dependency1__.ManyArray; + var MapWithDefault = __dependency2__.MapWithDefault; + var OrderedSet = __dependency2__.OrderedSet; var get = Ember.get; - var set = Ember.set; var forEach = Ember.EnumerableUtils.forEach; /** @@ -8139,7 +8011,7 @@ define("ember-data/system/record_array_manager", */ __exports__["default"] = Ember.Object.extend({ init: function() { - this.filteredRecordArrays = Ember.MapWithDefault.create({ + this.filteredRecordArrays = MapWithDefault.create({ defaultValue: function() { return []; } }); @@ -8154,7 +8026,7 @@ define("ember-data/system/record_array_manager", }, recordArraysForRecord: function(record) { - record._recordArrays = record._recordArrays || Ember.OrderedSet.create(); + record._recordArrays = record._recordArrays || OrderedSet.create(); return record._recordArrays; }, @@ -8187,9 +8059,11 @@ define("ember-data/system/record_array_manager", if (!recordArrays) { return; } - forEach(recordArrays, function(array) { + recordArrays.forEach(function(array){ array.removeRecord(record); }); + + record._recordArrays = null; }, _recordWasChanged: function (record) { @@ -8241,7 +8115,7 @@ define("ember-data/system/record_array_manager", recordArrays.add(array); } } else if (!shouldBeInArray) { - recordArrays.remove(array); + recordArrays["delete"](array); array.removeRecord(record); } }, @@ -8254,9 +8128,9 @@ define("ember-data/system/record_array_manager", method is invoked when the filter is created in th first place. @method updateFilter - @param array - @param type - @param filter + @param {Array} array + @param {String} type + @param {Function} filter */ updateFilter: function(array, type, filter) { var typeMap = this.store.typeMapFor(type); @@ -8453,7 +8327,6 @@ define("ember-data/system/record_arrays/adapter_populated_record_array", */ var get = Ember.get; - var set = Ember.set; function cloneNull(source) { var clone = Object.create(null); @@ -8579,22 +8452,16 @@ define("ember-data/system/record_arrays/filtered_record_array", }); }); define("ember-data/system/record_arrays/many_array", - ["ember-data/system/record_arrays/record_array","ember-data/system/changes","exports"], - function(__dependency1__, __dependency2__, __exports__) { + ["ember-data/system/record_arrays/record_array","exports"], + function(__dependency1__, __exports__) { "use strict"; var RecordArray = __dependency1__["default"]; - var RelationshipChange = __dependency2__.RelationshipChange; /** @module ember-data */ var get = Ember.get, set = Ember.set; - var map = Ember.EnumerableUtils.map; - - function sync(change) { - change.sync(); - } /** A `ManyArray` is a `RecordArray` that represents the contents of a has-many @@ -8635,25 +8502,8 @@ define("ember-data/system/record_arrays/many_array", __exports__["default"] = RecordArray.extend({ init: function() { this._super.apply(this, arguments); - this._changesToSync = Ember.OrderedSet.create(); }, - /** - The property name of the relationship - - @property {String} name - @private - */ - name: null, - - /** - The record to which this relationship belongs. - - @property {DS.Model} owner - @private - */ - owner: null, - /** `true` if the relationship is polymorphic, `false` otherwise. @@ -8666,6 +8516,16 @@ define("ember-data/system/record_arrays/many_array", isLoaded: false, + /** + The relationship which manages this array. + + @property {DS.Model} owner + @private + */ + + relationship: null, + + /** Used for async `hasMany` arrays to keep track of when they will resolve. @@ -8696,100 +8556,22 @@ define("ember-data/system/record_arrays/many_array", } }, + replaceContent: function(idx, amt, objects){ + var records; + if (amt > 0){ + records = get(this, 'content').slice(idx, idx+amt); + this.get('relationship').removeRecords(records); + } + if (objects){ + this.get('relationship').addRecords(objects, idx); + } + }, /** - @method fetch - @private + @method reload + @public */ - fetch: function() { - var records = get(this, 'content'); - var store = get(this, 'store'); - var owner = get(this, 'owner'); - - var unloadedRecords = records.filterBy('isEmpty', true); - store.scheduleFetchMany(unloadedRecords, owner); - }, - - // Overrides Ember.Array's replace method to implement - replaceContent: function(index, removed, added) { - // Map the array of record objects into an array of client ids. - added = map(added, function(record) { - Ember.assert("You cannot add '" + record.constructor.typeKey + "' records to this relationship (only '" + this.type.typeKey + "' allowed)", !this.type || record instanceof this.type); - return record; - }, this); - - this._super(index, removed, added); - }, - - arrangedContentDidChange: function() { - Ember.run.once(this, 'fetch'); - }, - - arrayContentWillChange: function(index, removed, added) { - var owner = get(this, 'owner'); - var name = get(this, 'name'); - - if (!owner._suspendedRelationships) { - // This code is the first half of code that continues inside - // of arrayContentDidChange. It gets or creates a change from - // the child object, adds the current owner as the old - // parent if this is the first time the object was removed - // from a ManyArray, and sets `newParent` to null. - // - // Later, if the object is added to another ManyArray, - // the `arrayContentDidChange` will set `newParent` on - // the change. - for (var i=index; i1) { + if ( value === undefined ) { + value = null; + } + if (value && value.then) { + this._relationships[key].setRecordPromise(value); + } else { + this._relationships[key].setRecord(value); + } } - if (arguments.length === 2) { - Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass); - return value === undefined ? null : value; - } - - belongsTo = data[key]; - - if (isNone(belongsTo)) { return null; } - - store.findById(belongsTo.constructor, belongsTo.get('id')); - - return belongsTo; + return this._relationships[key].getRecord(); }).meta(meta); } @@ -9237,72 +8950,29 @@ define("ember-data/system/relationships/belongs_to", @namespace DS */ Model.reopen({ + notifyBelongsToAdded: function(key, relationship) { + this.notifyPropertyChange(key); + }, - /** - @method belongsToWillChange - @private - @static - @param record - @param key - */ - belongsToWillChange: Ember.beforeObserver(function(record, key) { - if (get(record, 'isLoaded') && isSyncRelationship(record, key)) { - var oldParent = get(record, key); - - if (oldParent) { - var store = get(record, 'store'); - var change = RelationshipChange.createChange(record, oldParent, store, { - key: key, - kind: 'belongsTo', - changeType: 'remove' - }); - - change.sync(); - this._changesToSync[key] = change; - } - } - }), - - /** - @method belongsToDidChange - @private - @static - @param record - @param key - */ - belongsToDidChange: Ember.immediateObserver(function(record, key) { - if (get(record, 'isLoaded')) { - var newParent = get(record, key); - - if (newParent) { - var store = get(record, 'store'); - var change = RelationshipChange.createChange(record, newParent, store, { - key: key, - kind: 'belongsTo', - changeType: 'add' - }); - - change.sync(); - } - } - - delete this._changesToSync[key]; - }) + notifyBelongsToRemoved: function(key) { + this.notifyPropertyChange(key); + } }); __exports__["default"] = belongsTo; }); define("ember-data/system/relationships/ext", - ["ember-inflector/system","ember-data/system/relationship-meta","ember-data/system/model"], + ["ember-data/system/relationship-meta","ember-data/system/model","ember-data/system/map"], function(__dependency1__, __dependency2__, __dependency3__) { "use strict"; - var singularize = __dependency1__.singularize; - var typeForRelationshipMeta = __dependency2__.typeForRelationshipMeta; - var relationshipFromMeta = __dependency2__.relationshipFromMeta; - var Model = __dependency3__.Model; + var typeForRelationshipMeta = __dependency1__.typeForRelationshipMeta; + var relationshipFromMeta = __dependency1__.relationshipFromMeta; + var Model = __dependency2__.Model; + var Map = __dependency3__.Map; + var MapWithDefault = __dependency3__.MapWithDefault; var get = Ember.get; - var set = Ember.set; + var filter = Ember.ArrayPolyfills.filter; /** @module ember-data @@ -9341,9 +9011,9 @@ define("ember-data/system/relationships/ext", property returned by `DS.belongsTo` as the value. @method didDefineProperty - @param proto - @param key - @param value + @param {Object} proto + @param {String} key + @param {Ember.ComputedProperty} value */ didDefineProperty: function(proto, key, value) { // Check if the value being set is a computed property. @@ -9354,11 +9024,6 @@ define("ember-data/system/relationships/ext", // the computed property. var meta = value.meta(); - if (meta.isRelationship && meta.kind === 'belongsTo') { - Ember.addObserver(proto, key, null, 'belongsToDidChange'); - Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange'); - } - meta.parentType = proto.constructor; } } @@ -9382,6 +9047,7 @@ define("ember-data/system/relationships/ext", */ Model.reopenClass({ + /** For a given relationship name, returns the model type of the relationship. @@ -9405,17 +9071,59 @@ define("ember-data/system/relationships/ext", return relationship && relationship.type; }, + inverseMap: Ember.computed(function() { + return Object.create(null); + }), + + /* + Find the relationship which is the inverse of the one asked for. + + For example, if you define models like this: + + ```javascript + App.Post = DS.Model.extend({ + comments: DS.hasMany('message') + }); + + App.Message = DS.Model.extend({ + owner: DS.belongsTo('post') + }); + ``` + + App.Post.inverseFor('comments') -> {type: App.Message, name:'owner', kind:'belongsTo'} + App.Message.inverseFor('owner') -> {type: App.Post, name:'comments', kind:'hasMany'} + + @method inverseFor + @static + @param {String} name the name of the relationship + @return {Object} the inverse relationship, or null + */ inverseFor: function(name) { + var inverseMap = get(this, 'inverseMap'); + if (inverseMap[name]) { + return inverseMap[name]; + } else { + var inverse = this._findInverseFor(name); + inverseMap[name] = inverse; + return inverse; + } + }, + + //Calculate the inverse, ignoring the cache + _findInverseFor: function(name) { + var inverseType = this.typeForRelationship(name); + if (!inverseType) { + return null; + } - if (!inverseType) { return null; } - + //If inverse is manually specified to be null, like `comments: DS.hasMany('message', {inverse: null})` var options = this.metaForProperty(name).options; - if (options.inverse === null) { return null; } var inverseName, inverseKind, inverse; + //If inverse is specified manually, return the inverse if (options.inverse) { inverseName = options.inverse; inverse = Ember.get(inverseType, 'relationshipsByName').get(inverseName); @@ -9425,10 +9133,24 @@ define("ember-data/system/relationships/ext", inverseKind = inverse.kind; } else { + //No inverse was specified manually, we need to use a heuristic to guess one var possibleRelationships = findPossibleInverses(this, inverseType); if (possibleRelationships.length === 0) { return null; } + var filteredRelationships = filter.call(possibleRelationships, function(possibleRelationship) { + var optionsForRelationship = inverseType.metaForProperty(possibleRelationship.name).options; + return name === optionsForRelationship.inverse; + }); + + Ember.assert("You defined the '" + name + "' relationship on " + this + ", but you defined the inverse relationships of type " + + inverseType.toString() + " multiple times. Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses", + filteredRelationships.length < 2); + + if (filteredRelationships.length === 1 ) { + possibleRelationships = filteredRelationships; + } + Ember.assert("You defined the '" + name + "' relationship on " + this + ", but multiple possible inverse relationships of type " + this + " were found on " + inverseType + ". Look at http://emberjs.com/guides/models/defining-models/#toc_explicit-inverses for how to explicitly specify inverses", possibleRelationships.length === 1); @@ -9437,17 +9159,29 @@ define("ember-data/system/relationships/ext", inverseKind = possibleRelationships[0].kind; } - function findPossibleInverses(type, inverseType, possibleRelationships) { - possibleRelationships = possibleRelationships || []; + function findPossibleInverses(type, inverseType, relationshipsSoFar) { + var possibleRelationships = relationshipsSoFar || []; var relationshipMap = get(inverseType, 'relationships'); if (!relationshipMap) { return; } var relationships = relationshipMap.get(type); + + relationships = filter.call(relationships, function(relationship) { + var optionsForRelationship = inverseType.metaForProperty(relationship.name).options; + + if (!optionsForRelationship.inverse){ + return true; + } + + return name === optionsForRelationship.inverse; + }); + if (relationships) { - possibleRelationships.push.apply(possibleRelationships, relationshipMap.get(type)); + possibleRelationships.push.apply(possibleRelationships, relationships); } + //Recurse to support polymorphism if (type.superclass) { findPossibleInverses(type.superclass, inverseType, possibleRelationships); } @@ -9496,7 +9230,7 @@ define("ember-data/system/relationships/ext", @readOnly */ relationships: Ember.computed(function() { - var map = new Ember.MapWithDefault({ + var map = new MapWithDefault({ defaultValue: function() { return []; } }); @@ -9646,7 +9380,7 @@ define("ember-data/system/relationships/ext", @readOnly */ relationshipsByName: Ember.computed(function() { - var map = Ember.Map.create(); + var map = Map.create(); this.eachComputedProperty(function(name, meta) { if (meta.isRelationship) { @@ -9696,7 +9430,7 @@ define("ember-data/system/relationships/ext", @readOnly */ fields: Ember.computed(function() { - var map = Ember.Map.create(); + var map = Map.create(); this.eachComputedProperty(function(name, meta) { if (meta.isRelationship) { @@ -9720,7 +9454,7 @@ define("ember-data/system/relationships/ext", @param {any} binding the value to which the callback's `this` should be bound */ eachRelationship: function(callback, binding) { - get(this, 'relationshipsByName').forEach(function(name, relationship) { + get(this, 'relationshipsByName').forEach(function(relationship, name) { callback.call(binding, name, relationship); }); }, @@ -9740,7 +9474,28 @@ define("ember-data/system/relationships/ext", get(this, 'relatedTypes').forEach(function(type) { callback.call(binding, type); }); + }, + + determineRelationshipType: function(knownSide) { + var knownKey = knownSide.key; + var knownKind = knownSide.kind; + var inverse = this.inverseFor(knownKey); + var key, otherKind; + + if (!inverse) { + return knownKind === 'belongsTo' ? 'oneToNone' : 'manyToNone'; + } + + key = inverse.name; + otherKind = inverse.kind; + + if (otherKind === 'belongsTo') { + return knownKind === 'belongsTo' ? 'oneToOne' : 'manyToOne'; + } else { + return knownKind === 'belongsTo' ? 'oneToMany' : 'manyToMany'; + } } + }); Model.reopen({ @@ -9755,125 +9510,27 @@ define("ember-data/system/relationships/ext", */ eachRelationship: function(callback, binding) { this.constructor.eachRelationship(callback, binding); + }, + + relationshipFor: function(name) { + return get(this.constructor, 'relationshipsByName').get(name); + }, + + inverseFor: function(key) { + return this.constructor.inverseFor(key); } + }); }); define("ember-data/system/relationships/has_many", - ["ember-data/system/store","ember-data/system/relationship-meta","exports"], - function(__dependency1__, __dependency2__, __exports__) { + ["ember-data/system/model","exports"], + function(__dependency1__, __exports__) { "use strict"; /** @module ember-data */ - var PromiseArray = __dependency1__.PromiseArray; - - var relationshipFromMeta = __dependency2__.relationshipFromMeta; - var typeForRelationshipMeta = __dependency2__.typeForRelationshipMeta; - - var get = Ember.get; - var set = Ember.set; - var setProperties = Ember.setProperties; - var map = Ember.EnumerableUtils.map; - - /** - Returns a computed property that synchronously returns a ManyArray for - this relationship. If not all of the records in this relationship are - loaded, it will raise an exception. - */ - - function syncHasMany(type, options, meta) { - return Ember.computed('data', function(key) { - return buildRelationship(this, key, options, function(store, data) { - // Configure the metadata for the computed property to contain - // the key. - meta.key = key; - - var records = data[key]; - - Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).isEvery('isEmpty', false)); - - return store.findMany(this, data[key], typeForRelationshipMeta(store, meta)); - }); - }).meta(meta).readOnly(); - } - - /** - Returns a computed property that itself returns a promise that resolves to a - ManyArray. - */ - - function asyncHasMany(type, options, meta) { - return Ember.computed('data', function(key) { - // Configure the metadata for the computed property to contain - // the key. - meta.key = key; - - var relationship = buildRelationship(this, key, options, function(store, data) { - var link = data.links && data.links[key]; - var rel; - var promiseLabel = "DS: Async hasMany " + this + " : " + key; - var resolver = Ember.RSVP.defer(promiseLabel); - - if (link) { - rel = store.findHasMany(this, link, relationshipFromMeta(store, meta), resolver); - } else { - - //This is a temporary workaround for setting owner on the relationship - //until single source of truth lands. It only works for OneToMany atm - var records = data[key]; - var inverse = this.constructor.inverseFor(key); - var owner = this; - if (inverse && records) { - if (inverse.kind === 'belongsTo'){ - map(records, function(record){ - set(record, inverse.name, owner); - }); - } - } - - rel = store.findMany(owner, data[key], typeForRelationshipMeta(store, meta), resolver); - } - - // Cache the promise so we can use it when we come back and don't - // need to rebuild the relationship. - set(rel, 'promise', resolver.promise); - - return rel; - }); - - var promise = relationship.get('promise').then(function() { - return relationship; - }, null, "DS: Async hasMany records received"); - - return PromiseArray.create({ - promise: promise - }); - }).meta(meta).readOnly(); - } - - /* - Builds the ManyArray for a relationship using the provided callback, - but only if it had not been created previously. After building, it - sets some metadata on the created ManyArray, such as the record which - owns it and the name of the relationship. - */ - function buildRelationship(record, key, options, callback) { - var rels = record._relationships; - - if (rels[key]) { return rels[key]; } - - var data = get(record, 'data'); - var store = get(record, 'store'); - - var relationship = rels[key] = callback.call(record, store, data); - - return setProperties(relationship, { - owner: record, - name: key, - isPolymorphic: options.polymorphic - }); - } + var Model = __dependency1__.Model; /** `DS.hasMany` is used to define One-To-Many and Many-To-Many @@ -9973,18 +9630,418 @@ define("ember-data/system/relationships/has_many", key: null }; - if (options.async) { - return asyncHasMany(type, options, meta); - } else { - return syncHasMany(type, options, meta); - } + return Ember.computed(function(key) { + var relationship = this._relationships[key]; + return relationship.getRecords(); + }).meta(meta).readOnly(); } + Model.reopen({ + notifyHasManyAdded: function(key, record, idx) { + var relationship = this._relationships[key]; + var manyArray = relationship.manyArray; + manyArray.addRecord(record, idx); + //We need to notifyPropertyChange in the adding case because we need to make sure + //we fetch the newly added record in case it is unloaded + //TODO(Igor): Consider whether we could do this only if the record state is unloaded + this.notifyPropertyChange(key); + }, + + notifyHasManyRemoved: function(key, record) { + var relationship = this._relationships[key]; + var manyArray = relationship.manyArray; + manyArray.removeRecord(record); + } + }); + + __exports__["default"] = hasMany; }); -define("ember-data/system/store", - ["ember-data/system/adapter","ember-inflector/system/string","exports"], +define("ember-data/system/relationships/relationship", + ["ember-data/system/promise_proxies","ember-data/system/map","exports"], function(__dependency1__, __dependency2__, __exports__) { + "use strict"; + var PromiseManyArray = __dependency1__.PromiseManyArray; + var PromiseObject = __dependency1__.PromiseObject; + var OrderedSet = __dependency2__.OrderedSet; + + var Relationship = function(store, record, inverseKey, relationshipMeta) { + this.members = new OrderedSet(); + this.store = store; + this.key = relationshipMeta.key; + this.inverseKey = inverseKey; + this.record = record; + this.key = relationshipMeta.key; + this.isAsync = relationshipMeta.options.async; + this.relationshipMeta = relationshipMeta; + //This probably breaks for polymorphic relationship in complex scenarios, due to + //multiple possible typeKeys + this.inverseKeyForImplicit = this.store.modelFor(this.record.constructor).typeKey + this.key; + //Cached promise when fetching the relationship from a link + this.linkPromise = null; + }; + + Relationship.prototype = { + constructor: Relationship, + + destroy: Ember.K, + + clear: function() { + this.members.forEach(function(member) { + this.removeRecord(member); + }, this); + }, + + disconnect: function(){ + this.members.forEach(function(member) { + this.removeRecordFromInverse(member); + }, this); + }, + + reconnect: function(){ + this.members.forEach(function(member) { + this.addRecordToInverse(member); + }, this); + }, + + removeRecords: function(records){ + var that = this; + records.forEach(function(record){ + that.removeRecord(record); + }); + }, + + addRecords: function(records, idx){ + var that = this; + records.forEach(function(record){ + that.addRecord(record, idx); + if (idx !== undefined) { + idx++; + } + }); + }, + + addRecord: function(record, idx) { + if (!this.members.has(record)) { + this.members.add(record); + this.notifyRecordRelationshipAdded(record, idx); + if (this.inverseKey) { + record._relationships[this.inverseKey].addRecord(this.record); + } else { + if (!record._implicitRelationships[this.inverseKeyForImplicit]) { + record._implicitRelationships[this.inverseKeyForImplicit] = new Relationship(this.store, record, this.key, {options:{}}); + } + record._implicitRelationships[this.inverseKeyForImplicit].addRecord(this.record); + } + this.record.updateRecordArrays(); + } + }, + + removeRecord: function(record) { + if (this.members.has(record)) { + this.removeRecordFromOwn(record); + if (this.inverseKey) { + this.removeRecordFromInverse(record); + } else { + if (record._implicitRelationships[this.inverseKeyForImplicit]) { + record._implicitRelationships[this.inverseKeyForImplicit].removeRecord(this.record); + } + } + } + }, + + addRecordToInverse: function(record) { + if (this.inverseKey) { + record._relationships[this.inverseKey].addRecord(this.record); + } + }, + + removeRecordFromInverse: function(record) { + var inverseRelationship = record._relationships[this.inverseKey]; + //Need to check for existence, as the record might unloading at the moment + if (inverseRelationship) { + inverseRelationship.removeRecordFromOwn(this.record); + } + }, + + removeRecordFromOwn: function(record) { + this.members["delete"](record); + this.notifyRecordRelationshipRemoved(record); + this.record.updateRecordArrays(); + }, + + updateLink: function(link) { + if (link !== this.link) { + this.link = link; + this.linkPromise = null; + this.record.notifyPropertyChange(this.key); + } + }, + + findLink: function() { + if (this.linkPromise) { + return this.linkPromise; + } else { + var promise = this.fetchLink(); + this.linkPromise = promise; + return promise.then(function(result) { + return result; + }); + } + }, + + updateRecordsFromAdapter: function(records) { + //TODO Once we have adapter support, we need to handle updated and canonical changes + this.computeChanges(records); + }, + + notifyRecordRelationshipAdded: Ember.K, + notifyRecordRelationshipRemoved: Ember.K + }; + + var ManyRelationship = function(store, record, inverseKey, relationshipMeta) { + this._super$constructor(store, record, inverseKey, relationshipMeta); + this.belongsToType = relationshipMeta.type; + this.manyArray = store.recordArrayManager.createManyArray(this.belongsToType, Ember.A()); + this.manyArray.relationship = this; + this.isPolymorphic = relationshipMeta.options.polymorphic; + this.manyArray.isPolymorphic = this.isPolymorphic; + }; + + ManyRelationship.prototype = Object.create(Relationship.prototype); + ManyRelationship.prototype.constructor = ManyRelationship; + ManyRelationship.prototype._super$constructor = Relationship; + + ManyRelationship.prototype.destroy = function() { + this.manyArray.destroy(); + }; + + ManyRelationship.prototype.notifyRecordRelationshipAdded = function(record, idx) { + Ember.assert("You cannot add '" + record.constructor.typeKey + "' records to this relationship (only '" + this.belongsToType.typeKey + "' allowed)", !this.belongsToType || record instanceof this.belongsToType); + this.record.notifyHasManyAdded(this.key, record, idx); + }; + + ManyRelationship.prototype.notifyRecordRelationshipRemoved = function(record) { + this.record.notifyHasManyRemoved(this.key, record); + }; + + ManyRelationship.prototype.reload = function() { + var self = this; + if (this.link) { + return this.fetchLink(); + } else { + return this.store.scheduleFetchMany(this.manyArray.toArray()).then(function() { + //Goes away after the manyArray refactor + self.manyArray.set('isLoaded', true); + return self.manyArray; + }); + } + }; + + ManyRelationship.prototype.computeChanges = function(records) { + var members = this.members; + var recordsToRemove = []; + var length; + var record; + var i; + + records = setForArray(records); + + members.forEach(function(member) { + if (records.has(member)) return; + + recordsToRemove.push(member); + }); + this.removeRecords(recordsToRemove); + + var hasManyArray = this.manyArray; + + // Using records.toArray() since currently using + // removeRecord can modify length, messing stuff up + // forEach since it directly looks at "length" each + // iteration + records = records.toArray(); + length = records.length; + for (i = 0; i < length; i++){ + record = records[i]; + //Need to preserve the order of incoming records + if (hasManyArray.objectAt(i) === record ) { + continue; + } + this.removeRecord(record); + this.addRecord(record, i); + } + }; + + ManyRelationship.prototype.fetchLink = function() { + var self = this; + return this.store.findHasMany(this.record, this.link, this.relationshipMeta).then(function(records){ + self.updateRecordsFromAdapter(records); + return self.manyArray; + }); + }; + + ManyRelationship.prototype.findRecords = function() { + var manyArray = this.manyArray; + return this.store.findMany(manyArray.toArray()).then(function(){ + //Goes away after the manyArray refactor + manyArray.set('isLoaded', true); + return manyArray; + }); + }; + + ManyRelationship.prototype.getRecords = function() { + if (this.isAsync) { + var self = this; + var promise; + if (this.link) { + promise = this.findLink().then(function() { + return self.findRecords(); + }); + } else { + promise = this.findRecords(); + } + return PromiseManyArray.create({ + content: this.manyArray, + promise: promise + }); + } else { + Ember.assert("You looked up the '" + this.key + "' relationship on a '" + this.record.constructor.typeKey + "' with id " + this.record.get('id') + " but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", this.manyArray.isEvery('isEmpty', false)); + + this.manyArray.set('isLoaded', true); + return this.manyArray; + } + }; + + var BelongsToRelationship = function(store, record, inverseKey, relationshipMeta) { + this._super$constructor(store, record, inverseKey, relationshipMeta); + this.record = record; + this.key = relationshipMeta.key; + this.inverseRecord = null; + }; + + BelongsToRelationship.prototype = Object.create(Relationship.prototype); + BelongsToRelationship.prototype.constructor = BelongsToRelationship; + BelongsToRelationship.prototype._super$constructor = Relationship; + + BelongsToRelationship.prototype.setRecord = function(newRecord) { + if (newRecord) { + this.addRecord(newRecord); + } else if (this.inverseRecord) { + this.removeRecord(this.inverseRecord); + } + }; + + BelongsToRelationship.prototype._super$addRecord = Relationship.prototype.addRecord; + BelongsToRelationship.prototype.addRecord = function(newRecord) { + if (this.members.has(newRecord)){ return;} + var type = this.relationshipMeta.type; + Ember.assert("You can only add a '" + type.typeKey + "' record to this relationship", newRecord instanceof type); + + if (this.inverseRecord) { + this.removeRecord(this.inverseRecord); + } + + this.inverseRecord = newRecord; + this._super$addRecord(newRecord); + }; + + BelongsToRelationship.prototype.setRecordPromise = function(newPromise) { + var content = newPromise.get && newPromise.get('content'); + Ember.assert("You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.", content !== undefined); + this.setRecord(content); + }; + + BelongsToRelationship.prototype.notifyRecordRelationshipAdded = function(newRecord) { + this.record.notifyBelongsToAdded(this.key, this); + }; + + BelongsToRelationship.prototype.notifyRecordRelationshipRemoved = function(record) { + this.record.notifyBelongsToRemoved(this.key, this); + }; + + BelongsToRelationship.prototype._super$removeRecordFromOwn = Relationship.prototype.removeRecordFromOwn; + BelongsToRelationship.prototype.removeRecordFromOwn = function(record) { + if (!this.members.has(record)){ return;} + this._super$removeRecordFromOwn(record); + this.inverseRecord = null; + }; + + BelongsToRelationship.prototype.findRecord = function() { + if (this.inverseRecord) { + return this.store._findByRecord(this.inverseRecord); + } else { + return Ember.RSVP.Promise.resolve(null); + } + }; + + BelongsToRelationship.prototype.fetchLink = function() { + var self = this; + return this.store.findBelongsTo(this.record, this.link, this.relationshipMeta).then(function(record){ + self.addRecord(record); + return record; + }); + }; + + BelongsToRelationship.prototype.getRecord = function() { + if (this.isAsync) { + var promise; + if (this.link){ + var self = this; + promise = this.findLink().then(function() { + return self.findRecord(); + }); + } else { + promise = this.findRecord(); + } + + return PromiseObject.create({ + promise: promise, + content: this.inverseRecord + }); + } else { + Ember.assert("You looked up the '" + this.key + "' relationship on a '" + this.record.constructor.typeKey + "' with id " + this.record.get('id') + " but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.belongsTo({ async: true })`)", this.inverseRecord === null || !this.inverseRecord.get('isEmpty')); + return this.inverseRecord; + } + }; + + function setForArray(array) { + var set = new OrderedSet(); + + if (array) { + for (var i=0, l=array.length; i 3 ? slice.call(arguments, 3) : undefined; + var stack = this.DEBUG ? new Error() : undefined; + var length = arguments.length; + var args; + + if (length > 3) { + args = new Array(length - 3); + for (var i = 3; i < length; i++) { + args[i-3] = arguments[i]; + } + } else { + args = undefined; + } + if (!this.currentInstance) { createAutorun(this); } return this.currentInstance.schedule(queueName, target, method, args, false, stack); }, @@ -233,15 +232,34 @@ define("backburner", method = target[method]; } - var stack = this.DEBUG ? new Error() : undefined, - args = arguments.length > 3 ? slice.call(arguments, 3) : undefined; - if (!this.currentInstance) { createAutorun(this); } + var stack = this.DEBUG ? new Error() : undefined; + var length = arguments.length; + var args; + + if (length > 3) { + args = new Array(length - 3); + for (var i = 3; i < length; i++) { + args[i-3] = arguments[i]; + } + } else { + args = undefined; + } + + if (!this.currentInstance) { + createAutorun(this); + } return this.currentInstance.schedule(queueName, target, method, args, true, stack); }, setTimeout: function() { - var args = slice.call(arguments), - length = args.length, + var l = arguments.length; + var args = new Array(l); + + for (var x = 0; x < l; x++) { + args[x] = arguments[x]; + } + + var length = args.length, method, wait, target, methodOrTarget, methodOrWait, methodOrArgs; @@ -287,7 +305,7 @@ define("backburner", } } - var executeAt = (+new Date()) + parseInt(wait, 10); + var executeAt = now() + parseInt(wait, 10); if (isString(method)) { method = target[method]; @@ -308,9 +326,9 @@ define("backburner", } // find position to insert - var i = searchTimer(executeAt, timers); + var i = searchTimer(executeAt, this._timers); - timers.splice(i, 0, executeAt, fn); + this._timers.splice(i, 0, executeAt, fn); updateLaterTimer(this, executeAt, wait); @@ -318,13 +336,10 @@ define("backburner", }, throttle: function(target, method /* , args, wait, [immediate] */) { - var self = this, - args = arguments, - immediate = pop.call(args), - wait, - throttler, - index, - timer; + var backburner = this; + var args = arguments; + var immediate = pop.call(args); + var wait, throttler, index, timer; if (isNumber(immediate) || isString(immediate)) { wait = immediate; @@ -340,16 +355,16 @@ define("backburner", timer = global.setTimeout(function() { if (!immediate) { - self.run.apply(self, args); + backburner.run.apply(backburner, args); } - var index = findThrottler(target, method, self._throttlers); + var index = findThrottler(target, method, backburner._throttlers); if (index > -1) { - self._throttlers.splice(index, 1); + backburner._throttlers.splice(index, 1); } }, wait); if (immediate) { - self.run.apply(self, args); + this.run.apply(this, args); } throttler = [target, method, timer]; @@ -360,13 +375,10 @@ define("backburner", }, debounce: function(target, method /* , args, wait, [immediate] */) { - var self = this, - args = arguments, - immediate = pop.call(args), - wait, - index, - debouncee, - timer; + var backburner = this; + var args = arguments; + var immediate = pop.call(args); + var wait, index, debouncee, timer; if (isNumber(immediate) || isString(immediate)) { wait = immediate; @@ -387,21 +399,25 @@ define("backburner", timer = global.setTimeout(function() { if (!immediate) { - self.run.apply(self, args); + backburner.run.apply(backburner, args); } - var index = findDebouncee(target, method, self._debouncees); + var index = findDebouncee(target, method, backburner._debouncees); if (index > -1) { - self._debouncees.splice(index, 1); + backburner._debouncees.splice(index, 1); } }, wait); if (immediate && index === -1) { - self.run.apply(self, args); + backburner.run.apply(backburner, args); } - debouncee = [target, method, timer]; + debouncee = [ + target, + method, + timer + ]; - self._debouncees.push(debouncee); + backburner._debouncees.push(debouncee); return debouncee; }, @@ -421,7 +437,7 @@ define("backburner", clearTimeout(this._laterTimer); this._laterTimer = null; } - timers = []; + this._timers = []; if (this._autorun) { clearTimeout(this._autorun); @@ -430,7 +446,7 @@ define("backburner", }, hasTimers: function() { - return !!timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun; + return !!this._timers.length || !!this._debouncees.length || !!this._throttlers.length || this._autorun; }, cancel: function(timer) { @@ -439,9 +455,18 @@ define("backburner", if (timer && timerType === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce return timer.queue.cancel(timer); } else if (timerType === 'function') { // we're cancelling a setTimeout - for (var i = 0, l = timers.length; i < l; i += 2) { - if (timers[i + 1] === timer) { - timers.splice(i, 2); // remove the two elements + for (var i = 0, l = this._timers.length; i < l; i += 2) { + if (this._timers[i + 1] === timer) { + this._timers.splice(i, 2); // remove the two elements + if (i === 0) { + if (this._laterTimer) { // Active timer? Then clear timer and reset for future timer + clearTimeout(this._laterTimer); + this._laterTimer = null; + } + if (this._timers.length > 0) { // Update to next available timer when available + updateLaterTimer(this, this._timers[0], this._timers[0] - now()); + } + } return true; } } @@ -454,18 +479,17 @@ define("backburner", }, _cancelItem: function(findMethod, array, timer){ - var item, - index; + var item, index; if (timer.length < 3) { return false; } index = findMethod(timer[0], timer[1], array); - if(index > -1) { + if (index > -1) { item = array[index]; - if(item[2] === timer[2]){ + if (item[2] === timer[2]) { array.splice(index, 1); clearTimeout(timer[2]); return true; @@ -488,21 +512,10 @@ define("backburner", Backburner.prototype.end = wrapInTryCatch(originalEnd); } - function wrapInTryCatch(func) { - return function () { - try { - return func.apply(this, arguments); - } catch (e) { - throw e; - } - }; - } - function getOnError(options) { return options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]); } - function createAutorun(backburner) { backburner.begin(); backburner._autorun = global.setTimeout(function() { @@ -511,33 +524,48 @@ define("backburner", }); } - function updateLaterTimer(self, executeAt, wait) { - if (!self._laterTimer || executeAt < self._laterTimerExpiresAt) { - self._laterTimer = global.setTimeout(function() { - self._laterTimer = null; - self._laterTimerExpiresAt = null; - executeTimers(self); + function updateLaterTimer(backburner, executeAt, wait) { + var n = now(); + if (!backburner._laterTimer || executeAt < backburner._laterTimerExpiresAt || backburner._laterTimerExpiresAt < n) { + + if (backburner._laterTimer) { + // Clear when: + // - Already expired + // - New timer is earlier + clearTimeout(backburner._laterTimer); + + if (backburner._laterTimerExpiresAt < n) { // If timer was never triggered + // Calculate the left-over wait-time + wait = Math.max(0, executeAt - n); + } + } + + backburner._laterTimer = global.setTimeout(function() { + backburner._laterTimer = null; + backburner._laterTimerExpiresAt = null; + executeTimers(backburner); }, wait); - self._laterTimerExpiresAt = executeAt; + + backburner._laterTimerExpiresAt = n + wait; } } - function executeTimers(self) { - var now = +new Date(), - time, fns, i, l; + function executeTimers(backburner) { + var n = now(); + var fns, i, l; - self.run(function() { - i = searchTimer(now, timers); + backburner.run(function() { + i = searchTimer(n, backburner._timers); - fns = timers.splice(0, i); + fns = backburner._timers.splice(0, i); for (i = 1, l = fns.length; i < l; i += 2) { - self.schedule(self.options.defaultQueue, null, fns[i]); + backburner.schedule(backburner.options.defaultQueue, null, fns[i]); } }); - if (timers.length) { - updateLaterTimer(self, timers[0], timers[0] - now); + if (backburner._timers.length) { + updateLaterTimer(backburner, backburner._timers[0], backburner._timers[0] - n); } } @@ -550,8 +578,8 @@ define("backburner", } function findItem(target, method, collection) { - var item, - index = -1; + var item; + var index = -1; for (var i = 0, l = collection.length; i < l; i++) { item = collection[i]; @@ -564,10 +592,31 @@ define("backburner", return index; } - function searchTimer(time, timers) { - var start = 0, - end = timers.length - 2, - middle, l; + __exports__["default"] = Backburner; + }); +enifed("backburner.umd", + ["./backburner"], + function(__dependency1__) { + "use strict"; + var Backburner = __dependency1__["default"]; + + /* global define:true module:true window: true */ + if (typeof enifed === 'function' && enifed.amd) { + enifed(function() { return Backburner; }); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = Backburner; + } else if (typeof this !== 'undefined') { + this['Backburner'] = Backburner; + } + }); +enifed("backburner/binary-search", + ["exports"], + function(__exports__) { + "use strict"; + __exports__["default"] = function binarySearch(time, timers) { + var start = 0; + var end = timers.length - 2; + var middle, l; while (start < end) { // since timers is an array of pairs 'l' will always @@ -587,18 +636,14 @@ define("backburner", return (time >= timers[start]) ? start + 2 : start; } - - __exports__.Backburner = Backburner; }); -define("backburner/deferred_action_queues", - ["backburner/utils","backburner/queue","exports"], +enifed("backburner/deferred-action-queues", + ["./utils","./queue","exports"], function(__dependency1__, __dependency2__, __exports__) { "use strict"; - var Utils = __dependency1__["default"]; - var Queue = __dependency2__.Queue; - - var each = Utils.each, - isString = Utils.isString; + var each = __dependency1__.each; + var isString = __dependency1__.isString; + var Queue = __dependency2__["default"]; function DeferredActionQueues(queueNames, options) { var queues = this.queues = {}; @@ -607,20 +652,20 @@ define("backburner/deferred_action_queues", this.options = options; each(queueNames, function(queueName) { - queues[queueName] = new Queue(this, queueName, options); + queues[queueName] = new Queue(queueName, options[queueName], options); }); } + function noSuchQueue(name) { + throw new Error("You attempted to schedule an action in a queue (" + name + ") that doesn't exist"); + } + DeferredActionQueues.prototype = { - queueNames: null, - queues: null, - options: null, + schedule: function(name, target, method, args, onceFlag, stack) { + var queues = this.queues; + var queue = queues[name]; - schedule: function(queueName, target, method, args, onceFlag, stack) { - var queues = this.queues, - queue = queues[queueName]; - - if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); } + if (!queue) { noSuchQueue(name); } if (onceFlag) { return queue.pushUnique(target, method, args, stack); @@ -629,7 +674,7 @@ define("backburner/deferred_action_queues", } }, - invoke: function(target, method, args, _) { + invoke: function(target, method, args, _, _errorRecordedForStack) { if (args && args.length > 0) { method.apply(target, args); } else { @@ -637,7 +682,7 @@ define("backburner/deferred_action_queues", } }, - invokeWithOnError: function(target, method, args, onError) { + invokeWithOnError: function(target, method, args, onError, errorRecordedForStack) { try { if (args && args.length > 0) { method.apply(target, args); @@ -645,59 +690,80 @@ define("backburner/deferred_action_queues", method.call(target); } } catch(error) { - onError(error); + onError(error, errorRecordedForStack); } }, flush: function() { - var queues = this.queues, - queueNames = this.queueNames, - queueName, queue, queueItems, priorQueueNameIndex, - queueNameIndex = 0, numberOfQueues = queueNames.length, - options = this.options, - onError = options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]), - invoke = onError ? this.invokeWithOnError : this.invoke; + var queues = this.queues; + var queueNames = this.queueNames; + var queueName, queue, queueItems, priorQueueNameIndex; + var queueNameIndex = 0; + var numberOfQueues = queueNames.length; + var options = this.options; + var onError = options.onError || (options.onErrorTarget && options.onErrorTarget[options.onErrorMethod]); + var invoke = onError ? this.invokeWithOnError : this.invoke; - outerloop: while (queueNameIndex < numberOfQueues) { queueName = queueNames[queueNameIndex]; queue = queues[queueName]; queueItems = queue._queueBeingFlushed = queue._queue.slice(); queue._queue = []; + queue.targetQueues = Object.create(null); - var queueOptions = queue.options, // TODO: write a test for this - before = queueOptions && queueOptions.before, - after = queueOptions && queueOptions.after, - target, method, args, stack, - queueIndex = 0, numberOfQueueItems = queueItems.length; + var queueOptions = queue.options; // TODO: write a test for this + var before = queueOptions && queueOptions.before; + var after = queueOptions && queueOptions.after; + var target, method, args, errorRecordedForStack; + var queueIndex = 0; + var numberOfQueueItems = queueItems.length; - if (numberOfQueueItems && before) { before(); } + if (numberOfQueueItems && before) { + before(); + } while (queueIndex < numberOfQueueItems) { - target = queueItems[queueIndex]; - method = queueItems[queueIndex+1]; - args = queueItems[queueIndex+2]; - stack = queueItems[queueIndex+3]; // Debugging assistance + target = queueItems[queueIndex]; + method = queueItems[queueIndex+1]; + args = queueItems[queueIndex+2]; + errorRecordedForStack = queueItems[queueIndex+3]; // Debugging assistance - if (isString(method)) { method = target[method]; } + // + + if (isString(method)) { + method = target[method]; + } // method could have been nullified / canceled during flush if (method) { - invoke(target, method, args, onError); + // + // ** Attention intrepid developer ** + // + // To find out the stack of this task when it was scheduled onto + // the run loop, add the following to your app.js: + // + // Ember.run.backburner.DEBUG = true; // NOTE: This slows your app, don't leave it on in production. + // + // Once that is in place, when you are at a breakpoint and navigate + // here in the stack explorer, you can look at `errorRecordedForStack.stack`, + // which will be the captured stack when this job was scheduled. + // + invoke(target, method, args, onError, errorRecordedForStack); } queueIndex += 4; } queue._queueBeingFlushed = null; - if (numberOfQueueItems && after) { after(); } + if (numberOfQueueItems && after) { + after(); + } if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) { queueNameIndex = priorQueueNameIndex; - continue outerloop; + } else { + queueNameIndex++; } - - queueNameIndex++; } } }; @@ -714,60 +780,134 @@ define("backburner/deferred_action_queues", return -1; } - __exports__.DeferredActionQueues = DeferredActionQueues; + __exports__["default"] = DeferredActionQueues; }); -define("backburner/queue", +enifed("backburner/platform", ["exports"], function(__exports__) { "use strict"; - function Queue(daq, name, options) { - this.daq = daq; + // In IE 6-8, try/finally doesn't work without a catch. + // Unfortunately, this is impossible to test for since wrapping it in a parent try/catch doesn't trigger the bug. + // This tests for another broken try/catch behavior that only exhibits in the same versions of IE. + var needsIETryCatchFix = (function(e,x){ + try{ x(); } + catch(e) { } // jshint ignore:line + return !!e; + })(); + __exports__.needsIETryCatchFix = needsIETryCatchFix; + }); +enifed("backburner/queue", + ["exports"], + function(__exports__) { + "use strict"; + function Queue(name, options, globalOptions) { this.name = name; - this.globalOptions = options; - this.options = options[name]; + this.globalOptions = globalOptions || {}; + this.options = options; this._queue = []; + this.targetQueues = Object.create(null); + this._queueBeingFlushed = undefined; } Queue.prototype = { - daq: null, - name: null, - options: null, - onError: null, - _queue: null, - push: function(target, method, args, stack) { var queue = this._queue; queue.push(target, method, args, stack); - return {queue: this, target: target, method: method}; + + return { + queue: this, + target: target, + method: method + }; }, - pushUnique: function(target, method, args, stack) { - var queue = this._queue, currentTarget, currentMethod, i, l; + pushUniqueWithoutGuid: function(target, method, args, stack) { + var queue = this._queue; - for (i = 0, l = queue.length; i < l; i += 4) { - currentTarget = queue[i]; - currentMethod = queue[i+1]; + for (var i = 0, l = queue.length; i < l; i += 4) { + var currentTarget = queue[i]; + var currentMethod = queue[i+1]; if (currentTarget === target && currentMethod === method) { - queue[i+2] = args; // replace args + queue[i+2] = args; // replace args queue[i+3] = stack; // replace stack - return {queue: this, target: target, method: method}; + return; } } queue.push(target, method, args, stack); - return {queue: this, target: target, method: method}; + }, + + targetQueue: function(targetQueue, target, method, args, stack) { + var queue = this._queue; + + for (var i = 0, l = targetQueue.length; i < l; i += 4) { + var currentMethod = targetQueue[i]; + var currentIndex = targetQueue[i + 1]; + + if (currentMethod === method) { + queue[currentIndex + 2] = args; // replace args + queue[currentIndex + 3] = stack; // replace stack + return; + } + } + + targetQueue.push( + method, + queue.push(target, method, args, stack) - 4 + ); + }, + + pushUniqueWithGuid: function(guid, target, method, args, stack) { + var hasLocalQueue = this.targetQueues[guid]; + + if (hasLocalQueue) { + this.targetQueue(hasLocalQueue, target, method, args, stack); + } else { + this.targetQueues[guid] = [ + method, + this._queue.push(target, method, args, stack) - 4 + ]; + } + + return { + queue: this, + target: target, + method: method + }; + }, + + pushUnique: function(target, method, args, stack) { + var queue = this._queue, currentTarget, currentMethod, i, l; + var KEY = this.globalOptions.GUID_KEY; + + if (target && KEY) { + var guid = target[KEY]; + if (guid) { + return this.pushUniqueWithGuid(guid, target, method, args, stack); + } + } + + this.pushUniqueWithoutGuid(target, method, args, stack); + + return { + queue: this, + target: target, + method: method + }; }, // TODO: remove me, only being used for Ember.run.sync flush: function() { - var queue = this._queue, - globalOptions = this.globalOptions, - options = this.options, - before = options && options.before, - after = options && options.after, - onError = globalOptions.onError || (globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]), - target, method, args, stack, i, l = queue.length; + var queue = this._queue; + var globalOptions = this.globalOptions; + var options = this.options; + var before = options && options.before; + var after = options && options.after; + var onError = globalOptions.onError || (globalOptions.onErrorTarget && globalOptions.onErrorTarget[globalOptions.onErrorMethod]); + var target, method, args, stack, i, l = queue.length; + + this.targetQueues = Object.create(null); if (l && before) { before(); } for (i = 0; i < l; i += 4) { @@ -812,12 +952,28 @@ define("backburner/queue", cancel: function(actionToCancel) { var queue = this._queue, currentTarget, currentMethod, i, l; + var target = actionToCancel.target; + var method = actionToCancel.method; + var GUID_KEY = this.globalOptions.GUID_KEY; + + if (GUID_KEY && this.targetQueues && target) { + var targetQueue = this.targetQueues[target[GUID_KEY]]; + + if (targetQueue) { + for (i = 0, l = targetQueue.length; i < l; i++) { + if (targetQueue[i] === method) { + targetQueue.splice(i, 1); + } + } + } + } for (i = 0, l = queue.length; i < l; i += 4) { currentTarget = queue[i]; currentMethod = queue[i+1]; - if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) { + if (currentTarget === target && + currentMethod === method) { queue.splice(i, 4); return true; } @@ -833,7 +989,8 @@ define("backburner/queue", currentTarget = queue[i]; currentMethod = queue[i+1]; - if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) { + if (currentTarget === target && + currentMethod === method) { // don't mess with array during flush // just nullify the method queue[i+1] = null; @@ -843,43 +1000,63 @@ define("backburner/queue", } }; - __exports__.Queue = Queue; + __exports__["default"] = Queue; }); -define("backburner/utils", +enifed("backburner/utils", ["exports"], function(__exports__) { "use strict"; - __exports__["default"] = { - each: function(collection, callback) { - for (var i = 0; i < collection.length; i++) { - callback(collection[i]); - } - }, + var NUMBER = /\d+/; - isString: function(suspect) { - return typeof suspect === 'string'; - }, - - isFunction: function(suspect) { - return typeof suspect === 'function'; - }, - - isNumber: function(suspect) { - return typeof suspect === 'number'; + function each(collection, callback) { + for (var i = 0; i < collection.length; i++) { + callback(collection[i]); } - }; + } + + __exports__.each = each;// Date.now is not available in browsers < IE9 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility + var now = Date.now || function() { return new Date().getTime(); }; + __exports__.now = now; + function isString(suspect) { + return typeof suspect === 'string'; + } + + __exports__.isString = isString;function isFunction(suspect) { + return typeof suspect === 'function'; + } + + __exports__.isFunction = isFunction;function isNumber(suspect) { + return typeof suspect === 'number'; + } + + __exports__.isNumber = isNumber;function isCoercableNumber(number) { + return isNumber(number) || NUMBER.test(number); + } + + __exports__.isCoercableNumber = isCoercableNumber;function wrapInTryCatch(func) { + return function () { + try { + return func.apply(this, arguments); + } catch (e) { + throw e; + } + }; + } + + __exports__.wrapInTryCatch = wrapInTryCatch; }); -define("calculateVersion", +enifed("calculateVersion", [], function() { "use strict"; 'use strict'; - var fs = require('fs'); - var path = require('path'); + var fs = eriuqer('fs'); + var path = eriuqer('path'); module.exports = function () { - var packageVersion = require('../package.json').version; + var packageVersion = eriuqer('../package.json').version; var output = [packageVersion]; var gitPath = path.join(__dirname,'..','.git'); var headFilePath = path.join(gitPath, 'HEAD'); @@ -910,7 +1087,7 @@ define("calculateVersion", } }; }); -define("container", +enifed("container", ["container/container","exports"], function(__dependency1__, __exports__) { "use strict"; @@ -937,13 +1114,14 @@ define("container", __exports__["default"] = Container; }); -define("container/container", - ["ember-metal/core","ember-metal/dictionary","exports"], - function(__dependency1__, __dependency2__, __exports__) { +enifed("container/container", + ["ember-metal/core","ember-metal/keys","ember-metal/dictionary","exports"], + function(__dependency1__, __dependency2__, __dependency3__, __exports__) { "use strict"; var Ember = __dependency1__["default"]; // Ember.assert - var dictionary = __dependency2__["default"]; + var emberKeys = __dependency2__["default"]; + var dictionary = __dependency3__["default"]; // A lightweight container that helps to assemble and decouple components. // Public api for the container is still in flux. @@ -1740,7 +1918,7 @@ define("container/container", function eachDestroyable(container, callback) { var cache = container.cache; - var keys = Object.keys(cache); + var keys = emberKeys(cache); var key, value; for (var i = 0, l = keys.length; i < l; i++) { @@ -1793,7 +1971,7 @@ define("container/container", __exports__["default"] = Container; }); -define("ember-application", +enifed("ember-application", ["ember-metal/core","ember-runtime/system/lazy_load","ember-application/system/dag","ember-application/system/resolver","ember-application/system/application","ember-application/ext/controller"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__) { "use strict"; @@ -1821,7 +1999,7 @@ define("ember-application", runLoadHooks('Ember.Application', Application); }); -define("ember-application/ext/controller", +enifed("ember-application/ext/controller", ["ember-metal/core","ember-metal/property_get","ember-metal/property_set","ember-metal/error","ember-metal/utils","ember-metal/computed","ember-runtime/mixins/controller","ember-routing/system/controller_for","exports"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) { "use strict"; @@ -1842,7 +2020,8 @@ define("ember-application/ext/controller", var controllerFor = __dependency8__["default"]; function verifyNeedsDependencies(controller, container, needs) { - var dependency, i, l, missing = []; + var dependency, i, l; + var missing = []; for (i=0, l=needs.length; i -1; } else { @@ -5503,7 +5787,7 @@ define("ember-handlebars/controls/select", ``` ```handlebars - {{view Ember.Select content=names}} + {{view "select" content=names}} ``` Would result in the following HTML: @@ -5526,10 +5810,7 @@ define("ember-handlebars/controls/select", ``` ```handlebars - {{view Ember.Select - content=names - value=selectedName - }} + {{view "select" content=names value=selectedName}} ``` Would result in the following HTML with the `