Created
December 22, 2016 17:55
-
-
Save diegomanuel/53675de7266ef70e0cad3ae0b41ed5cf to your computer and use it in GitHub Desktop.
Ejemplos de CoffeeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#=require usecases/app/undeleteObjectModal | |
#=require usecases/app/confirmModal | |
window.defaultCRUDApiValues = (crudName, hashName, extensions = {}) -> _.extend({ | |
create: | |
call: Api["create#{capitalize(crudName)}"] | |
url: "#{hashName}/agregar" | |
update: | |
call: Api["update#{capitalize(crudName)}"] | |
url: "#{hashName}/editar" | |
detail: | |
call: Api["detail#{capitalize(crudName)}"] | |
url: "#{hashName}/detalle" | |
search: | |
call: Api["search#{capitalize(crudName)}"] | |
url: "#{hashName}/buscar" # Not used.. | |
remoteUrl: Api.url[crudName]?.search() | |
}, extensions) | |
class window.CRUDModel extends DynamicModel | |
@_entity: "ModelName" # Should be overriden! | |
@api: # Should be overriden! | |
create: { call: (->), url: "" } | |
update: { call: (->), url: "" } | |
detail: { call: (->), url: "" } | |
delete: { call: (->), url: "" } | |
list: { call: (->), url: "" } | |
search: { call: (->), url: "" } | |
@detailLinkPermission: undefined | |
initialize: -> super | |
construct: -> super | |
entity: -> @constructor._entity | |
PK: -> parseInt @get "id" | |
isUpdating: -> @id > 0 | |
isCreating: -> !@isUpdating() | |
isPersistent: -> @isUpdating() | |
isDeleted: -> !!@get("deleted") | |
submitId: -> @attr undefined, "id" | |
iconizedStatus: -> | |
color = "gray" | |
title = App.keyToText "NO_DISPONIBLE" | |
if @get "deleted" | |
color = "white" | |
title = App.keyToText "STATUS_DELETED" | |
obj = CRUDView.getIconizedStatusObjectFromColor color | |
obj.attr "title", App.keyToText title | |
CRUDView.parseIconizedStatusObject obj | |
remove: ( func="remove" )-> | |
@collection[func](@) if @collection?[func]? | |
attr: ( createAttr, updateAttr )-> | |
attr = if @isCreating() then createAttr else updateAttr | |
@attributes[ attr ] | |
bindForm: ( aDeclarativeForm )-> | |
@form = aDeclarativeForm | |
hashCreateURL: -> @constructor.urlComponentCreate() | |
hashUpdateURL: -> @constructor.urlComponentUpdate @PK() | |
hashDetailURL: -> @constructor.urlComponentDetail @PK() | |
fetchDetailData: ( onSuccess )-> | |
success = ( response )=> | |
response = @onFetchDetailData response | |
onSuccess? response | |
@_fetchData "detail", success | |
onFetchDetailData: ( response )-> response | |
# By default, load detail data as form data | |
fetchFormData: ( onSuccess )-> | |
return if !(id = @get "id") | |
success = ( response )=> | |
response = @onFetchFormData response | |
onSuccess? response | |
@_fetchData "detail", success | |
onFetchFormData: ( response )-> response | |
# By default, load detail data when App.getParam "id" | |
fetchDataByID: ( onSuccess )-> | |
@fetchDetailData onSuccess | |
# Sets fetched data as model attributes | |
_fetchData: ( wich="detail", onSuccess )-> | |
if (apiCall = @constructor.api[wich]) | |
apiCall.call @get("id"), ( response )=> | |
response = @_fetchDataFormatter response | |
@set response | |
onSuccess? response | |
else | |
onSuccess? @attributes | |
_fetchDataFormatter: ( response )-> response | |
validateExistance: -> | |
indetifyKey = @form.identifyKeyModel() | |
entityApiName = @constructor.entityApiName.toLowerCase() | |
onExistanceResponse = (response)=> | |
if(response.status == "BORRADO") | |
apiCallFunction = (entityId, callback, options) => | |
callPostAPI( {url: Api.url[entityApiName].undelete(),data: {id: entityId}}, callback, handleError500, options) | |
new UndeleteObjectModal({apiCallFunction: apiCallFunction, hashToNavigate: @constructor.api.detail.url, entityId: response.id}).show() | |
callPostAPI( {url: Api.url[entityApiName].exists(),data: {idString: @get(indetifyKey)}}, onExistanceResponse, handleError500) | |
entityApiName: -> @constructor.entityApiName.toLowerCase() | |
permissionTagHeader: -> @constructor.permissionTagHeader | |
crudFeatures: -> | |
@constructor.crudFeatures() | |
executeDelete: -> | |
this.confirmEntityOperation(@doExecuteDelete, 'eliminar') | |
confirmEntityOperation: (operation, operationName) -> | |
new ConfirmModal({onConfirm: operation, title: "#{capitalize(operationName)} entidad", message: "¿Está seguro que desea #{operationName} la entidad?" }).show() | |
executeUndelete: -> | |
this.confirmEntityOperation(@doExecuteUndelete, 'recuperar') | |
doExecuteAction: (api, actionNameExecuted, event) -> | |
onSuccess = (response) => | |
App.logMessage("Entidad #{actionNameExecuted}") | |
this.trigger(event) | |
callPostAPI( {url: api,data: {id: @get('id')}}, onSuccess, handleError500) | |
doExecuteDelete: => | |
this.doExecuteAction(Api.url[@entityApiName()].delete(), 'eliminada', 'onDelete') | |
doExecuteUndelete: => | |
this.doExecuteAction(Api.url[@entityApiName()].undelete(), 'recuperada', 'onUndelete') | |
########### CLASS METHODS ########### | |
@crudFeatures: -> | |
crudFeatures[@entityApiName.toLowerCase()] || [] | |
@getInstance: ( options={} )-> # VERY important to be "->" | |
new @ options | |
@urlComponent: -> @_entity.toLowerCase() | |
@urlComponentCreate: -> @api.create.url | |
@urlComponentUpdate: ( id )-> "#{@api.update.url}?id=#{id}" | |
@urlComponentDetail: ( id )-> "#{@api.detail.url}?id=#{id}" | |
@parseDetailLink: ( id, text, permission )-> | |
return text unless id and text and User.can (permission || @detailLinkPermission) | |
href = @urlComponentDetail id | |
CRUDView.parseDetailLink {href, id, text} | |
@fetchMarkets: ( success=(->), error=(->) )=> | |
@fetch "markets", "getMarkets", "mercados", "mercados", success, error | |
@fetchCountries: ( success=(->), error=(->) )=> | |
@fetch "countries", "getCountries", "zonas", "países", success, error | |
@fetchRoles: ( success=(->), error=(->) )=> | |
@fetch "roles", "getRoles", "roles", "roles y permisos", success, error | |
@fetchRadios: ( success=(->), error=(->) )=> | |
@fetch "radios", "getRadiosCRUD", "radios", "radios", success, error | |
@fetchFleets: ( success=(->), error=(->) )=> | |
@fetch "radios", "getFleetsCRUD", "flotillas", "flotillas", success, error | |
@findRole: ( roleId, callback=null )=> | |
# @TODO DMC: Aplicar el uso opcional de callback, | |
# que sólo tendría sentido si los roles aun no fueron fetched, | |
# pero mejor forzarlo asi.. que pasen un callback que recibirá | |
# como parámetro el rol encontrado, sin importar si están | |
# cargados o no al momento de llamar este método! | |
if (roles = @::roles) and roles.length is 0 then throw "Roles were not loaded from server!" | |
for role in roles | |
return role if role.name is roleId | |
throw "The role '#{roleId}' was not found within defined roles!" | |
activo: -> | |
if @get("deleted") then "No" else "Sí" | |
# Creates instance properties from api call responses | |
@fetch: ( attr, apiGetter, key, wtf, success, error )=> | |
obj = @:: #@TODO DMC: Y si uso window o local storage?? | |
# Wait a little bit if we are getting data from server for given apiGetter | |
if @_fetching[apiGetter] | |
_fetch = => | |
@fetch attr, apiGetter, key, wtf, success, error | |
return setTimeout _fetch, 100 | |
# Tweaks for DeclarativeForm fetcher | |
#@TODO: No debería hacerse esto! | |
if success?.onSuccess? then success = success.onSuccess | |
else if !success then success = (->) | |
if error?.onError? then error = error.onError | |
else if !error then error = (->) | |
# Return if we already fetched the data! | |
return success obj[attr] if obj[attr] isnt undefined | |
# OK, fetch fresh data from server.. | |
onSuccess = ( rs )=> | |
@_fetching[apiGetter] = false | |
obj[attr] = (if key? then rs[key] else rs) | |
success rs[key] if success | |
onError = ( jqXHR )=> | |
@_fetching[apiGetter] = false | |
App.errorMessage "No se pudo cargar la lista de <b>#{wtf}</b>.<br/><br/>Intente nuevamente en unos instantes." | |
error jqXHR if error | |
@_fetching[apiGetter] = attr | |
Api[apiGetter] onSuccess, onError | |
@_fetching: {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#= require models | |
class window.CRUDView extends Backbone.View | |
@_entity: CRUDModel | |
#__$: @constructor | |
entity: -> @constructor._entity | |
genericEvents: | |
"click .cancel" : -> @constructor.goBack() | |
initialize: -> | |
super | |
@$el.attr "class", "#{@_className} #{@className}" | |
@events = _.extend @genericEvents, @viewEvents, @events | |
@preInit() | |
@doInit() | |
@postInit() | |
preInit: -> | |
postInit: -> | |
doInit: -> | |
#@TODO DMC: Revisar.. me gustaria que sea como doRender. | |
#Debería estar vacío y jamás tendría que ser necesario | |
#llamar "super" en ningún doAlgo(), ni init ni render, etc! | |
if ((id = App.getParam("id")) && !@options.embedded && (! (@options.model? && @options.model.get('id')) )) | |
@model = @newModel {id} # Important to assing to @model right here! | |
@model.fetchDataByID => @setModel @model, true | |
else | |
@model = (@options.model ? @newModel()) | |
@setModel @model | |
render: -> | |
super | |
@preRender() | |
@doRender() | |
@postRender() | |
@delegateEvents() | |
@$el | |
preRender: -> | |
postRender: -> | |
doRender: -> | |
delegateEvents: -> super # Does nothing, for now | |
remove: => | |
@mapClose() | |
super() | |
crudFeatures: -> | |
@entity().crudFeatures() | |
newModel: ( options={} )-> # @TODO DMC: Revisar uso de CRUDFormView.modelForm y CRUDListView.modelList | |
@entity().getInstance options | |
setModel: ( model, render=false )-> | |
@model = model | |
@render() if render | |
template: ( tpl=@tpl, view )-> | |
throw("No template defined!") unless tpl and view | |
"templates/#{tpl}/#{view}" | |
templateView: ( view )-> | |
@template @tpl, view | |
parseView: ( view, opts={} )-> | |
@parse @templateView(view), opts | |
parseModule: ( opts={} )-> | |
@parse @template(), opts | |
parse: ( view, opts )-> | |
opts = _.extend @model.toJSON(), {@model}, opts | |
JST[view] opts | |
showDetailView: ( object, hash=@navHashDetailView(object))-> | |
#viewsCommunication.set "objectForCRUDDetailView", object | |
#object.fetchDetailData ->App.navigator.navigateToHash hash, {id: object.get('id')} | |
App.navigator.navigateToHash hash, {id: object.get('id')} | |
showFormView: ( object, hash=@navHashFormView(object))-> | |
#viewsCommunication.set "objectForCRUDFormView", object | |
#object.fetchFormData ->App.navigator.navigateToHash hash, {id: object.get('id')} | |
App.navigator.navigateToHash hash, {id: object.get('id')} | |
navHashDetailView: ( object )-> | |
#@TODO DMC: Este método no debería existir! | |
@entity().api.detail.url | |
navHashFormView: ( object )-> | |
#@TODO DMC: Este método no debería existir! | |
what = if object.isUpdating() then "update" else "create" | |
@entity().api[what].url | |
sendMessage: ( options={} )-> | |
options = _.extend { | |
#entityId | |
title: "Enviar Mensaje" | |
addApi: null | |
}, options | |
new SendMessageModal options | |
createMap: ( container )=> | |
@map = new EcoTaxiGMap container[0] | |
@map.reposition = true | |
@map.parseInfoWindow = @infoWindowMap | |
@map | |
updateMap: ( rs )=> # Should be overriden! | |
@mapUpdate [] | |
infoWindowMap: ( gMarker, marker )=> | |
if @constructor.infoWindowMapVars? # The view class should have a class method @infoWindowMapVars defined! | |
iwinvars = @constructor.infoWindowMapVars gMarker, marker | |
return @map.renderInfoWindow iwinvars | |
null | |
refreshMap: ( callback=@updateMap, apiFunc=@entity().api.mapLocations )=> | |
return unless @map? | |
return if !@map.container.is ":visible" # FIX HORRENDOOOOOOOOO...!!! | |
### | |
App.showLoading @map.container | |
success = ( rs )=> | |
App.hideLoading @map.container | |
callback rs | |
apiFunc?.call success | |
### | |
apiFunc?.call callback | |
mapOpen: ( hide=null )=> | |
@createMap() unless @map? | |
refresh = @refreshMap | |
if hide? then hide.fadeOut =>@map.container.fadeIn ->refresh() | |
else @map.container.fadeIn ->refresh() | |
mapClose: ( show=null )=> | |
return unless @map? | |
if show? then @map.container.fadeOut ->show.fadeIn() | |
else @map.container.hide() | |
clearTimeout @mapUpdateTimer if @mapUpdateTimer? | |
@mapUpdateTimer = null | |
mapUpdate: ( markers, timeout=10000 )=> | |
return unless @map? | |
return if !@map.container.is ":visible" # FIX HORRENDOOOOOOOOO...!!! | |
@map.setMarkers markers, @map.reposition | |
@map.reposition = markers?.length is 1 | |
clearTimeout @mapUpdateTimer if @mapUpdateTimer? | |
if @mapUpdateTimer isnt false | |
func = @refreshMap | |
@mapUpdateTimer = setTimeout func, timeout | |
back: ( times=1 )-> | |
@constructor.goBack times | |
########### CLASS METHODS ########### | |
@getInstance: ( options={} )-> # VERY important to be "->" | |
new @ options | |
@include: ( obj )-> | |
throw("CRUDView.include(obj) requires obj") unless obj | |
for key, value of obj.prototype when key not in ["included", "extended"] | |
@::[key] = value | |
(included=obj.included).apply(this) if included | |
@ | |
@goBack: ( times=1 )-> | |
obj = | |
times: times | |
back: -> history.back() if @times-- <= 1 | |
obj.back() if times is 1 | |
obj | |
@parseDetailLink: ( opts={} )-> | |
dataID = if opts.id? then "data-id=\"#{opts.id}\"" else "" | |
href = if opts.href? then "href=\"##{opts.href}\"" else "" | |
tag = if href.length then "a" else "span" | |
"<#{tag} #{dataID} #{href} class=\"link #{(opts.css||"")}\" title=\"#{(opts.title||"Abrir detalles..")}\">#{opts.text}</#{tag}>" | |
@parseIconizedStatus: ( status )-> | |
@parseIconizedStatusColor @getIconizedColorFromStatus status | |
@parseIconizedStatusColor: ( color )-> | |
return "" if !color | |
@parseIconizedStatusObject @getIconizedStatusObjectFromColor color | |
@parseIconizedStatusObject: ( obj )-> | |
#obj.wrap("<div/>").parent().html() | |
obj[0].outerHTML | |
@getIconizedObjectFromStatus: ( status )-> | |
@getIconizedStatusObjectFromColor @getIconizedColorFromStatus status | |
@getIconizedStatusObjectFromColor: ( color="unknown" )-> | |
$("<div/>").addClass "iconizedStatus #{color}Status" | |
@getIconizedColorFromStatus: ( status )-> | |
status = status.toLowerCase() | |
if status in ["online","libre","ocupado"] then color = "green" | |
else if status in ["offline","no_disponible"] then color = "gray" | |
else if status in ["suspendido","suspended"] then color = "red" | |
else color = "white" | |
color | |
@__elFromEv: ( ev )=> | |
$(ev.target).parent() | |
# Create alias "@__" for "@constructor" to call statics | |
#Object.defineProperty @::, '__', get:->@constructor | |
#@::$$ = @constructor | |
#@__ = @constructor | |
#__: @constructor |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### | |
# DMC NOTA MENTAL: Las vistas que necesitan extender de CRUDDetailView | |
# es por que tienen alguna particularidad que no puede ser resuelta | |
# de forma genérica por CRUDDetailView con su CRUDModel asociado. | |
# CRUDDetailView tendría que ser capaz de manejar TODO con sólo un CRUDModel. | |
### | |
class window.CRUDDetailView extends CRUDView | |
_className: "detailView" | |
formClass: "Subclasses should override" #@TODO DMC: Volar esto. | |
viewEvents: | |
'click .edit' : 'showEdit' | |
'click .delete': -> @model.executeDelete() | |
'click .undelete': -> @model.executeUndelete() | |
postInit: -> | |
#if (model = viewsCommunication.get "objectForCRUDDetailView") | |
# @setModel model, true | |
@model.on "onDelete", -> CRUDView.goBack() | |
@model.on "onUndelete", => | |
@model.fetchDetailData( => @render()) | |
if ! @model?.isPersistent() | |
#TODO: SUPER HACK. Remover cuando se cambie el navigator por algo más poderoso. La navegación debería llamar a | |
#algún otro hook además de render, así no se pisan funcionalidades de render con navegación, por ejemplo | |
setTimeout (->CRUDView.goBack()), 500 | |
doRender: -> | |
@$el.html @parseModule() | |
@renderFeatures() | |
renderFeatures: -> | |
for feature in this.crudFeatures() when _.isFunction(this["render_#{feature}"]) | |
this["render_#{feature}"]() | |
render_notes: -> | |
if User.can("#{this.permissionsPrefix()}_NOTAS") | |
@$(".notesContainer").html @parseNotes() | |
render_files: -> | |
if User.can("#{this.permissionsPrefix()}_ARCHIVOS") | |
@$(".filesContainer").html @parseFiles() | |
render_log: -> | |
if User.can("#{this.permissionsPrefix()}_EVENTOS") | |
@$(".logContainer").html @parseLog() | |
render_currentAccount: -> | |
if User.can("#{this.permissionsPrefix()}_VER_CUENTA_CORRIENTE") | |
@$(".currentAccountContainer").html @parseCurrentAccount() | |
permissionsPrefix: -> | |
#TODO: Quizás queremos mover esto al CrudModel, pero como por ahora sólo se usa en el details... que quede aquí | |
throw "Subclasses should override" | |
parseCurrentAccount: ( opts )-> | |
opts = _.extend {holder:@model}, opts | |
view = new CurrentAccountView opts | |
view.render() | |
view.$el | |
parseNotes: ( opts )-> | |
opts = _.extend {entity:@model}, opts | |
view = new NotesView opts | |
view.render() | |
view.$el | |
parseFiles: ( opts )-> | |
opts = _.extend {entity:@model}, opts | |
view = new FilesView opts | |
view.render() | |
view.$el | |
parseLog: ( opts )-> | |
opts = _.extend {entityId: @model.get("id"), apiCall: "#{@model.entityApiName()}Log" }, opts | |
view = new EntityLogView opts | |
view.render() | |
view.$el | |
template: ( tpl=@tpl, view="detail_view" )-> | |
super tpl, view | |
createFormView: ( options )-> | |
new @formClass options | |
showEdit: -> | |
form = @createFormView {model:@model} | |
form.render() | |
App.navigator.changePage form | |
showForResponse: ( response, show=(->) )=> | |
#view = @constructor.getInstance {response, show} | |
@options.response = response | |
@options.show = show | |
@render() | |
showForID: ( id, show=(->), success=@showForResponse )=> | |
#@TODO DMC: Este método debería ser de clase.. | |
onSuccess = ( response )=> | |
success response, show | |
onError = ( jqXHR )=> | |
App.errorMessage App.keyToText jqXHR.getResponseHeader("exception-key") | |
@entity().api.detail.call id, onSuccess, onError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#= require fwk/views/declarativeForm | |
### | |
# DMC NOTA MENTAL: Las vistas que necesitan extender de CRUDFormView | |
# es por que tienen alguna particularidad que no puede ser resuelta | |
# de forma genérica por CRUDFormView con su CRUDModel asociado. | |
# CRUDFormView tendría que ser capaz de manejar TODO con sólo un CRUDModel. | |
### | |
class window.CRUDFormView extends CRUDView | |
_className: "formView" | |
fieldDefinitions: [] # Should be overriden! | |
submitableProperties: [] # Should be overriden! | |
viewEvents: | |
"click .save": "save" | |
postInit: -> | |
embedded = @options.embedded | |
@form = new DeclarativeForm {@fieldDefinitions,@template,embedded} | |
doRender: -> | |
@$el.html @form.el | |
@form.bindTo @model | |
@model.bindForm @form # This binding is done here and NOT | |
# inside DeclarativeForm because we just want to do it for CRUD! | |
@addCheckExistanceEvent() | |
@form.render() | |
this.delegateEvents() | |
addCheckExistanceEvent: -> | |
identifyKeyField = @form.identifyKeyField() | |
if identifyKeyField | |
@events["change :not(.nested) ##{identifyKeyField}"] = -> @model.validateExistance() | |
delegateEvents: -> | |
super | |
@form.delegateEvents() if @form? | |
remove: -> | |
super | |
@form.remove() if @form? | |
template: ( tpl=@tpl, view="form_view" )-> | |
super tpl, view | |
modelForm: # Should be overriden! @TODO DMC: Esto va a desaparecer! | |
model: @constructor._entity # @TODO DMC: Ver qué onda esto. | |
url: null | |
validate: ( callback )=> | |
#__log callback | |
@form.validate callback | |
getErrors: -> | |
@form.errors | |
save: => | |
@validate ( validObject )=> | |
objectToSubmit = this.getSubmitableModel(validObject) | |
this.preSubmit(validObject, objectToSubmit) | |
@doSave validObject, objectToSubmit | |
getSubmitableModel: (validObject) -> | |
if(!validObject?) | |
validObject = @model | |
objectToSubmit = new Backbone.Model() | |
for property in @getSubmitableProperties() when (property.model?=property.field) and validObject.has(property.model) | |
objectToSubmit.set property.field, validObject.get(property.model) | |
#__log objectToSubmit, validObject | |
this.prepareForSubmit(validObject, objectToSubmit) | |
objectToSubmit | |
getSubmitableProperties: -> | |
propertyList = for property in @submitableProperties | |
if typeof property == 'string' then {field: property, model: property} else property | |
@calculateSubmitableProperties(propertyList) | |
calculateSubmitableProperties: (propertyList) -> | |
skippedProperties = @skipSubmitableProperties() | |
propertyList.filter (property) -> not (property.field in skippedProperties) | |
skipSubmitableProperties: -> [] | |
prepareForSubmit: (validObject, objectToSubmit) -> #for subclasses to override | |
preSubmit: (validObject, objectToSubmit) -> #for subclasses to override | |
postSubmitSuccess: -> | |
@constructor.goBack 1 | |
doSave: ( validObject, objectToSubmit )=> | |
func = if validObject.isCreating() then @doCreate else @doUpdate | |
func validObject, objectToSubmit | |
doCreate: ( validObject, objectToSubmit )=> | |
@entity().api.create.call { | |
model: objectToSubmit | |
onSuccess: ( response ) => | |
# @TODO Internacionalizar! | |
App.logMessage validObject.entity()+" creado correctamente" | |
this.setModel validObject | |
this.postSubmitSuccess response | |
} | |
doUpdate: ( validObject, objectToSubmit )=> | |
@entity().api.update.call { | |
model: objectToSubmit | |
onSuccess: ( response )=> | |
# @TODO Internacionalizar! | |
App.logMessage validObject.entity()+" actualizado correctamente" | |
this.setModel validObject | |
this.postSubmitSuccess response | |
} | |
deletePhoto: ( apiFunc )-> | |
onSuccess = => | |
App.logMessage("La foto se eliminó correctamente") | |
@form.$('.currentPhotoImg').attr 'src', noPhotoURL | |
@form.$('.currentPhoto').hide() | |
onConfirm = => | |
apiFunc( { | |
id: @model.get('id') | |
success: onSuccess | |
error: -> App.errorMessage("No se pudo eliminar la foto") | |
}) | |
new ConfirmModal({onConfirm, title:"Confirmar eliminación de foto", message:"¿Está seguro que desea eliminar la foto?"}).show() | |
########### CLASS METHODS ########### | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#= require fwk/views/tableView | |
class window.CRUDListView extends CRUDView | |
_className: "listView" | |
itemsPerPage: 25 | |
itemsMaxPages: 20 | |
viewEvents: | |
"keyup .quick-search": "doQuickSearch" | |
"click .quick-search": (e)->$(e.target).select() | |
"click .quick-search-clear": "doQuickSearchClear" | |
#@TODO DMC: xq esto acá y tb en parseTable? (si no, NO funciona) | |
"rowclick": "doRowClick" | |
columnDefinitions: | |
none: { title: 'None', type: ColumnTypes.none } | |
doInit: -> | |
@loadConfig() | |
@createSoftDeleteModel() | |
@objects = @createCollection() | |
@objects.on('prefetch', => this.saveConfig()) | |
@fetch() | |
fetch: -> | |
@objects.softDeleteFilter = @softDeleteModel?.get('filter') | |
if @config.searchTerms | |
@doSearch(@config.searchTerms, @config.currentPage) | |
else | |
@objects.fetchSorted @config.sortBy, {ascendant: @config.ascendant}, @config.currentPage | |
bindSoftDeleteField: -> | |
@softDeleteField?.bindTo(@softDeleteModel) | |
createSoftDeleteModel: -> | |
@softDeleteModel = new Backbone.Model({filter: @config.softDeleteFilter}) | |
@softDeleteModel.on('change', => | |
@objects.softDeleteFilter = @softDeleteModel?.get('filter') | |
@doQuickSearchClear() | |
) | |
softDeleteFilterWidget: ( title=null )-> | |
widget = $(JST['templates/soft_delete_filter']({title})) | |
@softDeleteField = new SelectField( | |
container: widget.find('.soft-select-container') | |
fieldId: 'filter' | |
definition: | |
type: FieldTypes.select | |
optionParams: {"id","title"} | |
values: [{id:'ACTIVOS',title:'A'},{id:'BORRADOS',title:'B'},{id:'TODOS',title:'T'}] | |
) | |
@softDeleteField.render() | |
widget | |
saveConfig: -> | |
@config.currentPage = @objects.currentPage | |
@config.sortBy = @objects.sortedBy | |
@config.ascendant = @objects.ascendant | |
@config.itemsPerPage = @objects.itemsPorPagina | |
@config.searchTerms = @objects.searchTerms | |
@config.softDeleteFilter = @softDeleteModel?.get('filter') | |
localStorage.setItem(@configKey(), JSON.stringify(@config)) | |
loadConfig: -> | |
@config = _.defaults @storedConfig(), @defaultConfig() | |
storedConfig: -> | |
configString = localStorage.getItem(@configKey()) | |
if configString then JSON.parse(configString) else {} | |
configKey: -> | |
"#{@constructor.name}_#{@entity().entityApiName}_listConfig" | |
defaultConfig: -> | |
if typeof @modelList.sortBy is "string" | |
sortBy = @modelList.sortBy | |
ascendant = true | |
else if @modelList.sortBy.field? | |
sortBy = @modelList.sortBy.field | |
ascendant = ((@modelList.sortBy.order||"ASC").toLowerCase().substr(0,1) is "a") | |
currentPage = if @modelList.currentPage? then @modelList.currentPage else 1 | |
softDeleteFilter = if @modelList.softDeleteFilter? then @modelList.softDeleteFilter else "ACTIVOS" | |
{sortBy, @itemsPerPage, currentPage, ascendant, softDeleteFilter} | |
createCollection: ( options={}, collClass=CRUDListViewCollection )-> | |
values = [] | |
if options.values? # Collection values given! | |
values = options.values | |
delete options.values | |
opts = | |
model: @entity() | |
url: @modelList.url # @TODO DMC: Volar @modelList de una vez! | |
itemsPorPagina: @config.itemsPerPage | |
maxPaginas: @itemsMaxPages | |
opts = _.extend opts, options | |
new collClass values, opts | |
template: ( tpl=@tpl, view="list_view" )-> | |
super tpl, view | |
doRender: ( template=@template() )-> | |
quickSearchContainer = JST["templates/crud/list_view_search"]() | |
@$el.html JST[template] {view:@,quickSearchContainer} | |
@$('.quick-search').val @config.searchTerms | |
@tableView = @parseTable() | |
@tableView.render() | |
@bindSoftDeleteField() | |
@$el | |
buildColumnDefinitions: -> | |
if $.isFunction(@columnDefinitions) then @columnDefinitions() else @columnDefinitions | |
parseTable: ( values=@objects, columnDefinitions=@buildColumnDefinitions(), el=@$(".table")[0] )-> | |
table = new EmbellishedTableView {el, columnDefinitions, values} | |
#@TODO DMC: xq esto acá y tb en viewEvents? (si no, NO funciona) | |
table.on "rowclick", @doRowClick | |
table | |
showLoading: -> | |
App.showLoading(@tableView?.el) | |
hideLoading: -> | |
App.hideLoading(@tableView?.el) | |
doQuickSearch: ( ev )=> | |
return true unless ev.keyCode is 13 | |
return App.showError "Búsqueda no soportada." if !@entity().api.search? | |
input = $ ev.target | |
this.doSearch(input.val()) | |
doSearch: (terms, page) -> | |
@showLoading() | |
@objects.search( terms, page, => | |
@$(".quick-search-container .iconizedButtonWidget").show() | |
@hideLoading() | |
) | |
doQuickSearchClear: ( ev )=> | |
@objects.clearSearch(); | |
@$(".quick-search-container input").val "" | |
@$(".quick-search-container .iconizedButtonWidget").hide() | |
@render() | |
doRowClick: ( rowObject )=> | |
@showDetailView rowObject | |
doOpenMap: ( ev )=> | |
ev.preventDefault() | |
$(ev.target).fadeOut =>@$(".actions .close-map").fadeIn() | |
@mapOpen @$(".table") | |
doCloseMap: ( ev )=> | |
ev.preventDefault() | |
$(ev.target).fadeOut =>@$(".actions .open-map").fadeIn() | |
@mapClose @$(".table") | |
class window.CRUDListViewCollection extends EmbellishedCollection | |
defaultData: ( options )-> | |
data = super options | |
if @searchTerms then data.terminos = @searchTerms | |
if @softDeleteFilter?.id then data.estado = @softDeleteFilter.id | |
data | |
setDefaults: (options) -> | |
super | |
if(@searchTerms) then options.url = @model.api.search.remoteUrl | |
search: (terms, page, options) -> | |
if options && !options.success then options = {success: options} | |
this.searchTerms = terms | |
this.fetchPerPage(page, options) | |
clearSearch: -> | |
this.searchTerms = null | |
this.fetchPerPage(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Any component that understands renderPage(pageNumber) can be paginated with this widget. | |
class Paginator extends Backbone.View | |
events: | |
'change .amountPerPage' : 'amountPerPageChange' | |
'click .nextPage' : 'nextPageClick' | |
'click .prevPage' : 'previousPageClick' | |
'click .page' : 'especifiedPageClick' | |
initialize: -> | |
# The currently selected page (1-based). | |
@currentPage = @options.currentPage ? 1 | |
@changePerPage = @options.changePerPage ? true | |
@friendly = @options.friendly ? true | |
@list = @options.list | |
amountPerPageChange: (e)=> | |
el = $ e.target | |
val = 0 if (val=el.val()) is "all" | |
@list.setItemsPerPage parseInt val | |
@renderPage 1, el, =>@render() | |
nextPageClick: (e)=> | |
el = $ e.target | |
@currentPage = Math.min @pageCount(), @currentPage + 1 | |
@renderPage @currentPage, el | |
previousPageClick: (e)=> | |
el = $ e.target | |
@currentPage-- if @currentPage isnt 1 | |
@renderPage @currentPage, el | |
especifiedPageClick: (e)=> | |
el = $ e.target | |
pageNumber = parseInt el.data('pagenum') | |
return unless pageNumber > 0 | |
@currentPage = pageNumber | |
@renderPage @currentPage, el | |
hasPage:(pageNumber)=> | |
1 <= pageNumber && pageNumber <= @pageCount() | |
pageCount: -> | |
return 1 if (perPage=@list.getItemsPerPage()) is 0 | |
Math.ceil @list.itemCount() / perPage | |
getRange: -> | |
threshold = 0 # Distance from middle, to activate "friendly" mode | |
max = (@list.getMaxPages?()) or 10 | |
from = ((Math.ceil(@currentPage/max)-1) * max) + 1 | |
if @friendly and @currentPage-threshold > (mid=Math.ceil(max/2)) | |
from = Math.max 1, @currentPage-mid | |
to = Math.min (from+max-1), @pageCount() | |
{from,to} | |
renderPage: (pageNum, el, callback=(->)) -> | |
App.showLoading el | |
success = (rs) => | |
App.hideLoading el | |
@currentPage = pageNum | |
callback rs | |
@list.renderPage pageNum, {success} | |
renderAt: (anElement) -> | |
this.setElement(anElement) | |
this.render() | |
render: => | |
@$el.html "" | |
return if (perPage=@list.getItemsPerPage()) > 0 and perPage >= @list.itemCount() | |
if @changePerPage | |
@$el.html "Ítems/Página" | |
@$el.append @parsePerPageSelect() | |
return if (pageCount=@pageCount()) <= 1 and @currentPage == 1 | |
if pageCount > 0 and @currentPage > 1 | |
prev = '<button class="pageChanger prevPage">prev</button>' | |
else | |
prev = '<button class="pageChanger prevPage" disabled="disabled">prev</button>' | |
@$el.append prev | |
if pageCount | |
range = @getRange() | |
for idx in [range.from..range.to] | |
if @currentPage is idx | |
button = "<button data-pagenum='#{idx}' class='pageChanger page pageNumber active' disabled='disabled'>#{idx}</button>" | |
else | |
button = "<button data-pagenum='#{idx}' class='pageChanger page pageNumber'>#{idx}</button>" | |
@$el.append button | |
#To ensure that the current page button is shown even if the page is not existent | |
unless @hasPage @currentPage | |
button = "<button data-pagenum='#{@currentPage}' class='pageChanger page pageNumber active' disabled='disabled'>#{@currentPage}</button>" | |
@$el.append button | |
if @hasPage @currentPage+1 | |
next = '<button class="pageChanger nextPage">next</button>' | |
else | |
next = '<button class="pageChanger nextPage" disabled="disabled">next</button>' | |
@$el.append next | |
parsePerPageSelect: -> | |
sel = $("<select/>").addClass "amountPerPage" | |
total = parseInt @list.itemCount() | |
curr = parseInt @list.getItemsPerPage() | |
parseOptionCheck = ( amount, text=amount )=> | |
if curr isnt amount and amount <= total | |
sel.append parseOption amount, text | |
parseOption = ( val, text=val )=> | |
$("<option/>").val(val).text(text) | |
if curr is 0 | |
sel.html parseOption curr, "Todos" | |
else | |
sel.html parseOption curr | |
sel.append $("<option/>").prop("disabled",true).text "------" | |
parseOptionCheck 5 | |
parseOptionCheck 10 | |
parseOptionCheck 25 | |
parseOptionCheck 50 | |
parseOptionCheck 100 | |
parseOptionCheck 200 | |
sel | |
window.Paginator = Paginator |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment