diff --git a/app/application.coffee b/app/application.coffee index d3df822..33541e1 100644 --- a/app/application.coffee +++ b/app/application.coffee @@ -35,7 +35,12 @@ class Application extends Mn.Application @model = new AppViewModel null, model: config @contacts = new ContactsCollection() - @filtered = new FilteredCollection() + @filtered = new FilteredCollection { + contacts: @contacts + model: @model + channel: @channel + } + @tags = new TagsCollection @contacts @layout = new AppLayout model: @model @@ -50,14 +55,10 @@ class Application extends Mn.Application onStart: -> @layout.render() - callback = (models, collection) => - console.log 'SUCCESS', models - + @contacts.fetch {reset: true}, (models, collection) => # As long as Tags require Contacts to be loaded (see Tags.refs), we # trigger all TagsCollection fetch after syncing ContactsCollection. - # @tags.underlying.fetch {reset: true} - - @contacts.fetch {reset: true}, callback + @tags.underlying.fetch {reset: true} # prohibit pushState because URIs mapped from cozy-home rely on # fragment diff --git a/app/collections/contacts.coffee b/app/collections/contacts.coffee index 7737f4e..620cdb8 100644 --- a/app/collections/contacts.coffee +++ b/app/collections/contacts.coffee @@ -26,15 +26,61 @@ module.exports = class Contacts extends Backbone.Collection @reset() if options.reset - cozy.init { isV2: true } - cozy.defineIndex 'io.cozy.contacts', ['id'] - .then (index) => - cozy.query index, { selector: id: {"$gte": " "} } - .then (result=[]) => - @add result - success result - , (err) => - success null, err + # FIXME: remove this fakeData + # when request will work + n = 'last name;first name;middle name;préfix;suffixe' + datapoints = [] + datapoints.push { + id: "06", + name: "email", + value: "noelie@cozycloud.cc", + type: "work" + } + datapoints.push { + id: "05", + name: "email", + value: "noelieandrieu@gmail.com", + type: "work" + } + datapoints.push { + id: "04", + name: "tel", + value: "0619384462", + type: "main" + } + datapoints.push { + id: "03", + name: "tel", + value: "0619384462", + type: "portable" + } + datapoints.push { + id: "02", + name: "tel", + value: "n0619384462", + type: "work" + } + + + result = [] + result.push { _id: '01', n, datapoints } + result.push { _id: '02', n, datapoints } + result.push { _id: '03', n, datapoints } + result.push { _id: '04', n, datapoints } + result.push { _id: '05', n, datapoints } + # + @add result + success result, @ + + # cozy.init { isV2: true } + # cozy.defineIndex 'io.cozy.contacts', ['id'] + # .then (index) => + # cozy.query index, { selector: id: {"$gte": " "} } + # .then (result=[]) => + # @add result + # success result, @ + # , (err) => + # success null, err diff --git a/app/collections/filtered.coffee b/app/collections/filtered.coffee index b7eb653..a5a3bd9 100644 --- a/app/collections/filtered.coffee +++ b/app/collections/filtered.coffee @@ -3,7 +3,6 @@ ContactViewModel = require 'views/models/contact' {indexes, search} = require 'const-config' tagRegExp = search.pattern 'tag' -app = null scores = new Map() @@ -30,8 +29,9 @@ module.exports = class FilteredCollection query: null - constructor: -> - app = require 'application' + constructor: (options={}) -> + { model, channel, contacts } = options + @app = { model, channel, contacts } @models = [] @indexes = new Map Array::map.call indexes, (char) -> @@ -39,16 +39,16 @@ module.exports = class FilteredCollection vmodels.channel = Backbone.Radio.channel "idx.#{char}" [char, vmodels] - @listenTo app.model, + @listenTo @app.model, 'change:sort': @reset 'change:scored': (nil, scored) -> @comparator = if scored (a, b) -> (scores.get(b?.model) or 0) - scores.get(a.model) else 'model.attributes.sortedName' - @listenTo app.channel, 'filter:text': @setQuery + @listenTo @app.channel, 'filter:text': @setQuery - @listenTo app.contacts, + @listenTo @app.contacts, 'reset': @reset 'add': @add 'remove': @remove @@ -69,7 +69,7 @@ module.exports = class FilteredCollection @query = query if query - res = app.contacts.filter filter @query + res = @contacts.filter filter @query # Reduce to get max score and get a median limit, then excludes all # results $lt it. @@ -103,7 +103,7 @@ module.exports = class FilteredCollection get: (opts = {}) -> base = if opts.index then @indexes.get(opts.index) else @models - tag = _.last app.model.get('filter')?.match tagRegExp + tag = _.last @app.model.get('filter')?.match tagRegExp if tag and opts.tagged base.filter (vmodel) -> _.includes vmodel.get('tags'), tag @@ -114,9 +114,9 @@ module.exports = class FilteredCollection # Internal models collection handlers # ################################### reset: -> - @models = app.contacts + @models = @app.contacts .filter filter @query - .map (model) -> new ContactViewModel {}, model: model + .map (model) -> new ContactViewModel @app, { model } @resetIndexes() @@ -127,14 +127,15 @@ module.exports = class FilteredCollection models = if _.isArray models then _.clone models else [models] models.forEach (model) => - return unless filter(@query, model) - - vmodel = new ContactViewModel {}, model: model + return unless filter @query, model + vmodel = new ContactViewModel @app, { model } idx = _.sortedIndex @models, vmodel, @comparator + @models.splice idx, 0, vmodel @addToIndex vmodel + @trigger 'add', vmodel, @, {add:true, index: idx} @@ -163,9 +164,9 @@ module.exports = class FilteredCollection addToIndex: (vmodel, opts = {}) -> set = @indexes.get vmodel.getIndexKey() - return vmodel if _.includes set, vmodel + # add model to collection idx = _.sortedIndex set, vmodel, @comparator set.splice idx, 0, vmodel diff --git a/app/models/contact.coffee b/app/models/contact.coffee index c0b2323..01c57e2 100644 --- a/app/models/contact.coffee +++ b/app/models/contact.coffee @@ -1,6 +1,3 @@ -app = undefined - - class Datapoint extends Backbone.Model defaults: @@ -25,9 +22,24 @@ module.exports = class Contact extends Backbone.Model n: ';;;;' + constructor: (model, options={}) -> + options.parse = true + super model, options + + + initialize: -> + @AppViewModel = require('application').model + @listenTo @AppViewModel, 'change:sort': -> + @set 'sortedName': @_buildSortedName() + + parse: (attrs) -> + datapoints = attrs.datapoints or [] + + # Remove empty values delete attrs[key] for key, value of attrs when value is '' + # Get attrs from n property if attrs.n [gn, fn, ...] = attrs.n.split ';' attrs.initials = _.chain [fn, gn] @@ -47,20 +59,18 @@ module.exports = class Contact extends Backbone.Model # following code. if (url = attrs.url) delete attrs.url - attrs.datapoints.unshift + datapoints.unshift name: 'url' type: 'main' value: url # Ensure Datapoints consistency - datapoints = @attributes.datapoints or - new Backbone.Collection attrs.datapoints or [], - model: Datapoint - parse: true - datapoints.comparator = 'name' - attrs.datapoints = datapoints + attrs.datapoints = new Backbone.Collection datapoints, + model: Datapoint + parse: true + attrs.datapoints.comparator = 'name' - attrs.tags = _.invoke attrs.tags, 'toLowerCase' + attrs.tags = _.invoke (attrs.tags or []), 'toLowerCase' return attrs @@ -89,12 +99,15 @@ module.exports = class Contact extends Backbone.Model # Handle specific attributes. attrs.fn = VCardParser.nToFN attrs.n.split ';' - datapoints = (attrs.datapoints?.toJSON() or []) - attrs.datapoints = datapoints.map (point) -> + datapoints = (attrs.datapoints or []).map (point) -> if point.name is 'adr' point.value = VCardParser.adrStringToArray point.value return point + attrs.datapoints = new Backbone.Collection datapoints, + model: Datapoint + parse: true + # (legacy, see comments in "parse" function) The condition to decide if # an e-mail address can be the main url is: it must be an url and it's # mediatype is empty. Indeed if an url has a mediatype it means that @@ -124,7 +137,7 @@ module.exports = class Contact extends Backbone.Model # waits for cozy-client-js can handle this case save: (options={}) -> cozy.init { isV2: true } - cozy.create 'io.cozy.contacts', @attributes + cozy.create 'io.cozy.contacts', @toJSON() .then (resp) => @attributes = resp , () => @@ -134,7 +147,7 @@ module.exports = class Contact extends Backbone.Model _buildSortedName: (n) -> n ?= @get 'n' - sortKey = app.model.get 'sort' + sortKey = @AppViewModel?.get 'sort' [gn, fn, mn, ...] = n.split ';' _.chain if sortKey is 'fn' then [fn, mn, gn] else [gn, fn, mn] @@ -143,16 +156,6 @@ module.exports = class Contact extends Backbone.Model .value() - constructor: -> - app = require 'application' - super - - - initialize: -> - @listenTo app.model, 'change:sort': -> - @set 'sortedName': @_buildSortedName() - - toString: (opts = {}) -> [gn, fn, mn, pf, sf] = @attributes.n.split ';' # wrap given name (at index 0) in pre/post tags if provided diff --git a/app/views/contacts/components/xtras.coffee b/app/views/contacts/components/xtras.coffee index 86022f2..3d3931d 100644 --- a/app/views/contacts/components/xtras.coffee +++ b/app/views/contacts/components/xtras.coffee @@ -3,12 +3,12 @@ CONFIG = require('const-config').contact module.exports = class ContactXtrasView extends Mn.ItemView - tagName: -> - if @model.get 'edit' then 'fieldset' else 'ul' - template: require 'views/templates/contacts/components/xtras' + tagName: -> if @model.get 'edit' then 'fieldset' else 'ul' + + modelEvents: -> _.reduce CONFIG.xtras, (memo, prop) -> memo["change:#{prop}"] = (args...) -> @@ -31,4 +31,3 @@ module.exports = class ContactXtrasView extends Mn.ItemView ref = @model.get 'ref' field = _.keys(model.changed)[0] @$("##{field}-#{ref}").trigger 'focus' - diff --git a/app/views/contacts/index.coffee b/app/views/contacts/index.coffee index cbaf774..707d285 100644 --- a/app/views/contacts/index.coffee +++ b/app/views/contacts/index.coffee @@ -10,8 +10,12 @@ filtered = undefined module.exports = class Contacts extends Mn.CompositeView + template: require 'views/templates/contacts' + + sort: false + # backbone dom node dynamics are called before initialize, internal sets are # so unavailable, so fallback to root method for detection tagName: -> @@ -23,11 +27,11 @@ module.exports = class Contacts extends Mn.CompositeView else 'div' + attributes: -> {model} = require 'application' role: 'rowgroup' if model.get('scored') or @model?.has('char') - template: require 'views/templates/contacts' events: -> @@ -46,13 +50,13 @@ module.exports = class Contacts extends Mn.CompositeView el.appendChild buffer - buildChildView: (child) -> + buildChildView: (model) -> if @_hasContacts ChildView = ContactRowView else ChildView = @constructor - new ChildView model: child + new ChildView { model } showCollection: -> diff --git a/app/views/contacts/row.coffee b/app/views/contacts/row.coffee index c78938f..a3e88df 100644 --- a/app/views/contacts/row.coffee +++ b/app/views/contacts/row.coffee @@ -1,14 +1,15 @@ PATTERN = require('const-config').search.pattern 'text' Slugifier = require '../../lib/slugifier' -app = undefined module.exports = class ContactRow extends Backbone.View supportsRenderLifecycle: false + tagName: 'li' + attributes: role: 'row' @@ -17,14 +18,14 @@ module.exports = class ContactRow extends Backbone.View pre: '' post: '' + _isAvatarLoaded: false initialize: -> - app = require 'application' - + @AppViewModel = require('application').model @listenTo @model, 'change': @render - @listenTo app.model, 'change:selected': @refreshChecked + @listenTo @AppViewModel, 'change:selected': @refreshChecked render: -> @@ -50,13 +51,14 @@ module.exports = class ContactRow extends Backbone.View serializeData: -> _.extend @model.toJSON(), - selected: @model.id in app.model.get 'selected' + selected: @model.id in @AppViewModel.get 'selected' fullname: @model.toString @_format isAvatarLoaded: @_isAvatarLoaded + bg: ColorHash.getColor @model.get('n'), 'cozy' highlight: -> - filter = app.model.get('filter')?.match PATTERN + filter = @AppViewModel.get('filter')?.match PATTERN return unless filter fullname = @model.toHighlightedString filter[1], diff --git a/app/views/models/contact.coffee b/app/views/models/contact.coffee index b49a8f8..88ef612 100644 --- a/app/views/models/contact.coffee +++ b/app/views/models/contact.coffee @@ -3,8 +3,6 @@ Filtered = BackboneProjections.Filtered CONFIG = require('const-config').contact CH = require('lib/contact_helper') -app = null - module.exports = class ContactViewModel extends Backbone.ViewModel @@ -42,8 +40,14 @@ module.exports = class ContactViewModel extends Backbone.ViewModel proxy: ['toString', 'toHighlightedString', 'match'] - initialize: -> - app = require 'application' + config: + datapoints: CONFIG.datapoints.main + xtras: CONFIG.xtras + + + initialize: (options={}) -> + { model, contacts } = options + @app = { model, contacts } @_setRef() @@ -60,10 +64,11 @@ module.exports = class ContactViewModel extends Backbone.ViewModel data = super delete data.datapoints data.xtras = @getDatapoints('xtras').toJSON() - _.reduce CONFIG.datapoints.main, (memo, attr) => + _.reduce @config.datapoints, (memo, attr) => memo[attr] = @getDatapoints(attr).toJSON() return memo , data + data getMappedAvatar: (attachments) -> @@ -111,46 +116,48 @@ module.exports = class ContactViewModel extends Backbone.ViewModel getIndexKey: -> - sortIdx = if app.model.get('sort') is 'fn' then 0 else 1 + sortIdx = if @app.model.get('sort') is 'fn' then 0 else 1 initial = @get('initials')?[sortIdx]?.toLowerCase() or '' if /[a-z]/.test initial then initial else '#' onAddField: (type) -> - @set type, '' if type in CONFIG.xtras + @set type, '' if type in @config.xtras getDatapoints: (name) -> - filter = (datapoint) -> - if name in CONFIG.datapoints.main - datapoint.get('name') is name + isEdit = @attributes.edit + datapoints = @model.get 'datapoints' + datapointsConfig = @config.datapoints + + filter = (datapoint) => + datapointName = datapoint.get('name') + if name in @config.datapoints + return datapointName is name else - datapoint.get('name') not in CONFIG.datapoints.main + return datapointName not in datapointsConfig - if @attributes.edit - models = @model.get 'datapoints' - .filter filter - .map (model) -> model.clone() + if isEdit + models = datapoints.filter(filter).map((model) -> model.clone()) new Backbone.Collection models else - new Filtered @model.get('datapoints'), filter: filter + new Filtered datapoints, { filter } # Extract datapoints from the view model to store them in the data model. syncDatapoints: -> - # The getDatapoints function has been memoized, it means it keeps all # its returned values in a cache. # Here we deal directly with this cache because this function has # already been called for all fields and edited fields were already # marked. - cache = @getDatapoints.cache - datapoints = @model.get 'datapoints' + if (cache = @getDatapoints.cache) + datapoints = @model.get 'datapoints' + models = [] - # It grabs all datapoints that has been edited. - models = [] - for key, collection of cache.__data__ when /^edit/.test key - models = models.concat CH.getNonEmptyDatapoints collection + # It grabs all datapoints that has been edited. + for key, collection of cache.__data__ when /^edit/.test key + models = models.concat CH.getNonEmptyDatapoints collection # Load new datapoints in the data model. datapoints?.reset models @@ -179,7 +186,7 @@ module.exports = class ContactViewModel extends Backbone.ViewModel onSave: -> - app.contacts.add @model if @get 'new' + @app.contacts.add @model if @get 'new' onReset: -> @_setRef() diff --git a/app/views/templates/contacts/row.jade b/app/views/templates/contacts/row.jade index 81e1781..cb88c36 100644 --- a/app/views/templates/contacts/row.jade +++ b/app/views/templates/contacts/row.jade @@ -27,7 +27,6 @@ a.name(role="gridcell", href="contacts/#{_id}") - var src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" img.avatar(data-src=avatar, src=src) else - - var bg = ColorHash.getColor(n, 'cozy') span.avatar.initials(style="background-color:#{bg}")= initials p!= fullname