index.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { getMethods, checkForAlpine } from './utils'
  2. import { createObservable } from './observable'
  3. const Spruce = {
  4. stores: {},
  5. persisted: [],
  6. subscribers: [],
  7. watchers: {},
  8. disableReactivity: false,
  9. start() {
  10. this.attach()
  11. this.stores = createObservable(this.stores, {
  12. set: (target, key, value) => {
  13. if (this.disableReactivity) {
  14. return
  15. }
  16. this.updateSubscribers()
  17. this.runWatchers(this.stores, target, key, value)
  18. this.disableReactivity = true
  19. try {
  20. this.persisted.forEach(this.updateLocalStorage.bind(this))
  21. } catch (e) {
  22. // Do nothing here (thanks Safari!)
  23. }
  24. this.disableReactivity = false
  25. }
  26. })
  27. },
  28. attach() {
  29. if (! checkForAlpine()) {
  30. throw new Error('[Spruce] You must be using Alpine >= 2.5.0 to use Spruce.')
  31. }
  32. const self = this
  33. window.Alpine.addMagicProperty('store', el => {
  34. self.subscribe(el)
  35. return self.stores
  36. })
  37. },
  38. store(name, state, persist = false) {
  39. if (typeof state === 'function') {
  40. state = state()
  41. }
  42. if (persist) {
  43. try {
  44. this.stores[name] = this.retrieveFromLocalStorage(name, getMethods(state))
  45. if (!this.persisted.includes(name)) {
  46. this.persisted.push(name)
  47. }
  48. } catch (e) {
  49. // Do nothing here (thanks Safari!)
  50. }
  51. }
  52. if (!this.stores[name]) {
  53. this.stores[name] = state
  54. }
  55. return this.stores[name]
  56. },
  57. reset(name, state) {
  58. this.stores[name] = state
  59. },
  60. subscribe(el) {
  61. if (!this.subscribers.includes(el)) {
  62. this.subscribers.push(el)
  63. }
  64. return this.stores
  65. },
  66. updateSubscribers() {
  67. this.subscribers.filter(el => !!el.__x).forEach(el => {
  68. el.__x.updateElements(el)
  69. })
  70. },
  71. retrieveFromLocalStorage(name, methods = {}) {
  72. const storage = JSON.parse(window.localStorage.getItem(`__spruce:${name}`))
  73. return storage ? Object.assign(methods, storage) : null
  74. },
  75. updateLocalStorage(name) {
  76. window.localStorage.setItem(`__spruce:${name}`, JSON.stringify(this.store(name)))
  77. },
  78. watch(name, callback) {
  79. if (!this.watchers[name]) {
  80. this.watchers[name] = []
  81. }
  82. this.watchers[name].push(callback)
  83. },
  84. runWatchers(stores, target, key, value) {
  85. const self = this
  86. if (self.watchers[key]) {
  87. return self.watchers[key].forEach(callback => callback(value))
  88. }
  89. Object.keys(self.watchers)
  90. .filter(watcher => watcher.includes('.'))
  91. .forEach(fullDotNotationKey => {
  92. let dotNotationParts = fullDotNotationKey.split('.')
  93. if (key !== dotNotationParts[dotNotationParts.length - 1]) return
  94. dotNotationParts.reduce((comparison, part) => {
  95. if (comparison[key] === target[key] || Object.is(target, comparison)) {
  96. self.watchers[fullDotNotationKey].forEach(callback => callback(value))
  97. }
  98. return comparison[part]
  99. }, stores)
  100. })
  101. }
  102. }
  103. window.Spruce = Spruce
  104. const deferrer = window.deferLoadingAlpine || function (callback) { callback() }
  105. window.deferLoadingAlpine = function (callback) {
  106. window.Spruce.start()
  107. deferrer(callback)
  108. }
  109. export default Spruce