neft
Version:
Universal Platform
347 lines (237 loc) • 8.05 kB
text/coffeescript
# Dict
Access it with:
```javascript
const { Dict } = Neft
```
'use strict'
utils = require 'src/utils'
assert = require 'src/assert'
signal = require 'src/signal'
module.exports = class Dict extends signal.Emitter
@__name__ = 'Dict'
@__path__ = 'Dict'
KEYS = 1<<0
VALUES = 1<<1
ITEMS = 1<<2
ALL = (ITEMS<<1) - 1
## *Dict* Dict.fromJSON(*String*|*Object* json)
Returns a new *Dict* based on the given *json*.
Example:
```javascript
const dict = Dict.fromJSON('{"param": "value"}');
dict.param; // value
```
@fromJSON = (json) ->
json = utils.tryFunction JSON.parse, JSON, [json], json
assert.isPlainObject json, "Dict.fromJSON cannot parse json, #{json} given"
new Dict json
NOT_ENUMERABLE = utils.CONFIGURABLE | utils.WRITABLE
utils.defineProperty @::, 'constructor', NOT_ENUMERABLE, Dict
## Dict::constructor([*Object* data])
Creates a new dict instance.
The given *data* parameter determines default keys with their values.
Example:
```javascript
var data = new Dict({
name: 'xyz'
});
data.name; // xyz
```
constructor: (obj) ->
# support no `new` syntax
unless @ instanceof Dict
return new Dict obj
if obj?
assert.isObject obj, """
Dict can be called with no argument or an object, but #{obj} given
"""
assert.operator arguments.length, "<=", 1, """
Dict must be called with zero or one argument
"""
super()
utils.defineProperty @, '_signals', utils.WRITABLE, @_signals
utils.defineProperty @, '_keys', utils.WRITABLE, null
utils.defineProperty @, '_values', utils.WRITABLE, null
utils.defineProperty @, '_items', utils.WRITABLE, null
utils.defineProperty @, '_dirty', utils.WRITABLE, ALL
if utils.isObject(obj)
utils.merge @, obj
## ReadOnly *Integer* Dict::length
Returns an amount of keys stored in this *Dict*.
This property cannot be changed manually.
Example:
```javascript
var dict = Dict({ prop: 1 });
dict.length; // 1
```
desc = NOT_ENUMERABLE
utils.defineProperty @::, 'length', desc, ->
@keys().length
, null
## *Signal* Dict::onChange(*String* key, *Any* oldValue)
Signal called on each key value change.
Example:
```javascript
var user = new Dict({
country: 'Germany'
});
user.onChange.connect(function(key, oldVal){
console.log(key + " property changed from " + oldVal + " to " + this[key]);
});
user.set('country', 'US');
// country property changed from Germany to US
```
signal.Emitter.createSignal @, 'onChange'
## *Any* Dict::set(*String* key, *Any* value)
Sets the given value under the given key in this *Dict*.
The given value can't be an undefined. Use `pop()` instead.
Calls `onChange()` signal.
Example:
```javascript
var links = new Dict({
facebook: 'https://facebook.com/neft.io',
twitter: 'https://twitter.com/neft_io'
});
links.onChange.connect(function(key, oldVal){
console.log(key + " changed from " + oldVal + " to " + this[key]);
});
links.set('googlePlus', 'https://plus.google.com/+NeftIo-for-apps/');
// googlePlus changed from undefined to https://...
```
utils.defineProperty @::, 'set', NOT_ENUMERABLE, (key, val) ->
assert.isString key, "Dict.set key needs to be a string, but #{key} given"
assert.notLengthOf key, 0, "Dict.set key cannot be an empty string"
assert.isNot val, undefined, """
Dict.set value cannot be an undefined; \
undefined is reserved value for not existing keys; \
use null instead
"""
oldVal = @[key]
# break if value didn't change
if oldVal is val
return val
# update value
@[key] = val
# dirty
@_dirty |= ALL
# signal
@onChange.emit key, oldVal
val
## *Boolean* Dict::has(*String* key)
Returns `true` if the given key exists in this *Dict*.
utils.defineProperty @::, 'has', NOT_ENUMERABLE, (key) ->
assert.isString key, "Dict.has key needs to be a string, but #{key} given"
assert.notLengthOf key, 0, "Dict.has key cannot be an empty string"
@[key] isnt undefined
## *Dict* Dict::extend(*Object*|*Dict* object)
Sets all keys with their values from the given object into this *Dict*.
Calls `onChange()` signal for each given key.
utils.defineProperty @::, 'extend', NOT_ENUMERABLE, (obj) ->
assert.isObject obj, "Dict.extend argument needs to be an object, but #{obj} given"
for key, val of obj
if obj.hasOwnProperty(key)
@set key, val
@
## *Any* Dict::pop(*String* key)
Removes the given key from this *Dict*.
The given key must exists.
Calls `onChange()` signal.
Example:
```javascript
var data = new Dict;
data.set('name', 'John');
data.onChange.connect(function(key, oldVal){
if (this[key] === undefined){
console.log(key + " property has been removed");
}
});
data.pop('name');
// name property has been removed
```
utils.defineProperty @::, 'pop', NOT_ENUMERABLE, (key) ->
assert.isString key, "Dict.pop key must be a string, but #{key} given"
assert.notLengthOf key, 0, "Dict.pop key cannot be an empty string"
assert.isNot @[key], undefined, """
Dict.pop cannot be called on not existing key, #{key} given
"""
oldVal = @[key]
delete @[key]
# dirty
@_dirty |= ALL
# signal
@onChange.emit key, oldVal
oldVal
## Dict::clear()
Removes all stored keys from this *Dict*.
Calls `onChange()` signal for each stored key.
utils.defineProperty @::, 'clear', NOT_ENUMERABLE, ->
for key, val of this
@pop key
return
## ReadOnly *Array* Dict::keys()
Returns an array of keys stored in this *Dict*.
Returned array is *read only* and cannot be modified.
Always returns the same array instance.
Example:
```javascript
var data = new Dict({
x: 10,
y: 30
});
data.keys(); // ['x', 'y']
```
utils.defineProperty @::, 'keys', NOT_ENUMERABLE, ->
if @_dirty & KEYS
@_dirty ^= KEYS
arr = @_keys ?= []
i = 0
for key, val of @
arr[i] = key
i++
arr.length = i
@_keys
## *Array* Dict::values()
Returns an array of values stored in this *Dict*.
Returned array is *read only* and cannot be modified.
Always returns the same array instance.
Example:
```javascript
var data = new Dict({
x: 10,
y: 30
});
data.values(); // [10, 30]
```
utils.defineProperty @::, 'values', NOT_ENUMERABLE, ->
if @_dirty & VALUES
@_dirty ^= VALUES
arr = @_values ?= []
i = 0
for key, val of @
arr[i] = val
i++
arr.length = i
@_values
## *Array* Dict::items()
Returns an array of key-value pairs stored in this *Dict*.
Returned array is *read only* and cannot be modified.
Always returns the same array instance.
Example:
```javascript
var data = new Dict({
x: 10,
y: 30
});
data.items(); // [['x', 10], ['y', 30]]
```
utils.defineProperty @::, 'items', NOT_ENUMERABLE, ->
if @_dirty & ITEMS
arr = @_values ?= []
i = 0
for key, val of @
arr[i] ?= ['', null]
arr[i][0] = key
arr[i][1] = val
i++
arr.length = i
@_values