alchemymvc 1.2.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
- var fileCache = alchemy.shared('files.fileCache'),
2
- libstream = alchemy.use('stream'),
1
+ const FILECACHE = alchemy.getCache('served_files'),
2
+ RX_TEXT = /svg|xml|javascript|text/i;
3
+
4
+ var libstream = alchemy.use('stream'),
3
5
  libpath = alchemy.use('path'),
4
- libmime = alchemy.use('mime'),
5
6
  libua = alchemy.use('useragent'),
6
7
  zlib = alchemy.use('zlib'),
7
8
  BODY = Symbol('body'),
@@ -345,7 +346,7 @@ Conduit.setMethod(function setRequestFiles(files) {
345
346
  *
346
347
  * @author Jelle De Loecker <jelle@elevenways.be>
347
348
  * @since 1.2.0
348
- * @version 1.2.0
349
+ * @version 1.3.0
349
350
  *
350
351
  * @param {Conduit} conduit
351
352
  * @param {Array} files
@@ -370,7 +371,7 @@ function _setRequestFiles(conduit, files, target) {
370
371
 
371
372
  _setRequestFiles(conduit, entry, context);
372
373
  } else {
373
- target[key] = Classes.Alchemy.Inode.File.from(entry);
374
+ target[key] = Classes.Alchemy.Inode.File.fromUntrusted(entry);
374
375
  }
375
376
  }
376
377
  }
@@ -980,7 +981,7 @@ Conduit.setMethod(function getRouteByName(name) {
980
981
  *
981
982
  * @author Jelle De Loecker <jelle@develry.be>
982
983
  * @since 0.2.0
983
- * @version 1.1.7
984
+ * @version 1.3.0
984
985
  *
985
986
  * @param {Route} after_route Only check routes after this one
986
987
  *
@@ -1009,7 +1010,7 @@ Conduit.setMethod(async function parseRoute(after_route) {
1009
1010
 
1010
1011
  if (temp) {
1011
1012
  this.route = temp.route;
1012
- this.params = temp.parameters;
1013
+ this.setRouteParameters(temp.parameters);
1013
1014
  this.route_string_parameters = temp.original_parameters;
1014
1015
  this.path_definition = temp.definition;
1015
1016
  } else {
@@ -1048,11 +1049,11 @@ Conduit.setMethod(async function parseRoute(after_route) {
1048
1049
  }
1049
1050
 
1050
1051
  if (temp) {
1051
- this.params = temp.parameters || {};
1052
+ this.setRouteParameters(temp.parameters);
1052
1053
  this.route_string_parameters = temp.original_parameters || {};
1053
1054
  this.path_definition = temp.definition;
1054
1055
  } else {
1055
- this.params = {};
1056
+ this.setRouteParameters();
1056
1057
  }
1057
1058
  }
1058
1059
  });
@@ -1287,20 +1288,46 @@ Conduit.setMethod(function postpone(options) {
1287
1288
  /**
1288
1289
  * Set the response url
1289
1290
  *
1291
+ * @deprecated Use {@link #setResponseUrl} instead
1292
+ *
1290
1293
  * @author Jelle De Loecker <jelle@develry.be>
1291
1294
  * @since 1.2.5
1292
- * @version 1.2.5
1295
+ * @version 1.3.0
1293
1296
  *
1294
1297
  * @param {String|RURL} url
1295
1298
  */
1296
1299
  Conduit.setMethod(function overrideResponseUrl(url) {
1300
+ return this.setResponseUrl(url);
1301
+ });
1297
1302
 
1298
- if (typeof url != 'string') {
1299
- url = String(url);
1303
+ /**
1304
+ * Set the response url
1305
+ *
1306
+ * @author Jelle De Loecker <jelle@elevenways.be>
1307
+ * @since 1.3.0
1308
+ * @version 1.3.0
1309
+ *
1310
+ * @param {String|RURL|Boolean} new_url
1311
+ */
1312
+ Conduit.setMethod(function setResponseUrl(new_url) {
1313
+
1314
+ if (new_url == null) {
1315
+ return;
1316
+ }
1317
+
1318
+ if (!new_url) {
1319
+ this.renderer.history = false;
1320
+ return;
1321
+ } else {
1322
+ this.renderer.history = true;
1300
1323
  }
1301
1324
 
1302
- this.setHeader('x-history-url', url);
1303
- this.expose('redirected_to', url);
1325
+ if (typeof new_url != 'string') {
1326
+ new_url = String(new_url);
1327
+ }
1328
+
1329
+ this.setHeader('x-history-url', new_url);
1330
+ this.expose('redirected_to', new_url);
1304
1331
  });
1305
1332
 
1306
1333
  /**
@@ -1583,9 +1610,9 @@ Conduit.setMethod(function notModified() {
1583
1610
  /**
1584
1611
  * Respond with text. Objects get JSON-dry encoded
1585
1612
  *
1586
- * @author Jelle De Loecker <jelle@develry.be>
1613
+ * @author Jelle De Loecker <jelle@elevenways.be>
1587
1614
  * @since 0.2.0
1588
- * @version 1.1.0
1615
+ * @version 1.3.0
1589
1616
  *
1590
1617
  * @param {String|Object} message
1591
1618
  */
@@ -1659,7 +1686,7 @@ Conduit.setMethod(function end(message) {
1659
1686
 
1660
1687
  // Compress the output if the client accepts it,
1661
1688
  // but only if the file is at least 150 bytes
1662
- if (alchemy.settings.compression && message.length > 150 && this.accepts('gzip')) {
1689
+ if (alchemy.settings.compression !== false && message.length > 150 && this.accepts('gzip')) {
1663
1690
 
1664
1691
  // Set the decompressed content-length for use in progress bars
1665
1692
  this.setHeader('x-decompressed-content-length', Buffer.byteLength(message));
@@ -1865,287 +1892,229 @@ function bufferToStream(buffer) {
1865
1892
  }
1866
1893
 
1867
1894
  /**
1868
- * Send a file to the browser.
1869
- * Uses cache-control by default.
1895
+ * Send a file to the browser
1870
1896
  *
1871
- * @author Jelle De Loecker <jelle@develry.be>
1897
+ * @author Jelle De Loecker <jelle@elevenways.be>
1872
1898
  * @since 0.2.0
1873
- * @version 1.1.0
1899
+ * @version 1.3.0
1874
1900
  *
1875
1901
  * @param {String} path The path on the server to send to the browser
1876
1902
  * @param {Object} options Options, including headers
1877
1903
  */
1878
- Conduit.setMethod(function serveFile(path, options) {
1904
+ Conduit.setTypedMethod([Types.String, Types.Object.optional()], function serveFile(path, options = {}) {
1879
1905
 
1880
- var that = this,
1881
- tasks = [],
1882
- stats,
1883
- isStream;
1884
-
1885
- // Create an options object if it doesn't exist yet
1886
- if (options == null) {
1887
- options = {};
1888
- }
1906
+ let file = FILECACHE.get(path);
1889
1907
 
1890
- // Error handling function
1891
- if (!options.onError) {
1892
- options.onError = function onError(err) {
1893
- that.notFound(err);
1894
- };
1908
+ if (!file) {
1909
+ file = new Classes.Alchemy.Inode.File(path);
1895
1910
  }
1896
1911
 
1897
- // See if we have a stats object
1898
- if (Object.isObject(path)) {
1899
-
1900
- if (Buffer.isBuffer(path)) {
1901
- path = bufferToStream(path);
1902
- }
1912
+ return this.serveFile(file, options);
1913
+ });
1903
1914
 
1904
- if (path.readable) {
1905
- isStream = true;
1906
- stats = {
1907
- mimetype: 'application/octet-stream'
1908
- };
1909
- } else {
1910
- stats = path;
1911
- }
1912
- } else {
1913
- stats = fileCache[path];
1915
+ /**
1916
+ * Send a file to the browser
1917
+ *
1918
+ * @author Jelle De Loecker <jelle@elevenways.be>
1919
+ * @since 0.2.0
1920
+ * @version 1.3.0
1921
+ *
1922
+ * @param {Alchemy.Inode.File} file The file to serve
1923
+ * @param {Object} options Options, including headers
1924
+ */
1925
+ Conduit.setTypedMethod([Types.Alchemy.Inode.File, Types.Object.optional()], async function serveFile(file, options = {}) {
1914
1926
 
1915
- if (stats == null) {
1916
- stats = {
1917
- path: path
1918
- };
1919
- }
1927
+ if (file.path && !FILECACHE.has(file.path)) {
1928
+ FILECACHE.set(file.path, file);
1929
+ }
1930
+
1931
+ if (alchemy.settings.cache && (!options.cache_time && options.cache_time !== false)) {
1932
+ let stats = await file.getStats();
1933
+ options.cache_time = stats.mtime;
1920
1934
  }
1921
1935
 
1922
- // Don't check for file information when it's a stream
1923
- if (!isStream) {
1924
-
1925
- if (!stats.path) {
1926
- return options.onError(new Error('No file to serve'));
1927
- }
1928
-
1929
- // Make sure the stats object is in the cache
1930
- if (fileCache[stats.path] == null) {
1931
- fileCache[stats.path] = stats;
1932
- }
1933
-
1934
- // Get file stats if it isn't available yet
1935
- if (stats.mtime == null) {
1936
- tasks.push(function getFileStats(next) {
1937
-
1938
- fs.stat(stats.path, function gotStats(err, fileStats) {
1939
-
1940
- if (err) {
1941
- stats.err = err;
1942
- stats.mtime = new Date();
1943
- } else {
1944
- Object.assign(stats, fileStats);
1945
- }
1946
-
1947
- next();
1948
- });
1949
- });
1950
- }
1951
-
1952
- // Get the mimetype if it isn't available yet
1953
- if (!options.mimetype && stats.mimetype == null) {
1954
- tasks.push(function getMimetype(next) {
1955
-
1956
- // Don't use libmime if it isn't loaded,
1957
- // that could be the case on NW.js
1958
- if (!libmime) {
1959
- return next();
1960
- }
1961
-
1962
- // Lookup the mimetype by the extension alone
1963
- stats.mimetype = libmime.getType(stats.path);
1964
-
1965
- // Return the result if a valid mimetype was found
1966
- if (stats.mimetype !== 'application/octet-stream') {
1967
- return next();
1968
- }
1936
+ if (!options.mimetype) {
1937
+ options.mimetype = await file.getMimetype();
1938
+ }
1969
1939
 
1970
- // If no mimetype was found,
1971
- // see if we can find it using the original path (for resized images)
1972
- if (options.original_path) {
1973
- stats.mimetype = libmime.getType(options.original_path);
1940
+ if (!options.filename) {
1941
+ options.filename = file.name;
1942
+ }
1943
+
1944
+ return this.serveFile(file.createReadStream(), options);
1945
+ });
1974
1946
 
1975
- if (stats.mimetype !== 'application/octet-stream') {
1976
- return next();
1977
- }
1978
- }
1947
+ /**
1948
+ * Send a buffer to the client
1949
+ *
1950
+ * @author Jelle De Loecker <jelle@elevenways.be>
1951
+ * @since 1.3.0
1952
+ * @version 1.3.0
1953
+ *
1954
+ * @param {Stream} buffer The buffer to send
1955
+ * @param {Object} options Options, including headers
1956
+ */
1957
+ Conduit.setTypedMethod([Buffer, Types.Object.optional()], function serveFile(buffer, options = {}) {
1958
+ return this.serveFile(bufferToStream(buffer), options);
1959
+ });
1979
1960
 
1980
- // "magic" currently doesn't work in nw.js
1981
- if (Blast.isNW) {
1982
- return next();
1983
- }
1961
+ /**
1962
+ * Send a stream to the client
1963
+ *
1964
+ * @author Jelle De Loecker <jelle@elevenways.be>
1965
+ * @since 1.3.0
1966
+ * @version 1.3.0
1967
+ *
1968
+ * @param {Stream} stream The stream to send
1969
+ * @param {Object} options Options, including headers
1970
+ */
1971
+ Conduit.setTypedMethod([Types.Stream, Types.Object.optional()], function serveFile(stream, options = {}) {
1984
1972
 
1985
- // Don't try to use magic if it's not loaded
1986
- if (!getMagic()) {
1987
- return next();
1988
- }
1973
+ let is_text = false;
1989
1974
 
1990
- // Look inside the data (using "magic") for a better mimetype
1991
- magic.detectFile(stats.path, function detectedMimetype(err, result) {
1975
+ if (options.mimetype && RX_TEXT.test(options.mimetype)) {
1976
+ options.mimetype += '; charset=utf-8';
1977
+ is_text = true;
1978
+ }
1992
1979
 
1993
- if (!err) {
1994
- stats.mimetype = result;
1995
- }
1980
+ if (alchemy.settings.cache === false) {
1981
+ options.cache_time = null;
1982
+ }
1996
1983
 
1997
- next();
1998
- });
1999
- });
2000
- }
1984
+ if (options.compress == null) {
1985
+ options.compress = is_text;
2001
1986
  }
2002
1987
 
2003
- Function.parallel(tasks, function gotFileInfo(err) {
1988
+ if (options.compress && (alchemy.settings.compression === false || !this.accepts('gzip'))) {
1989
+ options.compress = false;
1990
+ }
2004
1991
 
2005
- var disposition,
2006
- outStream,
2007
- mimetype,
2008
- headers,
2009
- isText,
2010
- since,
2011
- key;
1992
+ if (options.cache_time && !alchemy.settings.cache) {
1993
+ options.cache_time = false;
1994
+ }
2012
1995
 
2013
- if (err) {
2014
- return that.error(err);
2015
- }
1996
+ if (!options.onError) {
1997
+ options.onError = err => this.notFound(err);
1998
+ }
2016
1999
 
2017
- if (stats.err) {
2018
- return options.onError(stats.err);
2019
- }
2000
+ return this._sendStream(stream, options);
2001
+ });
2020
2002
 
2021
- if (!isStream && !stats.path) {
2022
- return options.onError(new Error('File not found'));
2023
- }
2003
+ /**
2004
+ * Send a stream to the client
2005
+ *
2006
+ * @author Jelle De Loecker <jelle@elevenways.be>
2007
+ * @since 1.3.0
2008
+ * @version 1.3.0
2009
+ *
2010
+ * @param {Stream} stream The stream to send
2011
+ * @param {Object} options Options, including headers
2012
+ */
2013
+ Conduit.setMethod(function _sendStream(stream, options) {
2024
2014
 
2025
- // Check the if-modified-since header if it's supplied
2026
- if (alchemy.settings.cache !== false && that.headers['if-modified-since'] != null) {
2015
+ if (options.cache_time) {
2016
+ let modified_since = this.headers['if-modified-since'];
2027
2017
 
2028
- // Turn the string into a date
2029
- since = new Date(that.headers['if-modified-since']);
2018
+ if (modified_since != null) {
2019
+ let since = new Date(modified_since);
2030
2020
 
2031
2021
  // If the file's modifytime is smaller or equal to the since time,
2032
2022
  // don't serve the contents!
2033
- if (stats.mtime <= since) {
2034
- return that.notModified();
2035
- }
2036
- }
2037
-
2038
- mimetype = stats.mimetype;
2039
-
2040
- // If we get a general mimetype, and an alternative is provided, use that one
2041
- if (!mimetype || mimetype === 'application/octet-stream') {
2042
- if (options.mimetype != null) {
2043
- mimetype = options.mimetype;
2023
+ if (options.cache_time <= since) {
2024
+ return this.notModified();
2044
2025
  }
2045
2026
  }
2046
2027
 
2047
- isText = /svg|xml|javascript|text/.test(mimetype);
2048
-
2049
- // Serve text files as utf-8
2050
- if (isText) {
2051
- mimetype += '; charset=utf-8';
2052
- }
2053
-
2054
- that.setHeader('content-type', mimetype);
2055
-
2056
- // Setting the disposition makes the browser download the file
2057
- // This is on by default, but can be disabled
2058
- if (options.disposition == 'inline') {
2059
- disposition = 'inline';
2060
-
2061
- if (options.filename) {
2062
- disposition += '; filename=' + JSON.stringify(options.filename)
2063
- }
2064
-
2065
- that.setHeader('content-disposition', disposition);
2066
- } else if (options.disposition !== false) {
2067
- if (options.filename) {
2068
- disposition = 'attachment; filename=' + JSON.stringify(options.filename);
2069
- } else {
2070
- disposition = 'attachment';
2071
- }
2028
+ // Allow the browser to cache this for 60 minutes,
2029
+ // after which it has to revalidate the content
2030
+ // by seeing if it has been modified
2031
+ this.setHeader('cache-control', 'public, max-age=3600, must-revalidate');
2032
+ this.setHeader('last-modified', options.cache_time.toGMTString());
2033
+ } else {
2034
+ this.setHeader('cache-control', 'no-cache');
2035
+ }
2072
2036
 
2073
- that.setHeader('content-disposition', disposition);
2074
- }
2037
+ let disposition,
2038
+ key;
2075
2039
 
2076
- if (stats.mtime && alchemy.settings.cache) {
2077
- // Allow the browser to cache this for 60 minutes,
2078
- // after which it has to revalidate the content
2079
- // by seeing if it has been modified
2080
- that.setHeader('cache-control', 'public, max-age=3600, must-revalidate');
2081
- that.setHeader('last-modified', stats.mtime.toGMTString());
2082
- } else if (!alchemy.settings.cache) {
2083
- that.setHeader('cache-control', 'no-cache');
2084
- }
2040
+ if (options.mimetype) {
2041
+ this.setHeader('content-type', options.mimetype);
2042
+ }
2085
2043
 
2086
- for (key in options.headers) {
2087
- that.setHeader(key, options.headers[key]);
2088
- }
2044
+ // Setting the disposition makes the browser download the file
2045
+ // This is on by default, but can be disabled
2046
+ if (options.disposition == 'inline') {
2047
+ disposition = 'inline';
2089
2048
 
2090
- // End now if it's just a HEAD request
2091
- if (that.method == 'head') {
2092
- return that.end();
2049
+ if (options.filename) {
2050
+ disposition += '; filename=' + JSON.stringify(options.filename)
2093
2051
  }
2094
2052
 
2095
- if (isStream) {
2096
- outStream = path;
2053
+ this.setHeader('content-disposition', disposition);
2054
+ } else if (options.disposition !== false) {
2055
+ if (options.filename) {
2056
+ disposition = 'attachment; filename=' + JSON.stringify(options.filename);
2097
2057
  } else {
2098
- outStream = fs.createReadStream(path, {bufferSize: 64*1024});
2099
-
2100
- // Listen for file errors
2101
- outStream.on('error', options.onError);
2058
+ disposition = 'attachment';
2102
2059
  }
2103
2060
 
2104
- // Compress text responses
2105
- if (isText && alchemy.settings.compression && that.accepts('gzip')) {
2061
+ this.setHeader('content-disposition', disposition);
2062
+ }
2106
2063
 
2107
- // Set the gzip header
2108
- that.setHeader('content-encoding', 'gzip');
2109
- that.setHeader('vary', 'accept-encoding');
2064
+ // Set all the headers
2065
+ for (key in options.headers) {
2066
+ this.setHeader(key, options.headers[key]);
2067
+ }
2110
2068
 
2111
- // Create the gzip stream
2112
- outStream = outStream.pipe(zlib.createGzip());
2113
- }
2069
+ // Don't send anything if it's a HEAD request
2070
+ if (this.method == 'head') {
2071
+ return this.end();
2072
+ }
2114
2073
 
2115
- // If we received a stream as parameter...
2116
- if (isStream) {
2117
- that.response.on('end', cleanup);
2118
- that.response.on('finish', cleanup);
2119
- that.response.on('error', cleanup);
2120
- that.response.on('close', cleanup);
2121
- }
2074
+ const response = this.response;
2075
+ let out_stream = stream;
2076
+
2077
+ if (options.compress) {
2078
+ // Set the gzip header
2079
+ this.setHeader('content-encoding', 'gzip');
2080
+ this.setHeader('vary', 'accept-encoding');
2122
2081
 
2123
- function cleanup() {
2082
+ // Create the gzip stream
2083
+ out_stream = out_stream.pipe(zlib.createGzip());
2084
+ }
2124
2085
 
2086
+ if (options.cleanup_stream) {
2087
+ const cleanup = function cleanupOriginalStream() {
2125
2088
  // Remove all pipes
2126
- outStream.unpipe();
2089
+ stream.unpipe();
2127
2090
 
2128
- if (outStream.destroy) {
2129
- outStream.destroy();
2130
- } else if (outStream.end) {
2131
- outStream.end();
2091
+ if (stream.destroy) {
2092
+ stream.destroy();
2093
+ } else if (stream.end) {
2094
+ stream.end();
2132
2095
  }
2133
- }
2096
+ };
2134
2097
 
2135
- // Send the headers
2136
- for (key in that.response_headers) {
2137
- that.response.setHeader(key, that.response_headers[key]);
2138
- }
2098
+ response.on('end', cleanup);
2099
+ response.on('finish', cleanup);
2100
+ response.on('error', cleanup);
2101
+ response.on('close', cleanup);
2102
+ }
2139
2103
 
2140
- if (that.new_cookie_header.length) {
2141
- that.response.setHeader('set-cookie', that.new_cookie_header);
2142
- }
2104
+ // Set the response headers
2105
+ for (key in this.response_headers) {
2106
+ response.setHeader(key, this.response_headers[key]);
2107
+ }
2143
2108
 
2144
- that.response.statusCode = 200;
2109
+ if (this.new_cookie_header.length) {
2110
+ response.setHeader('set-cookie', this.new_cookie_header);
2111
+ }
2145
2112
 
2146
- // Stream the file to the client
2147
- outStream.pipe(that.response);
2148
- });
2113
+ // If we got this far, the file has been found!
2114
+ response.statusCode = 200;
2115
+
2116
+ // Actually stream the contents to the client
2117
+ out_stream.pipe(response);
2149
2118
  });
2150
2119
 
2151
2120
  /**
@@ -2307,6 +2276,24 @@ Conduit.setMethod(function routeParam(name) {
2307
2276
  return this.params[name];
2308
2277
  });
2309
2278
 
2279
+ /**
2280
+ * Set route parameters
2281
+ *
2282
+ * @author Jelle De Loecker <jelle@elevenways.be>
2283
+ * @since 1.3.0
2284
+ * @version 1.3.0
2285
+ */
2286
+ Conduit.setMethod(function setRouteParameters(data) {
2287
+
2288
+ if (!this.params) {
2289
+ this.params = {};
2290
+ }
2291
+
2292
+ if (data) {
2293
+ Object.assign(this.params, data);
2294
+ }
2295
+ });
2296
+
2310
2297
  /**
2311
2298
  * Get/set a cookie
2312
2299
  *
@@ -2370,7 +2357,7 @@ Conduit.setMethod(function cookie(name, value, options) {
2370
2357
  *
2371
2358
  * @author Jelle De Loecker <jelle@develry.be>
2372
2359
  * @since 0.2.0
2373
- * @version 1.1.0
2360
+ * @version 1.3.0
2374
2361
  *
2375
2362
  * @param {String} name
2376
2363
  * @param {Mixed} value
@@ -2382,7 +2369,7 @@ Conduit.setMethod(function setHeader(name, value) {
2382
2369
  }
2383
2370
 
2384
2371
  if (this.websocket) {
2385
- throw new Error("Can't set a header on a websocket connection");
2372
+ throw new Error("Can't set header `" + name + "` on a websocket connection");
2386
2373
  }
2387
2374
 
2388
2375
  this.response_headers[name] = value;
@@ -165,7 +165,7 @@ Datasource.setMethod(function getSchema(schema) {
165
165
  *
166
166
  * @author Jelle De Loecker <jelle@develry.be>
167
167
  * @since 0.2.0
168
- * @version 1.2.0
168
+ * @version 1.3.0
169
169
  *
170
170
  * @param {Schema|Model} schema
171
171
  * @param {Object} data
@@ -187,7 +187,11 @@ Datasource.setMethod(function toDatasource(schema, data, callback) {
187
187
  }
188
188
 
189
189
  if (!schema) {
190
- log.todo('Schema not found: not normalizing data', data);
190
+
191
+ if (alchemy.settings.debug) {
192
+ log.todo('Schema not found: not normalizing data', data);
193
+ }
194
+
191
195
  pledge = Pledge.resolve(data);
192
196
  pledge.done(callback);
193
197
  return pledge;
@@ -647,7 +647,7 @@ Document.setMethod(['populate', 'addAssociatedData'], function addAssociatedData
647
647
  *
648
648
  * @author Jelle De Loecker <jelle@develry.be>
649
649
  * @since 0.3.0
650
- * @version 0.3.0
650
+ * @version 1.3.0
651
651
  *
652
652
  * @param {Object} options
653
653
  *
@@ -663,7 +663,7 @@ Document.setMethod(function getDisplayFieldValue(options) {
663
663
  options = {};
664
664
  }
665
665
 
666
- display_field = Array.cast(this.$model.displayField);
666
+ display_field = Array.cast(this.$model.display_field);
667
667
 
668
668
  // If there are fields we prefer, check those first
669
669
  if (options.prefer) {
@@ -674,7 +674,7 @@ Document.setMethod(function getDisplayFieldValue(options) {
674
674
  result = this[display_field[i]];
675
675
 
676
676
  if (result) {
677
- result = alchemy.pickTranslation(undefined, result).result;
677
+ result = alchemy.pickTranslation(options.prefix, result).result;
678
678
 
679
679
  if (result) {
680
680
  return result;